plugins: add missing compatible plugins
This commit is contained in:
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, TheLonelyDev <https://github.com/TheLonelyDev>
|
||||
* Copyright (c) 2018, Jeremy Plsek <https://github.com/jplsek>
|
||||
* 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.bank;
|
||||
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
import net.runelite.client.config.Keybind;
|
||||
|
||||
@ConfigGroup("bank")
|
||||
public interface BankConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
keyName = "showGE",
|
||||
name = "Show Grand Exchange price",
|
||||
description = "Show grand exchange price total (GE)",
|
||||
position = 1
|
||||
)
|
||||
default boolean showGE()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showHA",
|
||||
name = "Show high alchemy price",
|
||||
description = "Show high alchemy price total (HA)",
|
||||
position = 2
|
||||
)
|
||||
default boolean showHA()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showExact",
|
||||
name = "Show exact bank value",
|
||||
description = "Show exact bank value",
|
||||
position = 3
|
||||
)
|
||||
default boolean showExact()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "rightClickBankInventory",
|
||||
name = "Disable left click bank inventory",
|
||||
description = "Configures whether the bank inventory button will bank your inventory on left click",
|
||||
position = 4
|
||||
)
|
||||
default boolean rightClickBankInventory()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "rightClickBankEquip",
|
||||
name = "Disable left click bank equipment",
|
||||
description = "Configures whether the bank equipment button will bank your equipment on left click",
|
||||
position = 5
|
||||
)
|
||||
default boolean rightClickBankEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "rightClickBankLoot",
|
||||
name = "Disable left click bank looting bag",
|
||||
description = "Configures whether the bank looting bag button will bank your looting bag contents on left click",
|
||||
position = 6
|
||||
)
|
||||
default boolean rightClickBankLoot()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "seedVaultValue",
|
||||
name = "Show seed vault value",
|
||||
description = "Adds the total value of all seeds inside the seed vault to the title",
|
||||
position = 7
|
||||
)
|
||||
default boolean seedVaultValue()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "bankPinKeyboard",
|
||||
name = "Keyboard Bankpin",
|
||||
description = "Allows using the keyboard keys for bank pin input",
|
||||
position = 8
|
||||
)
|
||||
default boolean bankPinKeyboard()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "searchKeybind",
|
||||
name = "Search Shortcut",
|
||||
description = "Keyboard shortcut for initiating a bank search",
|
||||
position = 9
|
||||
)
|
||||
default Keybind searchKeybind()
|
||||
{
|
||||
return new Keybind(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK);
|
||||
}
|
||||
}
|
||||
@@ -1,546 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, TheLonelyDev <https://github.com/TheLonelyDev>
|
||||
* Copyright (c) 2018, Jeremy Plsek <https://github.com/jplsek>
|
||||
* Copyright (c) 2019, Hydrox6 <ikada@protonmail.ch>
|
||||
* 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.bank;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.text.ParseException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.InventoryID;
|
||||
import net.runelite.api.Item;
|
||||
import net.runelite.api.ItemComposition;
|
||||
import net.runelite.api.ItemContainer;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.MenuEntry;
|
||||
import net.runelite.api.ScriptID;
|
||||
import net.runelite.api.VarClientStr;
|
||||
import net.runelite.api.events.ItemContainerChanged;
|
||||
import net.runelite.api.events.MenuEntryAdded;
|
||||
import net.runelite.api.events.MenuShouldLeftClick;
|
||||
import net.runelite.api.events.ScriptCallbackEvent;
|
||||
import net.runelite.api.events.ScriptPostFired;
|
||||
import net.runelite.api.events.WidgetLoaded;
|
||||
import net.runelite.api.widgets.JavaScriptCallback;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetID;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import net.runelite.client.callback.ClientThread;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.config.Keybind;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
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.QuantityFormatter;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Bank",
|
||||
description = "Modifications to the banking interface",
|
||||
tags = {"grand", "exchange", "high", "alchemy", "prices", "deposit"}
|
||||
)
|
||||
@Slf4j
|
||||
public class BankPlugin extends Plugin
|
||||
{
|
||||
private static final String DEPOSIT_WORN = "Deposit worn items";
|
||||
private static final String DEPOSIT_INVENTORY = "Deposit inventory";
|
||||
private static final String DEPOSIT_LOOT = "Deposit loot";
|
||||
private static final String SEED_VAULT_TITLE = "Seed Vault";
|
||||
|
||||
private static final String NUMBER_REGEX = "[0-9]+(\\.[0-9]+)?[kmb]?";
|
||||
private static final Pattern VALUE_SEARCH_PATTERN = Pattern.compile("^(?<mode>ge|ha|alch)?" +
|
||||
" *(((?<op>[<>=]|>=|<=) *(?<num>" + NUMBER_REGEX + "))|" +
|
||||
"((?<num1>" + NUMBER_REGEX + ") *- *(?<num2>" + NUMBER_REGEX + ")))$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private ItemManager itemManager;
|
||||
|
||||
@Inject
|
||||
private BankConfig config;
|
||||
|
||||
@Inject
|
||||
private BankSearch bankSearch;
|
||||
|
||||
@Inject
|
||||
private KeyManager keyManager;
|
||||
|
||||
private boolean forceRightClickFlag;
|
||||
private Multiset<Integer> itemQuantities; // bank item quantities for bank value search
|
||||
private String searchString;
|
||||
|
||||
private final KeyListener searchHotkeyListener = new KeyListener()
|
||||
{
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e)
|
||||
{
|
||||
Keybind keybind = config.searchKeybind();
|
||||
if (keybind.matches(e))
|
||||
{
|
||||
Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER);
|
||||
if (bankContainer == null || bankContainer.isSelfHidden())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Search hotkey pressed");
|
||||
|
||||
bankSearch.initSearch();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@Provides
|
||||
BankConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(BankConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
keyManager.registerKeyListener(searchHotkeyListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
keyManager.unregisterKeyListener(searchHotkeyListener);
|
||||
clientThread.invokeLater(() -> bankSearch.reset(false));
|
||||
forceRightClickFlag = false;
|
||||
itemQuantities = null;
|
||||
searchString = null;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuShouldLeftClick(MenuShouldLeftClick event)
|
||||
{
|
||||
if (!forceRightClickFlag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
forceRightClickFlag = false;
|
||||
MenuEntry[] menuEntries = client.getMenuEntries();
|
||||
for (MenuEntry entry : menuEntries)
|
||||
{
|
||||
if ((entry.getOption().equals(DEPOSIT_WORN) && config.rightClickBankEquip())
|
||||
|| (entry.getOption().equals(DEPOSIT_INVENTORY) && config.rightClickBankInventory())
|
||||
|| (entry.getOption().equals(DEPOSIT_LOOT) && config.rightClickBankLoot()))
|
||||
{
|
||||
event.setForceRightClick(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuEntryAdded(MenuEntryAdded event)
|
||||
{
|
||||
if ((event.getOption().equals(DEPOSIT_WORN) && config.rightClickBankEquip())
|
||||
|| (event.getOption().equals(DEPOSIT_INVENTORY) && config.rightClickBankInventory())
|
||||
|| (event.getOption().equals(DEPOSIT_LOOT) && config.rightClickBankLoot()))
|
||||
{
|
||||
forceRightClickFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptCallbackEvent(ScriptCallbackEvent event)
|
||||
{
|
||||
int[] intStack = client.getIntStack();
|
||||
String[] stringStack = client.getStringStack();
|
||||
int intStackSize = client.getIntStackSize();
|
||||
int stringStackSize = client.getStringStackSize();
|
||||
|
||||
switch (event.getEventName())
|
||||
{
|
||||
case "bankSearchFilter":
|
||||
int itemId = intStack[intStackSize - 1];
|
||||
String search = stringStack[stringStackSize - 1];
|
||||
|
||||
if (valueSearch(itemId, search))
|
||||
{
|
||||
// return true
|
||||
intStack[intStackSize - 2] = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
case "bankpinButtonSetup":
|
||||
{
|
||||
if (!config.bankPinKeyboard())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final int compId = intStack[intStackSize - 2];
|
||||
final int buttonId = intStack[intStackSize - 1];
|
||||
//TODO Implement client.getWidget(compId)
|
||||
Widget button = null;
|
||||
Widget buttonRect = button.getChild(0);
|
||||
|
||||
final Object[] onOpListener = buttonRect.getOnOpListener();
|
||||
buttonRect.setOnKeyListener((JavaScriptCallback) e ->
|
||||
{
|
||||
int typedChar = e.getTypedKeyChar() - '0';
|
||||
if (typedChar != buttonId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Bank pin keypress");
|
||||
|
||||
final String input = client.getVar(VarClientStr.CHATBOX_TYPED_TEXT);
|
||||
clientThread.invokeLater(() ->
|
||||
{
|
||||
// reset chatbox input to avoid pin going to chatbox..
|
||||
client.setVar(VarClientStr.CHATBOX_TYPED_TEXT, input);
|
||||
client.runScript(ScriptID.CHAT_PROMPT_INIT);
|
||||
|
||||
client.runScript(onOpListener);
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onWidgetLoaded(WidgetLoaded event)
|
||||
{
|
||||
if (event.getGroupId() != WidgetID.SEED_VAULT_GROUP_ID || !config.seedVaultValue())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
updateSeedVaultTotal();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptPostFired(ScriptPostFired event)
|
||||
{
|
||||
if (event.getScriptId() == ScriptID.BANKMAIN_BUILD)
|
||||
{
|
||||
// Compute bank prices using only the shown items so that we can show bank value during searches
|
||||
final Widget bankItemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER);
|
||||
final ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK);
|
||||
final Widget[] children = bankItemContainer.getChildren();
|
||||
long geTotal = 0, haTotal = 0;
|
||||
|
||||
if (children != null)
|
||||
{
|
||||
log.debug("Computing bank price of {} items", bankContainer.size());
|
||||
|
||||
// The first components are the bank items, followed by tabs etc. There are always 816 components regardless
|
||||
// of bank size, but we only need to check up to the bank size.
|
||||
for (int i = 0; i < bankContainer.size(); ++i)
|
||||
{
|
||||
Widget child = children[i];
|
||||
if (child != null && !child.isSelfHidden() && child.getItemId() > -1)
|
||||
{
|
||||
final int alchPrice = getHaPrice(child.getItemId());
|
||||
geTotal += (long) itemManager.getItemPrice(child.getItemId()) * child.getItemQuantity();
|
||||
haTotal += (long) alchPrice * child.getItemQuantity();
|
||||
}
|
||||
}
|
||||
|
||||
Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR);
|
||||
bankTitle.setText(bankTitle.getText() + createValueText(geTotal, haTotal));
|
||||
}
|
||||
}
|
||||
else if (event.getScriptId() == ScriptID.BANKMAIN_SEARCH_REFRESH)
|
||||
{
|
||||
// vanilla only lays out the bank every 40 client ticks, so if the search input has changed,
|
||||
// and the bank wasn't laid out this tick, lay it out early
|
||||
final String inputText = client.getVar(VarClientStr.INPUT_TEXT);
|
||||
if (searchString != inputText && client.getGameCycle() % 40 != 0)
|
||||
{
|
||||
clientThread.invokeLater(bankSearch::layoutBank);
|
||||
searchString = inputText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onItemContainerChanged(ItemContainerChanged event)
|
||||
{
|
||||
int containerId = event.getContainerId();
|
||||
|
||||
if (containerId == InventoryID.BANK.getId())
|
||||
{
|
||||
itemQuantities = null;
|
||||
}
|
||||
else if (containerId == InventoryID.SEED_VAULT.getId() && config.seedVaultValue())
|
||||
{
|
||||
updateSeedVaultTotal();
|
||||
}
|
||||
}
|
||||
|
||||
private String createValueText(long gePrice, long haPrice)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (config.showGE() && gePrice != 0)
|
||||
{
|
||||
stringBuilder.append(" (");
|
||||
|
||||
if (config.showHA())
|
||||
{
|
||||
stringBuilder.append("GE: ");
|
||||
}
|
||||
|
||||
if (config.showExact())
|
||||
{
|
||||
stringBuilder.append(QuantityFormatter.formatNumber(gePrice));
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.append(QuantityFormatter.quantityToStackSize(gePrice));
|
||||
}
|
||||
stringBuilder.append(')');
|
||||
}
|
||||
|
||||
if (config.showHA() && haPrice != 0)
|
||||
{
|
||||
stringBuilder.append(" (");
|
||||
|
||||
if (config.showGE())
|
||||
{
|
||||
stringBuilder.append("HA: ");
|
||||
}
|
||||
|
||||
if (config.showExact())
|
||||
{
|
||||
stringBuilder.append(QuantityFormatter.formatNumber(haPrice));
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.append(QuantityFormatter.quantityToStackSize(haPrice));
|
||||
}
|
||||
stringBuilder.append(')');
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private void updateSeedVaultTotal()
|
||||
{
|
||||
final Widget titleContainer = client.getWidget(WidgetInfo.SEED_VAULT_TITLE_CONTAINER);
|
||||
if (titleContainer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final Widget title = titleContainer.getChild(1);
|
||||
if (title == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final ContainerPrices prices = calculate(getSeedVaultItems());
|
||||
if (prices == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final String titleText = createValueText(prices.getGePrice(), prices.getHighAlchPrice());
|
||||
title.setText(SEED_VAULT_TITLE + titleText);
|
||||
}
|
||||
|
||||
private Item[] getSeedVaultItems()
|
||||
{
|
||||
final ItemContainer itemContainer = client.getItemContainer(InventoryID.SEED_VAULT);
|
||||
if (itemContainer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return itemContainer.getItems();
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
boolean valueSearch(final int itemId, final String str)
|
||||
{
|
||||
final Matcher matcher = VALUE_SEARCH_PATTERN.matcher(str);
|
||||
if (!matcher.matches())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count bank items and remember it for determining item quantity
|
||||
if (itemQuantities == null)
|
||||
{
|
||||
itemQuantities = getBankItemSet();
|
||||
}
|
||||
|
||||
final ItemComposition itemComposition = itemManager.getItemComposition(itemId);
|
||||
final int qty = itemQuantities.count(itemId);
|
||||
final long gePrice = (long) itemManager.getItemPrice(itemId) * qty;
|
||||
final long haPrice = (long) itemComposition.getHaPrice() * qty;
|
||||
|
||||
long value = Math.max(gePrice, haPrice);
|
||||
|
||||
final String mode = matcher.group("mode");
|
||||
if (mode != null)
|
||||
{
|
||||
value = mode.toLowerCase().equals("ge") ? gePrice : haPrice;
|
||||
}
|
||||
|
||||
final String op = matcher.group("op");
|
||||
if (op != null)
|
||||
{
|
||||
long compare;
|
||||
try
|
||||
{
|
||||
compare = QuantityFormatter.parseQuantity(matcher.group("num"));
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case ">":
|
||||
return value > compare;
|
||||
case "<":
|
||||
return value < compare;
|
||||
case "=":
|
||||
return value == compare;
|
||||
case ">=":
|
||||
return value >= compare;
|
||||
case "<=":
|
||||
return value <= compare;
|
||||
}
|
||||
}
|
||||
|
||||
final String num1 = matcher.group("num1");
|
||||
final String num2 = matcher.group("num2");
|
||||
if (num1 != null && num2 != null)
|
||||
{
|
||||
long compare1, compare2;
|
||||
try
|
||||
{
|
||||
compare1 = QuantityFormatter.parseQuantity(num1);
|
||||
compare2 = QuantityFormatter.parseQuantity(num2);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return compare1 <= value && compare2 >= value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Multiset<Integer> getBankItemSet()
|
||||
{
|
||||
ItemContainer itemContainer = client.getItemContainer(InventoryID.BANK);
|
||||
if (itemContainer == null)
|
||||
{
|
||||
return HashMultiset.create();
|
||||
}
|
||||
|
||||
Multiset<Integer> set = HashMultiset.create();
|
||||
for (Item item : itemContainer.getItems())
|
||||
{
|
||||
if (item.getId() != ItemID.BANK_FILLER)
|
||||
{
|
||||
set.add(item.getId(), item.getQuantity());
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
ContainerPrices calculate(@Nullable Item[] items)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
long ge = 0;
|
||||
long alch = 0;
|
||||
|
||||
for (final Item item : items)
|
||||
{
|
||||
final int qty = item.getQuantity();
|
||||
final int id = item.getId();
|
||||
|
||||
if (id <= 0 || qty == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
alch += (long) getHaPrice(id) * qty;
|
||||
ge += (long) itemManager.getItemPrice(id) * qty;
|
||||
}
|
||||
|
||||
return new ContainerPrices(ge, alch);
|
||||
}
|
||||
|
||||
private int getHaPrice(int itemId)
|
||||
{
|
||||
switch (itemId)
|
||||
{
|
||||
case ItemID.COINS_995:
|
||||
return 1;
|
||||
case ItemID.PLATINUM_TOKEN:
|
||||
return 1000;
|
||||
default:
|
||||
return itemManager.getItemComposition(itemId).getHaPrice();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
|
||||
* 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.bank;
|
||||
|
||||
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.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import net.runelite.client.callback.ClientThread;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
@Singleton
|
||||
public class BankSearch
|
||||
{
|
||||
private final Client client;
|
||||
private final ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private BankSearch(
|
||||
final Client client,
|
||||
final ClientThread clientThread
|
||||
)
|
||||
{
|
||||
this.client = client;
|
||||
this.clientThread = clientThread;
|
||||
}
|
||||
|
||||
public void layoutBank()
|
||||
{
|
||||
Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER);
|
||||
if (bankContainer == null || bankContainer.isHidden())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Object[] scriptArgs = bankContainer.getOnInvTransmitListener();
|
||||
if (scriptArgs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
client.runScript(scriptArgs);
|
||||
}
|
||||
|
||||
public void initSearch()
|
||||
{
|
||||
clientThread.invoke(() ->
|
||||
{
|
||||
Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER);
|
||||
if (bankContainer == null || bankContainer.isHidden())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Object[] bankBuildArgs = bankContainer.getOnInvTransmitListener();
|
||||
if (bankBuildArgs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// the search toggle script requires 1 as its first argument
|
||||
Object[] searchToggleArgs = ArrayUtils.insert(1, bankBuildArgs, 1);
|
||||
searchToggleArgs[0] = ScriptID.BANKMAIN_SEARCH_TOGGLE;
|
||||
|
||||
// reset search to clear tab tags and also allow us to initiate a new search while searching
|
||||
reset(true);
|
||||
client.runScript(searchToggleArgs);
|
||||
});
|
||||
}
|
||||
|
||||
public void reset(boolean closeChat)
|
||||
{
|
||||
clientThread.invoke(() ->
|
||||
{
|
||||
// This ensures that any chatbox input (e.g from search) will not remain visible when
|
||||
// selecting/changing tab
|
||||
if (closeChat)
|
||||
{
|
||||
// this clears the input text and type, and resets the chatbox to allow input
|
||||
client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setVar(VarClientInt.INPUT_TYPE, InputType.NONE.getType());
|
||||
client.setVar(VarClientStr.INPUT_TEXT, "");
|
||||
}
|
||||
|
||||
layoutBank();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,598 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@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.banktags;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.Shorts;
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.InventoryID;
|
||||
import net.runelite.api.Item;
|
||||
import net.runelite.api.ItemComposition;
|
||||
import net.runelite.api.ItemContainer;
|
||||
import net.runelite.api.KeyCode;
|
||||
import net.runelite.api.MenuAction;
|
||||
import net.runelite.api.MenuEntry;
|
||||
import net.runelite.api.ScriptID;
|
||||
import net.runelite.api.SpriteID;
|
||||
import net.runelite.api.VarClientStr;
|
||||
import net.runelite.api.events.DraggingWidgetChanged;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.GrandExchangeSearched;
|
||||
import net.runelite.api.events.MenuEntryAdded;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
import net.runelite.api.events.ScriptCallbackEvent;
|
||||
import net.runelite.api.events.ScriptPostFired;
|
||||
import net.runelite.api.events.ScriptPreFired;
|
||||
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.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.game.ItemVariationMapping;
|
||||
import net.runelite.client.game.SpriteManager;
|
||||
import net.runelite.client.game.chatbox.ChatboxPanelManager;
|
||||
import net.runelite.client.input.MouseManager;
|
||||
import net.runelite.client.input.MouseWheelListener;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDependency;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.plugins.banktags.tabs.TabInterface;
|
||||
import static net.runelite.client.plugins.banktags.tabs.TabInterface.FILTERED_CHARS;
|
||||
import net.runelite.client.plugins.banktags.tabs.TabSprites;
|
||||
import net.runelite.client.plugins.banktags.tabs.TagTab;
|
||||
import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin;
|
||||
import net.runelite.client.util.Text;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Bank Tags",
|
||||
description = "Enable tagging of bank items and searching of bank tags",
|
||||
tags = {"searching", "tagging"}
|
||||
)
|
||||
@PluginDependency(ClueScrollPlugin.class)
|
||||
public class BankTagsPlugin extends Plugin implements MouseWheelListener
|
||||
{
|
||||
public static final String CONFIG_GROUP = "banktags";
|
||||
public static final String TAG_SEARCH = "tag:";
|
||||
private static final String EDIT_TAGS_MENU_OPTION = "Edit-tags";
|
||||
public static final String ICON_SEARCH = "icon_";
|
||||
public static final String TAG_TABS_CONFIG = "tagtabs";
|
||||
public static final String VAR_TAG_SUFFIX = "*";
|
||||
private static final int ITEMS_PER_ROW = 8;
|
||||
private static final int ITEM_VERTICAL_SPACING = 36;
|
||||
private static final int ITEM_HORIZONTAL_SPACING = 48;
|
||||
private static final int ITEM_ROW_START = 51;
|
||||
|
||||
private static final int MAX_RESULT_COUNT = 250;
|
||||
|
||||
private static final String SEARCH_BANK_INPUT_TEXT =
|
||||
"Show items whose names or tags contain the following text:<br>" +
|
||||
"(To show only tagged items, start your search with 'tag:')";
|
||||
private static final String SEARCH_BANK_INPUT_TEXT_FOUND =
|
||||
"Show items whose names or tags contain the following text: (%d found)<br>" +
|
||||
"(To show only tagged items, start your search with 'tag:')";
|
||||
|
||||
@Inject
|
||||
private ItemManager itemManager;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private ChatboxPanelManager chatboxPanelManager;
|
||||
|
||||
@Inject
|
||||
private MouseManager mouseManager;
|
||||
|
||||
@Inject
|
||||
private BankTagsConfig config;
|
||||
|
||||
@Inject
|
||||
private TagManager tagManager;
|
||||
|
||||
@Inject
|
||||
private TabInterface tabInterface;
|
||||
|
||||
@Inject
|
||||
private SpriteManager spriteManager;
|
||||
|
||||
@Inject
|
||||
private ConfigManager configManager;
|
||||
|
||||
@Provides
|
||||
BankTagsConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(BankTagsConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetConfiguration()
|
||||
{
|
||||
List<String> extraKeys = Lists.newArrayList(
|
||||
CONFIG_GROUP + "." + TagManager.ITEM_KEY_PREFIX,
|
||||
CONFIG_GROUP + "." + ICON_SEARCH,
|
||||
CONFIG_GROUP + "." + TAG_TABS_CONFIG
|
||||
);
|
||||
|
||||
for (String prefix : extraKeys)
|
||||
{
|
||||
List<String> keys = configManager.getConfigurationKeys(prefix);
|
||||
for (String key : keys)
|
||||
{
|
||||
String[] str = key.split("\\.", 2);
|
||||
if (str.length == 2)
|
||||
{
|
||||
configManager.unsetConfiguration(str[0], str[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clientThread.invokeLater(() ->
|
||||
{
|
||||
tabInterface.destroy();
|
||||
tabInterface.init();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void startUp()
|
||||
{
|
||||
cleanConfig();
|
||||
mouseManager.registerMouseWheelListener(this);
|
||||
clientThread.invokeLater(tabInterface::init);
|
||||
spriteManager.addSpriteOverrides(TabSprites.values());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private void cleanConfig()
|
||||
{
|
||||
removeInvalidTags("tagtabs");
|
||||
|
||||
List<String> tags = configManager.getConfigurationKeys(CONFIG_GROUP + ".item_");
|
||||
tags.forEach(s ->
|
||||
{
|
||||
String[] split = s.split("\\.", 2);
|
||||
removeInvalidTags(split[1]);
|
||||
});
|
||||
|
||||
List<String> icons = configManager.getConfigurationKeys(CONFIG_GROUP + ".icon_");
|
||||
icons.forEach(s ->
|
||||
{
|
||||
String[] split = s.split("\\.", 2);
|
||||
String replaced = split[1].replaceAll("[<>/]", "");
|
||||
if (!split[1].equals(replaced))
|
||||
{
|
||||
String value = configManager.getConfiguration(CONFIG_GROUP, split[1]);
|
||||
configManager.unsetConfiguration(CONFIG_GROUP, split[1]);
|
||||
if (replaced.length() > "icon_".length())
|
||||
{
|
||||
configManager.setConfiguration(CONFIG_GROUP, replaced, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private void removeInvalidTags(final String key)
|
||||
{
|
||||
final String value = configManager.getConfiguration(CONFIG_GROUP, key);
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String replaced = value.replaceAll("[<>:/]", "");
|
||||
if (!value.equals(replaced))
|
||||
{
|
||||
replaced = Text.toCSV(Text.fromCSV(replaced));
|
||||
if (replaced.isEmpty())
|
||||
{
|
||||
configManager.unsetConfiguration(CONFIG_GROUP, key);
|
||||
}
|
||||
else
|
||||
{
|
||||
configManager.setConfiguration(CONFIG_GROUP, key, replaced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutDown()
|
||||
{
|
||||
mouseManager.unregisterMouseWheelListener(this);
|
||||
clientThread.invokeLater(tabInterface::destroy);
|
||||
spriteManager.removeSpriteOverrides(TabSprites.values());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGrandExchangeSearched(GrandExchangeSearched event)
|
||||
{
|
||||
final String input = client.getVar(VarClientStr.INPUT_TEXT);
|
||||
if (!input.startsWith(TAG_SEARCH))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
event.consume();
|
||||
|
||||
final String tag = input.substring(TAG_SEARCH.length()).trim();
|
||||
final Set<Integer> ids = tagManager.getItemsForTag(tag)
|
||||
.stream()
|
||||
.mapToInt(Math::abs)
|
||||
.mapToObj(ItemVariationMapping::getVariations)
|
||||
.flatMap(Collection::stream)
|
||||
.distinct()
|
||||
.filter(i -> itemManager.getItemComposition(i).isTradeable())
|
||||
.limit(MAX_RESULT_COUNT)
|
||||
.collect(Collectors.toCollection(TreeSet::new));
|
||||
|
||||
client.setGeSearchResultIndex(0);
|
||||
client.setGeSearchResultCount(ids.size());
|
||||
client.setGeSearchResultIds(Shorts.toArray(ids));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptCallbackEvent(ScriptCallbackEvent event)
|
||||
{
|
||||
String eventName = event.getEventName();
|
||||
|
||||
int[] intStack = client.getIntStack();
|
||||
String[] stringStack = client.getStringStack();
|
||||
int intStackSize = client.getIntStackSize();
|
||||
int stringStackSize = client.getStringStackSize();
|
||||
|
||||
tabInterface.handleScriptEvent(event);
|
||||
|
||||
switch (eventName)
|
||||
{
|
||||
case "setSearchBankInputText":
|
||||
stringStack[stringStackSize - 1] = SEARCH_BANK_INPUT_TEXT;
|
||||
break;
|
||||
case "setSearchBankInputTextFound":
|
||||
{
|
||||
int matches = intStack[intStackSize - 1];
|
||||
stringStack[stringStackSize - 1] = String.format(SEARCH_BANK_INPUT_TEXT_FOUND, matches);
|
||||
break;
|
||||
}
|
||||
case "bankSearchFilter":
|
||||
final int itemId = intStack[intStackSize - 1];
|
||||
final String searchfilter = stringStack[stringStackSize - 1];
|
||||
|
||||
// This event only fires when the bank is in search mode. It will fire even if there is no search
|
||||
// input. We prevent having a tag tab open while also performing a normal search, so if a tag tab
|
||||
// is active here it must mean we have placed the bank into search mode. See onScriptPostFired().
|
||||
TagTab activeTab = tabInterface.getActiveTab();
|
||||
String search = activeTab != null ? TAG_SEARCH + activeTab.getTag() : searchfilter;
|
||||
|
||||
if (search.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
boolean tagSearch = search.startsWith(TAG_SEARCH);
|
||||
if (tagSearch)
|
||||
{
|
||||
search = search.substring(TAG_SEARCH.length()).trim();
|
||||
}
|
||||
|
||||
if (tagManager.findTag(itemId, search))
|
||||
{
|
||||
// return true
|
||||
intStack[intStackSize - 2] = 1;
|
||||
}
|
||||
else if (tagSearch)
|
||||
{
|
||||
// if the item isn't tagged we return false to prevent the item matching if the item name happens
|
||||
// to contain the tag name.
|
||||
intStack[intStackSize - 2] = 0;
|
||||
}
|
||||
break;
|
||||
case "getSearchingTagTab":
|
||||
intStack[intStackSize - 1] = tabInterface.isActive() ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuEntryAdded(MenuEntryAdded event)
|
||||
{
|
||||
MenuEntry[] entries = client.getMenuEntries();
|
||||
|
||||
if (event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId()
|
||||
&& event.getOption().equals("Examine"))
|
||||
{
|
||||
Widget container = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER);
|
||||
Widget item = container.getChild(event.getActionParam());
|
||||
int itemID = item.getItemId();
|
||||
String text = EDIT_TAGS_MENU_OPTION;
|
||||
int tagCount = tagManager.getTags(itemID, false).size() + tagManager.getTags(itemID, true).size();
|
||||
|
||||
if (tagCount > 0)
|
||||
{
|
||||
text += " (" + tagCount + ")";
|
||||
}
|
||||
|
||||
MenuEntry editTags = new MenuEntry();
|
||||
editTags.setActionParam(event.getActionParam());
|
||||
editTags.setParam1(event.getActionParam1());
|
||||
editTags.setTarget(event.getTarget());
|
||||
editTags.setOption(text);
|
||||
editTags.setType(MenuAction.RUNELITE.getId());
|
||||
editTags.setIdentifier(event.getIdentifier());
|
||||
entries = Arrays.copyOf(entries, entries.length + 1);
|
||||
entries[entries.length - 1] = editTags;
|
||||
client.setMenuEntries(entries);
|
||||
}
|
||||
|
||||
tabInterface.handleAdd(event);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuOptionClicked(MenuOptionClicked event)
|
||||
{
|
||||
if (event.getWidgetId() == WidgetInfo.BANK_ITEM_CONTAINER.getId()
|
||||
&& event.getMenuAction() == MenuAction.RUNELITE
|
||||
&& event.getMenuOption().startsWith(EDIT_TAGS_MENU_OPTION))
|
||||
{
|
||||
event.consume();
|
||||
int inventoryIndex = event.getActionParam();
|
||||
ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK);
|
||||
if (bankContainer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Item[] items = bankContainer.getItems();
|
||||
if (inventoryIndex < 0 || inventoryIndex >= items.length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Item item = bankContainer.getItems()[inventoryIndex];
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int itemId = item.getId();
|
||||
ItemComposition itemComposition = itemManager.getItemComposition(itemId);
|
||||
String name = itemComposition.getName();
|
||||
|
||||
// Get both tags and vartags and append * to end of vartags name
|
||||
Collection<String> tags = tagManager.getTags(itemId, false);
|
||||
tagManager.getTags(itemId, true).stream()
|
||||
.map(i -> i + "*")
|
||||
.forEach(tags::add);
|
||||
|
||||
String initialValue = Text.toCSV(tags);
|
||||
|
||||
chatboxPanelManager.openTextInput(name + " tags:<br>(append " + VAR_TAG_SUFFIX + " for variation tag)")
|
||||
.addCharValidator(FILTERED_CHARS)
|
||||
.value(initialValue)
|
||||
.onDone((Consumer<String>) (newValue) ->
|
||||
clientThread.invoke(() ->
|
||||
{
|
||||
// Split inputted tags to vartags (ending with *) and regular tags
|
||||
final Collection<String> newTags = new ArrayList<>(Text.fromCSV(newValue.toLowerCase()));
|
||||
final Collection<String> newVarTags = new ArrayList<>(newTags).stream().filter(s -> s.endsWith(VAR_TAG_SUFFIX)).map(s ->
|
||||
{
|
||||
newTags.remove(s);
|
||||
return s.substring(0, s.length() - VAR_TAG_SUFFIX.length());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
// And save them
|
||||
tagManager.setTagString(itemId, Text.toCSV(newTags), false);
|
||||
tagManager.setTagString(itemId, Text.toCSV(newVarTags), true);
|
||||
|
||||
// Check both previous and current tags in case the tag got removed in new tags or in case
|
||||
// the tag got added in new tags
|
||||
tabInterface.updateTabIfActive(Text.fromCSV(initialValue.toLowerCase().replaceAll(Pattern.quote(VAR_TAG_SUFFIX), "")));
|
||||
tabInterface.updateTabIfActive(Text.fromCSV(newValue.toLowerCase().replaceAll(Pattern.quote(VAR_TAG_SUFFIX), "")));
|
||||
}))
|
||||
.build();
|
||||
}
|
||||
else
|
||||
{
|
||||
tabInterface.handleClick(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged configChanged)
|
||||
{
|
||||
if (configChanged.getGroup().equals(CONFIG_GROUP) && configChanged.getKey().equals("useTabs"))
|
||||
{
|
||||
if (config.tabs())
|
||||
{
|
||||
clientThread.invokeLater(tabInterface::init);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientThread.invokeLater(tabInterface::destroy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptPreFired(ScriptPreFired event)
|
||||
{
|
||||
int scriptId = event.getScriptId();
|
||||
if (scriptId == ScriptID.BANKMAIN_FINISHBUILDING)
|
||||
{
|
||||
// Since we apply tag tab search filters even when the bank is not in search mode,
|
||||
// bankkmain_build will reset the bank title to "The Bank of Gielinor". So apply our
|
||||
// own title.
|
||||
TagTab activeTab = tabInterface.getActiveTab();
|
||||
if (tabInterface.isTagTabActive())
|
||||
{
|
||||
// Tag tab tab has its own title since it isn't a real tag
|
||||
Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR);
|
||||
bankTitle.setText("Tag tab tab");
|
||||
}
|
||||
else if (activeTab != null)
|
||||
{
|
||||
Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR);
|
||||
bankTitle.setText("Tag tab <col=ff0000>" + activeTab.getTag() + "</col>");
|
||||
}
|
||||
}
|
||||
else if (scriptId == ScriptID.BANKMAIN_SEARCH_TOGGLE)
|
||||
{
|
||||
tabInterface.handleSearch();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptPostFired(ScriptPostFired event)
|
||||
{
|
||||
if (event.getScriptId() == ScriptID.BANKMAIN_SEARCHING)
|
||||
{
|
||||
// The return value of bankmain_searching is on the stack. If we have a tag tab active
|
||||
// make it return true to put the bank in a searching state.
|
||||
if (tabInterface.getActiveTab() != null || tabInterface.isTagTabActive())
|
||||
{
|
||||
client.getIntStack()[client.getIntStackSize() - 1] = 1; // true
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getScriptId() != ScriptID.BANKMAIN_BUILD || !config.removeSeparators())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tabInterface.isActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Widget itemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER);
|
||||
if (itemContainer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int items = 0;
|
||||
|
||||
Widget[] containerChildren = itemContainer.getDynamicChildren();
|
||||
|
||||
// sort the child array as the items are not in the displayed order
|
||||
Arrays.sort(containerChildren, Comparator.comparing(Widget::getOriginalY)
|
||||
.thenComparing(Widget::getOriginalX));
|
||||
|
||||
for (Widget child : containerChildren)
|
||||
{
|
||||
if (child.getItemId() != -1 && !child.isHidden())
|
||||
{
|
||||
// calculate correct item position as if this was a normal tab
|
||||
int adjYOffset = (items / ITEMS_PER_ROW) * ITEM_VERTICAL_SPACING;
|
||||
int adjXOffset = (items % ITEMS_PER_ROW) * ITEM_HORIZONTAL_SPACING + ITEM_ROW_START;
|
||||
|
||||
if (child.getOriginalY() != adjYOffset)
|
||||
{
|
||||
child.setOriginalY(adjYOffset);
|
||||
child.revalidate();
|
||||
}
|
||||
|
||||
if (child.getOriginalX() != adjXOffset)
|
||||
{
|
||||
child.setOriginalX(adjXOffset);
|
||||
child.revalidate();
|
||||
}
|
||||
|
||||
items++;
|
||||
}
|
||||
|
||||
// separator line or tab text
|
||||
if (child.getSpriteId() == SpriteID.RESIZEABLE_MODE_SIDE_PANEL_BACKGROUND
|
||||
|| child.getText().contains("Tab"))
|
||||
{
|
||||
child.setHidden(true);
|
||||
}
|
||||
}
|
||||
|
||||
final Widget bankItemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER);
|
||||
int itemContainerHeight = bankItemContainer.getHeight();
|
||||
// add a second row of height here to allow users to scroll down when the last row is partially visible
|
||||
int adjustedScrollHeight = (items / ITEMS_PER_ROW) * ITEM_VERTICAL_SPACING + ITEM_VERTICAL_SPACING;
|
||||
itemContainer.setScrollHeight(Math.max(adjustedScrollHeight, itemContainerHeight));
|
||||
|
||||
final int itemContainerScroll = bankItemContainer.getScrollY();
|
||||
clientThread.invokeLater(() ->
|
||||
client.runScript(ScriptID.UPDATE_SCROLLBAR,
|
||||
WidgetInfo.BANK_SCROLLBAR.getId(),
|
||||
WidgetInfo.BANK_ITEM_CONTAINER.getId(),
|
||||
itemContainerScroll));
|
||||
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameTick(GameTick event)
|
||||
{
|
||||
tabInterface.update();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDraggingWidgetChanged(DraggingWidgetChanged event)
|
||||
{
|
||||
final boolean shiftPressed = client.isKeyPressed(KeyCode.KC_SHIFT);
|
||||
tabInterface.handleDrag(event.isDraggingWidget(), shiftPressed);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onWidgetLoaded(WidgetLoaded event)
|
||||
{
|
||||
if (event.getGroupId() == WidgetID.BANK_GROUP_ID)
|
||||
{
|
||||
tabInterface.init();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseWheelEvent mouseWheelMoved(MouseWheelEvent event)
|
||||
{
|
||||
tabInterface.handleWheel(event);
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
|
||||
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
|
||||
* 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.banktags;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
import net.runelite.client.game.ItemVariationMapping;
|
||||
import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP;
|
||||
import net.runelite.client.plugins.cluescrolls.ClueScrollService;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.ClueScroll;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.CoordinateClue;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.EmoteClue;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.FairyRingClue;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.HotColdClue;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.MapClue;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement;
|
||||
import net.runelite.client.util.Text;
|
||||
|
||||
@Singleton
|
||||
public class TagManager
|
||||
{
|
||||
static final String ITEM_KEY_PREFIX = "item_";
|
||||
private final ConfigManager configManager;
|
||||
private final ItemManager itemManager;
|
||||
private final ClueScrollService clueScrollService;
|
||||
|
||||
@Inject
|
||||
private TagManager(
|
||||
final ItemManager itemManager,
|
||||
final ConfigManager configManager,
|
||||
final ClueScrollService clueScrollService)
|
||||
{
|
||||
this.itemManager = itemManager;
|
||||
this.configManager = configManager;
|
||||
this.clueScrollService = clueScrollService;
|
||||
}
|
||||
|
||||
String getTagString(int itemId, boolean variation)
|
||||
{
|
||||
itemId = getItemId(itemId, variation);
|
||||
|
||||
String config = configManager.getConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId);
|
||||
if (config == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
Collection<String> getTags(int itemId, boolean variation)
|
||||
{
|
||||
return new LinkedHashSet<>(Text.fromCSV(getTagString(itemId, variation).toLowerCase()));
|
||||
}
|
||||
|
||||
void setTagString(int itemId, String tags, boolean variation)
|
||||
{
|
||||
itemId = getItemId(itemId, variation);
|
||||
|
||||
if (Strings.isNullOrEmpty(tags))
|
||||
{
|
||||
configManager.unsetConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId);
|
||||
}
|
||||
else
|
||||
{
|
||||
configManager.setConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId, tags);
|
||||
}
|
||||
}
|
||||
|
||||
public void addTags(int itemId, final Collection<String> t, boolean variation)
|
||||
{
|
||||
final Collection<String> tags = getTags(itemId, variation);
|
||||
if (tags.addAll(t))
|
||||
{
|
||||
setTags(itemId, tags, variation);
|
||||
}
|
||||
}
|
||||
|
||||
public void addTag(int itemId, String tag, boolean variation)
|
||||
{
|
||||
final Collection<String> tags = getTags(itemId, variation);
|
||||
if (tags.add(Text.standardize(tag)))
|
||||
{
|
||||
setTags(itemId, tags, variation);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTags(int itemId, Collection<String> tags, boolean variation)
|
||||
{
|
||||
setTagString(itemId, Text.toCSV(tags), variation);
|
||||
}
|
||||
|
||||
boolean findTag(int itemId, String search)
|
||||
{
|
||||
if (search.equals("clue") && testClue(itemId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Collection<String> tags = getTags(itemId, false);
|
||||
tags.addAll(getTags(itemId, true));
|
||||
return tags.stream().anyMatch(tag -> tag.startsWith(Text.standardize(search)));
|
||||
}
|
||||
|
||||
public List<Integer> getItemsForTag(String tag)
|
||||
{
|
||||
final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX;
|
||||
return configManager.getConfigurationKeys(prefix).stream()
|
||||
.map(item -> Integer.parseInt(item.replace(prefix, "")))
|
||||
.filter(item -> getTags(item, false).contains(tag) || getTags(item, true).contains(tag))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void removeTag(String tag)
|
||||
{
|
||||
final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX;
|
||||
configManager.getConfigurationKeys(prefix).forEach(item ->
|
||||
{
|
||||
int id = Integer.parseInt(item.replace(prefix, ""));
|
||||
removeTag(id, tag);
|
||||
});
|
||||
}
|
||||
|
||||
public void removeTag(int itemId, String tag)
|
||||
{
|
||||
Collection<String> tags = getTags(itemId, false);
|
||||
if (tags.remove(Text.standardize(tag)))
|
||||
{
|
||||
setTags(itemId, tags, false);
|
||||
}
|
||||
|
||||
tags = getTags(itemId, true);
|
||||
if (tags.remove(Text.standardize(tag)))
|
||||
{
|
||||
setTags(itemId, tags, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void renameTag(String oldTag, String newTag)
|
||||
{
|
||||
List<Integer> items = getItemsForTag(Text.standardize(oldTag));
|
||||
items.forEach(id ->
|
||||
{
|
||||
Collection<String> tags = getTags(id, id < 0);
|
||||
|
||||
tags.remove(Text.standardize(oldTag));
|
||||
tags.add(Text.standardize(newTag));
|
||||
|
||||
setTags(id, tags, id < 0);
|
||||
});
|
||||
}
|
||||
|
||||
private int getItemId(int itemId, boolean variation)
|
||||
{
|
||||
itemId = Math.abs(itemId);
|
||||
itemId = itemManager.canonicalize(itemId);
|
||||
|
||||
if (variation)
|
||||
{
|
||||
itemId = ItemVariationMapping.map(itemId) * -1;
|
||||
}
|
||||
|
||||
return itemId;
|
||||
}
|
||||
|
||||
private boolean testClue(int itemId)
|
||||
{
|
||||
ClueScroll c = clueScrollService.getClue();
|
||||
|
||||
if (c == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c instanceof EmoteClue)
|
||||
{
|
||||
EmoteClue emote = (EmoteClue) c;
|
||||
|
||||
for (ItemRequirement ir : emote.getItemRequirements())
|
||||
{
|
||||
if (ir.fulfilledBy(itemId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c instanceof CoordinateClue || c instanceof HotColdClue || c instanceof FairyRingClue)
|
||||
{
|
||||
return itemId == ItemID.SPADE;
|
||||
}
|
||||
else if (c instanceof MapClue)
|
||||
{
|
||||
MapClue mapClue = (MapClue) c;
|
||||
|
||||
return mapClue.getObjectId() == -1 && itemId == ItemID.SPADE;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
|
||||
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
|
||||
* 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.banktags.tabs;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP;
|
||||
import static net.runelite.client.plugins.banktags.BankTagsPlugin.ICON_SEARCH;
|
||||
import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_TABS_CONFIG;
|
||||
import net.runelite.client.util.Text;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
|
||||
@Singleton
|
||||
class TabManager
|
||||
{
|
||||
@Getter
|
||||
private final List<TagTab> tabs = new ArrayList<>();
|
||||
private final ConfigManager configManager;
|
||||
|
||||
@Inject
|
||||
private TabManager(ConfigManager configManager)
|
||||
{
|
||||
this.configManager = configManager;
|
||||
}
|
||||
|
||||
void add(TagTab tagTab)
|
||||
{
|
||||
if (!contains(tagTab.getTag()))
|
||||
{
|
||||
tabs.add(tagTab);
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
tabs.forEach(t -> t.setHidden(true));
|
||||
tabs.clear();
|
||||
}
|
||||
|
||||
TagTab find(String tag)
|
||||
{
|
||||
Optional<TagTab> first = tabs.stream().filter(t -> t.getTag().equals(Text.standardize(tag))).findAny();
|
||||
return first.orElse(null);
|
||||
}
|
||||
|
||||
List<String> getAllTabs()
|
||||
{
|
||||
return Text.fromCSV(MoreObjects.firstNonNull(configManager.getConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG), ""));
|
||||
}
|
||||
|
||||
TagTab load(String tag)
|
||||
{
|
||||
TagTab tagTab = find(tag);
|
||||
|
||||
if (tagTab == null)
|
||||
{
|
||||
tag = Text.standardize(tag);
|
||||
String item = configManager.getConfiguration(CONFIG_GROUP, ICON_SEARCH + tag);
|
||||
int itemid = NumberUtils.toInt(item, ItemID.SPADE);
|
||||
tagTab = new TagTab(itemid, tag);
|
||||
}
|
||||
|
||||
return tagTab;
|
||||
}
|
||||
|
||||
void swap(String tagToMove, String tagDestination)
|
||||
{
|
||||
tagToMove = Text.standardize(tagToMove);
|
||||
tagDestination = Text.standardize(tagDestination);
|
||||
|
||||
if (contains(tagToMove) && contains(tagDestination))
|
||||
{
|
||||
Collections.swap(tabs, indexOf(tagToMove), indexOf(tagDestination));
|
||||
}
|
||||
}
|
||||
|
||||
void insert(String tagToMove, String tagDestination)
|
||||
{
|
||||
tagToMove = Text.standardize(tagToMove);
|
||||
tagDestination = Text.standardize(tagDestination);
|
||||
|
||||
if (contains(tagToMove) && contains(tagDestination))
|
||||
{
|
||||
tabs.add(indexOf(tagDestination), tabs.remove(indexOf(tagToMove)));
|
||||
}
|
||||
}
|
||||
|
||||
void remove(String tag)
|
||||
{
|
||||
TagTab tagTab = find(tag);
|
||||
|
||||
if (tagTab != null)
|
||||
{
|
||||
tagTab.setHidden(true);
|
||||
tabs.remove(tagTab);
|
||||
removeIcon(tag);
|
||||
}
|
||||
}
|
||||
|
||||
void save()
|
||||
{
|
||||
String tags = Text.toCSV(tabs.stream().map(TagTab::getTag).collect(Collectors.toList()));
|
||||
configManager.setConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG, tags);
|
||||
}
|
||||
|
||||
void removeIcon(final String tag)
|
||||
{
|
||||
configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag));
|
||||
}
|
||||
|
||||
void setIcon(final String tag, final String icon)
|
||||
{
|
||||
configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag), icon);
|
||||
}
|
||||
|
||||
int size()
|
||||
{
|
||||
return tabs.size();
|
||||
}
|
||||
|
||||
private boolean contains(String tag)
|
||||
{
|
||||
return tabs.stream().anyMatch(t -> t.getTag().equals(tag));
|
||||
}
|
||||
|
||||
private int indexOf(TagTab tagTab)
|
||||
{
|
||||
return tabs.indexOf(tagTab);
|
||||
}
|
||||
|
||||
private int indexOf(String tag)
|
||||
{
|
||||
return indexOf(find(tag));
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Abex
|
||||
* 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.camera;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
import net.runelite.client.config.Range;
|
||||
|
||||
@ConfigGroup("zoom") // using the old plugin's group name
|
||||
public interface CameraConfig extends Config
|
||||
{
|
||||
int OUTER_LIMIT_MIN = -400;
|
||||
int OUTER_LIMIT_MAX = 400;
|
||||
/**
|
||||
* The largest (most zoomed in) value that can be used without the client crashing.
|
||||
*
|
||||
* Larger values trigger an overflow in the engine's fov to scale code.
|
||||
*/
|
||||
int INNER_ZOOM_LIMIT = 1004;
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "inner",
|
||||
name = "Expand inner zoom limit",
|
||||
description = "Configures whether or not the inner zoom limit is reduced",
|
||||
position = 1
|
||||
)
|
||||
default boolean innerLimit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Range(
|
||||
min = OUTER_LIMIT_MIN,
|
||||
max = OUTER_LIMIT_MAX
|
||||
)
|
||||
@ConfigItem(
|
||||
keyName = "outerLimit",
|
||||
name = "Expand outer zoom limit",
|
||||
description = "Configures how much the outer zoom limit is adjusted",
|
||||
position = 2
|
||||
)
|
||||
default int outerLimit()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "relaxCameraPitch",
|
||||
name = "Vertical camera",
|
||||
description = "Relax the camera's upper pitch limit",
|
||||
position = 3
|
||||
)
|
||||
default boolean relaxCameraPitch()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "controlFunction",
|
||||
name = "Control Function",
|
||||
description = "Configures the zoom function when control is pressed",
|
||||
position = 4
|
||||
)
|
||||
default ControlFunction controlFunction()
|
||||
{
|
||||
return ControlFunction.NONE;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "ctrlZoomValue",
|
||||
name = "Reset zoom position",
|
||||
description = "Position of zoom when it is reset",
|
||||
position = 5
|
||||
)
|
||||
@Range(
|
||||
min = OUTER_LIMIT_MIN,
|
||||
max = INNER_ZOOM_LIMIT
|
||||
)
|
||||
default int ctrlZoomValue()
|
||||
{
|
||||
return 512;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "zoomIncrement",
|
||||
name = "Zoom Speed",
|
||||
description = "Speed of zoom",
|
||||
position = 6
|
||||
)
|
||||
default int zoomIncrement()
|
||||
{
|
||||
return 25;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "rightClickMovesCamera",
|
||||
name = "Right click moves camera",
|
||||
description = "Remaps right click to middle mouse click if there are no menu options",
|
||||
position = 7
|
||||
)
|
||||
default boolean rightClickMovesCamera()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "ignoreExamine",
|
||||
name = "Ignore Examine",
|
||||
description = "Ignore the Examine menu entry",
|
||||
position = 8
|
||||
)
|
||||
default boolean ignoreExamine()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "middleClickMenu",
|
||||
name = "Middle-button opens menu",
|
||||
description = "Middle-mouse button always opens the menu",
|
||||
position = 9
|
||||
)
|
||||
default boolean middleClickMenu()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "compassLook",
|
||||
name = "Compass options",
|
||||
description = "Adds Look South, East, and West options to the compass",
|
||||
position = 10
|
||||
)
|
||||
default boolean compassLook()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "invertYaw",
|
||||
name = "Invert Yaw",
|
||||
description = "Makes moving the camera horizontally with the mouse backwards",
|
||||
position = 11
|
||||
)
|
||||
default boolean invertYaw()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "invertPitch",
|
||||
name = "Invert Pitch",
|
||||
description = "Makes moving the camera vertically with the mouse backwards",
|
||||
position = 12
|
||||
)
|
||||
default boolean invertPitch()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,529 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Abex
|
||||
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||
* Copyright (c) 2019, Wynadorn <https://github.com/Wynadorn>
|
||||
* 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.camera;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Arrays;
|
||||
import javax.inject.Inject;
|
||||
import javax.swing.SwingUtilities;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.MenuAction;
|
||||
import net.runelite.api.MenuEntry;
|
||||
import net.runelite.api.ScriptID;
|
||||
import net.runelite.api.SettingID;
|
||||
import net.runelite.api.VarClientInt;
|
||||
import net.runelite.api.VarPlayer;
|
||||
import net.runelite.api.events.BeforeRender;
|
||||
import net.runelite.api.events.ClientTick;
|
||||
import net.runelite.api.events.FocusChanged;
|
||||
import net.runelite.api.events.MenuEntryAdded;
|
||||
import net.runelite.api.events.ScriptCallbackEvent;
|
||||
import net.runelite.api.events.ScriptPreFired;
|
||||
import net.runelite.api.events.WidgetLoaded;
|
||||
import net.runelite.api.widgets.JavaScriptCallback;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetID;
|
||||
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.input.KeyListener;
|
||||
import net.runelite.client.input.KeyManager;
|
||||
import net.runelite.client.input.MouseListener;
|
||||
import net.runelite.client.input.MouseManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.ui.overlay.tooltip.Tooltip;
|
||||
import net.runelite.client.ui.overlay.tooltip.TooltipManager;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Camera",
|
||||
description = "Expands zoom limit, provides vertical camera, and remaps mouse input keys",
|
||||
tags = {"zoom", "limit", "vertical", "click", "mouse"},
|
||||
enabledByDefault = false
|
||||
)
|
||||
public class CameraPlugin extends Plugin implements KeyListener, MouseListener
|
||||
{
|
||||
private static final int DEFAULT_ZOOM_INCREMENT = 25;
|
||||
private static final int DEFAULT_OUTER_ZOOM_LIMIT = 128;
|
||||
static final int DEFAULT_INNER_ZOOM_LIMIT = 896;
|
||||
|
||||
private static final String LOOK_NORTH = "Look North";
|
||||
private static final String LOOK_SOUTH = "Look South";
|
||||
private static final String LOOK_EAST = "Look East";
|
||||
private static final String LOOK_WEST = "Look West";
|
||||
|
||||
private boolean controlDown;
|
||||
// flags used to store the mousedown states
|
||||
private boolean rightClick;
|
||||
private boolean middleClick;
|
||||
/**
|
||||
* Whether or not the current menu has any non-ignored menu entries
|
||||
*/
|
||||
private boolean menuHasEntries;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private CameraConfig config;
|
||||
|
||||
@Inject
|
||||
private KeyManager keyManager;
|
||||
|
||||
@Inject
|
||||
private MouseManager mouseManager;
|
||||
|
||||
@Inject
|
||||
private TooltipManager tooltipManager;
|
||||
|
||||
private Tooltip sliderTooltip;
|
||||
|
||||
@Provides
|
||||
CameraConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(CameraConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
rightClick = false;
|
||||
middleClick = false;
|
||||
menuHasEntries = false;
|
||||
copyConfigs();
|
||||
keyManager.registerKeyListener(this);
|
||||
mouseManager.registerMouseListener(this);
|
||||
clientThread.invoke(() ->
|
||||
{
|
||||
Widget sideSlider = client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK);
|
||||
if (sideSlider != null)
|
||||
{
|
||||
addZoomTooltip(sideSlider);
|
||||
}
|
||||
|
||||
Widget settingsInit = client.getWidget(WidgetInfo.SETTINGS_INIT);
|
||||
if (settingsInit != null)
|
||||
{
|
||||
//TODO: Implement client.createScriptEvent
|
||||
//client.createScriptEvent(settingsInit.getOnLoadListener())
|
||||
//.setSource(settingsInit)
|
||||
//.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
client.setCameraPitchRelaxerEnabled(false);
|
||||
client.setInvertYaw(false);
|
||||
client.setInvertPitch(false);
|
||||
keyManager.unregisterKeyListener(this);
|
||||
mouseManager.unregisterMouseListener(this);
|
||||
controlDown = false;
|
||||
|
||||
clientThread.invoke(() ->
|
||||
{
|
||||
Widget sideSlider = client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK);
|
||||
if (sideSlider != null)
|
||||
{
|
||||
sideSlider.setOnMouseRepeatListener((Object[]) null);
|
||||
}
|
||||
|
||||
Widget settingsInit = client.getWidget(WidgetInfo.SETTINGS_INIT);
|
||||
if (settingsInit != null)
|
||||
{
|
||||
//TODO: Implement client.createScriptEvent
|
||||
//client.createScriptEvent(settingsInit.getOnLoadListener())
|
||||
//.setSource(settingsInit)
|
||||
//.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void copyConfigs()
|
||||
{
|
||||
client.setCameraPitchRelaxerEnabled(config.relaxCameraPitch());
|
||||
client.setInvertYaw(config.invertYaw());
|
||||
client.setInvertPitch(config.invertPitch());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded)
|
||||
{
|
||||
if (menuEntryAdded.getType() == MenuAction.CC_OP.getId() && menuEntryAdded.getOption().equals(LOOK_NORTH) && config.compassLook())
|
||||
{
|
||||
MenuEntry[] menuEntries = client.getMenuEntries();
|
||||
int len = menuEntries.length;
|
||||
MenuEntry north = menuEntries[len - 1];
|
||||
|
||||
menuEntries = Arrays.copyOf(menuEntries, len + 3);
|
||||
|
||||
// The handling for these entries is done in ToplevelCompassOp.rs2asm
|
||||
menuEntries[--len] = createCameraLookEntry(menuEntryAdded, 4, LOOK_WEST);
|
||||
menuEntries[++len] = createCameraLookEntry(menuEntryAdded, 3, LOOK_EAST);
|
||||
menuEntries[++len] = createCameraLookEntry(menuEntryAdded, 2, LOOK_SOUTH);
|
||||
menuEntries[++len] = north;
|
||||
|
||||
client.setMenuEntries(menuEntries);
|
||||
}
|
||||
}
|
||||
|
||||
private MenuEntry createCameraLookEntry(MenuEntryAdded lookNorth, int identifier, String option)
|
||||
{
|
||||
MenuEntry m = new MenuEntry();
|
||||
m.setOption(option);
|
||||
m.setTarget(lookNorth.getTarget());
|
||||
m.setIdentifier(identifier);
|
||||
m.setType(MenuAction.CC_OP.getId());
|
||||
m.setParam0(lookNorth.getActionParam0());
|
||||
m.setParam1(lookNorth.getActionParam1());
|
||||
return m;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptCallbackEvent(ScriptCallbackEvent event)
|
||||
{
|
||||
if (client.getIndexScripts().isOverlayOutdated())
|
||||
{
|
||||
// if any cache overlay fails to load then assume at least one of the zoom scripts is outdated
|
||||
// and prevent zoom extending entirely.
|
||||
return;
|
||||
}
|
||||
|
||||
int[] intStack = client.getIntStack();
|
||||
int intStackSize = client.getIntStackSize();
|
||||
|
||||
if (!controlDown && "scrollWheelZoom".equals(event.getEventName()) && config.controlFunction() == ControlFunction.CONTROL_TO_ZOOM)
|
||||
{
|
||||
intStack[intStackSize - 1] = 1;
|
||||
}
|
||||
|
||||
if ("innerZoomLimit".equals(event.getEventName()) && config.innerLimit())
|
||||
{
|
||||
intStack[intStackSize - 1] = CameraConfig.INNER_ZOOM_LIMIT;
|
||||
return;
|
||||
}
|
||||
|
||||
if ("outerZoomLimit".equals(event.getEventName()))
|
||||
{
|
||||
int outerLimit = Ints.constrainToRange(config.outerLimit(), CameraConfig.OUTER_LIMIT_MIN, CameraConfig.OUTER_LIMIT_MAX);
|
||||
int outerZoomLimit = DEFAULT_OUTER_ZOOM_LIMIT - outerLimit;
|
||||
intStack[intStackSize - 1] = outerZoomLimit;
|
||||
return;
|
||||
}
|
||||
|
||||
if ("scrollWheelZoomIncrement".equals(event.getEventName()) && config.zoomIncrement() != DEFAULT_ZOOM_INCREMENT)
|
||||
{
|
||||
intStack[intStackSize - 1] = config.zoomIncrement();
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.innerLimit())
|
||||
{
|
||||
// This lets the options panel's slider have an exponential rate
|
||||
final double exponent = 2.d;
|
||||
switch (event.getEventName())
|
||||
{
|
||||
case "zoomLinToExp":
|
||||
{
|
||||
double range = intStack[intStackSize - 1];
|
||||
double value = intStack[intStackSize - 2];
|
||||
value = Math.pow(value / range, exponent) * range;
|
||||
intStack[intStackSize - 2] = (int) value;
|
||||
break;
|
||||
}
|
||||
case "zoomExpToLin":
|
||||
{
|
||||
double range = intStack[intStackSize - 1];
|
||||
double value = intStack[intStackSize - 2];
|
||||
value = Math.pow(value / range, 1.d / exponent) * range;
|
||||
intStack[intStackSize - 2] = (int) value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFocusChanged(FocusChanged event)
|
||||
{
|
||||
if (!event.isFocused())
|
||||
{
|
||||
controlDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged ev)
|
||||
{
|
||||
copyConfigs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e)
|
||||
{
|
||||
if (e.getKeyCode() == KeyEvent.VK_CONTROL)
|
||||
{
|
||||
controlDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e)
|
||||
{
|
||||
if (e.getKeyCode() == KeyEvent.VK_CONTROL)
|
||||
{
|
||||
controlDown = false;
|
||||
|
||||
if (config.controlFunction() == ControlFunction.CONTROL_TO_RESET)
|
||||
{
|
||||
final int zoomValue = Ints.constrainToRange(config.ctrlZoomValue(), CameraConfig.OUTER_LIMIT_MIN, CameraConfig.INNER_ZOOM_LIMIT);
|
||||
clientThread.invokeLater(() -> client.runScript(ScriptID.CAMERA_DO_ZOOM, zoomValue, zoomValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the menu has any non-ignored entries
|
||||
*/
|
||||
private boolean hasMenuEntries(MenuEntry[] menuEntries)
|
||||
{
|
||||
for (MenuEntry menuEntry : menuEntries)
|
||||
{
|
||||
MenuAction action = MenuAction.of(menuEntry.getType());
|
||||
switch (action)
|
||||
{
|
||||
case CANCEL:
|
||||
case WALK:
|
||||
break;
|
||||
case EXAMINE_OBJECT:
|
||||
case EXAMINE_NPC:
|
||||
case EXAMINE_ITEM_GROUND:
|
||||
case EXAMINE_ITEM:
|
||||
case CC_OP_LOW_PRIORITY:
|
||||
if (config.ignoreExamine())
|
||||
{
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the menu has any options, because menu entries are built each
|
||||
* tick and the MouseListener runs on the awt thread
|
||||
*/
|
||||
@Subscribe
|
||||
public void onClientTick(ClientTick event)
|
||||
{
|
||||
menuHasEntries = hasMenuEntries(client.getMenuEntries());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onScriptPreFired(ScriptPreFired ev)
|
||||
{
|
||||
if (ev.getScriptId() == ScriptID.SETTINGS_SLIDER_CHOOSE_ONOP)
|
||||
{
|
||||
int arg = client.getIntStackSize() - 7;
|
||||
int[] is = client.getIntStack();
|
||||
|
||||
if (is[arg] == SettingID.CAMERA_ZOOM)
|
||||
{
|
||||
//TODO: Implement client.createScriptEvent
|
||||
//addZoomTooltip(client.getScriptActiveWidget());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onWidgetLoaded(WidgetLoaded ev)
|
||||
{
|
||||
if (ev.getGroupId() == WidgetID.SETTINGS_SIDE_GROUP_ID)
|
||||
{
|
||||
addZoomTooltip(client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK));
|
||||
}
|
||||
}
|
||||
|
||||
private void addZoomTooltip(Widget w)
|
||||
{
|
||||
w.setOnMouseRepeatListener((JavaScriptCallback) ev ->
|
||||
{
|
||||
int value = client.getVar(VarClientInt.CAMERA_ZOOM_RESIZABLE_VIEWPORT);
|
||||
int max = config.innerLimit() ? config.INNER_ZOOM_LIMIT : CameraPlugin.DEFAULT_INNER_ZOOM_LIMIT;
|
||||
sliderTooltip = new Tooltip("Camera Zoom: " + value + " / " + max);
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onBeforeRender(BeforeRender ev)
|
||||
{
|
||||
if (sliderTooltip != null)
|
||||
{
|
||||
tooltipManager.add(sliderTooltip);
|
||||
sliderTooltip = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The event that is triggered when a mouse button is pressed
|
||||
* In this method the right click is changed to a middle-click to enable rotating the camera
|
||||
* <p>
|
||||
* This method also provides the config option to enable the middle-mouse button to always open the right click menu
|
||||
*/
|
||||
@Override
|
||||
public MouseEvent mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
if (SwingUtilities.isRightMouseButton(mouseEvent) && config.rightClickMovesCamera())
|
||||
{
|
||||
boolean oneButton = client.getVar(VarPlayer.MOUSE_BUTTONS) == 1;
|
||||
// Only move the camera if there is nothing at the menu, or if
|
||||
// in one-button mode. In one-button mode, left and right click always do the same thing,
|
||||
// so always treat it as the menu is empty
|
||||
if (!menuHasEntries || oneButton)
|
||||
{
|
||||
// Set the rightClick flag to true so we can release the button in mouseReleased() later
|
||||
rightClick = true;
|
||||
// Change the mousePressed() MouseEvent to the middle mouse button
|
||||
mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(),
|
||||
mouseEvent.getID(),
|
||||
mouseEvent.getWhen(),
|
||||
mouseEvent.getModifiersEx(),
|
||||
mouseEvent.getX(),
|
||||
mouseEvent.getY(),
|
||||
mouseEvent.getClickCount(),
|
||||
mouseEvent.isPopupTrigger(),
|
||||
MouseEvent.BUTTON2);
|
||||
}
|
||||
}
|
||||
else if (SwingUtilities.isMiddleMouseButton((mouseEvent)) && config.middleClickMenu())
|
||||
{
|
||||
// Set the middleClick flag to true so we can release it later in mouseReleased()
|
||||
middleClick = true;
|
||||
// Chance the middle mouse button MouseEvent to a right-click
|
||||
mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(),
|
||||
mouseEvent.getID(),
|
||||
mouseEvent.getWhen(),
|
||||
mouseEvent.getModifiersEx(),
|
||||
mouseEvent.getX(),
|
||||
mouseEvent.getY(),
|
||||
mouseEvent.getClickCount(),
|
||||
mouseEvent.isPopupTrigger(),
|
||||
MouseEvent.BUTTON3);
|
||||
}
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the MouseEvent to release the correct button
|
||||
*/
|
||||
@Override
|
||||
public MouseEvent mouseReleased(MouseEvent mouseEvent)
|
||||
{
|
||||
if (rightClick)
|
||||
{
|
||||
rightClick = false;
|
||||
// Change the MouseEvent to button 2 so the middle mouse button will be released
|
||||
mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(),
|
||||
mouseEvent.getID(),
|
||||
mouseEvent.getWhen(),
|
||||
mouseEvent.getModifiersEx(),
|
||||
mouseEvent.getX(),
|
||||
mouseEvent.getY(),
|
||||
mouseEvent.getClickCount(),
|
||||
mouseEvent.isPopupTrigger(),
|
||||
MouseEvent.BUTTON2);
|
||||
|
||||
}
|
||||
if (middleClick)
|
||||
{
|
||||
middleClick = false;
|
||||
// Change the MouseEvent ot button 3 so the right mouse button will be released
|
||||
mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(),
|
||||
mouseEvent.getID(),
|
||||
mouseEvent.getWhen(),
|
||||
mouseEvent.getModifiersEx(),
|
||||
mouseEvent.getX(),
|
||||
mouseEvent.getY(),
|
||||
mouseEvent.getClickCount(),
|
||||
mouseEvent.isPopupTrigger(),
|
||||
MouseEvent.BUTTON3);
|
||||
}
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
/*
|
||||
* These methods are unused but required to be present in a MouseListener implementation
|
||||
*/
|
||||
// region Unused MouseListener methods
|
||||
@Override
|
||||
public MouseEvent mouseDragged(MouseEvent mouseEvent)
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseEvent mouseMoved(MouseEvent mouseEvent)
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseEvent mouseClicked(MouseEvent mouseEvent)
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseEvent mouseEntered(MouseEvent mouseEvent)
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseEvent mouseExited(MouseEvent mouseEvent)
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.corp;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Polygon;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.NPC;
|
||||
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.OverlayUtil;
|
||||
|
||||
class CoreOverlay extends Overlay
|
||||
{
|
||||
private final CorpPlugin corpPlugin;
|
||||
private final CorpConfig config;
|
||||
|
||||
@Inject
|
||||
private CoreOverlay(CorpPlugin corpPlugin, CorpConfig corpConfig)
|
||||
{
|
||||
setPosition(OverlayPosition.DYNAMIC);
|
||||
setLayer(OverlayLayer.ABOVE_SCENE);
|
||||
this.corpPlugin = corpPlugin;
|
||||
this.config = corpConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
NPC core = corpPlugin.getCore();
|
||||
if (core != null && config.markDarkCore())
|
||||
{
|
||||
Polygon canvasTilePoly = core.getCanvasTilePoly();
|
||||
if (canvasTilePoly != null)
|
||||
{
|
||||
OverlayUtil.renderPolygon(graphics, canvasTilePoly, Color.RED.brighter());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.corp;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup("corp")
|
||||
public interface CorpConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
keyName = "showDamage",
|
||||
name = "Show damage overlay",
|
||||
description = "Show total damage overlay",
|
||||
position = 0
|
||||
)
|
||||
default boolean showDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "markDarkCore",
|
||||
name = "Mark dark core",
|
||||
description = "Marks the dark energy core.",
|
||||
position = 1
|
||||
)
|
||||
default boolean markDarkCore()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.corp;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FontMetrics;
|
||||
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.NPC;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetID;
|
||||
import net.runelite.client.ui.overlay.OverlayLayer;
|
||||
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.ComponentConstants;
|
||||
import net.runelite.client.ui.overlay.components.LineComponent;
|
||||
|
||||
class CorpDamageOverlay extends OverlayPanel
|
||||
{
|
||||
private final Client client;
|
||||
private final CorpPlugin corpPlugin;
|
||||
private final CorpConfig config;
|
||||
|
||||
@Inject
|
||||
private CorpDamageOverlay(Client client, CorpPlugin corpPlugin, CorpConfig config)
|
||||
{
|
||||
super(corpPlugin);
|
||||
setPosition(OverlayPosition.TOP_LEFT);
|
||||
setLayer(OverlayLayer.UNDER_WIDGETS);
|
||||
setPriority(OverlayPriority.LOW);
|
||||
this.client = client;
|
||||
this.corpPlugin = corpPlugin;
|
||||
this.config = config;
|
||||
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Corp overlay"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
Widget damageWidget = client.getWidget(WidgetID.CORP_DAMAGE, 0);
|
||||
if (damageWidget != null)
|
||||
{
|
||||
damageWidget.setHidden(true);
|
||||
}
|
||||
|
||||
NPC corp = corpPlugin.getCorp();
|
||||
if (corp == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int myDamage = client.getVar(Varbits.CORP_DAMAGE);
|
||||
int totalDamage = corpPlugin.getTotalDamage();
|
||||
int players = corpPlugin.getPlayers().size();
|
||||
|
||||
// estimate how much damage is required for kill based on number of players
|
||||
int damageForKill = players != 0 ? totalDamage / players : 0;
|
||||
|
||||
NPC core = corpPlugin.getCore();
|
||||
if (core != null)
|
||||
{
|
||||
WorldPoint corePoint = core.getWorldLocation();
|
||||
WorldPoint myPoint = client.getLocalPlayer().getWorldLocation();
|
||||
|
||||
String text = null;
|
||||
|
||||
if (core.getInteracting() == client.getLocalPlayer())
|
||||
{
|
||||
text = "The core is targeting you!";
|
||||
}
|
||||
else if (corePoint.distanceTo(myPoint) <= 1)
|
||||
{
|
||||
text = "Stay away from the core!";
|
||||
}
|
||||
|
||||
if (text != null)
|
||||
{
|
||||
final FontMetrics fontMetrics = graphics.getFontMetrics();
|
||||
int textWidth = Math.max(ComponentConstants.STANDARD_WIDTH, fontMetrics.stringWidth(text));
|
||||
|
||||
panelComponent.setPreferredSize(new Dimension(textWidth, 0));
|
||||
panelComponent.getChildren().add(LineComponent.builder()
|
||||
.left(text)
|
||||
.leftColor(Color.RED)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
if (config.showDamage())
|
||||
{
|
||||
panelComponent.getChildren().add(LineComponent.builder()
|
||||
.left("Your damage")
|
||||
.right(Integer.toString(myDamage))
|
||||
.rightColor(damageForKill > 0 && myDamage >= damageForKill ? Color.GREEN : Color.RED)
|
||||
.build());
|
||||
|
||||
panelComponent.getChildren().add(LineComponent.builder()
|
||||
.left("Total damage")
|
||||
.right(Integer.toString(totalDamage))
|
||||
.build());
|
||||
}
|
||||
|
||||
return super.render(graphics);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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.corp;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Actor;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.NpcID;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.HitsplatApplied;
|
||||
import net.runelite.api.events.InteractingChanged;
|
||||
import net.runelite.api.events.NpcDespawned;
|
||||
import net.runelite.api.events.NpcSpawned;
|
||||
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.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.ui.overlay.OverlayManager;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Corporeal Beast",
|
||||
description = "Show damage statistics and highlight dark energy cores",
|
||||
tags = {"bosses", "combat", "pve", "overlay"}
|
||||
)
|
||||
@Slf4j
|
||||
public class CorpPlugin extends Plugin
|
||||
{
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private NPC corp;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private NPC core;
|
||||
|
||||
private int yourDamage;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private int totalDamage;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final Set<Actor> players = new HashSet<>();
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ChatMessageManager chatMessageManager;
|
||||
|
||||
@Inject
|
||||
private OverlayManager overlayManager;
|
||||
|
||||
@Inject
|
||||
private CorpDamageOverlay corpOverlay;
|
||||
|
||||
@Inject
|
||||
private CoreOverlay coreOverlay;
|
||||
|
||||
@Provides
|
||||
CorpConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(CorpConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
overlayManager.add(corpOverlay);
|
||||
overlayManager.add(coreOverlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
overlayManager.remove(corpOverlay);
|
||||
overlayManager.remove(coreOverlay);
|
||||
|
||||
corp = core = null;
|
||||
yourDamage = 0;
|
||||
totalDamage = 0;
|
||||
players.clear();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged gameStateChanged)
|
||||
{
|
||||
if (gameStateChanged.getGameState() == GameState.LOADING)
|
||||
{
|
||||
players.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNpcSpawned(NpcSpawned npcSpawned)
|
||||
{
|
||||
NPC npc = npcSpawned.getNpc();
|
||||
|
||||
switch (npc.getId())
|
||||
{
|
||||
case NpcID.CORPOREAL_BEAST:
|
||||
log.debug("Corporeal beast spawn: {}", npc);
|
||||
corp = npc;
|
||||
yourDamage = 0;
|
||||
totalDamage = 0;
|
||||
players.clear();
|
||||
break;
|
||||
case NpcID.DARK_ENERGY_CORE:
|
||||
core = npc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNpcDespawned(NpcDespawned npcDespawned)
|
||||
{
|
||||
NPC npc = npcDespawned.getNpc();
|
||||
|
||||
if (npc == corp)
|
||||
{
|
||||
log.debug("Corporeal beast despawn: {}", npc);
|
||||
corp = null;
|
||||
players.clear();
|
||||
|
||||
if (npc.isDead())
|
||||
{
|
||||
// Show kill stats
|
||||
String message = new ChatMessageBuilder()
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append("Corporeal Beast: Your damage: ")
|
||||
.append(ChatColorType.HIGHLIGHT)
|
||||
.append(Integer.toString(yourDamage))
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append(", Total damage: ")
|
||||
.append(ChatColorType.HIGHLIGHT)
|
||||
.append(Integer.toString(totalDamage))
|
||||
.build();
|
||||
|
||||
chatMessageManager.queue(QueuedMessage.builder()
|
||||
.type(ChatMessageType.CONSOLE)
|
||||
.runeLiteFormattedMessage(message)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
else if (npc == core)
|
||||
{
|
||||
core = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onHitsplatApplied(HitsplatApplied hitsplatApplied)
|
||||
{
|
||||
Actor actor = hitsplatApplied.getActor();
|
||||
|
||||
if (actor != corp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int myDamage = client.getVar(Varbits.CORP_DAMAGE);
|
||||
// sometimes hitsplats are applied after the damage counter has been reset
|
||||
if (myDamage > 0)
|
||||
{
|
||||
yourDamage = myDamage;
|
||||
}
|
||||
totalDamage += hitsplatApplied.getHitsplat().getAmount();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onInteractingChanged(InteractingChanged interactingChanged)
|
||||
{
|
||||
Actor source = interactingChanged.getSource();
|
||||
Actor target = interactingChanged.getTarget();
|
||||
|
||||
if (corp == null || target != corp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
players.add(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.http.api.RuneLiteAPI;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class CrowdsourcingManager
|
||||
{
|
||||
private static final String CROWDSOURCING_BASE = "https://crowdsource.runescape.wiki/runelite";
|
||||
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
private static final Gson GSON = RuneLiteAPI.GSON;
|
||||
|
||||
@Inject
|
||||
private OkHttpClient okHttpClient;
|
||||
|
||||
private List<Object> data = new ArrayList<>();
|
||||
|
||||
public void storeEvent(Object event)
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
data.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
protected void submitToAPI()
|
||||
{
|
||||
List<Object> temp;
|
||||
synchronized (this)
|
||||
{
|
||||
if (data.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
temp = data;
|
||||
data = new ArrayList<>();
|
||||
}
|
||||
|
||||
Request r = new Request.Builder()
|
||||
.url(CROWDSOURCING_BASE)
|
||||
.post(RequestBody.create(JSON, GSON.toJson(temp)))
|
||||
.build();
|
||||
|
||||
okHttpClient.newCall(r).enqueue(new Callback()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e)
|
||||
{
|
||||
log.debug("Error sending crowdsourcing data", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response)
|
||||
{
|
||||
log.debug("Successfully sent crowdsourcing data");
|
||||
response.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import javax.inject.Inject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.plugins.crowdsourcing.cooking.CrowdsourcingCooking;
|
||||
import net.runelite.client.plugins.crowdsourcing.dialogue.CrowdsourcingDialogue;
|
||||
import net.runelite.client.plugins.crowdsourcing.movement.CrowdsourcingMovement;
|
||||
import net.runelite.client.plugins.crowdsourcing.music.CrowdsourcingMusic;
|
||||
import net.runelite.client.plugins.crowdsourcing.thieving.CrowdsourcingThieving;
|
||||
import net.runelite.client.plugins.crowdsourcing.woodcutting.CrowdsourcingWoodcutting;
|
||||
import net.runelite.client.plugins.crowdsourcing.zmi.CrowdsourcingZMI;
|
||||
import net.runelite.client.task.Schedule;
|
||||
|
||||
@Slf4j
|
||||
@PluginDescriptor(
|
||||
name = "OSRS Wiki Crowdsourcing",
|
||||
description = "Send data to the wiki to help figure out skilling success rates, burn rates, more. See osrs.wiki/RS:CROWD"
|
||||
)
|
||||
public class CrowdsourcingPlugin extends Plugin
|
||||
{
|
||||
// Number of seconds to wait between trying to send data to the wiki.
|
||||
private static final int SECONDS_BETWEEN_UPLOADS = 300;
|
||||
|
||||
@Inject
|
||||
private EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingManager manager;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingCooking cooking;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingDialogue dialogue;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingMovement movement;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingMusic music;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingThieving thieving;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingWoodcutting woodcutting;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingZMI zmi;
|
||||
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
eventBus.register(cooking);
|
||||
eventBus.register(dialogue);
|
||||
eventBus.register(movement);
|
||||
eventBus.register(music);
|
||||
eventBus.register(thieving);
|
||||
eventBus.register(woodcutting);
|
||||
eventBus.register(zmi);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
eventBus.unregister(cooking);
|
||||
eventBus.unregister(dialogue);
|
||||
eventBus.unregister(movement);
|
||||
eventBus.unregister(music);
|
||||
eventBus.unregister(thieving);
|
||||
eventBus.unregister(woodcutting);
|
||||
eventBus.unregister(zmi);
|
||||
}
|
||||
|
||||
@Schedule(
|
||||
period = SECONDS_BETWEEN_UPLOADS,
|
||||
unit = ChronoUnit.SECONDS,
|
||||
asynchronous = true
|
||||
)
|
||||
public void submitToAPI()
|
||||
{
|
||||
manager.submitToAPI();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.cooking;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class CookingData
|
||||
{
|
||||
private final String message;
|
||||
private final boolean hasCookingGauntlets;
|
||||
private final boolean inHosidiusKitchen;
|
||||
private final boolean kourendElite;
|
||||
private final int lastGameObjectClicked;
|
||||
private final int level;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.cooking;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.InventoryID;
|
||||
import net.runelite.api.ItemContainer;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.MenuAction;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.Skill;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager;
|
||||
|
||||
public class CrowdsourcingCooking
|
||||
{
|
||||
private static final int HOSIDIUS_KITCHEN_REGION = 6712;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingManager manager;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
private int lastGameObjectClicked;
|
||||
|
||||
private boolean hasCookingGauntlets()
|
||||
{
|
||||
ItemContainer equipmentContainer = client.getItemContainer(InventoryID.EQUIPMENT);
|
||||
if (equipmentContainer == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return equipmentContainer.contains(ItemID.COOKING_GAUNTLETS);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage event)
|
||||
{
|
||||
if (event.getType() != ChatMessageType.SPAM)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final String message = event.getMessage();
|
||||
// Message prefixes taken from CookingPlugin
|
||||
if (message.startsWith("You successfully cook")
|
||||
|| message.startsWith("You successfully bake")
|
||||
|| message.startsWith("You manage to cook")
|
||||
|| message.startsWith("You roast a")
|
||||
|| message.startsWith("You cook")
|
||||
|| message.startsWith("You accidentally burn")
|
||||
|| message.startsWith("You accidentally spoil"))
|
||||
{
|
||||
boolean inHosidiusKitchen = false;
|
||||
Player local = client.getLocalPlayer();
|
||||
if (local != null && local.getWorldLocation().getRegionID() == HOSIDIUS_KITCHEN_REGION)
|
||||
{
|
||||
inHosidiusKitchen = true;
|
||||
}
|
||||
|
||||
int cookingLevel = client.getBoostedSkillLevel(Skill.COOKING);
|
||||
boolean hasCookingGauntlets = hasCookingGauntlets();
|
||||
boolean kourendElite = client.getVar(Varbits.DIARY_KOUREND_ELITE) == 1;
|
||||
CookingData data = new CookingData(message, hasCookingGauntlets, inHosidiusKitchen, kourendElite, lastGameObjectClicked, cookingLevel);
|
||||
manager.storeEvent(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked)
|
||||
{
|
||||
MenuAction action = menuOptionClicked.getMenuAction();
|
||||
if (action == MenuAction.ITEM_USE_ON_GAME_OBJECT
|
||||
|| action == MenuAction.GAME_OBJECT_FIRST_OPTION
|
||||
|| action == MenuAction.GAME_OBJECT_SECOND_OPTION
|
||||
|| action == MenuAction.GAME_OBJECT_THIRD_OPTION
|
||||
|| action == MenuAction.GAME_OBJECT_FOURTH_OPTION
|
||||
|| action == MenuAction.GAME_OBJECT_FIFTH_OPTION)
|
||||
{
|
||||
lastGameObjectClicked = menuOptionClicked.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.dialogue;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetID;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager;
|
||||
|
||||
public class CrowdsourcingDialogue
|
||||
{
|
||||
private static final String USERNAME_TOKEN = "%USERNAME%";
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingManager manager;
|
||||
|
||||
private String lastNpcDialogueText = null;
|
||||
private String lastPlayerDialogueText = null;
|
||||
private Widget[] dialogueOptions;
|
||||
|
||||
private String sanitize(String dialogue)
|
||||
{
|
||||
String username = client.getLocalPlayer().getName();
|
||||
return dialogue.replaceAll(username, USERNAME_TOKEN);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameTick(GameTick tick)
|
||||
{
|
||||
Widget npcDialogueTextWidget = client.getWidget(WidgetInfo.DIALOG_NPC_TEXT);
|
||||
if (npcDialogueTextWidget != null && !npcDialogueTextWidget.getText().equals(lastNpcDialogueText))
|
||||
{
|
||||
lastNpcDialogueText = npcDialogueTextWidget.getText();
|
||||
String npcName = client.getWidget(WidgetInfo.DIALOG_NPC_NAME).getText();
|
||||
NpcDialogueData data = new NpcDialogueData(sanitize(lastNpcDialogueText), npcName);
|
||||
manager.storeEvent(data);
|
||||
}
|
||||
|
||||
Widget playerDialogueTextWidget = client.getWidget(WidgetID.DIALOG_PLAYER_GROUP_ID, 4);
|
||||
if (playerDialogueTextWidget != null && !playerDialogueTextWidget.getText().equals(lastPlayerDialogueText))
|
||||
{
|
||||
lastPlayerDialogueText = playerDialogueTextWidget.getText();
|
||||
PlayerDialogueData data = new PlayerDialogueData(sanitize(lastPlayerDialogueText));
|
||||
manager.storeEvent(data);
|
||||
}
|
||||
|
||||
Widget playerDialogueOptionsWidget = client.getWidget(WidgetID.DIALOG_OPTION_GROUP_ID, 1);
|
||||
if (playerDialogueOptionsWidget != null && playerDialogueOptionsWidget.getChildren() != dialogueOptions)
|
||||
{
|
||||
dialogueOptions = playerDialogueOptionsWidget.getChildren();
|
||||
String[] optionsText = new String[dialogueOptions.length];
|
||||
for (int i = 0; i < dialogueOptions.length; i++)
|
||||
{
|
||||
optionsText[i] = sanitize(dialogueOptions[i].getText());
|
||||
}
|
||||
DialogueOptionsData data = new DialogueOptionsData(optionsText);
|
||||
manager.storeEvent(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.dialogue;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class DialogueOptionsData
|
||||
{
|
||||
private final String[] options;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.dialogue;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class NpcDialogueData
|
||||
{
|
||||
private final String message;
|
||||
private final String name;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.dialogue;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class PlayerDialogueData
|
||||
{
|
||||
private final String message;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.movement;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.MenuAction;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager;
|
||||
|
||||
public class CrowdsourcingMovement
|
||||
{
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingManager manager;
|
||||
|
||||
private WorldPoint lastPoint;
|
||||
private int ticksStill;
|
||||
private boolean lastIsInInstance;
|
||||
private MenuOptionClicked lastClick;
|
||||
|
||||
@Subscribe
|
||||
public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked)
|
||||
{
|
||||
if (menuOptionClicked.getMenuAction() != MenuAction.WALK
|
||||
&& !menuOptionClicked.getMenuOption().equals("Message"))
|
||||
{
|
||||
lastClick = menuOptionClicked;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameTick(GameTick tick)
|
||||
{
|
||||
LocalPoint local = LocalPoint.fromWorld(client, client.getLocalPlayer().getWorldLocation());
|
||||
WorldPoint nextPoint = WorldPoint.fromLocalInstance(client, local);
|
||||
boolean nextIsInInstance = client.isInInstancedRegion();
|
||||
if (lastPoint != null)
|
||||
{
|
||||
int distance = nextPoint.distanceTo(lastPoint);
|
||||
if (distance > 2 || nextIsInInstance != lastIsInInstance)
|
||||
{
|
||||
MovementData data = new MovementData(lastPoint, nextPoint, lastIsInInstance, nextIsInInstance, ticksStill, lastClick);
|
||||
manager.storeEvent(data);
|
||||
}
|
||||
if (distance > 0)
|
||||
{
|
||||
ticksStill = 0;
|
||||
}
|
||||
}
|
||||
ticksStill++;
|
||||
lastPoint = nextPoint;
|
||||
lastIsInInstance = nextIsInInstance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.movement;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class MovementData
|
||||
{
|
||||
private final WorldPoint start;
|
||||
private final WorldPoint end;
|
||||
private final boolean fromInstance;
|
||||
private final boolean toInstance;
|
||||
private final int ticks;
|
||||
private MenuOptionClicked lastClick;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.music;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.client.callback.ClientThread;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager;
|
||||
|
||||
public class CrowdsourcingMusic
|
||||
{
|
||||
private static final String MUSIC_UNLOCK_MESSAGE = "You have unlocked a new music track:";
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingManager manager;
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage event)
|
||||
{
|
||||
if (event.getType() == ChatMessageType.GAMEMESSAGE)
|
||||
{
|
||||
String message = event.getMessage();
|
||||
if (message.contains(MUSIC_UNLOCK_MESSAGE))
|
||||
{
|
||||
// Need to delay this by a tick because the location is not set until after the message
|
||||
clientThread.invokeLater(() ->
|
||||
{
|
||||
LocalPoint local = LocalPoint.fromWorld(client, client.getLocalPlayer().getWorldLocation());
|
||||
WorldPoint location = WorldPoint.fromLocalInstance(client, local);
|
||||
boolean isInInstance = client.isInInstancedRegion();
|
||||
MusicUnlockData data = new MusicUnlockData(location, isInInstance, message);
|
||||
manager.storeEvent(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.music;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class MusicUnlockData
|
||||
{
|
||||
private final WorldPoint location;
|
||||
private final boolean isInInstance;
|
||||
private final String message;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -22,13 +22,12 @@
|
||||
* (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.bank;
|
||||
|
||||
import lombok.Value;
|
||||
package net.runelite.client.plugins.crowdsourcing.skilling;
|
||||
|
||||
@Value
|
||||
class ContainerPrices
|
||||
public enum SkillingEndReason
|
||||
{
|
||||
private long gePrice;
|
||||
private long highAlchPrice;
|
||||
DEPLETED,
|
||||
INVENTORY_FULL,
|
||||
INTERRUPTED
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.skilling;
|
||||
|
||||
public enum SkillingState
|
||||
{
|
||||
READY,
|
||||
CLICKED,
|
||||
CUTTING,
|
||||
RECOVERING
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.thieving;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.InventoryID;
|
||||
import net.runelite.api.ItemContainer;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.Skill;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager;
|
||||
|
||||
public class CrowdsourcingThieving
|
||||
{
|
||||
private static final String BLACKJACK_SUCCESS = "You smack the bandit over the head and render them unconscious.";
|
||||
private static final String BLACKJACK_FAIL = "Your blow only glances off the bandit's head.";
|
||||
private static final Pattern PICKPOCKET_SUCCESS = Pattern.compile("You pick .*'s pocket\\.");
|
||||
private static final Pattern PICKPOCKET_FAIL = Pattern.compile("You fail to pick .*'s pocket\\.");
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingManager manager;
|
||||
|
||||
private int lastPickpocketTarget;
|
||||
|
||||
private boolean hasGlovesOfSilence()
|
||||
{
|
||||
ItemContainer equipmentContainer = client.getItemContainer(InventoryID.EQUIPMENT);
|
||||
if (equipmentContainer == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return equipmentContainer.contains(ItemID.GLOVES_OF_SILENCE);
|
||||
}
|
||||
|
||||
private boolean hasThievingCape()
|
||||
{
|
||||
ItemContainer equipmentContainer = client.getItemContainer(InventoryID.EQUIPMENT);
|
||||
if (equipmentContainer == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return equipmentContainer.contains(ItemID.THIEVING_CAPE) ||
|
||||
equipmentContainer.contains(ItemID.THIEVING_CAPET) ||
|
||||
equipmentContainer.contains(ItemID.MAX_CAPE);
|
||||
}
|
||||
|
||||
private int getArdougneDiary()
|
||||
{
|
||||
int easy = client.getVar(Varbits.DIARY_ARDOUGNE_EASY);
|
||||
int medium = client.getVar(Varbits.DIARY_ARDOUGNE_MEDIUM);
|
||||
int hard = client.getVar(Varbits.DIARY_ARDOUGNE_HARD);
|
||||
int elite = client.getVar(Varbits.DIARY_ARDOUGNE_ELITE);
|
||||
return easy + 2 * medium + 4 * hard + 8 * elite;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage event)
|
||||
{
|
||||
if (event.getType() != ChatMessageType.SPAM)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String message = event.getMessage();
|
||||
if (BLACKJACK_SUCCESS.equals(message)
|
||||
|| BLACKJACK_FAIL.equals(message)
|
||||
|| PICKPOCKET_FAIL.matcher(message).matches()
|
||||
|| PICKPOCKET_SUCCESS.matcher(message).matches())
|
||||
{
|
||||
WorldPoint location = client.getLocalPlayer().getWorldLocation();
|
||||
int ardougneDiary = getArdougneDiary();
|
||||
boolean silence = hasGlovesOfSilence();
|
||||
boolean thievingCape = hasThievingCape();
|
||||
int thievingLevel = client.getBoostedSkillLevel(Skill.THIEVING);
|
||||
PickpocketData data = new PickpocketData(thievingLevel, lastPickpocketTarget, message, location, silence, thievingCape, ardougneDiary);
|
||||
manager.storeEvent(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuOptionClicked(MenuOptionClicked event)
|
||||
{
|
||||
if (event.getMenuOption().equals("Pickpocket") || event.getMenuOption().equals("Knock-Out"))
|
||||
{
|
||||
NPC[] cachedNPCs = client.getCachedNPCs();
|
||||
NPC npc = cachedNPCs[event.getId()];
|
||||
lastPickpocketTarget = npc.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.thieving;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class PickpocketData
|
||||
{
|
||||
private final int level;
|
||||
private final int target;
|
||||
private final String message;
|
||||
private final WorldPoint location;
|
||||
private final boolean silence;
|
||||
private final boolean thievingCape;
|
||||
private final int ardougneDiary;
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.woodcutting;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.AnimationID;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.MenuAction;
|
||||
import net.runelite.api.ObjectID;
|
||||
import net.runelite.api.Skill;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.GameObjectDespawned;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager;
|
||||
import net.runelite.client.plugins.crowdsourcing.skilling.SkillingEndReason;
|
||||
import net.runelite.client.plugins.crowdsourcing.skilling.SkillingState;
|
||||
|
||||
public class CrowdsourcingWoodcutting
|
||||
{
|
||||
private static final String CHOPPING_MESSAGE = "You swing your axe at the tree.";
|
||||
private static final String INVENTORY_FULL_MESSAGE = "Your inventory is too full to hold any more logs.";
|
||||
private static final String NEST_MESSAGE = "A bird's nest falls out of the tree";
|
||||
private static final Set<Integer> TREE_OBJECTS = new ImmutableSet.Builder<Integer>().
|
||||
add(ObjectID.OAK_10820).
|
||||
add(ObjectID.YEW).
|
||||
add(ObjectID.TREE).
|
||||
add(ObjectID.TREE_1278).
|
||||
add(ObjectID.WILLOW).
|
||||
add(ObjectID.WILLOW_10829).
|
||||
add(ObjectID.WILLOW_10831).
|
||||
add(ObjectID.WILLOW_10833).
|
||||
add(ObjectID.SCRAPEY_TREE).
|
||||
add(ObjectID.JUNGLE_TREE_15951).
|
||||
add(ObjectID.JUNGLE_TREE_15954).
|
||||
add(ObjectID.JUNGLE_TREE_15948).
|
||||
add(ObjectID.MAPLE_TREE_10832).
|
||||
add(ObjectID.MAHOGANY).
|
||||
add(ObjectID.TEAK).
|
||||
add(ObjectID.MAGIC_TREE_10834).
|
||||
add(ObjectID.HOLLOW_TREE_10821).
|
||||
add(ObjectID.HOLLOW_TREE_10830).
|
||||
add(ObjectID.ACHEY_TREE).
|
||||
build();
|
||||
|
||||
private static final Map<Integer, Integer> AXE_ANIMS = new ImmutableMap.Builder<Integer, Integer>().
|
||||
put(AnimationID.WOODCUTTING_BRONZE, ItemID.BRONZE_AXE).
|
||||
put(AnimationID.WOODCUTTING_IRON, ItemID.IRON_AXE).
|
||||
put(AnimationID.WOODCUTTING_STEEL, ItemID.STEEL_AXE).
|
||||
put(AnimationID.WOODCUTTING_BLACK, ItemID.BLACK_AXE).
|
||||
put(AnimationID.WOODCUTTING_MITHRIL, ItemID.MITHRIL_AXE).
|
||||
put(AnimationID.WOODCUTTING_ADAMANT, ItemID.ADAMANT_AXE).
|
||||
put(AnimationID.WOODCUTTING_RUNE, ItemID.RUNE_AXE).
|
||||
put(AnimationID.WOODCUTTING_DRAGON, ItemID.DRAGON_AXE).
|
||||
put(AnimationID.WOODCUTTING_INFERNAL, ItemID.INFERNAL_AXE).
|
||||
put(AnimationID.WOODCUTTING_3A_AXE, ItemID._3RD_AGE_AXE).
|
||||
put(AnimationID.WOODCUTTING_CRYSTAL, ItemID.CRYSTAL_AXE).
|
||||
put(AnimationID.WOODCUTTING_TRAILBLAZER, ItemID.TRAILBLAZER_AXE).build();
|
||||
|
||||
private static final Set<String> SUCCESS_MESSAGES = new ImmutableSet.Builder<String>().
|
||||
add("You get some logs.").
|
||||
add("You get some oak logs.").
|
||||
add("You get some willow logs.").
|
||||
add("You get some teak logs.").
|
||||
add("You get some teak logs and give them to Carpenter Kjallak.").
|
||||
add("You get some maple logs.").
|
||||
add("You get some maple logs and give them to Lumberjack Leif.").
|
||||
add("You get some mahogany logs.").
|
||||
add("You get some mahogany logs and give them to Carpenter Kjallak.").
|
||||
add("You get some yew logs.").
|
||||
add("You get some magic logs.").
|
||||
add("You get some scrapey tree logs.").
|
||||
add("You get some bark.").
|
||||
build();
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingManager manager;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
private SkillingState state = SkillingState.RECOVERING;
|
||||
private int lastExperimentEnd = 0;
|
||||
private WorldPoint treeLocation;
|
||||
private int treeId;
|
||||
private int startTick;
|
||||
private int axe;
|
||||
private List<Integer> chopTicks = new ArrayList<>();
|
||||
private List<Integer> nestTicks = new ArrayList<>();
|
||||
|
||||
private void endExperiment(SkillingEndReason reason)
|
||||
{
|
||||
if (state == SkillingState.CUTTING)
|
||||
{
|
||||
int endTick = client.getTickCount();
|
||||
int woodcuttingLevel = client.getBoostedSkillLevel(Skill.WOODCUTTING);
|
||||
WoodcuttingData data = new WoodcuttingData(
|
||||
woodcuttingLevel,
|
||||
startTick,
|
||||
endTick,
|
||||
chopTicks,
|
||||
nestTicks,
|
||||
axe,
|
||||
treeId,
|
||||
treeLocation,
|
||||
reason
|
||||
);
|
||||
manager.storeEvent(data);
|
||||
|
||||
chopTicks = new ArrayList<>();
|
||||
nestTicks = new ArrayList<>();
|
||||
}
|
||||
state = SkillingState.RECOVERING;
|
||||
lastExperimentEnd = client.getTickCount();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage event)
|
||||
{
|
||||
final String message = event.getMessage();
|
||||
final ChatMessageType type = event.getType();
|
||||
if (state == SkillingState.CLICKED && type == ChatMessageType.SPAM && message.equals(CHOPPING_MESSAGE))
|
||||
{
|
||||
startTick = client.getTickCount();
|
||||
state = SkillingState.CUTTING;
|
||||
}
|
||||
else if (state == SkillingState.CUTTING && type == ChatMessageType.SPAM && SUCCESS_MESSAGES.contains(message))
|
||||
{
|
||||
chopTicks.add(client.getTickCount());
|
||||
}
|
||||
else if (state == SkillingState.CUTTING && type == ChatMessageType.GAMEMESSAGE && message.equals(INVENTORY_FULL_MESSAGE))
|
||||
{
|
||||
endExperiment(SkillingEndReason.INVENTORY_FULL);
|
||||
}
|
||||
else if (state == SkillingState.CUTTING && type == ChatMessageType.GAMEMESSAGE && message.contains(NEST_MESSAGE))
|
||||
{
|
||||
nestTicks.add(client.getTickCount());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameTick(GameTick tick)
|
||||
{
|
||||
int animId = client.getLocalPlayer().getAnimation();
|
||||
if (state == SkillingState.CUTTING)
|
||||
{
|
||||
if (AXE_ANIMS.containsKey(animId))
|
||||
{
|
||||
axe = AXE_ANIMS.get(animId);
|
||||
}
|
||||
else
|
||||
{
|
||||
endExperiment(SkillingEndReason.INTERRUPTED);
|
||||
}
|
||||
}
|
||||
else if (animId != -1)
|
||||
{
|
||||
endExperiment(SkillingEndReason.INTERRUPTED);
|
||||
}
|
||||
else if (state == SkillingState.RECOVERING && client.getTickCount() - lastExperimentEnd >= 4)
|
||||
{
|
||||
state = SkillingState.READY;
|
||||
}
|
||||
else if (state == SkillingState.CLICKED && client.getTickCount() - lastExperimentEnd >= 20)
|
||||
{
|
||||
state = SkillingState.READY;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked)
|
||||
{
|
||||
MenuAction menuAction = menuOptionClicked.getMenuAction();
|
||||
int id = menuOptionClicked.getId();
|
||||
if (state == SkillingState.READY && menuAction == MenuAction.GAME_OBJECT_FIRST_OPTION && TREE_OBJECTS.contains(id))
|
||||
{
|
||||
state = SkillingState.CLICKED;
|
||||
lastExperimentEnd = client.getTickCount();
|
||||
treeId = id;
|
||||
treeLocation = WorldPoint.fromScene(client, menuOptionClicked.getActionParam(), menuOptionClicked.getWidgetId(), client.getPlane());
|
||||
}
|
||||
else
|
||||
{
|
||||
endExperiment(SkillingEndReason.INTERRUPTED);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameObjectDespawned(GameObjectDespawned event)
|
||||
{
|
||||
if (state != SkillingState.CUTTING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (treeId == event.getGameObject().getId() && treeLocation.equals(event.getTile().getWorldLocation()))
|
||||
{
|
||||
endExperiment(SkillingEndReason.DEPLETED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.woodcutting;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.client.plugins.crowdsourcing.skilling.SkillingEndReason;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class WoodcuttingData
|
||||
{
|
||||
private final int level;
|
||||
private final int startTick;
|
||||
private final int endTick;
|
||||
private final List<Integer> chopTicks;
|
||||
private final List<Integer> nestTicks;
|
||||
private final int axe;
|
||||
private final int treeId;
|
||||
private final WorldPoint treeLocation;
|
||||
private final SkillingEndReason reason;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.zmi;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.common.collect.Multisets;
|
||||
import java.util.Arrays;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.InventoryID;
|
||||
import net.runelite.api.ItemContainer;
|
||||
import net.runelite.api.MenuAction;
|
||||
import net.runelite.api.Skill;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.ItemContainerChanged;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
import net.runelite.api.events.StatChanged;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager;
|
||||
|
||||
public class CrowdsourcingZMI
|
||||
{
|
||||
private static final String CHAT_MESSAGE_ZMI = "You bind the temple's power into runes.";
|
||||
|
||||
@Inject
|
||||
private CrowdsourcingManager manager;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
private int gameTickZMI = -1;
|
||||
private int illegalActionTick = -1;
|
||||
private int previousRunecraftXp = 0;
|
||||
private int runecraftXpGained = 0;
|
||||
private Multiset<Integer> previousInventorySnapshot;
|
||||
|
||||
private Multiset<Integer> getInventorySnapshot()
|
||||
{
|
||||
final ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY);
|
||||
Multiset<Integer> inventorySnapshot = HashMultiset.create();
|
||||
|
||||
if (inventory != null)
|
||||
{
|
||||
Arrays.stream(inventory.getItems())
|
||||
.forEach(item -> inventorySnapshot.add(item.getId(), item.getQuantity()));
|
||||
}
|
||||
|
||||
return inventorySnapshot;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked)
|
||||
{
|
||||
MenuAction action = menuOptionClicked.getMenuAction();
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case ITEM_FIRST_OPTION:
|
||||
case ITEM_SECOND_OPTION:
|
||||
case ITEM_THIRD_OPTION:
|
||||
case ITEM_FOURTH_OPTION:
|
||||
case ITEM_FIFTH_OPTION:
|
||||
case GROUND_ITEM_FIRST_OPTION:
|
||||
case GROUND_ITEM_SECOND_OPTION:
|
||||
case GROUND_ITEM_THIRD_OPTION:
|
||||
case GROUND_ITEM_FOURTH_OPTION:
|
||||
case GROUND_ITEM_FIFTH_OPTION:
|
||||
case ITEM_DROP:
|
||||
illegalActionTick = client.getTickCount();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage chatMessage)
|
||||
{
|
||||
if (chatMessage.getMessage().equals(CHAT_MESSAGE_ZMI))
|
||||
{
|
||||
gameTickZMI = client.getTickCount();
|
||||
previousRunecraftXp = client.getSkillExperience(Skill.RUNECRAFT);
|
||||
previousInventorySnapshot = getInventorySnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatChanged(StatChanged statChanged)
|
||||
{
|
||||
if (gameTickZMI == client.getTickCount())
|
||||
{
|
||||
int currentRunecraftXp = statChanged.getXp();
|
||||
runecraftXpGained = currentRunecraftXp - previousRunecraftXp;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onItemContainerChanged(ItemContainerChanged event)
|
||||
{
|
||||
int itemContainerChangedTick = client.getTickCount();
|
||||
|
||||
if (event.getItemContainer() != client.getItemContainer(InventoryID.INVENTORY) || gameTickZMI != itemContainerChangedTick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int tickDelta = itemContainerChangedTick - illegalActionTick;
|
||||
boolean ardougneMedium = client.getVar(Varbits.DIARY_ARDOUGNE_MEDIUM) == 1;
|
||||
int runecraftBoostedLevel = client.getBoostedSkillLevel(Skill.RUNECRAFT);
|
||||
Multiset<Integer> currentInventorySnapshot = getInventorySnapshot();
|
||||
final Multiset<Integer> itemsReceived = Multisets.difference(currentInventorySnapshot, previousInventorySnapshot);
|
||||
final Multiset<Integer> itemsRemoved = Multisets.difference(previousInventorySnapshot, currentInventorySnapshot);
|
||||
|
||||
ZMIData data = new ZMIData(tickDelta, ardougneMedium, runecraftBoostedLevel, runecraftXpGained, itemsReceived, itemsRemoved);
|
||||
manager.storeEvent(data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Weird Gloop <admin@weirdgloop.org>
|
||||
* 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.crowdsourcing.zmi;
|
||||
|
||||
import com.google.common.collect.Multiset;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ZMIData
|
||||
{
|
||||
private final int tickDelta;
|
||||
private final boolean ardougneMedium;
|
||||
private final int level;
|
||||
private final int xpGained;
|
||||
private final Multiset<Integer> itemsReceived;
|
||||
private final Multiset<Integer> itemsRemoved;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Kruithne <kruithne@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.customcursor;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.Getter;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
|
||||
@Getter
|
||||
public enum CustomCursor
|
||||
{
|
||||
RS3_GOLD("RS3 Gold", "cursor-rs3-gold.png"),
|
||||
RS3_SILVER("RS3 Silver", "cursor-rs3-silver.png"),
|
||||
DRAGON_DAGGER("Dragon Dagger", "cursor-dragon-dagger.png"),
|
||||
DRAGON_DAGGER_POISON("Dragon Dagger (p)", "cursor-dragon-dagger-p.png"),
|
||||
TROUT("Trout", "cursor-trout.png"),
|
||||
DRAGON_SCIMITAR("Dragon Scimitar", "cursor-dragon-scimitar.png"),
|
||||
EQUIPPED_WEAPON("Equipped Weapon"),
|
||||
CUSTOM_IMAGE("Custom Image");
|
||||
|
||||
private final String name;
|
||||
@Nullable
|
||||
private final BufferedImage cursorImage;
|
||||
|
||||
CustomCursor(String name)
|
||||
{
|
||||
this.name = name;
|
||||
this.cursorImage = null;
|
||||
}
|
||||
|
||||
CustomCursor(String name, String icon)
|
||||
{
|
||||
this.name = name;
|
||||
this.cursorImage = ImageUtil.getResourceStreamFromClass(CustomCursorPlugin.class, icon);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Kruithne <kruithne@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.customcursor;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup("customcursor")
|
||||
public interface CustomCursorConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
keyName = "cursorStyle",
|
||||
name = "Cursor",
|
||||
description = "Select which cursor you wish to use"
|
||||
)
|
||||
default CustomCursor selectedCursor()
|
||||
{
|
||||
return CustomCursor.RS3_GOLD;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Kruithne <kruithne@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.customcursor;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.inject.Inject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.EquipmentInventorySlot;
|
||||
import net.runelite.api.InventoryID;
|
||||
import net.runelite.api.Item;
|
||||
import net.runelite.api.ItemContainer;
|
||||
import net.runelite.api.events.ItemContainerChanged;
|
||||
import net.runelite.client.RuneLite;
|
||||
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.ClientUI;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Custom Cursor",
|
||||
description = "Replaces your mouse cursor image",
|
||||
enabledByDefault = false
|
||||
)
|
||||
@Slf4j
|
||||
public class CustomCursorPlugin extends Plugin
|
||||
{
|
||||
private static final File CUSTOM_IMAGE_FILE = new File(RuneLite.RUNELITE_DIR, "cursor.png");
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private ClientUI clientUI;
|
||||
|
||||
@Inject
|
||||
private CustomCursorConfig config;
|
||||
|
||||
@Inject
|
||||
private ItemManager itemManager;
|
||||
|
||||
@Provides
|
||||
CustomCursorConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(CustomCursorConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
clientUI.resetCursor();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged event)
|
||||
{
|
||||
if (event.getGroup().equals("customcursor") && event.getKey().equals("cursorStyle"))
|
||||
{
|
||||
updateCursor();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onItemContainerChanged(ItemContainerChanged event)
|
||||
{
|
||||
if (config.selectedCursor() == CustomCursor.EQUIPPED_WEAPON && event.getContainerId() == InventoryID.EQUIPMENT.getId())
|
||||
{
|
||||
updateCursor();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCursor()
|
||||
{
|
||||
CustomCursor selectedCursor = config.selectedCursor();
|
||||
|
||||
if (selectedCursor == CustomCursor.CUSTOM_IMAGE)
|
||||
{
|
||||
if (CUSTOM_IMAGE_FILE.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
BufferedImage image;
|
||||
synchronized (ImageIO.class)
|
||||
{
|
||||
image = ImageIO.read(CUSTOM_IMAGE_FILE);
|
||||
}
|
||||
clientUI.setCursor(image, selectedCursor.getName());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("error setting custom cursor", e);
|
||||
clientUI.resetCursor();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clientUI.resetCursor();
|
||||
}
|
||||
}
|
||||
else if (selectedCursor == CustomCursor.EQUIPPED_WEAPON)
|
||||
{
|
||||
clientThread.invokeLater(() ->
|
||||
{
|
||||
final ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT);
|
||||
|
||||
if (equipment == null)
|
||||
{
|
||||
clientUI.resetCursor();
|
||||
return;
|
||||
}
|
||||
|
||||
Item weapon = equipment.getItem(EquipmentInventorySlot.WEAPON.getSlotIdx());
|
||||
if (weapon == null)
|
||||
{
|
||||
clientUI.resetCursor();
|
||||
return;
|
||||
}
|
||||
|
||||
final BufferedImage image = itemManager.getImage(weapon.getId());
|
||||
|
||||
if (weapon.getQuantity() > 0)
|
||||
{
|
||||
clientUI.setCursor(image, selectedCursor.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
clientUI.resetCursor();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
assert selectedCursor.getCursorImage() != null;
|
||||
clientUI.setCursor(selectedCursor.getCursorImage(), selectedCursor.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Infinitay <https://github.com/Infinitay>
|
||||
* Copyright (c) 2018, Shaun Dreclin <https://github.com/ShaunDreclin>
|
||||
*
|
||||
* 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.dailytaskindicators;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup("dailytaskindicators")
|
||||
public interface DailyTasksConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
position = 1,
|
||||
keyName = "showHerbBoxes",
|
||||
name = "Show Herb Boxes",
|
||||
description = "Show a message when you can collect your daily herb boxes at NMZ."
|
||||
)
|
||||
default boolean showHerbBoxes()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 2,
|
||||
keyName = "showStaves",
|
||||
name = "Show Claimable Staves",
|
||||
description = "Show a message when you can collect your daily battlestaves from Zaff."
|
||||
)
|
||||
default boolean showStaves()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 3,
|
||||
keyName = "showEssence",
|
||||
name = "Show Claimable Essence",
|
||||
description = "Show a message when you can collect your daily pure essence from Wizard Cromperty."
|
||||
)
|
||||
default boolean showEssence()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 4,
|
||||
keyName = "showRunes",
|
||||
name = "Show Claimable Random Runes",
|
||||
description = "Show a message when you can collect your daily random runes from Lundail."
|
||||
)
|
||||
default boolean showRunes()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 5,
|
||||
keyName = "showSand",
|
||||
name = "Show Claimable Sand",
|
||||
description = "Show a message when you can collect your daily sand from Bert."
|
||||
)
|
||||
default boolean showSand()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 6,
|
||||
keyName = "showFlax",
|
||||
name = "Show Claimable Bow Strings",
|
||||
description = "Show a message when you can convert noted flax to bow strings with the Flax keeper."
|
||||
)
|
||||
default boolean showFlax()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 7,
|
||||
keyName = "showBonemeal",
|
||||
name = "Show Claimable Bonemeal & Slime",
|
||||
description = "Show a message when you can collect bonemeal & slime from Robin."
|
||||
)
|
||||
default boolean showBonemeal()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 8,
|
||||
keyName = "showDynamite",
|
||||
name = "Show Claimable Dynamite",
|
||||
description = "Show a message when you can collect Dynamite from Thirus."
|
||||
)
|
||||
default boolean showDynamite()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 9,
|
||||
keyName = "showArrows",
|
||||
name = "Show Claimable Ogre Arrows",
|
||||
description = "Show a message when you can collect ogre arrows from Rantz."
|
||||
)
|
||||
default boolean showArrows()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Infinitay <https://github.com/Infinitay>
|
||||
* Copyright (c) 2018-2019, Shaun Dreclin <https://github.com/ShaunDreclin>
|
||||
* 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.dailytaskindicators;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.VarClientInt;
|
||||
import net.runelite.api.VarPlayer;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.vars.AccountType;
|
||||
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.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Daily Task Indicator",
|
||||
description = "Show chat notifications for daily tasks upon login"
|
||||
)
|
||||
public class DailyTasksPlugin extends Plugin
|
||||
{
|
||||
private static final int ONE_DAY = 86400000;
|
||||
|
||||
private static final String HERB_BOX_MESSAGE = "You have herb boxes waiting to be collected at NMZ.";
|
||||
private static final int HERB_BOX_MAX = 15;
|
||||
private static final int HERB_BOX_COST = 9500;
|
||||
private static final String STAVES_MESSAGE = "You have battlestaves waiting to be collected from Zaff.";
|
||||
private static final String ESSENCE_MESSAGE = "You have essence waiting to be collected from Wizard Cromperty.";
|
||||
private static final String RUNES_MESSAGE = "You have random runes waiting to be collected from Lundail.";
|
||||
private static final String SAND_MESSAGE = "You have sand waiting to be collected from Bert.";
|
||||
private static final int SAND_QUEST_COMPLETE = 160;
|
||||
private static final String FLAX_MESSAGE = "You have bowstrings waiting to be converted from flax from the Flax keeper.";
|
||||
private static final String ARROWS_MESSAGE = "You have ogre arrows waiting to be collected from Rantz.";
|
||||
private static final String BONEMEAL_MESSAGE = "You have bonemeal and slime waiting to be collected from Robin.";
|
||||
private static final int BONEMEAL_PER_DIARY = 13;
|
||||
private static final String DYNAMITE_MESSAGE = "You have dynamite waiting to be collected from Thirus.";
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private DailyTasksConfig config;
|
||||
|
||||
@Inject
|
||||
private ChatMessageManager chatMessageManager;
|
||||
|
||||
private long lastReset;
|
||||
private boolean loggingIn;
|
||||
|
||||
@Provides
|
||||
DailyTasksConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(DailyTasksConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startUp()
|
||||
{
|
||||
loggingIn = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutDown()
|
||||
{
|
||||
lastReset = 0L;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged event)
|
||||
{
|
||||
if (event.getGameState() == GameState.LOGGING_IN)
|
||||
{
|
||||
loggingIn = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameTick(GameTick event)
|
||||
{
|
||||
long currentTime = System.currentTimeMillis();
|
||||
boolean dailyReset = !loggingIn && currentTime - lastReset > ONE_DAY;
|
||||
|
||||
if ((dailyReset || loggingIn)
|
||||
&& client.getVar(VarClientInt.MEMBERSHIP_STATUS) == 1)
|
||||
{
|
||||
// Round down to the nearest day
|
||||
lastReset = (long) Math.floor(currentTime / ONE_DAY) * ONE_DAY;
|
||||
loggingIn = false;
|
||||
|
||||
if (config.showHerbBoxes())
|
||||
{
|
||||
checkHerbBoxes(dailyReset);
|
||||
}
|
||||
|
||||
if (config.showStaves())
|
||||
{
|
||||
checkStaves(dailyReset);
|
||||
}
|
||||
|
||||
if (config.showEssence())
|
||||
{
|
||||
checkEssence(dailyReset);
|
||||
}
|
||||
|
||||
if (config.showRunes())
|
||||
{
|
||||
checkRunes(dailyReset);
|
||||
}
|
||||
|
||||
if (config.showSand())
|
||||
{
|
||||
checkSand(dailyReset);
|
||||
}
|
||||
|
||||
if (config.showFlax())
|
||||
{
|
||||
checkFlax(dailyReset);
|
||||
}
|
||||
|
||||
if (config.showBonemeal())
|
||||
{
|
||||
checkBonemeal(dailyReset);
|
||||
}
|
||||
|
||||
if (config.showDynamite())
|
||||
{
|
||||
checkDynamite(dailyReset);
|
||||
}
|
||||
|
||||
if (config.showArrows())
|
||||
{
|
||||
checkArrows(dailyReset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkHerbBoxes(boolean dailyReset)
|
||||
{
|
||||
if (client.getAccountType() == AccountType.NORMAL
|
||||
&& client.getVar(VarPlayer.NMZ_REWARD_POINTS) >= HERB_BOX_COST
|
||||
&& (client.getVar(Varbits.DAILY_HERB_BOXES_COLLECTED) < HERB_BOX_MAX
|
||||
|| dailyReset))
|
||||
{
|
||||
sendChatMessage(HERB_BOX_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStaves(boolean dailyReset)
|
||||
{
|
||||
if (client.getVar(Varbits.DIARY_VARROCK_EASY) == 1
|
||||
&& (client.getVar(Varbits.DAILY_STAVES_COLLECTED) == 0
|
||||
|| dailyReset))
|
||||
{
|
||||
sendChatMessage(STAVES_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEssence(boolean dailyReset)
|
||||
{
|
||||
if (client.getVar(Varbits.DIARY_ARDOUGNE_MEDIUM) == 1
|
||||
&& (client.getVar(Varbits.DAILY_ESSENCE_COLLECTED) == 0
|
||||
|| dailyReset))
|
||||
{
|
||||
sendChatMessage(ESSENCE_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRunes(boolean dailyReset)
|
||||
{
|
||||
if (client.getVar(Varbits.DIARY_WILDERNESS_EASY) == 1
|
||||
&& (client.getVar(Varbits.DAILY_RUNES_COLLECTED) == 0
|
||||
|| dailyReset))
|
||||
{
|
||||
sendChatMessage(RUNES_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSand(boolean dailyReset)
|
||||
{
|
||||
if (client.getVar(Varbits.QUEST_THE_HAND_IN_THE_SAND) >= SAND_QUEST_COMPLETE
|
||||
&& (client.getVar(Varbits.DAILY_SAND_COLLECTED) == 0
|
||||
|| dailyReset))
|
||||
{
|
||||
sendChatMessage(SAND_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFlax(boolean dailyReset)
|
||||
{
|
||||
if (client.getVar(Varbits.DIARY_KANDARIN_EASY) == 1
|
||||
&& (client.getVar(Varbits.DAILY_FLAX_STATE) == 0
|
||||
|| dailyReset))
|
||||
{
|
||||
sendChatMessage(FLAX_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkArrows(boolean dailyReset)
|
||||
{
|
||||
if (client.getVar(Varbits.DIARY_WESTERN_EASY) == 1
|
||||
&& (client.getVar(Varbits.DAILY_ARROWS_STATE) == 0
|
||||
|| dailyReset))
|
||||
{
|
||||
sendChatMessage(ARROWS_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkBonemeal(boolean dailyReset)
|
||||
{
|
||||
if (client.getVar(Varbits.DIARY_MORYTANIA_MEDIUM) == 1)
|
||||
{
|
||||
int collected = client.getVar(Varbits.DAILY_BONEMEAL_STATE);
|
||||
int max = BONEMEAL_PER_DIARY;
|
||||
if (client.getVar(Varbits.DIARY_MORYTANIA_HARD) == 1)
|
||||
{
|
||||
max += BONEMEAL_PER_DIARY;
|
||||
if (client.getVar(Varbits.DIARY_MORYTANIA_ELITE) == 1)
|
||||
{
|
||||
max += BONEMEAL_PER_DIARY;
|
||||
}
|
||||
}
|
||||
if (collected < max || dailyReset)
|
||||
{
|
||||
sendChatMessage(BONEMEAL_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDynamite(boolean dailyReset)
|
||||
{
|
||||
if (client.getVar(Varbits.DIARY_KOUREND_MEDIUM) == 1
|
||||
&& (client.getVar(Varbits.DAILY_DYNAMITE_COLLECTED) == 0
|
||||
|| dailyReset))
|
||||
{
|
||||
sendChatMessage(DYNAMITE_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendChatMessage(String chatMessage)
|
||||
{
|
||||
final String message = new ChatMessageBuilder()
|
||||
.append(ChatColorType.HIGHLIGHT)
|
||||
.append(chatMessage)
|
||||
.build();
|
||||
|
||||
chatMessageManager.queue(
|
||||
QueuedMessage.builder()
|
||||
.type(ChatMessageType.CONSOLE)
|
||||
.runeLiteFormattedMessage(message)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@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.defaultworld;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup(DefaultWorldConfig.GROUP)
|
||||
public interface DefaultWorldConfig extends Config
|
||||
{
|
||||
String GROUP = "defaultworld";
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "defaultWorld",
|
||||
name = "Default world",
|
||||
description = "World to use as default one"
|
||||
)
|
||||
default int getWorld()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "useLastWorld",
|
||||
name = "Use Last World",
|
||||
description = "Use the last world you used as the default"
|
||||
)
|
||||
default boolean useLastWorld()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "lastWorld",
|
||||
name = "",
|
||||
description = "",
|
||||
hidden = true
|
||||
)
|
||||
default int lastWorld()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "lastWorld",
|
||||
name = "",
|
||||
description = ""
|
||||
)
|
||||
void lastWorld(int lastWorld);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@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.defaultworld;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import javax.inject.Inject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.events.SessionOpen;
|
||||
import net.runelite.client.game.WorldService;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.util.WorldUtil;
|
||||
import net.runelite.http.api.worlds.World;
|
||||
import net.runelite.http.api.worlds.WorldResult;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Default World",
|
||||
description = "Enable a default world to be selected when launching the client",
|
||||
tags = {"home"}
|
||||
)
|
||||
@Slf4j
|
||||
public class DefaultWorldPlugin extends Plugin
|
||||
{
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private DefaultWorldConfig config;
|
||||
|
||||
@Inject
|
||||
private WorldService worldService;
|
||||
|
||||
private int worldCache;
|
||||
private boolean worldChangeRequired;
|
||||
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
worldChangeRequired = true;
|
||||
applyWorld();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
worldChangeRequired = true;
|
||||
changeWorld(worldCache);
|
||||
}
|
||||
|
||||
@Provides
|
||||
DefaultWorldConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(DefaultWorldConfig.class);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onSessionOpen(SessionOpen event)
|
||||
{
|
||||
worldChangeRequired = true;
|
||||
applyWorld();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged event)
|
||||
{
|
||||
if (event.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
config.lastWorld(client.getWorld());
|
||||
}
|
||||
|
||||
applyWorld();
|
||||
}
|
||||
|
||||
private void changeWorld(int newWorld)
|
||||
{
|
||||
if (!worldChangeRequired || client.getGameState() != GameState.LOGIN_SCREEN)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
worldChangeRequired = false;
|
||||
int correctedWorld = newWorld < 300 ? newWorld + 300 : newWorld;
|
||||
|
||||
// Old School RuneScape worlds start on 301 so don't even bother trying to find lower id ones
|
||||
// and also do not try to set world if we are already on it
|
||||
if (correctedWorld <= 300 || client.getWorld() == correctedWorld)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final WorldResult worldResult = worldService.getWorlds();
|
||||
|
||||
if (worldResult == null)
|
||||
{
|
||||
log.warn("Failed to lookup worlds.");
|
||||
return;
|
||||
}
|
||||
|
||||
final World world = worldResult.findWorld(correctedWorld);
|
||||
|
||||
if (world != null)
|
||||
{
|
||||
final net.runelite.api.World rsWorld = client.createWorld();
|
||||
rsWorld.setActivity(world.getActivity());
|
||||
rsWorld.setAddress(world.getAddress());
|
||||
rsWorld.setId(world.getId());
|
||||
rsWorld.setPlayerCount(world.getPlayers());
|
||||
rsWorld.setLocation(world.getLocation());
|
||||
rsWorld.setTypes(WorldUtil.toWorldTypes(world.getTypes()));
|
||||
|
||||
client.changeWorld(rsWorld);
|
||||
log.debug("Applied new world {}", correctedWorld);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("World {} not found.", correctedWorld);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyWorld()
|
||||
{
|
||||
if (worldCache == 0)
|
||||
{
|
||||
worldCache = client.getWorld();
|
||||
log.debug("Stored old world {}", worldCache);
|
||||
}
|
||||
|
||||
final int newWorld = !config.useLastWorld() ? config.getWorld() : config.lastWorld();
|
||||
changeWorld(newWorld);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2018, PandahRS <https://github.com/PandahRS>
|
||||
* 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.discord;
|
||||
|
||||
enum DiscordAreaType
|
||||
{
|
||||
BOSSES,
|
||||
CITIES,
|
||||
DUNGEONS,
|
||||
MINIGAMES,
|
||||
RAIDS,
|
||||
REGIONS
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@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.discord;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
import net.runelite.client.config.Units;
|
||||
|
||||
@ConfigGroup("discord")
|
||||
public interface DiscordConfig extends Config
|
||||
{
|
||||
@AllArgsConstructor
|
||||
enum ElapsedTimeType
|
||||
{
|
||||
TOTAL("Total elapsed time"),
|
||||
ACTIVITY("Per activity"),
|
||||
HIDDEN("Hide elapsed time");
|
||||
|
||||
private final String value;
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "elapsedTime",
|
||||
name = "Elapsed Time",
|
||||
description = "Configures elapsed time shown.",
|
||||
position = 1
|
||||
)
|
||||
default ElapsedTimeType elapsedTimeType()
|
||||
{
|
||||
return ElapsedTimeType.ACTIVITY;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "actionTimeout",
|
||||
name = "Activity timeout",
|
||||
description = "Configures after how long of not updating activity will be reset (in minutes)",
|
||||
position = 2
|
||||
)
|
||||
@Units(Units.MINUTES)
|
||||
default int actionTimeout()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showSkillActivity",
|
||||
name = "Skilling",
|
||||
description = "Show your activity while training skills",
|
||||
position = 3
|
||||
)
|
||||
default boolean showSkillingActivity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showBossActivity",
|
||||
name = "Bosses",
|
||||
description = "Show your activity and location while at bosses",
|
||||
position = 4
|
||||
)
|
||||
default boolean showBossActivity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showCityActivity",
|
||||
name = "Cities",
|
||||
description = "Show your activity and location while in cities",
|
||||
position = 5
|
||||
)
|
||||
default boolean showCityActivity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showDungeonActivity",
|
||||
name = "Dungeons",
|
||||
description = "Show your activity and location while in dungeons",
|
||||
position = 6
|
||||
)
|
||||
default boolean showDungeonActivity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showMinigameActivity",
|
||||
name = "Minigames",
|
||||
description = "Show your activity and location while in minigames",
|
||||
position = 7
|
||||
)
|
||||
default boolean showMinigameActivity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showRaidingActivity",
|
||||
name = "Raids",
|
||||
description = "Show your activity and location while in Raids",
|
||||
position = 8
|
||||
)
|
||||
default boolean showRaidingActivity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showRegionsActivity",
|
||||
name = "Regions",
|
||||
description = "Show your activity and location while in other regions",
|
||||
position = 9
|
||||
)
|
||||
default boolean showRegionsActivity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
|
||||
* Copyright (c) 2018, PandahRS <https://github.com/PandahRS>
|
||||
* Copyright (c) 2020, Brooklyn <https://github.com/Broooklyn>
|
||||
* 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.discord;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Skill;
|
||||
import net.runelite.api.Varbits;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
enum DiscordGameEventType
|
||||
{
|
||||
|
||||
IN_MENU("In Menu", -3, true, true, true, false, true),
|
||||
IN_GAME("In Game", -3, true, false, false, false, true),
|
||||
PLAYING_DEADMAN("Playing Deadman Mode", -3),
|
||||
PLAYING_PVP("Playing in a PVP world", -3),
|
||||
TRAINING_ATTACK(Skill.ATTACK),
|
||||
TRAINING_DEFENCE(Skill.DEFENCE),
|
||||
TRAINING_STRENGTH(Skill.STRENGTH),
|
||||
TRAINING_HITPOINTS(Skill.HITPOINTS, -1),
|
||||
TRAINING_SLAYER(Skill.SLAYER, 1),
|
||||
TRAINING_RANGED(Skill.RANGED),
|
||||
TRAINING_MAGIC(Skill.MAGIC),
|
||||
TRAINING_PRAYER(Skill.PRAYER),
|
||||
TRAINING_COOKING(Skill.COOKING),
|
||||
TRAINING_WOODCUTTING(Skill.WOODCUTTING),
|
||||
TRAINING_FLETCHING(Skill.FLETCHING),
|
||||
TRAINING_FISHING(Skill.FISHING, 1),
|
||||
TRAINING_FIREMAKING(Skill.FIREMAKING),
|
||||
TRAINING_CRAFTING(Skill.CRAFTING),
|
||||
TRAINING_SMITHING(Skill.SMITHING),
|
||||
TRAINING_MINING(Skill.MINING),
|
||||
TRAINING_HERBLORE(Skill.HERBLORE),
|
||||
TRAINING_AGILITY(Skill.AGILITY),
|
||||
TRAINING_THIEVING(Skill.THIEVING),
|
||||
TRAINING_FARMING(Skill.FARMING),
|
||||
TRAINING_RUNECRAFT(Skill.RUNECRAFT),
|
||||
TRAINING_HUNTER(Skill.HUNTER),
|
||||
TRAINING_CONSTRUCTION(Skill.CONSTRUCTION),
|
||||
|
||||
// Bosses
|
||||
BOSS_ABYSSAL_SIRE("Abyssal Sire", DiscordAreaType.BOSSES, 11851, 11850, 12363, 12362),
|
||||
BOSS_CERBERUS("Cerberus", DiscordAreaType.BOSSES, 4883, 5140, 5395),
|
||||
BOSS_COMMANDER_ZILYANA("Commander Zilyana", DiscordAreaType.BOSSES, 11602),
|
||||
BOSS_CORP("Corporeal Beast", DiscordAreaType.BOSSES, 11842, 11844),
|
||||
BOSS_DKS("Dagannoth Kings", DiscordAreaType.BOSSES, 11588, 11589),
|
||||
BOSS_GENERAL_GRAARDOR("General Graardor", DiscordAreaType.BOSSES, 11347),
|
||||
BOSS_GIANT_MOLE("Giant Mole", DiscordAreaType.BOSSES, 6993, 6992),
|
||||
BOSS_GROTESQUE_GUARDIANS("Grotesque Guardians", DiscordAreaType.BOSSES, 6727),
|
||||
BOSS_HESPORI("Hespori", DiscordAreaType.BOSSES, 5021),
|
||||
BOSS_HYDRA("Alchemical Hydra", DiscordAreaType.BOSSES, 5536),
|
||||
BOSS_KQ("Kalphite Queen", DiscordAreaType.BOSSES, 13972),
|
||||
BOSS_KRAKEN("Kraken", DiscordAreaType.BOSSES, 9116),
|
||||
BOSS_KREEARRA("Kree'arra", DiscordAreaType.BOSSES, 11346),
|
||||
BOSS_KRIL_TSUTSAROTH("K'ril Tsutsaroth", DiscordAreaType.BOSSES, 11603),
|
||||
BOSS_NIGHTMARE("Nightmare of Ashihama", DiscordAreaType.BOSSES, 15515),
|
||||
BOSS_SARACHNIS("Sarachnis", DiscordAreaType.BOSSES, 7322),
|
||||
BOSS_SKOTIZO("Skotizo", DiscordAreaType.BOSSES, 6810),
|
||||
BOSS_SMOKE_DEVIL("Thermonuclear smoke devil", DiscordAreaType.BOSSES, 9363, 9619),
|
||||
BOSS_VORKATH("Vorkath", DiscordAreaType.BOSSES, 9023),
|
||||
BOSS_WINTERTODT("Wintertodt", DiscordAreaType.BOSSES, 6462),
|
||||
BOSS_ZALCANO("Zalcano", DiscordAreaType.BOSSES, 12126),
|
||||
BOSS_ZULRAH("Zulrah", DiscordAreaType.BOSSES, 9007),
|
||||
|
||||
// Cities
|
||||
CITY_AL_KHARID("Al Kharid" , DiscordAreaType.CITIES, 13105, 13106),
|
||||
CITY_ARCEUUS_HOUSE("Arceuus" , DiscordAreaType.CITIES, 6458, 6459, 6460, 6714, 6715),
|
||||
CITY_ARDOUGNE("Ardougne" , DiscordAreaType.CITIES, 9779, 9780, 10035, 10036, 10291, 10292, 10547, 10548),
|
||||
CITY_BANDIT_CAMP("Bandit Camp" , DiscordAreaType.CITIES, 12590),
|
||||
CITY_BARBARIAN_OUTPOST("Barbarian Outpost", DiscordAreaType.CITIES, 10039),
|
||||
CITY_BARBARIAN_VILLAGE("Barbarian Village" , DiscordAreaType.CITIES, 12341),
|
||||
CITY_BEDABIN_CAMP("Bedabin Camp" , DiscordAreaType.CITIES, 12591),
|
||||
CITY_BRIMHAVEN("Brimhaven" , DiscordAreaType.CITIES, 11057, 11058),
|
||||
CITY_BURGH_DE_ROTT("Burgh de Rott" , DiscordAreaType.CITIES, 13874, 13873, 14130, 14129),
|
||||
CITY_BURTHORPE("Burthorpe" , DiscordAreaType.CITIES, 11319, 11575),
|
||||
CITY_CANIFIS("Canifis" , DiscordAreaType.CITIES, 13878),
|
||||
CITY_CATHERBY("Catherby" , DiscordAreaType.CITIES, 11317, 11318, 11061),
|
||||
CITY_CORSAIR_COVE("Corsair Cove" , DiscordAreaType.CITIES, 10028, 10284),
|
||||
CITY_DARKMEYER("Darkmeyer", DiscordAreaType.CITIES, 14388, 14644),
|
||||
CITY_DORGESH_KAAN("Dorgesh-Kaan" , DiscordAreaType.CITIES, 10835, 10834),
|
||||
CITY_DRAYNOR("Draynor" , DiscordAreaType.CITIES, 12338, 12339),
|
||||
CITY_EDGEVILLE("Edgeville" , DiscordAreaType.CITIES, 12342),
|
||||
CITY_ENTRANA("Entrana" , DiscordAreaType.CITIES, 11060, 11316),
|
||||
CITY_ETCETERIA("Etceteria", DiscordAreaType.CITIES, 10300),
|
||||
CITY_FALADOR("Falador" , DiscordAreaType.CITIES, 11828, 11572, 11827, 12084),
|
||||
CITY_GUTANOTH("Gu'Tanoth" , DiscordAreaType.CITIES, 10031),
|
||||
CITY_GWENITH("Gwenith", DiscordAreaType.CITIES, 8757),
|
||||
CITY_HOSIDIUS_HOUSE("Hosidius" , DiscordAreaType.CITIES, 6710, 6711, 6712, 6455, 6456, 6966, 6967, 6968, 7221, 7223, 7224, 7478, 7479),
|
||||
CITY_JATIZSO("Jatizso" , DiscordAreaType.CITIES, 9531),
|
||||
CITY_KELDAGRIM("Keldagrim" , DiscordAreaType.CITIES, 11423, 11422, 11679, 11678),
|
||||
CITY_LANDS_END("Land's End", DiscordAreaType.CITIES, 5941),
|
||||
CITY_LLETYA("Lletya" , DiscordAreaType.CITIES, 9265),
|
||||
CITY_LOVAKENGJ_HOUSE("Lovakengj" , DiscordAreaType.CITIES, 5692, 5691, 5947, 6203, 6202, 5690, 5946),
|
||||
CITY_LUMBRIDGE("Lumbridge" , DiscordAreaType.CITIES, 12850),
|
||||
CITY_LUNAR_ISLE("Lunar Isle" , DiscordAreaType.CITIES, 8253, 8252, 8509, 8508),
|
||||
CITY_MARIM("Marim", DiscordAreaType.REGIONS, 11051),
|
||||
CITY_MEIYERDITCH("Meiyerditch" , DiscordAreaType.CITIES, 14132, 14387, 14386, 14385),
|
||||
CITY_MISCELLANIA("Miscellania" , DiscordAreaType.CITIES, 10044),
|
||||
CITY_MOR_UL_REK("Mor Ul Rek" , DiscordAreaType.CITIES, 9808, 9807, 10064, 10063),
|
||||
CITY_MORTTON("Mort'ton" , DiscordAreaType.CITIES, 13875),
|
||||
CITY_MOS_LE_HARMLESS("Mos Le'Harmless" , DiscordAreaType.CITIES, 14638, 14639, 14894, 14895, 15151, 15406, 15407),
|
||||
CITY_MOUNT_KARUULM("Mount Karuulm", DiscordAreaType.CITIES, 5179, 4923, 5180),
|
||||
CITY_MOUNTAIN_CAMP("Mountain Camp", DiscordAreaType.CITIES, 11065),
|
||||
CITY_MYNYDD("Mynydd", DiscordAreaType.CITIES, 8501),
|
||||
CITY_NARDAH("Nardah" , DiscordAreaType.CITIES, 13613),
|
||||
CITY_NEITIZNOT("Neitiznot" , DiscordAreaType.CITIES, 9275),
|
||||
CITY_PISCARILIUS_HOUSE("Port Piscarilius" , DiscordAreaType.CITIES, 6971, 7227, 6970, 7226),
|
||||
CITY_PISCATORIS("Piscatoris" , DiscordAreaType.CITIES, 9273),
|
||||
CITY_POLLNIVNEACH("Pollnivneach" , DiscordAreaType.CITIES, 13358),
|
||||
CITY_PORT_KHAZARD("Port Khazard" , DiscordAreaType.CITIES, 10545),
|
||||
CITY_PORT_PHASMATYS("Port Phasmatys" , DiscordAreaType.CITIES, 14646),
|
||||
CITY_PORT_SARIM("Port Sarim" , DiscordAreaType.CITIES, 12081, 12082),
|
||||
CITY_PRIFDDINAS("Prifddinas", DiscordAreaType.CITIES, 8499, 8500, 8755, 8756, 9011, 9012, 9013, 12894, 12895, 13150, 13151),
|
||||
CITY_RELLEKKA("Rellekka" , DiscordAreaType.CITIES, 10297, 10553),
|
||||
CITY_RIMMINGTON("Rimmington" , DiscordAreaType.CITIES, 11826, 11570),
|
||||
CITY_SEERS_VILLAGE("Seers' Village" , DiscordAreaType.CITIES, 10806),
|
||||
CITY_SHAYZIEN_HOUSE("Shayzien" , DiscordAreaType.CITIES, 5944, 5943, 6200, 6199, 5686, 5687, 5688, 5689, 5945),
|
||||
CITY_SHILO_VILLAGE("Shilo Village" , DiscordAreaType.CITIES, 11310),
|
||||
CITY_SLEPE("Slepe", DiscordAreaType.CITIES, 14643, 14899, 14900, 14901),
|
||||
CITY_SOPHANEM("Sophanem" , DiscordAreaType.CITIES, 13099),
|
||||
CITY_TAI_BWO_WANNAI("Tai Bwo Wannai" , DiscordAreaType.CITIES, 11056, 11055),
|
||||
CITY_TAVERLEY("Taverley" , DiscordAreaType.CITIES, 11574, 11573),
|
||||
CITY_TREE_GNOME_STRONGHOLD("Tree Gnome Stronghold" , DiscordAreaType.CITIES, 9525, 9526, 9782, 9781),
|
||||
CITY_TREE_GNOME_VILLAGE("Tree Gnome Village" , DiscordAreaType.CITIES, 10033),
|
||||
CITY_TROLL_STRONGHOLD("Troll Stronghold" , DiscordAreaType.CITIES, 11321, 11421),
|
||||
CITY_UZER("Uzer" , DiscordAreaType.CITIES, 13872),
|
||||
CITY_VARROCK("Varrock" , DiscordAreaType.CITIES, 12596, 12597, 12852, 12853, 12854, 13108, 13109, 13110),
|
||||
CITY_VER_SINHAZA("Ver Sinhaza", DiscordAreaType.CITIES, 14642),
|
||||
CITY_VOID_OUTPOST("Void Knights' Outpost", DiscordAreaType.CITIES, 10537),
|
||||
CITY_WEISS("Weiss", DiscordAreaType.CITIES, 11325, 11581),
|
||||
CITY_WITCHHAVEN("Witchaven" , DiscordAreaType.CITIES, 10803),
|
||||
CITY_YANILLE("Yanille" , DiscordAreaType.CITIES, 10288, 10032),
|
||||
CITY_ZANARIS("Zanaris" , DiscordAreaType.CITIES, 9285, 9541, 9540, 9797),
|
||||
CITY_ZULANDRA("Zul-Andra" , DiscordAreaType.CITIES, 8495, 8751),
|
||||
|
||||
// Dungeons
|
||||
DUNGEON_ABANDONED_MINE("Abandoned Mine", DiscordAreaType.DUNGEONS, 13718, 11079, 11078, 11077, 10823, 10822, 10821),
|
||||
DUNGEON_AH_ZA_RHOON("Ah Za Rhoon", DiscordAreaType.DUNGEONS, 11666),
|
||||
DUNGEON_ANCIENT_CAVERN("Ancient Cavern", DiscordAreaType.DUNGEONS, 6483, 6995),
|
||||
DUNGEON_APE_ATOLL("Ape Atoll Dungeon", DiscordAreaType.DUNGEONS, 11150, 10894),
|
||||
DUNGEON_APE_ATOLL_BANANA_PLANTATION("Ape Atoll Banana Plantation", DiscordAreaType.DUNGEONS, 10895),
|
||||
DUNGEON_ARDY_SEWERS("Ardougne Sewers", DiscordAreaType.DUNGEONS, 10136, 10647),
|
||||
DUNGEON_ASGARNIAN_ICE_CAVES("Asgarnian Ice Caves", DiscordAreaType.DUNGEONS, 11925, 12181),
|
||||
DUNGEON_BERVIRIUS_TOMB("Tomb of Bervirius", DiscordAreaType.DUNGEONS, 11154),
|
||||
DUNGEON_BRIMHAVEN("Brimhaven Dungeon", DiscordAreaType.DUNGEONS, 10901, 10900, 10899, 10645, 10644, 10643),
|
||||
DUNGEON_BRINE_RAT_CAVERN("Brine Rat Cavern", DiscordAreaType.DUNGEONS, 10910),
|
||||
DUNGEON_CATACOMBS_OF_KOUREND("Catacombs of Kourend", DiscordAreaType.DUNGEONS, 6557, 6556, 6813, 6812),
|
||||
DUNGEON_CHAMPIONS_CHALLENGE("Champions' Challenge", DiscordAreaType.DUNGEONS, 12696),
|
||||
DUNGEON_CHAOS_DRUID_TOWER("Chaos Druid Tower", DiscordAreaType.DUNGEONS, 10392),
|
||||
DUNGEON_CHASM_OF_FIRE("Chasm of Fire", DiscordAreaType.DUNGEONS, 5789),
|
||||
DUNGEON_CHASM_OF_TEARS("Chasm of Tears", DiscordAreaType.DUNGEONS, 12948),
|
||||
DUNGEON_CHINCHOMPA("Chinchompa Hunting Ground", DiscordAreaType.DUNGEONS, 10129),
|
||||
DUNGEON_CLOCK_TOWER("Clock Tower Basement", DiscordAreaType.DUNGEONS, 10390),
|
||||
DUNGEON_CORSAIR_COVE("Corsair Cove Dungeon", DiscordAreaType.DUNGEONS, 8076, 8332),
|
||||
DUNGEON_CRABCLAW_CAVES("Crabclaw Caves", DiscordAreaType.DUNGEONS, 6553, 6809),
|
||||
DUNGEON_CRANDOR("Crandor Dungeon", DiscordAreaType.DUNGEONS, 11414),
|
||||
DUNGEON_CRASH_SITE_CAVERN("Crash Site Cavern", DiscordAreaType.DUNGEONS, 8280, 8536),
|
||||
DUNGEON_DAEYALT_ESSENCE_MINE("Daeyalt Essence Mine", DiscordAreaType.DUNGEONS, 14744),
|
||||
DUNGEON_DIGSITE("Digsite Dungeon", DiscordAreaType.DUNGEONS, 13464, 13465),
|
||||
DUNGEON_DORGESHKAAN("Dorgesh-Kaan South Dungeon", DiscordAreaType.DUNGEONS, 10833),
|
||||
DUNGEON_DORGESHUUN_MINES("Dorgeshuun Mines", DiscordAreaType.DUNGEONS, 12950, 13206),
|
||||
DUNGEON_DRAYNOR_SEWERS("Draynor Sewers", DiscordAreaType.DUNGEONS, 12439, 12438),
|
||||
DUNGEON_DWARVEN_MINES("Dwarven Mines", DiscordAreaType.DUNGEONS, 12185, 12184, 12183),
|
||||
DUNGEON_EAGLES_PEAK("Eagles' Peak Dungeon", DiscordAreaType.DUNGEONS, 8013),
|
||||
DUNGEON_ECTOFUNTUS("Ectofuntus", DiscordAreaType.DUNGEONS, 14746),
|
||||
DUNGEON_EDGEVILLE("Edgeville Dungeon", DiscordAreaType.DUNGEONS, 12441, 12442, 12443, 12698),
|
||||
DUNGEON_ELEMENTAL_WORKSHOP("Elemental Workshop", DiscordAreaType.DUNGEONS, 10906, 7760),
|
||||
DUNGEON_ENAKHRAS_TEMPLE("Enakhra's Temple", DiscordAreaType.DUNGEONS, 12423),
|
||||
DUNGEON_EVIL_CHICKENS_LAIR("Evil Chicken's Lair", DiscordAreaType.DUNGEONS, 9796),
|
||||
DUNGEON_EXPERIMENT_CAVE("Experiment Cave", DiscordAreaType.DUNGEONS, 14235, 13979),
|
||||
DUNGEON_FEROX_ENCLAVE("Ferox Enclave Dungeon", DiscordAreaType.DUNGEONS, 12700),
|
||||
DUNGEON_FORTHOS("Forthos Dungeon", DiscordAreaType.DUNGEONS, 7323),
|
||||
DUNGEON_FREMENNIK_SLAYER("Fremennik Slayer Dungeon", DiscordAreaType.DUNGEONS, 10907, 10908, 11164),
|
||||
DUNGEON_GLARIALS_TOMB("Glarial's Tomb", DiscordAreaType.DUNGEONS, 10137),
|
||||
DUNGEON_GOBLIN_CAVE("Goblin Cave", DiscordAreaType.DUNGEONS, 10393),
|
||||
DUNGEON_GRAND_TREE_TUNNELS("Grand Tree Tunnels", DiscordAreaType.DUNGEONS, 9882),
|
||||
DUNGEON_HAM_HIDEOUT("H.A.M. Hideout", DiscordAreaType.DUNGEONS, 12694),
|
||||
DUNGEON_HAM_STORE_ROOM("H.A.M. Store room", DiscordAreaType.DUNGEONS, 10321),
|
||||
DUNGEON_HEROES_GUILD("Heroes' Guild Mine", DiscordAreaType.DUNGEONS, 11674),
|
||||
DUNGEON_IORWERTH("Iorwerth Dungeon", DiscordAreaType.DUNGEONS, 12737, 12738, 12993, 12994),
|
||||
DUNGEON_JATIZSO_MINES("Jatizso Mines", DiscordAreaType.DUNGEONS, 9631),
|
||||
DUNGEON_JIGGIG_BURIAL_TOMB("Jiggig Burial Tomb", DiscordAreaType.DUNGEONS, 9875, 9874),
|
||||
DUNGEON_JOGRE("Jogre Dungeon", DiscordAreaType.DUNGEONS, 11412),
|
||||
DUNGEON_KARAMJA("Karamja Dungeon", DiscordAreaType.DUNGEONS, 11413),
|
||||
DUNGEON_KARUULM("Karuulm Slayer Dungeon", DiscordAreaType.DUNGEONS, 5280, 5279, 5023, 5535, 5022, 4766, 4510, 4511, 4767, 4768, 4512),
|
||||
DUNGEON_KGP_HEADQUARTERS("KGP Headquarters", DiscordAreaType.DUNGEONS, 10658),
|
||||
DUNGEON_KRUK("Kruk's Dungeon", DiscordAreaType.DUNGEONS, 9358, 9359, 9360, 9615, 9616, 9871, 10125, 10126, 10127, 10128, 10381, 10382, 10383, 10384, 10637, 10638, 10639, 10640),
|
||||
DUNGEON_LEGENDS_GUILD("Legends' Guild Dungeon", DiscordAreaType.DUNGEONS, 10904),
|
||||
DUNGEON_LIGHTHOUSE("Lighthouse", DiscordAreaType.DUNGEONS, 10140),
|
||||
DUNGEON_LIZARDMAN_CAVES("Lizardman Caves", DiscordAreaType.DUNGEONS, 5275),
|
||||
DUNGEON_LIZARDMAN_TEMPLE("Lizardman Temple", DiscordAreaType.DUNGEONS, 5277),
|
||||
DUNGEON_LUMBRIDGE_SWAMP_CAVES("Lumbridge Swamp Caves", DiscordAreaType.DUNGEONS, 12693, 12949),
|
||||
DUNGEON_LUNAR_ISLE_MINE("Lunar Isle Mine", DiscordAreaType.DUNGEONS, 9377),
|
||||
DUNGEON_MANIACAL_HUNTER("Maniacal Monkey Hunter Area", DiscordAreaType.DUNGEONS, 11662),
|
||||
DUNGEON_MEIYERDITCH_MINE("Meiyerditch Mine", DiscordAreaType.DUNGEONS, 9544),
|
||||
DUNGEON_MISCELLANIA("Miscellania Dungeon", DiscordAreaType.DUNGEONS, 10144, 10400),
|
||||
DUNGEON_MOGRE_CAMP("Mogre Camp", DiscordAreaType.DUNGEONS, 11924),
|
||||
DUNGEON_MOS_LE_HARMLESS_CAVES("Mos Le'Harmless Caves", DiscordAreaType.DUNGEONS, 14994, 14995, 15251),
|
||||
DUNGEON_MOTHERLODE_MINE("Motherlode Mine", DiscordAreaType.DUNGEONS, 14679, 14680, 14681, 14935, 14936, 14937, 15191, 15192, 15193),
|
||||
DUNGEON_MOURNER_TUNNELS("Mourner Tunnels", DiscordAreaType.DUNGEONS, 7752, 8008),
|
||||
DUNGEON_MOUSE_HOLE("Mouse Hole", DiscordAreaType.DUNGEONS, 9046),
|
||||
DUNGEON_MYREDITCH_LABORATORIES("Myreditch Laboratories", DiscordAreaType.DUNGEONS, 14232, 14233, 14487, 14488),
|
||||
DUNGEON_MYREQUE("Myreque Hideout", DiscordAreaType.DUNGEONS, 13721, 13974, 13977, 13978),
|
||||
DUNGEON_MYTHS_GUILD("Myths' Guild Dungeon", DiscordAreaType.DUNGEONS, 7564, 7820, 7821),
|
||||
DUNGEON_OBSERVATORY("Observatory Dungeon", DiscordAreaType.DUNGEONS, 9362),
|
||||
DUNGEON_OGRE_ENCLAVE("Ogre Enclave", DiscordAreaType.DUNGEONS, 10387),
|
||||
DUNGEON_OURANIA("Ourania Cave", DiscordAreaType.DUNGEONS, 12119),
|
||||
DUNGEON_QUIDAMORTEM_CAVE("Quidamortem Cave", DiscordAreaType.DUNGEONS, 4763),
|
||||
DUNGEON_RASHILIYIAS_TOMB("Rashiliyta's Tomb", DiscordAreaType.DUNGEONS, 11668),
|
||||
DUNGEON_SARADOMINSHRINE("Saradomin Shrine (Paterdomus)", DiscordAreaType.DUNGEONS, 13722),
|
||||
DUNGEON_SHADE_CATACOMBS("Shade Catacombs", DiscordAreaType.DUNGEONS, 13975),
|
||||
DUNGEON_SHADOW("Shadow Dungeon", DiscordAreaType.DUNGEONS, 10575, 10831),
|
||||
DUNGEON_SHAYZIEN_CRYPTS("Shayzien Crypts", DiscordAreaType.DUNGEONS, 6043),
|
||||
DUNGEON_SISTERHOOD_SANCTUARY("Sisterhood Sanctuary", DiscordAreaType.DUNGEONS, 14999, 15000, 15001, 15255, 15256, 15257, 15511, 15512, 15513),
|
||||
DUNGEON_SMOKE("Smoke Dungeon", DiscordAreaType.DUNGEONS, 12946, 13202),
|
||||
DUNGEON_SOPHANEM("Sophanem Dungeon", DiscordAreaType.DUNGEONS, 13200),
|
||||
DUNGEON_SOURHOG_CAVE("Sourhog Cave", DiscordAreaType.DUNGEONS, 12695),
|
||||
DUNGEON_STRONGHOLD_SECURITY("Stronghold of Security", DiscordAreaType.DUNGEONS, 7505, 8017, 8530, 9297),
|
||||
DUNGEON_STRONGHOLD_SLAYER("Stronghold Slayer Cave", DiscordAreaType.DUNGEONS, 9624, 9625, 9880, 9881),
|
||||
DUNGEON_TARNS_LAIR("Tarn's Lair", DiscordAreaType.DUNGEONS, 12616, 12615),
|
||||
DUNGEON_TAVERLEY("Taverley Dungeon", DiscordAreaType.DUNGEONS, 11416, 11417, 11671, 11672, 11673, 11928, 11929),
|
||||
DUNGEON_TEMPLE_OF_IKOV("Temple of Ikov", DiscordAreaType.DUNGEONS, 10649, 10905, 10650),
|
||||
DUNGEON_TEMPLE_OF_LIGHT("Temple of Light", DiscordAreaType.DUNGEONS, 7496),
|
||||
DUNGEON_TEMPLE_OF_MARIMBO("Temple of Marimbo", DiscordAreaType.DUNGEONS, 11151),
|
||||
DUNGEON_THE_WARRENS("The Warrens", DiscordAreaType.DUNGEONS, 7070, 7326),
|
||||
DUNGEON_TOLNA("Dungeon of Tolna", DiscordAreaType.DUNGEONS, 13209),
|
||||
DUNGEON_TOWER_OF_LIFE("Tower of Life Basement", DiscordAreaType.DUNGEONS, 12100),
|
||||
DUNGEON_TRAHAEARN_MINE("Trahaearn Mine", DiscordAreaType.DUNGEONS, 13250),
|
||||
DUNGEON_TUNNEL_OF_CHAOS("Tunnel of Chaos", DiscordAreaType.DUNGEONS, 12625),
|
||||
DUNGEON_UNDERGROUND_PASS("Underground Pass", DiscordAreaType.DUNGEONS, 9369, 9370),
|
||||
DUNGEON_VARROCKSEWERS("Varrock Sewers", DiscordAreaType.DUNGEONS, 12954, 13210),
|
||||
DUNGEON_VIYELDI_CAVES("Viyeldi Caves", DiscordAreaType.DUNGEONS, 9545, 11153),
|
||||
DUNGEON_WARRIORS_GUILD("Warriors' Guild Basement", DiscordAreaType.DUNGEONS, 11675),
|
||||
DUNGEON_WATER_RAVINE("Water Ravine", DiscordAreaType.DUNGEONS, 13461),
|
||||
DUNGEON_WATERBIRTH("Waterbirth Dungeon", DiscordAreaType.DUNGEONS, 9886, 10142, 7492, 7748),
|
||||
DUNGEON_WATERFALL("Waterfall Dungeon", DiscordAreaType.DUNGEONS, 10394),
|
||||
DUNGEON_WEREWOLF_AGILITY("Werewolf Agility Course", DiscordAreaType.DUNGEONS, 14234),
|
||||
DUNGEON_WHITE_WOLF_MOUNTAIN_CAVES("White Wolf Mountain Caves", DiscordAreaType.DUNGEONS, 11418, 11419),
|
||||
DUNGEON_WITCHAVEN_SHRINE("Witchhaven Shrine Dungeon", DiscordAreaType.DUNGEONS, 10903),
|
||||
DUNGEON_WIZARDS_TOWER("Wizards' Tower Basement", DiscordAreaType.DUNGEONS, 12437),
|
||||
DUNGEON_WOODCUTTING_GUILD("Woodcutting Guild Dungeon", DiscordAreaType.DUNGEONS, 6298),
|
||||
DUNGEON_WYVERN_CAVE("Wyvern Cave", DiscordAreaType.DUNGEONS, 14495, 14496),
|
||||
DUNGEON_YANILLE_AGILITY("Yanille Agility Dungeon", DiscordAreaType.DUNGEONS, 10388),
|
||||
|
||||
// Minigames
|
||||
MG_ARDOUGNE_RAT_PITS("Ardougne Rat Pits", DiscordAreaType.MINIGAMES, 10646),
|
||||
MG_BARBARIAN_ASSAULT("Barbarian Assault", DiscordAreaType.MINIGAMES, 7508, 7509, 10322),
|
||||
MG_BARROWS("Barrows", DiscordAreaType.MINIGAMES, 14131, 14231),
|
||||
MG_BLAST_FURNACE("Blast Furnace", DiscordAreaType.MINIGAMES, 7757),
|
||||
MG_BRIMHAVEN_AGILITY_ARENA("Brimhaven Agility Arena", DiscordAreaType.MINIGAMES, 11157),
|
||||
MG_BURTHORPE_GAMES_ROOM("Burthorpe Games Room", DiscordAreaType.MINIGAMES, 8781),
|
||||
MG_CASTLE_WARS("Castle Wars", DiscordAreaType.MINIGAMES, 9520, 9620),
|
||||
MG_CLAN_WARS("Clan Wars", DiscordAreaType.MINIGAMES, 12621, 12622, 12623, 13130, 13131, 13133, 13134, 13135, 13386, 13387, 13390, 13641, 13642, 13643, 13644, 13645, 13646, 13647, 13899, 13900, 14155, 14156),
|
||||
MG_DUEL_ARENA("Duel Arena", DiscordAreaType.MINIGAMES, 13362, 13363),
|
||||
MG_FISHING_TRAWLER("Fishing Trawler", DiscordAreaType.MINIGAMES, 7499),
|
||||
MG_GAUNTLET("The Gauntlet", DiscordAreaType.MINIGAMES, 12127, 7512, 7768),
|
||||
MG_HALLOWED_SEPULCHRE("Hallowed Sepulchre", DiscordAreaType.MINIGAMES, 8797, 9051, 9052, 9053, 9054, 9309, 9563, 9565, 9821, 10074, 10075, 10077),
|
||||
MG_INFERNO("The Inferno", DiscordAreaType.MINIGAMES, 9043),
|
||||
MG_KELDAGRIM_RAT_PITS("Keldagrim Rat Pits", DiscordAreaType.MINIGAMES, 7753),
|
||||
MG_LAST_MAN_STANDING("Last Man Standing", DiscordAreaType.MINIGAMES, 13660, 13659, 13658, 13916, 13915, 13914),
|
||||
MG_MAGE_TRAINING_ARENA("Mage Training Arena", DiscordAreaType.MINIGAMES, 13462, 13463),
|
||||
MG_NIGHTMARE_ZONE("Nightmare Zone", DiscordAreaType.MINIGAMES, 9033),
|
||||
MG_PEST_CONTROL("Pest Control", DiscordAreaType.MINIGAMES, 10536),
|
||||
MG_PORT_SARIM_RAT_PITS("Port Sarim Rat Pits", DiscordAreaType.MINIGAMES, 11926),
|
||||
MG_PYRAMID_PLUNDER("Pyramid Plunder", DiscordAreaType.MINIGAMES, 7749),
|
||||
MG_ROGUES_DEN("Rogues' Den", DiscordAreaType.MINIGAMES, 11855, 11854, 12111, 12110),
|
||||
MG_SORCERESS_GARDEN("Sorceress's Garden", DiscordAreaType.MINIGAMES, 11605),
|
||||
MG_TEMPLE_TREKKING("Temple Trekking", DiscordAreaType.MINIGAMES, 8014, 8270, 8256, 8782, 9038, 9294, 9550, 9806),
|
||||
MG_TITHE_FARM("Tithe Farm", DiscordAreaType.MINIGAMES, 7222),
|
||||
MG_TROUBLE_BREWING("Trouble Brewing", DiscordAreaType.MINIGAMES, 15150),
|
||||
MG_TZHAAR_FIGHT_CAVES("Tzhaar Fight Caves", DiscordAreaType.MINIGAMES, 9551),
|
||||
MG_TZHAAR_FIGHT_PITS("Tzhaar Fight Pits", DiscordAreaType.MINIGAMES, 9552),
|
||||
MG_VARROCK_RAT_PITS("Varrock Rat Pits", DiscordAreaType.MINIGAMES, 11599),
|
||||
MG_VOLCANIC_MINE("Volcanic Mine", DiscordAreaType.MINIGAMES, 15263, 15262),
|
||||
|
||||
// Raids
|
||||
RAIDS_CHAMBERS_OF_XERIC("Chambers of Xeric", DiscordAreaType.RAIDS, Varbits.IN_RAID),
|
||||
RAIDS_THEATRE_OF_BLOOD("Theatre of Blood", DiscordAreaType.RAIDS, Varbits.THEATRE_OF_BLOOD),
|
||||
|
||||
// Other
|
||||
REGION_ABYSSAL_AREA("Abyssal Area", DiscordAreaType.REGIONS, 12108),
|
||||
REGION_ABYSSAL_NEXUS("Abyssal Nexus", DiscordAreaType.REGIONS, 12106),
|
||||
REGION_AGILITY_PYRAMID("Agility Pyramid", DiscordAreaType.REGIONS, 12105, 13356),
|
||||
REGION_AIR_ALTAR("Air Altar", DiscordAreaType.REGIONS, 11339),
|
||||
REGION_AL_KHARID_MINE("Al Kharid Mine", DiscordAreaType.REGIONS, 13107),
|
||||
REGION_APE_ATOLL("Ape Atoll" , DiscordAreaType.REGIONS, 10795, 10974, 11050),
|
||||
REGION_ARANDAR("Arandar", DiscordAreaType.REGIONS, 9266, 9267, 9523),
|
||||
REGION_ASGARNIA("Asgarnia", DiscordAreaType.REGIONS, 11825, 11829, 11830, 12085, 12086),
|
||||
REGION_BATTLEFIELD("Battlefield", DiscordAreaType.REGIONS, 10034),
|
||||
REGION_BATTLEFRONT("Battlefront", DiscordAreaType.REGIONS, 5433, 5434),
|
||||
REGION_BLAST_MINE("Blast Mine", DiscordAreaType.REGIONS, 5948),
|
||||
REGION_BODY_ALTAR("Body Altar", DiscordAreaType.REGIONS, 10059),
|
||||
REGION_CHAOS_ALTAR("Chaos Altar", DiscordAreaType.REGIONS, 9035),
|
||||
REGION_COSMIC_ALTAR("Cosmic Altar", DiscordAreaType.REGIONS, 8523),
|
||||
REGION_COSMIC_ENTITYS_PLANE("Cosmic Entity's Plane", DiscordAreaType.REGIONS, 8267),
|
||||
REGION_CRABCLAW_ISLE("Crabclaw Isle", DiscordAreaType.REGIONS, 6965),
|
||||
REGION_CRAFTING_GUILD("Crafting Guild", DiscordAreaType.REGIONS, 11571),
|
||||
REGION_CRANDOR("Crandor", DiscordAreaType.REGIONS, 11314, 11315),
|
||||
REGION_CRASH_ISLAND("Crash Island", DiscordAreaType.REGIONS, 11562),
|
||||
REGION_DARK_ALTAR("Dark Altar", DiscordAreaType.REGIONS, 6716),
|
||||
REGION_DEATH_ALTAR("Death Altar", DiscordAreaType.REGIONS, 8779),
|
||||
REGION_DEATH_PLATEAU("Death Plateau", DiscordAreaType.REGIONS, 11320),
|
||||
REGION_DENSE_ESSENCE("Dense Essence Mine", DiscordAreaType.REGIONS, 6972),
|
||||
REGION_DIGSITE("Digsite", DiscordAreaType.REGIONS, 13365),
|
||||
REGION_DRAGONTOOTH("Dragontooth Island", DiscordAreaType.REGIONS, 15159),
|
||||
REGION_DRAYNOR_MANOR("Draynor Manor", DiscordAreaType.REGIONS, 12340),
|
||||
REGION_DRILL_SERGEANT("Drill Sergeant's Training Camp", DiscordAreaType.REGIONS, 12619),
|
||||
REGION_EAGLES_PEAK("Eagles' Peak", DiscordAreaType.REGIONS, 9270),
|
||||
REGION_EARTH_ALTAR("Earth Altar", DiscordAreaType.REGIONS, 10571),
|
||||
REGION_ENCHANTED_VALLEY("Enchanted Valley", DiscordAreaType.REGIONS, 12102),
|
||||
REGION_EVIL_TWIN("Evil Twin Crane Room", DiscordAreaType.REGIONS, 7504),
|
||||
REGION_EXAM_CENTRE("Exam Centre", DiscordAreaType.REGIONS, 13364),
|
||||
REGION_FALADOR_FARM("Falador Farm", DiscordAreaType.REGIONS, 12083),
|
||||
REGION_FARMING_GUILD("Farming Guild", DiscordAreaType.REGIONS, 4922),
|
||||
REGION_FELDIP_HILLS("Feldip Hills", DiscordAreaType.REGIONS, 9773, 9774, 10029, 10030, 10285, 10286, 10287, 10542, 10543),
|
||||
REGION_FENKENSTRAIN("Fenkenstrain's Castle", DiscordAreaType.REGIONS, 14135),
|
||||
REGION_FIRE_ALTAR("Fire Altar", DiscordAreaType.REGIONS, 10315),
|
||||
REGION_FISHER_REALM("Fisher Realm", DiscordAreaType.REGIONS, 10569),
|
||||
REGION_FISHING_GUILD("Fishing Guild", DiscordAreaType.REGIONS, 10293),
|
||||
REGION_FISHING_PLATFORM("Fishing Platform", DiscordAreaType.REGIONS, 11059),
|
||||
REGION_FORSAKEN_TOWER("The Forsaken Tower", DiscordAreaType.REGIONS, 5435),
|
||||
REGION_FOSSIL_ISLAND("Fossil Island", DiscordAreaType.REGIONS, 14650, 14651, 14652, 14906, 14907, 14908, 15162, 15163, 15164),
|
||||
REGION_FREAKY_FORESTER("Freaky Forester's Clearing", DiscordAreaType.REGIONS, 10314),
|
||||
REGION_FREMENNIK("Fremennik Province", DiscordAreaType.REGIONS, 10296, 10552, 10808, 10809, 10810, 10811, 11064),
|
||||
REGION_FREMENNIK_ISLES("Fremennik Isles", DiscordAreaType.REGIONS, 9276, 9532),
|
||||
REGION_FROGLAND("Frogland", DiscordAreaType.REGIONS, 9802),
|
||||
REGION_GALVEK_SHIPWRECKS("Galvek Shipwrecks", DiscordAreaType.REGIONS, 6486, 6487, 6488, 6489, 6742, 6743, 6744, 6745),
|
||||
REGION_GORAKS_PLANE("Gorak's Plane", DiscordAreaType.REGIONS, 12115),
|
||||
REGION_GRAND_EXCHANGE("Grand Exchange", DiscordAreaType.REGIONS, 12598),
|
||||
REGION_GWD("God Wars Dungeon", DiscordAreaType.REGIONS, 11578),
|
||||
REGION_HARMONY("Harmony Island", DiscordAreaType.REGIONS, 15148),
|
||||
REGION_ICE_PATH("Ice Path", DiscordAreaType.REGIONS, 11322, 11323),
|
||||
REGION_ICEBERG("Iceberg", DiscordAreaType.REGIONS, 10558, 10559),
|
||||
REGION_ICYENE_GRAVEYARD("Icyene Graveyard", DiscordAreaType.REGIONS, 14641, 14897, 14898),
|
||||
REGION_ISAFDAR("Isafdar", DiscordAreaType.REGIONS, 8497, 8753, 8754, 9009, 9010),
|
||||
REGION_ISLAND_OF_STONE("Island of Stone", DiscordAreaType.REGIONS, 9790),
|
||||
REGION_JIGGIG("Jiggig" , DiscordAreaType.REGIONS, 9775),
|
||||
REGION_KANDARIN("Kandarin", DiscordAreaType.REGIONS, 9014, 9263, 9264, 9519, 9524, 9527, 9776, 9783, 10037, 10290, 10294, 10546, 10551, 10805),
|
||||
REGION_KARAMJA("Karamja" , DiscordAreaType.REGIONS, 10801, 10802, 11054, 11311, 11312, 11313, 11566, 11567, 11568, 11569, 11822),
|
||||
REGION_KEBOS_LOWLANDS("Kebos Lowlands", DiscordAreaType.REGIONS, 4665, 4666, 4921, 5178),
|
||||
REGION_KEBOS_SWAMP("Kebos Swamp", DiscordAreaType.REGIONS, 4664, 4920, 5174, 5175, 5176, 5430, 5431),
|
||||
REGION_KHARAZI_JUNGLE("Kharazi Jungle", DiscordAreaType.REGIONS, 11053, 11309, 11565, 11821),
|
||||
REGION_KHARIDIAN_DESERT("Kharidian Desert", DiscordAreaType.REGIONS, 12844, 12845, 12846, 12847, 12848, 13100, 13101, 13102, 13103, 13104, 13357, 13359, 13360, 13614, 13615, 13616),
|
||||
REGION_KILLERWATT_PLANE("Killerwatt Plane", DiscordAreaType.REGIONS, 10577),
|
||||
REGION_KOUREND("Great Kourend", DiscordAreaType.REGIONS, 6201, 6457, 6713),
|
||||
REGION_KOUREND_WOODLAND("Kourend Woodland", DiscordAreaType.REGIONS, 5942, 6197, 6453),
|
||||
REGION_LAW_ALTAR("Law Altar", DiscordAreaType.REGIONS, 9803),
|
||||
REGION_LEGENDS_GUILD("Legends' Guild", DiscordAreaType.REGIONS, 10804),
|
||||
REGION_LIGHTHOUSE("Lighthouse", DiscordAreaType.REGIONS, 10040),
|
||||
REGION_LITHKREN("Lithkren", DiscordAreaType.REGIONS, 14142, 14398),
|
||||
REGION_LUMBRIDGE_SWAMP("Lumbridge Swamp", DiscordAreaType.REGIONS, 12593, 12849),
|
||||
REGION_MAX_ISLAND("Max Island", DiscordAreaType.REGIONS, 11063),
|
||||
REGION_MCGRUBORS_WOOD("McGrubor's Wood", DiscordAreaType.REGIONS, 10550),
|
||||
REGION_MIME_STAGE("Mime's Stage", DiscordAreaType.REGIONS, 8010),
|
||||
REGION_MIND_ALTAR("Mind Altar", DiscordAreaType.REGIONS, 11083),
|
||||
REGION_MISTHALIN("Misthalin", DiscordAreaType.REGIONS, 12594, 12595),
|
||||
REGION_MOLCH("Molch", DiscordAreaType.REGIONS, 5177),
|
||||
REGION_MOLCH_ISLAND("Molch Island", DiscordAreaType.REGIONS, 5432),
|
||||
REGION_MORYTANIA("Morytania", DiscordAreaType.REGIONS, 13619, 13620, 13621, 13622, 13876, 13877, 13879, 14133, 14134, 14389, 14390, 14391, 14645, 14647),
|
||||
REGION_MOUNT_QUIDAMORTEM("Mount Quidamortem", DiscordAreaType.REGIONS, 4662, 4663, 4918, 4919),
|
||||
REGION_MR_MORDAUTS_CLASSROOM("Mr. Mordaut's Classroom", DiscordAreaType.REGIONS, 7502),
|
||||
REGION_MUDSKIPPER("Mudskipper Point", DiscordAreaType.REGIONS, 11824),
|
||||
REGION_MYSTERIOUS_OLD_MAN_MAZE("Mysterious Old Man's Maze", DiscordAreaType.REGIONS, 11590, 11591, 11846, 11847),
|
||||
REGION_MYTHS_GUILD("Myths' Guild", DiscordAreaType.REGIONS, 9772),
|
||||
REGION_NATURE_ALTAR("Nature Altar", DiscordAreaType.REGIONS, 9547),
|
||||
REGION_NORTHERN_TUNDRAS("Northern Tundras", DiscordAreaType.REGIONS, 6204, 6205, 6717),
|
||||
REGION_OBSERVATORY("Observatory", DiscordAreaType.REGIONS, 9777),
|
||||
REGION_ODD_ONE_OUT("Odd One Out", DiscordAreaType.REGIONS, 7754),
|
||||
REGION_OTTOS_GROTTO("Otto's Grotto", DiscordAreaType.REGIONS, 10038),
|
||||
REGION_OURANIA_HUNTER("Ourania Hunter Area", DiscordAreaType.REGIONS, 9778),
|
||||
REGION_PIRATES_COVE("Pirates' Cove", DiscordAreaType.REGIONS, 8763),
|
||||
REGION_PISCATORIS_HUNTER_AREA("Piscatoris Hunter Area", DiscordAreaType.REGIONS, 9015, 9016, 9271, 9272, 9528),
|
||||
REGION_POH("Player Owned House", DiscordAreaType.REGIONS, 7513, 7514, 7769, 7770),
|
||||
REGION_POISON_WASTE("Poison Waste", DiscordAreaType.REGIONS, 8752, 9008),
|
||||
REGION_PORT_TYRAS("Port Tyras", DiscordAreaType.REGIONS, 8496),
|
||||
REGION_PURO_PURO("Puro Puro", DiscordAreaType.REGIONS, 10307),
|
||||
REGION_QUARRY("Quarry", DiscordAreaType.REGIONS, 12589),
|
||||
REGION_RANGING_GUILD("Ranging Guild", DiscordAreaType.REGIONS, 10549),
|
||||
REGION_RATCATCHERS_MANSION("Ratcatchers Mansion", DiscordAreaType.REGIONS, 11343),
|
||||
REGION_RUNE_ESSENCE_MINE("Rune Essence Mine", DiscordAreaType.REGIONS, 11595),
|
||||
// The Beekeper, Pinball, and Gravedigger randoms share a region (7758), and although they are not technically ScapeRune, that name is most commonly
|
||||
// associated with random events, so those three have been denoted ScapeRune to avoid leaving multiple random event regions without an assigned name.
|
||||
REGION_SCAPERUNE("ScapeRune", DiscordAreaType.REGIONS, 10058, 7758, 8261),
|
||||
REGION_SHIP_YARD("Ship Yard", DiscordAreaType.REGIONS, 11823),
|
||||
REGION_SILVAREA("Silvarea", DiscordAreaType.REGIONS, 13366),
|
||||
REGION_SINCLAR_MANSION("Sinclair Mansion", DiscordAreaType.REGIONS, 10807),
|
||||
REGION_SLAYER_TOWER("Slayer Tower", DiscordAreaType.REGIONS, 13623, 13723),
|
||||
REGION_SOUL_ALTAR("Soul Altar", DiscordAreaType.REGIONS, 7228),
|
||||
REGION_TROLL_ARENA("Troll Arena", DiscordAreaType.REGIONS, 11576),
|
||||
REGION_TROLLHEIM("Trollheim", DiscordAreaType.REGIONS, 11577),
|
||||
REGION_TROLLWEISS_MTN("Trollweiss Mountain", DiscordAreaType.REGIONS, 11066, 11067, 11068),
|
||||
REGION_UNDERWATER("Underwater", DiscordAreaType.REGIONS, 15008, 15264),
|
||||
REGION_WATER_ALTAR("Water Altar", DiscordAreaType.REGIONS, 10827),
|
||||
REGION_WINTERTODT_CAMP("Wintertodt Camp", DiscordAreaType.REGIONS, 6461),
|
||||
REGION_WIZARDS_TOWER("Wizards' Tower", DiscordAreaType.REGIONS, 12337),
|
||||
REGION_WOODCUTTING_GUILD("Woodcutting Guild", DiscordAreaType.REGIONS, 6198, 6454),
|
||||
REGION_WRATH_ALTAR("Wrath Altar", DiscordAreaType.REGIONS, 9291);
|
||||
|
||||
private static final Map<Integer, DiscordGameEventType> FROM_REGION;
|
||||
private static final List<DiscordGameEventType> FROM_VARBITS;
|
||||
|
||||
static
|
||||
{
|
||||
ImmutableMap.Builder<Integer, DiscordGameEventType> regionMapBuilder = new ImmutableMap.Builder<>();
|
||||
ImmutableList.Builder<DiscordGameEventType> fromVarbitsBuilder = ImmutableList.builder();
|
||||
for (DiscordGameEventType discordGameEventType : DiscordGameEventType.values())
|
||||
{
|
||||
if (discordGameEventType.getVarbits() != null)
|
||||
{
|
||||
fromVarbitsBuilder.add(discordGameEventType);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (discordGameEventType.getRegionIds() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int region : discordGameEventType.getRegionIds())
|
||||
{
|
||||
regionMapBuilder.put(region, discordGameEventType);
|
||||
}
|
||||
}
|
||||
FROM_REGION = regionMapBuilder.build();
|
||||
FROM_VARBITS = fromVarbitsBuilder.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String imageKey;
|
||||
|
||||
@Nullable
|
||||
private String state;
|
||||
|
||||
@Nullable
|
||||
private String details;
|
||||
|
||||
private int priority;
|
||||
|
||||
/**
|
||||
* Marks this event as root event, e.g event that should be used for total time tracking
|
||||
*/
|
||||
private boolean root;
|
||||
|
||||
/**
|
||||
* Determines if event should clear other clearable events when triggered
|
||||
*/
|
||||
private boolean shouldClear;
|
||||
|
||||
/**
|
||||
* Determines if event should be processed when it timeouts based on action timeout
|
||||
*/
|
||||
private boolean shouldTimeout;
|
||||
|
||||
/**
|
||||
* Determines if event start time should be reset when processed
|
||||
*/
|
||||
private boolean shouldRestart;
|
||||
|
||||
/**
|
||||
* Determines if event should be cleared when processed
|
||||
*/
|
||||
private boolean shouldBeCleared = true;
|
||||
|
||||
@Nullable
|
||||
private DiscordAreaType discordAreaType;
|
||||
|
||||
@Nullable
|
||||
private Varbits varbits;
|
||||
|
||||
@Nullable
|
||||
private int[] regionIds;
|
||||
|
||||
DiscordGameEventType(Skill skill)
|
||||
{
|
||||
this(skill, 0);
|
||||
}
|
||||
|
||||
DiscordGameEventType(Skill skill, int priority)
|
||||
{
|
||||
this.details = training(skill);
|
||||
this.priority = priority;
|
||||
this.imageKey = imageKeyOf(skill);
|
||||
this.shouldTimeout = true;
|
||||
}
|
||||
|
||||
DiscordGameEventType(String areaName, DiscordAreaType areaType, int... regionIds)
|
||||
{
|
||||
this.state = exploring(areaType, areaName);
|
||||
this.priority = -2;
|
||||
this.discordAreaType = areaType;
|
||||
this.regionIds = regionIds;
|
||||
this.shouldClear = true;
|
||||
}
|
||||
|
||||
DiscordGameEventType(String state, int priority, boolean shouldClear, boolean shouldTimeout, boolean shouldRestart, boolean shouldBeCleared, boolean root)
|
||||
{
|
||||
this.state = state;
|
||||
this.priority = priority;
|
||||
this.shouldClear = shouldClear;
|
||||
this.shouldTimeout = shouldTimeout;
|
||||
this.shouldRestart = shouldRestart;
|
||||
this.shouldBeCleared = shouldBeCleared;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
DiscordGameEventType(String state, int priority)
|
||||
{
|
||||
this(state, priority, true, false, false, true, false);
|
||||
}
|
||||
|
||||
DiscordGameEventType(String areaName, DiscordAreaType areaType, Varbits varbits)
|
||||
{
|
||||
this.state = exploring(areaType, areaName);
|
||||
this.priority = -2;
|
||||
this.discordAreaType = areaType;
|
||||
this.varbits = varbits;
|
||||
this.shouldClear = true;
|
||||
}
|
||||
|
||||
private static String training(final Skill skill)
|
||||
{
|
||||
return training(skill.getName());
|
||||
}
|
||||
|
||||
private static String training(final String what)
|
||||
{
|
||||
return "Training: " + what;
|
||||
}
|
||||
|
||||
private static String imageKeyOf(final Skill skill)
|
||||
{
|
||||
return imageKeyOf(skill.getName().toLowerCase());
|
||||
}
|
||||
|
||||
private static String imageKeyOf(final String what)
|
||||
{
|
||||
return "icon_" + what;
|
||||
}
|
||||
|
||||
private static String exploring(DiscordAreaType areaType, String areaName)
|
||||
{
|
||||
return areaName;
|
||||
}
|
||||
|
||||
public static DiscordGameEventType fromSkill(final Skill skill)
|
||||
{
|
||||
switch (skill)
|
||||
{
|
||||
case ATTACK: return TRAINING_ATTACK;
|
||||
case DEFENCE: return TRAINING_DEFENCE;
|
||||
case STRENGTH: return TRAINING_STRENGTH;
|
||||
case RANGED: return TRAINING_RANGED;
|
||||
case PRAYER: return TRAINING_PRAYER;
|
||||
case MAGIC: return TRAINING_MAGIC;
|
||||
case COOKING: return TRAINING_COOKING;
|
||||
case WOODCUTTING: return TRAINING_WOODCUTTING;
|
||||
case FLETCHING: return TRAINING_FLETCHING;
|
||||
case FISHING: return TRAINING_FISHING;
|
||||
case FIREMAKING: return TRAINING_FIREMAKING;
|
||||
case CRAFTING: return TRAINING_CRAFTING;
|
||||
case SMITHING: return TRAINING_SMITHING;
|
||||
case MINING: return TRAINING_MINING;
|
||||
case HERBLORE: return TRAINING_HERBLORE;
|
||||
case AGILITY: return TRAINING_AGILITY;
|
||||
case THIEVING: return TRAINING_THIEVING;
|
||||
case SLAYER: return TRAINING_SLAYER;
|
||||
case FARMING: return TRAINING_FARMING;
|
||||
case RUNECRAFT: return TRAINING_RUNECRAFT;
|
||||
case HUNTER: return TRAINING_HUNTER;
|
||||
case CONSTRUCTION: return TRAINING_CONSTRUCTION;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static DiscordGameEventType fromRegion(final int regionId)
|
||||
{
|
||||
return FROM_REGION.get(regionId);
|
||||
}
|
||||
|
||||
public static DiscordGameEventType fromVarbit(final Client client)
|
||||
{
|
||||
for (DiscordGameEventType fromVarbit : FROM_VARBITS)
|
||||
{
|
||||
if (client.getVar(fromVarbit.getVarbits()) != 0)
|
||||
{
|
||||
return fromVarbit;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
|
||||
* Copyright (c) 2018, PandahRS <https://github.com/PandahRS>
|
||||
* 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.discord;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import javax.imageio.ImageIO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.Skill;
|
||||
import net.runelite.api.WorldType;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.StatChanged;
|
||||
import net.runelite.api.events.VarbitChanged;
|
||||
import net.runelite.client.RuneLiteProperties;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.discord.DiscordService;
|
||||
import net.runelite.client.discord.events.DiscordJoinGame;
|
||||
import net.runelite.client.discord.events.DiscordJoinRequest;
|
||||
import net.runelite.client.discord.events.DiscordReady;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.events.PartyChanged;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.task.Schedule;
|
||||
import net.runelite.client.ui.ClientToolbar;
|
||||
import net.runelite.client.ui.NavigationButton;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.client.util.LinkBrowser;
|
||||
import net.runelite.client.ws.PartyMember;
|
||||
import net.runelite.client.ws.PartyService;
|
||||
import net.runelite.client.ws.WSClient;
|
||||
import net.runelite.http.api.ws.messages.party.UserJoin;
|
||||
import net.runelite.http.api.ws.messages.party.UserPart;
|
||||
import net.runelite.http.api.ws.messages.party.UserSync;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Discord",
|
||||
description = "Show your status and activity in the Discord user panel",
|
||||
tags = {"action", "activity", "external", "integration", "status"}
|
||||
)
|
||||
@Slf4j
|
||||
public class DiscordPlugin extends Plugin
|
||||
{
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private DiscordConfig config;
|
||||
|
||||
@Inject
|
||||
private ClientToolbar clientToolbar;
|
||||
|
||||
@Inject
|
||||
private DiscordState discordState;
|
||||
|
||||
@Inject
|
||||
private PartyService partyService;
|
||||
|
||||
@Inject
|
||||
private DiscordService discordService;
|
||||
|
||||
@Inject
|
||||
private WSClient wsClient;
|
||||
|
||||
@Inject
|
||||
private OkHttpClient okHttpClient;
|
||||
|
||||
private final Map<Skill, Integer> skillExp = new HashMap<>();
|
||||
private NavigationButton discordButton;
|
||||
private boolean loginFlag;
|
||||
|
||||
@Provides
|
||||
private DiscordConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(DiscordConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "discord.png");
|
||||
|
||||
discordButton = NavigationButton.builder()
|
||||
.tab(false)
|
||||
.tooltip("Join Discord")
|
||||
.icon(icon)
|
||||
.onClick(() -> LinkBrowser.browse(RuneLiteProperties.getDiscordInvite()))
|
||||
.build();
|
||||
|
||||
clientToolbar.addNavigation(discordButton);
|
||||
resetState();
|
||||
checkForGameStateUpdate();
|
||||
checkForAreaUpdate();
|
||||
|
||||
if (discordService.getCurrentUser() != null)
|
||||
{
|
||||
partyService.setUsername(discordService.getCurrentUser().username + "#" + discordService.getCurrentUser().discriminator);
|
||||
}
|
||||
|
||||
wsClient.registerMessage(DiscordUserInfo.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
clientToolbar.removeNavigation(discordButton);
|
||||
resetState();
|
||||
partyService.changeParty(null);
|
||||
wsClient.unregisterMessage(DiscordUserInfo.class);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged event)
|
||||
{
|
||||
switch (event.getGameState())
|
||||
{
|
||||
case LOGIN_SCREEN:
|
||||
resetState();
|
||||
checkForGameStateUpdate();
|
||||
return;
|
||||
case LOGGING_IN:
|
||||
loginFlag = true;
|
||||
break;
|
||||
case LOGGED_IN:
|
||||
if (loginFlag)
|
||||
{
|
||||
loginFlag = false;
|
||||
resetState();
|
||||
checkForGameStateUpdate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
checkForAreaUpdate();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged event)
|
||||
{
|
||||
if (event.getGroup().equalsIgnoreCase("discord"))
|
||||
{
|
||||
resetState();
|
||||
checkForGameStateUpdate();
|
||||
checkForAreaUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatChanged(StatChanged statChanged)
|
||||
{
|
||||
final Skill skill = statChanged.getSkill();
|
||||
final int exp = statChanged.getXp();
|
||||
final Integer previous = skillExp.put(skill, exp);
|
||||
|
||||
if (previous == null || previous >= exp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final DiscordGameEventType discordGameEventType = DiscordGameEventType.fromSkill(skill);
|
||||
|
||||
if (discordGameEventType != null && config.showSkillingActivity())
|
||||
{
|
||||
discordState.triggerEvent(discordGameEventType);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onVarbitChanged(VarbitChanged event)
|
||||
{
|
||||
if (!config.showRaidingActivity())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final DiscordGameEventType discordGameEventType = DiscordGameEventType.fromVarbit(client);
|
||||
|
||||
if (discordGameEventType != null)
|
||||
{
|
||||
discordState.triggerEvent(discordGameEventType);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordReady(DiscordReady event)
|
||||
{
|
||||
partyService.setUsername(event.getUsername() + "#" + event.getDiscriminator());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordJoinRequest(DiscordJoinRequest request)
|
||||
{
|
||||
// In order for the "Invite to join" message to work we need to have a valid party in Discord presence.
|
||||
// We lazily create the party here in order to avoid the (1 of 15) being permanently in the Discord status.
|
||||
if (!partyService.isInParty())
|
||||
{
|
||||
// Change to my party id, which is advertised in the Discord presence secret. This will open the socket,
|
||||
// send a join, and cause a UserJoin later for me, which will then update the presence and allow the
|
||||
// "Invite to join" to continue.
|
||||
partyService.changeParty(partyService.getLocalPartyId());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordJoinGame(DiscordJoinGame joinGame)
|
||||
{
|
||||
UUID partyId = UUID.fromString(joinGame.getJoinSecret());
|
||||
partyService.changeParty(partyId);
|
||||
updatePresence();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordUserInfo(final DiscordUserInfo event)
|
||||
{
|
||||
final PartyMember memberById = partyService.getMemberById(event.getMemberId());
|
||||
|
||||
if (memberById == null || memberById.getAvatar() != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String url = "https://cdn.discordapp.com/avatars/" + event.getUserId() + "/" + event.getAvatarId() + ".png";
|
||||
|
||||
if (Strings.isNullOrEmpty(event.getAvatarId()))
|
||||
{
|
||||
final String[] split = memberById.getName().split("#", 2);
|
||||
|
||||
if (split.length == 2)
|
||||
{
|
||||
int disc = Integer.valueOf(split[1]);
|
||||
int avatarId = disc % 5;
|
||||
url = "https://cdn.discordapp.com/embed/avatars/" + avatarId + ".png";
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Got user avatar {}", url);
|
||||
|
||||
final Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.build();
|
||||
|
||||
okHttpClient.newCall(request).enqueue(new Callback()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException
|
||||
{
|
||||
try // NOPMD: UseTryWithResources
|
||||
{
|
||||
if (!response.isSuccessful())
|
||||
{
|
||||
throw new IOException("Unexpected code " + response);
|
||||
}
|
||||
|
||||
final InputStream inputStream = response.body().byteStream();
|
||||
final BufferedImage image;
|
||||
synchronized (ImageIO.class)
|
||||
{
|
||||
image = ImageIO.read(inputStream);
|
||||
}
|
||||
memberById.setAvatar(image);
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onUserJoin(final UserJoin event)
|
||||
{
|
||||
updatePresence();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onUserSync(final UserSync event)
|
||||
{
|
||||
final PartyMember localMember = partyService.getLocalMember();
|
||||
|
||||
if (localMember != null)
|
||||
{
|
||||
if (discordService.getCurrentUser() != null)
|
||||
{
|
||||
final DiscordUserInfo userInfo = new DiscordUserInfo(
|
||||
discordService.getCurrentUser().userId,
|
||||
discordService.getCurrentUser().avatar);
|
||||
|
||||
userInfo.setMemberId(localMember.getMemberId());
|
||||
wsClient.send(userInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onUserPart(final UserPart event)
|
||||
{
|
||||
updatePresence();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPartyChanged(final PartyChanged event)
|
||||
{
|
||||
updatePresence();
|
||||
}
|
||||
|
||||
@Schedule(
|
||||
period = 1,
|
||||
unit = ChronoUnit.MINUTES
|
||||
)
|
||||
public void checkForValidStatus()
|
||||
{
|
||||
discordState.checkForTimeout();
|
||||
}
|
||||
|
||||
private void updatePresence()
|
||||
{
|
||||
discordState.refresh();
|
||||
}
|
||||
|
||||
private void resetState()
|
||||
{
|
||||
discordState.reset();
|
||||
}
|
||||
|
||||
private void checkForGameStateUpdate()
|
||||
{
|
||||
discordState.triggerEvent(client.getGameState() == GameState.LOGGED_IN
|
||||
? DiscordGameEventType.IN_GAME
|
||||
: DiscordGameEventType.IN_MENU);
|
||||
}
|
||||
|
||||
private void checkForAreaUpdate()
|
||||
{
|
||||
if (client.getLocalPlayer() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final int playerRegionID = WorldPoint.fromLocalInstance(client, client.getLocalPlayer().getLocalLocation()).getRegionID();
|
||||
|
||||
if (playerRegionID == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final EnumSet<WorldType> worldType = client.getWorldType();
|
||||
|
||||
if (worldType.contains(WorldType.DEADMAN))
|
||||
{
|
||||
discordState.triggerEvent(DiscordGameEventType.PLAYING_DEADMAN);
|
||||
return;
|
||||
}
|
||||
else if (WorldType.isPvpWorld(worldType))
|
||||
{
|
||||
discordState.triggerEvent(DiscordGameEventType.PLAYING_PVP);
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordGameEventType discordGameEventType = DiscordGameEventType.fromRegion(playerRegionID);
|
||||
|
||||
// NMZ uses the same region ID as KBD. KBD is always on plane 0 and NMZ is always above plane 0
|
||||
// Since KBD requires going through the wilderness there is no EventType for it
|
||||
if (DiscordGameEventType.MG_NIGHTMARE_ZONE == discordGameEventType
|
||||
&& client.getLocalPlayer().getWorldLocation().getPlane() == 0)
|
||||
{
|
||||
discordGameEventType = null;
|
||||
}
|
||||
|
||||
if (discordGameEventType == null)
|
||||
{
|
||||
// Unknown region, reset to default in-game
|
||||
discordState.triggerEvent(DiscordGameEventType.IN_GAME);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showArea(discordGameEventType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
discordState.triggerEvent(discordGameEventType);
|
||||
}
|
||||
|
||||
private boolean showArea(final DiscordGameEventType event)
|
||||
{
|
||||
if (event == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (event.getDiscordAreaType())
|
||||
{
|
||||
case BOSSES: return config.showBossActivity();
|
||||
case CITIES: return config.showCityActivity();
|
||||
case DUNGEONS: return config.showDungeonActivity();
|
||||
case MINIGAMES: return config.showMinigameActivity();
|
||||
case REGIONS: return config.showRegionsActivity();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@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.discord;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import lombok.Data;
|
||||
import net.runelite.client.RuneLiteProperties;
|
||||
import net.runelite.client.discord.DiscordPresence;
|
||||
import net.runelite.client.discord.DiscordService;
|
||||
import net.runelite.client.ws.PartyService;
|
||||
import static net.runelite.client.ws.PartyService.PARTY_MAX;
|
||||
|
||||
/**
|
||||
* This class contains data about currently active discord state.
|
||||
*/
|
||||
class DiscordState
|
||||
{
|
||||
@Data
|
||||
private static class EventWithTime
|
||||
{
|
||||
private final DiscordGameEventType type;
|
||||
private Instant start;
|
||||
private Instant updated;
|
||||
}
|
||||
|
||||
private final UUID partyId = UUID.randomUUID();
|
||||
private final List<EventWithTime> events = new ArrayList<>();
|
||||
private final DiscordService discordService;
|
||||
private final DiscordConfig config;
|
||||
private final PartyService party;
|
||||
private DiscordPresence lastPresence;
|
||||
|
||||
@Inject
|
||||
private DiscordState(final DiscordService discordService, final DiscordConfig config, final PartyService party)
|
||||
{
|
||||
this.discordService = discordService;
|
||||
this.config = config;
|
||||
this.party = party;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset state.
|
||||
*/
|
||||
void reset()
|
||||
{
|
||||
discordService.clearPresence();
|
||||
events.clear();
|
||||
lastPresence = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force refresh discord presence
|
||||
*/
|
||||
void refresh()
|
||||
{
|
||||
if (lastPresence == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder()
|
||||
.state(lastPresence.getState())
|
||||
.details(lastPresence.getDetails())
|
||||
.largeImageText(lastPresence.getLargeImageText())
|
||||
.startTimestamp(lastPresence.getStartTimestamp())
|
||||
.smallImageKey(lastPresence.getSmallImageKey())
|
||||
.partyMax(lastPresence.getPartyMax())
|
||||
.partySize(party.getMembers().size());
|
||||
|
||||
if (!party.isInParty() || party.isPartyOwner())
|
||||
{
|
||||
// This is only used to identify the invites on Discord's side. Our party ids are the secret.
|
||||
presenceBuilder.partyId(partyId.toString());
|
||||
presenceBuilder.joinSecret(party.getLocalPartyId().toString());
|
||||
}
|
||||
|
||||
discordService.updatePresence(presenceBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger new discord state update.
|
||||
*
|
||||
* @param eventType discord event type
|
||||
*/
|
||||
void triggerEvent(final DiscordGameEventType eventType)
|
||||
{
|
||||
final Optional<EventWithTime> foundEvent = events.stream().filter(e -> e.type == eventType).findFirst();
|
||||
final EventWithTime event;
|
||||
|
||||
if (foundEvent.isPresent())
|
||||
{
|
||||
event = foundEvent.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
event = new EventWithTime(eventType);
|
||||
event.setStart(Instant.now());
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
event.setUpdated(Instant.now());
|
||||
|
||||
if (event.getType().isShouldClear())
|
||||
{
|
||||
events.removeIf(e -> e.getType() != eventType && e.getType().isShouldBeCleared());
|
||||
}
|
||||
|
||||
if (event.getType().isShouldRestart())
|
||||
{
|
||||
event.setStart(Instant.now());
|
||||
}
|
||||
|
||||
events.sort((a, b) -> ComparisonChain.start()
|
||||
.compare(b.getType().getPriority(), a.getType().getPriority())
|
||||
.compare(b.getUpdated(), a.getUpdated())
|
||||
.result());
|
||||
|
||||
updatePresenceWithLatestEvent();
|
||||
}
|
||||
|
||||
private void updatePresenceWithLatestEvent()
|
||||
{
|
||||
if (events.isEmpty())
|
||||
{
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
final EventWithTime event = events.get(0);
|
||||
|
||||
String imageKey = null;
|
||||
String state = null;
|
||||
String details = null;
|
||||
|
||||
for (EventWithTime eventWithTime : events)
|
||||
{
|
||||
if (imageKey == null)
|
||||
{
|
||||
imageKey = eventWithTime.getType().getImageKey();
|
||||
}
|
||||
|
||||
if (details == null)
|
||||
{
|
||||
details = eventWithTime.getType().getDetails();
|
||||
}
|
||||
|
||||
if (state == null)
|
||||
{
|
||||
state = eventWithTime.getType().getState();
|
||||
}
|
||||
|
||||
if (imageKey != null && details != null && state != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace snapshot with + to make tooltip shorter (so it will span only 1 line)
|
||||
final String versionShortHand = RuneLiteProperties.getVersion().replace("-SNAPSHOT", "+");
|
||||
|
||||
final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder()
|
||||
.state(MoreObjects.firstNonNull(state, ""))
|
||||
.details(MoreObjects.firstNonNull(details, ""))
|
||||
.largeImageText(RuneLiteProperties.getTitle() + " v" + versionShortHand)
|
||||
.smallImageKey(imageKey)
|
||||
.partyMax(PARTY_MAX)
|
||||
.partySize(party.getMembers().size());
|
||||
|
||||
final Instant startTime;
|
||||
switch (config.elapsedTimeType())
|
||||
{
|
||||
case HIDDEN:
|
||||
startTime = null;
|
||||
break;
|
||||
case TOTAL:
|
||||
// We are tracking total time spent instead of per activity time so try to find
|
||||
// root event as this indicates start of tracking and find last updated one
|
||||
// to determine correct state we are in
|
||||
startTime = events.stream()
|
||||
.filter(e -> e.getType().isRoot())
|
||||
.sorted((a, b) -> b.getUpdated().compareTo(a.getUpdated()))
|
||||
.map(EventWithTime::getStart)
|
||||
.findFirst()
|
||||
.orElse(event.getStart());
|
||||
break;
|
||||
case ACTIVITY:
|
||||
default:
|
||||
startTime = event.getStart();
|
||||
break;
|
||||
}
|
||||
|
||||
presenceBuilder.startTimestamp(startTime);
|
||||
|
||||
if (!party.isInParty() || party.isPartyOwner())
|
||||
{
|
||||
presenceBuilder.partyId(partyId.toString());
|
||||
presenceBuilder.joinSecret(party.getLocalPartyId().toString());
|
||||
}
|
||||
|
||||
final DiscordPresence presence = presenceBuilder.build();
|
||||
|
||||
// This is to reduce amount of RPC calls
|
||||
if (!presence.equals(lastPresence))
|
||||
{
|
||||
lastPresence = presence;
|
||||
discordService.updatePresence(presence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for current state timeout and act upon it.
|
||||
*/
|
||||
void checkForTimeout()
|
||||
{
|
||||
if (events.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final Duration actionTimeout = Duration.ofMinutes(config.actionTimeout());
|
||||
final Instant now = Instant.now();
|
||||
final AtomicBoolean updatedAny = new AtomicBoolean();
|
||||
|
||||
final boolean removedAny = events.removeAll(events.stream()
|
||||
// Find only events that should time out
|
||||
.filter(event -> event.getType().isShouldTimeout() && now.isAfter(event.getUpdated().plus(actionTimeout)))
|
||||
// Reset start times on timed events that should restart
|
||||
.peek(event ->
|
||||
{
|
||||
if (event.getType().isShouldRestart())
|
||||
{
|
||||
event.setStart(null);
|
||||
updatedAny.set(true);
|
||||
}
|
||||
})
|
||||
// Now filter out events that should restart as we do not want to remove them
|
||||
.filter(event -> !event.getType().isShouldRestart())
|
||||
.filter(event -> event.getType().isShouldBeCleared())
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
if (removedAny || updatedAny.get())
|
||||
{
|
||||
updatePresenceWithLatestEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Tomas Slusny <slusnucky@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.discord;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import net.runelite.http.api.ws.messages.party.PartyMemberMessage;
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
class DiscordUserInfo extends PartyMemberMessage
|
||||
{
|
||||
private final String userId;
|
||||
private final String avatarId;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dpscounter;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup("dpscounter")
|
||||
public interface DpsConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
position = 0,
|
||||
keyName = "showDamage",
|
||||
name = "Show damage",
|
||||
description = "Show total damage instead of DPS"
|
||||
)
|
||||
default boolean showDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 1,
|
||||
keyName = "autopause",
|
||||
name = "Auto pause",
|
||||
description = "Pause the DPS tracker when a boss dies"
|
||||
)
|
||||
default boolean autopause()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 2,
|
||||
keyName = "autoreset",
|
||||
name = "Auto reset",
|
||||
description = "Reset the DPS tracker when a boss dies"
|
||||
)
|
||||
default boolean autoreset()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 3,
|
||||
keyName = "bossDamage",
|
||||
name = "Only boss damage",
|
||||
description = "Only count damage done to the boss, and not to other NPCs"
|
||||
)
|
||||
default boolean bossDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dpscounter;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.inject.Inject;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Actor;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Hitsplat;
|
||||
import net.runelite.api.NPC;
|
||||
import static net.runelite.api.NpcID.*;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.events.HitsplatApplied;
|
||||
import net.runelite.api.events.NpcDespawned;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.events.OverlayMenuClicked;
|
||||
import net.runelite.client.events.PartyChanged;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.ui.overlay.OverlayManager;
|
||||
import net.runelite.client.ws.PartyMember;
|
||||
import net.runelite.client.ws.PartyService;
|
||||
import net.runelite.client.ws.WSClient;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "DPS Counter",
|
||||
description = "Counts damage (per second) by a party",
|
||||
enabledByDefault = false
|
||||
)
|
||||
@Slf4j
|
||||
public class DpsCounterPlugin extends Plugin
|
||||
{
|
||||
private static final ImmutableSet<Integer> BOSSES = ImmutableSet.of(
|
||||
ABYSSAL_SIRE, ABYSSAL_SIRE_5887, ABYSSAL_SIRE_5888, ABYSSAL_SIRE_5889, ABYSSAL_SIRE_5890, ABYSSAL_SIRE_5891, ABYSSAL_SIRE_5908,
|
||||
ALCHEMICAL_HYDRA, ALCHEMICAL_HYDRA_8616, ALCHEMICAL_HYDRA_8617, ALCHEMICAL_HYDRA_8618, ALCHEMICAL_HYDRA_8619, ALCHEMICAL_HYDRA_8620, ALCHEMICAL_HYDRA_8621, ALCHEMICAL_HYDRA_8622,
|
||||
AHRIM_THE_BLIGHTED, DHAROK_THE_WRETCHED, GUTHAN_THE_INFESTED, KARIL_THE_TAINTED, TORAG_THE_CORRUPTED, VERAC_THE_DEFILED,
|
||||
BRYOPHYTA,
|
||||
CALLISTO, CALLISTO_6609,
|
||||
CERBERUS, CERBERUS_5863, CERBERUS_5866,
|
||||
CHAOS_ELEMENTAL, CHAOS_ELEMENTAL_6505,
|
||||
CHAOS_FANATIC,
|
||||
COMMANDER_ZILYANA, COMMANDER_ZILYANA_6493,
|
||||
CORPOREAL_BEAST,
|
||||
CRAZY_ARCHAEOLOGIST,
|
||||
CRYSTALLINE_HUNLLEF, CRYSTALLINE_HUNLLEF_9022, CRYSTALLINE_HUNLLEF_9023, CRYSTALLINE_HUNLLEF_9024,
|
||||
DAGANNOTH_SUPREME, DAGANNOTH_PRIME, DAGANNOTH_REX, DAGANNOTH_SUPREME_6496, DAGANNOTH_PRIME_6497, DAGANNOTH_REX_6498,
|
||||
DUSK, DAWN, DUSK_7851, DAWN_7852, DAWN_7853, DUSK_7854, DUSK_7855,
|
||||
GENERAL_GRAARDOR, GENERAL_GRAARDOR_6494,
|
||||
GIANT_MOLE, GIANT_MOLE_6499,
|
||||
HESPORI,
|
||||
KALPHITE_QUEEN, KALPHITE_QUEEN_963, KALPHITE_QUEEN_965, KALPHITE_QUEEN_4303, KALPHITE_QUEEN_4304, KALPHITE_QUEEN_6500, KALPHITE_QUEEN_6501,
|
||||
KING_BLACK_DRAGON, KING_BLACK_DRAGON_2642, KING_BLACK_DRAGON_6502,
|
||||
KRAKEN, KRAKEN_6640, KRAKEN_6656,
|
||||
KREEARRA, KREEARRA_6492,
|
||||
KRIL_TSUTSAROTH, KRIL_TSUTSAROTH_6495,
|
||||
THE_MIMIC, THE_MIMIC_8633,
|
||||
THE_NIGHTMARE, THE_NIGHTMARE_9426, THE_NIGHTMARE_9427, THE_NIGHTMARE_9428, THE_NIGHTMARE_9429, THE_NIGHTMARE_9430, THE_NIGHTMARE_9431, THE_NIGHTMARE_9432, THE_NIGHTMARE_9433,
|
||||
OBOR,
|
||||
SARACHNIS,
|
||||
SCORPIA,
|
||||
SKOTIZO,
|
||||
THERMONUCLEAR_SMOKE_DEVIL,
|
||||
TZKALZUK,
|
||||
TZTOKJAD, TZTOKJAD_6506,
|
||||
VENENATIS, VENENATIS_6610,
|
||||
VETION, VETION_REBORN,
|
||||
VORKATH, VORKATH_8058, VORKATH_8059, VORKATH_8060, VORKATH_8061,
|
||||
ZALCANO, ZALCANO_9050,
|
||||
ZULRAH, ZULRAH_2043, ZULRAH_2044,
|
||||
|
||||
// ToB
|
||||
THE_MAIDEN_OF_SUGADINTI, THE_MAIDEN_OF_SUGADINTI_8361, THE_MAIDEN_OF_SUGADINTI_8362, THE_MAIDEN_OF_SUGADINTI_8363, THE_MAIDEN_OF_SUGADINTI_8364, THE_MAIDEN_OF_SUGADINTI_8365,
|
||||
PESTILENT_BLOAT,
|
||||
NYLOCAS_VASILIAS, NYLOCAS_VASILIAS_8355, NYLOCAS_VASILIAS_8356, NYLOCAS_VASILIAS_8357,
|
||||
SOTETSEG, SOTETSEG_8388,
|
||||
XARPUS_8340, XARPUS_8341,
|
||||
VERZIK_VITUR_8370,
|
||||
VERZIK_VITUR_8372,
|
||||
VERZIK_VITUR_8374,
|
||||
|
||||
// CoX
|
||||
TEKTON, TEKTON_7541, TEKTON_7542, TEKTON_ENRAGED, TEKTON_ENRAGED_7544, TEKTON_7545,
|
||||
VESPULA, VESPULA_7531, VESPULA_7532, ABYSSAL_PORTAL,
|
||||
VANGUARD, VANGUARD_7526, VANGUARD_7527, VANGUARD_7528, VANGUARD_7529,
|
||||
GREAT_OLM, GREAT_OLM_LEFT_CLAW, GREAT_OLM_RIGHT_CLAW_7553, GREAT_OLM_7554, GREAT_OLM_LEFT_CLAW_7555,
|
||||
DEATHLY_RANGER, DEATHLY_MAGE,
|
||||
MUTTADILE, MUTTADILE_7562, MUTTADILE_7563,
|
||||
VASA_NISTIRIO, VASA_NISTIRIO_7567,
|
||||
GUARDIAN, GUARDIAN_7570, GUARDIAN_7571, GUARDIAN_7572,
|
||||
LIZARDMAN_SHAMAN_7573, LIZARDMAN_SHAMAN_7574,
|
||||
ICE_DEMON, ICE_DEMON_7585,
|
||||
SKELETAL_MYSTIC, SKELETAL_MYSTIC_7605, SKELETAL_MYSTIC_7606
|
||||
);
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private OverlayManager overlayManager;
|
||||
|
||||
@Inject
|
||||
private PartyService partyService;
|
||||
|
||||
@Inject
|
||||
private WSClient wsClient;
|
||||
|
||||
@Inject
|
||||
private DpsOverlay dpsOverlay;
|
||||
|
||||
@Inject
|
||||
private DpsConfig dpsConfig;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final Map<String, DpsMember> members = new ConcurrentHashMap<>();
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final DpsMember total = new DpsMember("Total");
|
||||
|
||||
@Provides
|
||||
DpsConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(DpsConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
total.reset();
|
||||
overlayManager.add(dpsOverlay);
|
||||
wsClient.registerMessage(DpsUpdate.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
wsClient.unregisterMessage(DpsUpdate.class);
|
||||
overlayManager.remove(dpsOverlay);
|
||||
members.clear();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPartyChanged(PartyChanged partyChanged)
|
||||
{
|
||||
members.clear();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onHitsplatApplied(HitsplatApplied hitsplatApplied)
|
||||
{
|
||||
Player player = client.getLocalPlayer();
|
||||
Actor actor = hitsplatApplied.getActor();
|
||||
if (!(actor instanceof NPC))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Hitsplat hitsplat = hitsplatApplied.getHitsplat();
|
||||
|
||||
if (hitsplat.isMine())
|
||||
{
|
||||
final int npcId = ((NPC) actor).getId();
|
||||
boolean isBoss = BOSSES.contains(npcId);
|
||||
if (dpsConfig.bossDamage() && !isBoss)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int hit = hitsplat.getAmount();
|
||||
// Update local member
|
||||
PartyMember localMember = partyService.getLocalMember();
|
||||
// If not in a party, user local player name
|
||||
final String name = localMember == null ? player.getName() : localMember.getName();
|
||||
DpsMember dpsMember = members.computeIfAbsent(name, DpsMember::new);
|
||||
dpsMember.addDamage(hit);
|
||||
|
||||
// broadcast damage
|
||||
if (localMember != null)
|
||||
{
|
||||
final DpsUpdate specialCounterUpdate = new DpsUpdate(hit);
|
||||
specialCounterUpdate.setMemberId(localMember.getMemberId());
|
||||
wsClient.send(specialCounterUpdate);
|
||||
}
|
||||
// apply to total
|
||||
}
|
||||
else if (hitsplat.isOthers())
|
||||
{
|
||||
final int npcId = ((NPC) actor).getId();
|
||||
boolean isBoss = BOSSES.contains(npcId);
|
||||
if ((dpsConfig.bossDamage() || actor != player.getInteracting()) && !isBoss)
|
||||
{
|
||||
// only track damage to npcs we are attacking, or is a nearby common boss
|
||||
return;
|
||||
}
|
||||
// apply to total
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
unpause();
|
||||
total.addDamage(hitsplat.getAmount());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDpsUpdate(DpsUpdate dpsUpdate)
|
||||
{
|
||||
if (partyService.getLocalMember().getMemberId().equals(dpsUpdate.getMemberId()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String name = partyService.getMemberById(dpsUpdate.getMemberId()).getName();
|
||||
if (name == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
unpause();
|
||||
|
||||
DpsMember dpsMember = members.computeIfAbsent(name, DpsMember::new);
|
||||
dpsMember.addDamage(dpsUpdate.getHit());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onOverlayMenuClicked(OverlayMenuClicked event)
|
||||
{
|
||||
if (event.getEntry() == DpsOverlay.RESET_ENTRY)
|
||||
{
|
||||
members.clear();
|
||||
total.reset();
|
||||
}
|
||||
else if (event.getEntry() == DpsOverlay.UNPAUSE_ENTRY)
|
||||
{
|
||||
unpause();
|
||||
}
|
||||
else if (event.getEntry() == DpsOverlay.PAUSE_ENTRY)
|
||||
{
|
||||
pause();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNpcDespawned(NpcDespawned npcDespawned)
|
||||
{
|
||||
NPC npc = npcDespawned.getNpc();
|
||||
|
||||
if (npc.isDead() && BOSSES.contains(npc.getId()))
|
||||
{
|
||||
log.debug("Boss has died!");
|
||||
|
||||
if (dpsConfig.autoreset())
|
||||
{
|
||||
members.values().forEach(DpsMember::reset);
|
||||
total.reset();
|
||||
}
|
||||
else if (dpsConfig.autopause())
|
||||
{
|
||||
pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pause()
|
||||
{
|
||||
if (total.isPaused())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Pausing");
|
||||
|
||||
for (DpsMember dpsMember : members.values())
|
||||
{
|
||||
dpsMember.pause();
|
||||
}
|
||||
total.pause();
|
||||
|
||||
dpsOverlay.setPaused(true);
|
||||
}
|
||||
|
||||
private void unpause()
|
||||
{
|
||||
if (!total.isPaused())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Unpausing");
|
||||
|
||||
for (DpsMember dpsMember : members.values())
|
||||
{
|
||||
dpsMember.unpause();
|
||||
}
|
||||
total.unpause();
|
||||
|
||||
dpsOverlay.setPaused(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dpscounter;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
class DpsMember
|
||||
{
|
||||
private final String name;
|
||||
private Instant start;
|
||||
private Instant end;
|
||||
private int damage;
|
||||
|
||||
void addDamage(int amount)
|
||||
{
|
||||
if (start == null)
|
||||
{
|
||||
start = Instant.now();
|
||||
}
|
||||
|
||||
damage += amount;
|
||||
}
|
||||
|
||||
float getDps()
|
||||
{
|
||||
if (start == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Instant now = end == null ? Instant.now() : end;
|
||||
int diff = (int) (now.toEpochMilli() - start.toEpochMilli()) / 1000;
|
||||
if (diff == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (float) damage / (float) diff;
|
||||
}
|
||||
|
||||
void pause()
|
||||
{
|
||||
end = Instant.now();
|
||||
}
|
||||
|
||||
boolean isPaused()
|
||||
{
|
||||
return start == null || end != null;
|
||||
}
|
||||
|
||||
void unpause()
|
||||
{
|
||||
if (end == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
start = start.plus(Duration.between(end, Instant.now()));
|
||||
end = null;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
damage = 0;
|
||||
start = end = Instant.now();
|
||||
}
|
||||
|
||||
Duration elapsed()
|
||||
{
|
||||
return Duration.between(start, end == null ? Instant.now() : end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dpscounter;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.client.ui.overlay.OverlayMenuEntry;
|
||||
import net.runelite.client.ui.overlay.OverlayPanel;
|
||||
import net.runelite.client.ui.overlay.components.ComponentConstants;
|
||||
import net.runelite.client.ui.overlay.components.LineComponent;
|
||||
import net.runelite.client.ui.overlay.components.TitleComponent;
|
||||
import net.runelite.client.ui.overlay.tooltip.Tooltip;
|
||||
import net.runelite.client.ui.overlay.tooltip.TooltipManager;
|
||||
import net.runelite.client.util.QuantityFormatter;
|
||||
import net.runelite.client.ws.PartyService;
|
||||
|
||||
class DpsOverlay extends OverlayPanel
|
||||
{
|
||||
private static final DecimalFormat DPS_FORMAT = new DecimalFormat("#0.0");
|
||||
private static final int PANEL_WIDTH_OFFSET = 10; // assumes 8 for panel component border + 2px between left and right
|
||||
|
||||
static final OverlayMenuEntry RESET_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Reset", "DPS counter");
|
||||
static final OverlayMenuEntry PAUSE_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Pause", "DPS counter");
|
||||
static final OverlayMenuEntry UNPAUSE_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Unpause", "DPS counter");
|
||||
|
||||
private final DpsCounterPlugin dpsCounterPlugin;
|
||||
private final DpsConfig dpsConfig;
|
||||
private final PartyService partyService;
|
||||
private final Client client;
|
||||
private final TooltipManager tooltipManager;
|
||||
|
||||
@Inject
|
||||
DpsOverlay(DpsCounterPlugin dpsCounterPlugin, DpsConfig dpsConfig, PartyService partyService, Client client,
|
||||
TooltipManager tooltipManager)
|
||||
{
|
||||
super(dpsCounterPlugin);
|
||||
this.dpsCounterPlugin = dpsCounterPlugin;
|
||||
this.dpsConfig = dpsConfig;
|
||||
this.partyService = partyService;
|
||||
this.client = client;
|
||||
this.tooltipManager = tooltipManager;
|
||||
getMenuEntries().add(RESET_ENTRY);
|
||||
setPaused(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMouseOver()
|
||||
{
|
||||
DpsMember total = dpsCounterPlugin.getTotal();
|
||||
Duration elapsed = total.elapsed();
|
||||
long s = elapsed.getSeconds();
|
||||
String format;
|
||||
if (s >= 3600)
|
||||
{
|
||||
format = String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60));
|
||||
}
|
||||
else
|
||||
{
|
||||
format = String.format("%d:%02d", s / 60, (s % 60));
|
||||
}
|
||||
tooltipManager.add(new Tooltip("Elapsed time: " + format));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
Map<String, DpsMember> dpsMembers = dpsCounterPlugin.getMembers();
|
||||
if (dpsMembers.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean inParty = !partyService.getMembers().isEmpty();
|
||||
boolean showDamage = dpsConfig.showDamage();
|
||||
DpsMember total = dpsCounterPlugin.getTotal();
|
||||
boolean paused = total.isPaused();
|
||||
|
||||
final String title = (inParty ? "Party " : "") + (showDamage ? "Damage" : "DPS") + (paused ? " (paused)" : "");
|
||||
panelComponent.getChildren().add(
|
||||
TitleComponent.builder()
|
||||
.text(title)
|
||||
.build());
|
||||
|
||||
int maxWidth = ComponentConstants.STANDARD_WIDTH;
|
||||
FontMetrics fontMetrics = graphics.getFontMetrics();
|
||||
|
||||
for (DpsMember dpsMember : dpsMembers.values())
|
||||
{
|
||||
String left = dpsMember.getName();
|
||||
String right = showDamage ? QuantityFormatter.formatNumber(dpsMember.getDamage()) : DPS_FORMAT.format(dpsMember.getDps());
|
||||
maxWidth = Math.max(maxWidth, fontMetrics.stringWidth(left) + fontMetrics.stringWidth(right));
|
||||
panelComponent.getChildren().add(
|
||||
LineComponent.builder()
|
||||
.left(left)
|
||||
.right(right)
|
||||
.build());
|
||||
}
|
||||
|
||||
panelComponent.setPreferredSize(new Dimension(maxWidth + PANEL_WIDTH_OFFSET, 0));
|
||||
|
||||
if (!inParty)
|
||||
{
|
||||
Player player = client.getLocalPlayer();
|
||||
if (player.getName() != null)
|
||||
{
|
||||
DpsMember self = dpsMembers.get(player.getName());
|
||||
|
||||
if (self != null && total.getDamage() > self.getDamage())
|
||||
{
|
||||
panelComponent.getChildren().add(
|
||||
LineComponent.builder()
|
||||
.left(total.getName())
|
||||
.right(showDamage ? Integer.toString(total.getDamage()) : DPS_FORMAT.format(total.getDps()))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.render(graphics);
|
||||
}
|
||||
|
||||
void setPaused(boolean paused)
|
||||
{
|
||||
OverlayMenuEntry remove = paused ? PAUSE_ENTRY : UNPAUSE_ENTRY;
|
||||
OverlayMenuEntry add = paused ? UNPAUSE_ENTRY : PAUSE_ENTRY;
|
||||
getMenuEntries().remove(remove);
|
||||
if (!getMenuEntries().contains(add))
|
||||
{
|
||||
getMenuEntries().add(add);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dpscounter;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import net.runelite.http.api.ws.messages.party.PartyMemberMessage;
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DpsUpdate extends PartyMemberMessage
|
||||
{
|
||||
private int hit;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2020, dekvall <https://github.com/dekvall>
|
||||
* 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 HOLDER 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.driftnet;
|
||||
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.runelite.api.GameObject;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
class DriftNet
|
||||
{
|
||||
private final int objectId;
|
||||
private final Varbits statusVarbit;
|
||||
private final Varbits countVarbit;
|
||||
private final Set<WorldPoint> adjacentTiles;
|
||||
|
||||
private GameObject net;
|
||||
private DriftNetStatus status;
|
||||
private int count;
|
||||
@Setter
|
||||
private DriftNetStatus prevTickStatus;
|
||||
|
||||
// Nets that are not accepting fish are those currently not accepting, or those which were not
|
||||
// accepting in the previous tick. (When a fish shoal is 2 tiles adjacent to a drift net and is
|
||||
// moving to a net that is just being setup it will be denied even though the net is currently
|
||||
// in the CATCHING status)
|
||||
boolean isNotAcceptingFish()
|
||||
{
|
||||
return (status != DriftNetStatus.CATCH && status != DriftNetStatus.SET) ||
|
||||
(prevTickStatus != DriftNetStatus.CATCH && prevTickStatus != DriftNetStatus.SET);
|
||||
}
|
||||
|
||||
String getFormattedCountText()
|
||||
{
|
||||
return status != DriftNetStatus.UNSET ? count + "/10" : "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2020, dekvall <https://github.com/dekvall>
|
||||
* 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 HOLDER 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.driftnet;
|
||||
|
||||
import java.awt.Color;
|
||||
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 net.runelite.client.config.Units;
|
||||
|
||||
@ConfigGroup(DriftNetPlugin.CONFIG_GROUP)
|
||||
public interface DriftNetConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
position = 1,
|
||||
keyName = "showNetStatus",
|
||||
name = "Show net status",
|
||||
description = "Show net status and fish count"
|
||||
)
|
||||
default boolean showNetStatus()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 2,
|
||||
keyName = "countColor",
|
||||
name = "Fish count color",
|
||||
description = "Color of the fish count text"
|
||||
)
|
||||
default Color countColor()
|
||||
{
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 3,
|
||||
keyName = "highlightUntaggedFish",
|
||||
name = "Highlight untagged fish",
|
||||
description = "Highlight the untagged fish"
|
||||
)
|
||||
default boolean highlightUntaggedFish()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 4,
|
||||
keyName = "timeoutDelay",
|
||||
name = "Tagged timeout",
|
||||
description = "Time required for a tag to expire"
|
||||
)
|
||||
@Range(
|
||||
min = 1,
|
||||
max = 100
|
||||
)
|
||||
@Units(Units.TICKS)
|
||||
default int timeoutDelay()
|
||||
{
|
||||
return 60;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "untaggedFishColor",
|
||||
name = "Untagged fish color",
|
||||
description = "Color of untagged fish",
|
||||
position = 5
|
||||
)
|
||||
default Color untaggedFishColor()
|
||||
{
|
||||
return Color.CYAN;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "tagAnnette",
|
||||
name = "Tag Annette when no nets in inventory",
|
||||
description = "Tag Annette when no nets in inventory",
|
||||
position = 6
|
||||
)
|
||||
default boolean tagAnnetteWhenNoNets()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "annetteTagColor",
|
||||
name = "Annette tag color",
|
||||
description = "Color of Annette tag",
|
||||
position = 7
|
||||
)
|
||||
default Color annetteTagColor()
|
||||
{
|
||||
return Color.RED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 2020, dekvall <https://github.com/dekvall>
|
||||
* 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.driftnet;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.GameObject;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.Point;
|
||||
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.OverlayPriority;
|
||||
import net.runelite.client.ui.overlay.OverlayUtil;
|
||||
|
||||
class DriftNetOverlay extends Overlay
|
||||
{
|
||||
private final DriftNetConfig config;
|
||||
private final DriftNetPlugin plugin;
|
||||
|
||||
@Inject
|
||||
private DriftNetOverlay(DriftNetConfig config, DriftNetPlugin plugin)
|
||||
{
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
setPosition(OverlayPosition.DYNAMIC);
|
||||
setPriority(OverlayPriority.LOW);
|
||||
setLayer(OverlayLayer.ABOVE_SCENE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
if (!plugin.isInDriftNetArea())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.highlightUntaggedFish())
|
||||
{
|
||||
renderFish(graphics);
|
||||
}
|
||||
if (config.showNetStatus())
|
||||
{
|
||||
renderNets(graphics);
|
||||
}
|
||||
if (config.tagAnnetteWhenNoNets())
|
||||
{
|
||||
renderAnnette(graphics);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void renderFish(Graphics2D graphics)
|
||||
{
|
||||
for (NPC fish : plugin.getFish())
|
||||
{
|
||||
if (!plugin.getTaggedFish().containsKey(fish))
|
||||
{
|
||||
OverlayUtil.renderActorOverlay(graphics, fish, "", config.untaggedFishColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderNets(Graphics2D graphics)
|
||||
{
|
||||
for (DriftNet net : plugin.getNETS())
|
||||
{
|
||||
final Shape polygon = net.getNet().getConvexHull();
|
||||
|
||||
if (polygon != null)
|
||||
{
|
||||
OverlayUtil.renderPolygon(graphics, polygon, net.getStatus().getColor());
|
||||
}
|
||||
|
||||
String text = net.getFormattedCountText();
|
||||
Point textLocation = net.getNet().getCanvasTextLocation(graphics, text, 0);
|
||||
if (textLocation != null)
|
||||
{
|
||||
OverlayUtil.renderTextLocation(graphics, textLocation, text, config.countColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderAnnette(Graphics2D graphics)
|
||||
{
|
||||
GameObject annette = plugin.getAnnette();
|
||||
if (annette != null && !plugin.isDriftNetsInInventory())
|
||||
{
|
||||
OverlayUtil.renderPolygon(graphics, annette.getConvexHull(), config.annetteTagColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright (c) 2020, dekvall <https://github.com/dekvall>
|
||||
* 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 HOLDER 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.driftnet;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.Actor;
|
||||
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.ItemContainer;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.NpcID;
|
||||
import net.runelite.api.NullObjectID;
|
||||
import net.runelite.api.ObjectID;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
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.events.InteractingChanged;
|
||||
import net.runelite.api.events.ItemContainerChanged;
|
||||
import net.runelite.api.events.NpcDespawned;
|
||||
import net.runelite.api.events.NpcSpawned;
|
||||
import net.runelite.api.events.VarbitChanged;
|
||||
import net.runelite.client.callback.ClientThread;
|
||||
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 = "Drift Net",
|
||||
description = "Display information about drift nets",
|
||||
tags = {"hunter", "fishing", "drift", "net"},
|
||||
enabledByDefault = false
|
||||
)
|
||||
public class DriftNetPlugin extends Plugin
|
||||
{
|
||||
static final String CONFIG_GROUP = "driftnet";
|
||||
private static final int UNDERWATER_REGION = 15008;
|
||||
private static final String CHAT_PRODDING_FISH = "You prod at the shoal of fish to scare it.";
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private DriftNetConfig config;
|
||||
|
||||
@Inject
|
||||
private OverlayManager overlayManager;
|
||||
|
||||
@Inject
|
||||
private DriftNetOverlay overlay;
|
||||
|
||||
@Getter
|
||||
private Set<NPC> fish = new HashSet<>();
|
||||
@Getter
|
||||
private Map<NPC, Integer> taggedFish = new HashMap<>();
|
||||
@Getter
|
||||
private final List<DriftNet> NETS = ImmutableList.of(
|
||||
new DriftNet(NullObjectID.NULL_31433, Varbits.NORTH_NET_STATUS, Varbits.NORTH_NET_CATCH_COUNT, ImmutableSet.of(
|
||||
new WorldPoint(3746, 10297, 1),
|
||||
new WorldPoint(3747, 10297, 1),
|
||||
new WorldPoint(3748, 10297, 1),
|
||||
new WorldPoint(3749, 10297, 1)
|
||||
)),
|
||||
new DriftNet(NullObjectID.NULL_31434, Varbits.SOUTH_NET_STATUS, Varbits.SOUTH_NET_CATCH_COUNT, ImmutableSet.of(
|
||||
new WorldPoint(3742, 10288, 1),
|
||||
new WorldPoint(3742, 10289, 1),
|
||||
new WorldPoint(3742, 10290, 1),
|
||||
new WorldPoint(3742, 10291, 1),
|
||||
new WorldPoint(3742, 10292, 1)
|
||||
)));
|
||||
|
||||
@Getter
|
||||
private boolean inDriftNetArea;
|
||||
private boolean armInteraction;
|
||||
|
||||
@Getter
|
||||
private boolean driftNetsInInventory;
|
||||
|
||||
@Getter
|
||||
private GameObject annette;
|
||||
|
||||
@Provides
|
||||
DriftNetConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(DriftNetConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
overlayManager.add(overlay);
|
||||
|
||||
if (client.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
clientThread.invokeLater(() ->
|
||||
{
|
||||
inDriftNetArea = checkArea();
|
||||
updateDriftNetVarbits();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
overlayManager.remove(overlay);
|
||||
reset();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged event)
|
||||
{
|
||||
if (event.getGameState() != GameState.LOGGED_IN)
|
||||
{
|
||||
annette = null;
|
||||
}
|
||||
switch (event.getGameState())
|
||||
{
|
||||
case LOGIN_SCREEN:
|
||||
case HOPPING:
|
||||
case LOADING:
|
||||
reset();
|
||||
break;
|
||||
case LOGGED_IN:
|
||||
inDriftNetArea = checkArea();
|
||||
updateDriftNetVarbits();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
fish.clear();
|
||||
taggedFish.clear();
|
||||
armInteraction = false;
|
||||
inDriftNetArea = false;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onVarbitChanged(VarbitChanged event)
|
||||
{
|
||||
updateDriftNetVarbits();
|
||||
}
|
||||
|
||||
private void updateDriftNetVarbits()
|
||||
{
|
||||
if (!inDriftNetArea)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (DriftNet net : NETS)
|
||||
{
|
||||
DriftNetStatus status = DriftNetStatus.of(client.getVar(net.getStatusVarbit()));
|
||||
int count = client.getVar(net.getCountVarbit());
|
||||
|
||||
net.setStatus(status);
|
||||
net.setCount(count);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onInteractingChanged(InteractingChanged event)
|
||||
{
|
||||
if (armInteraction
|
||||
&& event.getSource() == client.getLocalPlayer()
|
||||
&& event.getTarget() instanceof NPC
|
||||
&& ((NPC) event.getTarget()).getId() == NpcID.FISH_SHOAL)
|
||||
{
|
||||
tagFish(event.getTarget());
|
||||
armInteraction = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFishNextToNet(NPC fish, Collection<DriftNet> nets)
|
||||
{
|
||||
final WorldPoint fishTile = WorldPoint.fromLocalInstance(client, fish.getLocalLocation());
|
||||
return nets.stream().anyMatch(net -> net.getAdjacentTiles().contains(fishTile));
|
||||
}
|
||||
|
||||
private boolean isTagExpired(Integer tick)
|
||||
{
|
||||
return tick + config.timeoutDelay() < client.getTickCount();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameTick(GameTick tick)
|
||||
{
|
||||
if (!inDriftNetArea)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<DriftNet> closedNets = NETS.stream()
|
||||
.filter(DriftNet::isNotAcceptingFish)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
taggedFish.entrySet().removeIf(entry ->
|
||||
isTagExpired(entry.getValue()) ||
|
||||
isFishNextToNet(entry.getKey(), closedNets)
|
||||
);
|
||||
|
||||
NETS.forEach(net -> net.setPrevTickStatus(net.getStatus()));
|
||||
|
||||
armInteraction = false;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage event)
|
||||
{
|
||||
if (!inDriftNetArea)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getType() == ChatMessageType.SPAM && event.getMessage().equals(CHAT_PRODDING_FISH))
|
||||
{
|
||||
Actor target = client.getLocalPlayer().getInteracting();
|
||||
|
||||
if (target instanceof NPC && ((NPC) target).getId() == NpcID.FISH_SHOAL)
|
||||
{
|
||||
tagFish(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the fish is on an adjacent tile, the interaction change happens after
|
||||
// the chat message is sent, so we arm it
|
||||
armInteraction = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tagFish(Actor fish)
|
||||
{
|
||||
NPC fishTarget = (NPC) fish;
|
||||
taggedFish.put(fishTarget, client.getTickCount());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNpcSpawned(NpcSpawned event)
|
||||
{
|
||||
final NPC npc = event.getNpc();
|
||||
if (npc.getId() == NpcID.FISH_SHOAL)
|
||||
{
|
||||
fish.add(npc);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNpcDespawned(NpcDespawned event)
|
||||
{
|
||||
final NPC npc = event.getNpc();
|
||||
fish.remove(npc);
|
||||
taggedFish.remove(npc);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameObjectSpawned(GameObjectSpawned event)
|
||||
{
|
||||
GameObject object = event.getGameObject();
|
||||
if (object.getId() == ObjectID.ANNETTE)
|
||||
{
|
||||
annette = object;
|
||||
}
|
||||
|
||||
for (DriftNet net : NETS)
|
||||
{
|
||||
if (net.getObjectId() == object.getId())
|
||||
{
|
||||
net.setNet(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameObjectDespawned(GameObjectDespawned event)
|
||||
{
|
||||
GameObject object = event.getGameObject();
|
||||
if (object == annette)
|
||||
{
|
||||
annette = null;
|
||||
}
|
||||
|
||||
for (DriftNet net : NETS)
|
||||
{
|
||||
if (net.getObjectId() == object.getId())
|
||||
{
|
||||
net.setNet(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onItemContainerChanged(final ItemContainerChanged event)
|
||||
{
|
||||
final ItemContainer itemContainer = event.getItemContainer();
|
||||
if (itemContainer != client.getItemContainer(InventoryID.INVENTORY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
driftNetsInInventory = itemContainer.contains(ItemID.DRIFT_NET);
|
||||
}
|
||||
|
||||
private boolean checkArea()
|
||||
{
|
||||
final Player localPlayer = client.getLocalPlayer();
|
||||
if (localPlayer == null || !client.isInInstancedRegion())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final WorldPoint point = WorldPoint.fromLocalInstance(client, localPlayer.getLocalLocation());
|
||||
return point.getRegionID() == UNDERWATER_REGION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2020, dekvall <https://github.com/dekvall>
|
||||
* 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 HOLDER 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.driftnet;
|
||||
|
||||
import java.awt.Color;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
enum DriftNetStatus
|
||||
{
|
||||
UNSET(Color.YELLOW),
|
||||
SET(Color.GREEN),
|
||||
CATCH(Color.GREEN),
|
||||
FULL(Color.RED);
|
||||
|
||||
private final Color color;
|
||||
|
||||
static DriftNetStatus of(int varbitValue)
|
||||
{
|
||||
switch (varbitValue)
|
||||
{
|
||||
case 0:
|
||||
return UNSET;
|
||||
case 1:
|
||||
return SET;
|
||||
case 2:
|
||||
return CATCH;
|
||||
case 3:
|
||||
return FULL;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Lotto <https://github.com/devLotto>
|
||||
* 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.emojis;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Map;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
|
||||
enum Emoji
|
||||
{
|
||||
SLIGHT_SMILE(":)"),
|
||||
JOY("=')"),
|
||||
COWBOY("3:)"),
|
||||
BLUSH("^_^"),
|
||||
SMILE(":D"),
|
||||
GRINNING("=D"),
|
||||
WINK(";)"),
|
||||
STUCK_OUT_TONGUE_CLOSED_EYES("X-P"),
|
||||
STUCK_OUT_TONGUE(":P"),
|
||||
YUM("=P~"),
|
||||
HUGGING("<gt>:D<lt>"), // >:D<
|
||||
TRIUMPH(":<gt>"), // :>
|
||||
THINKING(":-?"),
|
||||
CONFUSED(":/"),
|
||||
NEUTRAL_FACE("=|"),
|
||||
EXPRESSIONLESS(":|"),
|
||||
UNAMUSED(":-|"),
|
||||
SLIGHT_FROWN(":("),
|
||||
FROWNING2("=("),
|
||||
CRY(":'("),
|
||||
SOB(":_("),
|
||||
FLUSHED(":$"),
|
||||
ZIPPER_MOUTH(":-#"),
|
||||
PERSEVERE("<gt>_<lt>"), // >_<
|
||||
SUNGLASSES("8-)"),
|
||||
INNOCENT("O:)"),
|
||||
SMILING_IMP("<gt>:)"), // >:)
|
||||
RAGE("<gt>:("), // >:(
|
||||
HUSHED(":-O"),
|
||||
OPEN_MOUTH(":O"),
|
||||
SCREAM(":-@"),
|
||||
SEE_NO_EVIL("X_X"),
|
||||
DANCER("\\:D/"),
|
||||
OK_HAND("(Ok)"),
|
||||
THUMBSUP("(Y)"),
|
||||
THUMBSDOWN("(N)"),
|
||||
HEARTS("<lt>3"), // <3
|
||||
BROKEN_HEART("<lt>/3"), // </3
|
||||
ZZZ("Zzz"),
|
||||
FISH("<lt><gt><lt>"), // <><
|
||||
CAT(":3"),
|
||||
DOG("=3"),
|
||||
CRAB("V(;,;)V"),
|
||||
FORK_AND_KNIFE("--E"),
|
||||
COOKING("--(o)"),
|
||||
PARTY_POPPER("@@@"),
|
||||
EYES("O.O"),
|
||||
SWEAT(";;"),
|
||||
PILE_OF_POO("~@~"),
|
||||
FIRE("(/\\)"),
|
||||
ALIEN("(@.@)"),
|
||||
EGGPLANT("8=D"),
|
||||
WAVE("(^_^)/"),
|
||||
HEART_EYES("(*.*)"),
|
||||
FACEPALM("M-)"),
|
||||
PENSIVE("V_V"),
|
||||
ACORN("<lt>D~"), // <D~
|
||||
GORILLA(":G"),
|
||||
PLEADING("(n_n)"),
|
||||
XD("Xd"),
|
||||
;
|
||||
|
||||
private static final Map<String, Emoji> emojiMap;
|
||||
|
||||
private final String trigger;
|
||||
|
||||
static
|
||||
{
|
||||
ImmutableMap.Builder<String, Emoji> builder = new ImmutableMap.Builder<>();
|
||||
|
||||
for (final Emoji emoji : values())
|
||||
{
|
||||
builder.put(emoji.trigger, emoji);
|
||||
}
|
||||
|
||||
emojiMap = builder.build();
|
||||
}
|
||||
|
||||
Emoji(String trigger)
|
||||
{
|
||||
this.trigger = trigger;
|
||||
}
|
||||
|
||||
BufferedImage loadImage()
|
||||
{
|
||||
return ImageUtil.getResourceStreamFromClass(getClass(), this.name().toLowerCase() + ".png");
|
||||
}
|
||||
|
||||
static Emoji getEmoji(String trigger)
|
||||
{
|
||||
return emojiMap.get(trigger);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Lotto <https://github.com/devLotto>
|
||||
* 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.emojis;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import joptsimple.internal.Strings;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
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.chat.ChatMessageManager;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.client.util.Text;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Emojis",
|
||||
description = "Replaces common emoticons such as :) with their corresponding emoji in the chat",
|
||||
enabledByDefault = false
|
||||
)
|
||||
@Slf4j
|
||||
public class EmojiPlugin extends Plugin
|
||||
{
|
||||
private static final Pattern WHITESPACE_REGEXP = Pattern.compile("[\\s\\u00A0]");
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private ChatMessageManager chatMessageManager;
|
||||
|
||||
private int modIconsStart = -1;
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
clientThread.invoke(this::loadEmojiIcons);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged gameStateChanged)
|
||||
{
|
||||
if (gameStateChanged.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
loadEmojiIcons();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadEmojiIcons()
|
||||
{
|
||||
final IndexedSprite[] modIcons = client.getModIcons();
|
||||
if (modIconsStart != -1 || modIcons == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final Emoji[] emojis = Emoji.values();
|
||||
final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + emojis.length);
|
||||
modIconsStart = modIcons.length;
|
||||
|
||||
for (int i = 0; i < emojis.length; i++)
|
||||
{
|
||||
final Emoji emoji = emojis[i];
|
||||
|
||||
try
|
||||
{
|
||||
final BufferedImage image = emoji.loadImage();
|
||||
final IndexedSprite sprite = ImageUtil.getImageIndexedSprite(image, client);
|
||||
newModIcons[modIconsStart + i] = sprite;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.warn("Failed to load the sprite for emoji " + emoji, ex);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Adding emoji icons");
|
||||
client.setModIcons(newModIcons);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage chatMessage)
|
||||
{
|
||||
if (client.getGameState() != GameState.LOGGED_IN || modIconsStart == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (chatMessage.getType())
|
||||
{
|
||||
case PUBLICCHAT:
|
||||
case MODCHAT:
|
||||
case FRIENDSCHAT:
|
||||
case PRIVATECHAT:
|
||||
case PRIVATECHATOUT:
|
||||
case MODPRIVATECHAT:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
final MessageNode messageNode = chatMessage.getMessageNode();
|
||||
final String message = messageNode.getValue();
|
||||
final String updatedMessage = updateMessage(message);
|
||||
|
||||
if (updatedMessage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
messageNode.setRuneLiteFormatMessage(updatedMessage);
|
||||
chatMessageManager.update(messageNode);
|
||||
client.refreshChat();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onOverheadTextChanged(final OverheadTextChanged event)
|
||||
{
|
||||
if (!(event.getActor() instanceof Player))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final String message = event.getOverheadText();
|
||||
final String updatedMessage = updateMessage(message);
|
||||
|
||||
if (updatedMessage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
event.getActor().setOverheadText(updatedMessage);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String updateMessage(final String message)
|
||||
{
|
||||
final String[] messageWords = WHITESPACE_REGEXP.split(message);
|
||||
|
||||
boolean editedMessage = false;
|
||||
for (int i = 0; i < messageWords.length; i++)
|
||||
{
|
||||
// Remove tags except for <lt> and <gt>
|
||||
final String trigger = Text.removeFormattingTags(messageWords[i]);
|
||||
final Emoji emoji = Emoji.getEmoji(trigger);
|
||||
|
||||
if (emoji == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
final int emojiId = modIconsStart + emoji.ordinal();
|
||||
|
||||
messageWords[i] = messageWords[i].replace(trigger, "<img=" + emojiId + ">");
|
||||
editedMessage = true;
|
||||
}
|
||||
|
||||
// If we haven't edited the message any, don't update it.
|
||||
if (!editedMessage)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Strings.join(messageWords, " ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||
* 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 HOLDER 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.entityhider;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup("entityhider")
|
||||
public interface EntityHiderConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
position = 1,
|
||||
keyName = "hidePlayers",
|
||||
name = "Hide Players",
|
||||
description = "Configures whether or not players are hidden"
|
||||
)
|
||||
default boolean hidePlayers()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 2,
|
||||
keyName = "hidePlayers2D",
|
||||
name = "Hide Players 2D",
|
||||
description = "Configures whether or not players 2D elements are hidden"
|
||||
)
|
||||
default boolean hidePlayers2D()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 3,
|
||||
keyName = "hideFriends",
|
||||
name = "Hide Friends",
|
||||
description = "Configures whether or not friends are hidden"
|
||||
)
|
||||
default boolean hideFriends()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 4,
|
||||
keyName = "hideClanMates",
|
||||
name = "Hide Friends Chat members",
|
||||
description = "Configures whether or not friends chat members are hidden"
|
||||
)
|
||||
default boolean hideFriendsChatMembers()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 5,
|
||||
keyName = "hideLocalPlayer",
|
||||
name = "Hide Local Player",
|
||||
description = "Configures whether or not the local player is hidden"
|
||||
)
|
||||
default boolean hideLocalPlayer()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 6,
|
||||
keyName = "hideLocalPlayer2D",
|
||||
name = "Hide Local Player 2D",
|
||||
description = "Configures whether or not the local player's 2D elements are hidden"
|
||||
)
|
||||
default boolean hideLocalPlayer2D()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 7,
|
||||
keyName = "hideNPCs",
|
||||
name = "Hide NPCs",
|
||||
description = "Configures whether or not NPCs are hidden"
|
||||
)
|
||||
default boolean hideNPCs()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 8,
|
||||
keyName = "hideNPCs2D",
|
||||
name = "Hide NPCs 2D",
|
||||
description = "Configures whether or not NPCs 2D elements are hidden"
|
||||
)
|
||||
default boolean hideNPCs2D()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 9,
|
||||
keyName = "hidePets",
|
||||
name = "Hide Pets",
|
||||
description = "Configures whether or not other player pets are hidden"
|
||||
)
|
||||
default boolean hidePets()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 10,
|
||||
keyName = "hideAttackers",
|
||||
name = "Hide Attackers",
|
||||
description = "Configures whether or not NPCs/players attacking you are hidden"
|
||||
)
|
||||
default boolean hideAttackers()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 11,
|
||||
keyName = "hideProjectiles",
|
||||
name = "Hide Projectiles",
|
||||
description = "Configures whether or not projectiles are hidden"
|
||||
)
|
||||
default boolean hideProjectiles()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||
* 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 HOLDER 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.entityhider;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Entity Hider",
|
||||
description = "Hide players, NPCs, and/or projectiles",
|
||||
tags = {"npcs", "players", "projectiles"},
|
||||
enabledByDefault = false
|
||||
)
|
||||
public class EntityHiderPlugin extends Plugin
|
||||
{
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private EntityHiderConfig config;
|
||||
|
||||
@Provides
|
||||
EntityHiderConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(EntityHiderConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged e)
|
||||
{
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged event)
|
||||
{
|
||||
if (event.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
client.setIsHidingEntities(isPlayerRegionAllowed());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConfig()
|
||||
{
|
||||
client.setIsHidingEntities(isPlayerRegionAllowed());
|
||||
|
||||
client.setPlayersHidden(config.hidePlayers());
|
||||
client.setPlayersHidden2D(config.hidePlayers2D());
|
||||
|
||||
client.setFriendsHidden(config.hideFriends());
|
||||
client.setFriendsChatMembersHidden(config.hideFriendsChatMembers());
|
||||
|
||||
client.setLocalPlayerHidden(config.hideLocalPlayer());
|
||||
client.setLocalPlayerHidden2D(config.hideLocalPlayer2D());
|
||||
|
||||
client.setNPCsHidden(config.hideNPCs());
|
||||
client.setNPCsHidden2D(config.hideNPCs2D());
|
||||
|
||||
client.setPetsHidden(config.hidePets());
|
||||
|
||||
client.setAttackersHidden(config.hideAttackers());
|
||||
|
||||
client.setProjectilesHidden(config.hideProjectiles());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
client.setIsHidingEntities(false);
|
||||
|
||||
client.setPlayersHidden(false);
|
||||
client.setPlayersHidden2D(false);
|
||||
|
||||
client.setFriendsHidden(false);
|
||||
client.setFriendsChatMembersHidden(false);
|
||||
|
||||
client.setLocalPlayerHidden(false);
|
||||
client.setLocalPlayerHidden2D(false);
|
||||
|
||||
client.setNPCsHidden(false);
|
||||
client.setNPCsHidden2D(false);
|
||||
|
||||
client.setPetsHidden(false);
|
||||
|
||||
client.setAttackersHidden(false);
|
||||
|
||||
client.setProjectilesHidden(false);
|
||||
}
|
||||
|
||||
private boolean isPlayerRegionAllowed()
|
||||
{
|
||||
final Player localPlayer = client.getLocalPlayer();
|
||||
|
||||
if (localPlayer == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
final int playerRegionID = WorldPoint.fromLocalInstance(client, localPlayer.getLocalLocation()).getRegionID();
|
||||
|
||||
// 9520 = Castle Wars
|
||||
return playerRegionID != 9520;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.examine;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
class CacheKey
|
||||
{
|
||||
private final ExamineType type;
|
||||
private final int id;
|
||||
|
||||
public CacheKey(ExamineType type, int id)
|
||||
{
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int hash = 3;
|
||||
hash = 23 * hash + Objects.hashCode(this.type);
|
||||
hash = 23 * hash + this.id;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
final CacheKey other = (CacheKey) obj;
|
||||
if (this.id != other.id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this.type == other.type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* 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.examine;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.inject.Provides;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.ItemComposition;
|
||||
import net.runelite.api.ItemID;
|
||||
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.WidgetID;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import static net.runelite.api.widgets.WidgetInfo.SEED_VAULT_ITEM_CONTAINER;
|
||||
import static net.runelite.api.widgets.WidgetInfo.TO_CHILD;
|
||||
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
|
||||
import net.runelite.api.widgets.WidgetItem;
|
||||
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.eventbus.Subscribe;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.util.QuantityFormatter;
|
||||
import net.runelite.client.util.Text;
|
||||
import net.runelite.http.api.examine.ExamineClient;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* Submits examine info to the api
|
||||
*
|
||||
* @author Adam
|
||||
*/
|
||||
@PluginDescriptor(
|
||||
name = "Examine",
|
||||
description = "Send examine information to the API",
|
||||
tags = {"npcs", "items", "inventory", "objects"}
|
||||
)
|
||||
@Slf4j
|
||||
public class ExaminePlugin extends Plugin
|
||||
{
|
||||
private static final Pattern X_PATTERN = Pattern.compile("^\\d+ x ");
|
||||
|
||||
private final Deque<PendingExamine> pending = new ArrayDeque<>();
|
||||
private final Cache<CacheKey, Boolean> cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(128L)
|
||||
.build();
|
||||
|
||||
@Inject
|
||||
private ExamineClient examineClient;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ItemManager itemManager;
|
||||
|
||||
@Inject
|
||||
private ChatMessageManager chatMessageManager;
|
||||
|
||||
@Provides
|
||||
ExamineClient provideExamineClient(OkHttpClient okHttpClient)
|
||||
{
|
||||
return new ExamineClient(okHttpClient);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged event)
|
||||
{
|
||||
pending.clear();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuOptionClicked(MenuOptionClicked event)
|
||||
{
|
||||
if (!Text.removeTags(event.getMenuOption()).equals("Examine"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ExamineType type;
|
||||
int id, quantity = -1;
|
||||
switch (event.getMenuAction())
|
||||
{
|
||||
case EXAMINE_ITEM:
|
||||
{
|
||||
type = ExamineType.ITEM;
|
||||
id = event.getId();
|
||||
|
||||
int widgetId = event.getWidgetId();
|
||||
int widgetGroup = TO_GROUP(widgetId);
|
||||
int widgetChild = TO_CHILD(widgetId);
|
||||
Widget widget = client.getWidget(widgetGroup, widgetChild);
|
||||
WidgetItem widgetItem = widget.getWidgetItem(event.getActionParam());
|
||||
quantity = widgetItem != null && widgetItem.getId() >= 0 ? widgetItem.getQuantity() : 1;
|
||||
|
||||
// Examine on inventory items with more than 100000 quantity is handled locally and shows the item stack
|
||||
// count, instead of sending the examine packet, so that you can see how many items are in the stack.
|
||||
// Replace that message with one that formats the quantity using the quantity formatter instead.
|
||||
if (quantity >= 100_000)
|
||||
{
|
||||
int itemId = event.getId();
|
||||
final ChatMessageBuilder message = new ChatMessageBuilder()
|
||||
.append(QuantityFormatter.formatNumber(quantity)).append(" x ").append(itemManager.getItemComposition(itemId).getName());
|
||||
chatMessageManager.queue(QueuedMessage.builder()
|
||||
.type(ChatMessageType.ITEM_EXAMINE)
|
||||
.runeLiteFormattedMessage(message.build())
|
||||
.build());
|
||||
event.consume();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EXAMINE_ITEM_GROUND:
|
||||
type = ExamineType.ITEM;
|
||||
id = event.getId();
|
||||
break;
|
||||
case CC_OP_LOW_PRIORITY:
|
||||
{
|
||||
type = ExamineType.ITEM_BANK_EQ;
|
||||
int[] qi = findItemFromWidget(event.getWidgetId(), event.getActionParam());
|
||||
if (qi == null)
|
||||
{
|
||||
log.debug("Examine for item with unknown widget: {}", event);
|
||||
return;
|
||||
}
|
||||
quantity = qi[0];
|
||||
id = qi[1];
|
||||
break;
|
||||
}
|
||||
case EXAMINE_OBJECT:
|
||||
type = ExamineType.OBJECT;
|
||||
id = event.getId();
|
||||
break;
|
||||
case EXAMINE_NPC:
|
||||
type = ExamineType.NPC;
|
||||
id = event.getId();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
PendingExamine pendingExamine = new PendingExamine();
|
||||
pendingExamine.setType(type);
|
||||
pendingExamine.setId(id);
|
||||
pendingExamine.setQuantity(quantity);
|
||||
pendingExamine.setCreated(Instant.now());
|
||||
pending.push(pendingExamine);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage event)
|
||||
{
|
||||
ExamineType type;
|
||||
switch (event.getType())
|
||||
{
|
||||
case ITEM_EXAMINE:
|
||||
type = ExamineType.ITEM;
|
||||
break;
|
||||
case OBJECT_EXAMINE:
|
||||
type = ExamineType.OBJECT;
|
||||
break;
|
||||
case NPC_EXAMINE:
|
||||
type = ExamineType.NPC;
|
||||
break;
|
||||
case GAMEMESSAGE:
|
||||
type = ExamineType.ITEM_BANK_EQ;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (pending.isEmpty())
|
||||
{
|
||||
log.debug("Got examine without a pending examine?");
|
||||
return;
|
||||
}
|
||||
|
||||
PendingExamine pendingExamine = pending.pop();
|
||||
|
||||
if (pendingExamine.getType() != type)
|
||||
{
|
||||
log.debug("Type mismatch for pending examine: {} != {}", pendingExamine.getType(), type);
|
||||
pending.clear(); // eh
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Got examine for {} {}: {}", pendingExamine.getType(), pendingExamine.getId(), event.getMessage());
|
||||
|
||||
// If it is an item, show the price of it
|
||||
final ItemComposition itemComposition;
|
||||
if (pendingExamine.getType() == ExamineType.ITEM || pendingExamine.getType() == ExamineType.ITEM_BANK_EQ)
|
||||
{
|
||||
final int itemId = pendingExamine.getId();
|
||||
final int itemQuantity = pendingExamine.getQuantity();
|
||||
|
||||
if (itemId == ItemID.COINS_995)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
itemComposition = itemManager.getItemComposition(itemId);
|
||||
getItemPrice(itemComposition.getId(), itemComposition, itemQuantity);
|
||||
}
|
||||
else
|
||||
{
|
||||
itemComposition = null;
|
||||
}
|
||||
|
||||
// Don't submit examine info for tradeable items, which we already have from the RS item api
|
||||
if (itemComposition != null && itemComposition.isTradeable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Large quantities of items show eg. 100000 x Coins
|
||||
if (type == ExamineType.ITEM && X_PATTERN.matcher(event.getMessage()).lookingAt())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CacheKey key = new CacheKey(type, pendingExamine.getId());
|
||||
Boolean cached = cache.getIfPresent(key);
|
||||
if (cached != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cache.put(key, Boolean.TRUE);
|
||||
submitExamine(pendingExamine, event.getMessage());
|
||||
}
|
||||
|
||||
private int[] findItemFromWidget(int widgetId, int actionParam)
|
||||
{
|
||||
int widgetGroup = TO_GROUP(widgetId);
|
||||
int widgetChild = TO_CHILD(widgetId);
|
||||
Widget widget = client.getWidget(widgetGroup, widgetChild);
|
||||
|
||||
if (widget == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (WidgetInfo.EQUIPMENT.getGroupId() == widgetGroup)
|
||||
{
|
||||
Widget widgetItem = widget.getChild(1);
|
||||
if (widgetItem != null)
|
||||
{
|
||||
return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()};
|
||||
}
|
||||
}
|
||||
else if (WidgetInfo.SMITHING_INVENTORY_ITEMS_CONTAINER.getGroupId() == widgetGroup)
|
||||
{
|
||||
Widget widgetItem = widget.getChild(2);
|
||||
if (widgetItem != null)
|
||||
{
|
||||
return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()};
|
||||
}
|
||||
}
|
||||
else if (WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER.getGroupId() == widgetGroup
|
||||
|| WidgetInfo.RUNE_POUCH_ITEM_CONTAINER.getGroupId() == widgetGroup)
|
||||
{
|
||||
Widget widgetItem = widget.getChild(actionParam);
|
||||
if (widgetItem != null)
|
||||
{
|
||||
return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()};
|
||||
}
|
||||
}
|
||||
else if (WidgetInfo.BANK_ITEM_CONTAINER.getGroupId() == widgetGroup
|
||||
|| WidgetInfo.CLUE_SCROLL_REWARD_ITEM_CONTAINER.getGroupId() == widgetGroup
|
||||
|| WidgetInfo.LOOTING_BAG_CONTAINER.getGroupId() == widgetGroup
|
||||
|| WidgetID.SEED_VAULT_INVENTORY_GROUP_ID == widgetGroup
|
||||
|| WidgetID.SEED_BOX_GROUP_ID == widgetGroup
|
||||
|| WidgetID.PLAYER_TRADE_SCREEN_GROUP_ID == widgetGroup
|
||||
|| WidgetID.PLAYER_TRADE_INVENTORY_GROUP_ID == widgetGroup)
|
||||
{
|
||||
Widget widgetItem = widget.getChild(actionParam);
|
||||
if (widgetItem != null)
|
||||
{
|
||||
return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()};
|
||||
}
|
||||
}
|
||||
else if (WidgetInfo.SHOP_ITEMS_CONTAINER.getGroupId() == widgetGroup)
|
||||
{
|
||||
Widget widgetItem = widget.getChild(actionParam);
|
||||
if (widgetItem != null)
|
||||
{
|
||||
return new int[]{1, widgetItem.getItemId()};
|
||||
}
|
||||
}
|
||||
else if (WidgetID.SEED_VAULT_GROUP_ID == widgetGroup)
|
||||
{
|
||||
Widget widgetItem = client.getWidget(SEED_VAULT_ITEM_CONTAINER).getChild(actionParam);
|
||||
if (widgetItem != null)
|
||||
{
|
||||
return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void getItemPrice(int id, ItemComposition itemComposition, int quantity)
|
||||
{
|
||||
// quantity is at least 1
|
||||
quantity = Math.max(1, quantity);
|
||||
final int gePrice = itemManager.getItemPrice(id);
|
||||
final int alchPrice = itemComposition.getHaPrice();
|
||||
|
||||
if (gePrice > 0 || alchPrice > 0)
|
||||
{
|
||||
final ChatMessageBuilder message = new ChatMessageBuilder()
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append("Price of ")
|
||||
.append(ChatColorType.HIGHLIGHT);
|
||||
|
||||
if (quantity > 1)
|
||||
{
|
||||
message
|
||||
.append(QuantityFormatter.formatNumber(quantity))
|
||||
.append(" x ");
|
||||
}
|
||||
|
||||
message
|
||||
.append(itemComposition.getName())
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append(":");
|
||||
|
||||
if (gePrice > 0)
|
||||
{
|
||||
message
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append(" GE average ")
|
||||
.append(ChatColorType.HIGHLIGHT)
|
||||
.append(QuantityFormatter.formatNumber((long) gePrice * quantity));
|
||||
|
||||
if (quantity > 1)
|
||||
{
|
||||
message
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append(" (")
|
||||
.append(ChatColorType.HIGHLIGHT)
|
||||
.append(QuantityFormatter.formatNumber(gePrice))
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append("ea)");
|
||||
}
|
||||
}
|
||||
|
||||
if (alchPrice > 0)
|
||||
{
|
||||
message
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append(" HA value ")
|
||||
.append(ChatColorType.HIGHLIGHT)
|
||||
.append(QuantityFormatter.formatNumber((long) alchPrice * quantity));
|
||||
|
||||
if (quantity > 1)
|
||||
{
|
||||
message
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append(" (")
|
||||
.append(ChatColorType.HIGHLIGHT)
|
||||
.append(QuantityFormatter.formatNumber(alchPrice))
|
||||
.append(ChatColorType.NORMAL)
|
||||
.append("ea)");
|
||||
}
|
||||
}
|
||||
|
||||
chatMessageManager.queue(QueuedMessage.builder()
|
||||
.type(ChatMessageType.ITEM_EXAMINE)
|
||||
.runeLiteFormattedMessage(message.build())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void submitExamine(PendingExamine examine, String text)
|
||||
{
|
||||
int id = examine.getId();
|
||||
|
||||
switch (examine.getType())
|
||||
{
|
||||
case ITEM:
|
||||
examineClient.submitItem(id, text);
|
||||
break;
|
||||
case OBJECT:
|
||||
examineClient.submitObject(id, text);
|
||||
break;
|
||||
case NPC:
|
||||
examineClient.submitNpc(id, text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.examine;
|
||||
|
||||
public enum ExamineType
|
||||
{
|
||||
ITEM,
|
||||
ITEM_BANK_EQ,
|
||||
NPC,
|
||||
OBJECT;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.examine;
|
||||
|
||||
import java.time.Instant;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
class PendingExamine
|
||||
{
|
||||
private ExamineType type;
|
||||
private int id;
|
||||
private int quantity;
|
||||
private Instant created;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Abex
|
||||
* 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.fairyring;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup("fairyrings")
|
||||
public interface FairyRingConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
keyName = "autoOpen",
|
||||
name = "Open search automatically",
|
||||
description = "Open the search widget every time you enter a fairy ring"
|
||||
)
|
||||
default boolean autoOpen()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Abex
|
||||
* Copyright (c) 2017, Tyler <https://github.com/tylerthardy>
|
||||
* Copyright (c) 2018, Yoav Ram <https://github.com/yoyo421>
|
||||
* Copyright (c) 2018, Infinitay <https://github.com/Infinitay>
|
||||
* 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.fairyring;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.ScriptEvent;
|
||||
import net.runelite.api.ScriptID;
|
||||
import net.runelite.api.SoundEffectID;
|
||||
import net.runelite.api.SpriteID;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.widgets.WidgetType;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.VarbitChanged;
|
||||
import net.runelite.api.events.WidgetLoaded;
|
||||
import net.runelite.api.widgets.JavaScriptCallback;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetID;
|
||||
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.game.chatbox.ChatboxPanelManager;
|
||||
import net.runelite.client.game.chatbox.ChatboxTextInput;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.util.Text;
|
||||
|
||||
@Slf4j
|
||||
@PluginDescriptor(
|
||||
name = "Fairy Rings",
|
||||
description = "Show the location of the fairy ring teleport",
|
||||
tags = {"teleportation"}
|
||||
)
|
||||
public class FairyRingPlugin extends Plugin
|
||||
{
|
||||
private static final String[] leftDial = {"A", "D", "C", "B"};
|
||||
private static final String[] middleDial = {"I", "L", "K", "J"};
|
||||
private static final String[] rightDial = {"P", "S", "R", "Q"};
|
||||
|
||||
private static final int ENTRY_PADDING = 3;
|
||||
|
||||
private static final String MENU_OPEN = "Open";
|
||||
private static final String MENU_CLOSE = "Close";
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private FairyRingConfig config;
|
||||
|
||||
@Inject
|
||||
private ChatboxPanelManager chatboxPanelManager;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
private ChatboxTextInput searchInput = null;
|
||||
private Widget searchBtn;
|
||||
private Collection<CodeWidgets> codes = null;
|
||||
|
||||
@Data
|
||||
private static class CodeWidgets
|
||||
{
|
||||
// The fairy hideout has both of these null, because its not the same as the rest of them
|
||||
@Nullable
|
||||
private Widget favorite;
|
||||
|
||||
@Nullable
|
||||
private Widget code;
|
||||
|
||||
private Widget description;
|
||||
}
|
||||
|
||||
@Provides
|
||||
FairyRingConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(FairyRingConfig.class);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onVarbitChanged(VarbitChanged event)
|
||||
{
|
||||
setWidgetTextToDestination();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onWidgetLoaded(WidgetLoaded widgetLoaded)
|
||||
{
|
||||
if (widgetLoaded.getGroupId() == WidgetID.FAIRY_RING_PANEL_GROUP_ID)
|
||||
{
|
||||
setWidgetTextToDestination();
|
||||
|
||||
Widget header = client.getWidget(WidgetInfo.FAIRY_RING_HEADER);
|
||||
if (header != null)
|
||||
{
|
||||
searchBtn = header.createChild(-1, WidgetType.GRAPHIC);
|
||||
searchBtn.setSpriteId(SpriteID.GE_SEARCH);
|
||||
searchBtn.setOriginalWidth(17);
|
||||
searchBtn.setOriginalHeight(17);
|
||||
searchBtn.setOriginalX(11);
|
||||
searchBtn.setOriginalY(11);
|
||||
searchBtn.setHasListener(true);
|
||||
searchBtn.setAction(1, MENU_OPEN);
|
||||
searchBtn.setOnOpListener((JavaScriptCallback) this::menuOpen);
|
||||
searchBtn.setName("Search");
|
||||
searchBtn.revalidate();
|
||||
|
||||
codes = null;
|
||||
|
||||
if (config.autoOpen())
|
||||
{
|
||||
openSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void menuOpen(ScriptEvent e)
|
||||
{
|
||||
openSearch();
|
||||
client.playSoundEffect(SoundEffectID.UI_BOOP);
|
||||
}
|
||||
|
||||
private void menuClose(ScriptEvent e)
|
||||
{
|
||||
updateFilter("");
|
||||
chatboxPanelManager.close();
|
||||
client.playSoundEffect(SoundEffectID.UI_BOOP);
|
||||
}
|
||||
|
||||
private void setWidgetTextToDestination()
|
||||
{
|
||||
Widget fairyRingTeleportButton = client.getWidget(WidgetInfo.FAIRY_RING_TELEPORT_BUTTON);
|
||||
if (fairyRingTeleportButton != null && !fairyRingTeleportButton.isHidden())
|
||||
{
|
||||
String destination;
|
||||
try
|
||||
{
|
||||
FairyRings fairyRingDestination = getFairyRingDestination(client.getVar(Varbits.FAIRY_RING_DIAL_ADCB),
|
||||
client.getVar(Varbits.FAIRY_RIGH_DIAL_ILJK), client.getVar(Varbits.FAIRY_RING_DIAL_PSRQ));
|
||||
destination = fairyRingDestination.getDestination();
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
destination = "Invalid location";
|
||||
}
|
||||
|
||||
fairyRingTeleportButton.setText(destination);
|
||||
}
|
||||
}
|
||||
|
||||
private FairyRings getFairyRingDestination(int varbitValueDialLeft, int varbitValueDialMiddle, int varbitValueDialRight)
|
||||
{
|
||||
return FairyRings.valueOf(leftDial[varbitValueDialLeft] + middleDial[varbitValueDialMiddle] + rightDial[varbitValueDialRight]);
|
||||
}
|
||||
|
||||
private void openSearch()
|
||||
{
|
||||
updateFilter("");
|
||||
searchBtn.setAction(1, MENU_CLOSE);
|
||||
searchBtn.setOnOpListener((JavaScriptCallback) this::menuClose);
|
||||
searchInput = chatboxPanelManager.openTextInput("Filter fairy rings")
|
||||
.onChanged(s -> clientThread.invokeLater(() -> updateFilter(s)))
|
||||
.onDone(s -> false)
|
||||
.onClose(() ->
|
||||
{
|
||||
clientThread.invokeLater(() -> updateFilter(""));
|
||||
searchBtn.setOnOpListener((JavaScriptCallback) this::menuOpen);
|
||||
searchBtn.setAction(1, MENU_OPEN);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameTick(GameTick t)
|
||||
{
|
||||
// This has to happen because the only widget that gets hidden is the tli one
|
||||
Widget fairyRingTeleportButton = client.getWidget(WidgetInfo.FAIRY_RING_TELEPORT_BUTTON);
|
||||
boolean fairyRingWidgetOpen = fairyRingTeleportButton != null && !fairyRingTeleportButton.isHidden();
|
||||
boolean chatboxOpen = searchInput != null && chatboxPanelManager.getCurrentInput() == searchInput;
|
||||
|
||||
if (!fairyRingWidgetOpen && chatboxOpen)
|
||||
{
|
||||
chatboxPanelManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFilter(String filter)
|
||||
{
|
||||
filter = filter.toLowerCase();
|
||||
final Widget list = client.getWidget(WidgetInfo.FAIRY_RING_LIST);
|
||||
final Widget favorites = client.getWidget(WidgetInfo.FAIRY_RING_FAVORITES);
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (codes != null)
|
||||
{
|
||||
// Check to make sure the list hasn't been rebuild since we were last her
|
||||
// Do this by making sure the list's dynamic children are the same as when we last saw them
|
||||
if (codes.stream().noneMatch(w ->
|
||||
{
|
||||
Widget codeWidget = w.getCode();
|
||||
if (codeWidget == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return list.getChild(codeWidget.getIndex()) == codeWidget;
|
||||
}))
|
||||
{
|
||||
codes = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (codes == null)
|
||||
{
|
||||
// Find all of the widgets that we care about, grouping by their Y value
|
||||
Map<Integer, CodeWidgets> codeMap = new TreeMap<>();
|
||||
|
||||
for (Widget w : list.getStaticChildren())
|
||||
{
|
||||
if (w.isSelfHidden())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (w.getSpriteId() != -1)
|
||||
{
|
||||
codeMap.computeIfAbsent(w.getRelativeY(), k -> new CodeWidgets()).setFavorite(w);
|
||||
}
|
||||
else if (!Strings.isNullOrEmpty(w.getText()))
|
||||
{
|
||||
codeMap.computeIfAbsent(w.getRelativeY(), k -> new CodeWidgets()).setDescription(w);
|
||||
}
|
||||
}
|
||||
|
||||
for (Widget w : list.getDynamicChildren())
|
||||
{
|
||||
if (w.isSelfHidden())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CodeWidgets c = codeMap.computeIfAbsent(w.getRelativeY(), k -> new CodeWidgets());
|
||||
c.setCode(w);
|
||||
}
|
||||
|
||||
codes = codeMap.values();
|
||||
}
|
||||
|
||||
// Relayout the panel
|
||||
int y = 0;
|
||||
|
||||
if (favorites != null)
|
||||
{
|
||||
boolean hide = !filter.isEmpty();
|
||||
favorites.setHidden(hide);
|
||||
if (!hide)
|
||||
{
|
||||
y += favorites.getOriginalHeight() + ENTRY_PADDING;
|
||||
}
|
||||
}
|
||||
|
||||
for (CodeWidgets c : codes)
|
||||
{
|
||||
String code = Text.removeTags(c.getDescription().getName()).replaceAll(" ", "");
|
||||
String tags = null;
|
||||
|
||||
if (!code.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
FairyRings ring = FairyRings.valueOf(code);
|
||||
tags = ring.getTags();
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
log.warn("Unable to find ring with code '{}'", code, e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hidden = !(filter.isEmpty()
|
||||
|| Text.removeTags(c.getDescription().getText()).toLowerCase().contains(filter)
|
||||
|| code.toLowerCase().contains(filter)
|
||||
|| tags != null && tags.contains(filter));
|
||||
|
||||
if (c.getCode() != null)
|
||||
{
|
||||
c.getCode().setHidden(hidden);
|
||||
c.getCode().setOriginalY(y);
|
||||
}
|
||||
|
||||
if (c.getFavorite() != null)
|
||||
{
|
||||
c.getFavorite().setHidden(hidden);
|
||||
c.getFavorite().setOriginalY(y);
|
||||
}
|
||||
|
||||
c.getDescription().setHidden(hidden);
|
||||
c.getDescription().setOriginalY(y);
|
||||
|
||||
if (!hidden)
|
||||
{
|
||||
y += c.getDescription().getHeight() + ENTRY_PADDING;
|
||||
}
|
||||
}
|
||||
|
||||
y -= ENTRY_PADDING;
|
||||
|
||||
if (y < 0)
|
||||
{
|
||||
y = 0;
|
||||
}
|
||||
|
||||
int newHeight = 0;
|
||||
if (list.getScrollHeight() > 0)
|
||||
{
|
||||
newHeight = (list.getScrollY() * y) / list.getScrollHeight();
|
||||
}
|
||||
|
||||
list.setScrollHeight(y);
|
||||
list.revalidateScroll();
|
||||
client.runScript(
|
||||
ScriptID.UPDATE_SCROLLBAR,
|
||||
WidgetInfo.FAIRY_RING_LIST_SCROLLBAR.getId(),
|
||||
WidgetInfo.FAIRY_RING_LIST.getId(),
|
||||
newHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Yoav Ram <https://github.com/yoyo421>
|
||||
* Copyright (c) 2018, Infinitay <https://github.com/Infinitay>
|
||||
* 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.fairyring;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public enum FairyRings
|
||||
{
|
||||
// A
|
||||
AIQ("Mudskipper Point"),
|
||||
AIR("(Island) South-east of Ardougne"),
|
||||
AJQ("Cave south of Dorgesh-Kaan"),
|
||||
AJR("Slayer cave"),
|
||||
AJS("Penguins near Miscellania"),
|
||||
AKQ("Piscatoris Hunter area"),
|
||||
AKS("Feldip Hunter area"),
|
||||
ALP("(Island) Lighthouse"),
|
||||
ALQ("Haunted Woods east of Canifis"),
|
||||
ALR("Abyssal Area"),
|
||||
ALS("McGrubor's Wood"),
|
||||
|
||||
// B
|
||||
BIP("(Island) South-west of Mort Myre"),
|
||||
BIQ("Kalphite Hive"),
|
||||
BIS("Ardougne Zoo - Unicorns"),
|
||||
BJR("Realm of the Fisher King"),
|
||||
BJS("(Island) Near Zul-Andra", "zulrah"),
|
||||
BKP("South of Castle Wars"),
|
||||
BKQ("Enchanted Valley"),
|
||||
BKR("Mort Myre Swamp, south of Canifis"),
|
||||
BKS("Zanaris"),
|
||||
BLP("TzHaar area"),
|
||||
BLR("Legends' Guild"),
|
||||
|
||||
// C
|
||||
CIP("(Island) Miscellania"),
|
||||
CIQ("North-west of Yanille"),
|
||||
CIS("North of the Arceuus Library"),
|
||||
CIR("North-east of the Farming Guild", "mount karuulm konar"),
|
||||
CJR("Sinclair Mansion", "falo bard"),
|
||||
CKP("Cosmic entity's plane"),
|
||||
CKR("South of Tai Bwo Wannai Village"),
|
||||
CKS("Canifis"),
|
||||
CLP("(Island) South of Draynor Village"),
|
||||
CLR("(Island) Ape Atoll"),
|
||||
CLS("(Island) Hazelmere's home"),
|
||||
|
||||
// D
|
||||
DIP("(Sire Boss) Abyssal Nexus"),
|
||||
DIR("Gorak's Plane"),
|
||||
DIQ("Player-owned house", "poh home"),
|
||||
DIS("Wizards' Tower"),
|
||||
DJP("Tower of Life"),
|
||||
DJR("Chasm of Fire"),
|
||||
DKP("South of Musa Point"),
|
||||
DKR("Edgeville, Grand Exchange"),
|
||||
DKS("Polar Hunter area"),
|
||||
DLQ("North of Nardah"),
|
||||
DLR("(Island) Poison Waste south of Isafdar"),
|
||||
DLS("Myreque hideout under The Hollows");
|
||||
|
||||
@Getter
|
||||
private final String destination;
|
||||
|
||||
@Getter
|
||||
private final String tags;
|
||||
|
||||
FairyRings(String destination)
|
||||
{
|
||||
this(destination, "");
|
||||
}
|
||||
|
||||
FairyRings(String destination, String tags)
|
||||
{
|
||||
this.destination = destination;
|
||||
this.tags = tags.toLowerCase() + " " + destination.toLowerCase();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.runelite.client.plugins.feed;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup("feed")
|
||||
public interface FeedConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
keyName = "includeBlogPosts",
|
||||
name = "Include Blog Posts",
|
||||
description = "Configures whether blog posts are displayed",
|
||||
position = 0
|
||||
)
|
||||
default boolean includeBlogPosts()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "includeTweets",
|
||||
name = "Include Tweets",
|
||||
description = "Configures whether tweets are displayed",
|
||||
position = 1
|
||||
)
|
||||
default boolean includeTweets()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "includeOsrsNews",
|
||||
name = "Include OSRS News",
|
||||
description = "Configures whether OSRS news are displayed",
|
||||
position = 2
|
||||
)
|
||||
default boolean includeOsrsNews()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||
* 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.feed;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Supplier;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.FontManager;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.client.util.LinkBrowser;
|
||||
import net.runelite.http.api.feed.FeedItem;
|
||||
import net.runelite.http.api.feed.FeedItemType;
|
||||
import net.runelite.http.api.feed.FeedResult;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
class FeedPanel extends PluginPanel
|
||||
{
|
||||
private static final ImageIcon RUNELITE_ICON;
|
||||
private static final ImageIcon OSRS_ICON;
|
||||
|
||||
private static final Color TWEET_BACKGROUND = new Color(15, 15, 15);
|
||||
private static final Color OSRS_NEWS_BACKGROUND = new Color(36, 30, 19);
|
||||
private static final Color BLOG_POST_BACKGROUND = new Color(11, 30, 41);
|
||||
|
||||
private static final int MAX_CONTENT_LINES = 3;
|
||||
private static final int CONTENT_WIDTH = 148;
|
||||
private static final int TIME_WIDTH = 20;
|
||||
|
||||
private static final Comparator<FeedItem> FEED_ITEM_COMPARATOR = (o1, o2) ->
|
||||
{
|
||||
if (o1.getType() != o2.getType())
|
||||
{
|
||||
if (o1.getType() == FeedItemType.BLOG_POST)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (o2.getType() == FeedItemType.BLOG_POST)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return -Long.compare(o1.getTimestamp(), o2.getTimestamp());
|
||||
};
|
||||
|
||||
static
|
||||
{
|
||||
RUNELITE_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(FeedPanel.class, "runelite.png"));
|
||||
OSRS_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(FeedPanel.class, "osrs.png"));
|
||||
}
|
||||
|
||||
private final FeedConfig config;
|
||||
private final Supplier<FeedResult> feedSupplier;
|
||||
private final OkHttpClient okHttpClient;
|
||||
|
||||
@Inject
|
||||
FeedPanel(FeedConfig config, Supplier<FeedResult> feedSupplier, OkHttpClient okHttpClient)
|
||||
{
|
||||
super(true);
|
||||
this.config = config;
|
||||
this.feedSupplier = feedSupplier;
|
||||
this.okHttpClient = okHttpClient;
|
||||
|
||||
setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
setLayout(new GridLayout(0, 1, 0, 4));
|
||||
}
|
||||
|
||||
void rebuildFeed()
|
||||
{
|
||||
FeedResult feed = feedSupplier.get();
|
||||
|
||||
if (feed == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
removeAll();
|
||||
|
||||
feed.getItems()
|
||||
.stream()
|
||||
.filter(f -> f.getType() != FeedItemType.BLOG_POST || config.includeBlogPosts())
|
||||
.filter(f -> f.getType() != FeedItemType.TWEET || config.includeTweets())
|
||||
.filter(f -> f.getType() != FeedItemType.OSRS_NEWS || config.includeOsrsNews())
|
||||
.sorted(FEED_ITEM_COMPARATOR)
|
||||
.forEach(this::addItemToPanel);
|
||||
});
|
||||
}
|
||||
|
||||
private void addItemToPanel(FeedItem item)
|
||||
{
|
||||
JPanel avatarAndRight = new JPanel(new BorderLayout());
|
||||
avatarAndRight.setPreferredSize(new Dimension(0, 56));
|
||||
|
||||
JLabel avatar = new JLabel();
|
||||
// width = 48+4 to compensate for the border
|
||||
avatar.setPreferredSize(new Dimension(52, 48));
|
||||
avatar.setBorder(new EmptyBorder(0, 4, 0, 0));
|
||||
|
||||
switch (item.getType())
|
||||
{
|
||||
case TWEET:
|
||||
try
|
||||
{
|
||||
Request request = new Request.Builder()
|
||||
.url(item.getAvatar())
|
||||
.build();
|
||||
|
||||
okHttpClient.newCall(request).enqueue(new Callback()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e)
|
||||
{
|
||||
log.warn(null, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException
|
||||
{
|
||||
try (ResponseBody responseBody = response.body())
|
||||
{
|
||||
if (!response.isSuccessful())
|
||||
{
|
||||
log.warn("Failed to download image " + item.getAvatar());
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedImage icon;
|
||||
synchronized (ImageIO.class)
|
||||
{
|
||||
icon = ImageIO.read(responseBody.byteStream());
|
||||
}
|
||||
avatar.setIcon(new ImageIcon(icon));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e)
|
||||
{
|
||||
log.warn(null, e);
|
||||
}
|
||||
avatarAndRight.setBackground(TWEET_BACKGROUND);
|
||||
break;
|
||||
case OSRS_NEWS:
|
||||
if (OSRS_ICON != null)
|
||||
{
|
||||
avatar.setIcon(OSRS_ICON);
|
||||
}
|
||||
avatarAndRight.setBackground(OSRS_NEWS_BACKGROUND);
|
||||
break;
|
||||
default:
|
||||
if (RUNELITE_ICON != null)
|
||||
{
|
||||
avatar.setIcon(RUNELITE_ICON);
|
||||
}
|
||||
avatarAndRight.setBackground(BLOG_POST_BACKGROUND);
|
||||
break;
|
||||
}
|
||||
|
||||
JPanel upAndContent = new JPanel();
|
||||
upAndContent.setLayout(new BoxLayout(upAndContent, BoxLayout.Y_AXIS));
|
||||
upAndContent.setBorder(new EmptyBorder(4, 8, 4, 4));
|
||||
upAndContent.setBackground(null);
|
||||
|
||||
JPanel titleAndTime = new JPanel();
|
||||
titleAndTime.setLayout(new BorderLayout());
|
||||
titleAndTime.setBackground(null);
|
||||
|
||||
Color darkerForeground = UIManager.getColor("Label.foreground").darker();
|
||||
|
||||
JLabel titleLabel = new JLabel(item.getTitle());
|
||||
titleLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||
titleLabel.setBackground(null);
|
||||
titleLabel.setForeground(darkerForeground);
|
||||
titleLabel.setPreferredSize(new Dimension(CONTENT_WIDTH - TIME_WIDTH, 0));
|
||||
|
||||
Duration duration = Duration.between(Instant.ofEpochMilli(item.getTimestamp()), Instant.now());
|
||||
JLabel timeLabel = new JLabel(durationToString(duration));
|
||||
timeLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||
timeLabel.setForeground(darkerForeground);
|
||||
|
||||
titleAndTime.add(titleLabel, BorderLayout.WEST);
|
||||
titleAndTime.add(timeLabel, BorderLayout.EAST);
|
||||
|
||||
JPanel content = new JPanel(new BorderLayout());
|
||||
content.setBackground(null);
|
||||
|
||||
JLabel contentLabel = new JLabel(lineBreakText(item.getContent(), FontManager.getRunescapeSmallFont()));
|
||||
contentLabel.setBorder(new EmptyBorder(2, 0, 0, 0));
|
||||
contentLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||
contentLabel.setForeground(darkerForeground);
|
||||
|
||||
content.add(contentLabel, BorderLayout.CENTER);
|
||||
|
||||
upAndContent.add(titleAndTime);
|
||||
upAndContent.add(content);
|
||||
upAndContent.add(new Box.Filler(new Dimension(0, 0),
|
||||
new Dimension(0, Short.MAX_VALUE),
|
||||
new Dimension(0, Short.MAX_VALUE)));
|
||||
|
||||
avatarAndRight.add(avatar, BorderLayout.WEST);
|
||||
avatarAndRight.add(upAndContent, BorderLayout.CENTER);
|
||||
|
||||
Color backgroundColor = avatarAndRight.getBackground();
|
||||
Color hoverColor = backgroundColor.brighter().brighter();
|
||||
Color pressedColor = hoverColor.brighter();
|
||||
|
||||
avatarAndRight.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
avatarAndRight.setBackground(hoverColor);
|
||||
avatarAndRight.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e)
|
||||
{
|
||||
avatarAndRight.setBackground(backgroundColor);
|
||||
avatarAndRight.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e)
|
||||
{
|
||||
avatarAndRight.setBackground(pressedColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e)
|
||||
{
|
||||
avatarAndRight.setBackground(hoverColor);
|
||||
LinkBrowser.browse(item.getUrl());
|
||||
}
|
||||
});
|
||||
|
||||
add(avatarAndRight);
|
||||
}
|
||||
|
||||
private String durationToString(Duration duration)
|
||||
{
|
||||
if (duration.getSeconds() >= 60 * 60 * 24)
|
||||
{
|
||||
return (int) (duration.getSeconds() / (60 * 60 * 24)) + "d";
|
||||
}
|
||||
else if (duration.getSeconds() >= 60 * 60)
|
||||
{
|
||||
return (int) (duration.getSeconds() / (60 * 60)) + "h";
|
||||
}
|
||||
return (int) (duration.getSeconds() / 60) + "m";
|
||||
}
|
||||
|
||||
private String lineBreakText(String text, Font font)
|
||||
{
|
||||
StringBuilder newText = new StringBuilder("<html>");
|
||||
|
||||
FontRenderContext fontRenderContext = new FontRenderContext(font.getTransform(),
|
||||
true, true);
|
||||
|
||||
int lines = 0;
|
||||
int pos = 0;
|
||||
String[] words = text.split(" ");
|
||||
String line = "";
|
||||
|
||||
while (lines < MAX_CONTENT_LINES && pos < words.length)
|
||||
{
|
||||
String newLine = pos > 0 ? line + " " + words[pos] : words[pos];
|
||||
double width = font.getStringBounds(newLine, fontRenderContext).getWidth();
|
||||
|
||||
if (width >= CONTENT_WIDTH)
|
||||
{
|
||||
newText.append(line);
|
||||
newText.append("<br>");
|
||||
line = "";
|
||||
lines++;
|
||||
}
|
||||
else
|
||||
{
|
||||
line = newLine;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
newText.append(line);
|
||||
newText.append("</html>");
|
||||
|
||||
return newText.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||
* 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.feed;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import javax.inject.Inject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.task.Schedule;
|
||||
import net.runelite.client.ui.ClientToolbar;
|
||||
import net.runelite.client.ui.NavigationButton;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.http.api.feed.FeedClient;
|
||||
import net.runelite.http.api.feed.FeedResult;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "News Feed",
|
||||
description = "Show the latest RuneLite blog posts, OSRS news, and JMod Twitter posts",
|
||||
tags = {"external", "integration", "panel", "twitter"},
|
||||
loadWhenOutdated = true
|
||||
)
|
||||
@Slf4j
|
||||
public class FeedPlugin extends Plugin
|
||||
{
|
||||
@Inject
|
||||
private ClientToolbar clientToolbar;
|
||||
|
||||
@Inject
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
@Inject
|
||||
private FeedClient feedClient;
|
||||
|
||||
private FeedPanel feedPanel;
|
||||
private NavigationButton navButton;
|
||||
|
||||
private final Supplier<FeedResult> feedSupplier = Suppliers.memoizeWithExpiration(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return feedClient.lookupFeed();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
log.warn(null, e);
|
||||
}
|
||||
return null;
|
||||
}, 10, TimeUnit.MINUTES);
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder)
|
||||
{
|
||||
// CHECKSTYLE:OFF
|
||||
binder.bind(new TypeLiteral<Supplier<FeedResult>>(){})
|
||||
.toInstance(feedSupplier);
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
feedPanel = injector.getInstance(FeedPanel.class);
|
||||
|
||||
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "icon.png");
|
||||
|
||||
navButton = NavigationButton.builder()
|
||||
.tooltip("News Feed")
|
||||
.icon(icon)
|
||||
.priority(8)
|
||||
.panel(feedPanel)
|
||||
.build();
|
||||
|
||||
clientToolbar.addNavigation(navButton);
|
||||
executorService.submit(this::updateFeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
clientToolbar.removeNavigation(navButton);
|
||||
}
|
||||
|
||||
private void updateFeed()
|
||||
{
|
||||
feedPanel.rebuildFeed();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged event)
|
||||
{
|
||||
if (event.getGroup().equals("feed"))
|
||||
{
|
||||
executorService.submit(this::updateFeed);
|
||||
}
|
||||
}
|
||||
|
||||
@Schedule(
|
||||
period = 10,
|
||||
unit = ChronoUnit.MINUTES,
|
||||
asynchronous = true
|
||||
)
|
||||
public void updateFeedTask()
|
||||
{
|
||||
updateFeed();
|
||||
}
|
||||
|
||||
@Provides
|
||||
FeedConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(FeedConfig.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
FeedClient provideFeedClient(OkHttpClient okHttpClient)
|
||||
{
|
||||
return new FeedClient(okHttpClient);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Levi <me@levischuck.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.fps;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
import net.runelite.client.config.Range;
|
||||
|
||||
@ConfigGroup(FpsPlugin.CONFIG_GROUP_KEY)
|
||||
public interface FpsConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
keyName = "limitFps",
|
||||
name = "Limit Global FPS",
|
||||
description = "Global FPS limit in effect regardless of<br>" +
|
||||
"whether window is in focus or not",
|
||||
position = 1
|
||||
)
|
||||
default boolean limitFps()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Range(
|
||||
min = 1,
|
||||
max = 50
|
||||
)
|
||||
@ConfigItem(
|
||||
keyName = "maxFps",
|
||||
name = "Global FPS target",
|
||||
description = "Desired max global frames per second",
|
||||
position = 2
|
||||
)
|
||||
default int maxFps()
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "limitFpsUnfocused",
|
||||
name = "Limit FPS unfocused",
|
||||
description = "FPS limit while window is out of focus",
|
||||
position = 3
|
||||
)
|
||||
default boolean limitFpsUnfocused()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Range(
|
||||
min = 1,
|
||||
max = 50
|
||||
)
|
||||
@ConfigItem(
|
||||
keyName = "maxFpsUnfocused",
|
||||
name = "Unfocused FPS target",
|
||||
description = "Desired max frames per second for unfocused",
|
||||
position = 4
|
||||
)
|
||||
default int maxFpsUnfocused()
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "drawFps",
|
||||
name = "Draw FPS indicator",
|
||||
description = "Show a number in the corner for the current FPS",
|
||||
position = 5
|
||||
)
|
||||
default boolean drawFps()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Levi <me@levischuck.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.fps;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.events.FocusChanged;
|
||||
|
||||
/**
|
||||
* FPS Draw Listener runs after the canvas has been painted with the buffered image (unused in this plugin)
|
||||
* It specifically is designed only to pause when RS decides to paint the canvas, after the canvas has been painted.
|
||||
* The RS client operates at 50 cycles happen per second in the RS Client with higher priority than draws per second.
|
||||
* For high powered computers, drawing is limited by client cycles, so 50 is the max FPS observed.
|
||||
* After running a game cycle and a draw operation, the RS client burns the rest of the time with a nano timer.
|
||||
* For low powered computers, the RS client is often throttled by the hardware or OS and draws at 25-30 fps.
|
||||
* The nano timer is not used in this scenario.
|
||||
* Instead to catch up the RS client runs several cycles before drawing, thus maintaining 50 cycles / second.
|
||||
* <p>
|
||||
* Enforcing FPS in the draw code does not impact the client engine's ability to run including its audio,
|
||||
* even when forced to 1 FPS with this plugin.
|
||||
*/
|
||||
public class FpsDrawListener implements Runnable
|
||||
{
|
||||
private static final int SAMPLE_SIZE = 4;
|
||||
|
||||
private final FpsConfig config;
|
||||
|
||||
private long targetDelay = 0;
|
||||
|
||||
// Often changing values
|
||||
private boolean isFocused = true;
|
||||
|
||||
// Working set
|
||||
private long lastMillis = 0;
|
||||
private long[] lastDelays = new long[SAMPLE_SIZE];
|
||||
private int lastDelayIndex = 0;
|
||||
private long sleepDelay = 0;
|
||||
|
||||
@Inject
|
||||
private FpsDrawListener(FpsConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
reloadConfig();
|
||||
}
|
||||
|
||||
void reloadConfig()
|
||||
{
|
||||
lastMillis = System.currentTimeMillis();
|
||||
|
||||
int fps = config.limitFpsUnfocused() && !isFocused
|
||||
? config.maxFpsUnfocused()
|
||||
: config.maxFps();
|
||||
|
||||
targetDelay = 1000 / Math.max(1, fps);
|
||||
|
||||
sleepDelay = targetDelay;
|
||||
|
||||
for (int i = 0; i < SAMPLE_SIZE; i++)
|
||||
{
|
||||
lastDelays[i] = targetDelay;
|
||||
}
|
||||
}
|
||||
|
||||
void onFocusChanged(FocusChanged event)
|
||||
{
|
||||
this.isFocused = event.isFocused();
|
||||
reloadConfig(); // load new delay
|
||||
}
|
||||
|
||||
private boolean isEnforced()
|
||||
{
|
||||
return config.limitFps()
|
||||
|| (config.limitFpsUnfocused() && !isFocused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (!isEnforced())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't trust client.getFPS to get frame-perfect FPS knowledge
|
||||
// If we do try to use client.getFPS, we will end up oscillating
|
||||
// So we rely on currentTimeMillis which is occasionally cached by the JVM unlike nanotime
|
||||
// Its caching will not cause oscillation as it is granular enough for our uses here
|
||||
final long before = lastMillis;
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
lastMillis = now;
|
||||
lastDelayIndex = (lastDelayIndex + 1) % SAMPLE_SIZE;
|
||||
lastDelays[lastDelayIndex] = now - before;
|
||||
|
||||
// We take a sampling approach because sometimes the game client seems to repaint
|
||||
// after only running 1 game cycle, and then runs repaint again after running 30 cycles
|
||||
long averageDelay = 0;
|
||||
for (int i = 0; i < SAMPLE_SIZE; i++)
|
||||
{
|
||||
averageDelay += lastDelays[i];
|
||||
}
|
||||
averageDelay /= lastDelays.length;
|
||||
|
||||
// Sleep delay is a moving target due to the nature of how and when the engine
|
||||
// decides to run cycles.
|
||||
// This will also keep us safe from time spent in plugins conditionally
|
||||
// as some plugins and overlays are only appropriate in some game areas
|
||||
if (averageDelay > targetDelay)
|
||||
{
|
||||
sleepDelay--;
|
||||
}
|
||||
else if (averageDelay < targetDelay)
|
||||
{
|
||||
sleepDelay++;
|
||||
}
|
||||
|
||||
if (sleepDelay > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(sleepDelay);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
// Can happen on shutdown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Levi <me@levischuck.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.fps;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Point;
|
||||
import net.runelite.api.events.FocusChanged;
|
||||
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.OverlayLayer;
|
||||
import net.runelite.client.ui.overlay.OverlayPosition;
|
||||
import net.runelite.client.ui.overlay.OverlayPriority;
|
||||
import net.runelite.client.ui.overlay.OverlayUtil;
|
||||
|
||||
/**
|
||||
* The built in FPS overlay has a few problems that this one does not have, most of all: it is distracting.
|
||||
* 1. The built in one also shows memory, which constantly fluctuates and garbage collects.
|
||||
* It is useful for devs profiling memory.
|
||||
* 2. The built in one shifts around constantly because it is not monospace.
|
||||
* This locks "FPS:" into one position (the far top right corner of the canvas),
|
||||
* along with a locked position for the FPS value.
|
||||
*/
|
||||
public class FpsOverlay extends Overlay
|
||||
{
|
||||
private static final int Y_OFFSET = 1;
|
||||
private static final int X_OFFSET = 1;
|
||||
private static final String FPS_STRING = " FPS";
|
||||
|
||||
// Local dependencies
|
||||
private final FpsConfig config;
|
||||
private final Client client;
|
||||
|
||||
// Often changing values
|
||||
private boolean isFocused = true;
|
||||
|
||||
@Inject
|
||||
private FpsOverlay(FpsConfig config, Client client)
|
||||
{
|
||||
this.config = config;
|
||||
this.client = client;
|
||||
setLayer(OverlayLayer.ABOVE_WIDGETS);
|
||||
setPriority(OverlayPriority.HIGH);
|
||||
setPosition(OverlayPosition.DYNAMIC);
|
||||
}
|
||||
|
||||
void onFocusChanged(FocusChanged event)
|
||||
{
|
||||
isFocused = event.isFocused();
|
||||
}
|
||||
|
||||
private boolean isEnforced()
|
||||
{
|
||||
return config.limitFps()
|
||||
|| (config.limitFpsUnfocused() && !isFocused);
|
||||
}
|
||||
|
||||
private Color getFpsValueColor()
|
||||
{
|
||||
return isEnforced() ? Color.red : Color.yellow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
if (!config.drawFps())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// On resizable bottom line mode the logout button is at the top right, so offset the overlay
|
||||
// to account for it
|
||||
Widget logoutButton = client.getWidget(WidgetInfo.RESIZABLE_MINIMAP_LOGOUT_BUTTON);
|
||||
int xOffset = X_OFFSET;
|
||||
if (logoutButton != null && !logoutButton.isHidden())
|
||||
{
|
||||
xOffset += logoutButton.getWidth();
|
||||
}
|
||||
|
||||
final String text = client.getFPS() + FPS_STRING;
|
||||
final int textWidth = graphics.getFontMetrics().stringWidth(text);
|
||||
final int textHeight = graphics.getFontMetrics().getAscent() - graphics.getFontMetrics().getDescent();
|
||||
|
||||
final int width = (int) client.getRealDimensions().getWidth();
|
||||
final Point point = new Point(width - textWidth - xOffset, textHeight + Y_OFFSET);
|
||||
OverlayUtil.renderTextLocation(graphics, point, text, getFpsValueColor());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Levi <me@levischuck.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.fps;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provides;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.api.events.FocusChanged;
|
||||
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.DrawManager;
|
||||
import net.runelite.client.ui.overlay.OverlayManager;
|
||||
|
||||
/**
|
||||
* FPS Control has two primary areas, this plugin class just keeps those areas up to date and handles setup / teardown.
|
||||
*
|
||||
* <p>Overlay paints the current FPS, the color depends on whether or not FPS is being enforced.
|
||||
* The overlay is lightweight and is merely and indicator.
|
||||
*
|
||||
* <p>Draw Listener, sleeps a calculated amount after each canvas paint operation.
|
||||
* This is the heart of the plugin, the amount of sleep taken is regularly adjusted to account varying
|
||||
* game and system load, it usually finds the sweet spot in about two seconds.
|
||||
*/
|
||||
@PluginDescriptor(
|
||||
name = "FPS Control",
|
||||
description = "Show current FPS and/or set an FPS limit",
|
||||
tags = {"frames", "framerate", "limit", "overlay"},
|
||||
enabledByDefault = false
|
||||
)
|
||||
public class FpsPlugin extends Plugin
|
||||
{
|
||||
static final String CONFIG_GROUP_KEY = "fpscontrol";
|
||||
|
||||
@Inject
|
||||
private OverlayManager overlayManager;
|
||||
|
||||
@Inject
|
||||
private FpsOverlay overlay;
|
||||
|
||||
@Inject
|
||||
private FpsDrawListener drawListener;
|
||||
|
||||
@Inject
|
||||
private DrawManager drawManager;
|
||||
|
||||
@Provides
|
||||
FpsConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(FpsConfig.class);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged event)
|
||||
{
|
||||
if (event.getGroup().equals(CONFIG_GROUP_KEY))
|
||||
{
|
||||
drawListener.reloadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFocusChanged(FocusChanged event)
|
||||
{
|
||||
drawListener.onFocusChanged(event);
|
||||
overlay.onFocusChanged(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
overlayManager.add(overlay);
|
||||
drawManager.registerEveryFrameListener(drawListener);
|
||||
drawListener.reloadConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
overlayManager.remove(overlay);
|
||||
drawManager.unregisterEveryFrameListener(drawListener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Connor <contact@connor-parks.email>
|
||||
* 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.friendlist;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Friend;
|
||||
import net.runelite.api.Ignore;
|
||||
import net.runelite.api.NameableContainer;
|
||||
import net.runelite.api.ScriptID;
|
||||
import net.runelite.api.VarPlayer;
|
||||
import net.runelite.api.events.ScriptPostFired;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Friend List",
|
||||
description = "Add extra information to the friend and ignore lists"
|
||||
)
|
||||
public class FriendListPlugin extends Plugin
|
||||
{
|
||||
private static final int MAX_FRIENDS_P2P = 400;
|
||||
private static final int MAX_FRIENDS_F2P = 200;
|
||||
|
||||
private static final int MAX_IGNORES_P2P = 400;
|
||||
private static final int MAX_IGNORES_F2P = 100;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
final int world = client.getWorld();
|
||||
setFriendsListTitle("Friends List - World " + world);
|
||||
setIgnoreListTitle("Ignore List - World " + world);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptPostFired(ScriptPostFired event)
|
||||
{
|
||||
if (event.getScriptId() == ScriptID.FRIENDS_UPDATE)
|
||||
{
|
||||
final int world = client.getWorld();
|
||||
final boolean isMember = client.getVar(VarPlayer.MEMBERSHIP_DAYS) > 0;
|
||||
final NameableContainer<Friend> friendContainer = client.getFriendContainer();
|
||||
final int friendCount = friendContainer.getCount();
|
||||
if (friendCount >= 0)
|
||||
{
|
||||
final int limit = isMember ? MAX_FRIENDS_P2P : MAX_FRIENDS_F2P;
|
||||
|
||||
final String title = "Friends - W" +
|
||||
world +
|
||||
" (" +
|
||||
friendCount +
|
||||
"/" +
|
||||
limit +
|
||||
")";
|
||||
|
||||
setFriendsListTitle(title);
|
||||
}
|
||||
}
|
||||
else if (event.getScriptId() == ScriptID.IGNORE_UPDATE)
|
||||
{
|
||||
final int world = client.getWorld();
|
||||
final boolean isMember = client.getVar(VarPlayer.MEMBERSHIP_DAYS) > 0;
|
||||
final NameableContainer<Ignore> ignoreContainer = client.getIgnoreContainer();
|
||||
final int ignoreCount = ignoreContainer.getCount();
|
||||
if (ignoreCount >= 0)
|
||||
{
|
||||
final int limit = isMember ? MAX_IGNORES_P2P : MAX_IGNORES_F2P;
|
||||
|
||||
final String title = "Ignores - W" +
|
||||
world +
|
||||
" (" +
|
||||
ignoreCount +
|
||||
"/" +
|
||||
limit +
|
||||
")";
|
||||
|
||||
setIgnoreListTitle(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setFriendsListTitle(final String title)
|
||||
{
|
||||
Widget friendListTitleWidget = client.getWidget(WidgetInfo.FRIEND_CHAT_TITLE);
|
||||
if (friendListTitleWidget != null)
|
||||
{
|
||||
friendListTitleWidget.setText(title);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIgnoreListTitle(final String title)
|
||||
{
|
||||
Widget ignoreTitleWidget = client.getWidget(WidgetInfo.IGNORE_TITLE);
|
||||
if (ignoreTitleWidget != null)
|
||||
{
|
||||
ignoreTitleWidget.setText(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2018, trimbe <github.com/trimbe>
|
||||
* 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.friendschat;
|
||||
|
||||
enum ActivityType
|
||||
{
|
||||
JOINED,
|
||||
LEFT
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Ron <https://github.com/raiyni>
|
||||
* 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.friendschat;
|
||||
|
||||
import java.awt.Color;
|
||||
import net.runelite.api.FriendsChatRank;
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup("clanchat") // group name from the old plugin
|
||||
public interface FriendsChatConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
keyName = "clanChatIcons",
|
||||
name = "Chat Icons",
|
||||
description = "Show rank icons next to friends chat members.",
|
||||
position = 1
|
||||
)
|
||||
default boolean chatIcons()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "recentChats",
|
||||
name = "Recent Chats",
|
||||
description = "Show recent friends chats.",
|
||||
position = 2
|
||||
)
|
||||
default boolean recentChats()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "clanCounter",
|
||||
name = "Members Counter",
|
||||
description = "Show the amount of friends chat members near you.",
|
||||
position = 3
|
||||
)
|
||||
default boolean showCounter()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "chatsData",
|
||||
name = "",
|
||||
description = "",
|
||||
hidden = true
|
||||
)
|
||||
default String chatsData()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "chatsData",
|
||||
name = "",
|
||||
description = ""
|
||||
)
|
||||
void chatsData(String str);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showJoinLeave",
|
||||
name = "Show Join/Leave",
|
||||
description = "Adds a temporary message notifying when a member joins or leaves.",
|
||||
position = 4
|
||||
)
|
||||
default boolean showJoinLeave()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "joinLeaveRank",
|
||||
name = "Join/Leave rank",
|
||||
description = "Only show join/leave messages for members at or above this rank.",
|
||||
position = 5
|
||||
)
|
||||
default FriendsChatRank joinLeaveRank()
|
||||
{
|
||||
return FriendsChatRank.UNRANKED;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "joinLeaveTimeout",
|
||||
name = "Join/Leave timeout",
|
||||
description = "Set the timeout duration of join/leave messages. A value of 0 will make the messages permanent.",
|
||||
position = 6
|
||||
)
|
||||
default int joinLeaveTimeout()
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "privateMessageIcons",
|
||||
name = "Private Message Icons",
|
||||
description = "Add rank icons to private messages received from members.",
|
||||
position = 7
|
||||
)
|
||||
default boolean privateMessageIcons()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "publicChatIcons",
|
||||
name = "Public Chat Icons",
|
||||
description = "Add rank icons to public chat messages from members.",
|
||||
position = 8
|
||||
)
|
||||
default boolean publicChatIcons()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "clanTabChat",
|
||||
name = "Tab Chat",
|
||||
description = "Message friends chat without appending '/' when the friends chat tab is selected.",
|
||||
position = 9
|
||||
)
|
||||
default boolean friendsChatTabChat()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "confirmKicks",
|
||||
name = "Confirm Kicks",
|
||||
description = "Shows a chat prompt to confirm kicks",
|
||||
position = 10
|
||||
)
|
||||
default boolean confirmKicks()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showIgnores",
|
||||
name = "Recolor ignored players",
|
||||
description = "Recolor members who are on your ignore list",
|
||||
position = 11
|
||||
)
|
||||
default boolean showIgnores()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showIgnoresColor",
|
||||
name = "Ignored color",
|
||||
description = "Allows you to change the color of the ignored players in your friends chat",
|
||||
position = 12
|
||||
)
|
||||
default Color showIgnoresColor()
|
||||
{
|
||||
return Color.RED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,722 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Devin French <https://github.com/devinfrench>
|
||||
* Copyright (c) 2019, Adam <Adam@sigterm.info>
|
||||
* Copyright (c) 2018, trimbe <github.com/trimbe>
|
||||
* 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.friendschat;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.Runnables;
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.ChatLineBuffer;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.FriendsChatManager;
|
||||
import net.runelite.api.FriendsChatMember;
|
||||
import net.runelite.api.FriendsChatRank;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.Ignore;
|
||||
import net.runelite.api.MessageNode;
|
||||
import net.runelite.api.NameableContainer;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.ScriptID;
|
||||
import net.runelite.api.SpriteID;
|
||||
import net.runelite.api.VarClientStr;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.FriendsChatChanged;
|
||||
import net.runelite.api.events.FriendsChatMemberJoined;
|
||||
import net.runelite.api.events.FriendsChatMemberLeft;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.PlayerDespawned;
|
||||
import net.runelite.api.events.PlayerSpawned;
|
||||
import net.runelite.api.events.ScriptCallbackEvent;
|
||||
import net.runelite.api.events.ScriptPostFired;
|
||||
import net.runelite.api.events.VarClientStrChanged;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import net.runelite.api.widgets.WidgetType;
|
||||
import net.runelite.client.callback.ClientThread;
|
||||
import net.runelite.client.chat.ChatMessageBuilder;
|
||||
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.game.SpriteManager;
|
||||
import net.runelite.client.game.chatbox.ChatboxPanelManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import static net.runelite.client.ui.JagexColors.CHAT_FC_NAME_OPAQUE_BACKGROUND;
|
||||
import static net.runelite.client.ui.JagexColors.CHAT_FC_NAME_TRANSPARENT_BACKGROUND;
|
||||
import static net.runelite.client.ui.JagexColors.CHAT_FC_TEXT_OPAQUE_BACKGROUND;
|
||||
import static net.runelite.client.ui.JagexColors.CHAT_FC_TEXT_TRANSPARENT_BACKGROUND;
|
||||
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
|
||||
import net.runelite.client.util.Text;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Friends Chat",
|
||||
description = "Add rank icons to users talking in friends chat",
|
||||
tags = {"icons", "rank", "recent", "clan"}
|
||||
)
|
||||
public class FriendsChatPlugin extends Plugin
|
||||
{
|
||||
private static final int MAX_CHATS = 10;
|
||||
private static final String TITLE = "FC";
|
||||
private static final String RECENT_TITLE = "Recent FCs";
|
||||
private static final int MESSAGE_DELAY = 10;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private FriendChatManager friendChatManager;
|
||||
|
||||
@Inject
|
||||
private FriendsChatConfig config;
|
||||
|
||||
@Inject
|
||||
private InfoBoxManager infoBoxManager;
|
||||
|
||||
@Inject
|
||||
private SpriteManager spriteManager;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
@Inject
|
||||
private ChatboxPanelManager chatboxPanelManager;
|
||||
|
||||
private List<String> chats = new ArrayList<>();
|
||||
private final List<Player> members = new ArrayList<>();
|
||||
private MembersIndicator membersIndicator;
|
||||
/**
|
||||
* queue of temporary messages added to the client
|
||||
*/
|
||||
private final Deque<MemberJoinMessage> joinMessages = new ArrayDeque<>();
|
||||
private final Map<String, MemberActivity> activityBuffer = new HashMap<>();
|
||||
private int joinedTick;
|
||||
|
||||
private boolean kickConfirmed = false;
|
||||
|
||||
@Provides
|
||||
FriendsChatConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(FriendsChatConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startUp()
|
||||
{
|
||||
chats = new ArrayList<>(Text.fromCSV(config.chatsData()));
|
||||
|
||||
if (config.showIgnores())
|
||||
{
|
||||
clientThread.invoke(() -> colorIgnoredPlayers(config.showIgnoresColor()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutDown()
|
||||
{
|
||||
clientThread.invoke(() -> colorIgnoredPlayers(Color.WHITE));
|
||||
members.clear();
|
||||
resetCounter();
|
||||
resetChats();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged configChanged)
|
||||
{
|
||||
if (configChanged.getGroup().equals("clanchat"))
|
||||
{
|
||||
if (!config.recentChats())
|
||||
{
|
||||
resetChats();
|
||||
}
|
||||
|
||||
if (config.showCounter())
|
||||
{
|
||||
clientThread.invoke(this::addCounter);
|
||||
}
|
||||
else
|
||||
{
|
||||
resetCounter();
|
||||
}
|
||||
|
||||
Color ignoreColor = config.showIgnores() ? config.showIgnoresColor() : Color.WHITE;
|
||||
clientThread.invoke(() -> colorIgnoredPlayers(ignoreColor));
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFriendsChatMemberJoined(FriendsChatMemberJoined event)
|
||||
{
|
||||
final FriendsChatMember member = event.getMember();
|
||||
|
||||
if (member.getWorld() == client.getWorld())
|
||||
{
|
||||
final Player local = client.getLocalPlayer();
|
||||
final String memberName = Text.toJagexName(member.getName());
|
||||
|
||||
for (final Player player : client.getPlayers())
|
||||
{
|
||||
if (player != null && player != local && memberName.equals(Text.toJagexName(player.getName())))
|
||||
{
|
||||
members.add(player);
|
||||
addCounter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// members getting initialized isn't relevant
|
||||
if (joinedTick == client.getTickCount())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.showJoinLeave() ||
|
||||
member.getRank().getValue() < config.joinLeaveRank().getValue())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// attempt to filter out world hopping joins
|
||||
if (!activityBuffer.containsKey(member.getName()))
|
||||
{
|
||||
MemberActivity joinActivity = new MemberActivity(ActivityType.JOINED,
|
||||
member, client.getTickCount());
|
||||
activityBuffer.put(member.getName(), joinActivity);
|
||||
}
|
||||
else
|
||||
{
|
||||
activityBuffer.remove(member.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFriendsChatMemberLeft(FriendsChatMemberLeft event)
|
||||
{
|
||||
final FriendsChatMember member = event.getMember();
|
||||
|
||||
if (member.getWorld() == client.getWorld())
|
||||
{
|
||||
final String memberName = Text.toJagexName(member.getName());
|
||||
final Iterator<Player> each = members.iterator();
|
||||
|
||||
while (each.hasNext())
|
||||
{
|
||||
if (memberName.equals(Text.toJagexName(each.next().getName())))
|
||||
{
|
||||
each.remove();
|
||||
|
||||
if (members.isEmpty())
|
||||
{
|
||||
resetCounter();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.showJoinLeave() ||
|
||||
member.getRank().getValue() < config.joinLeaveRank().getValue())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!activityBuffer.containsKey(member.getName()))
|
||||
{
|
||||
MemberActivity leaveActivity = new MemberActivity(ActivityType.LEFT,
|
||||
member, client.getTickCount());
|
||||
activityBuffer.put(member.getName(), leaveActivity);
|
||||
}
|
||||
else
|
||||
{
|
||||
activityBuffer.remove(member.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameTick(GameTick gameTick)
|
||||
{
|
||||
if (client.getGameState() != GameState.LOGGED_IN)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Widget chatTitleWidget = client.getWidget(WidgetInfo.FRIENDS_CHAT_TITLE);
|
||||
if (chatTitleWidget != null)
|
||||
{
|
||||
Widget chatList = client.getWidget(WidgetInfo.FRIENDS_CHAT_LIST);
|
||||
Widget owner = client.getWidget(WidgetInfo.FRIENDS_CHAT_OWNER);
|
||||
FriendsChatManager friendsChatManager = client.getFriendsChatManager();
|
||||
if (friendsChatManager != null && friendsChatManager.getCount() > 0)
|
||||
{
|
||||
chatTitleWidget.setText(TITLE + " (" + friendsChatManager.getCount() + "/100)");
|
||||
}
|
||||
else if (config.recentChats() && chatList.getChildren() == null && !Strings.isNullOrEmpty(owner.getText()))
|
||||
{
|
||||
chatTitleWidget.setText(RECENT_TITLE);
|
||||
|
||||
loadFriendsChats();
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.showJoinLeave())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timeoutMessages();
|
||||
|
||||
addActivityMessages();
|
||||
}
|
||||
|
||||
private void timeoutMessages()
|
||||
{
|
||||
if (joinMessages.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final int joinLeaveTimeout = config.joinLeaveTimeout();
|
||||
if (joinLeaveTimeout == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
boolean removed = false;
|
||||
|
||||
for (Iterator<MemberJoinMessage> it = joinMessages.iterator(); it.hasNext(); )
|
||||
{
|
||||
MemberJoinMessage joinMessage = it.next();
|
||||
MessageNode messageNode = joinMessage.getMessageNode();
|
||||
final int createdTick = joinMessage.getTick();
|
||||
|
||||
if (client.getTickCount() > createdTick + joinLeaveTimeout)
|
||||
{
|
||||
it.remove();
|
||||
|
||||
// If this message has been reused since, it will get a different id
|
||||
if (joinMessage.getGetMessageId() == messageNode.getId())
|
||||
{
|
||||
ChatLineBuffer ccInfoBuffer = client.getChatLineMap().get(ChatMessageType.FRIENDSCHATNOTIFICATION.getType());
|
||||
if (ccInfoBuffer != null)
|
||||
{
|
||||
ccInfoBuffer.removeMessageNode(messageNode);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Everything else in the deque is newer
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed)
|
||||
{
|
||||
clientThread.invoke(() -> client.runScript(ScriptID.BUILD_CHATBOX));
|
||||
}
|
||||
}
|
||||
|
||||
private void addActivityMessages()
|
||||
{
|
||||
FriendsChatManager friendsChatManager = client.getFriendsChatManager();
|
||||
if (friendsChatManager == null || activityBuffer.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<MemberActivity> activityIt = activityBuffer.values().iterator();
|
||||
|
||||
while (activityIt.hasNext())
|
||||
{
|
||||
MemberActivity activity = activityIt.next();
|
||||
|
||||
if (activity.getTick() < client.getTickCount() - MESSAGE_DELAY)
|
||||
{
|
||||
activityIt.remove();
|
||||
addActivityMessage(friendsChatManager, activity.getMember(), activity.getActivityType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addActivityMessage(FriendsChatManager friendsChatManager, FriendsChatMember member, ActivityType activityType)
|
||||
{
|
||||
final String activityMessage = activityType == ActivityType.JOINED ? " has joined." : " has left.";
|
||||
final FriendsChatRank rank = member.getRank();
|
||||
Color textColor = CHAT_FC_TEXT_OPAQUE_BACKGROUND;
|
||||
Color channelColor = CHAT_FC_NAME_OPAQUE_BACKGROUND;
|
||||
int rankIcon = -1;
|
||||
|
||||
if (client.isResized() && client.getVar(Varbits.TRANSPARENT_CHATBOX) == 1)
|
||||
{
|
||||
textColor = CHAT_FC_TEXT_TRANSPARENT_BACKGROUND;
|
||||
channelColor = CHAT_FC_NAME_TRANSPARENT_BACKGROUND;
|
||||
}
|
||||
|
||||
if (config.chatIcons() && rank != null && rank != FriendsChatRank.UNRANKED)
|
||||
{
|
||||
rankIcon = friendChatManager.getIconNumber(rank);
|
||||
}
|
||||
|
||||
ChatMessageBuilder message = new ChatMessageBuilder()
|
||||
.append("[")
|
||||
.append(channelColor, friendsChatManager.getName());
|
||||
if (rankIcon > -1)
|
||||
{
|
||||
message
|
||||
.append(" ")
|
||||
.img(rankIcon);
|
||||
}
|
||||
message
|
||||
.append("] ")
|
||||
.append(textColor, member.getName() + activityMessage);
|
||||
|
||||
final String messageString = message.build();
|
||||
client.addChatMessage(ChatMessageType.FRIENDSCHATNOTIFICATION, "", messageString, "");
|
||||
|
||||
final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(ChatMessageType.FRIENDSCHATNOTIFICATION.getType());
|
||||
final MessageNode[] lines = chatLineBuffer.getLines();
|
||||
final MessageNode line = lines[0];
|
||||
|
||||
MemberJoinMessage joinMessage = new MemberJoinMessage(line, line.getId(), client.getTickCount());
|
||||
joinMessages.addLast(joinMessage);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onVarClientStrChanged(VarClientStrChanged strChanged)
|
||||
{
|
||||
if (strChanged.getIndex() == VarClientStr.RECENT_FRIENDS_CHAT.getIndex() && config.recentChats())
|
||||
{
|
||||
updateRecentChat(client.getVar(VarClientStr.RECENT_FRIENDS_CHAT));
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage chatMessage)
|
||||
{
|
||||
if (client.getGameState() != GameState.LOADING && client.getGameState() != GameState.LOGGED_IN)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FriendsChatManager friendsChatManager = client.getFriendsChatManager();
|
||||
if (friendsChatManager == null || friendsChatManager.getCount() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (chatMessage.getType())
|
||||
{
|
||||
case PRIVATECHAT:
|
||||
case MODPRIVATECHAT:
|
||||
if (!config.privateMessageIcons())
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case PUBLICCHAT:
|
||||
case MODCHAT:
|
||||
if (!config.publicChatIcons())
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case FRIENDSCHAT:
|
||||
if (!config.chatIcons())
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
insertRankIcon(chatMessage);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged state)
|
||||
{
|
||||
GameState gameState = state.getGameState();
|
||||
|
||||
if (gameState == GameState.LOGIN_SCREEN || gameState == GameState.CONNECTION_LOST || gameState == GameState.HOPPING)
|
||||
{
|
||||
members.clear();
|
||||
resetCounter();
|
||||
|
||||
joinMessages.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPlayerSpawned(PlayerSpawned event)
|
||||
{
|
||||
final Player local = client.getLocalPlayer();
|
||||
final Player player = event.getPlayer();
|
||||
|
||||
if (player != local && player.isFriendsChatMember())
|
||||
{
|
||||
members.add(player);
|
||||
addCounter();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPlayerDespawned(PlayerDespawned event)
|
||||
{
|
||||
if (members.remove(event.getPlayer()) && members.isEmpty())
|
||||
{
|
||||
resetCounter();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFriendsChatChanged(FriendsChatChanged event)
|
||||
{
|
||||
if (event.isJoined())
|
||||
{
|
||||
joinedTick = client.getTickCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
members.clear();
|
||||
resetCounter();
|
||||
}
|
||||
|
||||
activityBuffer.clear();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptCallbackEvent(ScriptCallbackEvent scriptCallbackEvent)
|
||||
{
|
||||
switch (scriptCallbackEvent.getEventName())
|
||||
{
|
||||
case "friendsChatInput":
|
||||
{
|
||||
final int[] intStack = client.getIntStack();
|
||||
final int size = client.getIntStackSize();
|
||||
// If the user accidentally adds a / when the config and the friends chat chat tab is active, handle it like a normal message
|
||||
boolean alterDispatch = config.friendsChatTabChat() && !client.getVar(VarClientStr.CHATBOX_TYPED_TEXT).startsWith("/");
|
||||
intStack[size - 1] = alterDispatch ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
case "confirmFriendsChatKick":
|
||||
{
|
||||
if (!config.confirmKicks() || kickConfirmed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Set a flag so the script doesn't instantly kick them
|
||||
final int[] intStack = client.getIntStack();
|
||||
final int size = client.getIntStackSize();
|
||||
intStack[size - 1] = 1;
|
||||
|
||||
// Get name of player we are trying to kick
|
||||
final String[] stringStack = client.getStringStack();
|
||||
final int stringSize = client.getStringStackSize();
|
||||
final String kickPlayerName = stringStack[stringSize - 1];
|
||||
|
||||
// Show a chatbox panel confirming the kick
|
||||
clientThread.invokeLater(() -> confirmKickPlayer(kickPlayerName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptPostFired(ScriptPostFired event)
|
||||
{
|
||||
if (event.getScriptId() == ScriptID.FRIENDS_CHAT_CHANNEL_REBUILD && config.showIgnores())
|
||||
{
|
||||
colorIgnoredPlayers(config.showIgnoresColor());
|
||||
}
|
||||
}
|
||||
|
||||
int getMembersSize()
|
||||
{
|
||||
return members.size();
|
||||
}
|
||||
|
||||
private void insertRankIcon(final ChatMessage message)
|
||||
{
|
||||
final FriendsChatRank rank = friendChatManager.getRank(message.getName());
|
||||
|
||||
if (rank != null && rank != FriendsChatRank.UNRANKED)
|
||||
{
|
||||
int iconNumber = friendChatManager.getIconNumber(rank);
|
||||
final String img = "<img=" + iconNumber + ">";
|
||||
if (message.getType() == ChatMessageType.FRIENDSCHAT)
|
||||
{
|
||||
message.getMessageNode()
|
||||
.setSender(message.getMessageNode().getSender() + " " + img);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.getMessageNode()
|
||||
.setName(img + message.getMessageNode().getName());
|
||||
}
|
||||
client.refreshChat();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetChats()
|
||||
{
|
||||
Widget chatList = client.getWidget(WidgetInfo.FRIENDS_CHAT_LIST);
|
||||
Widget chatTitleWidget = client.getWidget(WidgetInfo.FRIENDS_CHAT_TITLE);
|
||||
|
||||
if (chatList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FriendsChatManager friendsChatManager = client.getFriendsChatManager();
|
||||
if (friendsChatManager == null || friendsChatManager.getCount() == 0)
|
||||
{
|
||||
chatList.setChildren(null);
|
||||
}
|
||||
|
||||
chatTitleWidget.setText(TITLE);
|
||||
}
|
||||
|
||||
private void loadFriendsChats()
|
||||
{
|
||||
Widget chatList = client.getWidget(WidgetInfo.FRIENDS_CHAT_LIST);
|
||||
if (chatList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int y = 2;
|
||||
chatList.setChildren(null);
|
||||
for (String chat : Lists.reverse(chats))
|
||||
{
|
||||
Widget widget = chatList.createChild(-1, WidgetType.TEXT);
|
||||
widget.setFontId(494);
|
||||
widget.setTextColor(0xffffff);
|
||||
widget.setText(chat);
|
||||
widget.setOriginalHeight(14);
|
||||
widget.setOriginalWidth(142);
|
||||
widget.setOriginalY(y);
|
||||
widget.setOriginalX(20);
|
||||
widget.revalidate();
|
||||
|
||||
y += 14;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRecentChat(String s)
|
||||
{
|
||||
if (Strings.isNullOrEmpty(s))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s = Text.toJagexName(s);
|
||||
|
||||
chats.removeIf(s::equalsIgnoreCase);
|
||||
chats.add(s);
|
||||
|
||||
while (chats.size() > MAX_CHATS)
|
||||
{
|
||||
chats.remove(0);
|
||||
}
|
||||
|
||||
config.chatsData(Text.toCSV(chats));
|
||||
}
|
||||
|
||||
private void resetCounter()
|
||||
{
|
||||
infoBoxManager.removeInfoBox(membersIndicator);
|
||||
membersIndicator = null;
|
||||
}
|
||||
|
||||
private void addCounter()
|
||||
{
|
||||
if (!config.showCounter() || membersIndicator != null || members.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final BufferedImage image = spriteManager.getSprite(SpriteID.TAB_FRIENDS_CHAT, 0);
|
||||
membersIndicator = new MembersIndicator(image, this);
|
||||
infoBoxManager.addInfoBox(membersIndicator);
|
||||
}
|
||||
|
||||
private void confirmKickPlayer(final String kickPlayerName)
|
||||
{
|
||||
chatboxPanelManager.openTextMenuInput("Attempting to kick: " + kickPlayerName)
|
||||
.option("1. Confirm kick", () ->
|
||||
clientThread.invoke(() ->
|
||||
{
|
||||
kickConfirmed = true;
|
||||
client.runScript(ScriptID.FRIENDS_CHAT_SEND_KICK, kickPlayerName);
|
||||
kickConfirmed = false;
|
||||
})
|
||||
)
|
||||
.option("2. Cancel", Runnables::doNothing)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void colorIgnoredPlayers(Color ignoreColor)
|
||||
{
|
||||
Widget chatList = client.getWidget(WidgetInfo.FRIENDS_CHAT_LIST);
|
||||
if (chatList == null || chatList.getChildren() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NameableContainer<Ignore> ignoreContainer = client.getIgnoreContainer();
|
||||
// Iterate every 3 widgets, since the order of widgets is name, world, icon
|
||||
for (int i = 0; i < chatList.getChildren().length; i += 3)
|
||||
{
|
||||
Widget listWidget = chatList.getChild(i);
|
||||
String memberName = listWidget.getText();
|
||||
if (memberName.isEmpty() || ignoreContainer.findByName(memberName) == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
listWidget.setTextColor(ignoreColor.getRGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2018, trimbe <github.com/trimbe>
|
||||
* 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.friendschat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import net.runelite.api.FriendsChatMember;
|
||||
|
||||
@Value
|
||||
@AllArgsConstructor
|
||||
class MemberActivity
|
||||
{
|
||||
private ActivityType activityType;
|
||||
private FriendsChatMember member;
|
||||
private Integer tick;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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.friendschat;
|
||||
|
||||
import lombok.Value;
|
||||
import net.runelite.api.MessageNode;
|
||||
|
||||
@Value
|
||||
class MemberJoinMessage
|
||||
{
|
||||
private final MessageNode messageNode;
|
||||
private final int getMessageId;
|
||||
private final int tick;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Sebastiaan <https://github.com/SebastiaanVanspauwen>
|
||||
* 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.friendschat;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import net.runelite.client.ui.overlay.infobox.Counter;
|
||||
|
||||
class MembersIndicator extends Counter
|
||||
{
|
||||
private final FriendsChatPlugin plugin;
|
||||
|
||||
MembersIndicator(BufferedImage image, FriendsChatPlugin plugin)
|
||||
{
|
||||
super(image, plugin, plugin.getMembersSize());
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount()
|
||||
{
|
||||
return plugin.getMembersSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTooltip()
|
||||
{
|
||||
return plugin.getMembersSize() + " friends chat member(s) near you";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getTextColor()
|
||||
{
|
||||
return Color.WHITE;
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ class SceneUploader
|
||||
WallObject wallObject = tile.getWallObject();
|
||||
if (wallObject != null)
|
||||
{
|
||||
Renderable renderable1 = wallObject.getRenderable();
|
||||
Renderable renderable1 = wallObject.getRenderable1();
|
||||
if (renderable1 instanceof Model)
|
||||
{
|
||||
uploadModel((Model) renderable1, vertexBuffer, uvBuffer);
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.grandexchange;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup(GrandExchangeConfig.CONFIG_GROUP)
|
||||
public interface GrandExchangeConfig extends Config
|
||||
{
|
||||
String CONFIG_GROUP = "grandexchange";
|
||||
|
||||
@ConfigItem(
|
||||
position = 1,
|
||||
keyName = "quickLookup",
|
||||
name = "Hotkey lookup (Alt + Left click)",
|
||||
description = "Configures whether to enable the hotkey lookup for ge searches"
|
||||
)
|
||||
default boolean quickLookup()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 2,
|
||||
keyName = "enableNotifications",
|
||||
name = "Enable Notifications",
|
||||
description = "Configures whether to enable notifications when an offer updates"
|
||||
)
|
||||
default boolean enableNotifications()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 3,
|
||||
keyName = "enableOsbPrices",
|
||||
name = "Enable OSB actively traded prices",
|
||||
description = "Shows the OSBuddy actively traded price at the GE"
|
||||
)
|
||||
default boolean enableOsbPrices()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 4,
|
||||
keyName = "enableGeLimits",
|
||||
name = "Enable GE Limits on GE",
|
||||
description = "Shows the GE Limits on the GE"
|
||||
)
|
||||
default boolean enableGELimits()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 5,
|
||||
keyName = "enableGELimitReset",
|
||||
name = "Enable GE Limit Reset Timer",
|
||||
description = "Shows when GE Trade limits reset (H:MM)"
|
||||
)
|
||||
|
||||
default boolean enableGELimitReset()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 6,
|
||||
keyName = "showTotal",
|
||||
name = "Show grand exchange total",
|
||||
description = "Show grand exchange total"
|
||||
)
|
||||
default boolean showTotal()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 7,
|
||||
keyName = "showExact",
|
||||
name = "Show exact total value",
|
||||
description = "Show exact total value"
|
||||
)
|
||||
default boolean showExact()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 8,
|
||||
keyName = "highlightSearchMatch",
|
||||
name = "Highlight Search Match",
|
||||
description = "Highlights the search match with an underline"
|
||||
)
|
||||
default boolean highlightSearchMatch()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 9,
|
||||
keyName = "geSearchMode",
|
||||
name = "Search Mode",
|
||||
description = "The search mode to use for the GE<br>"
|
||||
+ "Default - Matches exact text only<br>"
|
||||
+ "Fuzzy Only - Matches inexact text such as 'sara sword'<br>"
|
||||
+ "Fuzzy Fallback - Uses default search, falling back to fuzzy search if no results were found"
|
||||
)
|
||||
default GrandExchangeSearchMode geSearchMode()
|
||||
{
|
||||
return GrandExchangeSearchMode.DEFAULT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Jeremy Plsek <https://github.com/jplsek>
|
||||
* 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.grandexchange;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import javax.inject.Inject;
|
||||
import javax.swing.SwingUtilities;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.MenuEntry;
|
||||
import net.runelite.client.input.KeyListener;
|
||||
import net.runelite.client.input.MouseAdapter;
|
||||
import static net.runelite.client.plugins.grandexchange.GrandExchangePlugin.SEARCH_GRAND_EXCHANGE;
|
||||
import net.runelite.client.util.Text;
|
||||
|
||||
public class GrandExchangeInputListener extends MouseAdapter implements KeyListener
|
||||
{
|
||||
private final Client client;
|
||||
private final GrandExchangePlugin plugin;
|
||||
|
||||
@Inject
|
||||
private GrandExchangeInputListener(Client client, GrandExchangePlugin plugin)
|
||||
{
|
||||
this.client = client;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseEvent mouseClicked(MouseEvent e)
|
||||
{
|
||||
// Check if left click + alt
|
||||
if (e.getButton() == MouseEvent.BUTTON1 && e.isAltDown())
|
||||
{
|
||||
final MenuEntry[] menuEntries = client.getMenuEntries();
|
||||
for (final MenuEntry menuEntry : menuEntries)
|
||||
{
|
||||
if (menuEntry.getOption().equals(SEARCH_GRAND_EXCHANGE))
|
||||
{
|
||||
search(Text.removeTags(menuEntry.getTarget()));
|
||||
e.consume();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.mouseClicked(e);
|
||||
}
|
||||
|
||||
private void search(final String itemName)
|
||||
{
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
plugin.getPanel().showSearch();
|
||||
|
||||
if (!plugin.getButton().isSelected())
|
||||
{
|
||||
plugin.getButton().getOnSelect().run();
|
||||
}
|
||||
|
||||
plugin.getPanel().getSearchPanel().priceLookup(itemName);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e)
|
||||
{
|
||||
if (e.isAltDown())
|
||||
{
|
||||
plugin.setHotKeyPressed(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e)
|
||||
{
|
||||
if (!e.isAltDown())
|
||||
{
|
||||
plugin.setHotKeyPressed(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
|
||||
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||
* 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.grandexchange;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.util.AsyncBufferedImage;
|
||||
import net.runelite.client.util.QuantityFormatter;
|
||||
|
||||
/**
|
||||
* This panel displays an individual item result in the
|
||||
* Grand Exchange search plugin.
|
||||
*/
|
||||
class GrandExchangeItemPanel extends JPanel
|
||||
{
|
||||
private static final Dimension ICON_SIZE = new Dimension(32, 32);
|
||||
|
||||
GrandExchangeItemPanel(AsyncBufferedImage icon, String name, int itemID, int gePrice, Double
|
||||
haPrice, int geItemLimit)
|
||||
{
|
||||
BorderLayout layout = new BorderLayout();
|
||||
layout.setHgap(5);
|
||||
setLayout(layout);
|
||||
setToolTipText(name);
|
||||
setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
|
||||
Color background = getBackground();
|
||||
List<JPanel> panels = new ArrayList<>();
|
||||
panels.add(this);
|
||||
|
||||
MouseAdapter itemPanelMouseListener = new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
for (JPanel panel : panels)
|
||||
{
|
||||
matchComponentBackground(panel, ColorScheme.DARK_GRAY_HOVER_COLOR);
|
||||
}
|
||||
setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e)
|
||||
{
|
||||
for (JPanel panel : panels)
|
||||
{
|
||||
matchComponentBackground(panel, background);
|
||||
}
|
||||
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e)
|
||||
{
|
||||
GrandExchangePlugin.openGeLink(name, itemID);
|
||||
}
|
||||
};
|
||||
|
||||
addMouseListener(itemPanelMouseListener);
|
||||
|
||||
setBorder(new EmptyBorder(5, 5, 5, 0));
|
||||
|
||||
// Icon
|
||||
JLabel itemIcon = new JLabel();
|
||||
itemIcon.setPreferredSize(ICON_SIZE);
|
||||
if (icon != null)
|
||||
{
|
||||
icon.addTo(itemIcon);
|
||||
}
|
||||
add(itemIcon, BorderLayout.LINE_START);
|
||||
|
||||
// Item details panel
|
||||
JPanel rightPanel = new JPanel(new GridLayout(3, 1));
|
||||
panels.add(rightPanel);
|
||||
rightPanel.setBackground(background);
|
||||
|
||||
// Item name
|
||||
JLabel itemName = new JLabel();
|
||||
itemName.setForeground(Color.WHITE);
|
||||
itemName.setMaximumSize(new Dimension(0, 0)); // to limit the label's size for
|
||||
itemName.setPreferredSize(new Dimension(0, 0)); // items with longer names
|
||||
itemName.setText(name);
|
||||
rightPanel.add(itemName);
|
||||
|
||||
// Ge price
|
||||
JLabel gePriceLabel = new JLabel();
|
||||
if (gePrice > 0)
|
||||
{
|
||||
gePriceLabel.setText(QuantityFormatter.formatNumber(gePrice) + " gp");
|
||||
}
|
||||
else
|
||||
{
|
||||
gePriceLabel.setText("N/A");
|
||||
}
|
||||
gePriceLabel.setForeground(ColorScheme.GRAND_EXCHANGE_PRICE);
|
||||
rightPanel.add(gePriceLabel);
|
||||
|
||||
JPanel alchAndLimitPanel = new JPanel(new BorderLayout());
|
||||
panels.add(alchAndLimitPanel);
|
||||
alchAndLimitPanel.setBackground(background);
|
||||
|
||||
// Alch price
|
||||
JLabel haPriceLabel = new JLabel();
|
||||
haPriceLabel.setText(QuantityFormatter.formatNumber(haPrice.intValue()) + " alch");
|
||||
haPriceLabel.setForeground(ColorScheme.GRAND_EXCHANGE_ALCH);
|
||||
alchAndLimitPanel.add(haPriceLabel, BorderLayout.WEST);
|
||||
|
||||
// GE Limit
|
||||
JLabel geLimitLabel = new JLabel();
|
||||
String limitLabelText = geItemLimit == 0 ? "" : "Limit " + QuantityFormatter.formatNumber(geItemLimit);
|
||||
geLimitLabel.setText(limitLabelText);
|
||||
geLimitLabel.setForeground(ColorScheme.GRAND_EXCHANGE_LIMIT);
|
||||
geLimitLabel.setBorder(new CompoundBorder(geLimitLabel.getBorder(), new EmptyBorder(0, 0, 0, 7)));
|
||||
alchAndLimitPanel.add(geLimitLabel, BorderLayout.EAST);
|
||||
|
||||
rightPanel.add(alchAndLimitPanel);
|
||||
|
||||
add(rightPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private void matchComponentBackground(JPanel panel, Color color)
|
||||
{
|
||||
panel.setBackground(color);
|
||||
for (Component c : panel.getComponents())
|
||||
{
|
||||
c.setBackground(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.grandexchange;
|
||||
|
||||
import lombok.Value;
|
||||
import net.runelite.client.util.AsyncBufferedImage;
|
||||
|
||||
@Value
|
||||
class GrandExchangeItems
|
||||
{
|
||||
private final AsyncBufferedImage icon;
|
||||
private final String name;
|
||||
private final int itemId;
|
||||
private final int gePrice;
|
||||
private final double haPrice;
|
||||
private final int geItemLimit;
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (c) 2018, SomeoneWithAnInternetConnection
|
||||
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||
* 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.grandexchange;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import net.runelite.api.GrandExchangeOffer;
|
||||
import static net.runelite.api.GrandExchangeOfferState.BOUGHT;
|
||||
import static net.runelite.api.GrandExchangeOfferState.BUYING;
|
||||
import static net.runelite.api.GrandExchangeOfferState.CANCELLED_BUY;
|
||||
import static net.runelite.api.GrandExchangeOfferState.CANCELLED_SELL;
|
||||
import static net.runelite.api.GrandExchangeOfferState.EMPTY;
|
||||
import net.runelite.api.ItemComposition;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.FontManager;
|
||||
import net.runelite.client.ui.components.ThinProgressBar;
|
||||
import net.runelite.client.util.ColorUtil;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.client.util.QuantityFormatter;
|
||||
|
||||
public class GrandExchangeOfferSlot extends JPanel
|
||||
{
|
||||
private static final String FACE_CARD = "FACE_CARD";
|
||||
private static final String DETAILS_CARD = "DETAILS_CARD";
|
||||
|
||||
private static final ImageIcon RIGHT_ARROW_ICON;
|
||||
private static final ImageIcon LEFT_ARROW_ICON;
|
||||
|
||||
private final JPanel container = new JPanel();
|
||||
private final CardLayout cardLayout = new CardLayout();
|
||||
|
||||
private final JLabel itemIcon = new JLabel();
|
||||
private final JLabel itemName = new JLabel();
|
||||
private final JLabel offerInfo = new JLabel();
|
||||
|
||||
private final JLabel itemPrice = new JLabel();
|
||||
private final JLabel offerSpent = new JLabel();
|
||||
|
||||
private final ThinProgressBar progressBar = new ThinProgressBar();
|
||||
|
||||
private boolean showingFace = true;
|
||||
|
||||
static
|
||||
{
|
||||
final BufferedImage rightArrow = ImageUtil.alphaOffset(ImageUtil.getResourceStreamFromClass(GrandExchangeOfferSlot.class, "/util/arrow_right.png"), 0.25f);
|
||||
RIGHT_ARROW_ICON = new ImageIcon(rightArrow);
|
||||
LEFT_ARROW_ICON = new ImageIcon(ImageUtil.flipImage(rightArrow, true, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* This (sub)panel is used for each GE slot displayed
|
||||
* in the sidebar
|
||||
*/
|
||||
GrandExchangeOfferSlot()
|
||||
{
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
setBorder(new EmptyBorder(7, 0, 0, 0));
|
||||
|
||||
final MouseListener ml = new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
if (SwingUtilities.isLeftMouseButton(mouseEvent))
|
||||
{
|
||||
switchPanel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent mouseEvent)
|
||||
{
|
||||
container.setBackground(ColorScheme.DARKER_GRAY_HOVER_COLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent mouseEvent)
|
||||
{
|
||||
container.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
}
|
||||
};
|
||||
|
||||
container.setLayout(cardLayout);
|
||||
container.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
|
||||
JPanel faceCard = new JPanel();
|
||||
faceCard.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
faceCard.setLayout(new BorderLayout());
|
||||
faceCard.addMouseListener(ml);
|
||||
|
||||
itemIcon.setVerticalAlignment(JLabel.CENTER);
|
||||
itemIcon.setHorizontalAlignment(JLabel.CENTER);
|
||||
itemIcon.setPreferredSize(new Dimension(45, 45));
|
||||
|
||||
itemName.setForeground(Color.WHITE);
|
||||
itemName.setVerticalAlignment(JLabel.BOTTOM);
|
||||
itemName.setFont(FontManager.getRunescapeSmallFont());
|
||||
|
||||
offerInfo.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
|
||||
offerInfo.setVerticalAlignment(JLabel.TOP);
|
||||
offerInfo.setFont(FontManager.getRunescapeSmallFont());
|
||||
|
||||
JLabel switchFaceViewIcon = new JLabel();
|
||||
switchFaceViewIcon.setIcon(RIGHT_ARROW_ICON);
|
||||
switchFaceViewIcon.setVerticalAlignment(JLabel.CENTER);
|
||||
switchFaceViewIcon.setHorizontalAlignment(JLabel.CENTER);
|
||||
switchFaceViewIcon.setPreferredSize(new Dimension(30, 45));
|
||||
|
||||
JPanel offerFaceDetails = new JPanel();
|
||||
offerFaceDetails.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
offerFaceDetails.setLayout(new GridLayout(2, 1, 0, 2));
|
||||
|
||||
offerFaceDetails.add(itemName);
|
||||
offerFaceDetails.add(offerInfo);
|
||||
|
||||
faceCard.add(offerFaceDetails, BorderLayout.CENTER);
|
||||
faceCard.add(itemIcon, BorderLayout.WEST);
|
||||
faceCard.add(switchFaceViewIcon, BorderLayout.EAST);
|
||||
|
||||
JPanel detailsCard = new JPanel();
|
||||
detailsCard.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
detailsCard.setLayout(new BorderLayout());
|
||||
detailsCard.setBorder(new EmptyBorder(0, 15, 0, 0));
|
||||
detailsCard.addMouseListener(ml);
|
||||
|
||||
itemPrice.setForeground(Color.WHITE);
|
||||
itemPrice.setVerticalAlignment(JLabel.BOTTOM);
|
||||
itemPrice.setFont(FontManager.getRunescapeSmallFont());
|
||||
|
||||
offerSpent.setForeground(Color.WHITE);
|
||||
offerSpent.setVerticalAlignment(JLabel.TOP);
|
||||
offerSpent.setFont(FontManager.getRunescapeSmallFont());
|
||||
|
||||
JLabel switchDetailsViewIcon = new JLabel();
|
||||
switchDetailsViewIcon.setIcon(LEFT_ARROW_ICON);
|
||||
switchDetailsViewIcon.setVerticalAlignment(JLabel.CENTER);
|
||||
switchDetailsViewIcon.setHorizontalAlignment(JLabel.CENTER);
|
||||
switchDetailsViewIcon.setPreferredSize(new Dimension(30, 45));
|
||||
|
||||
JPanel offerDetails = new JPanel();
|
||||
offerDetails.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
offerDetails.setLayout(new GridLayout(2, 1));
|
||||
|
||||
offerDetails.add(itemPrice);
|
||||
offerDetails.add(offerSpent);
|
||||
|
||||
detailsCard.add(offerDetails, BorderLayout.CENTER);
|
||||
detailsCard.add(switchDetailsViewIcon, BorderLayout.EAST);
|
||||
|
||||
container.add(faceCard, FACE_CARD);
|
||||
container.add(detailsCard, DETAILS_CARD);
|
||||
|
||||
cardLayout.show(container, FACE_CARD);
|
||||
|
||||
add(container, BorderLayout.CENTER);
|
||||
add(progressBar, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
void updateOffer(ItemComposition offerItem, BufferedImage itemImage, @Nullable GrandExchangeOffer newOffer)
|
||||
{
|
||||
if (newOffer == null || newOffer.getState() == EMPTY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
cardLayout.show(container, FACE_CARD);
|
||||
|
||||
itemName.setText(offerItem.getName());
|
||||
itemIcon.setIcon(new ImageIcon(itemImage));
|
||||
|
||||
boolean buying = newOffer.getState() == BOUGHT
|
||||
|| newOffer.getState() == BUYING
|
||||
|| newOffer.getState() == CANCELLED_BUY;
|
||||
|
||||
String offerState = (buying ? "Bought " : "Sold ")
|
||||
+ QuantityFormatter.quantityToRSDecimalStack(newOffer.getQuantitySold()) + " / "
|
||||
+ QuantityFormatter.quantityToRSDecimalStack(newOffer.getTotalQuantity());
|
||||
|
||||
offerInfo.setText(offerState);
|
||||
|
||||
itemPrice.setText(htmlLabel("Price each: ", QuantityFormatter.formatNumber(newOffer.getPrice())));
|
||||
|
||||
String action = buying ? "Spent: " : "Received: ";
|
||||
|
||||
offerSpent.setText(htmlLabel(action, QuantityFormatter.formatNumber(newOffer.getSpent()) + " / "
|
||||
+ QuantityFormatter.formatNumber(newOffer.getPrice() * newOffer.getTotalQuantity())));
|
||||
|
||||
progressBar.setForeground(getProgressColor(newOffer));
|
||||
progressBar.setMaximumValue(newOffer.getTotalQuantity());
|
||||
progressBar.setValue(newOffer.getQuantitySold());
|
||||
|
||||
final JPopupMenu popupMenu = new JPopupMenu();
|
||||
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
|
||||
final JMenuItem openGeLink = new JMenuItem("Open Grand Exchange website");
|
||||
openGeLink.addActionListener(e -> GrandExchangePlugin.openGeLink(offerItem.getName(), offerItem.getId()));
|
||||
popupMenu.add(openGeLink);
|
||||
|
||||
/* Couldn't set the tooltip for the container panel as the children override it, so I'm setting
|
||||
* the tooltips on the children instead. */
|
||||
for (Component c : container.getComponents())
|
||||
{
|
||||
if (c instanceof JPanel)
|
||||
{
|
||||
JPanel panel = (JPanel) c;
|
||||
panel.setToolTipText(htmlTooltip(((int) progressBar.getPercentage()) + "%"));
|
||||
panel.setComponentPopupMenu(popupMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private String htmlTooltip(String value)
|
||||
{
|
||||
return "<html><body style = 'color:" + ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR) + "'>Progress: <span style = 'color:white'>" + value + "</span></body></html>";
|
||||
}
|
||||
|
||||
private String htmlLabel(String key, String value)
|
||||
{
|
||||
return "<html><body style = 'color:white'>" + key + "<span style = 'color:" + ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR) + "'>" + value + "</span></body></html>";
|
||||
}
|
||||
|
||||
private void switchPanel()
|
||||
{
|
||||
this.showingFace = !this.showingFace;
|
||||
cardLayout.show(container, showingFace ? FACE_CARD : DETAILS_CARD);
|
||||
}
|
||||
|
||||
private Color getProgressColor(GrandExchangeOffer offer)
|
||||
{
|
||||
if (offer.getState() == CANCELLED_BUY || offer.getState() == CANCELLED_SELL)
|
||||
{
|
||||
return ColorScheme.PROGRESS_ERROR_COLOR;
|
||||
}
|
||||
|
||||
if (offer.getQuantitySold() == offer.getTotalQuantity())
|
||||
{
|
||||
return ColorScheme.PROGRESS_COMPLETE_COLOR;
|
||||
}
|
||||
|
||||
return ColorScheme.PROGRESS_INPROGRESS_COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (c) 2018, SomeoneWithAnInternetConnection
|
||||
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||
* 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.grandexchange;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import net.runelite.api.GrandExchangeOffer;
|
||||
import net.runelite.api.GrandExchangeOfferState;
|
||||
import net.runelite.api.ItemComposition;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.components.PluginErrorPanel;
|
||||
|
||||
public class GrandExchangeOffersPanel extends JPanel
|
||||
{
|
||||
private static final String ERROR_PANEL = "ERROR_PANEL";
|
||||
private static final String OFFERS_PANEL = "OFFERS_PANEL";
|
||||
|
||||
private static final int MAX_OFFERS = 8;
|
||||
|
||||
private final GridBagConstraints constraints = new GridBagConstraints();
|
||||
private final CardLayout cardLayout = new CardLayout();
|
||||
|
||||
/* The offers container, this will hold all the individual ge offers panels */
|
||||
private final JPanel offerPanel = new JPanel();
|
||||
|
||||
/* The center panel, this holds either the error panel or the offers container */
|
||||
private final JPanel container = new JPanel(cardLayout);
|
||||
|
||||
private final GrandExchangeOfferSlot[] offerSlotPanels = new GrandExchangeOfferSlot[MAX_OFFERS];
|
||||
|
||||
GrandExchangeOffersPanel()
|
||||
{
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
|
||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
constraints.weightx = 1;
|
||||
constraints.gridx = 0;
|
||||
constraints.gridy = 0;
|
||||
|
||||
/* This panel wraps the offers panel and limits its height */
|
||||
JPanel offersWrapper = new JPanel(new BorderLayout());
|
||||
offersWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
offersWrapper.add(offerPanel, BorderLayout.NORTH);
|
||||
|
||||
offerPanel.setLayout(new GridBagLayout());
|
||||
offerPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
offerPanel.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
|
||||
/* This panel wraps the error panel and limits its height */
|
||||
JPanel errorWrapper = new JPanel(new BorderLayout());
|
||||
errorWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
/* The error panel, this displays an error message */
|
||||
PluginErrorPanel errorPanel = new PluginErrorPanel();
|
||||
errorWrapper.add(errorPanel, BorderLayout.NORTH);
|
||||
|
||||
errorPanel.setBorder(new EmptyBorder(50, 20, 20, 20));
|
||||
errorPanel.setContent("No offers detected", "No grand exchange offers were found on your account.");
|
||||
|
||||
container.add(offersWrapper, OFFERS_PANEL);
|
||||
container.add(errorWrapper, ERROR_PANEL);
|
||||
|
||||
add(container, BorderLayout.CENTER);
|
||||
|
||||
resetOffers();
|
||||
}
|
||||
|
||||
void resetOffers()
|
||||
{
|
||||
offerPanel.removeAll();
|
||||
for (int i = 0; i < offerSlotPanels.length; i++)
|
||||
{
|
||||
offerSlotPanels[i] = null;
|
||||
}
|
||||
updateEmptyOffersPanel();
|
||||
}
|
||||
|
||||
void updateOffer(ItemComposition item, BufferedImage itemImage, GrandExchangeOffer newOffer, int slot)
|
||||
{
|
||||
/* If slot was previously filled, and is now empty, remove it from the list */
|
||||
if (newOffer == null || newOffer.getState() == GrandExchangeOfferState.EMPTY)
|
||||
{
|
||||
if (offerSlotPanels[slot] != null)
|
||||
{
|
||||
offerPanel.remove(offerSlotPanels[slot]);
|
||||
offerSlotPanels[slot] = null;
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
removeTopMargin();
|
||||
updateEmptyOffersPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
/* If slot was empty, and is now filled, add it to the list */
|
||||
if (offerSlotPanels[slot] == null)
|
||||
{
|
||||
GrandExchangeOfferSlot newSlot = new GrandExchangeOfferSlot();
|
||||
offerSlotPanels[slot] = newSlot;
|
||||
offerPanel.add(newSlot, constraints);
|
||||
constraints.gridy++;
|
||||
}
|
||||
|
||||
offerSlotPanels[slot].updateOffer(item, itemImage, newOffer);
|
||||
|
||||
removeTopMargin();
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
|
||||
updateEmptyOffersPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the border for the first offer slot.
|
||||
*/
|
||||
private void removeTopMargin()
|
||||
{
|
||||
|
||||
if (offerPanel.getComponentCount() <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JPanel firstItem = (JPanel) offerPanel.getComponent(0);
|
||||
firstItem.setBorder(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates the amount of empty ge offer slots, if all slots are empty,
|
||||
* it shows the error panel.
|
||||
*/
|
||||
private void updateEmptyOffersPanel()
|
||||
{
|
||||
int nullCount = 0;
|
||||
for (GrandExchangeOfferSlot slot : offerSlotPanels)
|
||||
{
|
||||
if (slot == null)
|
||||
{
|
||||
nullCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nullCount == MAX_OFFERS)
|
||||
{
|
||||
offerPanel.removeAll();
|
||||
cardLayout.show(container, ERROR_PANEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
cardLayout.show(container, OFFERS_PANEL);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2018, SomeoneWithAnInternetConnection
|
||||
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||
* 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.grandexchange;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.inject.Inject;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import lombok.Getter;
|
||||
import net.runelite.client.callback.ClientThread;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.ui.components.materialtabs.MaterialTab;
|
||||
import net.runelite.client.ui.components.materialtabs.MaterialTabGroup;
|
||||
|
||||
class GrandExchangePanel extends PluginPanel
|
||||
{
|
||||
|
||||
// this panel will hold either the ge search panel or the ge offers panel
|
||||
private final JPanel display = new JPanel();
|
||||
|
||||
private final MaterialTabGroup tabGroup = new MaterialTabGroup(display);
|
||||
private final MaterialTab searchTab;
|
||||
|
||||
@Getter
|
||||
private GrandExchangeSearchPanel searchPanel;
|
||||
@Getter
|
||||
private GrandExchangeOffersPanel offersPanel;
|
||||
|
||||
@Inject
|
||||
private GrandExchangePanel(ClientThread clientThread, ItemManager itemManager, ScheduledExecutorService executor)
|
||||
{
|
||||
super(false);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
|
||||
// Search Panel
|
||||
searchPanel = new GrandExchangeSearchPanel(clientThread, itemManager, executor);
|
||||
|
||||
//Offers Panel
|
||||
offersPanel = new GrandExchangeOffersPanel();
|
||||
|
||||
MaterialTab offersTab = new MaterialTab("Offers", tabGroup, offersPanel);
|
||||
searchTab = new MaterialTab("Search", tabGroup, searchPanel);
|
||||
|
||||
tabGroup.setBorder(new EmptyBorder(5, 0, 0, 0));
|
||||
tabGroup.addTab(offersTab);
|
||||
tabGroup.addTab(searchTab);
|
||||
tabGroup.select(offersTab); // selects the default selected tab
|
||||
|
||||
add(tabGroup, BorderLayout.NORTH);
|
||||
add(display, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
void showSearch()
|
||||
{
|
||||
if (searchPanel.isShowing())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tabGroup.select(searchTab);
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,964 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Adam <Adam@sigterm.info>
|
||||
* Copyright (c) 2017, Robbie <https://github.com/rbbi>
|
||||
* Copyright (c) 2018, SomeoneWithAnInternetConnection
|
||||
* Copyright (c) 2020, Dennis <me@dennis.dev>
|
||||
* 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.grandexchange;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.hash.Hasher;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.primitives.Shorts;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.ToIntFunction;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import javax.inject.Inject;
|
||||
import javax.swing.SwingUtilities;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.GrandExchangeOffer;
|
||||
import net.runelite.api.GrandExchangeOfferState;
|
||||
import net.runelite.api.ItemComposition;
|
||||
import net.runelite.api.MenuAction;
|
||||
import net.runelite.api.MenuEntry;
|
||||
import net.runelite.api.ScriptID;
|
||||
import net.runelite.api.VarClientStr;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.FocusChanged;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.GrandExchangeOfferChanged;
|
||||
import net.runelite.api.events.GrandExchangeSearched;
|
||||
import net.runelite.api.events.MenuEntryAdded;
|
||||
import net.runelite.api.events.ScriptCallbackEvent;
|
||||
import net.runelite.api.events.ScriptPostFired;
|
||||
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.Notifier;
|
||||
import net.runelite.client.account.AccountSession;
|
||||
import net.runelite.client.account.SessionManager;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.events.SessionClose;
|
||||
import net.runelite.client.events.SessionOpen;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
import net.runelite.client.input.KeyManager;
|
||||
import net.runelite.client.input.MouseManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.ui.ClientToolbar;
|
||||
import net.runelite.client.ui.NavigationButton;
|
||||
import net.runelite.client.util.ColorUtil;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.client.util.LinkBrowser;
|
||||
import net.runelite.client.util.OSType;
|
||||
import net.runelite.client.util.QuantityFormatter;
|
||||
import net.runelite.client.util.Text;
|
||||
import net.runelite.http.api.ge.GrandExchangeClient;
|
||||
import net.runelite.http.api.ge.GrandExchangeTrade;
|
||||
import net.runelite.http.api.item.ItemStats;
|
||||
import net.runelite.http.api.osbuddy.OSBGrandExchangeClient;
|
||||
import net.runelite.http.api.osbuddy.OSBGrandExchangeResult;
|
||||
import net.runelite.http.api.worlds.WorldType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.apache.commons.lang3.time.DurationFormatUtils;
|
||||
import org.apache.commons.text.similarity.FuzzyScore;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Grand Exchange",
|
||||
description = "Provide additional and/or easier access to Grand Exchange information",
|
||||
tags = {"external", "integration", "notifications", "panel", "prices", "trade"}
|
||||
)
|
||||
@Slf4j
|
||||
public class GrandExchangePlugin extends Plugin
|
||||
{
|
||||
private static final int GE_SLOTS = 8;
|
||||
private static final int OFFER_CONTAINER_ITEM = 21;
|
||||
private static final int OFFER_DEFAULT_ITEM_ID = 6512;
|
||||
private static final String OSB_GE_TEXT = "<br>OSBuddy Actively traded price: ";
|
||||
|
||||
private static final String BUY_LIMIT_GE_TEXT = "<br>Buy limit: ";
|
||||
private static final String BUY_LIMIT_KEY = "buylimit";
|
||||
private static final Gson GSON = new Gson();
|
||||
private static final Duration BUY_LIMIT_RESET = Duration.ofHours(4);
|
||||
|
||||
static final String SEARCH_GRAND_EXCHANGE = "Search Grand Exchange";
|
||||
|
||||
private static final int MAX_RESULT_COUNT = 250;
|
||||
|
||||
private static final FuzzyScore FUZZY = new FuzzyScore(Locale.ENGLISH);
|
||||
|
||||
private static final Color FUZZY_HIGHLIGHT_COLOR = new Color(0x800000);
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private NavigationButton button;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private GrandExchangePanel panel;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private boolean hotKeyPressed;
|
||||
|
||||
@Inject
|
||||
private GrandExchangeInputListener inputListener;
|
||||
|
||||
@Inject
|
||||
private ItemManager itemManager;
|
||||
|
||||
@Inject
|
||||
private MouseManager mouseManager;
|
||||
|
||||
@Inject
|
||||
private KeyManager keyManager;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ClientToolbar clientToolbar;
|
||||
|
||||
@Inject
|
||||
private GrandExchangeConfig config;
|
||||
|
||||
@Inject
|
||||
private Notifier notifier;
|
||||
|
||||
@Inject
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
@Inject
|
||||
private SessionManager sessionManager;
|
||||
|
||||
@Inject
|
||||
private ConfigManager configManager;
|
||||
|
||||
private Widget grandExchangeText;
|
||||
private Widget grandExchangeItem;
|
||||
private String grandExchangeExamine;
|
||||
|
||||
private int osbItem;
|
||||
private OSBGrandExchangeResult osbGrandExchangeResult;
|
||||
|
||||
@Inject
|
||||
private GrandExchangeClient grandExchangeClient;
|
||||
private boolean loginBurstGeUpdates;
|
||||
|
||||
@Inject
|
||||
private OSBGrandExchangeClient osbGrandExchangeClient;
|
||||
|
||||
private boolean wasFuzzySearch;
|
||||
|
||||
private String machineUuid;
|
||||
private String lastUsername;
|
||||
|
||||
/**
|
||||
* Logic from {@link org.apache.commons.text.similarity.FuzzyScore}
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static List<Integer> findFuzzyIndices(String term, String query)
|
||||
{
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
|
||||
// fuzzy logic is case insensitive. We normalize the Strings to lower
|
||||
// case right from the start. Turning characters to lower case
|
||||
// via Character.toLowerCase(char) is unfortunately insufficient
|
||||
// as it does not accept a locale.
|
||||
final String termLowerCase = term.toLowerCase();
|
||||
final String queryLowerCase = query.toLowerCase();
|
||||
|
||||
// the position in the term which will be scanned next for potential
|
||||
// query character matches
|
||||
int termIndex = 0;
|
||||
|
||||
for (int queryIndex = 0; queryIndex < queryLowerCase.length(); queryIndex++)
|
||||
{
|
||||
final char queryChar = queryLowerCase.charAt(queryIndex);
|
||||
|
||||
boolean termCharacterMatchFound = false;
|
||||
for (; termIndex < termLowerCase.length()
|
||||
&& !termCharacterMatchFound; termIndex++)
|
||||
{
|
||||
final char termChar = termLowerCase.charAt(termIndex);
|
||||
|
||||
if (queryChar == termChar)
|
||||
{
|
||||
indices.add(termIndex);
|
||||
|
||||
// we can leave the nested loop. Every character in the
|
||||
// query can match at most one character in the term.
|
||||
termCharacterMatchFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
private SavedOffer getOffer(int slot)
|
||||
{
|
||||
String offer = configManager.getRSProfileConfiguration("geoffer", Integer.toString(slot));
|
||||
if (offer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return GSON.fromJson(offer, SavedOffer.class);
|
||||
}
|
||||
|
||||
private void setOffer(int slot, SavedOffer offer)
|
||||
{
|
||||
configManager.setRSProfileConfiguration("geoffer", Integer.toString(slot), GSON.toJson(offer));
|
||||
}
|
||||
|
||||
private void deleteOffer(int slot)
|
||||
{
|
||||
configManager.unsetRSProfileConfiguration("geoffer", Integer.toString(slot));
|
||||
}
|
||||
|
||||
@Provides
|
||||
GrandExchangeConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(GrandExchangeConfig.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
OSBGrandExchangeClient provideOsbGrandExchangeClient(OkHttpClient okHttpClient)
|
||||
{
|
||||
return new OSBGrandExchangeClient(okHttpClient);
|
||||
}
|
||||
|
||||
@Provides
|
||||
GrandExchangeClient provideGrandExchangeClient(OkHttpClient okHttpClient)
|
||||
{
|
||||
return new GrandExchangeClient(okHttpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
panel = injector.getInstance(GrandExchangePanel.class);
|
||||
|
||||
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "ge_icon.png");
|
||||
|
||||
button = NavigationButton.builder()
|
||||
.tooltip("Grand Exchange")
|
||||
.icon(icon)
|
||||
.priority(3)
|
||||
.panel(panel)
|
||||
.build();
|
||||
|
||||
clientToolbar.addNavigation(button);
|
||||
|
||||
if (config.quickLookup())
|
||||
{
|
||||
mouseManager.registerMouseListener(inputListener);
|
||||
keyManager.registerKeyListener(inputListener);
|
||||
}
|
||||
|
||||
AccountSession accountSession = sessionManager.getAccountSession();
|
||||
if (accountSession != null)
|
||||
{
|
||||
grandExchangeClient.setUuid(accountSession.getUuid());
|
||||
}
|
||||
else
|
||||
{
|
||||
grandExchangeClient.setUuid(null);
|
||||
}
|
||||
|
||||
osbItem = -1;
|
||||
osbGrandExchangeResult = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
clientToolbar.removeNavigation(button);
|
||||
mouseManager.unregisterMouseListener(inputListener);
|
||||
keyManager.unregisterKeyListener(inputListener);
|
||||
grandExchangeText = null;
|
||||
grandExchangeItem = null;
|
||||
lastUsername = machineUuid = null;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onSessionOpen(SessionOpen sessionOpen)
|
||||
{
|
||||
AccountSession accountSession = sessionManager.getAccountSession();
|
||||
grandExchangeClient.setUuid(accountSession.getUuid());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onSessionClose(SessionClose sessionClose)
|
||||
{
|
||||
grandExchangeClient.setUuid(null);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged event)
|
||||
{
|
||||
if (event.getGroup().equals(GrandExchangeConfig.CONFIG_GROUP))
|
||||
{
|
||||
if (event.getKey().equals("quickLookup"))
|
||||
{
|
||||
if (config.quickLookup())
|
||||
{
|
||||
mouseManager.registerMouseListener(inputListener);
|
||||
keyManager.registerKeyListener(inputListener);
|
||||
}
|
||||
else
|
||||
{
|
||||
mouseManager.unregisterMouseListener(inputListener);
|
||||
keyManager.unregisterKeyListener(inputListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGrandExchangeOfferChanged(GrandExchangeOfferChanged offerEvent)
|
||||
{
|
||||
final int slot = offerEvent.getSlot();
|
||||
final GrandExchangeOffer offer = offerEvent.getOffer();
|
||||
|
||||
if (offer.getState() == GrandExchangeOfferState.EMPTY && client.getGameState() != GameState.LOGGED_IN)
|
||||
{
|
||||
// Trades are cleared by the client during LOGIN_SCREEN/HOPPING/LOGGING_IN, ignore those so we don't
|
||||
// zero and re-submit the trade on login as an update
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("GE offer updated: state: {}, slot: {}, item: {}, qty: {}, login: {}",
|
||||
offer.getState(), slot, offer.getItemId(), offer.getQuantitySold(), loginBurstGeUpdates);
|
||||
|
||||
ItemComposition offerItem = itemManager.getItemComposition(offer.getItemId());
|
||||
boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1;
|
||||
BufferedImage itemImage = itemManager.getImage(offer.getItemId(), offer.getTotalQuantity(), shouldStack);
|
||||
SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offer, slot));
|
||||
|
||||
submitTrade(slot, offer);
|
||||
|
||||
updateConfig(slot, offer);
|
||||
|
||||
if (loginBurstGeUpdates && slot == GE_SLOTS - 1) // slots are sent sequentially on login; this is the last one
|
||||
{
|
||||
loginBurstGeUpdates = false;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void submitTrade(int slot, GrandExchangeOffer offer)
|
||||
{
|
||||
GrandExchangeOfferState state = offer.getState();
|
||||
|
||||
if (state != GrandExchangeOfferState.CANCELLED_BUY && state != GrandExchangeOfferState.CANCELLED_SELL && state != GrandExchangeOfferState.BUYING && state != GrandExchangeOfferState.SELLING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SavedOffer savedOffer = getOffer(slot);
|
||||
if (savedOffer == null && (state == GrandExchangeOfferState.BUYING || state == GrandExchangeOfferState.SELLING) && offer.getQuantitySold() == 0)
|
||||
{
|
||||
// new offer
|
||||
GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade();
|
||||
grandExchangeTrade.setBuy(state == GrandExchangeOfferState.BUYING);
|
||||
grandExchangeTrade.setItemId(offer.getItemId());
|
||||
grandExchangeTrade.setTotal(offer.getTotalQuantity());
|
||||
grandExchangeTrade.setOffer(offer.getPrice());
|
||||
grandExchangeTrade.setSlot(slot);
|
||||
grandExchangeTrade.setWorldType(getGeWorldType());
|
||||
grandExchangeTrade.setLogin(loginBurstGeUpdates);
|
||||
|
||||
log.debug("Submitting new trade: {}", grandExchangeTrade);
|
||||
grandExchangeClient.submit(grandExchangeTrade);
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedOffer == null || savedOffer.getItemId() != offer.getItemId() || savedOffer.getPrice() != offer.getPrice() || savedOffer.getTotalQuantity() != offer.getTotalQuantity())
|
||||
{
|
||||
// desync
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedOffer.getState() == offer.getState() && savedOffer.getQuantitySold() == offer.getQuantitySold())
|
||||
{
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == GrandExchangeOfferState.CANCELLED_BUY || state == GrandExchangeOfferState.CANCELLED_SELL)
|
||||
{
|
||||
GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade();
|
||||
grandExchangeTrade.setBuy(state == GrandExchangeOfferState.CANCELLED_BUY);
|
||||
grandExchangeTrade.setCancel(true);
|
||||
grandExchangeTrade.setItemId(offer.getItemId());
|
||||
grandExchangeTrade.setQty(offer.getQuantitySold());
|
||||
grandExchangeTrade.setTotal(offer.getTotalQuantity());
|
||||
grandExchangeTrade.setSpent(offer.getSpent());
|
||||
grandExchangeTrade.setOffer(offer.getPrice());
|
||||
grandExchangeTrade.setSlot(slot);
|
||||
grandExchangeTrade.setWorldType(getGeWorldType());
|
||||
grandExchangeTrade.setLogin(loginBurstGeUpdates);
|
||||
|
||||
log.debug("Submitting cancelled: {}", grandExchangeTrade);
|
||||
grandExchangeClient.submit(grandExchangeTrade);
|
||||
return;
|
||||
}
|
||||
|
||||
final int qty = offer.getQuantitySold() - savedOffer.getQuantitySold();
|
||||
final int dspent = offer.getSpent() - savedOffer.getSpent();
|
||||
if (qty <= 0 || dspent <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade();
|
||||
grandExchangeTrade.setBuy(state == GrandExchangeOfferState.BUYING);
|
||||
grandExchangeTrade.setItemId(offer.getItemId());
|
||||
grandExchangeTrade.setQty(offer.getQuantitySold());
|
||||
grandExchangeTrade.setDqty(qty);
|
||||
grandExchangeTrade.setTotal(offer.getTotalQuantity());
|
||||
grandExchangeTrade.setDspent(dspent);
|
||||
grandExchangeTrade.setSpent(offer.getSpent());
|
||||
grandExchangeTrade.setOffer(offer.getPrice());
|
||||
grandExchangeTrade.setSlot(slot);
|
||||
grandExchangeTrade.setWorldType(getGeWorldType());
|
||||
grandExchangeTrade.setLogin(loginBurstGeUpdates);
|
||||
|
||||
log.debug("Submitting trade: {}", grandExchangeTrade);
|
||||
grandExchangeClient.submit(grandExchangeTrade);
|
||||
}
|
||||
|
||||
private WorldType getGeWorldType()
|
||||
{
|
||||
EnumSet<net.runelite.api.WorldType> worldTypes = client.getWorldType();
|
||||
if (worldTypes.contains(net.runelite.api.WorldType.DEADMAN))
|
||||
{
|
||||
return WorldType.DEADMAN;
|
||||
}
|
||||
else if (worldTypes.contains(net.runelite.api.WorldType.DEADMAN_TOURNAMENT))
|
||||
{
|
||||
return WorldType.DEADMAN_TOURNAMENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConfig(int slot, GrandExchangeOffer offer)
|
||||
{
|
||||
if (offer.getState() == GrandExchangeOfferState.EMPTY)
|
||||
{
|
||||
deleteOffer(slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
SavedOffer savedOffer = new SavedOffer();
|
||||
savedOffer.setItemId(offer.getItemId());
|
||||
savedOffer.setQuantitySold(offer.getQuantitySold());
|
||||
savedOffer.setTotalQuantity(offer.getTotalQuantity());
|
||||
savedOffer.setPrice(offer.getPrice());
|
||||
savedOffer.setSpent(offer.getSpent());
|
||||
savedOffer.setState(offer.getState());
|
||||
setOffer(slot, savedOffer);
|
||||
|
||||
updateLimitTimer(offer);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChatMessage(ChatMessage event)
|
||||
{
|
||||
if (!this.config.enableNotifications() || event.getType() != ChatMessageType.GAMEMESSAGE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String message = Text.removeTags(event.getMessage());
|
||||
|
||||
if (message.startsWith("Grand Exchange:"))
|
||||
{
|
||||
this.notifier.notify(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(GameStateChanged gameStateChanged)
|
||||
{
|
||||
if (gameStateChanged.getGameState() == GameState.LOGIN_SCREEN)
|
||||
{
|
||||
panel.getOffersPanel().resetOffers();
|
||||
loginBurstGeUpdates = true;
|
||||
}
|
||||
else if (gameStateChanged.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
grandExchangeClient.setMachineId(getMachineUuid());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuEntryAdded(MenuEntryAdded event)
|
||||
{
|
||||
// At the moment, if the user disables quick lookup, the input listener gets disabled. Thus, isHotKeyPressed()
|
||||
// should always return false when quick lookup is disabled.
|
||||
// Replace the default option with "Search ..." when holding alt
|
||||
if (client.getGameState() != GameState.LOGGED_IN || !hotKeyPressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final MenuEntry[] entries = client.getMenuEntries();
|
||||
final MenuEntry menuEntry = entries[entries.length - 1];
|
||||
final int widgetId = menuEntry.getParam1();
|
||||
final int groupId = WidgetInfo.TO_GROUP(widgetId);
|
||||
|
||||
switch (groupId)
|
||||
{
|
||||
case WidgetID.BANK_GROUP_ID:
|
||||
// Don't show for view tabs and such
|
||||
if (WidgetInfo.TO_CHILD(widgetId) != WidgetInfo.BANK_ITEM_CONTAINER.getChildId())
|
||||
{
|
||||
break;
|
||||
}
|
||||
case WidgetID.INVENTORY_GROUP_ID:
|
||||
case WidgetID.BANK_INVENTORY_GROUP_ID:
|
||||
case WidgetID.GRAND_EXCHANGE_INVENTORY_GROUP_ID:
|
||||
case WidgetID.SHOP_INVENTORY_GROUP_ID:
|
||||
menuEntry.setOption(SEARCH_GRAND_EXCHANGE);
|
||||
menuEntry.setType(MenuAction.RUNELITE.getId());
|
||||
client.setMenuEntries(entries);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFocusChanged(FocusChanged focusChanged)
|
||||
{
|
||||
if (!focusChanged.isFocused())
|
||||
{
|
||||
setHotKeyPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onWidgetLoaded(WidgetLoaded event)
|
||||
{
|
||||
switch (event.getGroupId())
|
||||
{
|
||||
// Grand exchange was opened.
|
||||
case WidgetID.GRAND_EXCHANGE_GROUP_ID:
|
||||
Widget grandExchangeOffer = client.getWidget(WidgetInfo.GRAND_EXCHANGE_OFFER_CONTAINER);
|
||||
grandExchangeText = client.getWidget(WidgetInfo.GRAND_EXCHANGE_OFFER_TEXT);
|
||||
grandExchangeItem = grandExchangeOffer.getChild(OFFER_CONTAINER_ITEM);
|
||||
break;
|
||||
// Grand exchange was closed (if it was open before).
|
||||
case WidgetID.INVENTORY_GROUP_ID:
|
||||
grandExchangeText = null;
|
||||
grandExchangeItem = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptPostFired(ScriptPostFired event)
|
||||
{
|
||||
// GE offers setup init
|
||||
if (event.getScriptId() == ScriptID.GE_OFFERS_SETUP_BUILD)
|
||||
{
|
||||
rebuildGeText();
|
||||
}
|
||||
else if (event.getScriptId() == ScriptID.GE_ITEM_SEARCH && config.highlightSearchMatch())
|
||||
{
|
||||
highlightSearchMatches();
|
||||
}
|
||||
}
|
||||
|
||||
private void highlightSearchMatches()
|
||||
{
|
||||
if (!wasFuzzySearch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
String input = client.getVar(VarClientStr.INPUT_TEXT);
|
||||
|
||||
String underlineTag = "<u=" + ColorUtil.colorToHexCode(FUZZY_HIGHLIGHT_COLOR) + ">";
|
||||
|
||||
Widget results = client.getWidget(WidgetInfo.CHATBOX_GE_SEARCH_RESULTS);
|
||||
Widget[] children = results.getDynamicChildren();
|
||||
int resultCount = children.length / 3;
|
||||
|
||||
for (int i = 0; i < resultCount; i++)
|
||||
{
|
||||
Widget itemNameWidget = children[i * 3 + 1];
|
||||
String itemName = itemNameWidget.getText();
|
||||
|
||||
List<Integer> indices;
|
||||
String otherName = itemName.replace('-', ' ');
|
||||
if (!itemName.contains("-") || FUZZY.fuzzyScore(itemName, input) >= FUZZY.fuzzyScore(otherName, input))
|
||||
{
|
||||
indices = findFuzzyIndices(itemName, input);
|
||||
}
|
||||
else
|
||||
{
|
||||
indices = findFuzzyIndices(otherName, input);
|
||||
}
|
||||
Collections.reverse(indices);
|
||||
|
||||
StringBuilder newItemName = new StringBuilder(itemName);
|
||||
for (int index : indices)
|
||||
{
|
||||
if (itemName.charAt(index) == ' ' || itemName.charAt(index) == '-')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
newItemName.insert(index + 1, "</u>");
|
||||
newItemName.insert(index, underlineTag);
|
||||
}
|
||||
|
||||
itemNameWidget.setText(newItemName.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGrandExchangeSearched(GrandExchangeSearched event)
|
||||
{
|
||||
wasFuzzySearch = false;
|
||||
|
||||
GrandExchangeSearchMode searchMode = config.geSearchMode();
|
||||
final String input = client.getVar(VarClientStr.INPUT_TEXT);
|
||||
if (searchMode == GrandExchangeSearchMode.DEFAULT || input.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
event.consume();
|
||||
|
||||
client.setGeSearchResultIndex(0);
|
||||
|
||||
int resultCount = 0;
|
||||
if (searchMode == GrandExchangeSearchMode.FUZZY_FALLBACK)
|
||||
{
|
||||
List<Integer> ids = IntStream.range(0, client.getItemCount())
|
||||
.mapToObj(itemManager::getItemComposition)
|
||||
.filter(item -> item.isTradeable() && item.getNote() == -1
|
||||
&& item.getName().toLowerCase().contains(input))
|
||||
.limit(MAX_RESULT_COUNT + 1)
|
||||
.sorted(Comparator.comparing(ItemComposition::getName))
|
||||
.map(ItemComposition::getId)
|
||||
.collect(Collectors.toList());
|
||||
if (ids.size() > MAX_RESULT_COUNT)
|
||||
{
|
||||
client.setGeSearchResultCount(-1);
|
||||
client.setGeSearchResultIds(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCount = ids.size();
|
||||
client.setGeSearchResultCount(resultCount);
|
||||
client.setGeSearchResultIds(Shorts.toArray(ids));
|
||||
}
|
||||
}
|
||||
|
||||
if (resultCount == 0)
|
||||
{
|
||||
// We do this so that for example the items "Anti-venom ..." are still at the top
|
||||
// when searching "anti venom"
|
||||
ToIntFunction<ItemComposition> getScore = item ->
|
||||
{
|
||||
int score = FUZZY.fuzzyScore(item.getName(), input);
|
||||
if (item.getName().contains("-"))
|
||||
{
|
||||
return Math.max(FUZZY.fuzzyScore(item.getName().replace('-', ' '), input), score);
|
||||
}
|
||||
return score;
|
||||
};
|
||||
|
||||
List<Integer> ids = IntStream.range(0, client.getItemCount())
|
||||
.mapToObj(itemManager::getItemComposition)
|
||||
.filter(item -> item.isTradeable() && item.getNote() == -1)
|
||||
.filter(item -> getScore.applyAsInt(item) > 0)
|
||||
.sorted(Comparator.comparingInt(getScore).reversed()
|
||||
.thenComparing(ItemComposition::getName))
|
||||
.limit(MAX_RESULT_COUNT)
|
||||
.map(ItemComposition::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
client.setGeSearchResultCount(ids.size());
|
||||
client.setGeSearchResultIds(Shorts.toArray(ids));
|
||||
|
||||
wasFuzzySearch = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onScriptCallbackEvent(ScriptCallbackEvent event)
|
||||
{
|
||||
if (!event.getEventName().equals("setGETitle") || !config.showTotal())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long total = 0;
|
||||
GrandExchangeOffer[] offers = client.getGrandExchangeOffers();
|
||||
for (GrandExchangeOffer offer : offers)
|
||||
{
|
||||
if (offer != null)
|
||||
{
|
||||
total += offer.getPrice() * offer.getTotalQuantity();
|
||||
}
|
||||
}
|
||||
|
||||
if (total == 0L)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder titleBuilder = new StringBuilder(" (");
|
||||
|
||||
if (config.showExact())
|
||||
{
|
||||
titleBuilder.append(QuantityFormatter.formatNumber(total));
|
||||
}
|
||||
else
|
||||
{
|
||||
titleBuilder.append(QuantityFormatter.quantityToStackSize(total));
|
||||
}
|
||||
|
||||
titleBuilder.append(')');
|
||||
|
||||
// Append to title
|
||||
String[] stringStack = client.getStringStack();
|
||||
int stringStackSize = client.getStringStackSize();
|
||||
|
||||
stringStack[stringStackSize - 1] += titleBuilder.toString();
|
||||
}
|
||||
|
||||
private void setLimitResetTime(int itemId)
|
||||
{
|
||||
Instant lastDateTime = configManager.getRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP,
|
||||
BUY_LIMIT_KEY + "." + itemId, Instant.class);
|
||||
if (lastDateTime == null || lastDateTime.isBefore(Instant.now()))
|
||||
{
|
||||
configManager.setRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP, BUY_LIMIT_KEY + "." + itemId,
|
||||
Instant.now().plus(BUY_LIMIT_RESET));
|
||||
}
|
||||
}
|
||||
|
||||
private Instant getLimitResetTime(int itemId)
|
||||
{
|
||||
Instant lastDateTime = configManager.getRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP,
|
||||
BUY_LIMIT_KEY + "." + itemId, Instant.class);
|
||||
if (lastDateTime == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lastDateTime.isBefore(Instant.now()))
|
||||
{
|
||||
configManager.unsetRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP, BUY_LIMIT_KEY + "." + itemId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return lastDateTime;
|
||||
}
|
||||
|
||||
private void updateLimitTimer(GrandExchangeOffer offer)
|
||||
{
|
||||
if (offer.getState() == GrandExchangeOfferState.BOUGHT ||
|
||||
(offer.getQuantitySold() > 0 &&
|
||||
offer.getState() == GrandExchangeOfferState.BUYING))
|
||||
{
|
||||
setLimitResetTime(offer.getItemId());
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildGeText()
|
||||
{
|
||||
if (grandExchangeText == null || grandExchangeItem == null || grandExchangeItem.isHidden())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final Widget geText = grandExchangeText;
|
||||
final int itemId = grandExchangeItem.getItemId();
|
||||
|
||||
if (itemId == OFFER_DEFAULT_ITEM_ID || itemId == -1)
|
||||
{
|
||||
// This item is invalid/nothing has been searched for
|
||||
return;
|
||||
}
|
||||
|
||||
if (geText.getText() == grandExchangeExamine)
|
||||
{
|
||||
// if we've already set the text, don't set it again
|
||||
return;
|
||||
}
|
||||
|
||||
String text = geText.getText();
|
||||
|
||||
if (config.enableGELimits())
|
||||
{
|
||||
final ItemStats itemStats = itemManager.getItemStats(itemId, false);
|
||||
|
||||
// If we have item buy limit, append it
|
||||
if (itemStats != null && itemStats.getGeLimit() > 0)
|
||||
{
|
||||
text += BUY_LIMIT_GE_TEXT + QuantityFormatter.formatNumber(itemStats.getGeLimit());
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enableGELimitReset())
|
||||
{
|
||||
Instant resetTime = getLimitResetTime(itemId);
|
||||
if (resetTime != null)
|
||||
{
|
||||
Duration remaining = Duration.between(Instant.now(), resetTime);
|
||||
text += " (" + DurationFormatUtils.formatDuration(remaining.toMillis(), "H:mm") + ")";
|
||||
}
|
||||
}
|
||||
|
||||
grandExchangeExamine = text;
|
||||
geText.setText(text);
|
||||
|
||||
if (!config.enableOsbPrices())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we already have the result, use it
|
||||
if (osbGrandExchangeResult != null && osbGrandExchangeResult.getItem_id() == itemId && osbGrandExchangeResult.getOverall_average() > 0)
|
||||
{
|
||||
grandExchangeExamine = text + OSB_GE_TEXT + QuantityFormatter.formatNumber(osbGrandExchangeResult.getOverall_average());
|
||||
geText.setText(grandExchangeExamine);
|
||||
}
|
||||
|
||||
if (osbItem == itemId)
|
||||
{
|
||||
// avoid starting duplicate lookups
|
||||
return;
|
||||
}
|
||||
|
||||
osbItem = itemId;
|
||||
|
||||
log.debug("Looking up OSB item price {}", itemId);
|
||||
|
||||
final String start = text;
|
||||
executorService.submit(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
final OSBGrandExchangeResult result = osbGrandExchangeClient.lookupItem(itemId);
|
||||
if (result != null && result.getOverall_average() > 0)
|
||||
{
|
||||
osbGrandExchangeResult = result;
|
||||
// Update the text on the widget too
|
||||
grandExchangeExamine = start + OSB_GE_TEXT + QuantityFormatter.formatNumber(result.getOverall_average());
|
||||
geText.setText(grandExchangeExamine);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
log.debug("Error getting price of item {}", itemId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void openGeLink(String name, int itemId)
|
||||
{
|
||||
final String url = "https://services.runescape.com/m=itemdb_oldschool/"
|
||||
+ name.replaceAll(" ", "+")
|
||||
+ "/viewitem?obj="
|
||||
+ itemId;
|
||||
LinkBrowser.browse(url);
|
||||
}
|
||||
|
||||
private String getMachineUuid()
|
||||
{
|
||||
String username = client.getUsername();
|
||||
if (lastUsername == username)
|
||||
{
|
||||
return machineUuid;
|
||||
}
|
||||
|
||||
lastUsername = username;
|
||||
|
||||
try
|
||||
{
|
||||
Hasher hasher = Hashing.sha256().newHasher();
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
|
||||
hasher.putByte((byte) OSType.getOSType().ordinal());
|
||||
hasher.putByte((byte) runtime.availableProcessors());
|
||||
hasher.putUnencodedChars(System.getProperty("os.arch", ""));
|
||||
hasher.putUnencodedChars(System.getProperty("os.version", ""));
|
||||
hasher.putUnencodedChars(System.getProperty("user.name", ""));
|
||||
|
||||
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (networkInterfaces.hasMoreElements())
|
||||
{
|
||||
NetworkInterface networkInterface = networkInterfaces.nextElement();
|
||||
byte[] hardwareAddress = networkInterface.getHardwareAddress();
|
||||
if (hardwareAddress != null)
|
||||
{
|
||||
hasher.putBytes(hardwareAddress);
|
||||
}
|
||||
}
|
||||
hasher.putUnencodedChars(username);
|
||||
machineUuid = hasher.hash().toString();
|
||||
return machineUuid;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
log.debug("unable to generate machine id", ex);
|
||||
machineUuid = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Dennis <me@dennis.dev>
|
||||
* 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.grandexchange;
|
||||
|
||||
public enum GrandExchangeSearchMode
|
||||
{
|
||||
DEFAULT,
|
||||
|
||||
FUZZY_FALLBACK,
|
||||
|
||||
FUZZY_ONLY
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
|
||||
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||
* 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.grandexchange;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import net.runelite.api.ItemComposition;
|
||||
import net.runelite.client.callback.ClientThread;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.components.IconTextField;
|
||||
import net.runelite.client.ui.components.PluginErrorPanel;
|
||||
import net.runelite.client.util.AsyncBufferedImage;
|
||||
import net.runelite.http.api.item.ItemPrice;
|
||||
import net.runelite.http.api.item.ItemStats;
|
||||
|
||||
/**
|
||||
* This panel holds the search section of the Grand Exchange Plugin.
|
||||
* It should display a search bar and either item results or a error panel.
|
||||
*/
|
||||
class GrandExchangeSearchPanel extends JPanel
|
||||
{
|
||||
private static final String ERROR_PANEL = "ERROR_PANEL";
|
||||
private static final String RESULTS_PANEL = "RESULTS_PANEL";
|
||||
private static final int MAX_SEARCH_ITEMS = 100;
|
||||
|
||||
private final GridBagConstraints constraints = new GridBagConstraints();
|
||||
private final CardLayout cardLayout = new CardLayout();
|
||||
|
||||
private final ClientThread clientThread;
|
||||
private final ItemManager itemManager;
|
||||
private final ScheduledExecutorService executor;
|
||||
|
||||
private final IconTextField searchBar = new IconTextField();
|
||||
|
||||
/* The results container, this will hold all the individual ge item panels */
|
||||
private final JPanel searchItemsPanel = new JPanel();
|
||||
|
||||
/* The center panel, this holds either the error panel or the results container */
|
||||
private final JPanel centerPanel = new JPanel(cardLayout);
|
||||
|
||||
/* The error panel, this displays an error message */
|
||||
private final PluginErrorPanel errorPanel = new PluginErrorPanel();
|
||||
|
||||
private final List<GrandExchangeItems> itemsList = new ArrayList<>();
|
||||
|
||||
GrandExchangeSearchPanel(ClientThread clientThread, ItemManager itemManager, ScheduledExecutorService executor)
|
||||
{
|
||||
this.clientThread = clientThread;
|
||||
this.itemManager = itemManager;
|
||||
this.executor = executor;
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
|
||||
/* The main container, this holds the search bar and the center panel */
|
||||
JPanel container = new JPanel();
|
||||
container.setLayout(new BorderLayout(5, 5));
|
||||
container.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
container.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
|
||||
searchBar.setIcon(IconTextField.Icon.SEARCH);
|
||||
searchBar.setPreferredSize(new Dimension(100, 30));
|
||||
searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR);
|
||||
searchBar.addActionListener(e -> executor.execute(() -> priceLookup(false)));
|
||||
searchBar.addClearListener(this::updateSearch);
|
||||
|
||||
searchItemsPanel.setLayout(new GridBagLayout());
|
||||
searchItemsPanel.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
|
||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
constraints.weightx = 1;
|
||||
constraints.gridx = 0;
|
||||
constraints.gridy = 0;
|
||||
|
||||
/* This panel wraps the results panel and guarantees the scrolling behaviour */
|
||||
JPanel wrapper = new JPanel(new BorderLayout());
|
||||
wrapper.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
wrapper.add(searchItemsPanel, BorderLayout.NORTH);
|
||||
|
||||
/* The results wrapper, this scrolling panel wraps the results container */
|
||||
JScrollPane resultsWrapper = new JScrollPane(wrapper);
|
||||
resultsWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
resultsWrapper.getVerticalScrollBar().setPreferredSize(new Dimension(12, 0));
|
||||
resultsWrapper.getVerticalScrollBar().setBorder(new EmptyBorder(0, 5, 0, 0));
|
||||
resultsWrapper.setVisible(false);
|
||||
|
||||
/* This panel wraps the error panel and limits its height */
|
||||
JPanel errorWrapper = new JPanel(new BorderLayout());
|
||||
errorWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
errorWrapper.add(errorPanel, BorderLayout.NORTH);
|
||||
|
||||
errorPanel.setContent("Grand Exchange Search",
|
||||
"Here you can search for an item by its name to find price information.");
|
||||
|
||||
centerPanel.add(resultsWrapper, RESULTS_PANEL);
|
||||
centerPanel.add(errorWrapper, ERROR_PANEL);
|
||||
|
||||
cardLayout.show(centerPanel, ERROR_PANEL);
|
||||
|
||||
container.add(searchBar, BorderLayout.NORTH);
|
||||
container.add(centerPanel, BorderLayout.CENTER);
|
||||
|
||||
add(container, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
void priceLookup(String item)
|
||||
{
|
||||
searchBar.setText(item);
|
||||
executor.execute(() -> priceLookup(true));
|
||||
}
|
||||
|
||||
private boolean updateSearch()
|
||||
{
|
||||
String lookup = searchBar.getText();
|
||||
|
||||
if (Strings.isNullOrEmpty(lookup))
|
||||
{
|
||||
searchItemsPanel.removeAll();
|
||||
SwingUtilities.invokeLater(searchItemsPanel::updateUI);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Input is not empty, add searching label
|
||||
searchItemsPanel.removeAll();
|
||||
searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
searchBar.setEditable(false);
|
||||
searchBar.setIcon(IconTextField.Icon.LOADING);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void priceLookup(boolean exactMatch)
|
||||
{
|
||||
if (!updateSearch())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<ItemPrice> result = itemManager.search(searchBar.getText());
|
||||
if (result.isEmpty())
|
||||
{
|
||||
searchBar.setIcon(IconTextField.Icon.ERROR);
|
||||
errorPanel.setContent("No results found.", "No items were found with that name, please try again.");
|
||||
cardLayout.show(centerPanel, ERROR_PANEL);
|
||||
searchBar.setEditable(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// move to client thread to lookup item composition
|
||||
clientThread.invokeLater(() -> processResult(result, searchBar.getText(), exactMatch));
|
||||
}
|
||||
|
||||
private void processResult(List<ItemPrice> result, String lookup, boolean exactMatch)
|
||||
{
|
||||
itemsList.clear();
|
||||
|
||||
cardLayout.show(centerPanel, RESULTS_PANEL);
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (ItemPrice item : result)
|
||||
{
|
||||
if (count++ > MAX_SEARCH_ITEMS)
|
||||
{
|
||||
// Cap search
|
||||
break;
|
||||
}
|
||||
|
||||
int itemId = item.getId();
|
||||
|
||||
ItemComposition itemComp = itemManager.getItemComposition(itemId);
|
||||
ItemStats itemStats = itemManager.getItemStats(itemId, false);
|
||||
|
||||
int itemPrice = item.getPrice();
|
||||
int itemLimit = itemStats != null ? itemStats.getGeLimit() : 0;
|
||||
AsyncBufferedImage itemImage = itemManager.getImage(itemId);
|
||||
|
||||
itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice, itemComp.getPrice() * 0.6, itemLimit));
|
||||
|
||||
// If using hotkey to lookup item, stop after finding match.
|
||||
if (exactMatch && item.getName().equalsIgnoreCase(lookup))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
int index = 0;
|
||||
for (GrandExchangeItems item : itemsList)
|
||||
{
|
||||
GrandExchangeItemPanel panel = new GrandExchangeItemPanel(item.getIcon(), item.getName(),
|
||||
item.getItemId(), item.getGePrice(), item.getHaPrice(), item.getGeItemLimit());
|
||||
|
||||
/*
|
||||
Add the first item directly, wrap the rest with margin. This margin hack is because
|
||||
gridbaglayout does not support inter-element margins.
|
||||
*/
|
||||
if (index++ > 0)
|
||||
{
|
||||
JPanel marginWrapper = new JPanel(new BorderLayout());
|
||||
marginWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
marginWrapper.setBorder(new EmptyBorder(5, 0, 0, 0));
|
||||
marginWrapper.add(panel, BorderLayout.NORTH);
|
||||
searchItemsPanel.add(marginWrapper, constraints);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchItemsPanel.add(panel, constraints);
|
||||
}
|
||||
|
||||
constraints.gridy++;
|
||||
}
|
||||
|
||||
// if exactMatch was set, then it came from the applet, so don't lose focus
|
||||
if (!exactMatch)
|
||||
{
|
||||
searchItemsPanel.requestFocusInWindow();
|
||||
}
|
||||
searchBar.setEditable(true);
|
||||
|
||||
// Remove searching label after search is complete
|
||||
if (!itemsList.isEmpty())
|
||||
{
|
||||
searchBar.setIcon(IconTextField.Icon.SEARCH);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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.grandexchange;
|
||||
|
||||
import lombok.Data;
|
||||
import net.runelite.api.GrandExchangeOfferState;
|
||||
|
||||
@Data
|
||||
class SavedOffer
|
||||
{
|
||||
private int itemId;
|
||||
private int quantitySold;
|
||||
private int totalQuantity;
|
||||
private int price;
|
||||
private int spent;
|
||||
private GrandExchangeOfferState state;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@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.grounditems;
|
||||
|
||||
import java.time.Instant;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Value;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
class GroundItem
|
||||
{
|
||||
private int id;
|
||||
private int itemId;
|
||||
private String name;
|
||||
private int quantity;
|
||||
private WorldPoint location;
|
||||
private int height;
|
||||
private int haPrice;
|
||||
private int gePrice;
|
||||
private int offset;
|
||||
private boolean tradeable;
|
||||
@Nonnull
|
||||
private LootType lootType;
|
||||
@Nullable
|
||||
private Instant spawnTime;
|
||||
private boolean stackable;
|
||||
|
||||
int getHaPrice()
|
||||
{
|
||||
return haPrice * quantity;
|
||||
}
|
||||
|
||||
int getGePrice()
|
||||
{
|
||||
return gePrice * quantity;
|
||||
}
|
||||
|
||||
boolean isMine()
|
||||
{
|
||||
return lootType != LootType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class GroundItemKey
|
||||
{
|
||||
private int itemId;
|
||||
private WorldPoint location;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.grounditems;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import javax.inject.Inject;
|
||||
import javax.swing.SwingUtilities;
|
||||
import net.runelite.client.input.KeyListener;
|
||||
import net.runelite.client.input.MouseAdapter;
|
||||
|
||||
public class GroundItemInputListener extends MouseAdapter implements KeyListener
|
||||
{
|
||||
private static final int HOTKEY = KeyEvent.VK_ALT;
|
||||
|
||||
private Instant lastPress;
|
||||
|
||||
@Inject
|
||||
private GroundItemsPlugin plugin;
|
||||
|
||||
@Inject
|
||||
private GroundItemsConfig config;
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e)
|
||||
{
|
||||
if (e.getKeyCode() == HOTKEY)
|
||||
{
|
||||
if (plugin.isHideAll())
|
||||
{
|
||||
plugin.setHideAll(false);
|
||||
plugin.setHotKeyPressed(true);
|
||||
lastPress = null;
|
||||
}
|
||||
else if (lastPress != null && !plugin.isHotKeyPressed() && config.doubleTapDelay() > 0 && Duration.between(lastPress, Instant.now()).compareTo(Duration.ofMillis(config.doubleTapDelay())) < 0)
|
||||
{
|
||||
plugin.setHideAll(true);
|
||||
lastPress = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin.setHotKeyPressed(true);
|
||||
lastPress = Instant.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e)
|
||||
{
|
||||
if (e.getKeyCode() == HOTKEY)
|
||||
{
|
||||
plugin.setHotKeyPressed(false);
|
||||
plugin.setTextBoxBounds(null);
|
||||
plugin.setHiddenBoxBounds(null);
|
||||
plugin.setHighlightBoxBounds(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseEvent mousePressed(MouseEvent e)
|
||||
{
|
||||
final Point mousePos = e.getPoint();
|
||||
|
||||
if (plugin.isHotKeyPressed())
|
||||
{
|
||||
if (SwingUtilities.isLeftMouseButton(e))
|
||||
{
|
||||
// Process both click boxes for hidden and highlighted items
|
||||
if (plugin.getHiddenBoxBounds() != null && plugin.getHiddenBoxBounds().getKey().contains(mousePos))
|
||||
{
|
||||
plugin.updateList(plugin.getHiddenBoxBounds().getValue().getName(), true);
|
||||
e.consume();
|
||||
return e;
|
||||
}
|
||||
|
||||
if (plugin.getHighlightBoxBounds() != null && plugin.getHighlightBoxBounds().getKey().contains(mousePos))
|
||||
{
|
||||
plugin.updateList(plugin.getHighlightBoxBounds().getValue().getName(), false);
|
||||
e.consume();
|
||||
return e;
|
||||
}
|
||||
|
||||
// There is one name click box for left click and one for right click
|
||||
if (plugin.getTextBoxBounds() != null && plugin.getTextBoxBounds().getKey().contains(mousePos))
|
||||
{
|
||||
plugin.updateList(plugin.getTextBoxBounds().getValue().getName(), false);
|
||||
e.consume();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
else if (SwingUtilities.isRightMouseButton(e))
|
||||
{
|
||||
if (plugin.getTextBoxBounds() != null && plugin.getTextBoxBounds().getKey().contains(mousePos))
|
||||
{
|
||||
plugin.updateList(plugin.getTextBoxBounds().getValue().getName(), true);
|
||||
e.consume();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
* 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.grounditems;
|
||||
|
||||
import java.awt.Color;
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
import net.runelite.client.config.Units;
|
||||
import net.runelite.client.config.ConfigSection;
|
||||
import net.runelite.client.plugins.grounditems.config.DespawnTimerMode;
|
||||
import net.runelite.client.plugins.grounditems.config.HighlightTier;
|
||||
import net.runelite.client.plugins.grounditems.config.ItemHighlightMode;
|
||||
import net.runelite.client.plugins.grounditems.config.MenuHighlightMode;
|
||||
import net.runelite.client.plugins.grounditems.config.PriceDisplayMode;
|
||||
import net.runelite.client.plugins.grounditems.config.ValueCalculationMode;
|
||||
|
||||
@ConfigGroup("grounditems")
|
||||
public interface GroundItemsConfig extends Config
|
||||
{
|
||||
@ConfigSection(
|
||||
name = "Item Lists",
|
||||
description = "The highlighted and hidden item lists",
|
||||
position = 0,
|
||||
closedByDefault = true
|
||||
)
|
||||
String itemLists = "itemLists";
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "highlightedItems",
|
||||
name = "Highlighted Items",
|
||||
description = "Configures specifically highlighted ground items. Format: (item), (item)",
|
||||
position = 0,
|
||||
section = itemLists
|
||||
)
|
||||
default String getHighlightItems()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "highlightedItems",
|
||||
name = "",
|
||||
description = ""
|
||||
)
|
||||
void setHighlightedItem(String key);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "hiddenItems",
|
||||
name = "Hidden Items",
|
||||
description = "Configures hidden ground items. Format: (item), (item)",
|
||||
position = 1,
|
||||
section = itemLists
|
||||
)
|
||||
default String getHiddenItems()
|
||||
{
|
||||
return "Vial, Ashes, Coins, Bones, Bucket, Jug, Seaweed";
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "hiddenItems",
|
||||
name = "",
|
||||
description = ""
|
||||
)
|
||||
void setHiddenItems(String key);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showHighlightedOnly",
|
||||
name = "Show Highlighted items only",
|
||||
description = "Configures whether or not to draw items only on your highlighted list",
|
||||
position = 2
|
||||
)
|
||||
default boolean showHighlightedOnly()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "dontHideUntradeables",
|
||||
name = "Do not hide untradeables",
|
||||
description = "Configures whether or not untradeable items ignore hiding under settings",
|
||||
position = 3
|
||||
)
|
||||
default boolean dontHideUntradeables()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showMenuItemQuantities",
|
||||
name = "Show Menu Item Quantities",
|
||||
description = "Configures whether or not to show the item quantities in the menu",
|
||||
position = 4
|
||||
)
|
||||
default boolean showMenuItemQuantities()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "recolorMenuHiddenItems",
|
||||
name = "Recolor Menu Hidden Items",
|
||||
description = "Configures whether or not hidden items in right-click menu will be recolored",
|
||||
position = 5
|
||||
)
|
||||
default boolean recolorMenuHiddenItems()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "highlightTiles",
|
||||
name = "Highlight Tiles",
|
||||
description = "Configures whether or not to highlight tiles containing ground items",
|
||||
position = 6
|
||||
)
|
||||
default boolean highlightTiles()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "notifyHighlightedDrops",
|
||||
name = "Notify for Highlighted drops",
|
||||
description = "Configures whether or not to notify for drops on your highlighted list",
|
||||
position = 7
|
||||
)
|
||||
default boolean notifyHighlightedDrops()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "notifyTier",
|
||||
name = "Notify >= Tier",
|
||||
description = "Configures which price tiers will trigger a notification on drop",
|
||||
position = 8
|
||||
)
|
||||
default HighlightTier notifyTier()
|
||||
{
|
||||
return HighlightTier.OFF;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "priceDisplayMode",
|
||||
name = "Price Display Mode",
|
||||
description = "Configures which price types are shown alongside ground item name",
|
||||
position = 9
|
||||
)
|
||||
default PriceDisplayMode priceDisplayMode()
|
||||
{
|
||||
return PriceDisplayMode.BOTH;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "itemHighlightMode",
|
||||
name = "Item Highlight Mode",
|
||||
description = "Configures how ground items will be highlighted",
|
||||
position = 10
|
||||
)
|
||||
default ItemHighlightMode itemHighlightMode()
|
||||
{
|
||||
return ItemHighlightMode.BOTH;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "menuHighlightMode",
|
||||
name = "Menu Highlight Mode",
|
||||
description = "Configures what to highlight in right-click menu",
|
||||
position = 11
|
||||
)
|
||||
default MenuHighlightMode menuHighlightMode()
|
||||
{
|
||||
return MenuHighlightMode.NAME;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "highlightValueCalculation",
|
||||
name = "Highlight Value Calculation",
|
||||
description = "Configures which coin value is used to determine highlight color",
|
||||
position = 12
|
||||
)
|
||||
default ValueCalculationMode valueCalculationMode()
|
||||
{
|
||||
return ValueCalculationMode.HIGHEST;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "hideUnderValue",
|
||||
name = "Hide < Value",
|
||||
description = "Configures hidden ground items under both GE and HA value",
|
||||
position = 13
|
||||
)
|
||||
default int getHideUnderValue()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "defaultColor",
|
||||
name = "Default items color",
|
||||
description = "Configures the color for default, non-highlighted items",
|
||||
position = 14
|
||||
)
|
||||
default Color defaultColor()
|
||||
{
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "highlightedColor",
|
||||
name = "Highlighted items color",
|
||||
description = "Configures the color for highlighted items",
|
||||
position = 15
|
||||
)
|
||||
default Color highlightedColor()
|
||||
{
|
||||
return Color.decode("#AA00FF");
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "hiddenColor",
|
||||
name = "Hidden items color",
|
||||
description = "Configures the color for hidden items in right-click menu and when holding ALT",
|
||||
position = 16
|
||||
)
|
||||
default Color hiddenColor()
|
||||
{
|
||||
return Color.GRAY;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "lowValueColor",
|
||||
name = "Low value items color",
|
||||
description = "Configures the color for low value items",
|
||||
position = 17
|
||||
)
|
||||
default Color lowValueColor()
|
||||
{
|
||||
return Color.decode("#66B2FF");
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "lowValuePrice",
|
||||
name = "Low value price",
|
||||
description = "Configures the start price for low value items",
|
||||
position = 18
|
||||
)
|
||||
default int lowValuePrice()
|
||||
{
|
||||
return 20000;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "mediumValueColor",
|
||||
name = "Medium value items color",
|
||||
description = "Configures the color for medium value items",
|
||||
position = 19
|
||||
)
|
||||
default Color mediumValueColor()
|
||||
{
|
||||
return Color.decode("#99FF99");
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "mediumValuePrice",
|
||||
name = "Medium value price",
|
||||
description = "Configures the start price for medium value items",
|
||||
position = 20
|
||||
)
|
||||
default int mediumValuePrice()
|
||||
{
|
||||
return 100000;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "highValueColor",
|
||||
name = "High value items color",
|
||||
description = "Configures the color for high value items",
|
||||
position = 21
|
||||
)
|
||||
default Color highValueColor()
|
||||
{
|
||||
return Color.decode("#FF9600");
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "highValuePrice",
|
||||
name = "High value price",
|
||||
description = "Configures the start price for high value items",
|
||||
position = 22
|
||||
)
|
||||
default int highValuePrice()
|
||||
{
|
||||
return 1000000;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "insaneValueColor",
|
||||
name = "Insane value items color",
|
||||
description = "Configures the color for insane value items",
|
||||
position = 23
|
||||
)
|
||||
default Color insaneValueColor()
|
||||
{
|
||||
return Color.decode("#FF66B2");
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "insaneValuePrice",
|
||||
name = "Insane value price",
|
||||
description = "Configures the start price for insane value items",
|
||||
position = 24
|
||||
)
|
||||
default int insaneValuePrice()
|
||||
{
|
||||
return 10000000;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "onlyShowLoot",
|
||||
name = "Only show loot",
|
||||
description = "Only shows drops from NPCs and players",
|
||||
position = 25
|
||||
)
|
||||
default boolean onlyShowLoot()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "doubleTapDelay",
|
||||
name = "Delay for double-tap ALT to hide",
|
||||
description = "Decrease this number if you accidentally hide ground items often. (0 = Disabled)",
|
||||
position = 26
|
||||
)
|
||||
@Units(Units.MILLISECONDS)
|
||||
default int doubleTapDelay()
|
||||
{
|
||||
return 250;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "collapseEntries",
|
||||
name = "Collapse ground item menu entries",
|
||||
description = "Collapses ground item menu entries together and appends count",
|
||||
position = 27
|
||||
)
|
||||
default boolean collapseEntries()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "groundItemTimers",
|
||||
name = "Despawn timer",
|
||||
description = "Shows despawn timers for items you've dropped and received as loot",
|
||||
position = 28
|
||||
)
|
||||
default DespawnTimerMode groundItemTimers()
|
||||
{
|
||||
return DespawnTimerMode.OFF;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "textOutline",
|
||||
name = "Text Outline",
|
||||
description = "Use an outline around text instead of a text shadow",
|
||||
position = 29
|
||||
)
|
||||
default boolean textOutline()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Aria <aria@ar1as.space>
|
||||
* 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.grounditems;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Polygon;
|
||||
import java.awt.Rectangle;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Perspective;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.Point;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import static net.runelite.client.plugins.grounditems.GroundItemsPlugin.MAX_QUANTITY;
|
||||
import net.runelite.client.plugins.grounditems.config.DespawnTimerMode;
|
||||
import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.MENU;
|
||||
import net.runelite.client.plugins.grounditems.config.PriceDisplayMode;
|
||||
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.OverlayUtil;
|
||||
import net.runelite.client.ui.overlay.components.BackgroundComponent;
|
||||
import net.runelite.client.ui.overlay.components.ProgressPieComponent;
|
||||
import net.runelite.client.ui.overlay.components.TextComponent;
|
||||
import net.runelite.client.util.QuantityFormatter;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
public class GroundItemsOverlay extends Overlay
|
||||
{
|
||||
private static final int MAX_DISTANCE = 2500;
|
||||
// We must offset the text on the z-axis such that
|
||||
// it doesn't obscure the ground items below it.
|
||||
private static final int OFFSET_Z = 20;
|
||||
// The 15 pixel gap between each drawn ground item.
|
||||
private static final int STRING_GAP = 15;
|
||||
// Size of the hidden/highlight boxes
|
||||
private static final int RECTANGLE_SIZE = 8;
|
||||
private static final Color PUBLIC_TIMER_COLOR = Color.YELLOW;
|
||||
private static final Color PRIVATE_TIMER_COLOR = Color.GREEN;
|
||||
private static final int TIMER_OVERLAY_DIAMETER = 10;
|
||||
private static final Duration DESPAWN_TIME_INSTANCE = Duration.ofMinutes(30);
|
||||
private static final Duration DESPAWN_TIME_LOOT = Duration.ofMinutes(2);
|
||||
private static final Duration DESPAWN_TIME_DROP = Duration.ofMinutes(3);
|
||||
private static final int KRAKEN_REGION = 9116;
|
||||
private static final int KBD_NMZ_REGION = 9033;
|
||||
|
||||
private final Client client;
|
||||
private final GroundItemsPlugin plugin;
|
||||
private final GroundItemsConfig config;
|
||||
private final StringBuilder itemStringBuilder = new StringBuilder();
|
||||
private final BackgroundComponent backgroundComponent = new BackgroundComponent();
|
||||
private final TextComponent textComponent = new TextComponent();
|
||||
private final ProgressPieComponent progressPieComponent = new ProgressPieComponent();
|
||||
private final Map<WorldPoint, Integer> offsetMap = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
private GroundItemsOverlay(Client client, GroundItemsPlugin plugin, GroundItemsConfig config)
|
||||
{
|
||||
setPosition(OverlayPosition.DYNAMIC);
|
||||
setLayer(OverlayLayer.ABOVE_SCENE);
|
||||
this.client = client;
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
final boolean dontShowOverlay = (config.itemHighlightMode() == MENU || plugin.isHideAll()) && !plugin.isHotKeyPressed();
|
||||
|
||||
if (dontShowOverlay && !config.highlightTiles())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
final FontMetrics fm = graphics.getFontMetrics();
|
||||
final Player player = client.getLocalPlayer();
|
||||
|
||||
if (player == null || client.getViewportWidget() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
offsetMap.clear();
|
||||
final LocalPoint localLocation = player.getLocalLocation();
|
||||
final Point mousePos = client.getMouseCanvasPosition();
|
||||
Collection<GroundItem> groundItemList = plugin.getCollectedGroundItems().values();
|
||||
GroundItem topGroundItem = null;
|
||||
|
||||
if (plugin.isHotKeyPressed())
|
||||
{
|
||||
// Make copy of ground items because we are going to modify them here, and the array list supports our
|
||||
// desired behaviour here
|
||||
groundItemList = new ArrayList<>(groundItemList);
|
||||
final java.awt.Point awtMousePos = new java.awt.Point(mousePos.getX(), mousePos.getY());
|
||||
GroundItem groundItem = null;
|
||||
|
||||
for (GroundItem item : groundItemList)
|
||||
{
|
||||
item.setOffset(offsetMap.compute(item.getLocation(), (k, v) -> v != null ? v + 1 : 0));
|
||||
|
||||
if (groundItem != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (plugin.getTextBoxBounds() != null
|
||||
&& item.equals(plugin.getTextBoxBounds().getValue())
|
||||
&& plugin.getTextBoxBounds().getKey().contains(awtMousePos))
|
||||
{
|
||||
groundItem = item;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (plugin.getHiddenBoxBounds() != null
|
||||
&& item.equals(plugin.getHiddenBoxBounds().getValue())
|
||||
&& plugin.getHiddenBoxBounds().getKey().contains(awtMousePos))
|
||||
{
|
||||
groundItem = item;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (plugin.getHighlightBoxBounds() != null
|
||||
&& item.equals(plugin.getHighlightBoxBounds().getValue())
|
||||
&& plugin.getHighlightBoxBounds().getKey().contains(awtMousePos))
|
||||
{
|
||||
groundItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
if (groundItem != null)
|
||||
{
|
||||
groundItemList.remove(groundItem);
|
||||
groundItemList.add(groundItem);
|
||||
topGroundItem = groundItem;
|
||||
}
|
||||
}
|
||||
|
||||
plugin.setTextBoxBounds(null);
|
||||
plugin.setHiddenBoxBounds(null);
|
||||
plugin.setHighlightBoxBounds(null);
|
||||
|
||||
final boolean onlyShowLoot = config.onlyShowLoot();
|
||||
final DespawnTimerMode groundItemTimers = config.groundItemTimers();
|
||||
final boolean outline = config.textOutline();
|
||||
|
||||
for (GroundItem item : groundItemList)
|
||||
{
|
||||
final LocalPoint groundPoint = LocalPoint.fromWorld(client, item.getLocation());
|
||||
|
||||
if (groundPoint == null || localLocation.distanceTo(groundPoint) > MAX_DISTANCE
|
||||
|| (onlyShowLoot && !item.isMine()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
final Color highlighted = plugin.getHighlighted(new NamedQuantity(item), item.getGePrice(), item.getHaPrice());
|
||||
final Color hidden = plugin.getHidden(new NamedQuantity(item), item.getGePrice(), item.getHaPrice(), item.isTradeable());
|
||||
|
||||
if (highlighted == null && !plugin.isHotKeyPressed())
|
||||
{
|
||||
// Do not display hidden items
|
||||
if (hidden != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not display non-highlighted items
|
||||
if (config.showHighlightedOnly())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final Color color = plugin.getItemColor(highlighted, hidden);
|
||||
|
||||
if (config.highlightTiles())
|
||||
{
|
||||
final Polygon poly = Perspective.getCanvasTilePoly(client, groundPoint, item.getHeight());
|
||||
|
||||
if (poly != null)
|
||||
{
|
||||
OverlayUtil.renderPolygon(graphics, poly, color);
|
||||
}
|
||||
}
|
||||
|
||||
if (dontShowOverlay)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemStringBuilder.append(item.getName());
|
||||
|
||||
if (item.getQuantity() > 1)
|
||||
{
|
||||
if (item.getQuantity() >= MAX_QUANTITY)
|
||||
{
|
||||
itemStringBuilder.append(" (Lots!)");
|
||||
}
|
||||
else
|
||||
{
|
||||
itemStringBuilder.append(" (")
|
||||
.append(QuantityFormatter.quantityToStackSize(item.getQuantity()))
|
||||
.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.priceDisplayMode() == PriceDisplayMode.BOTH)
|
||||
{
|
||||
if (item.getGePrice() > 0)
|
||||
{
|
||||
itemStringBuilder.append(" (GE: ")
|
||||
.append(QuantityFormatter.quantityToStackSize(item.getGePrice()))
|
||||
.append(" gp)");
|
||||
}
|
||||
|
||||
if (item.getHaPrice() > 0)
|
||||
{
|
||||
itemStringBuilder.append(" (HA: ")
|
||||
.append(QuantityFormatter.quantityToStackSize(item.getHaPrice()))
|
||||
.append(" gp)");
|
||||
}
|
||||
}
|
||||
else if (config.priceDisplayMode() != PriceDisplayMode.OFF)
|
||||
{
|
||||
final int price = config.priceDisplayMode() == PriceDisplayMode.GE
|
||||
? item.getGePrice()
|
||||
: item.getHaPrice();
|
||||
|
||||
if (price > 0)
|
||||
{
|
||||
itemStringBuilder
|
||||
.append(" (")
|
||||
.append(QuantityFormatter.quantityToStackSize(price))
|
||||
.append(" gp)");
|
||||
}
|
||||
}
|
||||
|
||||
final String itemString = itemStringBuilder.toString();
|
||||
itemStringBuilder.setLength(0);
|
||||
|
||||
final Point textPoint = Perspective.getCanvasTextLocation(client,
|
||||
graphics,
|
||||
groundPoint,
|
||||
itemString,
|
||||
item.getHeight() + OFFSET_Z);
|
||||
|
||||
if (textPoint == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
final int offset = plugin.isHotKeyPressed()
|
||||
? item.getOffset()
|
||||
: offsetMap.compute(item.getLocation(), (k, v) -> v != null ? v + 1 : 0);
|
||||
|
||||
final int textX = textPoint.getX();
|
||||
final int textY = textPoint.getY() - (STRING_GAP * offset);
|
||||
|
||||
if (plugin.isHotKeyPressed())
|
||||
{
|
||||
final int stringWidth = fm.stringWidth(itemString);
|
||||
final int stringHeight = fm.getHeight();
|
||||
|
||||
// Item bounds
|
||||
int x = textX - 2;
|
||||
int y = textY - stringHeight - 2;
|
||||
int width = stringWidth + 4;
|
||||
int height = stringHeight + 4;
|
||||
final Rectangle itemBounds = new Rectangle(x, y, width, height);
|
||||
|
||||
// Hidden box
|
||||
x += width + 2;
|
||||
y = textY - (RECTANGLE_SIZE + stringHeight) / 2;
|
||||
width = height = RECTANGLE_SIZE;
|
||||
final Rectangle itemHiddenBox = new Rectangle(x, y, width, height);
|
||||
|
||||
// Highlight box
|
||||
x += width + 2;
|
||||
final Rectangle itemHighlightBox = new Rectangle(x, y, width, height);
|
||||
|
||||
boolean mouseInBox = itemBounds.contains(mousePos.getX(), mousePos.getY());
|
||||
boolean mouseInHiddenBox = itemHiddenBox.contains(mousePos.getX(), mousePos.getY());
|
||||
boolean mouseInHighlightBox = itemHighlightBox.contains(mousePos.getX(), mousePos.getY());
|
||||
|
||||
if (mouseInBox)
|
||||
{
|
||||
plugin.setTextBoxBounds(new SimpleEntry<>(itemBounds, item));
|
||||
}
|
||||
else if (mouseInHiddenBox)
|
||||
{
|
||||
plugin.setHiddenBoxBounds(new SimpleEntry<>(itemHiddenBox, item));
|
||||
|
||||
}
|
||||
else if (mouseInHighlightBox)
|
||||
{
|
||||
plugin.setHighlightBoxBounds(new SimpleEntry<>(itemHighlightBox, item));
|
||||
}
|
||||
|
||||
boolean topItem = topGroundItem == item;
|
||||
|
||||
// Draw background if hovering
|
||||
if (topItem && (mouseInBox || mouseInHiddenBox || mouseInHighlightBox))
|
||||
{
|
||||
backgroundComponent.setRectangle(itemBounds);
|
||||
backgroundComponent.render(graphics);
|
||||
}
|
||||
|
||||
// Draw hidden box
|
||||
drawRectangle(graphics, itemHiddenBox, topItem && mouseInHiddenBox ? Color.RED : color, hidden != null, true);
|
||||
|
||||
// Draw highlight box
|
||||
drawRectangle(graphics, itemHighlightBox, topItem && mouseInHighlightBox ? Color.GREEN : color, highlighted != null, false);
|
||||
}
|
||||
|
||||
// When the hotkey is pressed the hidden/highlight boxes are drawn to the right of the text,
|
||||
// so always draw the pie since it is on the left hand side.
|
||||
if (groundItemTimers == DespawnTimerMode.PIE || plugin.isHotKeyPressed())
|
||||
{
|
||||
drawTimerPieOverlay(graphics, textX, textY, item);
|
||||
}
|
||||
else if (groundItemTimers == DespawnTimerMode.SECONDS || groundItemTimers == DespawnTimerMode.TICKS)
|
||||
{
|
||||
Instant despawnTime = calculateDespawnTime(item);
|
||||
Color timerColor = getItemTimerColor(item);
|
||||
if (despawnTime != null && timerColor != null)
|
||||
{
|
||||
long despawnTimeMillis = despawnTime.toEpochMilli() - Instant.now().toEpochMilli();
|
||||
final String timerText;
|
||||
if (groundItemTimers == DespawnTimerMode.SECONDS)
|
||||
{
|
||||
timerText = String.format(" - %.1f", despawnTimeMillis / 1000f);
|
||||
}
|
||||
else // TICKS
|
||||
{
|
||||
timerText = String.format(" - %d", despawnTimeMillis / 600);
|
||||
}
|
||||
|
||||
// The timer text is drawn separately to have its own color, and is intentionally not included
|
||||
// in the getCanvasTextLocation() call because the timer text can change per frame and we do not
|
||||
// use a monospaced font, which causes the text location on screen to jump around slightly each frame.
|
||||
textComponent.setText(timerText);
|
||||
textComponent.setColor(timerColor);
|
||||
textComponent.setOutline(outline);
|
||||
textComponent.setPosition(new java.awt.Point(textX + fm.stringWidth(itemString), textY));
|
||||
textComponent.render(graphics);
|
||||
}
|
||||
}
|
||||
|
||||
textComponent.setText(itemString);
|
||||
textComponent.setColor(color);
|
||||
textComponent.setOutline(outline);
|
||||
textComponent.setPosition(new java.awt.Point(textX, textY));
|
||||
textComponent.render(graphics);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Instant calculateDespawnTime(GroundItem groundItem)
|
||||
{
|
||||
// We can only accurately guess despawn times for our own pvm loot and dropped items
|
||||
if (groundItem.getLootType() != LootType.PVM && groundItem.getLootType() != LootType.DROPPED)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Loot appears to others after 1 minute, and despawns after 2 minutes
|
||||
// Dropped items appear to others after 1 minute, and despawns after 3 minutes
|
||||
// Items in instances never appear to anyone and despawn after 30 minutes
|
||||
|
||||
Instant spawnTime = groundItem.getSpawnTime();
|
||||
if (spawnTime == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Instant despawnTime;
|
||||
Instant now = Instant.now();
|
||||
if (client.isInInstancedRegion())
|
||||
{
|
||||
// Items in the Kraken instance appear to never despawn?
|
||||
if (isInKraken())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (isInKBDorNMZ())
|
||||
{
|
||||
// NMZ and the KBD lair uses the same region ID but NMZ uses planes 1-3 and KBD uses plane 0
|
||||
if (client.getLocalPlayer().getWorldLocation().getPlane() == 0)
|
||||
{
|
||||
// Items in the KBD instance use the standard despawn timer
|
||||
if (groundItem.getLootType() == LootType.DROPPED)
|
||||
{
|
||||
despawnTime = spawnTime.plus(DESPAWN_TIME_DROP);
|
||||
}
|
||||
else
|
||||
{
|
||||
despawnTime = spawnTime.plus(DESPAWN_TIME_LOOT);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dropped items in the NMZ instance appear to never despawn?
|
||||
if (groundItem.getLootType() == LootType.DROPPED)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
despawnTime = spawnTime.plus(DESPAWN_TIME_LOOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
despawnTime = spawnTime.plus(DESPAWN_TIME_INSTANCE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (groundItem.getLootType() == LootType.DROPPED)
|
||||
{
|
||||
despawnTime = spawnTime.plus(DESPAWN_TIME_DROP);
|
||||
}
|
||||
else
|
||||
{
|
||||
despawnTime = spawnTime.plus(DESPAWN_TIME_LOOT);
|
||||
}
|
||||
}
|
||||
|
||||
if (now.isBefore(spawnTime) || now.isAfter(despawnTime))
|
||||
{
|
||||
// that's weird
|
||||
return null;
|
||||
}
|
||||
|
||||
return despawnTime;
|
||||
}
|
||||
|
||||
private Color getItemTimerColor(GroundItem groundItem)
|
||||
{
|
||||
// We can only accurately guess despawn times for our own pvm loot and dropped items
|
||||
if (groundItem.getLootType() != LootType.PVM && groundItem.getLootType() != LootType.DROPPED)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
final Instant spawnTime = groundItem.getSpawnTime();
|
||||
if (spawnTime == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
final Instant now = Instant.now();
|
||||
|
||||
// If it has not yet been a minute, the item is private
|
||||
if (client.isInInstancedRegion() || spawnTime.plus(1, ChronoUnit.MINUTES).isAfter(now))
|
||||
{
|
||||
return PRIVATE_TIMER_COLOR;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PUBLIC_TIMER_COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawTimerPieOverlay(Graphics2D graphics, int textX, int textY, GroundItem groundItem)
|
||||
{
|
||||
Instant now = Instant.now();
|
||||
Instant spawnTime = groundItem.getSpawnTime();
|
||||
Instant despawnTime = calculateDespawnTime(groundItem);
|
||||
Color fillColor = getItemTimerColor(groundItem);
|
||||
|
||||
if (spawnTime == null || despawnTime == null || fillColor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float percent = (float) (now.toEpochMilli() - spawnTime.toEpochMilli()) / (despawnTime.toEpochMilli() - spawnTime.toEpochMilli());
|
||||
|
||||
progressPieComponent.setDiameter(TIMER_OVERLAY_DIAMETER);
|
||||
// Shift over to not be on top of the text
|
||||
int x = textX - TIMER_OVERLAY_DIAMETER;
|
||||
int y = textY - TIMER_OVERLAY_DIAMETER / 2;
|
||||
progressPieComponent.setPosition(new Point(x, y));
|
||||
progressPieComponent.setFill(fillColor);
|
||||
progressPieComponent.setBorderColor(fillColor);
|
||||
progressPieComponent.setProgress(1 - percent); // inverse so pie drains over time
|
||||
progressPieComponent.render(graphics);
|
||||
}
|
||||
|
||||
private void drawRectangle(Graphics2D graphics, Rectangle rect, Color color, boolean inList, boolean hiddenBox)
|
||||
{
|
||||
graphics.setColor(Color.BLACK);
|
||||
graphics.drawRect(rect.x + 1, rect.y + 1, rect.width, rect.height);
|
||||
|
||||
graphics.setColor(color);
|
||||
graphics.draw(rect);
|
||||
|
||||
if (inList)
|
||||
{
|
||||
graphics.fill(rect);
|
||||
}
|
||||
|
||||
graphics.setColor(Color.WHITE);
|
||||
// Minus symbol
|
||||
graphics.drawLine
|
||||
(
|
||||
rect.x + 2,
|
||||
rect.y + (rect.height / 2),
|
||||
rect.x + rect.width - 2,
|
||||
rect.y + (rect.height / 2)
|
||||
);
|
||||
|
||||
if (!hiddenBox)
|
||||
{
|
||||
// Plus symbol
|
||||
graphics.drawLine
|
||||
(
|
||||
rect.x + (rect.width / 2),
|
||||
rect.y + 2,
|
||||
rect.x + (rect.width / 2),
|
||||
rect.y + rect.height - 2
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean isInKraken()
|
||||
{
|
||||
return ArrayUtils.contains(client.getMapRegions(), KRAKEN_REGION);
|
||||
}
|
||||
|
||||
private boolean isInKBDorNMZ()
|
||||
{
|
||||
return ArrayUtils.contains(client.getMapRegions(), KBD_NMZ_REGION);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,691 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Aria <aria@ar1as.space>
|
||||
* 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.grounditems;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.EvictingQueue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.Color;
|
||||
import java.awt.Rectangle;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.ItemComposition;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.MenuAction;
|
||||
import net.runelite.api.MenuEntry;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.Tile;
|
||||
import net.runelite.api.TileItem;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.ClientTick;
|
||||
import net.runelite.api.events.FocusChanged;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.ItemDespawned;
|
||||
import net.runelite.api.events.ItemQuantityChanged;
|
||||
import net.runelite.api.events.ItemSpawned;
|
||||
import net.runelite.api.events.MenuEntryAdded;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
import net.runelite.client.Notifier;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.events.NpcLootReceived;
|
||||
import net.runelite.client.events.PlayerLootReceived;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
import net.runelite.client.game.ItemStack;
|
||||
import net.runelite.client.input.KeyManager;
|
||||
import net.runelite.client.input.MouseManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.plugins.grounditems.config.HighlightTier;
|
||||
import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.OVERLAY;
|
||||
import net.runelite.client.plugins.grounditems.config.MenuHighlightMode;
|
||||
import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.BOTH;
|
||||
import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.NAME;
|
||||
import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.OPTION;
|
||||
import net.runelite.client.ui.overlay.OverlayManager;
|
||||
import net.runelite.client.util.ColorUtil;
|
||||
import net.runelite.client.util.QuantityFormatter;
|
||||
import net.runelite.client.util.Text;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Ground Items",
|
||||
description = "Highlight ground items and/or show price information",
|
||||
tags = {"grand", "exchange", "high", "alchemy", "prices", "highlight", "overlay"}
|
||||
)
|
||||
public class GroundItemsPlugin extends Plugin
|
||||
{
|
||||
@Value
|
||||
static class PriceHighlight
|
||||
{
|
||||
private final int price;
|
||||
private final Color color;
|
||||
}
|
||||
|
||||
// The game won't send anything higher than this value to the plugin -
|
||||
// so we replace any item quantity higher with "Lots" instead.
|
||||
static final int MAX_QUANTITY = 65535;
|
||||
// ItemID for coins
|
||||
private static final int COINS = ItemID.COINS_995;
|
||||
// Ground item menu options
|
||||
private static final int FIRST_OPTION = MenuAction.GROUND_ITEM_FIRST_OPTION.getId();
|
||||
private static final int SECOND_OPTION = MenuAction.GROUND_ITEM_SECOND_OPTION.getId();
|
||||
private static final int THIRD_OPTION = MenuAction.GROUND_ITEM_THIRD_OPTION.getId(); // this is Take
|
||||
private static final int FOURTH_OPTION = MenuAction.GROUND_ITEM_FOURTH_OPTION.getId();
|
||||
private static final int FIFTH_OPTION = MenuAction.GROUND_ITEM_FIFTH_OPTION.getId();
|
||||
private static final int EXAMINE_ITEM = MenuAction.EXAMINE_ITEM_GROUND.getId();
|
||||
private static final int CAST_ON_ITEM = MenuAction.SPELL_CAST_ON_GROUND_ITEM.getId();
|
||||
|
||||
private static final String TELEGRAB_TEXT = ColorUtil.wrapWithColorTag("Telekinetic Grab", Color.GREEN) + ColorUtil.prependColorTag(" -> ", Color.WHITE);
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private Map.Entry<Rectangle, GroundItem> textBoxBounds;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private Map.Entry<Rectangle, GroundItem> hiddenBoxBounds;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private Map.Entry<Rectangle, GroundItem> highlightBoxBounds;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private boolean hotKeyPressed;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private boolean hideAll;
|
||||
|
||||
private List<String> hiddenItemList = new CopyOnWriteArrayList<>();
|
||||
private List<String> highlightedItemsList = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Inject
|
||||
private GroundItemInputListener inputListener;
|
||||
|
||||
@Inject
|
||||
private MouseManager mouseManager;
|
||||
|
||||
@Inject
|
||||
private KeyManager keyManager;
|
||||
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ItemManager itemManager;
|
||||
|
||||
@Inject
|
||||
private OverlayManager overlayManager;
|
||||
|
||||
@Inject
|
||||
private GroundItemsConfig config;
|
||||
|
||||
@Inject
|
||||
private GroundItemsOverlay overlay;
|
||||
|
||||
@Inject
|
||||
private Notifier notifier;
|
||||
|
||||
@Inject
|
||||
private ScheduledExecutorService executor;
|
||||
|
||||
@Getter
|
||||
private final Map<GroundItem.GroundItemKey, GroundItem> collectedGroundItems = new LinkedHashMap<>();
|
||||
private List<PriceHighlight> priceChecks = ImmutableList.of();
|
||||
private LoadingCache<NamedQuantity, Boolean> highlightedItems;
|
||||
private LoadingCache<NamedQuantity, Boolean> hiddenItems;
|
||||
private final Queue<Integer> droppedItemQueue = EvictingQueue.create(16); // recently dropped items
|
||||
|
||||
@Provides
|
||||
GroundItemsConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(GroundItemsConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
overlayManager.add(overlay);
|
||||
mouseManager.registerMouseListener(inputListener);
|
||||
keyManager.registerKeyListener(inputListener);
|
||||
executor.execute(this::reset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
overlayManager.remove(overlay);
|
||||
mouseManager.unregisterMouseListener(inputListener);
|
||||
keyManager.unregisterKeyListener(inputListener);
|
||||
highlightedItems.invalidateAll();
|
||||
highlightedItems = null;
|
||||
hiddenItems.invalidateAll();
|
||||
hiddenItems = null;
|
||||
hiddenItemList = null;
|
||||
highlightedItemsList = null;
|
||||
collectedGroundItems.clear();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConfigChanged(ConfigChanged event)
|
||||
{
|
||||
if (event.getGroup().equals("grounditems"))
|
||||
{
|
||||
executor.execute(this::reset);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameStateChanged(final GameStateChanged event)
|
||||
{
|
||||
if (event.getGameState() == GameState.LOADING)
|
||||
{
|
||||
collectedGroundItems.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onItemSpawned(ItemSpawned itemSpawned)
|
||||
{
|
||||
TileItem item = itemSpawned.getItem();
|
||||
Tile tile = itemSpawned.getTile();
|
||||
|
||||
GroundItem groundItem = buildGroundItem(tile, item);
|
||||
|
||||
GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation());
|
||||
GroundItem existing = collectedGroundItems.putIfAbsent(groundItemKey, groundItem);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.setQuantity(existing.getQuantity() + groundItem.getQuantity());
|
||||
// The spawn time remains set at the oldest spawn
|
||||
}
|
||||
|
||||
if (!config.onlyShowLoot())
|
||||
{
|
||||
notifyHighlightedItem(groundItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onItemDespawned(ItemDespawned itemDespawned)
|
||||
{
|
||||
TileItem item = itemDespawned.getItem();
|
||||
Tile tile = itemDespawned.getTile();
|
||||
|
||||
GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation());
|
||||
GroundItem groundItem = collectedGroundItems.get(groundItemKey);
|
||||
if (groundItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (groundItem.getQuantity() <= item.getQuantity())
|
||||
{
|
||||
collectedGroundItems.remove(groundItemKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
groundItem.setQuantity(groundItem.getQuantity() - item.getQuantity());
|
||||
// When picking up an item when multiple stacks appear on the ground,
|
||||
// it is not known which item is picked up, so we invalidate the spawn
|
||||
// time
|
||||
groundItem.setSpawnTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onItemQuantityChanged(ItemQuantityChanged itemQuantityChanged)
|
||||
{
|
||||
TileItem item = itemQuantityChanged.getItem();
|
||||
Tile tile = itemQuantityChanged.getTile();
|
||||
int oldQuantity = itemQuantityChanged.getOldQuantity();
|
||||
int newQuantity = itemQuantityChanged.getNewQuantity();
|
||||
|
||||
int diff = newQuantity - oldQuantity;
|
||||
GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation());
|
||||
GroundItem groundItem = collectedGroundItems.get(groundItemKey);
|
||||
if (groundItem != null)
|
||||
{
|
||||
groundItem.setQuantity(groundItem.getQuantity() + diff);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNpcLootReceived(NpcLootReceived npcLootReceived)
|
||||
{
|
||||
Collection<ItemStack> items = npcLootReceived.getItems();
|
||||
lootReceived(items, LootType.PVM);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPlayerLootReceived(PlayerLootReceived playerLootReceived)
|
||||
{
|
||||
Collection<ItemStack> items = playerLootReceived.getItems();
|
||||
lootReceived(items, LootType.PVP);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onClientTick(ClientTick event)
|
||||
{
|
||||
if (!config.collapseEntries())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final MenuEntry[] menuEntries = client.getMenuEntries();
|
||||
final List<MenuEntryWithCount> newEntries = new ArrayList<>(menuEntries.length);
|
||||
|
||||
outer:
|
||||
for (int i = menuEntries.length - 1; i >= 0; i--)
|
||||
{
|
||||
MenuEntry menuEntry = menuEntries[i];
|
||||
|
||||
int menuType = menuEntry.getType();
|
||||
if (menuType == FIRST_OPTION || menuType == SECOND_OPTION || menuType == THIRD_OPTION
|
||||
|| menuType == FOURTH_OPTION || menuType == FIFTH_OPTION || menuType == EXAMINE_ITEM)
|
||||
{
|
||||
for (MenuEntryWithCount entryWCount : newEntries)
|
||||
{
|
||||
if (entryWCount.getEntry().equals(menuEntry))
|
||||
{
|
||||
entryWCount.increment();
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newEntries.add(new MenuEntryWithCount(menuEntry));
|
||||
}
|
||||
|
||||
Collections.reverse(newEntries);
|
||||
|
||||
client.setMenuEntries(newEntries.stream().map(e ->
|
||||
{
|
||||
final MenuEntry entry = e.getEntry();
|
||||
final int count = e.getCount();
|
||||
if (count > 1)
|
||||
{
|
||||
entry.setTarget(entry.getTarget() + " x " + count);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}).toArray(MenuEntry[]::new));
|
||||
}
|
||||
|
||||
private void lootReceived(Collection<ItemStack> items, LootType lootType)
|
||||
{
|
||||
for (ItemStack itemStack : items)
|
||||
{
|
||||
WorldPoint location = WorldPoint.fromLocal(client, itemStack.getLocation());
|
||||
GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(itemStack.getId(), location);
|
||||
GroundItem groundItem = collectedGroundItems.get(groundItemKey);
|
||||
if (groundItem != null)
|
||||
{
|
||||
groundItem.setLootType(lootType);
|
||||
|
||||
if (config.onlyShowLoot())
|
||||
{
|
||||
notifyHighlightedItem(groundItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GroundItem buildGroundItem(final Tile tile, final TileItem item)
|
||||
{
|
||||
// Collect the data for the item
|
||||
final int itemId = item.getId();
|
||||
final ItemComposition itemComposition = itemManager.getItemComposition(itemId);
|
||||
final int realItemId = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemId;
|
||||
final int alchPrice = itemComposition.getHaPrice();
|
||||
final boolean dropped = tile.getWorldLocation().equals(client.getLocalPlayer().getWorldLocation()) && droppedItemQueue.remove(itemId);
|
||||
|
||||
final GroundItem groundItem = GroundItem.builder()
|
||||
.id(itemId)
|
||||
.location(tile.getWorldLocation())
|
||||
.itemId(realItemId)
|
||||
.quantity(item.getQuantity())
|
||||
.name(itemComposition.getName())
|
||||
.haPrice(alchPrice)
|
||||
.height(tile.getItemLayer().getHeight())
|
||||
.tradeable(itemComposition.isTradeable())
|
||||
.lootType(dropped ? LootType.DROPPED : LootType.UNKNOWN)
|
||||
.spawnTime(Instant.now())
|
||||
.stackable(itemComposition.isStackable())
|
||||
.build();
|
||||
|
||||
|
||||
// Update item price in case it is coins
|
||||
if (realItemId == COINS)
|
||||
{
|
||||
groundItem.setHaPrice(1);
|
||||
groundItem.setGePrice(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
groundItem.setGePrice(itemManager.getItemPrice(realItemId));
|
||||
}
|
||||
|
||||
return groundItem;
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
// gets the hidden items from the text box in the config
|
||||
hiddenItemList = Text.fromCSV(config.getHiddenItems());
|
||||
|
||||
// gets the highlighted items from the text box in the config
|
||||
highlightedItemsList = Text.fromCSV(config.getHighlightItems());
|
||||
|
||||
highlightedItems = CacheBuilder.newBuilder()
|
||||
.maximumSize(512L)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build(new WildcardMatchLoader(highlightedItemsList));
|
||||
|
||||
hiddenItems = CacheBuilder.newBuilder()
|
||||
.maximumSize(512L)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build(new WildcardMatchLoader(hiddenItemList));
|
||||
|
||||
// Cache colors
|
||||
ImmutableList.Builder<PriceHighlight> priceCheckBuilder = ImmutableList.builder();
|
||||
|
||||
if (config.insaneValuePrice() > 0)
|
||||
{
|
||||
priceCheckBuilder.add(new PriceHighlight(config.insaneValuePrice(), config.insaneValueColor()));
|
||||
}
|
||||
|
||||
if (config.highValuePrice() > 0)
|
||||
{
|
||||
priceCheckBuilder.add(new PriceHighlight(config.highValuePrice(), config.highValueColor()));
|
||||
}
|
||||
|
||||
if (config.mediumValuePrice() > 0)
|
||||
{
|
||||
priceCheckBuilder.add(new PriceHighlight(config.mediumValuePrice(), config.mediumValueColor()));
|
||||
}
|
||||
|
||||
if (config.lowValuePrice() > 0)
|
||||
{
|
||||
priceCheckBuilder.add(new PriceHighlight(config.lowValuePrice(), config.lowValueColor()));
|
||||
}
|
||||
|
||||
priceChecks = priceCheckBuilder.build();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuEntryAdded(MenuEntryAdded event)
|
||||
{
|
||||
if (config.itemHighlightMode() != OVERLAY)
|
||||
{
|
||||
final boolean telegrabEntry = event.getOption().equals("Cast") && event.getTarget().startsWith(TELEGRAB_TEXT) && event.getType() == CAST_ON_ITEM;
|
||||
if (!(event.getOption().equals("Take") && event.getType() == THIRD_OPTION) && !telegrabEntry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final int itemId = event.getIdentifier();
|
||||
final int sceneX = event.getActionParam0();
|
||||
final int sceneY = event.getActionParam1();
|
||||
|
||||
MenuEntry[] menuEntries = client.getMenuEntries();
|
||||
MenuEntry lastEntry = menuEntries[menuEntries.length - 1];
|
||||
|
||||
final WorldPoint worldPoint = WorldPoint.fromScene(client, sceneX, sceneY, client.getPlane());
|
||||
GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(itemId, worldPoint);
|
||||
GroundItem groundItem = collectedGroundItems.get(groundItemKey);
|
||||
int quantity = groundItem.getQuantity();
|
||||
|
||||
final int gePrice = groundItem.getGePrice();
|
||||
final int haPrice = groundItem.getHaPrice();
|
||||
final Color hidden = getHidden(new NamedQuantity(groundItem.getName(), quantity), gePrice, haPrice, groundItem.isTradeable());
|
||||
final Color highlighted = getHighlighted(new NamedQuantity(groundItem.getName(), quantity), gePrice, haPrice);
|
||||
final Color color = getItemColor(highlighted, hidden);
|
||||
final boolean canBeRecolored = highlighted != null || (hidden != null && config.recolorMenuHiddenItems());
|
||||
|
||||
if (color != null && canBeRecolored && !color.equals(config.defaultColor()))
|
||||
{
|
||||
final MenuHighlightMode mode = config.menuHighlightMode();
|
||||
|
||||
if (mode == BOTH || mode == OPTION)
|
||||
{
|
||||
final String optionText = telegrabEntry ? "Cast" : "Take";
|
||||
lastEntry.setOption(ColorUtil.prependColorTag(optionText, color));
|
||||
}
|
||||
|
||||
if (mode == BOTH || mode == NAME)
|
||||
{
|
||||
String target = lastEntry.getTarget();
|
||||
|
||||
if (telegrabEntry)
|
||||
{
|
||||
target = target.substring(TELEGRAB_TEXT.length());
|
||||
}
|
||||
|
||||
target = ColorUtil.prependColorTag(target.substring(target.indexOf('>') + 1), color);
|
||||
|
||||
if (telegrabEntry)
|
||||
{
|
||||
target = TELEGRAB_TEXT + target;
|
||||
}
|
||||
|
||||
lastEntry.setTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.showMenuItemQuantities() && groundItem.isStackable() && quantity > 1)
|
||||
{
|
||||
lastEntry.setTarget(lastEntry.getTarget() + " (" + quantity + ")");
|
||||
}
|
||||
|
||||
client.setMenuEntries(menuEntries);
|
||||
}
|
||||
}
|
||||
|
||||
void updateList(String item, boolean hiddenList)
|
||||
{
|
||||
final List<String> hiddenItemSet = new ArrayList<>(hiddenItemList);
|
||||
final List<String> highlightedItemSet = new ArrayList<>(highlightedItemsList);
|
||||
|
||||
if (hiddenList)
|
||||
{
|
||||
highlightedItemSet.removeIf(item::equalsIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
hiddenItemSet.removeIf(item::equalsIgnoreCase);
|
||||
}
|
||||
|
||||
final List<String> items = hiddenList ? hiddenItemSet : highlightedItemSet;
|
||||
|
||||
if (!items.removeIf(item::equalsIgnoreCase))
|
||||
{
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
config.setHiddenItems(Text.toCSV(hiddenItemSet));
|
||||
config.setHighlightedItem(Text.toCSV(highlightedItemSet));
|
||||
}
|
||||
|
||||
Color getHighlighted(NamedQuantity item, int gePrice, int haPrice)
|
||||
{
|
||||
if (TRUE.equals(highlightedItems.getUnchecked(item)))
|
||||
{
|
||||
return config.highlightedColor();
|
||||
}
|
||||
|
||||
// Explicit hide takes priority over implicit highlight
|
||||
if (TRUE.equals(hiddenItems.getUnchecked(item)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
final int price = getValueByMode(gePrice, haPrice);
|
||||
for (PriceHighlight highlight : priceChecks)
|
||||
{
|
||||
if (price > highlight.getPrice())
|
||||
{
|
||||
return highlight.getColor();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Color getHidden(NamedQuantity item, int gePrice, int haPrice, boolean isTradeable)
|
||||
{
|
||||
final boolean isExplicitHidden = TRUE.equals(hiddenItems.getUnchecked(item));
|
||||
final boolean isExplicitHighlight = TRUE.equals(highlightedItems.getUnchecked(item));
|
||||
final boolean canBeHidden = gePrice > 0 || isTradeable || !config.dontHideUntradeables();
|
||||
final boolean underGe = gePrice < config.getHideUnderValue();
|
||||
final boolean underHa = haPrice < config.getHideUnderValue();
|
||||
|
||||
// Explicit highlight takes priority over implicit hide
|
||||
return isExplicitHidden || (!isExplicitHighlight && canBeHidden && underGe && underHa)
|
||||
? config.hiddenColor()
|
||||
: null;
|
||||
}
|
||||
|
||||
Color getItemColor(Color highlighted, Color hidden)
|
||||
{
|
||||
if (highlighted != null)
|
||||
{
|
||||
return highlighted;
|
||||
}
|
||||
|
||||
if (hidden != null)
|
||||
{
|
||||
return hidden;
|
||||
}
|
||||
|
||||
return config.defaultColor();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFocusChanged(FocusChanged focusChanged)
|
||||
{
|
||||
if (!focusChanged.isFocused())
|
||||
{
|
||||
setHotKeyPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyHighlightedItem(GroundItem item)
|
||||
{
|
||||
final boolean shouldNotifyHighlighted = config.notifyHighlightedDrops() &&
|
||||
TRUE.equals(highlightedItems.getUnchecked(new NamedQuantity(item)));
|
||||
|
||||
final boolean shouldNotifyTier = config.notifyTier() != HighlightTier.OFF &&
|
||||
getValueByMode(item.getGePrice(), item.getHaPrice()) > config.notifyTier().getValueFromTier(config) &&
|
||||
FALSE.equals(hiddenItems.getUnchecked(new NamedQuantity(item)));
|
||||
|
||||
final String dropType;
|
||||
if (shouldNotifyHighlighted)
|
||||
{
|
||||
dropType = "highlighted";
|
||||
}
|
||||
else if (shouldNotifyTier)
|
||||
{
|
||||
dropType = "valuable";
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final Player local = client.getLocalPlayer();
|
||||
final StringBuilder notificationStringBuilder = new StringBuilder()
|
||||
.append('[')
|
||||
.append(local.getName())
|
||||
.append("] received a ")
|
||||
.append(dropType)
|
||||
.append(" drop: ")
|
||||
.append(item.getName());
|
||||
|
||||
if (item.getQuantity() > 1)
|
||||
{
|
||||
if (item.getQuantity() >= MAX_QUANTITY)
|
||||
{
|
||||
notificationStringBuilder.append(" (Lots!)");
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationStringBuilder.append(" (")
|
||||
.append(QuantityFormatter.quantityToStackSize(item.getQuantity()))
|
||||
.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
notifier.notify(notificationStringBuilder.toString());
|
||||
}
|
||||
|
||||
private int getValueByMode(int gePrice, int haPrice)
|
||||
{
|
||||
switch (config.valueCalculationMode())
|
||||
{
|
||||
case GE:
|
||||
return gePrice;
|
||||
case HA:
|
||||
return haPrice;
|
||||
default: // Highest
|
||||
return Math.max(gePrice, haPrice);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked)
|
||||
{
|
||||
if (menuOptionClicked.getMenuAction() == MenuAction.ITEM_DROP)
|
||||
{
|
||||
int itemId = menuOptionClicked.getId();
|
||||
// Keep a queue of recently dropped items to better detect
|
||||
// item spawns that are drops
|
||||
droppedItemQueue.add(itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2020, dekvall <https://github.com/dekvall>
|
||||
* 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.grounditems;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
class ItemThreshold
|
||||
{
|
||||
enum Inequality
|
||||
{
|
||||
LESS_THAN,
|
||||
MORE_THAN
|
||||
}
|
||||
|
||||
private final String itemName;
|
||||
private final int quantity;
|
||||
private final Inequality inequality;
|
||||
|
||||
static ItemThreshold fromConfigEntry(String entry)
|
||||
{
|
||||
if (Strings.isNullOrEmpty(entry))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Inequality operator = Inequality.MORE_THAN;
|
||||
int qty = 0;
|
||||
|
||||
for (int i = entry.length() - 1; i >= 0; i--)
|
||||
{
|
||||
char c = entry.charAt(i);
|
||||
if (c >= '0' && c <= '9' || Character.isWhitespace(c))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
switch (c)
|
||||
{
|
||||
case '<':
|
||||
operator = Inequality.LESS_THAN;
|
||||
// fallthrough
|
||||
case '>':
|
||||
if (i + 1 < entry.length())
|
||||
{
|
||||
try
|
||||
{
|
||||
qty = Integer.parseInt(entry.substring(i + 1).trim());
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
qty = 0;
|
||||
operator = Inequality.MORE_THAN;
|
||||
}
|
||||
entry = entry.substring(0, i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return new ItemThreshold(entry.trim(), qty, operator);
|
||||
}
|
||||
|
||||
boolean quantityHolds(int itemCount)
|
||||
{
|
||||
if (inequality == Inequality.LESS_THAN)
|
||||
{
|
||||
return itemCount < quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
return itemCount > quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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.grounditems;
|
||||
|
||||
enum LootType
|
||||
{
|
||||
UNKNOWN,
|
||||
DROPPED,
|
||||
PVP,
|
||||
PVM;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user