From b80f09a4ea3e216dae2e8fdf90409db2b41f9ccf Mon Sep 17 00:00:00 2001 From: sonderau Date: Mon, 27 Oct 2025 08:28:05 +0800 Subject: [PATCH] First commit of group-ironmen-tracker-master --- .gitignore | 24 +- group-ironmen-tracker-master/.gitignore | 12 + group-ironmen-tracker-master/LICENSE | 25 ++ group-ironmen-tracker-master/README.md | 23 ++ group-ironmen-tracker-master/build.gradle | 32 ++ group-ironmen-tracker-master/gradlew | 234 +++++++++++++++ group-ironmen-tracker-master/gradlew.bat | 89 ++++++ group-ironmen-tracker-master/icon.png | Bin 0 -> 8836 bytes .../runelite-plugin.properties | 6 + group-ironmen-tracker-master/settings.gradle | 1 + .../men/groupiron/AchievementDiaryState.java | 78 +++++ .../men/groupiron/CollectionLogManager.java | 174 +++++++++++ .../men/groupiron/CollectionLogV2Manager.java | 42 +++ .../CollectionLogWidgetSubscriber.java | 95 ++++++ .../men/groupiron/CollectionPageState.java | 48 +++ .../java/men/groupiron/ConsumableState.java | 7 + .../main/java/men/groupiron/DataManager.java | 199 ++++++++++++ .../main/java/men/groupiron/DataState.java | 66 ++++ .../java/men/groupiron/DepositedItems.java | 45 +++ .../groupiron/GroupIronmenTrackerConfig.java | 55 ++++ .../groupiron/GroupIronmenTrackerPlugin.java | 282 ++++++++++++++++++ .../men/groupiron/HttpRequestService.java | 120 ++++++++ .../java/men/groupiron/InteractingState.java | 49 +++ .../java/men/groupiron/ItemContainerItem.java | 27 ++ .../men/groupiron/ItemContainerState.java | 161 ++++++++++ .../java/men/groupiron/LocationState.java | 45 +++ .../main/java/men/groupiron/QuestState.java | 52 ++++ .../main/java/men/groupiron/QuiverState.java | 48 +++ .../java/men/groupiron/ResourcesState.java | 79 +++++ .../java/men/groupiron/RunePouchState.java | 51 ++++ .../main/java/men/groupiron/SkillState.java | 69 +++++ .../GroupIronmenTrackerPluginTest.java | 13 + 32 files changed, 2249 insertions(+), 2 deletions(-) create mode 100644 group-ironmen-tracker-master/.gitignore create mode 100644 group-ironmen-tracker-master/LICENSE create mode 100644 group-ironmen-tracker-master/README.md create mode 100644 group-ironmen-tracker-master/build.gradle create mode 100644 group-ironmen-tracker-master/gradlew create mode 100644 group-ironmen-tracker-master/gradlew.bat create mode 100644 group-ironmen-tracker-master/icon.png create mode 100644 group-ironmen-tracker-master/runelite-plugin.properties create mode 100644 group-ironmen-tracker-master/settings.gradle create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/AchievementDiaryState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogManager.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogV2Manager.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogWidgetSubscriber.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionPageState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/ConsumableState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/DataManager.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/DataState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/DepositedItems.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/GroupIronmenTrackerConfig.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/GroupIronmenTrackerPlugin.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/HttpRequestService.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/InteractingState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/ItemContainerItem.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/ItemContainerState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/LocationState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/QuestState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/QuiverState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/ResourcesState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/RunePouchState.java create mode 100644 group-ironmen-tracker-master/src/main/java/men/groupiron/SkillState.java create mode 100644 group-ironmen-tracker-master/src/test/java/men/groupiron/GroupIronmenTrackerPluginTest.java diff --git a/.gitignore b/.gitignore index 0fe15a68..4c7b9a18 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,25 @@ group-ironmen-master/.github group-ironmen-master/backup group-ironmen-master/.gitignore group-ironmen-master/.ignore -group-ironmen-tracker-master/* -tasks-tracker-plugin-master/* \ No newline at end of file +group-ironmen-master/.#* +group-ironmen-master/.env + + +group-ironmen-tracker-master/gradle +group-ironmen-tracker-master/.gradle +group-ironmen-tracker-master/build +group-ironmen-tracker-master/.idea/ +group-ironmen-tracker-master/.project +group-ironmen-tracker-master/.settings/ +group-ironmen-tracker-master/.classpath +group-ironmen-tracker-master/nbactions.xml +group-ironmen-tracker-master/nb-configuration.xml +group-ironmen-tracker-master/nbproject/ +group-ironmen-tracker-master/.#* +group-ironmen-tracker-master/bin/ +group-ironmen-tracker-master/spotless/ + + +tasks-tracker-plugin-master/* + + diff --git a/group-ironmen-tracker-master/.gitignore b/group-ironmen-tracker-master/.gitignore new file mode 100644 index 00000000..70e4bf93 --- /dev/null +++ b/group-ironmen-tracker-master/.gitignore @@ -0,0 +1,12 @@ +.gradle +build +.idea/ +.project +.settings/ +.classpath +nbactions.xml +nb-configuration.xml +nbproject/ +.#* +bin/ +spotless/ diff --git a/group-ironmen-tracker-master/LICENSE b/group-ironmen-tracker-master/LICENSE new file mode 100644 index 00000000..2f4e8e73 --- /dev/null +++ b/group-ironmen-tracker-master/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2022, Christopher Brown +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. diff --git a/group-ironmen-tracker-master/README.md b/group-ironmen-tracker-master/README.md new file mode 100644 index 00000000..f945e617 --- /dev/null +++ b/group-ironmen-tracker-master/README.md @@ -0,0 +1,23 @@ +# Group Ironmen Tracker Plugin +Website: [groupiron.men](https://groupiron.men) +Demo: [grouiron.men/demo](https://groupiron.men/demo) +Source for frontend and server: [https://github.com/christoabrown/group-ironmen](https://github.com/christoabrown/group-ironmen) + +This plugin tracks information about your group ironman player and sends it to a server where you and your other group members can view it. Currently it tracks: + +* Inventory, equipment, bank, rune pouch, seed vault, and shared bank +* Skill XP +* World position, viewable in an interactive map +* HP, prayer, energy, and world +* Quest completion status +* Health and position of npcs the player is interacting with +* Achievement Diaries +* Collection Log + +![](https://i.imgur.com/1Mdz8RU.png) +![](https://i.imgur.com/Rs0ruRE.png) + +## Setup +Each group member that you want to track will need to install the plugin. Just one person will go to the site and create a group, the credentials it provides is shared with your group. + +![](https://i.imgur.com/Dyi8LXL.png) diff --git a/group-ironmen-tracker-master/build.gradle b/group-ironmen-tracker-master/build.gradle new file mode 100644 index 00000000..0f7867b9 --- /dev/null +++ b/group-ironmen-tracker-master/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'java' +} + +repositories { + mavenLocal() + maven { + url = 'https://repo.runelite.net' + } + mavenCentral() +} + +def runeLiteVersion = 'latest.release' + +dependencies { + compileOnly group: 'net.runelite', name:'client', version: runeLiteVersion + + compileOnly 'org.projectlombok:lombok:1.18.20' + annotationProcessor 'org.projectlombok:lombok:1.18.20' + + testImplementation 'junit:junit:4.12' + testImplementation group: 'net.runelite', name:'client', version: runeLiteVersion + testImplementation group: 'net.runelite', name:'jshell', version: runeLiteVersion +} + +group = 'men.groupiron' +version = '1.5.3' +sourceCompatibility = '1.8' + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} diff --git a/group-ironmen-tracker-master/gradlew b/group-ironmen-tracker-master/gradlew new file mode 100644 index 00000000..1b6c7873 --- /dev/null +++ b/group-ironmen-tracker-master/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/group-ironmen-tracker-master/gradlew.bat b/group-ironmen-tracker-master/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/group-ironmen-tracker-master/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/group-ironmen-tracker-master/icon.png b/group-ironmen-tracker-master/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d27b76e0dafa299fdc5d9930aa16734f4db8d1ed GIT binary patch literal 8836 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 literal 0 HcmV?d00001 diff --git a/group-ironmen-tracker-master/runelite-plugin.properties b/group-ironmen-tracker-master/runelite-plugin.properties new file mode 100644 index 00000000..5a8114ab --- /dev/null +++ b/group-ironmen-tracker-master/runelite-plugin.properties @@ -0,0 +1,6 @@ +displayName=Group Ironmen Tracker +author=Christopher Brown +support= +description=Tracks information (skills, inventory, bank, etc.) about a group ironman player and sends it to a website for other group members to view +tags=group,ironman,tracker +plugins=men.groupiron.GroupIronmenTrackerPlugin \ No newline at end of file diff --git a/group-ironmen-tracker-master/settings.gradle b/group-ironmen-tracker-master/settings.gradle new file mode 100644 index 00000000..d1b2d773 --- /dev/null +++ b/group-ironmen-tracker-master/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'GroupIronmenTracker' diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/AchievementDiaryState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/AchievementDiaryState.java new file mode 100644 index 00000000..55d60db1 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/AchievementDiaryState.java @@ -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; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogManager.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogManager.java new file mode 100644 index 00000000..e301c710 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogManager.java @@ -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 collections = new HashMap<>(); + private String playerName; + private Set consumedNewItems = null; + private Set 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> pageItems; + static Map> pageNameLookup; + private static final List 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 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 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 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 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 output) { + if (newItems != null && output.get("name").equals(this.playerName)) { + output.put("collection_log_new", newItems); + } + consumedNewItems = newItems; + newItems = null; + } + + public void consumeCollections(Map output) { + if (collections.isEmpty()) return; + List 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; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogV2Manager.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogV2Manager.java new file mode 100644 index 00000000..d5e81f82 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogV2Manager.java @@ -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 clogItems = new HashMap<>(); + + public synchronized void storeClogItem(int itemId, int quantity) { + if (quantity <= 0) return; + clogItems.put(itemId, quantity); + } + + public synchronized void consumeClogItems(Map 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 snapshotItems() { + return Collections.unmodifiableMap(new HashMap<>(clogItems)); + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogWidgetSubscriber.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogWidgetSubscriber.java new file mode 100644 index 00000000..128d3941 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionLogWidgetSubscriber.java @@ -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); + } + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionPageState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionPageState.java new file mode 100644 index 00000000..2fcd9891 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/CollectionPageState.java @@ -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 completionCounts; + + public CollectionPageState(int tabIdx, String pageName, ItemContainerState items, List completionCounts) { + this.tabIdx = tabIdx; + this.pageName = pageName; + this.items = items; + this.completionCounts = completionCounts; + } + + @Override + public Object get() { + Map 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)); + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/ConsumableState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/ConsumableState.java new file mode 100644 index 00000000..fa9ce52a --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/ConsumableState.java @@ -0,0 +1,7 @@ +package men.groupiron; + +public interface ConsumableState { + Object get(); + + String whoOwnsThis(); +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/DataManager.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/DataManager.java new file mode 100644 index 00000000..cbbd8b0f --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/DataManager.java @@ -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 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 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; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/DataState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/DataState.java new file mode 100644 index 00000000..31936fb4 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/DataState.java @@ -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 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 output) { + return consumeState((String) output.get("name"), output); + } + + public Object consumeState(String whoIsUpdating, Map 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); + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/DepositedItems.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/DepositedItems.java new file mode 100644 index 00000000..168e98d6 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/DepositedItems.java @@ -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 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; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/GroupIronmenTrackerConfig.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/GroupIronmenTrackerConfig.java new file mode 100644 index 00000000..fea17f9a --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/GroupIronmenTrackerConfig.java @@ -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 ""; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/GroupIronmenTrackerPlugin.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/GroupIronmenTrackerPlugin.java new file mode 100644 index 00000000..b22093cd --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/GroupIronmenTrackerPlugin.java @@ -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); + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/HttpRequestService.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/HttpRequestService.java new file mode 100644 index 00000000..9edf506e --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/HttpRequestService.java @@ -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() : ""; + } catch (Exception e) { + return ""; + } + } + + 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; + } + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/InteractingState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/InteractingState.java new file mode 100644 index 00000000..49b41849 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/InteractingState.java @@ -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; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/ItemContainerItem.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/ItemContainerItem.java new file mode 100644 index 00000000..27cc67de --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/ItemContainerItem.java @@ -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; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/ItemContainerState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/ItemContainerState.java new file mode 100644 index 00000000..5a91b0d2 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/ItemContainerState.java @@ -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 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 items) { + this.playerName = playerName; + this.items = items; + } + + @Nullable + public ItemContainerState add(ItemContainerState itemsToAdd) { + if (itemsToAdd == null || !itemsToAdd.whoOwnsThis().equals(whoOwnsThis())) return null; + Map thisItems = getItemMap(); + Map otherItems = itemsToAdd.getItemMap(); + List 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 thisItems = getItemMap(); + Map otherItems = other.getItemMap(); + List 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 getItemMap() { + Map 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 asFlatList() { + List 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; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/LocationState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/LocationState.java new file mode 100644 index 00000000..cae23bb8 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/LocationState.java @@ -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); + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/QuestState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/QuestState.java new file mode 100644 index 00000000..0f93e470 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/QuestState.java @@ -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 questStateMap; + private transient final String playerName; + private List 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 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; + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/QuiverState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/QuiverState.java new file mode 100644 index 00000000..3d77d7ae --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/QuiverState.java @@ -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); + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/ResourcesState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/ResourcesState.java new file mode 100644 index 00000000..14162480 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/ResourcesState.java @@ -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); + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/RunePouchState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/RunePouchState.java new file mode 100644 index 00000000..ae9cbe3c --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/RunePouchState.java @@ -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); + } +} diff --git a/group-ironmen-tracker-master/src/main/java/men/groupiron/SkillState.java b/group-ironmen-tracker-master/src/main/java/men/groupiron/SkillState.java new file mode 100644 index 00000000..6ac23632 --- /dev/null +++ b/group-ironmen-tracker-master/src/main/java/men/groupiron/SkillState.java @@ -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 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; + } +} diff --git a/group-ironmen-tracker-master/src/test/java/men/groupiron/GroupIronmenTrackerPluginTest.java b/group-ironmen-tracker-master/src/test/java/men/groupiron/GroupIronmenTrackerPluginTest.java new file mode 100644 index 00000000..20d85d94 --- /dev/null +++ b/group-ironmen-tracker-master/src/test/java/men/groupiron/GroupIronmenTrackerPluginTest.java @@ -0,0 +1,13 @@ +package men.groupiron; + +import net.runelite.client.RuneLite; +import net.runelite.client.externalplugins.ExternalPluginManager; + +public class GroupIronmenTrackerPluginTest +{ + public static void main(String[] args) throws Exception + { + ExternalPluginManager.loadBuiltin(GroupIronmenTrackerPlugin.class); + RuneLite.main(args); + } +} \ No newline at end of file