First commit of group-ironmen-tracker-master

This commit is contained in:
2025-10-27 08:28:05 +08:00
parent a8467389ef
commit b80f09a4ea
32 changed files with 2249 additions and 2 deletions

View File

@@ -0,0 +1,78 @@
package men.groupiron;
import net.runelite.api.Client;
public class AchievementDiaryState implements ConsumableState {
private final transient String playerName;
private static final int[] diaryVarbits = new int[]{
/* Karamja Easy */
3566, 3567, 3568, 3569, 3570, 3571, 3572, 3573, 3574, 3575,
/* Karamja Medium */
3579, 3580, 3581, 3582, 3583, 3584, 3596, 3586, 3587, 3588, 3589, 3590, 3591, 3592, 3593, 3594, 3595, 3597, 3585,
/* Karamja Hard */
3600, 3601, 3602, 3603, 3604, 3605, 3606, 3607, 3608, 3609
};
private static final int[] diaryVarps = new int[]{
/* Ardougne */
1196, 1197,
/* Desert */
1198, 1199,
/* Falador */
1186, 1187,
/* Fremennik */
1184, 1185,
/* Kandarin */
1178, 1179,
/* Karamja Elite */
1200,
/* Kourend & Kebos */
2085, 2086,
/* Lumbridge & Draynor */
1194, 1195,
/* Morytania */
1180, 1181,
/* Varrock */
1176, 1177,
/* Western Provinces */
1182, 1183,
/* Wilderness */
1192, 1193
};
private final int[] diaryVarValues = new int[diaryVarbits.length + diaryVarps.length];
public AchievementDiaryState(String playerName, Client client) {
this.playerName = playerName;
for (int i = 0; i < diaryVarps.length; ++i) {
diaryVarValues[i] = client.getVarpValue(diaryVarps[i]);
}
for (int i = 0; i < diaryVarbits.length; ++i) {
diaryVarValues[i + diaryVarps.length] = client.getVarbitValue(diaryVarbits[i]);
}
}
@Override
public Object get() {
return diaryVarValues;
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof AchievementDiaryState)) return false;
AchievementDiaryState other = (AchievementDiaryState) o;
for (int i = 0; i < diaryVarValues.length; ++i) {
if (diaryVarValues[i] != other.diaryVarValues[i]) return false;
}
return true;
}
}

View File

@@ -0,0 +1,174 @@
package men.groupiron;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.EnumComposition;
import net.runelite.api.ItemComposition;
import net.runelite.api.StructComposition;
import net.runelite.api.widgets.Widget;
import net.runelite.api.gameval.InterfaceID;
import net.runelite.client.game.ItemManager;
import net.runelite.client.util.Text;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
@Singleton
public class CollectionLogManager {
@Inject
Client client;
@Inject
ItemManager itemManager;
private final Map<String, DataState> collections = new HashMap<>();
private String playerName;
private Set<String> consumedNewItems = null;
private Set<String> newItems = null;
private static final int collectionLogTabVarbit = 6905;
private static final int collectionLogPageVarbit = 6906;
static final Pattern COLLECTION_LOG_COUNT_PATTERN = Pattern.compile(".+:(.+)");
static Map<String, Set<Integer>> pageItems;
static Map<Integer, Map<Integer, String>> pageNameLookup;
private static final List<Integer> COLLECTION_LOG_TAB_STRUCT_IDS = ImmutableList.of(
471, // Bosses
472, // Raids
473, // Clues
474, // Minigames
475 // Other
);
private static final int COLLECTION_LOG_PAGE_NAME_PARAM_ID = 689;
private static final int COLLECTION_LOG_TAB_ENUM_PARAM_ID = 683;
private static final int COLLECTION_LOG_PAGE_ITEMS_ENUM_PARAM_ID = 690;
public void initCollectionLog()
{
// NOTE: varbit 6905 gives us the selected collection log tab index and 6906 is the selected page index.
// In here we build a lookup map which will give us the page name with the tab index and the page index.
// This should be better than pulling the page name from the widget since that value can be changed by
// other runelite plugins.
// We also create a lookup of the item ids to the page name which should be better than using the item id
// in the collection log window as these will match the ids in the container state change.
pageItems = new HashMap<>();
pageNameLookup = new HashMap<>();
int tabIdx = 0;
for (Integer structId : COLLECTION_LOG_TAB_STRUCT_IDS) {
StructComposition tabStruct = client.getStructComposition(structId);
int tabEnumId = tabStruct.getIntValue(COLLECTION_LOG_TAB_ENUM_PARAM_ID);
EnumComposition tabEnum = client.getEnum(tabEnumId);
Map<Integer, String> pageIdToName = pageNameLookup.computeIfAbsent(tabIdx, k -> new HashMap<>());
int pageIdx = 0;
for (Integer pageStructId : tabEnum.getIntVals()) {
StructComposition pageStruct = client.getStructComposition(pageStructId);
String pageName = pageStruct.getStringValue(COLLECTION_LOG_PAGE_NAME_PARAM_ID);
int pageItemsEnumId = pageStruct.getIntValue(COLLECTION_LOG_PAGE_ITEMS_ENUM_PARAM_ID);
EnumComposition pageItemsEnum = client.getEnum(pageItemsEnumId);
pageIdToName.put(pageIdx, pageName);
Set<Integer> items = pageItems.computeIfAbsent(pageName, k -> new HashSet<>());
for (Integer pageItemId : pageItemsEnum.getIntVals()) {
ItemComposition itemComposition = itemManager.getItemComposition(pageItemId);
items.add(itemComposition.getId());
}
++pageIdx;
}
++tabIdx;
}
}
public void updateCollection(ItemContainerState containerState) {
Widget collectionLogHeader = client.getWidget(InterfaceID.Collection.HEADER_TEXT);
if (collectionLogHeader == null || collectionLogHeader.isHidden()) return;
Widget[] collectionLogHeaderChildren = collectionLogHeader.getChildren();
if (collectionLogHeaderChildren == null || collectionLogHeaderChildren.length == 0) return;
// Get the completion count information from the lines in the collection log header
List<Integer> completionCounts = new ArrayList<>();
for (int i = 2; i < collectionLogHeaderChildren.length; ++i) {
String text = Text.removeTags(collectionLogHeaderChildren[i].getText());
Matcher matcher = COLLECTION_LOG_COUNT_PATTERN.matcher(text);
if (matcher.find()) {
try {
Integer count = Integer.valueOf(matcher.group(1).trim());
completionCounts.add(count);
} catch(Exception ignored) {}
}
}
int tabIdx = client.getVarbitValue(collectionLogTabVarbit);
int pageIdx = client.getVarbitValue(collectionLogPageVarbit);
String pageName = getPageName(tabIdx, pageIdx);
if (!StringUtils.isBlank(pageName)) {
// Sending the tab index just in case the page name is not unique across them
DataState pageDataState = collections.computeIfAbsent(pageName + tabIdx, k -> new DataState());
pageDataState.update(new CollectionPageState(tabIdx, pageName, containerState, completionCounts));
}
}
private String getPageName(int tabIdx, int pageIdx) {
Map<Integer, String> x = pageNameLookup.get(tabIdx);
if (x != null) return x.get(pageIdx);
return null;
}
public synchronized void updateNewItem(String item) {
String playerName = client.getLocalPlayer().getName();
if (playerName != null) {
if (!playerName.equals(this.playerName) || newItems == null) {
this.playerName = playerName;
newItems = new HashSet<>();
}
newItems.add(item.trim());
}
}
public synchronized void consumeNewItems(Map<String, Object> output) {
if (newItems != null && output.get("name").equals(this.playerName)) {
output.put("collection_log_new", newItems);
}
consumedNewItems = newItems;
newItems = null;
}
public void consumeCollections(Map<String, Object> output) {
if (collections.isEmpty()) return;
List<Object> collectionLogOutput = new ArrayList<>();
String whoIsUpdating = (String) output.get("name");
for (DataState pageDataState : collections.values()) {
Object result = pageDataState.consumeState(whoIsUpdating);
if (result != null) {
collectionLogOutput.add(result);
}
}
// log.info("collectionLogOutput={}", collectionLogOutput);
if (!collectionLogOutput.isEmpty()) {
output.put("collection_log", collectionLogOutput);
}
}
public void restoreCollections() {
for (DataState pageDataState : collections.values()) {
pageDataState.restoreState();
}
}
public synchronized void restoreNewCollections() {
if (consumedNewItems == null) return;
for (String item : consumedNewItems) {
updateNewItem(item);
}
consumedNewItems = null;
}
}

View File

@@ -0,0 +1,42 @@
package men.groupiron;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.GameState;
import net.runelite.api.events.GameStateChanged;
import net.runelite.client.eventbus.Subscribe;
@Slf4j
@Singleton
public class CollectionLogV2Manager {
// item id -> quantity
private final Map<Integer, Integer> clogItems = new HashMap<>();
public synchronized void storeClogItem(int itemId, int quantity) {
if (quantity <= 0) return;
clogItems.put(itemId, quantity);
}
public synchronized void consumeClogItems(Map<String, Object> updates) {
if (clogItems.isEmpty()) return;
updates.put("collection_log_v2", new HashMap<>(clogItems));
}
public synchronized void clearClogItems() {
clogItems.clear();
}
@Subscribe
public synchronized void onGameStateChanged(GameStateChanged ev) {
if (ev.getGameState() != GameState.LOGGED_IN) {
clogItems.clear();
}
}
public synchronized Map<Integer, Integer> snapshotItems() {
return Collections.unmodifiableMap(new HashMap<>(clogItems));
}
}

View File

@@ -0,0 +1,95 @@
package men.groupiron;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.MenuAction;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ScriptPostFired;
import net.runelite.api.events.ScriptPreFired;
import net.runelite.api.gameval.InterfaceID;
import net.runelite.api.gameval.VarbitID;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
@Slf4j
@Singleton
public class CollectionLogWidgetSubscriber {
@Inject
private EventBus eventBus;
@Inject
private Client client;
@Inject
private CollectionLogV2Manager collectionLogV2Manager;
private boolean searchTriggered = false;
private int searchTriggeredTick = -1;
public void startUp() {
eventBus.register(this);
}
public void shutDown() {
eventBus.unregister(this);
}
@Subscribe
public void onGameStateChanged(GameStateChanged e) {
GameState s = e.getGameState();
if (s != GameState.HOPPING && s != GameState.LOGGED_IN) {
searchTriggered = false;
searchTriggeredTick = -1;
}
}
@Subscribe
public void onGameTick(GameTick tick) {
if (searchTriggeredTick != -1) {
int currentTick = client.getTickCount();
if (currentTick - searchTriggeredTick >= 500) {
searchTriggered = false;
searchTriggeredTick = -1;
}
}
}
@Subscribe
public void onScriptPreFired(ScriptPreFired pre) {
// Script 4100 fires when collection log items are enumerated via search
if (pre.getScriptId() == 4100) {
// Arguments: [widgetId, itemId, qty]
Object[] args = pre.getScriptEvent().getArguments();
if (args != null && args.length >= 3) {
try {
int itemId = (int) args[1];
int quantity = (int) args[2];
collectionLogV2Manager.storeClogItem(itemId, quantity);
} catch (Exception ignored) {
//
}
}
}
}
@Subscribe
public void onScriptPostFired(ScriptPostFired post) {
final int COLLECTION_LOG_SETUP = 7797;
if (post.getScriptId() == COLLECTION_LOG_SETUP) {
if (searchTriggered) return;
boolean isAdventureLog = client.getVarbitValue(VarbitID.COLLECTION_POH_HOST_BOOK_OPEN) == 1;
if (isAdventureLog) return;
searchTriggered = true;
searchTriggeredTick = client.getTickCount();
client.menuAction(-1, InterfaceID.Collection.SEARCH_TOGGLE, MenuAction.CC_OP, 1, -1, "Search", null);
final int COLLECTION_INIT = 2240;
client.runScript(COLLECTION_INIT);
}
}
}

View File

@@ -0,0 +1,48 @@
package men.groupiron;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CollectionPageState implements ConsumableState {
private final ItemContainerState items;
private final String pageName;
private final int tabIdx;
private final List<Integer> completionCounts;
public CollectionPageState(int tabIdx, String pageName, ItemContainerState items, List<Integer> completionCounts) {
this.tabIdx = tabIdx;
this.pageName = pageName;
this.items = items;
this.completionCounts = completionCounts;
}
@Override
public Object get() {
Map<String, Object> result = new HashMap<>();
result.put("tab", tabIdx);
result.put("page_name", pageName);
result.put("items", items.get());
result.put("completion_counts", completionCounts);
return result;
}
@Override
public String whoOwnsThis() { return items.whoOwnsThis(); }
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof CollectionPageState)) return false;
CollectionPageState other = (CollectionPageState) o;
boolean completionCountsEqual = completionCounts.size() == other.completionCounts.size();
if (completionCountsEqual) {
for (int i = 0; i < completionCounts.size(); ++i) {
completionCountsEqual = completionCounts.get(i).equals(other.completionCounts.get(i));
if (!completionCountsEqual) break;
}
}
return (completionCountsEqual && tabIdx == other.tabIdx && pageName.equals(other.pageName) && items.equals(other.items));
}
}

View File

@@ -0,0 +1,7 @@
package men.groupiron;
public interface ConsumableState {
Object get();
String whoOwnsThis();
}

View File

@@ -0,0 +1,199 @@
package men.groupiron;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.WorldType;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Singleton
public class DataManager {
@Inject
Client client;
@Inject
GroupIronmenTrackerConfig config;
@Inject
private CollectionLogManager collectionLogManager;
@Inject
private CollectionLogV2Manager collectionLogV2Manager;
@Inject
private HttpRequestService httpRequestService;
private boolean isMemberInGroup = false;
private int skipNextNAttempts = 0;
@Getter
private final DataState inventory = new DataState("inventory", false);
@Getter
private final DataState bank = new DataState("bank", false);
@Getter
private final DataState equipment = new DataState("equipment", false);
@Getter
private final DataState sharedBank = new DataState("shared_bank", true);
@Getter
private final DataState resources = new DataState("stats", false);
@Getter
private final DataState skills = new DataState("skills", false);
@Getter
private final DataState quests = new DataState("quests", false);
@Getter
private final DataState position = new DataState("coordinates", false);
@Getter
private final DataState runePouch = new DataState("rune_pouch", false);
@Getter
private final DataState quiver = new DataState("quiver", false);
@Getter
private final DataState interacting = new DataState("interacting", false);
@Getter
private final DataState seedVault = new DataState("seed_vault", false);
@Getter
private final DataState achievementDiary = new DataState("diary_vars", false);
@Getter
private final DepositedItems deposited = new DepositedItems();
public void submitToApi() {
if (client.getLocalPlayer() == null || client.getLocalPlayer().getName() == null || isBadWorldType()) return;
if (skipNextNAttempts-- > 0) return;
String playerName = client.getLocalPlayer().getName();
String groupToken = config.authorizationToken().trim();
if (groupToken.length() > 0) {
// NOTE: We do this check so characters who are not authorized won't waste time serializing and sending
// their data. It is OK if the user switches characters or is removed from the group since the update call
// below will return a 401 where we set isMemberOfGroup = false again.
if (!isMemberInGroup) {
boolean isMember = checkIfPlayerIsInGroup(groupToken, playerName);
if (!isMember) {
// NOTE: We don't really need to check this everytime I don't think.
// Waiting for a game state event is not what we really want either
// since membership can change at anytime from the website.
skipNextNAttempts = 10;
return;
}
isMemberInGroup = true;
}
String url = getUpdateGroupMemberUrl();
if (url == null) return;
Map<String, Object> updates = new HashMap<>();
updates.put("name", playerName);
inventory.consumeState(updates);
bank.consumeState(updates);
equipment.consumeState(updates);
sharedBank.consumeState(updates);
resources.consumeState(updates);
skills.consumeState(updates);
quests.consumeState(updates);
position.consumeState(updates);
runePouch.consumeState(updates);
quiver.consumeState(updates);
interacting.consumeState(updates);
deposited.consumeState(updates);
seedVault.consumeState(updates);
achievementDiary.consumeState(updates);
collectionLogManager.consumeCollections(updates);
collectionLogManager.consumeNewItems(updates);
collectionLogV2Manager.consumeClogItems(updates);
if (updates.size() > 1) {
HttpRequestService.HttpResponse response = httpRequestService.post(url, groupToken, updates);
if (!response.isSuccessful()) {
skipNextNAttempts = 10;
if (response.getCode() == 401) {
isMemberInGroup = false;
}
restoreStateIfNothingUpdated();
} else {
collectionLogV2Manager.clearClogItems();
}
} else {
log.debug("Skip POST: no changes to send (fields={})", updates.size());
}
}
}
private boolean checkIfPlayerIsInGroup(String groupToken, String playerName) {
String url = amIMemberOfGroupUrl(playerName);
if (url == null) return false;
HttpRequestService.HttpResponse response = httpRequestService.get(url, groupToken);
return response.isSuccessful();
}
// NOTE: These states should only be restored if a new update did not come in at some point before calling this
private void restoreStateIfNothingUpdated() {
inventory.restoreState();
bank.restoreState();
equipment.restoreState();
sharedBank.restoreState();
resources.restoreState();
skills.restoreState();
quests.restoreState();
position.restoreState();
runePouch.restoreState();
quiver.restoreState();
interacting.restoreState();
deposited.restoreState();
seedVault.restoreState();
achievementDiary.restoreState();
// collectionLogManager.restoreCollections();
// collectionLogManager.restoreNewCollections();
}
private String baseUrl() {
return httpRequestService.getBaseUrl();
}
private String groupName() {
String groupName = config.groupName().trim();
if (groupName.length() == 0) {
return null;
}
return groupName;
}
private String getUpdateGroupMemberUrl() {
String baseUrl = baseUrl();
String groupName = groupName();
if (baseUrl == null || groupName == null) return null;
return String.format("%s/api/group/%s/update-group-member", baseUrl, groupName);
}
private String amIMemberOfGroupUrl(String playerName) {
String baseUrl = baseUrl();
String groupName = groupName();
if (baseUrl == null || groupName == null) return null;
return String.format("%s/api/group/%s/am-i-in-group?member_name=%s", baseUrl, groupName, playerName);
}
private boolean isBadWorldType() {
EnumSet<WorldType> worldTypes = client.getWorldType();
for (WorldType worldType : worldTypes) {
if (worldType == WorldType.SEASONAL ||
worldType == WorldType.DEADMAN ||
worldType == WorldType.TOURNAMENT_WORLD ||
worldType == WorldType.PVP_ARENA ||
worldType == WorldType.BETA_WORLD ||
worldType == WorldType.QUEST_SPEEDRUNNING) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,66 @@
package men.groupiron;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
public class DataState {
private final AtomicReference<ConsumableState> state = new AtomicReference<>();
private ConsumableState previousState;
private final String key;
private final boolean transactionBased;
DataState() {
key = "";
transactionBased = false;
}
DataState(String key, boolean transactionBased) {
this.key = key;
this.transactionBased = transactionBased;
}
public void update(ConsumableState o) {
if (!o.equals(previousState)) {
previousState = o;
if (!transactionBased) {
state.set(o);
}
}
}
public Object consumeState(String whoIsUpdating) {
return consumeState(whoIsUpdating, new HashMap<>());
}
public Object consumeState(Map<String, Object> output) {
return consumeState((String) output.get("name"), output);
}
public Object consumeState(String whoIsUpdating, Map<String, Object> output) {
final ConsumableState consumedState = state.getAndSet(null);
if (consumedState != null) {
final String whoOwnsThis = consumedState.whoOwnsThis();
if (whoOwnsThis != null && whoOwnsThis.equals(whoIsUpdating)) {
Object c = consumedState.get();
output.put(key, c);
return c;
}
}
return null;
}
public ConsumableState mostRecentState() {
return this.previousState;
}
public void restoreState() {
state.compareAndSet(null, previousState);
}
public void commitTransaction() {
state.set(previousState);
}
}

View File

@@ -0,0 +1,45 @@
package men.groupiron;
import java.util.Map;
public class DepositedItems {
private ItemContainerState items = null;
private ItemContainerState consumedItems = null;
public DepositedItems() {
}
public synchronized void update(ItemContainerState deposited) {
if (deposited == null) return;
if (items == null || !deposited.whoOwnsThis().equals(items.whoOwnsThis())) {
items = deposited;
} else {
items = items.add(deposited);
}
}
public synchronized void consumeState(Map<String, Object> output) {
if (items != null) {
final String whoOwnsThis = items.whoOwnsThis();
final String whoIsUpdating = (String) output.get("name");
if (whoOwnsThis != null && whoOwnsThis.equals(whoIsUpdating)) output.put("deposited", items.get());
}
consumedItems = items;
items = null;
}
public synchronized void restoreState() {
if (consumedItems == null) return;
if (items != null) {
items = items.add(consumedItems);
} else {
items = consumedItems;
}
consumedItems = null;
}
public synchronized void reset() {
items = null;
consumedItems = null;
}
}

View File

@@ -0,0 +1,55 @@
package men.groupiron;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
@ConfigGroup("GroupIronmenTracker")
public interface GroupIronmenTrackerConfig extends Config {
@ConfigSection(
name = "Group Config",
description = "Enter the group details you created on the website here",
position = 0
)
String groupSection = "GroupSection";
@ConfigSection(
name = "Self Hosted Config",
description = "Configure your connection to a self hosted server",
position = 1,
closedByDefault = true
)
String connectionSection = "ConnectionSection";
@ConfigItem(
keyName = "groupName",
name = "Group Name (on the website)",
description = "This is the group name you provided on the website when creating your group",
section = groupSection
)
default String groupName() {
return "";
}
@ConfigItem(
keyName = "groupToken",
name = "Group Token",
description = "Secret token for your group provided by the website. Get this from the member which created the group on the site, or create a new one by visiting the site.",
secret = true,
section = groupSection
)
default String authorizationToken() {
return "";
}
@ConfigItem(
keyName = "baseUrlOverride",
name = "Server base URL override (leave blank to use public server)",
description = "Overrides the public server URL used to send data. Only change this if you are hosting your own server.",
section = connectionSection
)
default String baseUrlOverride() {
return "";
}
}

View File

@@ -0,0 +1,282 @@
package men.groupiron;
import com.google.inject.Provides;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.*;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.*;
import net.runelite.api.gameval.InventoryID;
import net.runelite.api.gameval.InterfaceID;
import net.runelite.api.gameval.VarClientID;
import net.runelite.api.gameval.VarPlayerID;
import net.runelite.api.widgets.Widget;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.task.Schedule;
import net.runelite.client.util.Text;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject;
import java.time.temporal.ChronoUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
@PluginDescriptor(
name = "Group Ironmen Tracker"
)
public class GroupIronmenTrackerPlugin extends Plugin {
@Inject
private Client client;
@Inject
private GroupIronmenTrackerConfig config;
@Inject
private DataManager dataManager;
@Inject
private ItemManager itemManager;
@Inject
private CollectionLogManager collectionLogManager;
@Inject
private CollectionLogV2Manager collectionLogV2Manager;
@Inject
private CollectionLogWidgetSubscriber collectionLogWidgetSubscriber;
@Inject
private HttpRequestService httpRequestService;
@Inject
ClientThread clientThread;
private int itemsDeposited = 0;
private static final int SECONDS_BETWEEN_UPLOADS = 1;
private static final int SECONDS_BETWEEN_INFREQUENT_DATA_CHANGES = 60;
private static final int DEPOSIT_ITEM = 12582914;
private static final int DEPOSIT_INVENTORY = 12582916;
private static final int DEPOSIT_EQUIPMENT = 12582918;
private static final int CHATBOX_ENTERED = 681;
private static final int GROUP_STORAGE_LOADER = 293;
private static final int COLLECTION_LOG_INVENTORYID = 620;
private static final Pattern COLLECTION_LOG_ITEM_PATTERN = Pattern.compile("New item added to your collection log: (.*)");
private boolean notificationStarted = false;
@Override
protected void startUp() throws Exception {
clientThread.invokeLater(() -> {
collectionLogManager.initCollectionLog();
});
collectionLogWidgetSubscriber.startUp();
log.info("Group Ironmen Tracker started!");
}
@Override
protected void shutDown() throws Exception {
collectionLogWidgetSubscriber.shutDown();
log.info("Group Ironmen Tracker stopped!");
}
@Schedule(
period = SECONDS_BETWEEN_UPLOADS,
unit = ChronoUnit.SECONDS,
asynchronous = true
)
public void submitToApi() {
dataManager.submitToApi();
}
@Schedule(
period = SECONDS_BETWEEN_UPLOADS,
unit = ChronoUnit.SECONDS
)
public void updateThingsThatDoChangeOften() {
if (doNotUseThisData())
return;
Player player = client.getLocalPlayer();
String playerName = player.getName();
dataManager.getResources().update(new ResourcesState(playerName, client));
LocalPoint localPoint = player.getLocalLocation();
WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, localPoint);
dataManager.getPosition().update(new LocationState(playerName, worldPoint));
dataManager.getRunePouch().update(new RunePouchState(playerName, client));
dataManager.getQuiver().update(new QuiverState(playerName, client, itemManager));
}
@Schedule(
period = SECONDS_BETWEEN_INFREQUENT_DATA_CHANGES,
unit = ChronoUnit.SECONDS
)
public void updateThingsThatDoNotChangeOften() {
if (doNotUseThisData())
return;
String playerName = client.getLocalPlayer().getName();
dataManager.getQuests().update(new QuestState(playerName, client));
dataManager.getAchievementDiary().update(new AchievementDiaryState(playerName, client));
}
@Subscribe
public void onVarbitChanged(VarbitChanged event) {
if (doNotUseThisData()) return;
final int varpId = event.getVarpId();
if (varpId == VarPlayerID.DIZANAS_QUIVER_TEMP_AMMO || varpId == VarPlayerID.DIZANAS_QUIVER_TEMP_AMMO_AMOUNT) {
String playerName = client.getLocalPlayer().getName();
dataManager.getQuiver().update(new QuiverState(playerName, client, itemManager));
}
}
@Subscribe
public void onGameTick(GameTick gameTick) {
--itemsDeposited;
updateInteracting();
Widget groupStorageLoaderText = client.getWidget(GROUP_STORAGE_LOADER, 1);
if (groupStorageLoaderText != null) {
if (groupStorageLoaderText.getText().equalsIgnoreCase("saving...")) {
dataManager.getSharedBank().commitTransaction();
}
}
}
@Subscribe
public void onStatChanged(StatChanged statChanged) {
if (doNotUseThisData())
return;
String playerName = client.getLocalPlayer().getName();
dataManager.getSkills().update(new SkillState(playerName, client));
}
@Subscribe
public void onItemContainerChanged(ItemContainerChanged event) {
if (doNotUseThisData())
return;
String playerName = client.getLocalPlayer().getName();
final int id = event.getContainerId();
ItemContainer container = event.getItemContainer();
if (id == InventoryID.BANK) {
dataManager.getDeposited().reset();
dataManager.getBank().update(new ItemContainerState(playerName, container, itemManager));
} else if (id == InventoryID.SEED_VAULT) {
dataManager.getSeedVault().update(new ItemContainerState(playerName, container, itemManager));
} else if (id == InventoryID.INV) {
ItemContainerState newInventoryState = new ItemContainerState(playerName, container, itemManager, 28);
if (itemsDeposited > 0) {
updateDeposited(newInventoryState, (ItemContainerState) dataManager.getInventory().mostRecentState());
}
dataManager.getInventory().update(newInventoryState);
} else if (id == InventoryID.WORN) {
ItemContainerState newEquipmentState = new ItemContainerState(playerName, container, itemManager, 14);
if (itemsDeposited > 0) {
updateDeposited(newEquipmentState, (ItemContainerState) dataManager.getEquipment().mostRecentState());
}
dataManager.getEquipment().update(newEquipmentState);
} else if (id == InventoryID.INV_GROUP_TEMP) {
dataManager.getSharedBank().update(new ItemContainerState(playerName, container, itemManager));
} else if (id == COLLECTION_LOG_INVENTORYID) {
collectionLogManager.updateCollection(new ItemContainerState(playerName, container, itemManager));
}
}
@Subscribe
private void onScriptPostFired(ScriptPostFired event) {
if (event.getScriptId() == CHATBOX_ENTERED && client.getWidget(InterfaceID.BankDepositbox.INVENTORY) != null) {
itemsMayHaveBeenDeposited();
}
}
@Subscribe
private void onMenuOptionClicked(MenuOptionClicked event) {
final int param1 = event.getParam1();
final MenuAction menuAction = event.getMenuAction();
if (menuAction == MenuAction.CC_OP) {
if (param1 == DEPOSIT_ITEM || param1 == DEPOSIT_INVENTORY || param1 == DEPOSIT_EQUIPMENT) {
itemsMayHaveBeenDeposited();
}
}
}
@Subscribe
private void onInteractingChanged(InteractingChanged event) {
if (event.getSource() != client.getLocalPlayer()) return;
updateInteracting();
}
@Subscribe
private void onChatMessage(ChatMessage chatMessage) {
if (doNotUseThisData())
return;
if (chatMessage.getType() != ChatMessageType.GAMEMESSAGE) return;
Matcher matcher = COLLECTION_LOG_ITEM_PATTERN.matcher(chatMessage.getMessage());
if (matcher.find()) {
String itemName = Text.removeTags(matcher.group(1));
if (!StringUtils.isBlank(itemName)) {
collectionLogManager.updateNewItem(itemName);
}
}
}
@Subscribe
public void onScriptPreFired(ScriptPreFired scriptPreFired)
{
switch (scriptPreFired.getScriptId())
{
case ScriptID.NOTIFICATION_START:
notificationStarted = true;
break;
case ScriptID.NOTIFICATION_DELAY:
if (!notificationStarted) return;
String topText = client.getVarcStrValue(VarClientID.NOTIFICATION_TITLE);
String bottomText = client.getVarcStrValue(VarClientID.NOTIFICATION_MAIN);
if (topText.equalsIgnoreCase("Collection log")) {
String entry = Text.removeTags(bottomText).substring("New item:".length());
collectionLogManager.updateNewItem(entry);
}
notificationStarted = false;
break;
}
}
private void itemsMayHaveBeenDeposited() {
// NOTE: In order to determine if an item has gone through the deposit box we first detect if any of the menu
// actions were performed OR a custom amount was entered while the deposit box inventory widget was opened.
// Then we allow up to two game ticks were an inventory changed event can occur and at that point we assume
// it must have been caused by the action detected just before. We can't check the inventory at the time of
// either interaction since the inventory may have not been updated yet. We also cannot just check that the deposit
// box window is open in the item container event since it is possible for a player to close the widget before
// the event handler is called.
itemsDeposited = 2;
}
private void updateInteracting() {
Player player = client.getLocalPlayer();
if (player != null) {
Actor actor = player.getInteracting();
if (actor != null) {
String playerName = player.getName();
dataManager.getInteracting().update(new InteractingState(playerName, actor, client));
}
}
}
private void updateDeposited(ItemContainerState newState, ItemContainerState previousState) {
ItemContainerState deposited = newState.whatGotRemoved(previousState);
dataManager.getDeposited().update(deposited);
}
private boolean doNotUseThisData() {
return client.getGameState() != GameState.LOGGED_IN || client.getLocalPlayer() == null;
}
@Provides
GroupIronmenTrackerConfig provideConfig(ConfigManager configManager) {
return configManager.getConfig(GroupIronmenTrackerConfig.class);
}
}

View File

@@ -0,0 +1,120 @@
package men.groupiron;
import com.google.gson.Gson;
import java.io.IOException;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.RuneLiteProperties;
import okhttp3.*;
@Slf4j
@Singleton
public class HttpRequestService {
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static final String USER_AGENT =
"GroupIronmenTracker/1.5.3 " + "RuneLite/" + RuneLiteProperties.getVersion();
private static final String PUBLIC_BASE_URL = "https://groupiron.men";
@Inject
private OkHttpClient okHttpClient;
@Inject
private GroupIronmenTrackerConfig config;
@Inject
private Gson gson;
public HttpResponse get(String url, String authToken) {
Request request = buildRequest(url, authToken).get().build();
return executeRequest(request, "GET", url, null);
}
public HttpResponse post(String url, String authToken, Object requestBody) {
String requestJson = gson.toJson(requestBody);
RequestBody body = RequestBody.create(JSON, requestJson);
Request request = buildRequest(url, authToken).post(body).build();
return executeRequest(request, "POST", url, requestJson);
}
private Request.Builder buildRequest(String url, String authToken) {
Request.Builder requestBuilder = new Request.Builder().url(url).header("User-Agent", USER_AGENT);
if (isInternalUrl(url)) {
if (authToken != null && !authToken.trim().isEmpty()) {
requestBuilder.header("Authorization", authToken);
}
requestBuilder.header("Accept", "application/json");
}
return requestBuilder;
}
private boolean isInternalUrl(String url) {
return url.startsWith(getBaseUrl());
}
private HttpResponse executeRequest(Request request, String method, String url, String requestBody) {
Call call = okHttpClient.newCall(request);
try (Response response = call.execute()) {
String responseBody = readBodySafe(response);
logRequest(method, url, requestBody, response, responseBody);
return new HttpResponse(response.isSuccessful(), response.code(), responseBody);
} catch (IOException ex) {
log.warn("{} {} failed: {}", method, url, ex.toString());
return new HttpResponse(false, -1, ex.getMessage());
}
}
private void logRequest(String method, String url, String requestBody, Response response, String responseBody) {
if (!log.isDebugEnabled()) {
return;
}
switch (method) {
case "GET":
log.debug("GET {} -> {}\nResponse: {}", url, response.code(), responseBody);
break;
case "POST":
log.debug("POST {}\nRequest: {}\nResponse({}): {}", url, requestBody, response.code(), responseBody);
break;
default:
log.debug("{} {} -> {}\nResponse: {}", method, url, response.code(), responseBody);
}
}
private static String readBodySafe(Response response) {
try {
ResponseBody responseBody = response.body();
return responseBody != null ? responseBody.string() : "<no body>";
} catch (Exception e) {
return "<unavailable: " + e.getMessage() + ">";
}
}
public String getBaseUrl() {
String baseUrlOverride = config.baseUrlOverride().trim();
if (!baseUrlOverride.isEmpty()) {
return baseUrlOverride;
}
return PUBLIC_BASE_URL;
}
@Getter
public static class HttpResponse {
private final boolean successful;
private final int code;
private final String body;
public HttpResponse(boolean successful, int code, String body) {
this.successful = successful;
this.code = code;
this.body = body;
}
}
}

View File

@@ -0,0 +1,49 @@
package men.groupiron;
import lombok.Getter;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.coords.WorldPoint;
public class InteractingState implements ConsumableState {
private final transient String playerName;
@Getter
private final String name;
@Getter
private final int scale;
@Getter
private final int ratio;
@Getter
private final LocationState location;
public InteractingState(String playerName, Actor actor, Client client) {
this.playerName = playerName;
this.scale = actor.getHealthScale();
this.ratio = actor.getHealthRatio();
this.name = actor.getName();
WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, actor.getLocalLocation());
this.location = new LocationState(playerName, worldPoint);
}
@Override
public Object get() {
return this;
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof InteractingState)) return false;
// NOTE: For interactions, we want to keep sending the data until the player stops interacting
// even if nothing changed about what is being interacted with. The UI will handle not showing
// the interaction once it goes stale from the player not interacting with anything.
return false;
}
}

View File

@@ -0,0 +1,27 @@
package men.groupiron;
import lombok.Getter;
public class ItemContainerItem {
@Getter
private final int id;
@Getter
private int quantity;
ItemContainerItem(int id, int quantity) {
this.id = id;
this.quantity = quantity;
}
public void addQuantity(int quantity) {
this.quantity += quantity;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ItemContainerItem)) return false;
ItemContainerItem other = (ItemContainerItem) o;
return other.id == id && other.quantity == quantity;
}
}

View File

@@ -0,0 +1,161 @@
package men.groupiron;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Item;
import net.runelite.api.ItemContainer;
import net.runelite.client.game.ItemManager;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class ItemContainerState implements ConsumableState {
private final List<ItemContainerItem> items;
private transient final String playerName;
public ItemContainerState(String playerName, ItemContainer container, ItemManager itemManager) {
this.playerName = playerName;
items = new ArrayList<>();
Item[] contents = container.getItems();
for (final Item item : contents) {
if (isItemValid(item, itemManager)) {
items.add(new ItemContainerItem(itemManager.canonicalize(item.getId()), item.getQuantity()));
}
}
}
// NOTE: This is for when we care about the order of the items in the container
// like the player inventory and equipment.
public ItemContainerState(String playerName, ItemContainer container, ItemManager itemManager, int containerSize) {
this.playerName = playerName;
items = new ArrayList<>();
for (int i = 0; i < containerSize; i++) {
Item item = container.getItem(i);
if (!isItemValid(item, itemManager)) {
items.add(new ItemContainerItem(0, 0));
} else {
items.add(new ItemContainerItem(itemManager.canonicalize(item.getId()), item.getQuantity()));
}
}
}
public ItemContainerState(String playerName, List<ItemContainerItem> items) {
this.playerName = playerName;
this.items = items;
}
@Nullable
public ItemContainerState add(ItemContainerState itemsToAdd) {
if (itemsToAdd == null || !itemsToAdd.whoOwnsThis().equals(whoOwnsThis())) return null;
Map<Integer, ItemContainerItem> thisItems = getItemMap();
Map<Integer, ItemContainerItem> otherItems = itemsToAdd.getItemMap();
List<ItemContainerItem> result = new ArrayList<>();
for (Integer itemId : thisItems.keySet()) {
ItemContainerItem item = thisItems.get(itemId);
if (otherItems.containsKey(itemId)) {
item.addQuantity(otherItems.get(itemId).getQuantity());
}
result.add(item);
}
for (Integer itemId : otherItems.keySet()) {
if (!thisItems.containsKey(itemId)) {
result.add(otherItems.get(itemId));
}
}
return new ItemContainerState(whoOwnsThis(), result);
}
@Nullable
public ItemContainerState whatGotRemoved(ItemContainerState other) {
if (other == null || !other.whoOwnsThis().equals(whoOwnsThis())) return null;
Map<Integer, ItemContainerItem> thisItems = getItemMap();
Map<Integer, ItemContainerItem> otherItems = other.getItemMap();
List<ItemContainerItem> result = new ArrayList<>();
for (Integer itemId : otherItems.keySet()) {
ItemContainerItem otherItem = otherItems.get(itemId);
if (otherItem.getId() == 0) continue;
if (thisItems.containsKey(itemId)) {
ItemContainerItem thisItem = thisItems.get(itemId);
int quantityDifference = otherItem.getQuantity() - thisItem.getQuantity();
if (quantityDifference > 0) {
result.add(new ItemContainerItem(itemId, quantityDifference));
}
} else {
result.add(new ItemContainerItem(itemId, otherItem.getQuantity()));
}
}
return new ItemContainerState(playerName, result);
}
public Map<Integer, ItemContainerItem> getItemMap() {
Map<Integer, ItemContainerItem> itemMap = new HashMap<>();
for (ItemContainerItem itemContainerItem : items) {
Integer id = itemContainerItem.getId();
if (itemMap.containsKey(id)) {
itemMap.get(id).addQuantity(itemContainerItem.getQuantity());
} else {
itemMap.put(id, new ItemContainerItem(id, itemContainerItem.getQuantity()));
}
}
return itemMap;
}
public boolean isEmpty() {
return items.isEmpty();
}
private boolean isItemValid(Item item, ItemManager itemManager) {
if (item == null) return false;
final int id = item.getId();
final int quantity = item.getQuantity();
if (itemManager != null) {
final boolean isPlaceholder = itemManager.getItemComposition(id).getPlaceholderTemplateId() != -1;
return id >= 0 && quantity >= 0 && !isPlaceholder;
}
return false;
}
public List<Integer> asFlatList() {
List<Integer> result = new ArrayList<>(items.size() * 2);
for (ItemContainerItem item : items) {
result.add(item.getId());
result.add(item.getQuantity());
}
return result;
}
@Override
public Object get() {
return asFlatList();
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ItemContainerState)) return false;
ItemContainerState other = (ItemContainerState) o;
if (other.items.size() != items.size()) return false;
for (int i = 0; i < items.size(); i++) {
if (!items.get(i).equals(other.items.get(i))) return false;
}
return true;
}
}

View File

@@ -0,0 +1,45 @@
package men.groupiron;
import lombok.Getter;
import net.runelite.api.coords.WorldPoint;
public class LocationState implements ConsumableState {
@Getter
private final int x;
@Getter
private final int y;
@Getter
private final int plane;
private transient final String playerName;
LocationState(String playerName, WorldPoint worldPoint) {
this.playerName = playerName;
x = worldPoint.getX();
y = worldPoint.getY();
plane = worldPoint.getPlane();
}
@Override
public Object get() {
return new int[] { x, y, plane };
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof LocationState)) return false;
LocationState other = (LocationState) o;
return (x == other.x) && (y == other.y) && (plane == other.plane);
}
@Override
public String toString() {
return String.format("{ x: %d, y: %d, plane: %d }", x, y, plane);
}
}

View File

@@ -0,0 +1,52 @@
package men.groupiron;
import net.runelite.api.Client;
import net.runelite.api.Quest;
import java.util.*;
import java.util.stream.Collectors;
public class QuestState implements ConsumableState {
private final Map<Integer, net.runelite.api.QuestState> questStateMap;
private transient final String playerName;
private List<Integer> sortedQuestIds = Arrays.stream(Quest.values()).map(Quest::getId).sorted().collect(Collectors.toList());
public QuestState(String playerName, Client client) {
this.playerName = playerName;
this.questStateMap = new HashMap<>();
for (Quest quest : Quest.values()) {
questStateMap.put(quest.getId(), quest.getState(client));
}
}
@Override
public Object get() {
List<Integer> result = new ArrayList<>(questStateMap.size());
for (Integer questId : sortedQuestIds) {
result.add(questStateMap.get(questId).ordinal());
}
return result;
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof QuestState)) return false;
QuestState other = (QuestState) o;
for (Quest quest : Quest.values()) {
Integer questId = quest.getId();
if (questStateMap.get(questId) != other.questStateMap.get(questId)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,48 @@
package men.groupiron;
import net.runelite.api.Client;
import net.runelite.api.gameval.VarPlayerID;
import net.runelite.client.game.ItemManager;
public class QuiverState implements ConsumableState {
private final ItemContainerItem ammo;
private final String playerName;
public QuiverState(String playerName, Client client, ItemManager itemManager) {
this.playerName = playerName;
int id = client.getVarpValue(VarPlayerID.DIZANAS_QUIVER_TEMP_AMMO);
int qty = client.getVarpValue(VarPlayerID.DIZANAS_QUIVER_TEMP_AMMO_AMOUNT);
if (id <= 0 || qty <= 0) {
this.ammo = new ItemContainerItem(0, 0);
} else {
int canonId = itemManager.canonicalize(id);
this.ammo = new ItemContainerItem(canonId, qty);
}
}
@Override
public Object get() {
return new int[] {ammo.getId(), ammo.getQuantity()};
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof QuiverState)) {
return false;
}
QuiverState other = (QuiverState) o;
return this.ammo.equals(other.ammo);
}
}

View File

@@ -0,0 +1,79 @@
package men.groupiron;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.Skill;
public class ResourcesState implements ConsumableState {
private static class CurrentMax {
@Getter
private final int current;
@Getter
private final int max;
CurrentMax(int current, int max) {
this.current = current;
this.max = max;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof CurrentMax)) return false;
CurrentMax other = (CurrentMax) o;
return other.getCurrent() == current && other.getMax() == max;
}
}
@Getter
private final CurrentMax hitpoints;
@Getter
private final CurrentMax prayer;
@Getter
private final CurrentMax energy;
@Getter
int world;
private transient final String playerName;
ResourcesState(String playerName, Client client) {
this.playerName = playerName;
hitpoints = new CurrentMax(
client.getBoostedSkillLevel(Skill.HITPOINTS),
client.getRealSkillLevel(Skill.HITPOINTS)
);
prayer = new CurrentMax(
client.getBoostedSkillLevel(Skill.PRAYER),
client.getRealSkillLevel(Skill.PRAYER)
);
energy = new CurrentMax(
client.getEnergy(),
100
);
world = client.getWorld();
}
@Override
public Object get() {
return new int[] {
hitpoints.current, hitpoints.max,
prayer.current, prayer.max,
energy.current, energy.max,
world
};
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ResourcesState)) return false;
ResourcesState other = (ResourcesState) o;
return other.world == world && other.hitpoints.equals(hitpoints) && other.prayer.equals(prayer) && other.energy.equals(energy);
}
}

View File

@@ -0,0 +1,51 @@
package men.groupiron;
import net.runelite.api.Client;
import net.runelite.api.EnumComposition;
import net.runelite.api.EnumID;
import net.runelite.api.gameval.VarbitID;
public class RunePouchState implements ConsumableState {
private final ItemContainerItem rune1;
private final ItemContainerItem rune2;
private final ItemContainerItem rune3;
private final ItemContainerItem rune4;
private final transient String playerName;
public RunePouchState(String playerName, Client client) {
this.playerName = playerName;
final EnumComposition runepouchEnum = client.getEnum(EnumID.RUNEPOUCH_RUNE);
rune1 = itemForRune(client.getVarbitValue(VarbitID.RUNE_POUCH_TYPE_1), client.getVarbitValue(VarbitID.RUNE_POUCH_QUANTITY_1), runepouchEnum);
rune2 = itemForRune(client.getVarbitValue(VarbitID.RUNE_POUCH_TYPE_2), client.getVarbitValue(VarbitID.RUNE_POUCH_QUANTITY_2), runepouchEnum);
rune3 = itemForRune(client.getVarbitValue(VarbitID.RUNE_POUCH_TYPE_3), client.getVarbitValue(VarbitID.RUNE_POUCH_QUANTITY_3), runepouchEnum);
rune4 = itemForRune(client.getVarbitValue(VarbitID.RUNE_POUCH_TYPE_4), client.getVarbitValue(VarbitID.RUNE_POUCH_QUANTITY_4), runepouchEnum);
}
private ItemContainerItem itemForRune(int runeId, int amount, EnumComposition runepouchEnum) {
return new ItemContainerItem(runepouchEnum.getIntValue(runeId), amount);
}
@Override
public Object get() {
return new int[] {
rune1.getId(), rune1.getQuantity(),
rune2.getId(), rune2.getQuantity(),
rune3.getId(), rune3.getQuantity(),
rune4.getId(), rune4.getQuantity()
};
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof RunePouchState)) return false;
RunePouchState other = (RunePouchState) o;
return rune1.equals(other.rune1) && rune2.equals(other.rune2) && rune3.equals(other.rune3) && rune4.equals(other.rune4);
}
}

View File

@@ -0,0 +1,69 @@
package men.groupiron;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Skill;
import java.util.*;
@Slf4j
public class SkillState implements ConsumableState {
private final Map<String, Integer> skillXpMap;
private transient final String playerName;
public SkillState(String playerName, Client client) {
this.playerName = playerName;
skillXpMap = new HashMap<>();
for (Skill skill : Skill.values()) {
skillXpMap.put(skill.getName(), client.getSkillExperience(skill));
}
}
@Override
public Object get() {
return new int[] {
skillXpMap.get("Agility"),
skillXpMap.get("Attack"),
skillXpMap.get("Construction"),
skillXpMap.get("Cooking"),
skillXpMap.get("Crafting"),
skillXpMap.get("Defence"),
skillXpMap.get("Farming"),
skillXpMap.get("Firemaking"),
skillXpMap.get("Fishing"),
skillXpMap.get("Fletching"),
skillXpMap.get("Herblore"),
skillXpMap.get("Hitpoints"),
skillXpMap.get("Hunter"),
skillXpMap.get("Magic"),
skillXpMap.get("Mining"),
skillXpMap.get("Prayer"),
skillXpMap.get("Ranged"),
skillXpMap.get("Runecraft"),
skillXpMap.get("Slayer"),
skillXpMap.get("Smithing"),
skillXpMap.get("Strength"),
skillXpMap.get("Thieving"),
skillXpMap.get("Woodcutting")
};
}
@Override
public String whoOwnsThis() {
return playerName;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof SkillState)) return false;
SkillState other = (SkillState) o;
for (Skill skill : Skill.values()) {
String skillName = skill.getName();
if (!skillXpMap.get(skillName).equals(other.skillXpMap.get(skillName))) return false;
}
return true;
}
}