From a5aab68ea458573eef5b406738c84f54b2fff838 Mon Sep 17 00:00:00 2001 From: sonderau Date: Mon, 27 Oct 2025 08:31:47 +0800 Subject: [PATCH] First commit of tasks-tracker-plugin-master --- .gitignore | 15 +- tasks-tracker-plugin-master/.gitignore | 12 + tasks-tracker-plugin-master/LICENSE | 25 + tasks-tracker-plugin-master/README.md | 18 + tasks-tracker-plugin-master/build.gradle | 33 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + tasks-tracker-plugin-master/gradlew | 252 ++++++++ tasks-tracker-plugin-master/gradlew.bat | 94 +++ tasks-tracker-plugin-master/icon.png | Bin 0 -> 15928 bytes .../runelite-plugin.properties | 6 + tasks-tracker-plugin-master/settings.gradle | 1 + .../java/net/reldo/taskstracker/HtmlUtil.java | 41 ++ .../taskstracker/TasksTrackerConfig.java | 140 +++++ .../taskstracker/TasksTrackerPlugin.java | 584 ++++++++++++++++++ .../taskstracker/config/ConfigValues.java | 40 ++ .../net/reldo/taskstracker/data/Export.java | 94 +++ .../taskstracker/data/LongSerializer.java | 17 + .../reldo/taskstracker/data/TasksSummary.java | 24 + .../taskstracker/data/TrackerConfigStore.java | 94 +++ .../data/jsondatastore/FilterDataClient.java | 50 ++ .../data/jsondatastore/JsonDataStore.java | 6 + .../data/jsondatastore/ManifestClient.java | 44 ++ .../data/jsondatastore/TaskDataClient.java | 67 ++ .../jsondatastore/reader/DataStoreReader.java | 11 + .../reader/HttpDataStoreReader.java | 130 ++++ .../jsondatastore/types/FilterConfig.java | 51 ++ .../jsondatastore/types/FilterCustomItem.java | 11 + .../data/jsondatastore/types/FilterType.java | 7 + .../jsondatastore/types/FilterValueType.java | 10 + .../data/jsondatastore/types/Manifest.java | 10 + .../jsondatastore/types/TaskDefinition.java | 46 ++ .../types/TaskDefinitionSkill.java | 20 + .../types/TaskTypeDefinition.java | 105 ++++ .../taskstracker/data/reldo/ReldoImport.java | 13 + .../data/reldo/ReldoTaskSave.java | 14 + .../data/task/ConfigTaskSave.java | 19 + .../data/task/TaskFromStruct.java | 208 +++++++ .../taskstracker/data/task/TaskService.java | 306 +++++++++ .../taskstracker/data/task/TaskType.java | 147 +++++ .../data/task/filters/Filter.java | 8 + .../data/task/filters/FilterService.java | 47 ++ .../data/task/filters/ParamButtonFilter.java | 34 + .../task/filters/ParamDropdownFilter.java | 46 ++ .../net/reldo/taskstracker/panel/Colors.java | 10 + .../net/reldo/taskstracker/panel/Icons.java | 41 ++ .../taskstracker/panel/LoggedInPanel.java | 543 ++++++++++++++++ .../taskstracker/panel/LoggedOutPanel.java | 12 + .../reldo/taskstracker/panel/SortPanel.java | 85 +++ .../taskstracker/panel/SubFilterPanel.java | 122 ++++ .../taskstracker/panel/TaskListPanel.java | 202 ++++++ .../reldo/taskstracker/panel/TaskPanel.java | 429 +++++++++++++ .../taskstracker/panel/TaskPanelFactory.java | 8 + .../panel/TasksTrackerPluginPanel.java | 108 ++++ .../panel/components/CheckBox.java | 42 ++ .../panel/components/FixedWidthPanel.java | 15 + .../panel/components/MultiToggleButton.java | 148 +++++ .../panel/components/SearchBox.java | 53 ++ .../panel/components/TriToggleButton.java | 39 ++ .../taskstracker/panel/filters/ComboItem.java | 29 + .../filters/DynamicButtonFilterPanel.java | 131 ++++ .../filters/DynamicDropdownFilterPanel.java | 92 +++ .../panel/filters/FilterButtonPanel.java | 223 +++++++ .../panel/filters/FilterPanel.java | 8 + .../reldo/taskstracker/quests/DiaryData.java | 15 + .../taskstracker/quests/DiaryVarbits.java | 66 ++ .../reldo/taskstracker/quests/QuestData.java | 17 + .../net/reldo/taskstracker/eye-cross-grey.png | Bin 0 -> 179 bytes .../net/reldo/taskstracker/eye-red.png | Bin 0 -> 181 bytes .../resources/net/reldo/taskstracker/eye.png | Bin 0 -> 614 bytes .../net/reldo/taskstracker/minus.png | Bin 0 -> 5549 bytes .../panel/components/collapsed.png | Bin 0 -> 198 bytes .../complete_and_incomplete_icon.png | Bin 0 -> 248 bytes .../complete_button/complete_only_icon.png | Bin 0 -> 270 bytes .../complete_button/incomplete_only_icon.png | Bin 0 -> 265 bytes .../panel/components/expanded.png | Bin 0 -> 227 bytes .../components/filter_buttons_collapsed.png | Bin 0 -> 389 bytes .../components/filter_buttons_expanded.png | Bin 0 -> 403 bytes .../components/filter_menu_collapsed.png | Bin 0 -> 380 bytes .../panel/components/filter_menu_expanded.png | Bin 0 -> 389 bytes .../ignored_button/invisible_icon.png | Bin 0 -> 425 bytes .../ignored_button/semivisible_icon.png | Bin 0 -> 398 bytes .../ignored_button/visible_icon.png | Bin 0 -> 312 bytes .../panel/components/no_skill.png | Bin 0 -> 694 bytes .../taskstracker/panel/components/search.png | Bin 0 -> 280 bytes .../components/sort_button/ascending_icon.png | Bin 0 -> 296 bytes .../sort_button/descending_icon.png | Bin 0 -> 288 bytes .../task_areas/league4/asgarnia.png | Bin 0 -> 610 bytes .../components/task_areas/league4/desert.png | Bin 0 -> 642 bytes .../task_areas/league4/fremennik.png | Bin 0 -> 606 bytes .../components/task_areas/league4/global.png | Bin 0 -> 245 bytes .../task_areas/league4/kandarin.png | Bin 0 -> 613 bytes .../components/task_areas/league4/karamja.png | Bin 0 -> 610 bytes .../components/task_areas/league4/kourend.png | Bin 0 -> 346 bytes .../task_areas/league4/misthalin.png | Bin 0 -> 619 bytes .../task_areas/league4/morytania.png | Bin 0 -> 661 bytes .../task_areas/league4/tirannwn.png | Bin 0 -> 627 bytes .../task_areas/league4/wilderness.png | Bin 0 -> 630 bytes .../task_categories/league4/achievement.png | Bin 0 -> 156 bytes .../task_categories/league4/combat.png | Bin 0 -> 3034 bytes .../task_categories/league4/minigame.png | Bin 0 -> 163 bytes .../task_categories/league4/other.png | Bin 0 -> 197 bytes .../task_categories/league4/quest.png | Bin 0 -> 163 bytes .../task_categories/league4/skill.png | Bin 0 -> 167 bytes .../components/task_tiers/combat/easy.png | Bin 0 -> 13481 bytes .../components/task_tiers/combat/elite.png | Bin 0 -> 1018 bytes .../task_tiers/combat/grandmaster.png | Bin 0 -> 987 bytes .../components/task_tiers/combat/hard.png | Bin 0 -> 1047 bytes .../components/task_tiers/combat/master.png | Bin 0 -> 875 bytes .../components/task_tiers/combat/medium.png | Bin 0 -> 1102 bytes .../components/task_tiers/league3/easy.png | Bin 0 -> 206 bytes .../components/task_tiers/league3/elite.png | Bin 0 -> 216 bytes .../components/task_tiers/league3/hard.png | Bin 0 -> 215 bytes .../components/task_tiers/league3/master.png | Bin 0 -> 223 bytes .../components/task_tiers/league3/medium.png | Bin 0 -> 206 bytes .../tracked_and_untracked_icon.png | Bin 0 -> 174 bytes .../tracked_button/tracked_icon.png | Bin 0 -> 165 bytes .../tracked_button/untracked_icon.png | Bin 0 -> 142 bytes .../net/reldo/taskstracker/panel_icon.png | Bin 0 -> 15928 bytes .../resources/net/reldo/taskstracker/plus.png | Bin 0 -> 606 bytes .../taskstracker/TasksTrackerPluginTest.java | 13 + 121 files changed, 5353 insertions(+), 3 deletions(-) create mode 100644 tasks-tracker-plugin-master/.gitignore create mode 100644 tasks-tracker-plugin-master/LICENSE create mode 100644 tasks-tracker-plugin-master/README.md create mode 100644 tasks-tracker-plugin-master/build.gradle create mode 100644 tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.jar create mode 100644 tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.properties create mode 100644 tasks-tracker-plugin-master/gradlew create mode 100644 tasks-tracker-plugin-master/gradlew.bat create mode 100644 tasks-tracker-plugin-master/icon.png create mode 100644 tasks-tracker-plugin-master/runelite-plugin.properties create mode 100644 tasks-tracker-plugin-master/settings.gradle create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/HtmlUtil.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/TasksTrackerConfig.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/TasksTrackerPlugin.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/config/ConfigValues.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/Export.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/LongSerializer.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/TasksSummary.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/TrackerConfigStore.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/FilterDataClient.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/JsonDataStore.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/ManifestClient.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/TaskDataClient.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/reader/DataStoreReader.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/reader/HttpDataStoreReader.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterConfig.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterCustomItem.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterType.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterValueType.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/Manifest.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskDefinition.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskDefinitionSkill.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskTypeDefinition.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/reldo/ReldoImport.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/reldo/ReldoTaskSave.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/ConfigTaskSave.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskFromStruct.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskService.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskType.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/Filter.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/FilterService.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/ParamButtonFilter.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/ParamDropdownFilter.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/Colors.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/Icons.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/LoggedInPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/LoggedOutPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/SortPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/SubFilterPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskListPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskPanelFactory.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TasksTrackerPluginPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/CheckBox.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/FixedWidthPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/MultiToggleButton.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/SearchBox.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/TriToggleButton.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/ComboItem.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/DynamicButtonFilterPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/DynamicDropdownFilterPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/FilterButtonPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/FilterPanel.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/DiaryData.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/DiaryVarbits.java create mode 100644 tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/QuestData.java create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/eye-cross-grey.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/eye-red.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/eye.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/minus.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/collapsed.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/complete_button/complete_and_incomplete_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/complete_button/complete_only_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/complete_button/incomplete_only_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/expanded.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_buttons_collapsed.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_buttons_expanded.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_menu_collapsed.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_menu_expanded.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/ignored_button/invisible_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/ignored_button/semivisible_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/ignored_button/visible_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/no_skill.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/search.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/sort_button/ascending_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/sort_button/descending_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/asgarnia.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/desert.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/fremennik.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/global.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/kandarin.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/karamja.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/kourend.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/misthalin.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/morytania.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/tirannwn.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/wilderness.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/achievement.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/combat.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/minigame.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/other.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/quest.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/skill.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/easy.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/elite.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/grandmaster.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/hard.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/master.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/medium.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/easy.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/elite.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/hard.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/master.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/medium.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/tracked_and_untracked_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/tracked_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/untracked_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel_icon.png create mode 100644 tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/plus.png create mode 100644 tasks-tracker-plugin-master/src/test/java/net/reldo/taskstracker/TasksTrackerPluginTest.java diff --git a/.gitignore b/.gitignore index 4c7b9a18..aecbd17d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,15 @@ group-ironmen-tracker-master/bin/ group-ironmen-tracker-master/spotless/ -tasks-tracker-plugin-master/* - - +tasks-tracker-plugin-master/.gradle +tasks-tracker-plugin-master/build +tasks-tracker-plugin-master/.idea/ +tasks-tracker-plugin-master/.project +tasks-tracker-plugin-master/.settings/ +tasks-tracker-plugin-master/.classpath +tasks-tracker-plugin-master/nbactions.xml +tasks-tracker-plugin-master/nb-configuration.xml +tasks-tracker-plugin-master/nbproject/ +tasks-tracker-plugin-master/.run +tasks-tracker-plugin-master/bin +tasks-tracker-plugin-master/**/FileDataStoreReader.java diff --git a/tasks-tracker-plugin-master/.gitignore b/tasks-tracker-plugin-master/.gitignore new file mode 100644 index 00000000..fa54513f --- /dev/null +++ b/tasks-tracker-plugin-master/.gitignore @@ -0,0 +1,12 @@ +.gradle +build +.idea/ +.project +.settings/ +.classpath +nbactions.xml +nb-configuration.xml +nbproject/ +.run +bin +**/FileDataStoreReader.java \ No newline at end of file diff --git a/tasks-tracker-plugin-master/LICENSE b/tasks-tracker-plugin-master/LICENSE new file mode 100644 index 00000000..b4d4a96c --- /dev/null +++ b/tasks-tracker-plugin-master/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2021, Tyler Hardy +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/tasks-tracker-plugin-master/README.md b/tasks-tracker-plugin-master/README.md new file mode 100644 index 00000000..9e3f1570 --- /dev/null +++ b/tasks-tracker-plugin-master/README.md @@ -0,0 +1,18 @@ +# Tasks Tracker +![image](https://img.shields.io/endpoint?url=https://api.runelite.net/pluginhub/shields/installs/plugin/tasks-tracker) +![image](https://img.shields.io/endpoint?url=https://api.runelite.net/pluginhub/shields/rank/plugin/tasks-tracker) + + +Provides a RuneLite panel to track & filter tasks for Combat Achievements and Leagues. + +## Support +Visit [our Discord](https://discord.gg/eCeKwhEzyK) for live support + +## Features +* View tasks & progress in RuneLite panel without opening game UI +* Search tasks by text or filters +* Track tasks in a tracked list tab +* Export for external services + +![image](https://user-images.githubusercontent.com/17709869/146846182-de573b11-ee53-482e-b3d2-a103f86d6089.png) +![image](https://user-images.githubusercontent.com/17709869/152713557-4f153acf-ff68-4299-852a-8898b5edb34c.gif) diff --git a/tasks-tracker-plugin-master/build.gradle b/tasks-tracker-plugin-master/build.gradle new file mode 100644 index 00000000..b6a006d1 --- /dev/null +++ b/tasks-tracker-plugin-master/build.gradle @@ -0,0 +1,33 @@ +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.30' + annotationProcessor 'org.projectlombok:lombok:1.18.30' + + testImplementation 'junit:junit:4.12' + testImplementation group: 'net.runelite', name:'client', version: runeLiteVersion + testImplementation group: 'net.runelite', name:'jshell', version: runeLiteVersion + testImplementation group: 'com.squareup.okhttp3', name: 'mockwebserver', version: '3.14.9' +} + +group = 'net.reldo' +version = '1.0-SNAPSHOT' + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + options.release.set(11) +} diff --git a/tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.jar b/tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 zcma&O1CT9Y(k9%tZQHhO+qUh#ZQHhO+qmuS+qP|E@9xZO?0h@l{(r>DQ>P;GjjD{w zH}lENr;dU&FbEU?00aa80D$0M0RRB{U*7-#kbjS|qAG&4l5%47zyJ#WrfA#1$1Ctx zf&Z_d{GW=lf^w2#qRJ|CvSJUi(^E3iv~=^Z(zH}F)3Z%V3`@+rNB7gTVU{Bb~90p|f+0(v;nz01EG7yDMX9@S~__vVgv%rS$+?IH+oZ03D5zYrv|^ zC1J)SruYHmCki$jLBlTaE5&dFG9-kq3!^i>^UQL`%gn6)jz54$WDmeYdsBE9;PqZ_ zoGd=P4+|(-u4U1dbAVQrFWoNgNd;0nrghPFbQrJctO>nwDdI`Q^i0XJDUYm|T|RWc zZ3^Qgo_Qk$%Fvjj-G}1NB#ZJqIkh;kX%V{THPqOyiq)d)0+(r9o(qKlSp*hmK#iIY zA^)Vr$-Hz<#SF=0@tL@;dCQsm`V9s1vYNq}K1B)!XSK?=I1)tX+bUV52$YQu*0%fnWEukW>mxkz+%3-S!oguE8u#MGzST8_Dy^#U?fA@S#K$S@9msUiX!gd_ow>08w5)nX{-KxqMOo7d?k2&?Vf z&diGDtZr(0cwPe9z9FAUSD9KC)7(n^lMWuayCfxzy8EZsns%OEblHFSzP=cL6}?J| z0U$H!4S_TVjj<`6dy^2j`V`)mC;cB%* z8{>_%E1^FH!*{>4a7*C1v>~1*@TMcLK{7nEQ!_igZC}ikJ$*<$yHy>7)oy79A~#xE zWavoJOIOC$5b6*q*F_qN1>2#MY)AXVyr$6x4b=$x^*aqF*L?vmj>Mgv+|ITnw_BoW zO?jwHvNy^prH{9$rrik1#fhyU^MpFqF2fYEt(;4`Q&XWOGDH8k6M=%@fics4ajI;st# zCU^r1CK&|jzUhRMv;+W~6N;u<;#DI6cCw-otsc@IsN3MoSD^O`eNflIoR~l4*&-%RBYk@gb^|-JXs&~KuSEmMxB}xSb z@K76cXD=Y|=I&SNC2E+>Zg?R6E%DGCH5J1nU!A|@eX9oS(WPaMm==k2s_ueCqdZw| z&hqHp)47`c{BgwgvY2{xz%OIkY1xDwkw!<0veB#yF4ZKJyabhyyVS`gZepcFIk%e2 zTcrmt2@-8`7i-@5Nz>oQWFuMC_KlroCl(PLSodswHqJ3fn<;gxg9=}~3x_L3P`9Sn zChIf}8vCHvTriz~T2~FamRi?rh?>3bX1j}%bLH+uFX+p&+^aXbOK7clZxdU~6Uxgy z8R=obwO4dL%pmVo*Ktf=lH6hnlz_5k3cG;m8lgaPp~?eD!Yn2kf)tU6PF{kLyn|oI@eQ`F z3IF7~Blqg8-uwUuWZScRKn%c2_}dXB6Dx_&xR*n9M9LXasJhtZdr$vBY!rP{c@=)& z#!?L$2UrkvClwQO>U*fSMs67oSj2mxiJ$t;E|>q%Kh_GzzWWO&3;ufU%2z%ucBU8H z3WIwr$n)cfCXR&>tyB7BcSInK>=ByZA%;cVEJhcg<#6N{aZC4>K41XF>ZgjG`z_u& zGY?;Ad?-sgiOnI`oppF1o1Gurqbi*;#x2>+SSV6|1^G@ooVy@fg?wyf@0Y!UZ4!}nGuLeC^l)6pwkh|oRY`s1Pm$>zZ3u-83T|9 zGaKJIV3_x+u1>cRibsaJpJqhcm%?0-L;2 zitBrdRxNmb0OO2J%Y&Ym(6*`_P3&&5Bw157{o7LFguvxC$4&zTy#U=W*l&(Q2MNO} zfaUwYm{XtILD$3864IA_nn34oVa_g^FRuHL5wdUd)+W-p-iWCKe8m_cMHk+=? zeKX)M?Dt(|{r5t7IenkAXo%&EXIb-i^w+0CX0D=xApC=|Xy(`xy+QG^UyFe z+#J6h_&T5i#sV)hj3D4WN%z;2+jJcZxcI3*CHXGmOF3^)JD5j&wfX)e?-|V0GPuA+ zQFot%aEqGNJJHn$!_}#PaAvQ^{3-Ye7b}rWwrUmX53(|~i0v{}G_sI9uDch_brX&6 zWl5Ndj-AYg(W9CGfQf<6!YmY>Ey)+uYd_JNXH=>|`OH-CDCmcH(0%iD_aLlNHKH z7bcW-^5+QV$jK?R*)wZ>r9t}loM@XN&M-Pw=F#xn(;u3!(3SXXY^@=aoj70;_=QE9 zGghsG3ekq#N||u{4We_25U=y#T*S{4I{++Ku)> zQ!DZW;pVcn>b;&g2;YE#+V`v*Bl&Y-i@X6D*OpNA{G@JAXho&aOk(_j^weW{#3X5Y z%$q_wpb07EYPdmyH(1^09i$ca{O<}7) zRWncXdSPgBE%BM#by!E>tdnc$8RwUJg1*x($6$}ae$e9Knj8gvVZe#bLi!<+&BkFj zg@nOpDneyc+hU9P-;jmOSMN|*H#>^Ez#?;%C3hg_65leSUm;iz)UkW)jX#p)e&S&M z1|a?wDzV5NVnlhRBCd_;F87wp>6c<&nkgvC+!@KGiIqWY4l}=&1w7|r6{oBN8xyzh zG$b#2=RJp_iq6)#t5%yLkKx(0@D=C3w+oiXtSuaQ%I1WIb-eiE$d~!)b@|4XLy!CZ z9p=t=%3ad@Ep+<9003D2KZ5VyP~_n$=;~r&YUg5UZ0KVD&tR1DHy9x)qWtKJp#Kq# zP*8p#W(8JJ_*h_3W}FlvRam?<4Z+-H77^$Lvi+#vmhL9J zJ<1SV45xi;SrO2f=-OB(7#iNA5)x1uNC-yNxUw|!00vcW2PufRm>e~toH;M0Q85MQLWd?3O{i8H+5VkR@l9Dg-ma ze2fZ%>G(u5(k9EHj2L6!;(KZ8%8|*-1V|B#EagbF(rc+5iL_5;Eu)L4Z-V;0HfK4d z*{utLse_rvHZeQ>V5H=f78M3Ntg1BPxFCVD{HbNA6?9*^YIq;B-DJd{Ca2L#)qWP? zvX^NhFmX?CTWw&Ns}lgs;r3i+Bq@y}Ul+U%pzOS0Fcv9~aB(0!>GT0)NO?p=25LjN z2bh>6RhgqD7bQj#k-KOm@JLgMa6>%-ok1WpOe)FS^XOU{c?d5shG(lIn3GiVBxmg`u%-j=)^v&pX1JecJics3&jvPI)mDut52? z3jEA)DM%}BYbxxKrizVYwq?(P&19EXlwD9^-6J+4!}9{ywR9Gk42jjAURAF&EO|~N z)?s>$Da@ikI4|^z0e{r`J8zIs>SpM~Vn^{3fArRu;?+43>lD+^XtUcY1HidJwnR6+ z!;oG2=B6Z_=M%*{z-RaHc(n|1RTKQdNjjV!Pn9lFt^4w|AeN06*j}ZyhqZ^!-=cyGP_ShV1rGxkx8t zB;8`h!S{LD%ot``700d0@Grql(DTt4Awgmi+Yr0@#jbe=2#UkK%rv=OLqF)9D7D1j z!~McAwMYkeaL$~kI~90)5vBhBzWYc3Cj1WI0RS`z000R8-@ET0dA~*r(gSiCJmQMN&4%1D zyVNf0?}sBH8zNbBLn>~(W{d3%@kL_eQ6jEcR{l>C|JK z(R-fA!z|TTRG40|zv}7E@PqCAXP3n`;%|SCQ|ZS%ym$I{`}t3KPL&^l5`3>yah4*6 zifO#{VNz3)?ZL$be;NEaAk9b#{tV?V7 zP|wf5YA*1;s<)9A4~l3BHzG&HH`1xNr#%){4xZ!jq%o=7nN*wMuXlFV{HaiQLJ`5G zBhDi#D(m`Q1pLh@Tq+L;OwuC52RdW7b8}~60WCOK5iYMUad9}7aWBuILb({5=z~YF zt?*Jr5NG+WadM{mDL>GyiByCuR)hd zA=HM?J6l1Xv0Dl+LW@w$OTcEoOda^nFCw*Sy^I@$sSuneMl{4ys)|RY#9&NxW4S)9 zq|%83IpslTLoz~&vTo!Ga@?rj_kw{|k{nv+w&Ku?fyk4Ki4I?);M|5Axm)t+BaE)D zm(`AQ#k^DWrjbuXoJf2{Aj^KT zFb1zMSqxq|vceV+Mf-)$oPflsO$@*A0n0Z!R{&(xh8s}=;t(lIy zv$S8x>m;vQNHuRzoaOo?eiWFe{0;$s`Bc+Osz~}Van${u;g(su`3lJ^TEfo~nERfP z)?aFzpDgnLYiERsKPu|0tq4l2wT)Atr6Qb%m-AUn6HnCue*yWICp7TjW$@sO zm5rm4aTcPQ(rfi7a`xP7cKCFrJD}*&_~xgLyr^-bmsL}y;A5P|al8J3WUoBSjqu%v zxC;mK!g(7r6RRJ852Z~feoC&sD3(6}^5-uLK8o)9{8L_%%rItZK9C){UxB|;G>JbP zsRRtS4-3B*5c+K2kvmgZK8472%l>3cntWUOVHxB|{Ay~aOg5RN;{PJgeVD*H%ac+y!h#wi%o2bF2Ca8IyMyH{>4#{E_8u^@+l-+n=V}Sq?$O z{091@v%Bd*3pk0^2UtiF9Z+(a@wy6 zUdw8J*ze$K#=$48IBi1U%;hmhO>lu!uU;+RS}p&6@rQila7WftH->*A4=5W|Fmtze z)7E}jh@cbmr9iup^i%*(uF%LG&!+Fyl@LFA-}Ca#bxRfDJAiR2dt6644TaYw1Ma79 zt8&DYj31j^5WPNf5P&{)J?WlCe@<3u^78wnd(Ja4^a>{^Tw}W>|Cjt^If|7l^l)^Q zbz|7~CF(k_9~n|h;ysZ+jHzkXf(*O*@5m zLzUmbHp=x!Q|!9NVXyipZ3)^GuIG$k;D)EK!a5=8MFLI_lpf`HPKl=-Ww%z8H_0$j ztJ||IfFG1lE9nmQ0+jPQy zCBdKkjArH@K7jVcMNz);Q(Q^R{d5G?-kk;Uu_IXSyWB)~KGIizZL(^&qF;|1PI7!E zTP`%l)gpX|OFn&)M%txpQ2F!hdA~hX1Cm5)IrdljqzRg!f{mN%G~H1&oqe`5eJCIF zHdD7O;AX-{XEV(a`gBFJ9ews#CVS2y!&>Cm_dm3C8*n3MA*e67(WC?uP@8TXuMroq z{#w$%z@CBIkRM7?}Xib+>hRjy?%G!fiw8! z8(gB+8J~KOU}yO7UGm&1g_MDJ$IXS!`+*b*QW2x)9>K~Y*E&bYMnjl6h!{17_8d!%&9D`a7r&LKZjC<&XOvTRaKJ1 zUY@hl5^R&kZl3lU3njk`3dPzxj$2foOL26r(9zsVF3n_F#v)s5vv3@dgs|lP#eylq62{<-vczqP!RpVBTgI>@O6&sU>W|do17+#OzQ7o5A$ICH z?GqwqnK^n2%LR;$^oZM;)+>$X3s2n}2jZ7CdWIW0lnGK-b#EG01)P@aU`pg}th&J-TrU`tIpb5t((0eu|!u zQz+3ZiOQ^?RxxK4;zs=l8q!-n7X{@jSwK(iqNFiRColuEOg}!7cyZi`iBX4g1pNBj zAPzL?P^Ljhn;1$r8?bc=#n|Ed7wB&oHcw()&*k#SS#h}jO?ZB246EGItsz*;^&tzp zu^YJ0=lwsi`eP_pU8}6JA7MS;9pfD;DsSsLo~ogzMNP70@@;Fm8f0^;>$Z>~}GWRw!W5J3tNX*^2+1f3hz{~rIzJo z6W%J(H!g-eI_J1>0juX$X4Cl6i+3wbc~k146UIX&G22}WE>0ga#WLsn9tY(&29zBvH1$`iWtTe zG2jYl@P!P)eb<5DsR72BdI7-zP&cZNI{7q3e@?N8IKc4DE#UVr->|-ryuJXk^u^>4 z$3wE~=q390;XuOQP~TNoDR?#|NSPJ%sTMInA6*rJ%go|=YjGe!B>z6u$IhgQSwoV* zjy3F2#I>uK{42{&IqP59)Y(1*Z>>#W8rCf4_eVsH)`v!P#^;BgzKDR`ARGEZzkNX+ zJUQu=*-ol=Xqqt5=`=pA@BIn@6a9G8C{c&`i^(i+BxQO9?YZ3iu%$$da&Kb?2kCCo zo7t$UpSFWqmydXf@l3bVJ=%K?SSw)|?srhJ-1ZdFu*5QhL$~-IQS!K1s@XzAtv6*Y zl8@(5BlWYLt1yAWy?rMD&bwze8bC3-GfNH=p zynNFCdxyX?K&G(ZZ)afguQ2|r;XoV^=^(;Cku#qYn4Lus`UeKt6rAlFo_rU`|Rq z&G?~iWMBio<78of-2X(ZYHx~=U0Vz4btyXkctMKdc9UM!vYr~B-(>)(Hc|D zMzkN4!PBg%tZoh+=Gba!0++d193gbMk2&krfDgcbx0jI92cq?FFESVg0D$>F+bil} zY~$)|>1HZsX=5sAZ2WgPB5P=8X#TI+NQ(M~GqyVB53c6IdX=k>Wu@A0Svf5#?uHaF zsYn|koIi3$(%GZ2+G+7Fv^lHTb#5b8sAHSTnL^qWZLM<(1|9|QFw9pnRU{svj}_Al zL)b9>fN{QiA($8peNEJyy`(a{&uh-T4_kdZFIVsKKVM(?05}76EEz?#W za^fiZOAd14IJ4zLX-n7Lq0qlQ^lW8Cvz4UKkV9~P}>sq0?xD3vg+$4vLm~C(+ zM{-3Z#qnZ09bJ>}j?6ry^h+@PfaD7*jZxBEY4)UG&daWb??6)TP+|3#Z&?GL?1i+280CFsE|vIXQbm| zM}Pk!U`U5NsNbyKzkrul-DzwB{X?n3E6?TUHr{M&+R*2%yOiXdW-_2Yd6?38M9Vy^ z*lE%gA{wwoSR~vN0=no}tP2Ul5Gk5M(Xq`$nw#ndFk`tcpd5A=Idue`XZ!FS>Q zG^0w#>P4pPG+*NC9gLP4x2m=cKP}YuS!l^?sHSFftZy{4CoQrb_ z^20(NnG`wAhMI=eq)SsIE~&Gp9Ne0nD4%Xiu|0Fj1UFk?6avDqjdXz{O1nKao*46y zT8~iA%Exu=G#{x=KD;_C&M+Zx4+n`sHT>^>=-1YM;H<72k>$py1?F3#T1*ef9mLZw z5naLQr?n7K;2l+{_uIw*_1nsTn~I|kkCgrn;|G~##hM;9l7Jy$yJfmk+&}W@JeKcF zx@@Woiz8qdi|D%aH3XTx5*wDlbs?dC1_nrFpm^QbG@wM=i2?Zg;$VK!c^Dp8<}BTI zyRhAq@#%2pGV49*Y5_mV4+OICP|%I(dQ7x=6Ob}>EjnB_-_18*xrY?b%-yEDT(wrO z9RY2QT0`_OpGfMObKHV;QLVnrK%mc?$WAdIT`kJQT^n%GuzE7|9@k3ci5fYOh(287 zuIbg!GB3xLg$YN=n)^pHGB0jH+_iIiC=nUcD;G6LuJsjn2VI1cyZx=a?ShCsF==QK z;q~*m&}L<-cb+mDDXzvvrRsybcgQ;Vg21P(uLv5I+eGc7o7tc6`;OA9{soHFOz zT~2?>Ts}gprIX$wRBb4yE>ot<8+*Bv`qbSDv*VtRi|cyWS>)Fjs>fkNOH-+PX&4(~ z&)T8Zam2L6puQl?;5zg9h<}k4#|yH9czHw;1jw-pwBM*O2hUR6yvHATrI%^mvs9q_ z&ccT0>f#eDG<^WG^q@oVqlJrhxH)dcq2cty@l3~|5#UDdExyXUmLQ}f4#;6fI{f^t zDCsgIJ~0`af%YR%Ma5VQq-p21k`vaBu6WE?66+5=XUd%Ay%D$irN>5LhluRWt7 zov-=f>QbMk*G##&DTQyou$s7UqjjW@k6=!I@!k+S{pP8R(2=e@io;N8E`EOB;OGoI zw6Q+{X1_I{OO0HPpBz!X!@`5YQ2)t{+!?M_iH25X(d~-Zx~cXnS9z>u?+If|iNJbx zyFU2d1!ITX64D|lE0Z{dLRqL1Ajj=CCMfC4lD3&mYR_R_VZ>_7_~|<^o*%_&jevU+ zQ4|qzci=0}Jydw|LXLCrOl1_P6Xf@c0$ieK2^7@A9UbF{@V_0p%lqW|L?5k>bVM8|p5v&2g;~r>B8uo<4N+`B zH{J)h;SYiIVx@#jI&p-v3dwL5QNV1oxPr8J%ooezTnLW>i*3Isb49%5i!&ac_dEXv zvXmVUck^QHmyrF8>CGXijC_R-y(Qr{3Zt~EmW)-nC!tiH`wlw5D*W7Pip;T?&j%kX z6DkZX4&}iw>hE(boLyjOoupf6JpvBG8}jIh!!VhnD0>}KSMMo{1#uU6kiFcA04~|7 zVO8eI&x1`g4CZ<2cYUI(n#wz2MtVFHx47yE5eL~8bot~>EHbevSt}LLMQX?odD{Ux zJMnam{d)W4da{l7&y-JrgiU~qY3$~}_F#G7|MxT)e;G{U`In&?`j<5D->}cb{}{T(4DF0BOk-=1195KB-E*o@c?`>y#4=dMtYtSY=&L{!TAjFVcq0y@AH`vH! z$41+u!Ld&}F^COPgL(EE{0X7LY&%D7-(?!kjFF7=qw<;`V{nwWBq<)1QiGJgUc^Vz ztMUlq1bZqKn17|6x6iAHbWc~l1HcmAxr%$Puv!znW)!JiukwIrqQ00|H$Z)OmGG@= zv%A8*4cq}(?qn4rN6o`$Y))(MyXr8R<2S^J+v(wmFmtac!%VOfN?&(8Nr!T@kV`N; z*Q33V3t`^rN&aBiHet)18wy{*wi1=W!B%B-Q6}SCrUl$~Hl{@!95ydml@FK8P=u4s z4e*7gV2s=YxEvskw2Ju!2%{8h01rx-3`NCPc(O zH&J0VH5etNB2KY6k4R@2Wvl^Ck$MoR3=)|SEclT2ccJ!RI9Nuter7u9@;sWf-%um;GfI!=eEIQ2l2p_YWUd{|6EG ze{yO6;lMc>;2tPrsNdi@&1K6(1;|$xe8vLgiouj%QD%gYk`4p{Ktv9|j+!OF-P?@p z;}SV|oIK)iwlBs+`ROXkhd&NK zzo__r!B>tOXpBJMDcv!Mq54P+n4(@dijL^EpO1wdg~q+!DT3lB<>9AANSe!T1XgC=J^)IP0XEZ()_vpu!!3HQyJhwh?r`Ae%Yr~b% zO*NY9t9#qWa@GCPYOF9aron7thfWT`eujS4`t2uG6)~JRTI;f(ZuoRQwjZjp5Pg34 z)rp$)Kr?R+KdJ;IO;pM{$6|2y=k_siqvp%)2||cHTe|b5Ht8&A{wazGNca zX$Ol?H)E_R@SDi~4{d-|8nGFhZPW;Cts1;08TwUvLLv&_2$O6Vt=M)X;g%HUr$&06 zISZb(6)Q3%?;3r~*3~USIg=HcJhFtHhIV(siOwV&QkQe#J%H9&E21!C*d@ln3E@J* zVqRO^<)V^ky-R|%{(9`l-(JXq9J)1r$`uQ8a}$vr9E^nNiI*thK8=&UZ0dsFN_eSl z(q~lnD?EymWLsNa3|1{CRPW60>DSkY9YQ;$4o3W7Ms&@&lv9eH!tk~N&dhqX&>K@} zi1g~GqglxkZ5pEFkllJ)Ta1I^c&Bt6#r(QLQ02yHTaJB~- zCcE=5tmi`UA>@P=1LBfBiqk)HB4t8D?02;9eXj~kVPwv?m{5&!&TFYhu>3=_ zsGmYZ^mo*-j69-42y&Jj0cBLLEulNRZ9vXE)8~mt9C#;tZs;=#M=1*hebkS;7(aGf zcs7zH(I8Eui9UU4L--))yy`&d&$In&VA2?DAEss4LAPCLd>-$i?lpXvn!gu^JJ$(DoUlc6wE98VLZ*z`QGQov5l4Fm_h?V-;mHLYDVOwKz7>e4+%AzeO>P6v}ndPW| zM>m#6Tnp7K?0mbK=>gV}=@k*0Mr_PVAgGMu$j+pWxzq4MAa&jpCDU&-5eH27Iz>m^ zax1?*HhG%pJ((tkR(V(O(L%7v7L%!_X->IjS3H5kuXQT2!ow(;%FDE>16&3r){!ex zhf==oJ!}YU89C9@mfDq!P3S4yx$aGB?rbtVH?sHpg?J5C->!_FHM%Hl3#D4eplxzQ zRA+<@LD%LKSkTk2NyWCg7u=$%F#;SIL44~S_OGR}JqX}X+=bc@swpiClB`Zbz|f!4 z7Ysah7OkR8liXfI`}IIwtEoL}(URrGe;IM8%{>b1SsqXh)~w}P>yiFRaE>}rEnNkT z!HXZUtxUp1NmFm)Dm@-{FI^aRQqpSkz}ZSyKR%Y}YHNzBk)ZIp} zMtS=aMvkgWKm9&oTcU0?S|L~CDqA+sHpOxwnswF-fEG)cXCzUR?ps@tZa$=O)=L+5 zf%m58cq8g_o}3?Bhh+c!w4(7AjxwQ3>WnVi<{{38g7yFboo>q|+7qs<$8CPXUFAN< zG&}BHbbyQ5n|qqSr?U~GY{@GJ{(Jny{bMaOG{|IkUj7tj^9pa9|FB_<+KHLxSxR;@ zHpS$4V)PP+tx}22fWx(Ku9y+}Ap;VZqD0AZW4gCDTPCG=zgJmF{|x;(rvdM|2|9a}cex6xrMkERnkE;}jvU-kmzd%_J50$M`lIPCKf+^*zL=@LW`1SaEc%=m zQ+lT06Gw+wVwvQ9fZ~#qd430v2HndFsBa9WjD0P}K(rZYdAt^5WQIvb%D^Q|pkVE^ zte$&#~zmULFACGfS#g=2OLOnIf2Of-k!(BIHjs77nr!5Q1*I9 z1%?=~#Oss!rV~?-6Gm~BWJiA4mJ5TY&iPm_$)H1_rTltuU1F3I(qTQ^U$S>%$l z)Wx1}R?ij0idp@8w-p!Oz{&*W;v*IA;JFHA9%nUvVDy7Q8woheC#|8QuDZb-L_5@R zOqHwrh|mVL9b=+$nJxM`3eE{O$sCt$UK^2@L$R(r^-_+z?lOo+me-VW=Zw z-Bn>$4ovfWd%SPY`ab-u9{INc*k2h+yH%toDHIyqQ zO68=u`N}RIIs7lsn1D){)~%>ByF<>i@qFb<-axvu(Z+6t7v<^z&gm9McRB~BIaDn$ z#xSGT!rzgad8o>~kyj#h1?7g96tOcCJniQ+*#=b7wPio>|6a1Z?_(TS{)KrPe}(8j z!#&A=k(&Pj^F;r)CI=Z{LVu>uj!_W1q4b`N1}E(i%;BWjbEcnD=mv$FL$l?zS6bW!{$7j1GR5ocn94P2u{ z70tAAcpqtQo<@cXw~@i-@6B23;317|l~S>CB?hR5qJ%J3EFgyBdJd^fHZu7AzHF(BQ!tyAz^L0`X z23S4Fe{2X$W0$zu9gm%rg~A>ijaE#GlYlrF9$ds^QtaszE#4M(OLVP2O-;XdT(XIC zatwzF*)1c+t~c{L=fMG8Z=k5lv>U0;C{caN1NItnuSMp)6G3mbahu>E#sj&oy94KC zpH}8oEw{G@N3pvHhp{^-YaZeH;K+T_1AUv;IKD<=mv^&Ueegrb!yf`4VlRl$M?wsl zZyFol(2|_QM`e_2lYSABpKR{{NlxlDSYQNkS;J66aT#MSiTx~;tUmvs-b*CrR4w=f z8+0;*th6kfZ3|5!Icx3RV11sp=?`0Jy3Fs0N4GZQMN=8HmT6%x9@{Dza)k}UwL6JT zHRDh;%!XwXr6yuuy`4;Xsn0zlR$k%r%9abS1;_v?`HX_hI|+EibVnlyE@3aL5vhQq zlIG?tN^w@0(v9M*&L+{_+RQZw=o|&BRPGB>e5=ys7H`nc8nx)|-g;s7mRc7hg{GJC zAe^vCIJhajmm7C6g! zL&!WAQ~5d_5)00?w_*|*H>3$loHrvFbitw#WvLB!JASO?#5Ig5$Ys10n>e4|3d;tS zELJ0|R4n3Az(Fl3-r^QiV_C;)lQ1_CW{5bKS15U|E9?ZgLec@%kXr84>5jV2a5v=w z?pB1GPdxD$IQL4)G||B_lI+A=08MUFFR4MxfGOu07vfIm+j=z9tp~5i_6jb`tR>qV z$#`=BQ*jpCjm$F0+F)L%xRlnS%#&gro6PiRfu^l!EVan|r3y}AHJQOORGx4~ z&<)3=K-tx518DZyp%|!EqpU!+X3Et7n2AaC5(AtrkW>_57i}$eqs$rupubg0a1+WO zGHZKLN2L0D;ab%{_S1Plm|hx8R?O14*w*f&2&bB050n!R2by zw!@XOQx$SqZ5I<(Qu$V6g>o#A!JVwErWv#(Pjx=KeS0@hxr4?13zj#oWwPS(7Ro|v z>Mp@Kmxo79q|}!5qtX2-O@U&&@6s~!I&)1WQIl?lTnh6UdKT_1R640S4~f=_xoN3- zI+O)$R@RjV$F=>Ti7BlnG1-cFKCC(t|Qjm{SalS~V-tX#+2ekRhwmN zZr`8{QF6y~Z!D|{=1*2D-JUa<(1Z=;!Ei!KiRNH?o{p5o3crFF=_pX9O-YyJchr$~ zRC`+G+8kx~fD2k*ZIiiIGR<8r&M@3H?%JVOfE>)})7ScOd&?OjgAGT@WVNSCZ8N(p zuQG~76GE3%(%h1*vUXg$vH{ua0b`sQ4f0*y=u~lgyb^!#CcPJa2mkSEHGLsnO^kb$ zru5_l#nu=Y{rSMWiYx?nO{8I!gH+?wEj~UM?IrG}E|bRIBUM>UlY<`T1EHpRr36vv zBi&dG8oxS|J$!zoaq{+JpJy+O^W(nt*|#g32bd&K^w-t>!Vu9N!k9eA8r!Xc{utY> zg9aZ(D2E0gL#W0MdjwES-7~Wa8iubPrd?8-$C4BP?*wok&O8+ykOx{P=Izx+G~hM8 z*9?BYz!T8~dzcZr#ux8kS7u7r@A#DogBH8km8Ry4slyie^n|GrTbO|cLhpqgMdsjX zJ_LdmM#I&4LqqsOUIXK8gW;V0B(7^$y#h3h>J0k^WJfAMeYek%Y-Dcb_+0zPJez!GM zAmJ1u;*rK=FNM0Nf}Y!!P9c4)HIkMnq^b;JFd!S3?_Qi2G#LIQ)TF|iHl~WKK6JmK zbv7rPE6VkYr_%_BT}CK8h=?%pk@3cz(UrZ{@h40%XgThP*-Oeo`T0eq9 zA8BnWZKzCy5e&&_GEsU4*;_k}(8l_&al5K-V*BFM=O~;MgRkYsOs%9eOY6s6AtE*<7GQAR2ulC3RAJrG_P1iQK5Z~&B z&f8X<>yJV6)oDGIlS$Y*D^Rj(cszTy5c81a5IwBr`BtnC6_e`ArI8CaTX_%rx7;cn zR-0?J_LFg*?(#n~G8cXut(1nVF0Oka$A$1FGcERU<^ggx;p@CZc?3UB41RY+wLS`LWFNSs~YP zuw1@DNN3lTd|jDL7gjBsd9}wIw}4xT2+8dBQzI00m<@?c2L%>}QLfK5%r!a-iII`p zX@`VEUH)uj^$;7jVUYdADQ2k*!1O3WdfgF?OMtUXNpQ1}QINamBTKDuv19^{$`8A1 zeq%q*O0mi@(%sZU>Xdb0Ru96CFqk9-L3pzLVsMQ`Xpa~N6CR{9Rm2)A|CI21L(%GW zh&)Y$BNHa=FD+=mBw3{qTgw)j0b!Eahs!rZnpu)z!!E$*eXE~##yaXz`KE5(nQM`s zD!$vW9XH)iMxu9R>r$VlLk9oIR%HxpUiW=BK@4U)|1WNQ=mz9a z^!KkO=>GaJ!GBXm{KJj^;kh-MkUlEQ%lza`-G&}C5y1>La1sR6hT=d*NeCnuK%_LV zOXt$}iP6(YJKc9j-Fxq~*ItVUqljQ8?oaysB-EYtFQp9oxZ|5m0^Hq(qV!S+hq#g( z?|i*H2MIr^Kxgz+3vIljQ*Feejy6S4v~jKEPTF~Qhq!(ms5>NGtRgO5vfPPc4Z^AM zTj!`5xEreIN)vaNxa|q6qWdg>+T`Ol0Uz)ckXBXEGvPNEL3R8hB3=C5`@=SYgAju1 z!)UBr{2~=~xa{b8>x2@C7weRAEuatC)3pkRhT#pMPTpSbA|tan%U7NGMvzmF?c!V8 z=pEWxbdXbTAGtWTyI?Fml%lEr-^AE}w#l(<7OIw;ctw}imYax&vR4UYNJZK6P7ZOd zP87XfhnUHxCUHhM@b*NbTi#(-8|wcv%3BGNs#zRCVV(W?1Qj6^PPQa<{yaBwZ`+<`w|;rqUY_C z&AeyKwwf*q#OW-F()lir=T^<^wjK65Lif$puuU5+tk$;e_EJ;Lu+pH>=-8=PDhkBg z8cWt%@$Sc#C6F$Vd+0507;{OOyT7Hs%nKS88q-W!$f~9*WGBpHGgNp}=C*7!RiZ5s zn1L_DbKF@B8kwhDiLKRB@lsXVVLK|ph=w%_`#owlf@s@V(pa`GY$8h%;-#h@TsO|Y8V=n@*!Rog7<7Cid%apR|x zOjhHCyfbIt%+*PCveTEcuiDi%Wx;O;+K=W?OFUV%)%~6;gl?<0%)?snDDqIvkHF{ zyI02)+lI9ov42^hL>ZRrh*HhjF9B$A@=H94iaBESBF=eC_KT$8A@uB^6$~o?3Wm5t1OIaqF^~><2?4e3c&)@wKn9bD? zoeCs;H>b8DL^F&>Xw-xjZEUFFTv>JD^O#1E#)CMBaG4DX9bD(Wtc8Rzq}9soQ8`jf zeSnHOL}<+WVSKp4kkq&?SbETjq6yr@4%SAqOG=9E(3YeLG9dtV+8vmzq+6PFPk{L; z(&d++iu=^F%b+ea$i2UeTC{R*0Isk;vFK!no<;L+(`y`3&H-~VTdKROkdyowo1iqR zbVW(3`+(PQ2>TKY>N!jGmGo7oeoB8O|P_!Ic@ zZ^;3dnuXo;WJ?S+)%P>{Hcg!Jz#2SI(s&dY4QAy_vRlmOh)QHvs_7c&zkJCmJGVvV zX;Mtb>QE+xp`KyciG$Cn*0?AK%-a|=o!+7x&&yzHQOS>8=B*R=niSnta^Pxp1`=md z#;$pS$4WCT?mbiCYU?FcHGZ#)kHVJTTBt^%XE(Q};aaO=Zik0UgLcc0I(tUpt(>|& zcxB_|fxCF7>&~5eJ=Dpn&5Aj{A^cV^^}(7w#p;HG&Q)EaN~~EqrE1qKrMAc&WXIE;>@<&)5;gD2?={Xf@Mvn@OJKw=8Mgn z!JUFMwD+s==JpjhroT&d{$kQAy%+d`a*XxDEVxy3`NHzmITrE`o!;5ClXNPb4t*8P zzAivdr{j_v!=9!^?T3y?gzmqDWX6mkzhIzJ-3S{T5bcCFMr&RPDryMcdwbBuZbsgN zGrp@^i?rcfN7v0NKGzDPGE#4yszxu=I_`MI%Z|10nFjU-UjQXXA?k8Pk|OE<(?ae) zE%vG#eZAlj*E7_3dx#Zz4kMLj>H^;}33UAankJiDy5ZvEhrjr`!9eMD8COp}U*hP+ zF}KIYx@pkccIgyxFm#LNw~G&`;o&5)2`5aogs`1~7cMZQ7zj!%L4E`2yzlQN6REX20&O<9 zKV6fyr)TScJPPzNTC2gL+0x#=u>(({{D7j)c-%tvqls3#Y?Z1m zV5WUE)zdJ{$p>yX;^P!UcXP?UD~YM;IRa#Rs5~l+*$&nO(;Ers`G=0D!twR(0GF@c zHl9E5DQI}Oz74n zfKP>&$q0($T4y$6w(p=ERAFh+>n%iaeRA%!T%<^+pg?M)@ucY<&59$x9M#n+V&>}=nO9wCV{O~lg&v#+jcUj(tQ z`0u1YH)-`U$15a{pBkGyPL0THv1P|4e@pf@3IBZS4dVJPo#H>pWq%Lr0YS-SeWash z8R7=jb28KPMI|_lo#GEO|5B?N_e``H*23{~a!AmUJ+fb4HX-%QI@lSEUxKlGV7z7Q zSKw@-TR>@1RL%w{x}dW#k1NgW+q4yt2Xf1J62Bx*O^WG8OJ|FqI4&@d3_o8Id@*)4 zYrk=>@!wv~mh7YWv*bZhxqSmFh2Xq)o=m;%n$I?GSz49l1$xRpPu_^N(vZ>*>Z<04 z2+rP70oM=NDysd!@fQdM2OcyT?3T^Eb@lIC-UG=Bw{BjQ&P`KCv$AcJ;?`vdZ4){d z&gkoUK{$!$$K`3*O-jyM1~p-7T*qb)Ys>Myt^;#1&a%O@x8A+E>! zY8=eD`ZG)LVagDLBeHg>=atOG?Kr%h4B%E6m@J^C+U|y)XX@f z8oyJDW|9g=<#f<{JRr{y#~euMnv)`7j=%cHWLc}ngjq~7k**6%4u>Px&W%4D94(r* z+akunK}O0DC2A%Xo9jyF;DobX?!1I(7%}@7F>i%&nk*LMO)bMGg2N+1iqtg+r(70q zF5{Msgsm5GS7DT`kBsjMvOrkx&|EU!{{~gL4d2MWrAT=KBQ-^zQCUq{5PD1orxlIL zq;CvlWx#f1NWvh`hg011I%?T_s!e38l*lWVt|~z-PO4~~1g)SrJ|>*tXh=QfXT)%( z+ex+inPvD&O4Ur;JGz>$sUOnWdpSLcm1X%aQDw4{dB!cnj`^muI$CJ2%p&-kULVCE z>$eMR36kN$wCPR+OFDM3-U(VOrp9k3)lI&YVFqd;Kpz~K)@Fa&FRw}L(SoD z9B4a+hQzZT-BnVltst&=kq6Y(f^S4hIGNKYBgMxGJ^;2yrO}P3;r)(-I-CZ)26Y6? z&rzHI_1GCvGkgy-t1E;r^3Le30|%$ebDRu2+gdLG)r=A~Qz`}~&L@aGJ{}vVs_GE* zVUjFnzHiXfKQbpv&bR&}l2bzIjAooB)=-XNcYmrGmBh(&iu@o!^hn0^#}m2yZZUK8 zufVm7Gq0y`Mj;9b>`c?&PZkU0j4>IL=UL&-Lp3j&47B5pAW4JceG{!XCA)kT<%2nqCxj<)uy6XR_uws~>_MEKPOpAQ!H zkn>FKh)<9DwwS*|Y(q?$^N!6(51O0 z^JM~Ax{AI1Oj$fs-S5d4T7Z_i1?{%0SsIuQ&r8#(JA=2iLcTN+?>wOL532%&dMYkT z*T5xepC+V6zxhS@vNbMoi|i)=rpli@R9~P!39tWbSSb904ekv7D#quKbgFEMTb48P zuq(VJ+&L8aWU(_FCD$3^uD!YM%O^K(dvy~Wm2hUuh6bD|#(I39Xt>N1Y{ZqXL`Fg6 zKQ?T2htHN!(Bx;tV2bfTtIj7e)liN-29s1kew>v(D^@)#v;}C4-G=7x#;-dM4yRWm zyY`cS21ulzMK{PoaQ6xChEZ}o_#}X-o}<&0)$1#3we?+QeLt;aVCjeA)hn!}UaKt< zat1fHEx13y-rXNMvpUUmCVzocPmN~-Y4(YJvQ#db)4|%B!rBsgAe+*yor~}FrNH08 z3V!97S}D7d$zbSD{$z;@IYMxM6aHdypIuS*pr_U6;#Y!_?0i|&yU*@16l z*dcMqDQgfNBf}?quiu4e>H)yTVfsp#f+Du0@=Kc41QockXkCkvu>FBd6Q+@FL!(Yx z2`YuX#eMEiLEDhp+9uFqME_E^faV&~9qjBHJkIp~%$x^bN=N)K@kvSVEMdDuzA0sn z88CBG?`RX1@#hQNd`o^V{37)!w|nA)QfiYBE^m=yQKv-fQF+UCMcuEe1d4BH7$?>b zJl-r9@0^Ie=)guO1vOd=i$_4sz>y3x^R7n4ED!5oXL3@5**h(xr%Hv)_gILarO46q+MaDOF%ChaymKoI6JU5Pg;7#2n9-18|S1;AK+ zgsn6;k6-%!QD>D?cFy}8F;r@z8H9xN1jsOBw2vQONVqBVEbkiNUqgw~*!^##ht>w0 zUOykwH=$LwX2j&nLy=@{hr)2O&-wm-NyjW7n~Zs9UlH;P7iP3 zI}S(r0YFVYacnKH(+{*)Tbw)@;6>%=&Th=+Z6NHo_tR|JCI8TJiXv2N7ei7M^Q+RM z?9o`meH$5Yi;@9XaNR#jIK^&{N|DYNNbtdb)XW1Lv2k{E>;?F`#Pq|&_;gm~&~Zc9 zf+6ZE%{x4|{YdtE?a^gKyzr}dA>OxQv+pq|@IXL%WS0CiX!V zm$fCePA%lU{%pTKD7|5NJHeXg=I0jL@$tOF@K*MI$)f?om)D63K*M|r`gb9edD1~Y zc|w7N)Y%do7=0{RC|AziW7#am$)9jciRJ?IWl9PE{G3U+$%FcyKs_0Cgq`=K3@ttV z9g;M!3z~f_?P%y3-ph%vBMeS@p7P&Ea8M@97+%XEj*(1E6vHj==d zjsoviB>j^$_^OI_DEPvFkVo(BGRo%cJeD){6Uckei=~1}>sp299|IRjhXe)%?uP0I zF5+>?0#Ye}T^Y$u_rc4=lPcq4K^D(TZG-w30-YiEM=dcK+4#o*>lJ8&JLi+3UcpZk z!^?95S^C0ja^jwP`|{<+3cBVog$(mRdQmadS+Vh~z zS@|P}=|z3P6uS+&@QsMp0no9Od&27O&14zHXGAOEy zh~OKpymK5C%;LLb467@KgIiVwYbYd6wFxI{0-~MOGfTq$nBTB!{SrWmL9Hs}C&l&l#m?s*{tA?BHS4mVKHAVMqm63H<|c5n0~k)-kbg zXidai&9ZUy0~WFYYKT;oe~rytRk?)r8bptITsWj(@HLI;@=v5|XUnSls7$uaxFRL+ zRVMGuL3w}NbV1`^=Pw*0?>bm8+xfeY(1PikW*PB>>Tq(FR`91N0c2&>lL2sZo5=VD zQY{>7dh_TX98L2)n{2OV=T10~*YzX27i2Q7W86M4$?gZIXZaBq#sA*{PH8){|GUi;oM>e?ua7eF4WFuFYZSG| zze?srg|5Ti8Og{O zeFxuw9!U+zhyk?@w zjsA6(oKD=Ka;A>Ca)oPORxK+kxH#O@zhC!!XS4@=swnuMk>t+JmLmFiE^1aX3f<)D@`%K0FGK^gg1a1j>zi z2KhV>sjU7AX3F$SEqrXSC}fRx64GDoc%!u2Yag68Lw@w9v;xOONf@o)Lc|Uh3<21ctTYu-mFZuHk*+R{GjXHIGq3p)tFtQp%TYqD=j1&y)>@zxoxUJ!G@ zgI0XKmP6MNzw>nRxK$-Gbzs}dyfFzt>#5;f6oR27ql!%+{tr+(`(>%51|k`ML} zY4eE)Lxq|JMas(;JibNQds1bUB&r}ydMQXBY4x(^&fY_&LlQC)3hylc$~8&~|06-D z#T+%66rYbHX%^KuqJED_wuGB+=h`nWA!>1n0)3wZrBG3%`b^Ozv6__dNa@%V14|!D zQ?o$z5u0^8`giv%qE!BzZ!3j;BlDlJDk)h@9{nSQeEk!z9RGW) z${RSF3phEM*ce*>Xdp}585vj$|40=&S{S-GTiE?Op*vY&Lvr9}BO$XWy80IF+6@%n z5*2ueT_g@ofP#u5pxb7n*fv^Xtt7&?SRc{*2Ka-*!BuOpf}neHGCiHy$@Ka1^Dint z;DkmIL$-e)rj4o2WQV%Gy;Xg(_Bh#qeOsTM2f@KEe~4kJ8kNLQ+;(!j^bgJMcNhvklP5Z6I+9Fq@c&D~8Fb-4rmDT!MB5QC{Dsb;BharP*O;SF4& zc$wj-7Oep7#$WZN!1nznc@Vb<_Dn%ga-O#J(l=OGB`dy=Sy&$(5-n3zzu%d7E#^8`T@}V+5B;PP8J14#4cCPw-SQTdGa2gWL0*zKM z#DfSXs_iWOMt)0*+Y>Lkd=LlyoHjublNLefhKBv@JoC>P7N1_#> zv=mLWe96%EY;!ZGSQDbZWb#;tzqAGgx~uk+-$+2_8U`!ypbwXl z^2E-FkM1?lY@yt8=J3%QK+xaZ6ok=-y%=KXCD^0r!5vUneW>95PzCkOPO*t}p$;-> ze5j-BLT_;)cZQzR2CEsm@rU7GZfFtdp*a|g4wDr%8?2QkIGasRfDWT-Dvy*U{?IHT z*}wGnzdlSptl#ZF^sf)KT|BJs&kLG91^A6ls{CzFprZ6-Y!V0Xysh%9p%iMd7HLsS zN+^Un$tDV)T@i!v?3o0Fsx2qI(AX_$dDkBzQ@fRM%n zRXk6hb9Py#JXUs+7)w@eo;g%QQ95Yq!K_d=z{0dGS+pToEI6=Bo8+{k$7&Z zo4>PH(`ce8E-Ps&uv`NQ;U$%t;w~|@E3WVOCi~R4oj5wP?%<*1C%}Jq%a^q~T7u>K zML5AKfQDv6>PuT`{SrKHRAF+^&edg6+5R_#H?Lz3iGoWo#PCEd0DS;)2U({{X#zU^ zw_xv{4x7|t!S)>44J;KfA|DC?;uQ($l+5Vp7oeqf7{GBF9356nx|&B~gs+@N^gSdd zvb*>&W)|u#F{Z_b`f#GVtQ`pYv3#||N{xj1NgB<#=Odt6{eB%#9RLt5v zIi|0u70`#ai}9fJjKv7dE!9ZrOIX!3{$z_K5FBd-Kp-&e4(J$LD-)NMTp^_pB`RT; zftVVlK2g@+1Ahv2$D){@Y#cL#dUj9*&%#6 zd2m9{1NYp>)6=oAvqdCn5#cx{AJ%S8skUgMglu2*IAtd+z1>B&`MuEAS(D(<6X#Lj z?f4CFx$)M&$=7*>9v1ER4b6!SIz-m0e{o0BfkySREchp?WdVPpQCh!q$t>?rL!&Jg zd#heM;&~A}VEm8Dvy&P|J*eAV&w!&Nx6HFV&B8jJFVTmgLaswn!cx$&%JbTsloz!3 zMEz1d`k==`Ueub_JAy_&`!ogbwx27^ZXgFNAbx=g_I~5nO^r)}&myw~+yY*cJl4$I znNJ32M&K=0(2Dj_>@39`3=FX!v3nZHno_@q^!y}%(yw0PqOo=);6Y@&ylVe>nMOZ~ zd>j#QQSBn3oaWd;qy$&5(5H$Ayi)0haAYO6TH>FR?rhqHmNOO+(})NB zLI@B@v0)eq!ug`>G<@htRlp3n!EpU|n+G+AvXFrWSUsLMBfL*ZB`CRsIVHNTR&b?K zxBgsN0BjfB>UVcJ|x%=-zb%OV7lmZc& zxiupadZVF7)6QuhoY;;FK2b*qL0J-Rn-8!X4ZY$-ZSUXV5DFd7`T41c(#lAeLMoeT z4%g655v@7AqT!i@)Edt5JMbN(=Q-6{=L4iG8RA%}w;&pKmtWvI4?G9pVRp|RTw`g0 zD5c12B&A2&P6Ng~8WM2eIW=wxd?r7A*N+&!Be7PX3s|7~z=APxm=A?5 zt>xB4WG|*Td@VX{Rs)PV0|yK`oI3^xn(4c_j&vgxk_Y3o(-`_5o`V zRTghg6%l@(qodXN;dB#+OKJEEvhfcnc#BeO2|E(5df-!fKDZ!%9!^BJ_4)9P+9Dq5 zK1=(v?KmIp34r?z{NEWnLB3Px{XYwy-akun4F7xTRr2^zeYW{gcK9)>aJDdU5;w5@ zak=<+-PLH-|04pelTb%ULpuuuJC7DgyT@D|p{!V!0v3KpDnRjANN12q6SUR3mb9<- z>2r~IApQGhstZ!3*?5V z8#)hJ0TdZg0M-BK#nGFP>$i=qk82DO z7h;Ft!D5E15OgW)&%lej*?^1~2=*Z5$2VX>V{x8SC+{i10BbtUk9@I#Vi&hX)q
Q!LwySI{Bnv%Sm)yh{^sSVJ8&h_D-BJ_YZe5eCaAWU9b$O2c z$T|{vWVRtOL!xC0DTc(Qbe`ItNtt5hr<)VijD0{U;T#bUEp381_y`%ZIav?kuYG{iyYdEBPW=*xNSc;Rlt6~F4M`5G+VtOjc z*0qGzCb@gME5udTjJA-9O<&TWd~}ysBd(eVT1-H82-doyH9RST)|+Pb{o*;$j9Tjs zhU!IlsPsj8=(x3bAKJTopW3^6AKROHR^7wZ185wJGVhA~hEc|LP;k7NEz-@4p5o}F z`AD6naG3(n=NF9HTH81=F+Q|JOz$7wm9I<+#BSmB@o_cLt2GkW9|?7mM;r!JZp89l zbo!Hp8=n!XH1{GwaDU+k)pGp`C|cXkCU5%vcH)+v@0eK>%7gWxmuMu9YLlChA|_D@ zi#5zovN_!a-0?~pUV-Rj*1P)KwdU-LguR>YM&*Nen+ln8Q$?WFCJg%DY%K}2!!1FE zDv-A%Cbwo^p(lzac&_TZ-l#9kq`mhLcY3h9ZTUVCM(Ad&=EriQY5{jJv<5K&g|*Lk zgV%ILnf1%8V2B0E&;Sp4sYbYOvvMebLwYwzkRQ#F8GpTQq#uv=J`uaSJ34OWITeSGo6+-8Xw znCk*n{kdDEi)Hi&u^)~cs@iyCkFWB2SWZU|Uc%^43ZIZQ-vWNExCCtDWjqHs;;tWf$v{}0{p0Rvxkq``)*>+Akq%|Na zA`@~-Vfe|+(AIlqru+7Ceh4nsVmO9p9jc8}HX^W&ViBDXT+uXbT#R#idPn&L>+#b6 zflC-4C5-X;kUnR~L>PSLh*gvL68}RBsu#2l`s_9KjUWRhiqF`j)`y`2`YU(>3bdBj z?>iyjEhe-~$^I5!nn%B6Wh+I`FvLNvauve~eX<+Ipl&04 zT}};W&1a3%W?dJ2=N#0t?e+aK+%t}5q%jSLvp3jZ%?&F}nOOWr>+{GFIa%wO_2`et z=JzoRR~}iKuuR+azPI8;Gf9)z3kyA4EIOSl!sRR$DlW}0>&?GbgPojmjmnln;cTqCt=ADbE zZ8GAnoM+S1(5$i8^O4t`ue;vO4i}z0wz-QEIVe5_u03;}-!G1NyY8;h^}y;tzY}i5 zqQr#Ur3Fy8sSa$Q0ys+f`!`+>9WbvU_I`Sj;$4{S>O3?#inLHCrtLy~!s#WXV=oVP zeE93*Nc`PBi4q@%Ao$x4lw9vLHM!6mn3-b_cebF|n-2vt-zYVF_&sDE--J-P;2WHo z+@n2areE0o$LjvjlV2X7ZU@j+`{*8zq`JR3gKF#EW|#+{nMyo-a>nFFTg&vhyT=b} zDa8+v0(Dgx0yRL@ZXOYIlVSZ0|MFizy0VPW8;AfA5|pe!#j zX}Py^8fl5SyS4g1WSKKtnyP+_PoOwMMwu`(i@Z)diJp~U54*-miOchy7Z35eL>^M z4p<-aIxH4VUZgS783@H%M7P9hX>t{|RU7$n4T(brCG#h9e9p! z+o`i;EGGq3&pF;~5V~eBD}lC)>if$w%Vf}AFxGqO88|ApfHf&Bvu+xdG)@vuF}Yvk z)o;~k-%+0K0g+L`Wala!$=ZV|z$e%>f0%XoLib%)!R^RoS+{!#X?h-6uu zF&&KxORdZU&EwQFITIRLo(7TA3W}y6X{?Y%y2j0It!ekU#<)$qghZtpcS>L3uh`Uj z7GY;6f$9qKynP#oS3$$a{p^{D+0oJQ71`1?OAn_m8)UGZmj3l*ZI)`V-a>MKGGFG< z&^jg#Ok%(hhm>hSrZ5;Qga4u(?^i>GiW_j9%_7M>j(^|Om$#{k+^*ULnEgzW_1gCICtAD^WpC`A z{9&DXkG#01Xo)U$OC(L5Y$DQ|Q4C6CjUKk1UkPj$nXH##J{c8e#K|&{mA*;b$r0E4 zUNo0jthwA(c&N1l=PEe8Rw_8cEl|-eya9z&H3#n`B$t#+aJ03RFMzrV@gowbe8v(c zIFM60^0&lCFO10NU4w@|61xiZ4CVXeaKjd;d?sv52XM*lS8XiVjgWpRB;&U_C0g+`6B5V&w|O6B*_q zsATxL!M}+$He)1eOWECce#eS@2n^xhlB4<_Nn?yCVEQWDs(r`|@2GqLe<#(|&P0U? z$7V5IgpWf09uIf_RazRwC?qEqRaHyL?iiS05UiGesJy%^>-C{{ypTBI&B0-iUYhk> zIk<5xpsuV@g|z(AZD+C-;A!fTG=df1=<%nxy(a(IS+U{ME4ZbDEBtcD_3V=icT6*_ z)>|J?>&6%nvHhZERBtjK+s4xnut*@>GAmA5m*OTp$!^CHTr}vM4n(X1Q*;{e-Rd2BCF-u@1ZGm z!S8hJ6L=Gl4T_SDa7Xx|-{4mxveJg=ctf`BJ*fy!yF6Dz&?w(Q_6B}WQVtNI!BVBC zKfX<>7vd6C96}XAQmF-Jd?1Q4eTfRB3q7hCh0f!(JkdWT5<{iAE#dKy*Jxq&3a1@~ z8C||Dn2mFNyrUV|<-)C^_y7@8c2Fz+2jrae9deBDu;U}tJ{^xAdxCD248(k;dCJ%o z`y3sADe>U%suxwwv~8A1+R$VB=Q?%U?4joI$um;aH+eCrBqpn- z%79D_7rb;R-;-9RTrwi9dPlg8&@tfWhhZ(Vx&1PQ+6(huX`;M9x~LrW~~#3{j0Bh2kDU$}@!fFQej4VGkJv?M4rU^x!RU zEwhu$!CA_iDjFjrJa`aocySDX16?~;+wgav;}Zut6Mg%C4>}8FL?8)Kgwc(Qlj{@#2Pt0?G`$h7P#M+qoXtlV@d}%c&OzO+QYKK`kyXaK{U(O^2DyIXCZlNQjt0^8~8JzNGrIxhj}}M z&~QZlbx%t;MJ(Vux;2tgNKGlAqphLq%pd}JG9uoVHUo?|hN{pLQ6Em%r*+7t^<);X zm~6=qChlNAVXNN*Sow->*4;}T;l;D1I-5T{Bif@4_}=>l`tK;qqDdt5zvisCKhMAH z#r}`)7VW?LZqfdmXQ%zo5bJ00{Xb9^YKrk0Nf|oIW*K@(=`o2Vndz}ZDyk{!u}PVx zzd--+_WC*U{~DH3{?GI64IB+@On&@9X>EUAo&L+G{L^dozaI4C3G#2wr~hseW@K&g zKWs{uHu-9Je!3;4pE>eBltKUXb^*hG8I&413)$J&{D4N%7PcloU6bn%jPxJyQL?g* z9g+YFFEDiE`8rW^laCNzQmi7CTnPfwyg3VDHRAl>h=In6jeaVOP@!-CP60j3+#vpL zEYmh_oP0{-gTe7Or`L6x)6w?77QVi~jD8lWN@3RHcm80iV%M1A!+Y6iHM)05iC64tb$X2lV_%Txk@0l^hZqi^%Z?#- zE;LE0uFx)R08_S-#(wC=dS&}vj6P4>5ZWjhthP=*Hht&TdLtKDR;rXEX4*z0h74FA zMCINqrh3Vq;s%3MC1YL`{WjIAPkVL#3rj^9Pj9Ss7>7duy!9H0vYF%>1jh)EPqvlr6h%R%CxDsk| z!BACz7E%j?bm=pH6Eaw{+suniuY7C9Ut~1cWfOX9KW9=H><&kQlinPV3h9R>3nJvK z4L9(DRM=x;R&d#a@oFY7mB|m8h4692U5eYfcw|QKwqRsshN(q^v$4$)HgPpAJDJ`I zkqjq(8Cd!K!+wCd=d@w%~e$=gdUgD&wj$LQ1r>-E=O@c ze+Z$x{>6(JA-fNVr)X;*)40Eym1TtUZI1Pwwx1hUi+G1Jlk~vCYeXMNYtr)1?qwyg zsX_e*$h?380O00ou?0R@7-Fc59o$UvyVs4cUbujHUA>sH!}L54>`e` zHUx#Q+Hn&Og#YVOuo*niy*GU3rH;%f``nk#NN5-xrZ34NeH$l`4@t);4(+0|Z#I>Y z)~Kzs#exIAaf--65L0UHT_SvV8O2WYeD>Mq^Y6L!Xu8%vnpofG@w!}R7M28?i1*T&zp3X4^OMCY6(Dg<-! zXmcGQrRgHXGYre7GfTJ)rhl|rs%abKT_Nt24_Q``XH{88NVPW+`x4ZdrMuO0iZ0g` z%p}y};~T5gbb9SeL8BSc`SO#ixC$@QhXxZ=B}L`tP}&k?1oSPS=4%{UOHe0<_XWln zwbl5cn(j-qK`)vGHY5B5C|QZd5)W7c@{bNVXqJ!!n$^ufc?N9C-BF2QK1(kv++h!>$QbAjq)_b$$PcJdV+F7hz0Hu@ zqj+}m0qn{t^tD3DfBb~0B36|Q`bs*xs|$i^G4uNUEBl4g;op-;Wl~iThgga?+dL7s zUP(8lMO?g{GcYpDS{NM!UA8Hco?#}eNEioRBHy4`mq!Pd-9@-97|k$hpEX>xoX+dY zDr$wfm^P&}Wu{!%?)U_(%Mn79$(ywvu*kJ9r4u|MyYLI_67U7%6Gd_vb##Nerf@>& z8W11z$$~xEZt$dPG}+*IZky+os5Ju2eRi;1=rUEeIn>t-AzC_IGM-IXWK3^6QNU+2pe=MBn4I*R@A%-iLDCOHTE-O^wo$sL_h{dcPl=^muAQb`_BRm};=cy{qSkui;`WSsj9%c^+bIDQ z0`_?KX0<-=o!t{u(Ln)v>%VGL z0pC=GB7*AQ?N7N{ut*a%MH-tdtNmNC+Yf$|KS)BW(gQJ*z$d{+{j?(e&hgTy^2|AR9vx1Xre2fagGv0YXWqtNkg*v%40v?BJBt|f9wX5 z{QTlCM}b-0{mV?IG>TW_BdviUKhtosrBqdfq&Frdz>cF~yK{P@(w{Vr7z2qKFwLhc zQuogKO@~YwyS9%+d-zD7mJG~@?EFJLSn!a&mhE5$_4xBl&6QHMzL?CdzEnC~C3$X@ zvY!{_GR06ep5;<#cKCSJ%srxX=+pn?ywDwtJ2{TV;0DKBO2t++B(tIO4)Wh`rD13P z4fE$#%zkd=UzOB74gi=-*CuID&Z3zI^-`4U^S?dHxK8fP*;fE|a(KYMgMUo`THIS1f!*6dOI2 zFjC3O=-AL`6=9pp;`CYPTdVX z8(*?V&%QoipuH0>WKlL8A*zTKckD!paN@~hh zmXzm~qZhMGVdQGd=AG8&20HW0RGV8X{$9LldFZYm zE?}`Q3i?xJRz43S?VFMmqRyvWaS#(~Lempg9nTM$EFDP(Gzx#$r)W&lpFKqcAoJh-AxEw$-bjW>`_+gEi z2w`99#UbFZGiQjS8kj~@PGqpsPX`T{YOj`CaEqTFag;$jY z8_{Wzz>HXx&G*Dx<5skhpETxIdhKH?DtY@b9l8$l?UkM#J-Snmts7bd7xayKTFJ(u zyAT&@6cAYcs{PBfpqZa%sxhJ5nSZBPji?Zlf&}#L?t)vC4X5VLp%~fz2Sx<*oN<7` z?ge=k<=X7r<~F7Tvp9#HB{!mA!QWBOf%EiSJ6KIF8QZNjg&x~-%e*tflL(ji_S^sO ztmib1rp09uon}RcsFi#k)oLs@$?vs(i>5k3YN%$T(5Or(TZ5JW9mA6mIMD08=749$ z!d+l*iu{Il7^Yu}H;lgw=En1sJpCKPSqTCHy4(f&NPelr31^*l%KHq^QE>z>Ks_bH zjbD?({~8Din7IvZeJ>8Ey=e;I?thpzD=zE5UHeO|neioJwG;IyLk?xOz(yO&0DTU~ z^#)xcs|s>Flgmp;SmYJ4g(|HMu3v7#;c*Aa8iF#UZo7CvDq4>8#qLJ|YdZ!AsH%^_7N1IQjCro

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.properties b/tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..12d38de6 --- /dev/null +++ b/tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/tasks-tracker-plugin-master/gradlew b/tasks-tracker-plugin-master/gradlew new file mode 100644 index 00000000..f5feea6d --- /dev/null +++ b/tasks-tracker-plugin-master/gradlew @@ -0,0 +1,252 @@ +#!/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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# 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/HEAD/platforms/jvm/plugins-application/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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + 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 + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# 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/tasks-tracker-plugin-master/gradlew.bat b/tasks-tracker-plugin-master/gradlew.bat new file mode 100644 index 00000000..9d21a218 --- /dev/null +++ b/tasks-tracker-plugin-master/gradlew.bat @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@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=. +@rem This is normally unused +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% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +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% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/tasks-tracker-plugin-master/icon.png b/tasks-tracker-plugin-master/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7dc707feef8aa5d5458eee957cefe34c64ba6c45 GIT binary patch literal 15928 zcmeHuWmKF?)^6kO?u|P%?(PuW8VfW{#G!P^ZGz3k8%OyEy zX3pGi?)TkU>)w9@tDCo~p8f1yPwidR@9LKrEe$0sbaHe60Dz^UEU)wU7xehzKt+Ch zrzaM(0RX6z{q(?|IuIYAtGkP}og)nB`OFmtg!$T80|36uwJ&VEVXf{F4^TpCcpX|y z6gSSPzQK!Dw{Zg>HSV#Xvg_224N8E1vmBh~?(_Sbj)%=xBRZxn%c|VN-!uG=+)mWK z7uKIrhfN)y2A0=nT|Sr>__zgj<33}%JAH4^v-WToaOoB@mDKNYJ{z&T7%_1BGoqlk zZat_|>y|u6v26WfG2(hLLg%&%w}%TPIeS*#di|Yn=jwK4UDvVjxdu-Ao#;2IyXUZ< zEpCN`UsPzHQA1-3;TY~PxUhok#3SdP(I?#&=xp=!wnk2c-p-3&6)J8AB~2u)2gOa0 zJ*P_7lRS16KRvL_q>*|!yRzt&c<_?B$h_PYH!|-XHZM$yJ#d-5y7xJDK3e;pvpN>R z$Z4K-U9fqRAmpBsw>FBtp7DI-_-CL`&kK5g6592ktMLnT3HbqcIc%|b*TH8}hG0yd zQ37@;s41fARI?s5iT#;UUSrQ-LPoR`9qBAxmx}fcPv*(dr}aYv+ZA7wH8KPe3{|9; zQ4d2GQeD&Cs#my8**0CxmZsV{!yrNti8C&E{39RgC6mv`$Lqa>i}HwvoF357!Ko?6 ziirATwxk&h&&`C9Db3o5j$W=ld^Z;;h!zjdT((gww4P@H7d)yMfJ|b~vn`A!1B!d0 zJ(8zGE6n+IEubRuV$JsSJAbwUkb?-ls;VqnANk{0VJ?LTKZlXv8_W4zFAByC)0%Xh zxIULTJ&$H%``X%bQDggdBlg;s9b=c$(E@Iu)X;N+&ow;@&3|BUNw(?{z3ZwIM_wHr3?kQV)qnC*h zxeHg~k|(s{z7lMYQfN>#V5KbgUU*b&f^inyYw>fE(4CJl6q|=TR$E@Z>+fxfYJQ^i zVw+4e!9XdYJ;{4vG9gEF{ZlS2=bG^Qo^CfZ3#%y!rW{u~nf!cGdjAKX?8t1nX3mR+`&y+3E~wlQqx z^Y}G3+xP=M#4tz7b04-i@_+KMYk}ex7-cySI+}B?m?`VQoR|WiRmNJsKe#kHwA~(g zuU49LMRy)R8PJ4P(cqoy4931~Dz*Hp|m|$q3_KEH4n2lK#q%rOE*lwvVfAfZ! z_Z8J99&pUADpL~tP1g{3$LmF1I^tsw=Ch9_8Dc)1IT19d4r(Uj3mzgGZ};@`E>hSJ ztktYbp)Q)1oCH!>3@(lhF^uAgg>jcZ8%mq$Oe62ik~h<_9*uaWYUy^&#jhESM3aqI&e(7>(6|<{{ z?c$fh02fuyamWrwVa7vYXS&4bWYR9FXzSnIwUjmb|iBfhSOQ{*(c}GOXqVne%7wKPaLOHi^IFej9j! zeQu@@<)u?T{9&bF;U>}UY+
@DVXm`3E8nER-5&#&Xc7&8*6-t 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" + text + ""; + } + + public static String wrapWithWrappingParagraph(String text, int width) + { + return "

" + text + "

"; + } + + public static String wrapWithBold(String text) + { + return "" + text + ""; + } + + public static String imageTag(URL url) + { + return ""; + } + + public static String colorTag(String color, String text) + { + return "" + text + ""; + } + + public static String colorTag(Color color, String text) + { + String buf = Integer.toHexString(color.getRGB()); + String hex = "#" + buf.substring(buf.length() - 6); + return colorTag(hex, text); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/TasksTrackerConfig.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/TasksTrackerConfig.java new file mode 100644 index 00000000..fbaa4a2c --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/TasksTrackerConfig.java @@ -0,0 +1,140 @@ +package net.reldo.taskstracker; + +import net.reldo.taskstracker.config.ConfigValues; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(TasksTrackerPlugin.CONFIG_GROUP_NAME) +public interface TasksTrackerConfig extends Config +{ + @ConfigItem( + position = 10, + keyName = "untrackUponCompletion", + name = "Untrack Tasks Upon Completion", + description = "Configures whether completed tasks should also automatically untracked when the task is completed." + ) + default boolean untrackUponCompletion() + { + return false; + } + + @ConfigItem( + position = 11, + keyName = "filterPanelCollapsible", + name = "Filter Panels Collapsible", + description = "Shows button that allows filter panels to be hidden." + ) + default boolean filterPanelCollapsible() + { + return true; + } + + @ConfigItem( + position = 12, + keyName = "saveSubFilterState", //@todo generalise this to all sub-filters + name = "Save Filter State", + description = "Configures whether the state of area filters should be saved and recalled when switching task type or restarting the plugin.", + hidden = true + ) + default boolean saveSubFilterState() + { + return true; + } + + @ConfigItem( + position = 100, + keyName = "completedFilter", + name = "Completed Tasks Filter", + description = "Configures whether completed tasks should be displayed.", + hidden = true + ) + default ConfigValues.CompletedFilterValues completedFilter() + { + return ConfigValues.CompletedFilterValues.COMPLETE_AND_INCOMPLETE; + } + + @ConfigItem( + position = 101, + keyName = "trackedFilter", + name = "Tracked Tasks Filter", + description = "Configures whether tracked tasks should be displayed.", + hidden = true + ) + default ConfigValues.TrackedFilterValues trackedFilter() + { + return ConfigValues.TrackedFilterValues.TRACKED_AND_UNTRACKED; + } + + @ConfigItem( + position = 102, + keyName = "ignoredFilter", + name = "Ignored Tasks Filter", + description = "Configures whether ignored tasks should be displayed.", + hidden = true + ) + default ConfigValues.IgnoredFilterValues ignoredFilter() + { + return ConfigValues.IgnoredFilterValues.NOT_IGNORED; + } + + @ConfigItem( + position = 103, + keyName = "taskListTab", + name = "Selected Task List Tab", + description = "Configures the currently selected tab on the task list.", + hidden = true + ) + default ConfigValues.TaskListTabs taskListTab() + { + return ConfigValues.TaskListTabs.ALL; + } + + @ConfigItem( + position = 106, + keyName = "taskTypeJsonName", + name = "Task Type", + description = "Configures the task type which is displayed in the panel.", + hidden = true + ) + default String taskTypeJsonName() + { + return "COMBAT"; + } + + @ConfigItem( + position = 109, + keyName = "dropdownFilter", + name = "Dropdown Filter", + description = "Configures the dropdown to filter tasks on.", + hidden = true + ) + default String dropdownFilter() + { + return ""; + } + + @ConfigItem( + position = 110, + keyName = "sortCriteria", + name = "Sort Criteria", + description = "Configures the criteria to sort tasks on.", + hidden = true + ) + default String sortCriteria() + { + return "Default"; + } + + @ConfigItem( + position = 111, + keyName = "sortDirection", + name = "Sort Direction", + description = "Configures the direction to sort tasks.", + hidden = true + ) + default ConfigValues.SortDirections sortDirection() + { + return ConfigValues.SortDirections.ASCENDING; + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/TasksTrackerPlugin.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/TasksTrackerPlugin.java new file mode 100644 index 00000000..94ca9f31 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/TasksTrackerPlugin.java @@ -0,0 +1,584 @@ +package net.reldo.taskstracker; + +import com.google.gson.Gson; +import com.google.inject.Binder; +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import java.awt.image.BufferedImage; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Named; +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.data.Export; +import net.reldo.taskstracker.data.LongSerializer; +import net.reldo.taskstracker.data.TasksSummary; +import net.reldo.taskstracker.data.TrackerConfigStore; +import net.reldo.taskstracker.data.jsondatastore.reader.DataStoreReader; +import net.reldo.taskstracker.data.jsondatastore.reader.HttpDataStoreReader; +import net.reldo.taskstracker.data.reldo.ReldoImport; +import net.reldo.taskstracker.data.task.TaskFromStruct; +import net.reldo.taskstracker.data.task.TaskService; +import net.reldo.taskstracker.data.task.TaskType; +import net.reldo.taskstracker.data.task.filters.FilterService; +import net.reldo.taskstracker.panel.TasksTrackerPluginPanel; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.Experience; +import net.runelite.api.GameState; +import net.runelite.api.Skill; +import net.runelite.api.events.CommandExecuted; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.StatChanged; +import net.runelite.api.events.VarbitChanged; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.RuneScapeProfileType; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.ProfileChanged; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.PluginManager; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.LinkBrowser; + +@Slf4j +@PluginDescriptor( + name = "Tasks Tracker" +) +public class TasksTrackerPlugin extends Plugin +{ + public static final String CONFIG_GROUP_NAME = "tasks-tracker"; + + public int[] playerSkills; + + public String taskTextFilter; + + public TasksTrackerPluginPanel pluginPanel; + + private static final long VARP_UPDATE_THROTTLE_DELAY_MS = 7 * 1000; + + private boolean forceUpdateVarpsFlag = false; + private Set varpIdsToUpdate = new HashSet<>(); + private long lastVarpUpdate = 0; + private NavigationButton navButton; + private RuneScapeProfileType currentProfileType; + private final Map oldExperience = new EnumMap<>(Skill.class); + + @Inject @Named("runelite.version") private String runeliteVersion; + @Inject private Gson gson; + @Inject private Client client; + @Inject private SpriteManager spriteManager; + @Inject private PluginManager pluginManager; + @Inject private ClientToolbar clientToolbar; + @Inject private ClientThread clientThread; + @Inject private ChatMessageManager chatMessageManager; + @Getter @Inject private ConfigManager configManager; + @Getter @Inject private TasksTrackerConfig config; + + @Inject private TrackerConfigStore trackerConfigStore; + @Inject private TaskService taskService; + @Inject private FilterService filterService; + + @Override + public void configure(Binder binder) + { + binder.bind(DataStoreReader.class).to(HttpDataStoreReader.class); + super.configure(binder); + } + + @Provides + TasksTrackerConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(TasksTrackerConfig.class); + } + + @Override + protected void startUp() + { + try + { + String taskTypeJsonName = config.taskTypeJsonName(); + taskService.setTaskType(taskTypeJsonName); + } + catch (Exception ex) + { + log.error("error setting task type in startUp", ex); + } + + forceUpdateVarpsFlag = false; + + pluginPanel = new TasksTrackerPluginPanel(this, config, spriteManager, taskService); + + boolean isLoggedIn = isLoggedInState(client.getGameState()); + pluginPanel.setLoggedIn(isLoggedIn); + if (isLoggedIn) + { + forceUpdateVarpsFlag = true; + } + + final BufferedImage icon = ImageUtil.loadImageResource(getClass(), "panel_icon.png"); + navButton = NavigationButton.builder() + .tooltip("Task Tracker") + .icon(icon) + .priority(5) + .panel(pluginPanel) + .build(); + clientToolbar.addNavigation(navButton); + + log.info("Tasks Tracker started!"); + } + + @Override + protected void shutDown() + { + pluginPanel.hideLoggedInPanel(); + pluginPanel = null; + taskService.clearTaskTypes(); + clientToolbar.removeNavigation(navButton); + log.info("Tasks Tracker stopped!"); + } + + @Subscribe + public void onCommandExecuted(CommandExecuted commandExecuted) + { + if (!commandExecuted.getCommand().startsWith("tt")) return; + + if (commandExecuted.getCommand().equalsIgnoreCase("tt-process-varp")) + { + String[] args = commandExecuted.getArguments(); + if (args.length == 0) return; + + try + { + int varpId = Integer.parseInt(args[0]); + log.debug("Processing varpId " + varpId); + processVarpAndUpdateTasks(varpId); + } + catch (NumberFormatException e) + { + log.debug("Invalid varpId, provide a valid integer"); + } + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged varbitChanged) + { + if (forceUpdateVarpsFlag || taskService.isTaskTypeChanged()) + { + // Force update is coming on next game tick, so ignore varbit change events + return; + } + int varpId = varbitChanged.getVarpId(); + if (!taskService.isVarpInCurrentTaskType(varpId)) + { + return; + } + varpIdsToUpdate.add(varbitChanged.getVarpId()); + } + + @Subscribe + public void onConfigChanged(ConfigChanged configChanged) + { + if (!configChanged.getGroup().equals(CONFIG_GROUP_NAME)) + { + return; + } + log.debug("onConfigChanged {} {}", configChanged.getKey(), configChanged.getNewValue()); + if (configChanged.getKey().equals("untrackUponCompletion") && config.untrackUponCompletion()) + { + forceVarpUpdate(); + } + + if (configChanged.getKey().equals("filterPanelCollapsible")) + { + SwingUtilities.invokeLater(pluginPanel::redraw); + } + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + log.debug("onGameStateChanged {}", gameStateChanged.getGameState().toString()); + GameState newGameState = gameStateChanged.getGameState(); + RuneScapeProfileType newProfileType = RuneScapeProfileType.getCurrent(client); + + SwingUtilities.invokeLater(() -> pluginPanel.setLoggedIn(isLoggedInState(newGameState))); + + // Logged in + if (newGameState == GameState.LOGGING_IN) + { + forceUpdateVarpsFlag = true; + } + // Changed game mode + if (isLoggedInState(newGameState) && currentProfileType != null && currentProfileType != newProfileType) + { + forceUpdateVarpsFlag = true; + } + + currentProfileType = newProfileType; + } + + private boolean isLoggedInState(GameState gameState) + { + return gameState == GameState.LOGGED_IN || gameState == GameState.HOPPING || gameState == GameState.LOADING; + } + + @Subscribe + public void onGameTick(GameTick gameTick) + { + if (forceUpdateVarpsFlag || taskService.isTaskTypeChanged()) + { + log.debug("forceUpdateVarpsFlag game tick {} {}", forceUpdateVarpsFlag, taskService.isTaskTypeChanged()); + trackerConfigStore.loadCurrentTaskTypeFromConfig(); + forceVarpUpdate(); + SwingUtilities.invokeLater(() -> pluginPanel.redraw()); + forceUpdateVarpsFlag = false; + taskService.setTaskTypeChanged(false); + } + + // Flush throttled varp updates + long currentTimeEpoch = System.currentTimeMillis(); + if (currentTimeEpoch - lastVarpUpdate > VARP_UPDATE_THROTTLE_DELAY_MS) + { + flushVarpUpdates(varpIdsToUpdate); + varpIdsToUpdate = new HashSet<>(); + lastVarpUpdate = currentTimeEpoch; + } + } + + @Subscribe + public void onStatChanged(StatChanged statChanged) + { + // @todo deprecate one of these, we don't need to track player skills twice. + // Cache current player skills + int[] newSkills = client.getRealSkillLevels(); + boolean changed = !Arrays.equals(playerSkills, newSkills); + if (changed) + { + playerSkills = client.getRealSkillLevels(); + } + + final Skill skill = statChanged.getSkill(); + + // Modified from m0bilebtw's modification from Nightfirecat's virtual level ups plugin + final int xpAfter = client.getSkillExperience(skill); + final int levelAfter = Experience.getLevelForXp(xpAfter); + final int xpBefore = oldExperience.getOrDefault(skill, -1); + final int levelBefore = xpBefore == -1 ? -1 : Experience.getLevelForXp(xpBefore); + + oldExperience.put(skill, xpAfter); + + // Do not proceed if any of the following are true: + // * xpBefore == -1 (don't fire when first setting new known value) + // * xpAfter <= xpBefore (do not allow 200m -> 200m exp drops) + // * levelBefore >= levelAfter (stop if we're not actually reaching a new level) + // * levelAfter > MAX_REAL_LEVEL (stop if above 99) + if (xpBefore == -1 || xpAfter <= xpBefore || levelBefore >= levelAfter || levelAfter > Experience.MAX_REAL_LEVEL) + { + return; + } + + // If we get here, 'skill' was leveled up! + SwingUtilities.invokeLater(() -> pluginPanel.taskListPanel.refreshTaskPanelsWithSkill(skill)); + } + + @Subscribe + public void onProfileChanged(ProfileChanged profileChanged) + { + final Optional taskTrackerPlugin = pluginManager.getPlugins().stream().filter(p -> p.getName().equals("Tasks Tracker")).findFirst(); + if (taskTrackerPlugin.isPresent() && pluginManager.isPluginEnabled(taskTrackerPlugin.get())) + { + reloadTaskType(); + } + } + + public void refreshAllTasks() + { + SwingUtilities.invokeLater(() -> pluginPanel.refreshAllTasks()); + } + + public void reloadTaskType() { + taskService.clearTaskTypes(); + filterService.clearFilterConfigs(); + try { + String taskTypeJsonName = config.taskTypeJsonName(); + taskService.setTaskType(taskTypeJsonName).thenAccept(isSet -> { + if (!isSet) { + return; + } + SwingUtilities.invokeLater(() -> + { + pluginPanel.redraw(); + pluginPanel.refreshAllTasks(); + }); + }); + } catch (Exception ex) { + log.error("error setting task type in reload", ex); + } + + } + + public void saveCurrentTaskTypeData() + { + log.debug("saveCurrentTaskTypeData"); + trackerConfigStore.saveCurrentTaskTypeData(); + } + + public void openImportJsonDialog() + { + JOptionPane optionPane = new JOptionPane("Paste import data into the text field below to import task tracker data.", JOptionPane.INFORMATION_MESSAGE); + optionPane.setWantsInput(true); + JDialog inputDialog = optionPane.createDialog(this.pluginPanel, "Import Tasks Input"); + inputDialog.setAlwaysOnTop(true); + inputDialog.setVisible(true); + + if (optionPane.getInputValue().equals("") || optionPane.getInputValue().equals("uninitializedValue")) + { + this.showMessageBox("Import Tasks Error", "Input was empty so no data has been imported.", JOptionPane.ERROR_MESSAGE, false); + return; + } + + String json = ""; + ReldoImport reldoImport; + try + { + json = (String) optionPane.getInputValue(); + reldoImport = this.gson.fromJson(json, ReldoImport.class); + } + catch (Exception ex) + { + this.showMessageBox("Import Tasks Error", "There was an issue importing task tracker data. " + ex.getMessage(), JOptionPane.ERROR_MESSAGE, false); + log.error("There was an issue importing task tracker data.", ex); + log.debug("reldoImport json: {}", json); + return; + } + + if (!reldoImport.taskTypeName.equalsIgnoreCase(config.taskTypeJsonName())) + { + this.showMessageBox("Import Tasks Error", String.format("Wrong task type. Select the %s task type to import this data.", reldoImport.taskTypeName), JOptionPane.ERROR_MESSAGE, false); + return; + } + + optionPane = new JOptionPane("Importing tasks will overwrite task tracker settings and cannot be undone. Are you sure you want to import tasks?", JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_OPTION); + JDialog confirmDialog = optionPane.createDialog(this.pluginPanel, "Import Tasks Overwrite Confirmation"); + confirmDialog.setAlwaysOnTop(true); + confirmDialog.setVisible(true); + + Object selectedValue = optionPane.getValue(); + if (selectedValue == null) + { + return; + } + + if (selectedValue.equals(JOptionPane.YES_OPTION)) + { + HashMap tasksById = new HashMap<>(); + taskService.getTasks().forEach((task) -> tasksById.put(task.getIntParam("id"), task)); + + reldoImport.getTasks().forEach((id, reldoTaskSave) -> { + TaskFromStruct task = tasksById.get(id); + task.loadReldoSave(reldoTaskSave); + }); + + trackerConfigStore.saveCurrentTaskTypeData(); + pluginPanel.redraw(); + } + } + + public void sendTotalsToChat() + { + TasksSummary summary = new TasksSummary(taskService.getTasks()); + int trackedTasks = summary.trackedTasksCount; + int trackedPoints = summary.trackedTasksPoints; + + final String message = new ChatMessageBuilder() + .append(Color.BLACK, String.format("Task Tracker - Tracked Tasks: %s | Tracked Points: %s", trackedTasks, trackedPoints)) + .build(); + + chatMessageManager.queue( + QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(message) + .build()); + } + + public void copyJsonToClipboard() + { + clientThread.invokeLater(() -> { + // Not worried with this complexity on the client thread because it's from an infrequent button press + String json = getCurrentTaskTypeExportJson(); + final StringSelection stringSelection = new StringSelection(json); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + + String message = "Copied " + taskService.getCurrentTaskType().getName() + " data to clipboard!"; + showMessageBox("Data Exported!", message, JOptionPane.INFORMATION_MESSAGE, true); + }); + } + + private void forceVarpUpdate() + { + log.debug("forceVarpUpdate"); + processVarpAndUpdateTasks(null).thenAccept((processed) -> { + if (processed) + { + log.debug("forceVarpUpdate processed complete, saving"); + saveCurrentTaskTypeData(); + } + }); + } + + private void flushVarpUpdates(Set varpIds) + { + log.debug("Flushing throttled varp updates {}", varpIds); + varpIds.forEach((id) -> processVarpAndUpdateTasks(id).thenAccept(processed -> { + if (processed) + { + log.debug("flushVarpUpdates processed complete, saving"); + saveCurrentTaskTypeData(); + } + })); + } + + private CompletableFuture processTaskStatus(TaskFromStruct task) + { + CompletableFuture future = new CompletableFuture<>(); + clientThread.invoke(() -> { + int taskId = task.getIntParam("id"); + int varbitIndex = taskId / 32; + int bitIndex = taskId % 32; + try + { + int varpId = task.getTaskType().getTaskVarps().get(varbitIndex); + BigInteger varpValue = BigInteger.valueOf(client.getVarpValue(varpId)); + boolean isTaskCompleted = varpValue.testBit(bitIndex); + task.setCompleted(isTaskCompleted); + if (isTaskCompleted && config.untrackUponCompletion()) + { + task.setTracked(false); + } + log.debug("process taskFromStruct {} ({}) {}", task.getStringParam("name"), task.getIntParam("id"), isTaskCompleted); + future.complete(isTaskCompleted); + } + catch (Exception ex) + { + log.error("Error processing task status {}", taskId, ex); + future.completeExceptionally(ex); + } + }); + return future; + } + + /** + * Update task completion status. If no varpId is specified, it updates all tasks in the current task type + * @param varpId varp id to update (optional) + * @return An observable that emits true if all tasks were processed + */ + private CompletableFuture processVarpAndUpdateTasks(@Nullable Integer varpId) + { + log.info("processVarpAndUpdateTasks: " + (varpId != null ? varpId : "all")); + + List tasks = varpId != null ? + taskService.getTasksFromVarpId(varpId) : + taskService.getTasks(); + + List> taskFutures = new ArrayList<>(); + for (TaskFromStruct task : tasks) + { + CompletableFuture taskFuture = processTaskStatus(task); + taskFutures.add(taskFuture); + } + + CompletableFuture allTasksFuture = CompletableFuture.allOf(taskFutures.toArray(new CompletableFuture[0])); + return allTasksFuture + .thenRun(() -> { + if (varpId != null) + { + SwingUtilities.invokeLater(() -> pluginPanel.taskListPanel.refreshMultipleTasks(tasks)); + } else { + SwingUtilities.invokeLater(() -> pluginPanel.refreshAllTasks()); + } + }) + .thenApply(v -> true); + } + + private String getCurrentTaskTypeExportJson() + { + TaskType taskType = taskService.getCurrentTaskType(); + Gson gson = this.gson.newBuilder() + .excludeFieldsWithoutExposeAnnotation() + .registerTypeAdapter(float.class, new LongSerializer()) + .create(); + + if (taskType == null) + { + String error = "Cannot export to JSON; no task type selected."; + log.error(error); + return error; + } + else + { + Export export = new Export(taskType, taskService.getTasks(), runeliteVersion, client); + return gson.toJson(export); + } + } + + private void showMessageBox(final String title, final String message, int messageType, boolean showOpenLeagueTools) + { + SwingUtilities.invokeLater(() -> { + JOptionPane optionPane; + JDialog dialog; + + if (showOpenLeagueTools) + { + String[] options = {"Open OS League Tools", "Ok"}; + + optionPane = new JOptionPane(message, messageType, JOptionPane.YES_NO_OPTION, null, options, options[1]); + } + else + { + optionPane = new JOptionPane(message, messageType); + } + + dialog = optionPane.createDialog(pluginPanel, title); + dialog.setAlwaysOnTop(true); + dialog.setVisible(true); + + Object selectedValue = optionPane.getValue(); + if (selectedValue == null) + { + return; + } + + if (selectedValue.equals("Open OS League Tools")) + { + LinkBrowser.browse("https://www.osleague.tools/tracker?open=import&tab=tasks"); + } + }); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/config/ConfigValues.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/config/ConfigValues.java new file mode 100644 index 00000000..21c7ac2f --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/config/ConfigValues.java @@ -0,0 +1,40 @@ +package net.reldo.taskstracker.config; + + +public class ConfigValues { + + public enum CompletedFilterValues + { + COMPLETE_AND_INCOMPLETE, + COMPLETE, + INCOMPLETE; + } + + public enum TrackedFilterValues + { + TRACKED_AND_UNTRACKED, + TRACKED, + UNTRACKED; + } + + public enum IgnoredFilterValues + { + NOT_IGNORED, + IGNORED_AND_NOT_IGNORED, + IGNORED; + } + + public enum TaskListTabs + { + TRACKED, + ALL, + CUSTOM; + } + + public enum SortDirections + { + ASCENDING, + DESCENDING; + } + +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/Export.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/Export.java new file mode 100644 index 00000000..1dcc0c5a --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/Export.java @@ -0,0 +1,94 @@ +package net.reldo.taskstracker.data; + +import com.google.gson.annotations.Expose; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import lombok.Getter; +import net.reldo.taskstracker.data.task.ConfigTaskSave; +import net.reldo.taskstracker.data.task.TaskFromStruct; +import net.reldo.taskstracker.data.task.TaskType; +import net.reldo.taskstracker.quests.DiaryData; +import net.reldo.taskstracker.quests.QuestData; +import net.runelite.api.Actor; +import net.runelite.api.Client; + +@Getter +public class Export +{ + private final Client client; + + @Expose private final QuestData quests; + @Expose private final DiaryData diaries; + @Expose private String displayName; + @Expose private final int runescapeVersion; + @Expose private final String runeliteVersion; + @Expose private final long timestamp; + @Expose private final String taskType; + @Expose private final HashMap varbits; + @Expose private final HashMap varps; + @Expose private final HashMap tasks; + + public Export(TaskType taskType, List tasks, String runeliteVersion, Client client) + { + this.client = client; + Actor localPlayer = client.getLocalPlayer(); + if (localPlayer != null) + { + this.displayName = localPlayer.getName(); + } + quests = new QuestData(client); + diaries = new DiaryData(client); + runescapeVersion = client.getRevision(); + this.runeliteVersion = runeliteVersion; + timestamp = Instant.now().toEpochMilli(); + this.taskType = taskType.getTaskJsonName(); + varbits = getVarbits(taskType); + varps = getVarps(taskType); + this.tasks = getTaskSavesById(tasks); + } + + private HashMap getVarbits(TaskType taskType) + { + assert client.isClientThread(); + + HashMap varbitValueMap = new HashMap<>(); + for (int varbitId : taskType.getVarbits()) + { + varbitValueMap.put(varbitId, client.getVarbitValue(varbitId)); + } + + return varbitValueMap; + } + + public HashMap getVarps(TaskType taskType) + { + assert client.isClientThread(); + + HashMap varpValueMap = new HashMap<>(); + for (int varpId : taskType.getTaskVarps()) + { + varpValueMap.put(varpId, client.getVarpValue(varpId)); + } + for (int varpId : taskType.getOtherVarps()) + { + varpValueMap.put(varpId, client.getVarpValue(varpId)); + } + + return varpValueMap; + } + + public HashMap getTaskSavesById(List tasks) + { + HashMap taskSavesById = new HashMap<>(); + for (TaskFromStruct task : tasks) + { + if (task.getCompletedOn() == 0 && task.getIgnoredOn() == 0 && task.getTrackedOn() == 0) + { + continue; + } + taskSavesById.put(String.valueOf(task.getIntParam("id")), task.getSaveData()); + } + return taskSavesById; + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/LongSerializer.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/LongSerializer.java new file mode 100644 index 00000000..9f7b27ca --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/LongSerializer.java @@ -0,0 +1,17 @@ +package net.reldo.taskstracker.data; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; +import java.text.DecimalFormat; + +public class LongSerializer implements JsonSerializer +{ + @Override + public JsonElement serialize(Long value, Type type, JsonSerializationContext jsonSerializationContext) + { + return new JsonPrimitive(new DecimalFormat("#").format(value)); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/TasksSummary.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/TasksSummary.java new file mode 100644 index 00000000..2c201ac7 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/TasksSummary.java @@ -0,0 +1,24 @@ +package net.reldo.taskstracker.data; + +import java.util.Collection; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.data.task.TaskFromStruct; + +@Slf4j +public class TasksSummary +{ + public int trackedTasksCount = 0; + public int trackedTasksPoints = 0; + + public TasksSummary(Collection tasks) + { + tasks.forEach(task -> { + if (task.isTracked()) { + trackedTasksCount++; + int points = task.getPoints(); + log.debug("TasksSummary {} {}", task.getName(), points); + trackedTasksPoints += points; + } + }); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/TrackerConfigStore.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/TrackerConfigStore.java new file mode 100644 index 00000000..9a267e81 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/TrackerConfigStore.java @@ -0,0 +1,94 @@ +package net.reldo.taskstracker.data; + +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.data.task.TaskFromStruct; +import net.reldo.taskstracker.data.task.ConfigTaskSave; +import net.reldo.taskstracker.data.task.TaskService; +import net.reldo.taskstracker.data.task.TaskType; +import net.runelite.client.config.ConfigManager; + +@Singleton +@Slf4j +public class TrackerConfigStore +{ + public static final String CONFIG_TASKS_PREFIX = "tasks"; + public static final String CONFIG_GROUP_PREFIX_SEPARATOR = "-"; + public static final String CONFIG_GROUP_NAME = TasksTrackerPlugin.CONFIG_GROUP_NAME; + + private final Gson customGson; + @Inject + private TaskService taskService; + @Inject + private ConfigManager configManager; + + @Inject + public TrackerConfigStore(Gson gson) + { + this.customGson = gson.newBuilder() + .excludeFieldsWithoutExposeAnnotation() + .registerTypeAdapter(float.class, new LongSerializer()) + .create(); + } + + public void loadCurrentTaskTypeFromConfig() + { + TaskType currentTaskType = taskService.getCurrentTaskType(); + if (currentTaskType == null) + { + log.debug("loadTaskTypeFromConfig type is null, skipping"); + return; + } + log.debug("loadTaskTypeFromConfig {}", currentTaskType.getName()); + String configKey = getCurrentTaskTypeConfigKey(); + String configJson = configManager.getRSProfileConfiguration(CONFIG_GROUP_NAME, configKey); + if (configJson == null) + { + log.debug("No save information for task type {}, not applying save", currentTaskType.getName()); + return; + } + + Type deserializeType = TypeToken.getParameterized(HashMap.class, Integer.class, ConfigTaskSave.class).getType(); + try + { + HashMap saveData = customGson.fromJson(configJson, deserializeType); + taskService.applySave(currentTaskType, saveData); + } + catch (JsonParseException ex) + { + log.error("{} {} json invalid. wiping saved data", CONFIG_GROUP_NAME, configKey, ex); + configManager.unsetRSProfileConfiguration(CONFIG_GROUP_NAME, configKey); + } + } + + public void saveCurrentTaskTypeData() + { + log.debug("saveTaskTypeToConfig"); + Map saveDataByStructId = taskService.getTasks().stream() + .filter(task -> task.getCompletedOn() != 0 || task.getIgnoredOn() != 0 || task.getTrackedOn() != 0) + .collect(Collectors.toMap( + TaskFromStruct::getStructId, + TaskFromStruct::getSaveData, + (existing, replacement) -> existing, + HashMap::new + )); + + String configValue = this.customGson.toJson(saveDataByStructId); + String configKey = CONFIG_TASKS_PREFIX + CONFIG_GROUP_PREFIX_SEPARATOR + taskService.getCurrentTaskType().getTaskJsonName(); + configManager.setRSProfileConfiguration(CONFIG_GROUP_NAME, configKey, configValue); + } + + private String getCurrentTaskTypeConfigKey() + { + return CONFIG_TASKS_PREFIX + CONFIG_GROUP_PREFIX_SEPARATOR + taskService.getCurrentTaskType().getTaskJsonName(); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/FilterDataClient.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/FilterDataClient.java new file mode 100644 index 00000000..633818af --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/FilterDataClient.java @@ -0,0 +1,50 @@ +package net.reldo.taskstracker.data.jsondatastore; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.data.jsondatastore.reader.DataStoreReader; +import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig; +import okhttp3.OkHttpClient; + +@Singleton +@Slf4j +public class FilterDataClient +{ + @Inject private ManifestClient manifestClient; + @Inject private OkHttpClient okHttpClient; + @Inject private Gson gson; + @Inject private DataStoreReader dataStoreReader; + + public FilterDataClient() + { + log.debug("init filter data client"); + } + + public HashMap getFilterConfigs() throws Exception + { + log.debug("get filter configs"); + try(InputStream stream = this.dataStoreReader.readFilterConfigs(this.manifestClient.getManifest().filterMetadata); + InputStreamReader responseReader = new InputStreamReader(stream, StandardCharsets.UTF_8)) + { + Type listType = TypeToken.getParameterized(ArrayList.class, FilterConfig.class).getType(); + + List filterConfigs = this.gson.fromJson(responseReader, listType); + HashMap filterConfigsByConfigKey = new HashMap<>(); + for (FilterConfig filterConfig : filterConfigs) + { + filterConfigsByConfigKey.put(filterConfig.getConfigKey(), filterConfig); + } + return filterConfigsByConfigKey; + } + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/JsonDataStore.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/JsonDataStore.java new file mode 100644 index 00000000..3c6ec83f --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/JsonDataStore.java @@ -0,0 +1,6 @@ +package net.reldo.taskstracker.data.jsondatastore; + +public class JsonDataStore +{ + public static String baseUrl = "https://raw.githubusercontent.com/osrs-reldo/task-json-store/refs/heads/main"; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/ManifestClient.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/ManifestClient.java new file mode 100644 index 00000000..3b03a11d --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/ManifestClient.java @@ -0,0 +1,44 @@ +package net.reldo.taskstracker.data.jsondatastore; + +import com.google.common.io.CharStreams; +import com.google.gson.Gson; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.data.jsondatastore.reader.DataStoreReader; +import net.reldo.taskstracker.data.jsondatastore.types.Manifest; +import okhttp3.OkHttpClient; + +@Singleton +@Slf4j +public class ManifestClient +{ + @Inject private OkHttpClient okHttpClient; + @Inject private Gson gson; + @Inject private DataStoreReader dataStoreReader; + + private static Manifest _manifest = null; + + public ManifestClient() + { + log.debug("init manifestclient"); + } + + public Manifest getManifest() throws Exception + { + if (_manifest != null) { + return _manifest; + } + try(InputStream stream = this.dataStoreReader.readManifestData(); + InputStreamReader responseReader = new InputStreamReader(stream, StandardCharsets.UTF_8)) + { + String manifestJson = CharStreams.toString(responseReader); // ew, why not a stream? not working... + _manifest = this.gson.fromJson(manifestJson, Manifest.class); + log.debug("_manifest = " + _manifest); + return _manifest; + } + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/TaskDataClient.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/TaskDataClient.java new file mode 100644 index 00000000..32bff48d --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/TaskDataClient.java @@ -0,0 +1,67 @@ +package net.reldo.taskstracker.data.jsondatastore; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.data.jsondatastore.reader.DataStoreReader; +import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinition; +import net.reldo.taskstracker.data.jsondatastore.types.TaskTypeDefinition; +import net.reldo.taskstracker.data.task.TaskType; +import net.runelite.api.Client; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.SpriteManager; +import okhttp3.OkHttpClient; + +@Singleton +@Slf4j +public class TaskDataClient +{ + @Inject private ManifestClient manifestClient; + @Inject private OkHttpClient okHttpClient; + @Inject private Gson gson; + @Inject private DataStoreReader dataStoreReader; + @Inject private Client client; + @Inject private ClientThread clientThread; + @Inject private SpriteManager spriteManager; + + public TaskDataClient() + { + log.debug("init task data client"); + } + + public HashMap getTaskTypes() throws Exception { + try (InputStream stream = this.dataStoreReader.readTaskTypes(this.manifestClient.getManifest().taskTypeMetadata); + InputStreamReader responseReader = new InputStreamReader(stream, StandardCharsets.UTF_8)) + { + Type listType = TypeToken.getParameterized(ArrayList.class, TaskTypeDefinition.class).getType(); + + List taskTypeDefinitions = this.gson.fromJson(responseReader, listType); + + HashMap taskTypes = new HashMap<>(); + for (TaskTypeDefinition taskTypeDefinition : taskTypeDefinitions) + { + taskTypes.put(taskTypeDefinition.getTaskJsonName(), new TaskType(client, clientThread, spriteManager, taskTypeDefinition)); + } + return taskTypes; + } + } + + public List getTaskDefinitions(String jsonFilename) throws Exception + { + try(InputStream stream = this.dataStoreReader.readTasks(jsonFilename); + InputStreamReader responseReader = new InputStreamReader(stream, StandardCharsets.UTF_8)) + { + Type listType = TypeToken.getParameterized(ArrayList.class, TaskDefinition.class).getType(); + return this.gson.fromJson(responseReader, listType); + } + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/reader/DataStoreReader.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/reader/DataStoreReader.java new file mode 100644 index 00000000..74dd601e --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/reader/DataStoreReader.java @@ -0,0 +1,11 @@ +package net.reldo.taskstracker.data.jsondatastore.reader; + +import java.io.InputStream; + +public interface DataStoreReader +{ + InputStream readManifestData() throws Exception; + InputStream readTaskTypes(String taskTypeFilename) throws Exception; + InputStream readTasks(String jsonFilename) throws Exception; + InputStream readFilterConfigs(String filterFilename) throws Exception; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/reader/HttpDataStoreReader.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/reader/HttpDataStoreReader.java new file mode 100644 index 00000000..34f43245 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/reader/HttpDataStoreReader.java @@ -0,0 +1,130 @@ +package net.reldo.taskstracker.data.jsondatastore.reader; + +import java.io.InputStream; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.data.jsondatastore.JsonDataStore; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +@Singleton +@Slf4j +public class HttpDataStoreReader implements DataStoreReader +{ + @Inject + private OkHttpClient okHttpClient; + + @Override + public InputStream readManifestData() throws Exception + { + String manifestUrl = JsonDataStore.baseUrl + "/manifest.json"; + log.debug("getManifest json from {} ...", manifestUrl); + Request request = new Request.Builder() + .url(manifestUrl) + .build(); + Response response = this.okHttpClient.newCall(request).execute(); + if (!response.isSuccessful()) + { + String unsuccessful = "getManifest json request unsuccessful with status " + response.code(); + if (response.body() != null) + { + unsuccessful += " and body \n" + response.body(); + } + log.error(unsuccessful); + throw new Exception(unsuccessful); + } + if (response.body() == null) + { + log.error("getManifest returned without body"); + throw new Exception("getManifest returned without body"); + } + log.debug("getManifest json fetched successfully, deserializing result"); + return response.body().byteStream(); + } + + @Override + public InputStream readTaskTypes(String taskTypeFilename) throws Exception + { + String taskJsonUrl = JsonDataStore.baseUrl + "/" + taskTypeFilename; + log.debug("getTaskTypes json from {} ...", taskJsonUrl); + Request request = new Request.Builder() + .url(taskJsonUrl) + .build(); + Response response = this.okHttpClient.newCall(request).execute(); + if (!response.isSuccessful()) + { + String unsuccessful = "getTaskTypes json request unsuccessful with status " + response.code(); + if (response.body() != null) + { + unsuccessful += " and body \n" + response.body(); + } + log.error(unsuccessful); + throw new Exception(unsuccessful); + } + if (response.body() == null) + { + log.error("getTaskTypes returned without body"); + throw new Exception("getTaskTypes returned without body"); + } + log.debug("getTaskTypes json fetched successfully, deserializing result"); + return response.body().byteStream(); + } + + @Override + public InputStream readTasks(String jsonFilename) throws Exception + { + String taskJsonUrl = String.format("%s/tasks/%s.min.json", JsonDataStore.baseUrl, jsonFilename); + log.debug("getTasks json from {} ...", taskJsonUrl); + Request request = new Request.Builder() + .url(taskJsonUrl) + .build(); + Response response = this.okHttpClient.newCall(request).execute(); + if (!response.isSuccessful()) + { + String unsuccessful = "getTasks json request unsuccessful with status " + response.code(); + if (response.body() != null) + { + unsuccessful += " and body \n" + response.body(); + } + log.error(unsuccessful); + throw new Exception(unsuccessful); + } + if (response.body() == null) + { + log.error("getTasks returned without body"); + throw new Exception("getTasks returned without body"); + } + log.debug("getTasks json fetched successfully, deserializing result"); + return response.body().byteStream(); + } + + @Override + public InputStream readFilterConfigs(String filterFilename) throws Exception + { + String filterJsonUrl = JsonDataStore.baseUrl + "/" + filterFilename; + log.debug("getTaskTypes json from {} ...", filterJsonUrl); + Request request = new Request.Builder() + .url(filterJsonUrl) + .build(); + Response response = this.okHttpClient.newCall(request).execute(); + if (!response.isSuccessful()) + { + String unsuccessful = "getFilters json request unsuccessful with status " + response.code(); + if (response.body() != null) + { + unsuccessful += " and body \n" + response.body(); + } + log.error(unsuccessful); + throw new Exception(unsuccessful); + } + if (response.body() == null) + { + log.error("getFilters returned without body"); + throw new Exception("getFilters returned without body"); + } + log.debug("getFilters json fetched successfully, deserializing result"); + return response.body().byteStream(); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterConfig.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterConfig.java new file mode 100644 index 00000000..2be17d79 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterConfig.java @@ -0,0 +1,51 @@ +package net.reldo.taskstracker.data.jsondatastore.types; + +import java.util.ArrayList; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * Represents the configuration for a filter + */ +@Data +@AllArgsConstructor +public class FilterConfig { + /** + * Key under which to store the filter's selected values, generally prefixed by task type + */ + private String configKey; + + /** + * The label displayed in the UI with the filter. + */ + private String label; + + /** + * The filter type, see enum for types of filters supported. + */ + private FilterType filterType; + + /** + * The source of the value(s) to use for the filter, see enum for types of values supported. + * If global is specified then configKey must match a filter config defined in filters.json + */ + private FilterValueType valueType; + + /** + * The name of the param or metadata property to use for the filter. + * Can be left null for SKILL value type + */ + private String valueName; + + /** + * Name of an enum specified in `TaskTypeDefinition.stringEnumMap` to provide labels for the filter + * Specifying this property will override the displayed integer value of `valueName` + */ + private String optionLabelEnum; + + /** + * Item values in a button filter (dropdown not yet supported) + */ + private ArrayList customItems; + +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterCustomItem.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterCustomItem.java new file mode 100644 index 00000000..63d766a9 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterCustomItem.java @@ -0,0 +1,11 @@ +package net.reldo.taskstracker.data.jsondatastore.types; + +import lombok.Data; + +@Data +public class FilterCustomItem +{ + private Integer value; + private String tooltip; + private Integer spriteId; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterType.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterType.java new file mode 100644 index 00000000..7f7d1f00 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterType.java @@ -0,0 +1,7 @@ +package net.reldo.taskstracker.data.jsondatastore.types; + +public enum FilterType +{ + BUTTON_FILTER, + DROPDOWN_FILTER +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterValueType.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterValueType.java new file mode 100644 index 00000000..9b8998a7 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/FilterValueType.java @@ -0,0 +1,10 @@ +package net.reldo.taskstracker.data.jsondatastore.types; + +public enum FilterValueType +{ + PARAM_INTEGER, + PARAM_STRING, + SKILL, + METADATA, + GLOBAL +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/Manifest.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/Manifest.java new file mode 100644 index 00000000..c5ae8c91 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/Manifest.java @@ -0,0 +1,10 @@ +package net.reldo.taskstracker.data.jsondatastore.types; + +import lombok.Data; + +@Data +public class Manifest +{ + public String taskTypeMetadata; + public String filterMetadata; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskDefinition.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskDefinition.java new file mode 100644 index 00000000..bb90ce89 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskDefinition.java @@ -0,0 +1,46 @@ +package net.reldo.taskstracker.data.jsondatastore.types; + +import java.util.List; +import java.util.Map; +import lombok.Data; + +/** + * Represents a task with various attributes + */ +@Data +public class TaskDefinition +{ + /** + * Struct id for task data + */ + private Integer structId; + + /** + * Sort id based on the sort order in the game's UI + */ + private Integer sortId; + + /** + * Skills required for the task. + */ + private List skills; + + /** + * Metadata related to the task that isn't represented in the Struct/params + * May or may not be used for task filters + * Examples: + * - notes = extra description like "a magic cabbage is a cabbage picked at Draynor Manor" + * - category = an extra category type that isn't a param + */ + private Map metadata; + + /** + * Notes from the OSRS wiki + */ + private String wikiNotes; + + /** + * Completion percent from the OSRS wiki + */ + private Float completionPercent; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskDefinitionSkill.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskDefinitionSkill.java new file mode 100644 index 00000000..a96739bf --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskDefinitionSkill.java @@ -0,0 +1,20 @@ +package net.reldo.taskstracker.data.jsondatastore.types; + +import lombok.Data; + +/** + * Represents a skill required for a task. + */ +@Data +public class TaskDefinitionSkill +{ + /** + * The skill + */ + private String skill; + + /** + * The level required + */ + private Integer level; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskTypeDefinition.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskTypeDefinition.java new file mode 100644 index 00000000..d8c2172a --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/jsondatastore/types/TaskTypeDefinition.java @@ -0,0 +1,105 @@ +package net.reldo.taskstracker.data.jsondatastore.types; + +import java.util.ArrayList; +import java.util.HashMap; +import lombok.Data; + +/** + * Represents a task type with relevant configuration for UI display and task management + */ +@Data +public class TaskTypeDefinition +{ + + /** + * Name of the task type for UI display + */ + private String name; + + /** + * Description of the task type + */ + private String description; + + /** + * Is the task type enabled? + */ + private boolean isEnabled; + + /** + * Filename for the task JSON found in the tasks directory of task-json-store + * Extension not included. + */ + private String taskJsonName; + + /** + * Filters for the task type + */ + private ArrayList filters; + + /** + * A dictionary of parameters relevant to the task, with required id, name, description, tier + * The key is the plain English name for the parameter + * The value is an array of OSRS cache Struct ParamIDs that match with the plain English parameter + * Generally, there is only 1 value in the array, but multiple are available for fallback + */ + private HashMap intParamMap; + + /** + * A dictionary of parameters relevant to the task, with required id, name, description, tier + * The key is the plain English name for the parameter + * The value is an array of OSRS cache Struct ParamIDs that match with the plain English parameter + * Generally, there is only 1 value in the array, but multiple are available for fallback + */ + private HashMap stringParamMap; + + /** + * A dictionary of integer enums relevant to the task type + * The key is the plain English name describing the enum + * The value is an integer representing the enum id + * e.g. "tierSprites": 3213 (tier id maps to a sprite id) + */ + private HashMap intEnumMap; + + /** + * A dictionary of string enums relevant to the task type + * The key is the plain English name describing the enum + * The value is an integer representing the enum id + * e.g. "tierNames": 4757 (tier id maps to a sprite id) + */ + private HashMap stringEnumMap; + + /** + * A dictionary of tier sprite ids + * The key is a string representation of the tier id integer + * The value is an integer representing the sprite id + */ + private HashMap tierSpriteIdMap = new HashMap<>(); + + /** + * Varps used to store task progress + * Used for exports from the plugin + */ + private ArrayList taskVarps; + + /** + * Other varps used for the task type + * Used for exports from the plugin + * Examples in the past: League Points, Sage Renown + */ + private int[] otherVarps; + + /** + * Varbits used for the task type + * Used for exports from the plugin + * Examples in the past: Relics chosen, Tasks completed, unlocks, Fragment xp + */ + private int[] varbits; + + /** + * The script id used to parse the completion of a task + * This is a rs2asm script + * Example: Combat achievements = script 4834 + */ + private int taskCompletedScriptId; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/reldo/ReldoImport.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/reldo/ReldoImport.java new file mode 100644 index 00000000..a2e2baac --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/reldo/ReldoImport.java @@ -0,0 +1,13 @@ +package net.reldo.taskstracker.data.reldo; + +import java.util.HashMap; +import lombok.Data; + +@Data +public class ReldoImport +{ + public String taskTypeName; + public int version; + public String rsn; + private HashMap tasks; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/reldo/ReldoTaskSave.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/reldo/ReldoTaskSave.java new file mode 100644 index 00000000..7254f819 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/reldo/ReldoTaskSave.java @@ -0,0 +1,14 @@ +package net.reldo.taskstracker.data.reldo; + +import lombok.Data; + +@Data +public class ReldoTaskSave +{ + long completed; + long todo; + long ignored; + int order; + String notes; + long lastUpdated; +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/ConfigTaskSave.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/ConfigTaskSave.java new file mode 100644 index 00000000..ae4fcdf9 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/ConfigTaskSave.java @@ -0,0 +1,19 @@ +package net.reldo.taskstracker.data.task; + +import com.google.gson.annotations.Expose; + +public class ConfigTaskSave +{ + @Expose public final long completed; + @Expose public final long tracked; + @Expose public final Integer structId; + @Expose public final long ignored; + + public ConfigTaskSave(TaskFromStruct task) + { + completed = task.getCompletedOn(); + tracked = task.getTrackedOn(); + ignored = task.getIgnoredOn(); + structId = task.getStructId(); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskFromStruct.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskFromStruct.java new file mode 100644 index 00000000..06963d31 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskFromStruct.java @@ -0,0 +1,208 @@ +package net.reldo.taskstracker.data.task; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinition; +import net.reldo.taskstracker.data.reldo.ReldoTaskSave; +import net.runelite.api.Client; +import net.runelite.api.StructComposition; + +@Slf4j +public class TaskFromStruct +{ + @Getter + private final Integer structId; + @Getter + private final Integer sortId; + @Getter + private TaskType taskType; + @Getter + private final TaskDefinition taskDefinition; + @Getter + private boolean structLoaded; + @Getter @Setter + private long completedOn; + @Getter @Setter + private long trackedOn; + @Getter @Setter + private long ignoredOn; + + private StructComposition _struct; + private final Map _stringParams = new HashMap<>(); + private final Map _intParams = new HashMap<>(); + + public TaskFromStruct(TaskType taskType, TaskDefinition taskDefinition) + { + this.taskType = taskType; + this.taskDefinition = taskDefinition; + this.structId = taskDefinition.getStructId(); + this.sortId = taskDefinition.getSortId(); + } + + public String getStringParam(String paramName) + { + return this._stringParams.get(paramName); + } + + public Integer getIntParam(String paramName) + { + return this._intParams.get(paramName); + } + + // TODO: Remove client from params + public boolean loadStructData(Client client) + { + assert client.isClientThread(); + + if (structLoaded) { + return true; + } + try + { + // log.debug("LOADING STRUCT DATA " + structId); + _struct = client.getStructComposition(structId); + taskType.getIntParamMap().forEach((paramName, paramId) -> { + int value = _struct.getIntValue(paramId); + // log.debug("{} {}", paramName, value); + _intParams.put(paramName, value); + }); + taskType.getStringParamMap().forEach((paramName, paramId) -> { + String value = _struct.getStringValue(paramId); + // log.debug("{} {}", paramName, value); + _stringParams.put(paramName, value); + }); + } + catch (Exception ex) + { + log.error("error loading struct data {}", ex, ex); + return false; + } + + structLoaded = true; + return true; + } + + public boolean isCompleted() + { + return completedOn > 0; + } + + public int getPoints() + { + if (taskType.getTierPoints().size() == 0) + { + return 0; + } + Integer points = taskType.getTierPoints().get(getTier()); + if (points == null) + { + return 0; + } + return points; + } + + public void setCompleted(boolean completed) + { + long now = Instant.now().toEpochMilli(); + if (completed && completedOn > 0 && completedOn <= now) + { + return; + } + completedOn = completed ? now : 0; + } + + public boolean isTracked() + { + return trackedOn > 0; + } + + public void setTracked(boolean state) + { + long now = Instant.now().toEpochMilli(); + if (state && trackedOn > 0 && trackedOn <= now) + { + return; + } + trackedOn = state ? now : 0; + } + + public boolean isIgnored() + { + return ignoredOn > 0; + } + + public void setIgnored(boolean state) + { + long now = Instant.now().toEpochMilli(); + if (state && ignoredOn > 0 && ignoredOn <= now) + { + return; + } + ignoredOn = state ? now : 0; + } + + public void loadConfigSave(ConfigTaskSave loadedData) + { + setDates(loadedData.completed, loadedData.ignored, loadedData.tracked); + } + + public void loadReldoSave(ReldoTaskSave loadedData) + { + setMostRecentDates(loadedData.getCompleted(), loadedData.getIgnored(), loadedData.getTodo()); + } + + private void setDates(long completedOn, long ignoredOn, long trackedOn) + { + // Set all dates regardless of how they compare + this.setCompletedOn(completedOn); + this.setIgnoredOn(ignoredOn); + this.setTrackedOn(trackedOn); + } + + private void setMostRecentDates(long completedOn, long ignoredOn, long trackedOn) + { + // Older completions take priority; incomplete (0) also takes priority + if (completedOn < this.getCompletedOn()) + { + this.setCompletedOn(completedOn); + } + // Newer ignores take priority + if (ignoredOn > this.getIgnoredOn()) + { + this.setIgnoredOn(ignoredOn); + } + // Newer tracks take priority + if (trackedOn > this.getTrackedOn()) + { + this.setTrackedOn(trackedOn); + } + } + + public String getName() + { + return getStringParam("name"); + } + + public int getTier() + { + return getIntParam("tier"); + } + + public String getDescription() + { + return getStringParam("description"); + } + + public ConfigTaskSave getSaveData() + { + return new ConfigTaskSave(this); + } + + public Float getCompletionPercent() { + return getTaskDefinition().getCompletionPercent(); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskService.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskService.java new file mode 100644 index 00000000..d86a8f02 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskService.java @@ -0,0 +1,306 @@ +package net.reldo.taskstracker.data.task; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.data.jsondatastore.ManifestClient; +import net.reldo.taskstracker.data.jsondatastore.TaskDataClient; +import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig; +import net.reldo.taskstracker.data.jsondatastore.types.FilterValueType; +import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinition; +import net.reldo.taskstracker.data.task.filters.FilterService; +import net.runelite.api.Client; +import net.runelite.api.EnumComposition; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; + +@Singleton +@Slf4j +public class TaskService +{ + @Inject private ManifestClient manifestClient; + @Inject private TaskDataClient taskDataClient; + @Inject private ClientThread clientThread; + @Inject private Client client; + @Inject private FilterService filterService; + @Inject private ConfigManager configManager; + + @Getter + @Setter + private boolean taskTypeChanged = false; + @Getter + private TaskType currentTaskType; + @Getter + private final List tasks = new ArrayList<>(); + @Getter + private final HashMap sortedIndexes = new HashMap<>(); + private HashMap _taskTypes = new HashMap<>(); + private HashSet currentTaskTypeVarps = new HashSet<>(); + private final ExecutorService futureExecutor = Executors.newSingleThreadExecutor(); + + public CompletableFuture setTaskType(String taskTypeJsonName) { + return getTaskTypesByJsonName().thenCompose(taskTypes -> + { + TaskType newTaskType = taskTypes.get(taskTypeJsonName); + if (newTaskType == null) + { + log.error("unsupported task type {}, falling back to COMBAT", taskTypeJsonName); + newTaskType = taskTypes.get("COMBAT"); + } + return this.setTaskType(newTaskType); + }); + } + + private CompletableFuture loadAllTasksStructData(Collection tasks) { + Collection> taskFutures = new ArrayList<>(); + for (TaskFromStruct task : tasks) { + CompletableFuture taskFuture = new CompletableFuture<>(); + clientThread.invoke(() -> { + boolean isTaskLoaded = task.loadStructData(client); + taskFuture.complete(isTaskLoaded); + }); + taskFutures.add(taskFuture); + } + return CompletableFuture.allOf(taskFutures.toArray(new CompletableFuture[0])).thenApply(v -> { + for (CompletableFuture future : taskFutures) { + if (!future.join()) { + return false; + } + } + return true; + }); + } + + public CompletableFuture setTaskType(TaskType newTaskType) { + log.debug("setTaskType {}", newTaskType.getTaskJsonName()); + if (newTaskType.equals(currentTaskType)) { + log.debug("Skipping setTaskType, same task type selected"); + return CompletableFuture.completedFuture(false); + } + currentTaskType = newTaskType; + configManager.setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "taskTypeJsonName", newTaskType.getTaskJsonName()); + + // Complete creation of any GLOBAL value type filterConfigs + for (FilterConfig filterConfig : currentTaskType.getFilters()) { + if (filterConfig.getValueType().equals(FilterValueType.GLOBAL)) { + // Set valueType to the one required by the global filter + FilterConfig globalFilterConfig = filterService.getGlobalFilterByKey(filterConfig.getConfigKey()); + filterConfig.setValueType(globalFilterConfig.getValueType()); + + // Set any filterConfig fields not already specified + Optional.ofNullable(filterConfig.getLabel()).ifPresentOrElse(val -> {}, () -> filterConfig.setLabel(globalFilterConfig.getLabel())); + Optional.ofNullable(filterConfig.getFilterType()).ifPresentOrElse(val -> {}, () -> filterConfig.setFilterType(globalFilterConfig.getFilterType())); + Optional.ofNullable(filterConfig.getValueName()).ifPresentOrElse(val -> {}, () -> filterConfig.setValueName(globalFilterConfig.getValueName())); + Optional.ofNullable(filterConfig.getOptionLabelEnum()).ifPresentOrElse(val -> {}, () -> filterConfig.setOptionLabelEnum(globalFilterConfig.getOptionLabelEnum())); + Optional.ofNullable(filterConfig.getCustomItems()).ifPresentOrElse(val -> {}, () -> filterConfig.setCustomItems(globalFilterConfig.getCustomItems())); + } + } + + List newTasks = new ArrayList<>(); + return newTaskType.loadTaskTypeDataAsync().thenCompose((isTaskTypeLoaded) -> { + if (!isTaskTypeLoaded) { + log.error("Error loading task type during setTaskType"); + return CompletableFuture.completedFuture(false); + } + + CompletableFuture future = new CompletableFuture<>(); + futureExecutor.submit(() -> { + try { + Collection taskDefinitions = taskDataClient.getTaskDefinitions(currentTaskType.getTaskJsonName()); + for (TaskDefinition definition : taskDefinitions) { + TaskFromStruct task = new TaskFromStruct(currentTaskType, definition); + newTasks.add(task); + } + loadAllTasksStructData(newTasks).thenApply(future::complete); + } catch (Exception e3) { + future.completeExceptionally(e3); + } + }); + return future; + }).thenCompose(areTasksLoaded -> { + if (!areTasksLoaded) { + return CompletableFuture.completedFuture(false); + } + + tasks.clear(); + tasks.addAll(newTasks); + + // Index task list for each property + sortedIndexes.clear(); + currentTaskType.getIntParamMap().keySet().forEach(paramName -> { + sortedIndexes.put(paramName, null); + addSortedIndex(paramName, Comparator.comparingInt((TaskFromStruct task) -> task.getIntParam(paramName))); + }); + currentTaskType.getStringParamMap().keySet().forEach(paramName -> { + sortedIndexes.put(paramName, null); + addSortedIndex(paramName, Comparator.comparing((TaskFromStruct task) -> task.getStringParam(paramName))); + }); + // todo: make this less of a special case. + if (tasks.stream().anyMatch(task -> task.getCompletionPercent() != null)) { + sortedIndexes.put("completion %", null); + addSortedIndex("completion %", + (TaskFromStruct task1, TaskFromStruct task2) -> + { + Float comp1 = task1.getTaskDefinition().getCompletionPercent() != null ? task1.getTaskDefinition().getCompletionPercent() : 0; + Float comp2 = task2.getTaskDefinition().getCompletionPercent() != null ? task2.getTaskDefinition().getCompletionPercent() : 0; + return comp1.compareTo(comp2); + }); + } + + currentTaskTypeVarps.clear(); + currentTaskTypeVarps = new HashSet<>(currentTaskType.getTaskVarps()); + + taskTypeChanged = true; + return CompletableFuture.completedFuture(true); + }); + } + + private void addSortedIndex(String paramName, Comparator comparator) + { + List sortedTasks = tasks.stream() + .sorted(comparator) + .collect(Collectors.toCollection(ArrayList::new)); + int[] sortedIndex = new int[tasks.size()]; + for(int i = 0; i < sortedTasks.size(); i++) + { + sortedIndex[i] = tasks.indexOf(sortedTasks.get(i)); + } + sortedIndexes.put(paramName, sortedIndex); + } + + public int getSortedTaskIndex(String sortCriteria, int position) + { + if(sortedIndexes.containsKey(sortCriteria)) + { + return sortedIndexes.get(sortCriteria)[position]; + } + else + { + return position; + } + } + + public boolean isVarpInCurrentTaskType(int varpId) + { + return currentTaskTypeVarps.contains(varpId); + } + + public void clearTaskTypes() + { + this._taskTypes.clear(); + } + + /** + * Get a map of task type json names to task type + * + * @return Hashmap of TaskType indexed by task type json name + */ + public CompletableFuture> getTaskTypesByJsonName() + { + if (_taskTypes.size() > 0) + { + return CompletableFuture.completedFuture(_taskTypes); + } + + try + { + CompletableFuture> future = new CompletableFuture<>(); + futureExecutor.submit(() -> + { + try + { + _taskTypes = taskDataClient.getTaskTypes(); + future.complete(_taskTypes); + } + catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } + catch (Exception ex) + { + log.error("Unable to populate task types from data client", ex); + return CompletableFuture.completedFuture(new HashMap<>()); + } + } + + public CompletableFuture> getStringEnumValuesAsync(String enumName) + { + Integer enumId = currentTaskType.getStringEnumMap().get(enumName); + if (enumId == null) + { + return CompletableFuture.completedFuture(new HashMap<>()); + } + + CompletableFuture> future = new CompletableFuture<>(); + clientThread.invoke(() -> { + try + { + EnumComposition enumComposition = client.getEnum(enumId); + int[] keys = enumComposition.getKeys(); + HashMap map = new HashMap<>(); + for (int key : keys) + { + map.put(key, enumComposition.getStringValue(key)); + } + future.complete(map); + } + catch (Exception ex) + { + log.error("Error getting string enum values", ex); + future.completeExceptionally(ex); + } + }); + return future; + } + + public void applySave(TaskType saveTaskType, HashMap saveData) + { + String currentTaskTypeName = currentTaskType.getTaskJsonName(); + String saveTaskTypeName = saveTaskType.getTaskJsonName(); + if (!currentTaskTypeName.equals(saveTaskTypeName)) + { + log.warn("Cannot apply save, task types do not match current={} save={}", currentTaskTypeName, saveTaskTypeName); + return; + } + + for (TaskFromStruct task : getTasks()) + { + ConfigTaskSave configTaskSave = saveData.get(task.getStructId()); + if (configTaskSave == null) + { + continue; + } + task.loadConfigSave(configTaskSave); + } + } + + public List getTasksFromVarpId(Integer varpId) + { + int varpIndex = getCurrentTaskType().getTaskVarps().indexOf(varpId); + int minTaskId = varpIndex * 32; + int maxTaskId = minTaskId + 32; + + return getTasks().stream().filter(t -> { + int taskId = t.getIntParam("id"); + return taskId >= minTaskId && taskId <= maxTaskId; + }).collect(Collectors.toList()); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskType.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskType.java new file mode 100644 index 00000000..684a276a --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/TaskType.java @@ -0,0 +1,147 @@ +package net.reldo.taskstracker.data.task; + +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.CompletableFuture; +import lombok.Getter; +import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig; +import net.reldo.taskstracker.data.jsondatastore.types.FilterType; +import net.reldo.taskstracker.data.jsondatastore.types.TaskTypeDefinition; +import net.runelite.api.Client; +import net.runelite.api.EnumComposition; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.SpriteManager; + +public class TaskType +{ + @Getter + private final HashMap spritesById = new HashMap<>(); + @Getter + private final HashMap tierSprites = new HashMap<>(); + @Getter + private final HashMap tierPoints = new HashMap<>(); + + private final Client client; + private final ClientThread clientThread; + private final SpriteManager spriteManager; + private final TaskTypeDefinition _taskTypeDefinition; + + public TaskType(Client client, ClientThread clientThread, SpriteManager spriteManager, TaskTypeDefinition taskTypeDefinition) + { + this.client = client; + this.clientThread = clientThread; + this.spriteManager = spriteManager; + this._taskTypeDefinition = taskTypeDefinition; + } + + public CompletableFuture loadTaskTypeDataAsync() + { + CompletableFuture future = new CompletableFuture<>(); + clientThread.invoke(() -> { + try { + getButtonFiltersSpriteIds().forEach((spriteId) -> { + BufferedImage spriteImage = spriteManager.getSprite(spriteId, 0); + spritesById.put(spriteId, spriteImage); + }); + _taskTypeDefinition.getTierSpriteIdMap().forEach((idKey, spriteId) -> { + Integer tierId = Integer.parseInt(idKey); + BufferedImage spriteImage = spriteManager.getSprite(spriteId, 0); + tierSprites.put(tierId, spriteImage); + }); + if (_taskTypeDefinition.getIntEnumMap().containsKey("tierPoints")) + { + int enumId = _taskTypeDefinition.getIntEnumMap().get("tierPoints"); + EnumComposition enumComposition = client.getEnum(enumId); + int[] keys = enumComposition.getKeys(); + for (int key : keys) + { + tierPoints.put(key, enumComposition.getIntValue(key)); + } + } + future.complete(true); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } + + public String getFilterConfigPrefix() + { + return _taskTypeDefinition.getTaskJsonName() + "."; + } + + private HashSet getButtonFiltersSpriteIds() + { + HashSet sprites = new HashSet<>(); + _taskTypeDefinition.getFilters().stream().filter( + (filterConfig) -> filterConfig.getFilterType().equals(FilterType.BUTTON_FILTER) + ).forEach((filterConfig) -> { + if (filterConfig.getCustomItems() != null) + { + filterConfig.getCustomItems().forEach((customSprite) -> { + Integer spriteId = customSprite.getSpriteId(); + if (spriteId == null) + { + return; + } + sprites.add(spriteId); + }); + } + }); + return sprites; + } + + public ArrayList getTaskVarps() + { + return _taskTypeDefinition.getTaskVarps(); + } + + public String getTaskJsonName() + { + return _taskTypeDefinition.getTaskJsonName(); + } + + public HashMap getIntParamMap() + { + return _taskTypeDefinition.getIntParamMap(); + } + + public HashMap getStringParamMap() + { + return _taskTypeDefinition.getStringParamMap(); + } + + public HashMap getStringEnumMap() + { + return _taskTypeDefinition.getStringEnumMap(); + } + + public String getName() + { + return _taskTypeDefinition.getName(); + } + + public ArrayList getFilters() + { + return _taskTypeDefinition.getFilters(); + } + + public int[] getOtherVarps() + { + return _taskTypeDefinition.getOtherVarps(); + } + + public int[] getVarbits() + { + return _taskTypeDefinition.getVarbits(); + } + + public int getTaskCompletedScriptId() + { + return _taskTypeDefinition.getTaskCompletedScriptId(); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/Filter.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/Filter.java new file mode 100644 index 00000000..db4fdbeb --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/Filter.java @@ -0,0 +1,8 @@ +package net.reldo.taskstracker.data.task.filters; + +import net.reldo.taskstracker.data.task.TaskFromStruct; + +public abstract class Filter +{ + public abstract boolean meetsCriteria(TaskFromStruct task); +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/FilterService.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/FilterService.java new file mode 100644 index 00000000..eb6190a1 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/FilterService.java @@ -0,0 +1,47 @@ +package net.reldo.taskstracker.data.task.filters; + +import java.util.HashMap; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.data.jsondatastore.FilterDataClient; +import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig; + +@Singleton +@Slf4j +public class FilterService +{ + @Inject + private FilterDataClient filterDataClient; + + // Filter config cache + private HashMap _filterConfigs = new HashMap<>(); + + public FilterConfig getGlobalFilterByKey(String filterKey) + { + // Instantiate filterConfigs if not already + if (_filterConfigs == null || _filterConfigs.isEmpty()) + { + try + { + _filterConfigs = filterDataClient.getFilterConfigs(); + return _filterConfigs.get(filterKey); + } + catch (Exception ex) + { + log.error("Unable to get filter configs", ex); + } + } + else + { + return _filterConfigs.get(filterKey); + } + + return null; + } + + public void clearFilterConfigs() + { + this._filterConfigs.clear(); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/ParamButtonFilter.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/ParamButtonFilter.java new file mode 100644 index 00000000..d91dd017 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/ParamButtonFilter.java @@ -0,0 +1,34 @@ +package net.reldo.taskstracker.data.task.filters; + +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.data.task.TaskFromStruct; +import net.runelite.client.config.ConfigManager; + +@Slf4j +public class ParamButtonFilter extends Filter +{ + private final String paramName; + private final String filterConfigKey; + private final ConfigManager configManager; + + public ParamButtonFilter(ConfigManager configManager, String paramName, String filterConfigKey) + { + this.configManager = configManager; + this.paramName = paramName; + this.filterConfigKey = filterConfigKey; + } + + @Override + public boolean meetsCriteria(TaskFromStruct task) + { + String configValue = configManager.getConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, filterConfigKey); + boolean isEmptyFilterSelection = configValue == null || configValue.isEmpty() || configValue.equals("-1"); + if (isEmptyFilterSelection) + { + return false; + } + + return configValue.contains("f-" + task.getIntParam(paramName) + "-f"); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/ParamDropdownFilter.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/ParamDropdownFilter.java new file mode 100644 index 00000000..77256ab4 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/data/task/filters/ParamDropdownFilter.java @@ -0,0 +1,46 @@ +package net.reldo.taskstracker.data.task.filters; + +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.data.task.TaskFromStruct; +import net.runelite.client.config.ConfigManager; + +@Slf4j +public class ParamDropdownFilter extends Filter +{ + private final String paramName; + private final String filterConfigKey; + private final ConfigManager configManager; + + public ParamDropdownFilter(ConfigManager configManager, String paramName, String filterConfigKey) + { + this.configManager = configManager; + this.paramName = paramName; + this.filterConfigKey = filterConfigKey; + } + + @Override + public boolean meetsCriteria(TaskFromStruct task) + { + String configValue = configManager.getConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, filterConfigKey); + boolean isEmptyFilterSelection = configValue == null || configValue.isEmpty() || configValue.equals("-1"); + if (isEmptyFilterSelection) + { + return true; + } + if (task.getIntParam(paramName) != null) + { + try + { + Integer parsedConfigValue = Integer.parseInt(configValue); + return parsedConfigValue.equals(task.getIntParam(paramName)); + } + catch (Exception ex) + { + log.warn("meetsCriteria error parsing config value for {}", configValue); + return true; + } + } + return true; + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/Colors.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/Colors.java new file mode 100644 index 00000000..8b51eb34 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/Colors.java @@ -0,0 +1,10 @@ +package net.reldo.taskstracker.panel; + +import java.awt.Color; + +public class Colors { + public static Color QUALIFIED_TEXT_COLOR = new Color(34, 177, 77); + public static Color UNQUALIFIED_BACKGROUND_COLOR = new Color(70, 30, 0); + public static Color UNQUALIFIED_TEXT_COLOR = new Color(251, 93, 93); + public static Color COMPLETED_BACKGROUND_COLOR = new Color(0, 50, 0); +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/Icons.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/Icons.java new file mode 100644 index 00000000..c8e90a05 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/Icons.java @@ -0,0 +1,41 @@ +package net.reldo.taskstracker.panel; + +import java.awt.image.BufferedImage; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.runelite.client.util.ImageUtil; + +public class Icons +{ + private static final String completeBtnPath = "panel/components/complete_button/"; + public static final Icon INCOMPLETE_ONLY_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, completeBtnPath + "incomplete_only_icon.png")); + public static final Icon COMPLETE_ONLY_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, completeBtnPath + "complete_only_icon.png")); + public static final Icon COMPLETE_INCOMPLETE_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, completeBtnPath + "complete_and_incomplete_icon.png")); + + private static final String ignoredBtnPath = "panel/components/ignored_button/"; + public static final BufferedImage semivisibleimg = ImageUtil.loadImageResource(TasksTrackerPlugin.class, ignoredBtnPath + "semivisible_icon.png"); + public static final Icon SEMIVISIBLE_ICON = new ImageIcon(ImageUtil.alphaOffset(semivisibleimg, -180)); + public static final Icon INVISIBLE_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, ignoredBtnPath + "invisible_icon.png")); + public static final Icon VISIBLE_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, ignoredBtnPath + "visible_icon.png")); + + private static final String trackedBtnPath = "panel/components/tracked_button/"; + public static final Icon UNTRACKED_ONLY_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, trackedBtnPath + "untracked_icon.png")); + public static final Icon TRACKED_ONLY_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, trackedBtnPath + "tracked_icon.png")); + public static final Icon TRACKED_UNTRACKED_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, trackedBtnPath + "tracked_and_untracked_icon.png")); + + private static final String sortBtnPath = "panel/components/sort_button/"; + public static final Icon ASCENDING_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, sortBtnPath + "ascending_icon.png")); + public static final Icon DESCENDING_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, sortBtnPath + "descending_icon.png")); + + private static final String expandBtnPath = "panel/components/"; + public static final Icon MENU_EXPANDED_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, expandBtnPath + "filter_menu_expanded.png")); + public static final BufferedImage collapseImg = ImageUtil.loadImageResource(TasksTrackerPlugin.class, expandBtnPath + "filter_menu_collapsed.png"); + public static final Icon MENU_ICON_HOVER = new ImageIcon(collapseImg); + + public static final Icon MENU_COLLAPSED_ICON = new ImageIcon(ImageUtil.alphaOffset(collapseImg, -180)); + public static final ImageIcon PLUS_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, "plus.png")); + public static final ImageIcon MINUS_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, "minus.png")); + public static final ImageIcon EYE_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, "eye.png")); + public static final ImageIcon EYE_CROSS_GREY = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, "eye-cross-grey.png")); +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/LoggedInPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/LoggedInPanel.java new file mode 100644 index 00000000..1d6abd20 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/LoggedInPanel.java @@ -0,0 +1,543 @@ +package net.reldo.taskstracker.panel; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JToggleButton; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; +import javax.swing.plaf.basic.BasicButtonUI; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerConfig; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.config.ConfigValues; +import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig; +import net.reldo.taskstracker.data.jsondatastore.types.FilterType; +import net.reldo.taskstracker.data.task.TaskService; +import net.reldo.taskstracker.data.task.TaskType; +import net.reldo.taskstracker.panel.components.SearchBox; +import net.reldo.taskstracker.panel.components.TriToggleButton; +import net.reldo.taskstracker.panel.filters.ComboItem; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.util.SwingUtil; + +@Slf4j +public class LoggedInPanel extends JPanel +{ + public TaskListPanel taskListPanel; + private JComboBox> taskTypeDropdown; + + private final TaskService taskService; + private final TasksTrackerPlugin plugin; + private final TasksTrackerConfig config; + + // Filter buttons + private final TriToggleButton completedFilterBtn = new TriToggleButton(); + private final TriToggleButton trackedFilterBtn = new TriToggleButton(); + private final TriToggleButton ignoredFilterBtn = new TriToggleButton(); + private final JPanel titlePanel = new JPanel(); + + // Task list tabs + private final JPanel tabPane = new JPanel(); + + // sub-filter panel + private SubFilterPanel subFilterPanel; + private SortPanel sortPanel; + private final JToggleButton collapseBtn = new JToggleButton(); + + public LoggedInPanel(TasksTrackerPlugin plugin, TasksTrackerConfig config, TaskService taskService) + { + super(false); + this.plugin = plugin; + this.taskService = taskService; + this.config = config; + + createPanel(); + } + + @Override + public Dimension getPreferredSize() + { + return getParent().getSize(); + } + + public void redraw() + { + // taskTypeDropdown may become de-synced after profile change + String selectedTaskTypeJsonName = taskTypeDropdown.getItemAt(taskTypeDropdown.getSelectedIndex()).getValue().getTaskJsonName(); + if(!selectedTaskTypeJsonName.equals(config.taskTypeJsonName())) + { + log.debug("Task type dropdown de-synced, attempting to find current task type"); + for(int i = 0; i < taskTypeDropdown.getItemCount(); i++) + { + ComboItem item = taskTypeDropdown.getItemAt(i); + if(item.getValue().getTaskJsonName().equals(config.taskTypeJsonName())) + { + log.debug("Current task type found, setting selected task type"); + taskTypeDropdown.setSelectedIndex(i); + break; + } + } + } + subFilterPanel.redraw(); + sortPanel.redraw(); + updateCollapseButtonText(); + + taskListPanel.redraw(); + } + + public void refreshAllTasks() + { + updateCollapseButtonText(); + taskListPanel.refreshAllTasks(); + } + + private void createPanel() + { + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + taskListPanel = new TaskListPanel(plugin, taskService); + + add(getNorthPanel(), BorderLayout.NORTH); + add(getCenterPanel(), BorderLayout.CENTER); + add(getSouthPanel(), BorderLayout.SOUTH); + + loadAndApplyFilters(config.taskListTab()); + if(config.taskListTab().equals(ConfigValues.TaskListTabs.TRACKED)) + { + trackedFilterBtn.setState(1); + trackedFilterBtn.setEnabled(false); + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "trackedFilter", ConfigValues.TrackedFilterValues.TRACKED); + } + } + + private JPanel getCenterPanel() { + // wrapper for the task list and tab buttons + final JPanel taskListPanel = new JPanel(new BorderLayout()); + taskListPanel.setBackground(ColorScheme.DARK_GRAY_COLOR); + taskListPanel.setBorder(new MatteBorder(0, 0, 1, 0, ColorScheme.MEDIUM_GRAY_COLOR)); + taskListPanel.setAlignmentX(JPanel.CENTER_ALIGNMENT); + + tabPane.setLayout(new BoxLayout(tabPane, BoxLayout.X_AXIS)); + tabPane.setBorder(new EmptyBorder(0,0,0,0)); + tabPane.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH,24)); + + JToggleButton trackedTab = tabButton("Tracked Tasks", ConfigValues.TaskListTabs.TRACKED); + JToggleButton allTab = tabButton("All Tasks", ConfigValues.TaskListTabs.ALL); + JToggleButton customTab = tabButton("Custom", ConfigValues.TaskListTabs.CUSTOM); + + ButtonGroup tabGroup = new ButtonGroup(); + + tabGroup.add(trackedTab); + tabGroup.add(allTab); + tabGroup.add(customTab); + + tabPane.add(Box.createHorizontalGlue()); + tabPane.add(trackedTab); + tabPane.add(Box.createHorizontalGlue()); + tabPane.add(allTab); + tabPane.add(Box.createHorizontalGlue()); + tabPane.add(customTab); + tabPane.add(Box.createHorizontalGlue()); + + taskListPanel.add(tabPane, BorderLayout.NORTH); + taskListPanel.add(this.taskListPanel, BorderLayout.CENTER); + + // set initial filter states to "complete and incomplete", "tracked and untracked", "not ignored" + Map filterStates = new HashMap<>(); + filterStates.put("completed",0); + filterStates.put("tracked",0); + filterStates.put("ignored",0); + for(ConfigValues.TaskListTabs tab : ConfigValues.TaskListTabs.values()) + { + filterStore.put(tab, filterStates); + } + + switch (config.taskListTab()) + { + case TRACKED: + trackedTab.setSelected(true); + break; + case ALL: + allTab.setSelected(true); + break; + case CUSTOM: + customTab.setSelected(true); + break; + } + tabChanged(config.taskListTab()); + + return taskListPanel; + } + + public void tabChanged(ConfigValues.TaskListTabs newTab) + { + if(newTab != null) { + changeTab(newTab); + + switch (newTab) { + case TRACKED: + trackedFilterBtn.setState(1); + trackedFilterBtn.setEnabled(false); + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "taskListTab", newTab); + filterButtonAction("tracked"); + break; + case ALL: + trackedFilterBtn.setState(0); + trackedFilterBtn.setEnabled(false); + completedFilterBtn.setState(0); + completedFilterBtn.setEnabled(false); + ignoredFilterBtn.setState(1); + ignoredFilterBtn.setEnabled(false); + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "taskListTab", newTab); + actionAllFilterButtons(); + break; + case CUSTOM: + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "taskListTab", newTab); + plugin.refreshAllTasks(); + break; + default: + plugin.refreshAllTasks(); + break; + } + } + } + + private JToggleButton tabButton(String label, ConfigValues.TaskListTabs tab) + { + JToggleButton button = new JToggleButton(label); + + button.setBorder(new EmptyBorder(2,5,2,5)); + button.setBackground(ColorScheme.DARK_GRAY_COLOR); + button.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + button.addActionListener(e -> tabChanged(tab)); + + return button; + } + + private void changeTab(ConfigValues.TaskListTabs newTab) + { + saveFilters(); + resetFilters(); + loadAndApplyFilters(newTab); + } + + private final Map> filterStore = new HashMap<>(); + + private void saveFilters() + { + ConfigValues.TaskListTabs tab = config.taskListTab(); + + Map filterStates = new HashMap<>(); + filterStates.put("completed", config.completedFilter().ordinal()); + filterStates.put("tracked", config.trackedFilter().ordinal()); + filterStates.put("ignored", config.ignoredFilter().ordinal()); + + filterStore.put(tab, filterStates); + } + + private void resetFilters() + { + completedFilterBtn.setEnabled(true); + trackedFilterBtn.setEnabled(true); + ignoredFilterBtn.setEnabled(true); + } + + private void loadAndApplyFilters(ConfigValues.TaskListTabs tab) + { + Map filterStates = filterStore.get(tab); + + if(filterStates == null) return; + + Enum configValue; + + completedFilterBtn.setState(filterStates.get("completed")); + trackedFilterBtn.setState(filterStates.get("tracked")); + ignoredFilterBtn.setState(filterStates.get("ignored")); + + configValue = ConfigValues.CompletedFilterValues.values()[completedFilterBtn.getState()]; + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "completedFilter", configValue); + + configValue = ConfigValues.TrackedFilterValues.values()[trackedFilterBtn.getState()]; + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "trackedFilter", configValue); + + configValue = ConfigValues.IgnoredFilterValues.values()[ignoredFilterBtn.getState()]; + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "ignoredFilter", configValue); + } + + private JPanel getSouthPanel() + { + JPanel southPanel = new JPanel(new BorderLayout()); + southPanel.setBorder(new EmptyBorder(5,0,2,0)); + + JButton importButton = new JButton("Import"); + importButton.setBorder(new EmptyBorder(5, 5, 5, 5)); + importButton.setLayout(new BorderLayout(0, PluginPanel.BORDER_OFFSET)); + importButton.addActionListener(e -> plugin.openImportJsonDialog()); + southPanel.add(importButton, BorderLayout.WEST); + + JButton totalsButton = new JButton("Totals"); + totalsButton.setBorder(new EmptyBorder(5, 5, 5, 5)); + totalsButton.setLayout(new BorderLayout(0, PluginPanel.BORDER_OFFSET)); + totalsButton.addActionListener(e -> plugin.sendTotalsToChat()); + southPanel.add(totalsButton, BorderLayout.CENTER); + + JButton exportButton = new JButton("Export"); + exportButton.setBorder(new EmptyBorder(5, 5, 5, 5)); + exportButton.setLayout(new BorderLayout(0, PluginPanel.BORDER_OFFSET)); + exportButton.addActionListener(e -> plugin.copyJsonToClipboard()); + southPanel.add(exportButton, BorderLayout.EAST); + + return southPanel; + } + + private JPanel getNorthPanel() + { + JPanel northPanel = new JPanel(); + BoxLayout layout = new BoxLayout(northPanel, BoxLayout.Y_AXIS); + northPanel.setLayout(layout); + northPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + taskTypeDropdown = new JComboBox<>(); + taskTypeDropdown.setAlignmentX(LEFT_ALIGNMENT); + taskTypeDropdown.setFocusable(false); + initTaskTypeDropdownAsync(); + + // Wrapper for collapsible sub-filter menu + JPanel subFilterWrapper = new JPanel(); + subFilterWrapper.setLayout(new BorderLayout()); + subFilterWrapper.setBorder(new MatteBorder(1, 0, 1, 0, ColorScheme.MEDIUM_GRAY_COLOR)); + subFilterWrapper.setAlignmentX(LEFT_ALIGNMENT); + subFilterWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + // collapse button + SwingUtil.removeButtonDecorations(collapseBtn); + collapseBtn.setIcon(Icons.MENU_COLLAPSED_ICON); + collapseBtn.setSelectedIcon(Icons.MENU_EXPANDED_ICON); + collapseBtn.setRolloverIcon(Icons.MENU_ICON_HOVER); + SwingUtil.addModalTooltip(collapseBtn, "Collapse filters", "Expand filters"); + collapseBtn.setBackground(ColorScheme.DARKER_GRAY_COLOR); + collapseBtn.setAlignmentX(LEFT_ALIGNMENT); + collapseBtn.setUI(new BasicButtonUI()); // substance breaks the layout + collapseBtn.addActionListener(ev -> subFilterPanel.setVisible(!subFilterPanel.isVisible())); + collapseBtn.setHorizontalTextPosition(JButton.CENTER); + collapseBtn.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + collapseBtn.setFont(FontManager.getRunescapeSmallFont()); + collapseBtn.setBorder(new EmptyBorder(2, 0, 2, 0)); + collapseBtn.setFocusable(false); + + // panel to hold sub-filters + subFilterPanel = new SubFilterPanel(plugin, taskService); + + subFilterWrapper.add(collapseBtn, BorderLayout.NORTH); + subFilterWrapper.add(subFilterPanel, BorderLayout.CENTER); + + sortPanel = new SortPanel(plugin, taskService, taskListPanel); + + northPanel.add(getTitleAndButtonPanel()); + northPanel.add(Box.createVerticalStrut(10)); + northPanel.add(taskTypeDropdown); + northPanel.add(Box.createVerticalStrut(2)); + northPanel.add(getSearchPanel()); + northPanel.add(Box.createVerticalStrut(2)); + northPanel.add(sortPanel); + northPanel.add(Box.createVerticalStrut(2)); + northPanel.add(subFilterWrapper); + + return northPanel; + } + + private JPanel getTitleAndButtonPanel() + { + titlePanel.setLayout(new BorderLayout()); + titlePanel.setBackground(ColorScheme.DARK_GRAY_COLOR); + titlePanel.setPreferredSize(new Dimension(0, 30)); + titlePanel.setBorder(new EmptyBorder(5, 5, 5, 10)); + + JLabel title = new JLabel("Tasks Tracker"); + title.setHorizontalAlignment(SwingConstants.LEFT); + title.setForeground(Color.WHITE); + + JPopupMenu reloadPluginPopup = new JPopupMenu(); + reloadPluginPopup.setBorder(new EmptyBorder(5, 5, 5, 5)); + JMenuItem reloadPluginMenuItem = new JMenuItem("Reload plugin"); + reloadPluginMenuItem.addActionListener(e -> plugin.reloadTaskType()); + reloadPluginPopup.add(reloadPluginMenuItem); + title.setComponentPopupMenu(reloadPluginPopup); + + // Filter button bar + final JPanel viewControls = new JPanel(); + viewControls.setLayout(new BoxLayout(viewControls, BoxLayout.X_AXIS)); + viewControls.setBackground(ColorScheme.DARK_GRAY_COLOR); + + // Completed tasks filter button + SwingUtil.removeButtonDecorations(completedFilterBtn); + completedFilterBtn.setIcons(Icons.COMPLETE_INCOMPLETE_ICON, Icons.COMPLETE_ONLY_ICON, Icons.INCOMPLETE_ONLY_ICON); + completedFilterBtn.setToolTips("All tasks", "Completed tasks only", "Incomplete tasks only"); + completedFilterBtn.setBackground(ColorScheme.DARK_GRAY_COLOR); + completedFilterBtn.setStateChangedAction(e -> filterButtonAction("completed")); + completedFilterBtn.popupMenuEnabled(true); + completedFilterBtn.setState(config.completedFilter().ordinal()); + + viewControls.add(completedFilterBtn); + + // Tracked tasks filter button + SwingUtil.removeButtonDecorations(trackedFilterBtn); + trackedFilterBtn.setIcons(Icons.TRACKED_UNTRACKED_ICON, Icons.TRACKED_ONLY_ICON, Icons.UNTRACKED_ONLY_ICON); + trackedFilterBtn.setToolTips("All tasks", "Tracked tasks only", "Untracked tasks only"); + trackedFilterBtn.setBackground(ColorScheme.DARK_GRAY_COLOR); + trackedFilterBtn.setStateChangedAction(e -> filterButtonAction("tracked")); + trackedFilterBtn.popupMenuEnabled(true); + trackedFilterBtn.setState(config.trackedFilter().ordinal()); + + viewControls.add(trackedFilterBtn); + + // Ignored tasks filter button + SwingUtil.removeButtonDecorations(ignoredFilterBtn); + ignoredFilterBtn.setIcons(Icons.SEMIVISIBLE_ICON, Icons.VISIBLE_ICON, Icons.INVISIBLE_ICON); + ignoredFilterBtn.setToolTips("Hide ignored tasks", "All tasks", "Ignored tasks only"); + ignoredFilterBtn.setBackground(ColorScheme.DARK_GRAY_COLOR); + ignoredFilterBtn.setStateChangedAction(e -> filterButtonAction("ignored")); + ignoredFilterBtn.popupMenuEnabled(true); + ignoredFilterBtn.setState(config.ignoredFilter().ordinal()); + + viewControls.add(ignoredFilterBtn); + + titlePanel.add(viewControls, BorderLayout.EAST); + titlePanel.add(title, BorderLayout.WEST); + titlePanel.setAlignmentX(LEFT_ALIGNMENT); + + return titlePanel; + } + + private void filterButtonActionNoRefresh(String filter) + { + int state; + Enum configValue; + + switch (filter) + { + case "completed": + state = completedFilterBtn.getState(); + configValue = ConfigValues.CompletedFilterValues.values()[state]; + break; + case "tracked": + state = trackedFilterBtn.getState(); + configValue = ConfigValues.TrackedFilterValues.values()[state]; + break; + case "ignored": + state = ignoredFilterBtn.getState(); + configValue = ConfigValues.IgnoredFilterValues.values()[state]; + break; + default: + log.debug("Filter button action failed due to unrecognised filter."); + return; + } + + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, filter + "Filter", configValue); + } + + private void filterButtonAction(String filter) + { + filterButtonActionNoRefresh(filter); + plugin.refreshAllTasks(); + } + + private void actionAllFilterButtons() + { + filterButtonActionNoRefresh("tracked"); + filterButtonActionNoRefresh("ignored"); + filterButtonActionNoRefresh("completed"); + plugin.refreshAllTasks(); + } + + private JPanel getSearchPanel() + { + JPanel filtersPanel = new JPanel(); + filtersPanel.setAlignmentX(LEFT_ALIGNMENT); + filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS)); + + SearchBox textSearch = new SearchBox(); + textSearch.addTextChangedListener(() -> { + plugin.taskTextFilter = textSearch.getText().toLowerCase(); + plugin.refreshAllTasks(); + }); + + filtersPanel.add(textSearch); + + return filtersPanel; + } + + private void updateCollapseButtonText() + { + if (taskService.getCurrentTaskType() == null) return; + + ArrayList filters = taskService.getCurrentTaskType().getFilters(); + + int countInclusive = 0; + int countExclusive = 0; + + for (FilterConfig filterConfig : filters) + { + String filterText = Optional.ofNullable(plugin.getConfigManager() + .getConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, + taskService.getCurrentTaskType().getFilterConfigPrefix() + filterConfig.getConfigKey())) + .orElse(""); + + int count = (filterText.isEmpty() || filterText.equals("-1")) ? 0 : filterText.split(",").length; + + if (filterConfig.getFilterType().equals(FilterType.BUTTON_FILTER)) countInclusive += count; + if (filterConfig.getFilterType().equals(FilterType.DROPDOWN_FILTER)) countExclusive += count; + } + + collapseBtn.setText(countInclusive + " inclusive, " + countExclusive + " exclusive filters"); + } + + private void initTaskTypeDropdownAsync() { + TaskType currentTaskType = taskService.getCurrentTaskType(); + taskService.getTaskTypesByJsonName().thenAccept(taskTypes -> { + ArrayList> taskTypeItems = new ArrayList<>(); + taskTypes.forEach((taskTypeJsonName, taskType) -> { + ComboItem item = new ComboItem<>(taskType, taskType.getName()); + taskTypeItems.add(item); + taskTypeDropdown.addItem(item); + }); + + ComboItem currentTaskTypeComboItem = taskTypeItems.stream() + .filter(item -> item.getValue().equals(currentTaskType)) + .findFirst().orElseGet(() -> taskTypeItems.get(0)); + taskTypeDropdown.setSelectedItem(currentTaskTypeComboItem); + taskTypeDropdown.addActionListener(e -> { + TaskType taskType = taskTypeDropdown.getItemAt(taskTypeDropdown.getSelectedIndex()).getValue(); + taskService.setTaskType(taskType).thenAccept(wasTaskTypeChanged ->{ + if (wasTaskTypeChanged) { + SwingUtilities.invokeLater(() -> + { + redraw(); + plugin.refreshAllTasks(); + }); + } + }); + }); + }); + } +} \ No newline at end of file diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/LoggedOutPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/LoggedOutPanel.java new file mode 100644 index 00000000..16331bc4 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/LoggedOutPanel.java @@ -0,0 +1,12 @@ +package net.reldo.taskstracker.panel; + +import javax.swing.JLabel; +import javax.swing.JPanel; + +public class LoggedOutPanel extends JPanel +{ + public LoggedOutPanel() + { + this.add(new JLabel("Log into an account to track tasks.")); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/SortPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/SortPanel.java new file mode 100644 index 00000000..dec703eb --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/SortPanel.java @@ -0,0 +1,85 @@ +package net.reldo.taskstracker.panel; + +import java.util.List; +import java.util.stream.Collectors; +import javax.swing.BoxLayout; +import javax.swing.Icon; +import javax.swing.JComboBox; +import javax.swing.SwingUtilities; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.config.ConfigValues; +import net.reldo.taskstracker.data.task.TaskService; +import net.reldo.taskstracker.panel.components.FixedWidthPanel; +import net.reldo.taskstracker.panel.components.MultiToggleButton; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.util.SwingUtil; + +@Slf4j +public class SortPanel extends FixedWidthPanel +{ + + private final TasksTrackerPlugin plugin; + private final TaskService taskService; + private final TaskListPanel taskListPanel; + private final ConfigManager configManager; + private JComboBox sortDropdown; + private MultiToggleButton directionButton; + + public SortPanel(TasksTrackerPlugin plugin, TaskService taskService, TaskListPanel taskListPanel) + { + this.plugin = plugin; + this.configManager = plugin.getConfigManager(); + this.taskService = taskService; + this.taskListPanel = taskListPanel; + + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + setAlignmentX(LEFT_ALIGNMENT); + } + + public void redraw() + { + removeAll(); + + List criteriaList = taskService.getSortedIndexes().keySet().stream() + .sorted() + .map((str) -> str.substring(0, 1).toUpperCase() + str.substring(1)) + .collect(Collectors.toList()); + criteriaList.add(0,"Default"); + + String[] criteriaArray = criteriaList.toArray(new String[0]); + sortDropdown = new JComboBox<>(criteriaArray); + sortDropdown.setAlignmentX(LEFT_ALIGNMENT); + sortDropdown.setSelectedIndex(0); + sortDropdown.addActionListener(e -> { + updateConfig(); + SwingUtilities.invokeLater(taskListPanel::redraw); + }); + sortDropdown.setFocusable(false); + + directionButton = new MultiToggleButton(2); + SwingUtil.removeButtonDecorations(directionButton); + directionButton.setIcons(new Icon[]{Icons.ASCENDING_ICON, Icons.DESCENDING_ICON}); + directionButton.setToolTips(new String[]{"Ascending", "Descending"}); + directionButton.setBackground(ColorScheme.DARK_GRAY_COLOR); + directionButton.setStateChangedAction(e -> { + updateConfig(); + SwingUtilities.invokeLater(taskListPanel::redraw); + }); + + add(sortDropdown); + add(directionButton); + updateConfig(); + } + + protected void updateConfig() + { + log.debug("updateConfig {}, {}, {}", TasksTrackerPlugin.CONFIG_GROUP_NAME, "sortCriteria", sortDropdown.getItemAt(sortDropdown.getSelectedIndex()).toLowerCase()); + configManager.setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "sortCriteria", sortDropdown.getItemAt(sortDropdown.getSelectedIndex()).toLowerCase()); + + ConfigValues.SortDirections configValue = ConfigValues.SortDirections.values()[directionButton.getState()]; + log.debug("updateConfig {}, {}, {}", TasksTrackerPlugin.CONFIG_GROUP_NAME, "sortDirection", configValue); + configManager.setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "sortDirection", configValue); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/SubFilterPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/SubFilterPanel.java new file mode 100644 index 00000000..16b69261 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/SubFilterPanel.java @@ -0,0 +1,122 @@ +package net.reldo.taskstracker.panel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import javax.swing.BoxLayout; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig; +import net.reldo.taskstracker.data.jsondatastore.types.FilterValueType; +import net.reldo.taskstracker.data.task.TaskService; +import net.reldo.taskstracker.panel.components.FixedWidthPanel; +import net.reldo.taskstracker.panel.filters.ComboItem; +import net.reldo.taskstracker.panel.filters.DynamicButtonFilterPanel; +import net.reldo.taskstracker.panel.filters.DynamicDropdownFilterPanel; +import net.reldo.taskstracker.panel.filters.FilterPanel; +import net.runelite.client.ui.ColorScheme; + +@Slf4j +public class SubFilterPanel extends FixedWidthPanel +{ + private final List filterPanels = new ArrayList<>(); + private TasksTrackerPlugin plugin; + private TaskService taskService; + + public SubFilterPanel(TasksTrackerPlugin plugin, TaskService taskService) + { + this.plugin = plugin; + this.taskService = taskService; + log.debug("SubFilterPanel.constructor"); + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setBorder(new EmptyBorder(0, 0, 0, 0)); + setBackground(ColorScheme.DARKER_GRAY_COLOR); + setVisible(false); + } + + public void redraw() + { + log.debug("SubFilterPanel.redraw"); // TODO: figure out why this calls multiple times upon switching task type + + removeAll(); + + filterPanels.clear(); + filterPanels.addAll(getFilterPanels(taskService.getCurrentTaskType().getFilters())); + filterPanels.forEach((filterPanel) -> { + add(filterPanel); + filterPanel.redraw(); + }); + } + + private List getFilterPanels(ArrayList filterConfigs) + { + List filterPanels = new ArrayList<>(); + for (FilterConfig filterConfig : filterConfigs) + { + try + { + FilterPanel filterPanel = createDynamicFilterPanel(filterConfig); + if (filterPanel == null) + { + continue; + } + filterPanels.add(filterPanel); + } + catch (Exception ex) + { + log.error("error creating filter panel {} {}", filterConfig.getConfigKey(), ex); + } + } + return filterPanels; + } + + private FilterPanel createDynamicFilterPanel(FilterConfig filterConfig) throws Exception + { + switch (filterConfig.getFilterType()) + { + case BUTTON_FILTER: + return new DynamicButtonFilterPanel(plugin, filterConfig, taskService.getCurrentTaskType()); + case DROPDOWN_FILTER: + ComboItem[] dropdownItems = getDropdownItems(filterConfig); + return new DynamicDropdownFilterPanel(plugin, filterConfig, taskService.getCurrentTaskType(), dropdownItems); + default: + log.error("invalid filter type " + filterConfig.getFilterType()); + return null; + } + } + + private ComboItem[] getDropdownItems(FilterConfig filterConfig) throws ExecutionException, InterruptedException + { + if (filterConfig.getValueType() == null) + { + throw new Error("invalid filterConfig for dropdown items"); + } + if (filterConfig.getValueType().equals(FilterValueType.PARAM_INTEGER)) + { + String enumName = filterConfig.getOptionLabelEnum(); + if (!enumName.isEmpty()) + { + HashMap enumEntries = taskService.getStringEnumValuesAsync(enumName).get(); // TODO: blocking call + ArrayList> options = new ArrayList<>(); + options.add(new ComboItem<>(-1, "")); + for (Map.Entry entry : enumEntries.entrySet()) + { + if (filterConfig.getValueName().equals("tier")) + { + if (entry.getValue().equals("All") || entry.getValue().equals("Tier")) + { + continue; + } + } + options.add(new ComboItem<>(entry.getKey(), entry.getValue())); + } + return options.toArray(new ComboItem[0]); + } + } + + return new ComboItem[0]; + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskListPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskListPanel.java new file mode 100644 index 00000000..0fffbd9d --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskListPanel.java @@ -0,0 +1,202 @@ +package net.reldo.taskstracker.panel; + +import java.awt.Component; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.config.ConfigValues; +import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinitionSkill; +import net.reldo.taskstracker.data.task.TaskFromStruct; +import net.reldo.taskstracker.data.task.TaskService; +import net.reldo.taskstracker.panel.components.FixedWidthPanel; +import net.runelite.api.Skill; +import net.runelite.client.ui.FontManager; + +@Slf4j +public class TaskListPanel extends JScrollPane +{ + public TasksTrackerPlugin plugin; + private final HashMap taskPanelsByStructId = new HashMap<>(); + private final TaskListListPanel taskList; + private final TaskService taskService; + private final JLabel emptyTasks = new JLabel(); + + public TaskListPanel(TasksTrackerPlugin plugin, TaskService taskService) + { + this.plugin = plugin; + + taskList = new TaskListListPanel(plugin); + this.taskService = taskService; + + setViewportView(taskList); + setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + } + + public String getEmptyTaskListMessage() + { + return "No tasks match the current filters."; + } + + public void redraw() + { + taskList.redraw(); + } + + public void refreshAllTasks() + { + log.debug("TaskListPanel.refreshAllTasks"); + if (!SwingUtilities.isEventDispatchThread()) + { + log.error("Task list panel refresh failed - not event dispatch thread."); + return; + } + for (TaskPanel taskPanel : taskPanelsByStructId.values()) + { + taskPanel.refresh(); + } + } + + public void refreshMultipleTasks(Collection tasks) + { + log.debug("TaskListPanel.refreshMultipleTasks {}", tasks.size()); + if (!SwingUtilities.isEventDispatchThread()) + { + log.error("Task list panel refresh failed - not event dispatch thread."); + return; + } + for (TaskFromStruct task : tasks) + { + refresh(task); + } + } + + public void refreshTask(TaskFromStruct task) + { + log.debug("TaskListPanel.refreshMultipleTasks {}", task.getName()); + refresh(task); + } + + private void refresh(TaskFromStruct task) + { + if (!SwingUtilities.isEventDispatchThread()) + { + log.error("Task list panel refresh failed - not event dispatch thread."); + return; + } + if (task == null) + { + log.debug("Attempted to refresh null task"); + return; + } + + emptyTasks.setVisible(false); + + TaskPanel panel = taskPanelsByStructId.get(task.getStructId()); + if (panel != null) + { + panel.refresh(); + } + + boolean isAnyTaskPanelVisible = taskPanelsByStructId.values().stream() + .anyMatch(TaskPanel::isVisible); + + if (!isAnyTaskPanelVisible) + { + emptyTasks.setVisible(true); + } + } + + public void refreshTaskPanelsWithSkill(Skill skill) + { + // Refresh all task panels for tasks with 'skill' or + // 'SKILLS' (any skill) or 'TOTAL LEVEL' as a requirement. + taskPanelsByStructId.values().stream() + .filter(tp -> + { + List skillsList = tp.task.getTaskDefinition().getSkills(); + if (skillsList == null || skillsList.isEmpty()) + { + return false; + } + + return skillsList.stream() + .map(TaskDefinitionSkill::getSkill) + .anyMatch(s -> s.equalsIgnoreCase(skill.getName()) || + s.equalsIgnoreCase("SKILLS") || + s.equalsIgnoreCase("TOTAL LEVEL") + ); + }) + .forEach(TaskPanel::refresh); + } + + private class TaskListListPanel extends FixedWidthPanel + { + private final TasksTrackerPlugin plugin; + + public TaskListListPanel(TasksTrackerPlugin plugin) + { + this.plugin = plugin; + + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setBorder(new EmptyBorder(0, 10, 10, 10)); + setAlignmentX(Component.LEFT_ALIGNMENT); + + emptyTasks.setBorder(new EmptyBorder(10,0,10,0)); + emptyTasks.setText("
" + getEmptyTaskListMessage() + "
"); + emptyTasks.setFont(FontManager.getRunescapeSmallFont()); + emptyTasks.setHorizontalAlignment(JLabel.CENTER); + emptyTasks.setVerticalAlignment(JLabel.CENTER); + add(emptyTasks); + emptyTasks.setVisible(false); + } + + public void redraw() + { + log.debug("TaskListPanel.redraw"); + if(SwingUtilities.isEventDispatchThread()) + { + removeAll(); + taskPanelsByStructId.clear(); + add(emptyTasks); + emptyTasks.setVisible(false); + + log.debug("TaskListPanel creating panels"); + List tasks = taskService.getTasks(); + if (tasks == null || tasks.isEmpty()) + { + emptyTasks.setVisible(true); + return; + } + + for (int indexPosition = 0; indexPosition < tasks.size(); indexPosition++) + { + int adjustedIndexPosition = indexPosition; + if (plugin.getConfig().sortDirection().equals(ConfigValues.SortDirections.DESCENDING)) + { + adjustedIndexPosition = tasks.size() - (adjustedIndexPosition + 1); + } + TaskFromStruct task = tasks.get(taskService.getSortedTaskIndex(plugin.getConfig().sortCriteria(), adjustedIndexPosition)); + TaskPanel taskPanel = new TaskPanel(plugin, task); + add(taskPanel); + taskPanelsByStructId.put(task.getStructId(), taskPanel); + } + + log.debug("TaskListPanel validate and repaint"); + validate(); + repaint(); + } + else + { + log.error("Task list panel redraw failed - not event dispatch thread."); + } + } + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskPanel.java new file mode 100644 index 00000000..b17fda6c --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskPanel.java @@ -0,0 +1,429 @@ +package net.reldo.taskstracker.panel; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JToggleButton; +import javax.swing.JToolTip; +import javax.swing.ToolTipManager; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.HtmlUtil; +import net.reldo.taskstracker.TasksTrackerConfig; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.config.ConfigValues.CompletedFilterValues; +import net.reldo.taskstracker.config.ConfigValues.IgnoredFilterValues; +import net.reldo.taskstracker.config.ConfigValues.TrackedFilterValues; +import net.reldo.taskstracker.data.jsondatastore.types.FilterType; +import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinitionSkill; +import net.reldo.taskstracker.data.task.TaskFromStruct; +import net.reldo.taskstracker.data.task.filters.Filter; +import net.reldo.taskstracker.data.task.filters.ParamButtonFilter; +import net.reldo.taskstracker.data.task.filters.ParamDropdownFilter; +import net.runelite.api.Constants; +import net.runelite.api.Skill; +import net.runelite.client.game.SkillIconManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.util.SwingUtil; + +@Slf4j +public class TaskPanel extends JPanel +{ + public final TaskFromStruct task; + + private final JLabel tierIcon = new JLabel(); + private final JPanel container = new JPanel(new BorderLayout()); + private final JPanel body = new JPanel(new BorderLayout()); + private final JLabel name = new JLabel("task"); + private final JLabel description = new JLabel("description"); + private final JPanel buttons = new JPanel(); + private final JToggleButton toggleTrack = new JToggleButton(); + private final JToggleButton toggleIgnore = new JToggleButton(); + + protected final ArrayList filters = new ArrayList<>(); + + protected TasksTrackerPlugin plugin; + + public TaskPanel(TasksTrackerPlugin plugin, TaskFromStruct task) + { + super(new BorderLayout()); + this.plugin = plugin; + this.task = task; + createPanel(); + setComponentPopupMenu(getPopupMenu()); + ToolTipManager.sharedInstance().registerComponent(this); + + task.getTaskType().getFilters().forEach((filterConfig) -> { + String paramName = filterConfig.getValueName(); + if (filterConfig.getFilterType().equals(FilterType.BUTTON_FILTER)) + { + Filter filter = new ParamButtonFilter(plugin.getConfigManager(), paramName, task.getTaskType().getTaskJsonName() + "." + filterConfig.getConfigKey()); + filters.add(filter); + } + else if (filterConfig.getFilterType().equals(FilterType.DROPDOWN_FILTER)) + { + Filter filter = new ParamDropdownFilter(plugin.getConfigManager(), paramName, task.getTaskType().getTaskJsonName() + "." + filterConfig.getConfigKey()); + filters.add(filter); + } + }); + + refresh(); + } + + public JPopupMenu getPopupMenu() + { + return null; + } + + public String getTaskTooltip() + { + StringBuilder tooltipText = new StringBuilder(); + tooltipText.append(HtmlUtil.wrapWithBold(task.getName())).append(HtmlUtil.HTML_LINE_BREAK); + tooltipText.append(task.getDescription()).append(HtmlUtil.HTML_LINE_BREAK); + + String skillSection = getSkillSectionHtml(); + if (skillSection != null) + { + tooltipText.append(skillSection).append(HtmlUtil.HTML_LINE_BREAK); + } + + String wikiNotes = task.getTaskDefinition().getWikiNotes(); + if (wikiNotes != null) + { + tooltipText.append(HtmlUtil.HTML_LINE_BREAK).append(wikiNotes).append(HtmlUtil.HTML_LINE_BREAK); + } + + if (task.isCompleted()) + { + tooltipText.append(HtmlUtil.HTML_LINE_BREAK); + String datePattern = "MM-dd-yyyy hh:mma"; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern); + tooltipText.append("✔ ").append(simpleDateFormat.format(new Date(task.getCompletedOn()))); + } + + Float completionPercent = task.getTaskDefinition().getCompletionPercent(); + if (completionPercent != null) + { + tooltipText.append(HtmlUtil.HTML_LINE_BREAK).append("Players Completed: ").append(completionPercent).append('%'); + } + + return HtmlUtil.wrapWithHtml( + HtmlUtil.wrapWithWrappingParagraph(tooltipText.toString(), 200) + ); + } + + public Color getTaskBackgroundColor() + { + if (plugin.playerSkills == null) + { + return ColorScheme.DARKER_GRAY_COLOR; + } + + if (task.isCompleted()) + { + return Colors.COMPLETED_BACKGROUND_COLOR; + } + + if (task.getTaskDefinition().getSkills() == null || task.getTaskDefinition().getSkills().size() == 0) + { + return ColorScheme.DARKER_GRAY_COLOR; + } + + for (TaskDefinitionSkill requiredSkill : task.getTaskDefinition().getSkills()) + { + Skill skill; + String requiredSkillName = requiredSkill.getSkill().toUpperCase(); + try + { + skill = Skill.valueOf(requiredSkillName); + } + catch (IllegalArgumentException ex) + { + log.error("invalid skill name " + requiredSkillName); + continue; + } + + if (plugin.playerSkills[skill.ordinal()] < requiredSkill.getLevel()) + { + return Colors.UNQUALIFIED_BACKGROUND_COLOR; + } + } + + return ColorScheme.DARKER_GRAY_COLOR; + } + + public void createPanel() + { + setLayout(new BorderLayout()); + setBorder(new EmptyBorder(0, 0, 7, 0)); + + container.setBorder(new EmptyBorder(7, 7, 6, 0)); + + // Body + + name.setFont(FontManager.getRunescapeSmallFont()); + name.setForeground(Color.WHITE); + body.add(name, BorderLayout.NORTH); + + description.setFont(FontManager.getRunescapeSmallFont()); + description.setForeground(Color.GRAY); + body.add(description, BorderLayout.CENTER); + + // Buttons + buttons.setLayout(new BoxLayout(buttons, BoxLayout.Y_AXIS)); + buttons.setBorder(new EmptyBorder(0, 0, 0, 7)); + + toggleTrack.setPreferredSize(new Dimension(8, 8)); + toggleTrack.setIcon(Icons.PLUS_ICON); + toggleTrack.setSelectedIcon(Icons.MINUS_ICON); + toggleTrack.setBorder(new EmptyBorder(5, 0, 5, 0)); + toggleTrack.addActionListener(e -> { + task.setTracked(toggleTrack.isSelected()); + plugin.pluginPanel.taskListPanel.refreshTask(task); + plugin.saveCurrentTaskTypeData(); + }); + SwingUtil.removeButtonDecorations(toggleTrack); + + toggleIgnore.setPreferredSize(new Dimension(8, 8)); + toggleIgnore.setIcon(Icons.EYE_CROSS_GREY); + toggleIgnore.setSelectedIcon(Icons.EYE_ICON); + SwingUtil.addModalTooltip(toggleIgnore, "Unignore", "Ignore"); + toggleIgnore.setBorder(new EmptyBorder(5, 0, 5, 0)); + toggleIgnore.addActionListener(e -> { + task.setIgnored(!task.isIgnored()); + plugin.pluginPanel.taskListPanel.refreshTask(task); + plugin.saveCurrentTaskTypeData(); + }); + SwingUtil.removeButtonDecorations(toggleIgnore); + + buttons.add(toggleTrack); + buttons.add(toggleIgnore); + + // Full + container.add(tierIcon, BorderLayout.WEST); + container.add(body, BorderLayout.CENTER); + container.add(buttons, BorderLayout.EAST); + + BufferedImage tierSprite = task.getTaskType().getTierSprites().get(task.getTier()); + if (tierSprite != null) + { + tierIcon.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT)); + tierIcon.setIcon(new ImageIcon(tierSprite)); + tierIcon.setBorder(new EmptyBorder(0, 0, 0, 5)); + } + else + { + tierIcon.setBorder(new EmptyBorder(0, 0, 0, 0)); + } + + add(container, BorderLayout.NORTH); + + addMouseListener(new MouseAdapter() + { + @Override + public void mouseReleased(MouseEvent e) + { + if (e.isPopupTrigger()) + { + JPopupMenu menu = createWikiPopupMenu(); + menu.show(e.getComponent(), e.getX(), e.getY()); + } + } + }); + } + + public JPopupMenu createWikiPopupMenu() + { + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem openWikiItem = new JMenuItem("Wiki"); + openWikiItem.addActionListener(e -> openRuneScapeWiki()); + popupMenu.add(openWikiItem); + return popupMenu; + } + + private void openRuneScapeWiki() + { + String wikiUrl = String.format("https://oldschool.runescape.wiki/%s", URLEncoder.encode(task.getName().replace(' ', '_'), StandardCharsets.UTF_8)); + if (Desktop.isDesktopSupported()) + { + try + { + Desktop.getDesktop().browse(new URI(wikiUrl)); + } + catch (IOException | URISyntaxException ex) + { + ex.printStackTrace(); + } + } + else + { + log.warn("Desktop browsing is not supported on this system."); + } + } + + public void refresh() + { + setBackgroundColor(getTaskBackgroundColor()); + name.setText(HtmlUtil.wrapWithHtml(task.getName())); + description.setText(HtmlUtil.wrapWithHtml(task.getDescription())); + toggleTrack.setSelected(task.isTracked()); + toggleIgnore.setSelected(task.isIgnored()); + + setVisible(meetsFilterCriteria()); + + revalidate(); + } + + protected boolean meetsFilterCriteria() + { + String nameLowercase = task.getName().toLowerCase(); + String descriptionLowercase = task.getDescription().toLowerCase(); + if (plugin.taskTextFilter != null && + !nameLowercase.contains(plugin.taskTextFilter) && + !descriptionLowercase.contains(plugin.taskTextFilter)) + { + return false; + } + + TasksTrackerConfig config = plugin.getConfig(); + + for (Filter filter : filters) + { + if (!filter.meetsCriteria(task)) + { + return false; + } + } + + if (config.completedFilter().equals(CompletedFilterValues.INCOMPLETE) && task.isCompleted()) + { + return false; + } + if (config.completedFilter().equals(CompletedFilterValues.COMPLETE) && !task.isCompleted()) + { + return false; + } + + if (config.ignoredFilter().equals(IgnoredFilterValues.NOT_IGNORED) && task.isIgnored()) + { + return false; + } + if (config.ignoredFilter().equals(IgnoredFilterValues.IGNORED) && !task.isIgnored()) + { + return false; + } + + if (config.trackedFilter().equals(TrackedFilterValues.UNTRACKED) && task.isTracked()) + { + return false; + } + return !config.trackedFilter().equals(TrackedFilterValues.TRACKED) || task.isTracked(); + } + + private void setBackgroundColor(Color color) + { + container.setBackground(color); + body.setBackground(color); + buttons.setBackground(color); + } + + @Override + public Dimension getMaximumSize() + { + return new Dimension(PluginPanel.PANEL_WIDTH, getPreferredSize().height); + } + + @Override + public JToolTip createToolTip() + { + JToolTip customTooltip = new JToolTip(); + customTooltip.setFont(FontManager.getRunescapeSmallFont()); + return customTooltip; + } + + @Override + public String getToolTipText(MouseEvent mouseEvent) + { + return getTaskTooltip(); + } + + private String getSkillSectionHtml() + { + List requiredSkills = task.getTaskDefinition().getSkills(); + if (requiredSkills == null) + { + return null; + } + StringBuilder skillSection = new StringBuilder(); + skillSection.append(HtmlUtil.HTML_LINE_BREAK); + for (TaskDefinitionSkill requiredSkill : requiredSkills) + { + Skill skill; + try + { + skill = Skill.valueOf(requiredSkill.getSkill().toUpperCase()); + } + catch (IllegalArgumentException ex) + { + log.warn("unknown skill: {}", requiredSkill.getSkill().toUpperCase(), ex); + continue; + } + + + Integer requiredLevel = requiredSkill.getLevel(); + int playerLevel = -1; + if (requiredLevel == null) + { + continue; + } + if (plugin.playerSkills != null) + { + playerLevel = plugin.playerSkills[skill.ordinal()]; + } + String skillMessage = getSkillRequirementHtml(requiredSkill.getSkill().toLowerCase(), playerLevel, requiredLevel); + skillSection.append(skillMessage).append(" "); + } + + return skillSection.toString(); + } + + private String getSkillRequirementHtml(String skillName, Integer playerLevel, int requiredLevel) + { + String skillIconPath = "/skill_icons_small/" + skillName + ".png"; + URL url = SkillIconManager.class.getResource(skillIconPath); + Color color = playerLevel >= requiredLevel ? Colors.QUALIFIED_TEXT_COLOR : Colors.UNQUALIFIED_TEXT_COLOR; + return HtmlUtil.imageTag(url) + " " + HtmlUtil.colorTag(color, playerLevel + "/" + requiredLevel); + } + + private String getPointsTooltipText() + { + int points = this.task.getPoints(); + if (points == 0) + { + return ""; + } + return " - " + points + " points"; + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskPanelFactory.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskPanelFactory.java new file mode 100644 index 00000000..df1b6fda --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TaskPanelFactory.java @@ -0,0 +1,8 @@ +package net.reldo.taskstracker.panel; + +import net.reldo.taskstracker.data.task.TaskFromStruct; + +public interface TaskPanelFactory +{ + TaskPanel create(TaskFromStruct task); +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TasksTrackerPluginPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TasksTrackerPluginPanel.java new file mode 100644 index 00000000..9fd6a485 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/TasksTrackerPluginPanel.java @@ -0,0 +1,108 @@ +package net.reldo.taskstracker.panel; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerConfig; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.data.task.TaskService; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.PluginPanel; + +@Slf4j +public class TasksTrackerPluginPanel extends PluginPanel +{ + private final LoggedInPanel loggedInPanel; + private final LoggedOutPanel loggedOutPanel = new LoggedOutPanel(); + + public TaskListPanel taskListPanel; + + private boolean loggedInPanelVisible = false; + + public TasksTrackerPluginPanel(TasksTrackerPlugin plugin, TasksTrackerConfig config, SpriteManager spriteManager, TaskService taskService) + { + super(false); + + setBorder(new EmptyBorder(6, 6, 6, 6)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setLayout(new BorderLayout()); + + loggedInPanel = new LoggedInPanel(plugin, config, taskService); + taskListPanel = loggedInPanel.taskListPanel; + add(loggedInPanel, BorderLayout.NORTH); + loggedInPanel.setVisible(false); + + // Add error pane + add(loggedOutPanel); + } + + @Override + public Dimension getPreferredSize() + { + return new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, super.getPreferredSize().height); + } + + public void redraw() + { + if (loggedInPanelVisible) + { + loggedInPanel.redraw(); + } + } + + public void refreshAllTasks() + { + if (loggedInPanelVisible) + { + loggedInPanel.refreshAllTasks(); + } + } + + public void setLoggedIn(boolean loggedIn) + { + if(SwingUtilities.isEventDispatchThread()) + { + updateVisiblePanel(loggedIn); + } + else + { + log.error("Failed to update loggedIn state - not event dispatch thread."); + } + } + + public void hideLoggedInPanel() + { + if(SwingUtilities.isEventDispatchThread()) + { + updateVisiblePanel(false); + } + else + { + log.error("Failed to update logged in panel visibility - not event dispatch thread."); + } + } + + private void updateVisiblePanel(boolean loggedInPanelVisible) + { + if (loggedInPanelVisible != this.loggedInPanelVisible) + { + if (loggedInPanelVisible) + { + loggedOutPanel.setVisible(false); + loggedInPanel.setVisible(true); + } + else + { + loggedInPanel.setVisible(false); + loggedOutPanel.setVisible(true); + } + + validate(); + repaint(); + } + this.loggedInPanelVisible = loggedInPanelVisible; + } +} \ No newline at end of file diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/CheckBox.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/CheckBox.java new file mode 100644 index 00000000..2905ce84 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/CheckBox.java @@ -0,0 +1,42 @@ +package net.reldo.taskstracker.panel.components; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionListener; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import net.runelite.client.ui.ColorScheme; +import static net.runelite.client.ui.PluginPanel.PANEL_WIDTH; + +public class CheckBox extends JPanel +{ + private final JCheckBox jCheckBox = new JCheckBox(); + + public CheckBox(String name) + { + setLayout(new BorderLayout()); + setMinimumSize(new Dimension(PANEL_WIDTH, 0)); + + JLabel label = new JLabel(name); + add(label, BorderLayout.CENTER); + + jCheckBox.setBackground(ColorScheme.LIGHT_GRAY_COLOR); + add(jCheckBox, BorderLayout.EAST); + } + + public boolean isSelected() + { + return jCheckBox.isSelected(); + } + + public void setSelected(boolean selected) + { + jCheckBox.setSelected(selected); + } + + public void addActionListener(ActionListener l) + { + jCheckBox.addActionListener(l); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/FixedWidthPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/FixedWidthPanel.java new file mode 100644 index 00000000..d2803d8b --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/FixedWidthPanel.java @@ -0,0 +1,15 @@ +package net.reldo.taskstracker.panel.components; + +import java.awt.Dimension; +import javax.swing.JPanel; +import net.runelite.client.ui.PluginPanel; + +public class FixedWidthPanel extends JPanel +{ + @Override + public Dimension getPreferredSize() + { + return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height); + } + +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/MultiToggleButton.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/MultiToggleButton.java new file mode 100644 index 00000000..6c4fc47f --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/MultiToggleButton.java @@ -0,0 +1,148 @@ +package net.reldo.taskstracker.panel.components; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.border.EmptyBorder; +import lombok.Getter; +import lombok.Setter; + +public class MultiToggleButton extends JButton +{ + + // Icons + private final Icon[] icons; + // Tooltips + private final String[] tooltips; + private final int stateCount; + @Setter + private ActionListener stateChangedAction = null; + @Getter + private int state = 0; + final JPopupMenu popupMenu = new JPopupMenu(); + private boolean popupMenuEnabled = false; + + public MultiToggleButton(int stateCount) + { + super(); + this.stateCount = stateCount; + icons = new Icon[stateCount]; + tooltips = new String[stateCount]; + popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5)); + addActionListener(e -> changeStateThenAction()); + } + + public void popupMenuEnabled(boolean enabled) + { + if(popupMenuEnabled != enabled) + { + popupMenuEnabled = enabled; + if(popupMenuEnabled) + { + this.setComponentPopupMenu(popupMenu); + } + else + { + this.remove(popupMenu); + } + } + } + + public void setIcon(Icon icon, int state) + { + if (state < 0 || state > stateCount || icon == null) + { + return; + } + + icons[state] = icon; + + if (state == this.state) setIconState(); + } + + public boolean setIcons(Icon[] icons) + { + if (icons == null || icons.length == 0) + { + return false; + } + + for (int i = 0; i < icons.length; i++) + { + setIcon(icons[i], i); + } + + return true; + } + + public void setToolTip(String tooltip, int state) + { + if (state < 0 || state > 3 || tooltip == null) + { + return; + } + + tooltips[state] = tooltip; + addPopupMenuItem(tooltip, state); + + if (state == this.state) setTooltipState(); + } + + public boolean setToolTips(String[] tooltips) + { + if (tooltips == null || tooltips.length == 0) + { + return false; + } + + for (int i = 0; i < tooltips.length; i++) + { + setToolTip(tooltips[i], i); + } + + return true; + } + + public void changeState() + { + setState((++state) % stateCount); + } + + public void changeStateThenAction() + { + setStateThenAction((++state) % stateCount); + } + + private void setIconState() + { + super.setIcon(icons[state]); + } + + private void setTooltipState() + { + super.setToolTipText(tooltips[state]); + } + + public void setState(int state) + { + this.state = state; + setIconState(); + setTooltipState(); + } + + public void setStateThenAction(int state) + { + setState(state); + if(stateChangedAction != null) this.stateChangedAction.actionPerformed(new ActionEvent(this, 0, "")); + } + + private void addPopupMenuItem(String text, int state) + { + JMenuItem menuItem = new JMenuItem(text); + menuItem.addActionListener(e -> {if(isEnabled())setState(state);}); + popupMenu.add(menuItem); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/SearchBox.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/SearchBox.java new file mode 100644 index 00000000..68529959 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/SearchBox.java @@ -0,0 +1,53 @@ +package net.reldo.taskstracker.panel.components; + +import java.awt.Dimension; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.IconTextField; + +public class SearchBox extends IconTextField +{ + private SearchBoxCallback fn; + + public SearchBox() + { + this.setIcon(IconTextField.Icon.SEARCH); + this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30)); + this.setBackground(ColorScheme.DARKER_GRAY_COLOR); + this.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); + this.getDocument().addDocumentListener(new DocumentListener() + { + @Override + public void insertUpdate(DocumentEvent documentEvent) + { + fn.call(); + } + + @Override + public void removeUpdate(DocumentEvent documentEvent) + { + fn.call(); + } + + @Override + public void changedUpdate(DocumentEvent documentEvent) + { + } + }); + + this.addActionListener(e -> fn.call() + ); + } + + public void addTextChangedListener(SearchBoxCallback fn) + { + this.fn = fn; + } + + public interface SearchBoxCallback + { + void call(); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/TriToggleButton.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/TriToggleButton.java new file mode 100644 index 00000000..603d57e2 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/components/TriToggleButton.java @@ -0,0 +1,39 @@ +package net.reldo.taskstracker.panel.components; + +import javax.swing.Icon; + +public class TriToggleButton extends MultiToggleButton +{ + public TriToggleButton() + { + super(3); + } + + public boolean setIcons(Icon icon0, Icon icon1, Icon icon2) + { + if (icon0 == null || icon1 == null || icon2 == null) + { + return false; + } + + setIcon(icon0, 0); + setIcon(icon1, 1); + setIcon(icon2, 2); + + return true; + } + + public boolean setToolTips(String tooltip0, String tooltip1, String tooltip2) + { + if (tooltip0 == null || tooltip1 == null || tooltip2 == null) + { + return false; + } + + setToolTip(tooltip0, 0); + setToolTip(tooltip1, 1); + setToolTip(tooltip2, 2); + + return true; + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/ComboItem.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/ComboItem.java new file mode 100644 index 00000000..6cdac2a1 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/ComboItem.java @@ -0,0 +1,29 @@ +package net.reldo.taskstracker.panel.filters; + +public class ComboItem +{ + private T value; + private String label; + + public ComboItem(T value, String label) + { + this.value = value; + this.label = label; + } + + public T getValue() + { + return this.value; + } + + public String getLabel() + { + return this.label; + } + + @Override + public String toString() + { + return label; + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/DynamicButtonFilterPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/DynamicButtonFilterPanel.java new file mode 100644 index 00000000..1e1881bc --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/DynamicButtonFilterPanel.java @@ -0,0 +1,131 @@ +package net.reldo.taskstracker.panel.filters; + +import com.google.common.collect.ImmutableList; +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.image.BufferedImage; +import java.util.LinkedHashMap; +import java.util.List; +import javax.swing.JPanel; +import javax.swing.JToggleButton; +import javax.swing.border.EmptyBorder; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig; +import net.reldo.taskstracker.data.jsondatastore.types.FilterCustomItem; +import net.reldo.taskstracker.data.task.TaskType; +import net.runelite.client.hiscore.HiscoreSkill; +import static net.runelite.client.hiscore.HiscoreSkill.*; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.util.ImageUtil; + +public class DynamicButtonFilterPanel extends FilterButtonPanel +{ + private final FilterConfig filterConfig; + private final TaskType taskType; + + /** + * Real skills, ordered in the way they should be displayed in the panel. + */ + private static final List SKILLS = ImmutableList.of( + ATTACK, HITPOINTS, MINING, + STRENGTH, AGILITY, SMITHING, + DEFENCE, HERBLORE, FISHING, + RANGED, THIEVING, COOKING, + PRAYER, CRAFTING, FIREMAKING, + MAGIC, FLETCHING, WOODCUTTING, + RUNECRAFT, SLAYER, FARMING, + CONSTRUCTION, HUNTER + ); + + public DynamicButtonFilterPanel(TasksTrackerPlugin plugin, FilterConfig filterConfig, TaskType taskType) + { + super(plugin, filterConfig.getLabel()); + this.filterConfig = filterConfig; + this.taskType = taskType; + this.configKey = taskType.getFilterConfigPrefix() + filterConfig.getConfigKey(); + + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARKER_GRAY_COLOR); + setBorder(new EmptyBorder(10, 10, 10, 10)); + + redraw(); + } + + @Override + protected JPanel makeButtonPanel() + { + // Panel that holds tier icons + JPanel buttonPanel = new JPanel(); + buttonPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + LinkedHashMap buttonImages = getIconImages(); + LinkedHashMap buttonTooltips = getTooltips(); + + buttonPanel.setLayout(new GridLayout(buttonImages.size() / 3, 3)); + + // For each filter value create a button and add it to the UI + buttonImages.forEach((key, image) -> { + String tooltip = buttonTooltips.get(key); + JToggleButton button = makeButton(tooltip, image); + button.setSelected(getConfigButtonState(key)); + buttons.put(key, button); + buttonPanel.add(button); + }); + + return buttonPanel; + } + + @Override + protected LinkedHashMap getIconImages() + { + LinkedHashMap images = new LinkedHashMap<>(); + + if (filterConfig.getConfigKey().equals("skill")) + { + String skillName; + BufferedImage skillImage; + int index = 0; + + for (FilterCustomItem customItem : filterConfig.getCustomItems()) + { + if (customItem.getValue() != 0) + { + skillName = SKILLS.get(index).name().toLowerCase(); + + String directory = "/skill_icons_small/"; + String skillIcon = directory + skillName + ".png"; + + skillImage = ImageUtil.loadImageResource(getClass(), skillIcon); + } + else + { + skillImage = ImageUtil.loadImageResource(TasksTrackerPlugin.class, "panel/components/no_skill.png"); + } + + String key = customItem.getValue().toString(); + images.put(key, skillImage); + index++; + } + } + else + { + for (FilterCustomItem customItem : filterConfig.getCustomItems()) + { + String key = customItem.getValue().toString(); + images.put(key, taskType.getSpritesById().get(customItem.getSpriteId())); + } + } + return images; + } + + private LinkedHashMap getTooltips() + { + LinkedHashMap tooltips = new LinkedHashMap<>(); + for (FilterCustomItem customItem : filterConfig.getCustomItems()) + { + String key = customItem.getValue().toString(); + tooltips.put(key, customItem.getTooltip()); + } + return tooltips; + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/DynamicDropdownFilterPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/DynamicDropdownFilterPanel.java new file mode 100644 index 00000000..6277b505 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/DynamicDropdownFilterPanel.java @@ -0,0 +1,92 @@ +package net.reldo.taskstracker.panel.filters; + +import java.awt.GridLayout; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig; +import net.reldo.taskstracker.data.task.TaskType; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; + +@Slf4j +public class DynamicDropdownFilterPanel extends FilterPanel +{ + private final String configKey; + private final FilterConfig filterConfig; + private final ConfigManager configManager; + private final TasksTrackerPlugin plugin; + private final ComboItem[] items; + private JComboBox dropdown; + + public DynamicDropdownFilterPanel(TasksTrackerPlugin plugin, FilterConfig filterConfig, TaskType taskType, ComboItem[] items) + { + this.configManager = plugin.getConfigManager(); + this.plugin = plugin; + this.filterConfig = filterConfig; + this.items = items; + this.configKey = taskType.getFilterConfigPrefix() + filterConfig.getConfigKey(); + + setLayout(new GridLayout(1,2)); + setBackground(ColorScheme.DARKER_GRAY_COLOR); + setBorder(new EmptyBorder(5, 10, 5, 10)); + } + + private JComboBox makeDropdownPanel() + { + JComboBox dropdown = new JComboBox<>(items); + dropdown.setFont(FontManager.getRunescapeSmallFont()); + dropdown.setAlignmentX(LEFT_ALIGNMENT); + dropdown.setSelectedItem(items[0]); + dropdown.setFocusable(false); + dropdown.setBackground(ColorScheme.DARK_GRAY_COLOR.brighter()); + dropdown.addActionListener(e -> { + ComboItem selection = dropdown.getItemAt(dropdown.getSelectedIndex()); + updateFilterConfig(); + plugin.refreshAllTasks(); + log.debug("selected: {} {}", selection.getLabel(), selection.getValue()); + }); + + return dropdown; + } + + private JLabel makeLabel() + { + JLabel label = new JLabel(filterConfig.getLabel() + ":"); + label.setFont(FontManager.getRunescapeSmallFont()); + label.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + return label; + } + + protected void updateFilterConfig() + { + log.debug("updateFilterConfig {}, {}, {}", TasksTrackerPlugin.CONFIG_GROUP_NAME, configKey, dropdown.getItemAt(dropdown.getSelectedIndex()).getValue()); + configManager.setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, configKey, dropdown.getItemAt(dropdown.getSelectedIndex()).getValue()); + } + + public void redraw() + { + if(SwingUtilities.isEventDispatchThread()) + { + removeAll(); + + dropdown = makeDropdownPanel(); + + add(makeLabel()); + add(dropdown); + + updateFilterConfig(); + + validate(); + repaint(); + } + else + { + log.error("Dropdown filter panel redraw failed - not event dispatch thread."); + } + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/FilterButtonPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/FilterButtonPanel.java new file mode 100644 index 00000000..8395bf99 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/FilterButtonPanel.java @@ -0,0 +1,223 @@ +package net.reldo.taskstracker.panel.filters; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JToggleButton; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import javax.swing.plaf.basic.BasicBorders; +import javax.swing.plaf.basic.BasicButtonUI; +import lombok.extern.slf4j.Slf4j; +import net.reldo.taskstracker.TasksTrackerPlugin; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.SwingUtil; + +@Slf4j +public abstract class FilterButtonPanel extends FilterPanel +{ + protected final TasksTrackerPlugin plugin; + private final String label; + + protected final Map buttons = new HashMap<>(); + protected String configKey; + protected JPanel buttonPanel; + + protected JToggleButton collapseBtn; + private final String expandBtnPath = "panel/components/"; + private final BufferedImage collapseImg = ImageUtil.loadImageResource(TasksTrackerPlugin.class, expandBtnPath + "filter_buttons_collapsed.png"); + private final Icon MENU_COLLAPSED_ICON = new ImageIcon(ImageUtil.alphaOffset(collapseImg, -180)); + private final Icon MENU_ICON_HOVER = new ImageIcon(collapseImg); + private final BufferedImage expandedImg = ImageUtil.loadImageResource(TasksTrackerPlugin.class, expandBtnPath + "filter_buttons_expanded.png"); + private final Icon MENU_EXPANDED_ICON = new ImageIcon(ImageUtil.alphaOffset(expandedImg, -180)); + private final Icon MENU_ICON_HOVER_SELECTED = new ImageIcon(expandedImg); + + + public FilterButtonPanel(TasksTrackerPlugin plugin, String label) + { + this.plugin = plugin; + + this.label = label; + } + + protected abstract LinkedHashMap getIconImages(); + + protected abstract JPanel makeButtonPanel(); + + protected JToggleButton makeButton(String tooltip, BufferedImage image) + { + JToggleButton button = new JToggleButton(); + button.setBackground(ColorScheme.DARKER_GRAY_COLOR); + button.setBorder(new BasicBorders.ToggleButtonBorder(ColorScheme.DARKER_GRAY_COLOR, + ColorScheme.DARKER_GRAY_COLOR.darker(), + ColorScheme.MEDIUM_GRAY_COLOR.darker(), + ColorScheme.MEDIUM_GRAY_COLOR)); + button.setFocusable(false); + + if (image != null) { + ImageIcon selectedIcon = new ImageIcon(image); + ImageIcon deselectedIcon = new ImageIcon(ImageUtil.alphaOffset(image, -180)); + + button.setIcon(deselectedIcon); + button.setSelectedIcon(selectedIcon); + button.setPreferredSize(new Dimension(image.getWidth(), image.getHeight() + 10)); + } else { + button.setPreferredSize(new Dimension(button.getPreferredSize().width, 50)); + } + button.setToolTipText(tooltip.substring(0,1).toUpperCase() + tooltip.substring(1).toLowerCase()); + + button.addActionListener(e -> { + updateFilterText(); + updateCollapseButtonText(); + plugin.refreshAllTasks(); + }); + + button.setSelected(true); + + return button; + } + + protected JPanel allOrNoneButtons() + { + JPanel buttonWrapper = new JPanel(); + buttonWrapper.setLayout(new BoxLayout(buttonWrapper, BoxLayout.X_AXIS)); + buttonWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); + buttonWrapper.setAlignmentX(JPanel.CENTER_ALIGNMENT); + + JButton all = new JButton("all"); + SwingUtil.removeButtonDecorations(all); + all.setFocusable(false); + all.setForeground(ColorScheme.MEDIUM_GRAY_COLOR); + all.setFont(FontManager.getRunescapeSmallFont()); + all.setPreferredSize(new Dimension(50, 0)); + all.addActionListener(e -> { + setAllSelected(true); + updateFilterText(); + updateCollapseButtonText(); + plugin.refreshAllTasks(); + }); + + JButton none = new JButton("none"); + SwingUtil.removeButtonDecorations(none); + none.setFocusable(false); + none.setForeground(ColorScheme.MEDIUM_GRAY_COLOR); + none.setFont(FontManager.getRunescapeSmallFont()); + none.setPreferredSize(new Dimension(50, 0)); + none.addActionListener(e -> { + setAllSelected(false); + updateFilterText(); + updateCollapseButtonText(); + plugin.refreshAllTasks(); + }); + + JLabel separator = new JLabel("|"); + separator.setForeground(ColorScheme.MEDIUM_GRAY_COLOR); + + buttonWrapper.add(Box.createHorizontalGlue()); + buttonWrapper.add(all); + buttonWrapper.add(Box.createHorizontalGlue()); + buttonWrapper.add(separator); + buttonWrapper.add(Box.createHorizontalGlue()); + buttonWrapper.add(none); + buttonWrapper.add(Box.createHorizontalGlue()); + + return buttonWrapper; + } + + public JToggleButton makeCollapseButton() + { + JToggleButton collapseBtn = new JToggleButton(); + + // collapse button + SwingUtil.removeButtonDecorations(collapseBtn); + collapseBtn.setIcon(MENU_COLLAPSED_ICON); + collapseBtn.setSelectedIcon(MENU_EXPANDED_ICON); + collapseBtn.setRolloverIcon(MENU_ICON_HOVER); + collapseBtn.setRolloverSelectedIcon(MENU_ICON_HOVER_SELECTED); + SwingUtil.addModalTooltip(collapseBtn, "Collapse filters", "Expand filters"); + collapseBtn.setBackground(ColorScheme.DARKER_GRAY_COLOR); + collapseBtn.setAlignmentX(LEFT_ALIGNMENT); + collapseBtn.setUI(new BasicButtonUI()); // substance breaks the layout + collapseBtn.addActionListener(ev -> buttonPanel.setVisible(!buttonPanel.isVisible())); + collapseBtn.setHorizontalTextPosition(JButton.CENTER); + collapseBtn.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + collapseBtn.setFont(FontManager.getRunescapeSmallFont()); + collapseBtn.setBorder(new EmptyBorder(2, 0, 2, 0)); + collapseBtn.setFocusable(false); + collapseBtn.setSelected(true); + + return collapseBtn; + } + + protected void updateFilterText() + { + String filterText = buttons.entrySet().stream() + .filter(e -> e.getValue().isSelected()) + .map(e -> "f-" + e.getKey() + "-f") // prefix included to cover cases where one key name is contained in another (e.g. "Master" -> "Grandmaster") + .collect(Collectors.joining(",")); + + plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, configKey, filterText); + } + + protected boolean getConfigButtonState(String buttonKey) + { + String configValue = plugin.getConfigManager().getConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, configKey); + boolean isEmptyFilterSelection = configValue == null || configValue.isEmpty() || configValue.equals("-1"); + if (isEmptyFilterSelection) + { + return true; + } + + return configValue.contains("f-" + buttonKey + "-f"); + } + + protected void setAllSelected(boolean state) + { + buttons.values().forEach(button -> button.setSelected(state)); + } + + protected void updateCollapseButtonText() + { + collapseBtn.setText(label + " - " + buttons.values().stream().filter(JToggleButton::isSelected).count() + " / " + buttons.size()); + } + + public void redraw() + { + if(SwingUtilities.isEventDispatchThread()) + { + buttons.clear(); + removeAll(); + + collapseBtn = makeCollapseButton(); + buttonPanel = makeButtonPanel(); + + add(collapseBtn, BorderLayout.NORTH); + add(buttonPanel, BorderLayout.CENTER); + add(allOrNoneButtons(), BorderLayout.SOUTH); + updateFilterText(); + updateCollapseButtonText(); + + collapseBtn.setVisible(plugin.getConfig().filterPanelCollapsible()); + + validate(); + repaint(); + } + else + { + log.error("Filter button panel redraw failed - not event dispatch thread."); + } + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/FilterPanel.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/FilterPanel.java new file mode 100644 index 00000000..ce76e853 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/panel/filters/FilterPanel.java @@ -0,0 +1,8 @@ +package net.reldo.taskstracker.panel.filters; + +import net.reldo.taskstracker.panel.components.FixedWidthPanel; + +public abstract class FilterPanel extends FixedWidthPanel +{ + public abstract void redraw(); +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/DiaryData.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/DiaryData.java new file mode 100644 index 00000000..e867414b --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/DiaryData.java @@ -0,0 +1,15 @@ +package net.reldo.taskstracker.quests; + +import java.util.HashMap; +import net.runelite.api.Client; + +public class DiaryData extends HashMap +{ + public DiaryData(Client client) + { + for (DiaryVarbits diary : DiaryVarbits.values()) + { + this.put(diary.id, diary.getProgress(client)); + } + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/DiaryVarbits.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/DiaryVarbits.java new file mode 100644 index 00000000..732f9c96 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/DiaryVarbits.java @@ -0,0 +1,66 @@ +package net.reldo.taskstracker.quests; + +import net.runelite.api.Client; + +public enum DiaryVarbits +{ + DIARY_ARDOUGNE_EASY(4458), + DIARY_ARDOUGNE_MEDIUM(4459), + DIARY_ARDOUGNE_HARD(4460), + DIARY_ARDOUGNE_ELITE(4461), + DIARY_DESERT_EASY(4483), + DIARY_DESERT_MEDIUM(4484), + DIARY_DESERT_HARD(4485), + DIARY_DESERT_ELITE(4486), + DIARY_FALADOR_EASY(4462), + DIARY_FALADOR_MEDIUM(4463), + DIARY_FALADOR_HARD(4464), + DIARY_FALADOR_ELITE(4465), + DIARY_FREMENNIK_EASY(4491), + DIARY_FREMENNIK_MEDIUM(4492), + DIARY_FREMENNIK_HARD(4493), + DIARY_FREMENNIK_ELITE(4494), + DIARY_KANDARIN_EASY(4475), + DIARY_KANDARIN_MEDIUM(4476), + DIARY_KANDARIN_HARD(4477), + DIARY_KANDARIN_ELITE(4478), + DIARY_KARAMJA_EASY(3578), + DIARY_KARAMJA_MEDIUM(3599), + DIARY_KARAMJA_HARD(3611), + DIARY_KARAMJA_ELITE(4566), + DIARY_KOUREND_EASY(7925), + DIARY_KOUREND_MEDIUM(7926), + DIARY_KOUREND_HARD(7927), + DIARY_KOUREND_ELITE(7928), + DIARY_LUMBRIDGE_EASY(4495), + DIARY_LUMBRIDGE_MEDIUM(4496), + DIARY_LUMBRIDGE_HARD(4497), + DIARY_LUMBRIDGE_ELITE(4498), + DIARY_MORYTANIA_EASY(4487), + DIARY_MORYTANIA_MEDIUM(4488), + DIARY_MORYTANIA_HARD(4489), + DIARY_MORYTANIA_ELITE(4490), + DIARY_VARROCK_EASY(4479), + DIARY_VARROCK_MEDIUM(4480), + DIARY_VARROCK_HARD(4481), + DIARY_VARROCK_ELITE(4482), + DIARY_WESTERN_EASY(4471), + DIARY_WESTERN_MEDIUM(4472), + DIARY_WESTERN_HARD(4473), + DIARY_WESTERN_ELITE(4474), + DIARY_WILDERNESS_EASY(4466), + DIARY_WILDERNESS_MEDIUM(4467), + DIARY_WILDERNESS_HARD(4468), + DIARY_WILDERNESS_ELITE(4469); + + public int id; + + DiaryVarbits(int id) { + this.id = id; + } + + public int getProgress(Client client) + { + return client.getVarbitValue(id); + } +} diff --git a/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/QuestData.java b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/QuestData.java new file mode 100644 index 00000000..10f2aa19 --- /dev/null +++ b/tasks-tracker-plugin-master/src/main/java/net/reldo/taskstracker/quests/QuestData.java @@ -0,0 +1,17 @@ +package net.reldo.taskstracker.quests; + +import java.util.HashMap; +import net.runelite.api.Client; +import net.runelite.api.Quest; +import net.runelite.api.QuestState; + +public class QuestData extends HashMap +{ + public QuestData(Client client) + { + for (Quest quest : Quest.values()) + { + this.put(quest.getId(), quest.getState(client)); + } + } +} diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/eye-cross-grey.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/eye-cross-grey.png new file mode 100644 index 0000000000000000000000000000000000000000..7e42040b36ee1669687f95caa031a4e5c550cdbd GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK@U$C#}JL++CEM$1_ch5waecf{*z@Xq$J2& zq2H~RIaBgu$^?O()~3@1ith=rY`;7+T6mAk=P#~0nyOyAxJ#B-JrQH!z%d!U&Np00i_>zopr0Dc-XWdHyG literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/eye-red.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/eye-red.png new file mode 100644 index 0000000000000000000000000000000000000000..b4cea10e56dd93fe654253d924900723557dd3de GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK`&1i#}JL++P+3k1_ch5{mU{mm*=kBrtzOs zu#->g>|?>x8yM%R_lhqy{K|GOTTSE2lH1DuDjf{UM`rKZ?Qp@)y+rzOg7 XeXU~iMsvq}ps5U=u6{1-oD!MEX>4Tx04R}tkv&MmKpe$iTct%R4(%XPBtvzwC@SJ8 zRV;#q(pG5I!Q|2}Xws0RxHt-~1qVMCs}3&Cx;nTDg5U>;o12rOir6bq^0;?_xa5{oJ3U zUoIF7@Cd|nOw&!`4dR(iQ{%i(9A;%vCO#(~)9Hf5k6f1=e&bxS*~>G-dNw^z93~b@ zT`YAmE9(mJG;vf`RLb|}9F{q6aaO8z*1jiyVX&aCWVlWuke%!@BWcyR(lE_s7BgX=2P$1fV@IUz7tx=qsbdrK`p!dbHK1KllF3@UN*7vbx zwN3!vGjOGL{Iw=9`$>AEqeYK^fo zZ_Vkgy^qreAWK~>-v9@Pz-WoG*InKn=y{D4^000SaNLh0L04^f{04^f|c%?sf00007bV*G`2jvJH6E`D_;!hX=000?u zMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0000jNklp5wMwVAoMC+gVD*Q z28@nba21Vasc(yZFROWqV^^!Iao|L|)SK<;-@kMB{^Zj+2M-=E%rB}R|D@#I^pd}9 z3fl4Y{KDr?YN1mGXd~~O+R=O9riw}1gvju=q@2syri1=l8hFc&?CP+qn0V~lq+OPV z>32t8`ZBCxukKkwo?l-2)>HmxmNuNtFTcBb?A`&|fq_*urETuX^q)4L@$58tIe7nE zadF{~yc62A&PyAK_LRDPX3c!r*TTn{Gop&v&iiw_`ctQ2vgxxAlaIo98iGgiDNVj-k9(7lbMhpt7+U*wI;&7#i znYyXcl+>o;@na^**NlsqdV~RWY+QHcB(8||4(#0ASiLr$Z!)P{ZhjY5Ulf4U?40oV z{TesJ{V0C?oEDWcdw3wy>YBduL#24+)BD>i9Y+5$?K_vDHKk)Gq!pbgMIT5G-pCg% zkMhME>@pcOZFcJiI({F}8F`uGnvaY_%I|D<4{^zrF^RW+R2 z+FPB6WM{`3iZ6uhZKxaH^~v{7R*D}q_4WtZdDRDE7HxOUFN zNtV33>h|WcBh%VX7UjEJK0BgrpW`{Ki+Jy*)YQ1VtMJfu_vYs*&t|90JW#b|`diPO z$j+6X@p0Lfxgy|GawoCT z?EK7e*9}R4-KN6gv|BWq-8x(*3zy4eFNzkFYVOLdejybBW4~Dvc{F&cQ}XbV%zfh6 z;chN*1!=*bh1WZLD&8LV+aQHUmaF@$(?=T{tCW{de-j&RA1xi;MN2XoBgm~Io^Knq zu+n06!9v%URS z*4Kxx)jk#dmXlje%LtKFkrj;V^gY*)kmsIMN*Cm~HW`~ZYgi#&XG~?)`rM&TLnXY# zg6xh;sj}q!X~O>S-}a3NbJ^fFKYzu9OX#|eQ3*%muuqpia34(6ho;@(Z*Zv?(G(J} zHhpU7l#0N$3pTrsl-Fk*f3#ujs?<1cS!jH7Ou(S26RVt`*`GQg=*S-VT|t2Btq)4t zGF-m;VkrOp(x)pm4Vy=P^~5m_*RJ~5sb)piVzv4wue??796P2{KNx2y8{jzOcGd(@ z*0#dT+5z_xeo@56H;%fsNd53iebwy&nT3Zw(yv}4i$4R1V-nx_$-JSY{8Scj2RAe34yA_5kWJ1D8R^%kw}sTF`I2Rn^|Ta zOHU-QIU`@Wu$+o}-HireV zwc36bMlv|L&)-Wej8Wib#*V;@dJ}atQHj77KtLgID?7MSTQKV#x$4~1Y!itoPKx`SM?oGzx_}vzY7Go z>*eoFp3J0vv9zJ2t*vN2oo_?N=U@yb6_D8MiBv1B;*SrjLY?b zV4e*{7=eRIfHk&JQB)|P;wv$f%fkdrNW|eW`9dYk6hSD6#+L&jzL1XqMG-3%bq-=_ zxZIDyWkG#2;To7!=?N_;FIVpZnzdsz}N;|f^~s97%|umkQSz@129|X!EVGd0)t6C5vA8_ z{1{ZUbc)hiRCGyCxrpcJ5v!pUjG5Q z&3Zx|MuaKf#}M+>JTC+HGDU!HXC#TG@;}(rf8ZqD2^#`z^~6#e|B+Zy_o%xiX>hAl zbh@<-h+(8#ej}WWq1Gb+I(9E1iLfpK1O2wA*j~)zFG*xBBH(Z^p@_+WFkdEL1@V|d zl&@l9Tto#DT*Vb2{Qm4ly^1u$1SU-YIRMTX6qog!=~H_0&$~a~oQP4$1#vk{&$MbkE%2M{dL!3sDezk0H`(=nlgpv+l^fH6 z=RPxdp{7lGdIG$R4@{6Mq%>Md#?UT!5Eu_u&Yl%Sdrtl5l^11$nYRrgu|^t=vXalZkZdQ9nAx$sK^) w!lvI6;>1s;*b3=DjSL74G){)!Z!V6>-;V~9p@vIE=y|NqysJgU+Vw`b70!t|)# zfrm*oAn^~w3Io25my0!-I!<2t!oG&pQ&@P48cW3jlQISq!JD3U0}|gbtYEl#`ATSy pOyLjF!wdn68kr9Q&m3T2nD=thuFsB%zk${;c)I$ztaD0e0swhSKP3PF literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/complete_button/complete_and_incomplete_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/complete_button/complete_and_incomplete_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..60c67c8c0a757a6b026ebf80ef88f3c98398114d GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!V6Uf(V~BPDK%N-mzA=8;#(#5rTPM|O4%Qm@33rL pTs^Ptf^_2(HPhU#6A4k8=ASQlt4p=LmjODA!PC{xWt~$(698mWSjPYW literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/complete_button/complete_only_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/complete_button/complete_only_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ae0d44b0a74b5eba3d4f872d56f7c1ec7c71524e GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!;9^e~#}Etuvy(3J9yZ``xqEK9-ot}?Tz1DF zeyFU}>sm0S-%fLrWwH8%puESk8`e5MsoZ#4^Ol9fylM9o6Zf>t@Vv{i)$W{+${Qbx z>X(N!TQ=ky-(Jbcc2r!lvI6;>1s;*b3=DjSL74G){)!Z!;5<(k#}EtuyOSe@niT|G44+^2E-=s&eBXX| zA^XBRYRA^^S+z29iS)C>A4Hsc3|HH!EHznihv9F>`_JBgbu*g2q?Ji~h8PMTc~Tcx zU3^>FPHoQ4o{|cu8IkEucUEsrJG1trZq8nN<>z}MSDy=rNou;ZHf_ym4r9^f9@jZV zpX?Tw%sAU{<@(<*>q}4k6p^h>GhGtL5i$LuRq;l3jm-jLirIb>ILcRnyvX3`>gTe~ HDWM4fT9#xT literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/expanded.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/expanded.png new file mode 100644 index 0000000000000000000000000000000000000000..35e4f4ed9c2788a69291f87e10ae44a14bca5876 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!V1=iPV~9rZ-mcS}4F&?Ni%TOL)$`}&xVM;0 zG6S*q(dj_L_nVv1t*r_nfpm_rdwU>Y>2%vn>ug>^@RFC-H~XKZ)mN3E%rP Uax|^30o}mh>FVdQ&MBb@09Axj2><{9 literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_buttons_collapsed.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_buttons_collapsed.png new file mode 100644 index 0000000000000000000000000000000000000000..d23684c4225e049c0a7384afbdd77f37d7f5e493 GIT binary patch literal 389 zcmeAS@N?(olHy`uVBq!ia0vp^M}U}zgAGX9$_i})QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4uUY_j)~cCff}SdT^vI)oZsFu~?zU27Z+Z)ecJ``}6QK-v@ zKUw31=8K!oGg&6=)T>Y3%9qv?`Ep7|Ssk0xKG}eC9EX?~trxyqt?u&qvNT7FqQgW3 z3C`xNW#P7pzl??0dVU@M5;(ncPV>Lqcd7>R^%r}5-*#KJHO^_b({paLdOKhIhJ+E1 hiMmkNKZlBY(ocJBdlq<}o(T*(22WQ%mvv4FO#nIvl?4C* literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_buttons_expanded.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_buttons_expanded.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac69454c8ad95cd34b3034bf71d850cb8ba76c8 GIT binary patch literal 403 zcmV;E0c`$>P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!2kdb!2!6DYwZ940VqjCK~!i%?bi9WimIlVh=nA)PqO0twzu~y zK_t&aCaA9Kg3~1D+#9DN(}PB6dP+8ptA?|osJ zWn^1+-|P1T0$=h#WtSz(Xp|*uSSwi;89dAD2Dt^yS?&j6=fP-W!4X+gBgm*&X0LAs zKa~60+7BevL9R^{>t|2!8)+02Wi%zt(9k$05j$n6?&;VsUsF0xaDsw34GZNI0W=)yUx#pB&<6&GuuK%FVdQ&MBb@00<_Q^#A|> literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_menu_expanded.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/filter_menu_expanded.png new file mode 100644 index 0000000000000000000000000000000000000000..0981525c9cf44199d779e39230efe4476f92bf66 GIT binary patch literal 389 zcmV;00eb$4P)zR00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0UAj}K~!i%?b@+! z!Y~j9;J5?4RG*+qB{rr$M)$BFB>Ff!L#56gSa}S)n3(_KOOz@km~?9Lf6^((<;>)h zgM!J3s1ZWQH63J(IcQ2mZ8AiNRKo*@hG$KCrSuT}N8bC~Iv1S#31wMU6LZ7eA=)8i z^_!MDBHFeOwbfVx?uNue2uiS|*w-?DI|B?v;&}(_v9POlQQ4hU9`nM6-as{Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0X|7YK~y+T&6BN8 z13?rNS3qEJMewtn@9y2% z&2*Q92Ej>AXXf5Jvz^_U1*N> zW990h@K{S%7}msX3$*dp%Javdg<}ingqKa*b;X*;ilZ@ohzYjw#?m?Os87D@UZ5Tj z-kD8of`S9oq5FwbFAvxiiJ@?gW(!x{MSR6;)b+T-aGhp`2Q|N=-|{DmOp_$vEBw=A T()jY000000NkvXXu0mjfT!OqQ literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/ignored_button/semivisible_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/ignored_button/semivisible_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a52cbfe0a76315e75450ecf99b2d4a1ba6cba263 GIT binary patch literal 398 zcmV;90df9`P)pWjvID*%(O= zoHOH+%$A!vUV7YJL9eH#(DuZ-)8-7xH4NKKodQA3cx2$%HFAyg`*4{BtGcSLuHvKI sFS)HQ`)lH2{EYi25{P>A{1bKa2l6jn4>Jw?FaQ7m07*qoM6N<$f+x_c<^TWy literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/ignored_button/visible_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/ignored_button/visible_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5c4232c808f46b2fee47a5ac1f5045792118165b GIT binary patch literal 312 zcmV-80muG{P)PbXFR49?Xk)cilQ4mGX+kl~|Xh=}~0+3ZeLIV6k(}WZpztPGP zWQzlVKp+q#grIQz0O1R&Rv@UN2--ae-tHFG_o|u6N#@QSSgOKe;|51GfG=Ko4)Y4_ zp0~~{m3s0Yxlr}nk!{-vNAFlIkahhYMk_*gN%?OfG2*YfLX z-W3sa>^O|Ky;%R7)Um0HO6}( literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/no_skill.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/no_skill.png new file mode 100644 index 0000000000000000000000000000000000000000..12b88401b1666252bc7dc28522aae59903fb142a GIT binary patch literal 694 zcmV;n0!jUeP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0!v9mK~y+Tb&^YH z6G0rtzihI}X7iwlElLfo(iRf?04+iZNHGUN@njB)6a-I2d-dewpcm~SSEVQE*zWrdeZu@ac!66EoLGT`u1pT4w?4 z-a`SN=fOK1Hvi@la4s`?Nd=ek@3aeRXq41mo`m8N7lKs?S5@iYwOm4L#0#FtZjS&tdjO{zgxzIFS`@79tpG}Q2*vEoa%>V!Z literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/search.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/search.png new file mode 100644 index 0000000000000000000000000000000000000000..3e723753a18926d51373fa6a551c5fb880676f66 GIT binary patch literal 280 zcmV+z0q6dSP)Tz0qr^Y`sI=W4E^6n=Sw*wntDuKHI=cusGELJV|VND{=3W`}JTD zP>B1r@IiU!JQa7&@C#hnHNhYxgC%UlorwR1oZ&iXXV(k{!*$5k_6CFD-x|U!lvI6;>1s;*b3=DjSL74G){)!Z!;9gG`#}Etut&lz=j?SlRDz{E@@kl+}7r;|7yU{rL%<5{lbf3#x?kF*Z3YOOV@xJ=H z@we5YMtjSBEpo>{_NpKJ%~NT#XYC?qj*st_CY@Y(%<}rYK+kDjNsA7e<-D&o`w_=^ oV8ixDA!Sp}e7>W*w;{+r<#0l5#@92+K#wqZy85}Sb4q9e0EDw}*8l(j literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/sort_button/descending_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/sort_button/descending_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f78e4893adf315778ea67c73f42353b08cab8ef5 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!;8sr;#}EtutrHu04=eDnD4tC?b4V%YkdDtm zqqVa>9a*+)Lc^3VOE`{4s8?LMe`BA`iqOQhcRwr*dOvq=>oqxvbqbDZSKn_DxjA!% zlb6J#)MYL@AMZ^UE;w6#c=cD6o++lYJ!YPA%|2VYP~GgEX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgQN3MF0Q*|NsBY%*+f}$_PZH933N} z1V6X{0004WQchC4UGep1C=`h>o7mqtTitCXP+yDRo07*qoM6N<$f?DzZI{*Lx literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/desert.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/desert.png new file mode 100644 index 0000000000000000000000000000000000000000..6d061e708accb1ffd6e994a0474b00137eaa62d4 GIT binary patch literal 642 zcmV-|0)737P)EX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgy)Nbnm#1J cLjr@#9Z0EOgCy{}g#Z8m07*qoM6N<$f>&7*0RR91 literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/fremennik.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/fremennik.png new file mode 100644 index 0000000000000000000000000000000000000000..3899272237655c5df7cb0df5708322e66cd37c25 GIT binary patch literal 606 zcmV-k0-^nhP)EX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgu=vj{=iGwB3W sJ&OQmvY|8CuyIG{j?M!C4Fp2N3vtIyfJ$w?V*mgE07*qoM6N<$g7gFaCjbBd literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/global.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/global.png new file mode 100644 index 0000000000000000000000000000000000000000..1213077537c424a8cf7aed10da6f8f95bc27a106 GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw|3?!p1cPs@`jKx9jP7LeL$-D$|gaUj*T!HlW zmzxjlD)C6o)H3(t<>T{wxOoalX-SY@FoVOh8)-mJi>HfYNX4zt{)4lVJbd>__CQvZO6Tbk75gXGO?p3@mL`QQ z`}sgww98=UWa*OI&jZvKuIe^6V|?{QMX9}GhW%Od$4Q^R{`>u=^8I(it--ew8>EX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgQN3MF0Q*|NsBY%*?(fGj%9O933N| z9zl)(0004WQchCJQ3`Egu7{Fu#lnD}NgFsKg?Y|0g z>>wmsuKId~W$+o^xyJ}{g8@f)4`D1DyvB4m1ulo(OOvunfEbo)S7fMZ_Qk00000NkvXXu0mjf1u6iv literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/karamja.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/karamja.png new file mode 100644 index 0000000000000000000000000000000000000000..e409366728e222a8b919695026f1b2ce0f745d42 GIT binary patch literal 610 zcmV-o0-gPdP)EX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgbsQ1hvdhrJqra)mXjK$i|!{=d6)+ty!+Mx?ULB)plKLexKF0k}-S6#|}YH w)Gn0ZLpPGUfMCa9N9V-BiGwEsng|rf7c{9X2k=n|lmGw#07*qoM6N<$f|6wcx&QzG literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/kourend.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/kourend.png new file mode 100644 index 0000000000000000000000000000000000000000..f526569c82d12c9db94fadfba0a799237388cff6 GIT binary patch literal 346 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz1!VDzu*(gTbr_k-It^lq+H%=+Bms+ync3urA@0>_|LK)zJe4@lkYFDbILya{^Hz} zlw~YTn0!c|6B{qJJg|O$HEKVX z|F%mdKI;Vst0Gn)u2><{9 literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/misthalin.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/misthalin.png new file mode 100644 index 0000000000000000000000000000000000000000..2a3ae7b8d81d3905c143b9c6ea3caf0f023e8c60 GIT binary patch literal 619 zcmV-x0+juUP)EX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgQN3MF0Q*|NsBY%*+f}$_PZH933N} z1V6X{0004WQchCJmIs)-($65kE= zDeI%5zD|yTv0_zIeZ(=0C9LY|LpWwEf>nJG92NrWz#mysGLphEb4vgK002ovPDHLk FV1g0?1l0fl literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/morytania.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/morytania.png new file mode 100644 index 0000000000000000000000000000000000000000..85dc110de647e0530d47e4a9bf3b1fcd72973f0c GIT binary patch literal 661 zcmV;G0&4wEX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgOE{n3Wv0t+Ur3IOF zoIxqMO~(AQgtEVo3v(|1LYK~5TlL?nmaJW4ohEY~)m~2?OWs4f!9KyKAw}Np1=yR2 v=w<+tU5PFpBab-pjGkxod_=&G2&56e!{%fIr&a?{00000NkvXXu0mjfj@%ll literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/tirannwn.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/tirannwn.png new file mode 100644 index 0000000000000000000000000000000000000000..4b7e9919cecc15e8221ad8890dc75864c71fc055 GIT binary patch literal 627 zcmV-(0*w8MP)EX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgQN3MF0Q*%I4Zv#`PDBQ4e8I933MM zxLf=H0004WQchCOP^PgT|o*sFoE#A z)Ss{Vs^1Uw`=P$-tMJ}XcrGa3$&KRSLU|{XoAQ{rsCUBTR`G9Cyxc~a-0y@E*JCIq z6BJ=k!-S|shNU|LnbTvzvTJ9^9Ca*~t?W$ZP>!{1ik&$q4jYAi;18X_GLmvO^e6xT N002ovPDHLkV1o9G4pRUC literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/wilderness.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_areas/league4/wilderness.png new file mode 100644 index 0000000000000000000000000000000000000000..aee8fec3cd404af99f54d55fed277e8ea5368878 GIT binary patch literal 630 zcmV-+0*U>JP)EX>4Tx04R}tkv&MmKpe$iTcs)$2Mb#1kfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRvTclN3Kf_zi}?w?B$t3J(HRz4iXE+4wgEYm2`!8nm8gWD&>2# z4$GXkI4hMJYu%H-FpyVQ(p;w*MjT5>APE5yYAB-u6H!`KQVgVMKjz{evHeMMN#rVn zkz)Z>C=hKw_#gc4)+kI(I!S>T(EVarAESVO7iiWk>-*TUnkRto8Msp0{%Ql5{Up8K z)*?s1z&3Dk-PWW%;Bp5Te$qr;v?U)+Z=nFZpV2qvfWcd!Z_VkgwU5&WAVXa(-v9@P zz*v#8*InM-)7jgQN3MF0Q*|NsBY%*;wkN-{G)933M* zR_7o90004WQchCY>7?U!N=|(0A~g&m2KZ&P9&o7}-d&dX}E%X?>x1QpH5iS;wp+bDuJcrd^iD zq&&9V$y>hGtkgf*YUCq6whDSjZo&|}>V@P@K(J%5qjO^C#Lg!IIuRHgKUQo!qBa#+ Q#sB~S07*qoM6N<$g4ACQEdT%j literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/achievement.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/achievement.png new file mode 100644 index 0000000000000000000000000000000000000000..9c3f0171111dc501e08df360e3643114a29cf0b4 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4xHBpZ(Y^aoO+0X`wF{~7*2et7J|5%tpxgj|dT zwA3U(G9CJ1K#H*>$S;_|;n|HeAjj0x#WAGfR&s&|Gb0;Yo66-27Yu;(69I8?b08fe vuJKkwqvKGyports#g`2J8hloG#?0{Rz3{3!E#?zI0~kDA{an^LB{Ts5vy3j* literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/combat.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/combat.png new file mode 100644 index 0000000000000000000000000000000000000000..a4dcf5c2e938f5c82a8a225f6633d6a6a2b05f84 GIT binary patch literal 3034 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 cNwTj20IH<{907*qoM6N<$f)}r%W&i*H literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/minigame.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/minigame.png new file mode 100644 index 0000000000000000000000000000000000000000..86b2b94abfdae983406ce222337a07fb8463401a GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4xHBpZ(Y^aoNx0X`wF{~7+j+dAc}ljVFBGiNy) zpm5#R11o?OV@Z%-FoVOh8)-m}y{C&~NX4z>goFf+1P11?U=GnFA+ANuu|9JgjTF_i z)EIeq*65u(rMHfU=io6fr&Grm3+#O4*2yiCPm?=R!;rFp`{<2_A02^4F?hQAxvXc99V0Po2t=G6uoZ*qncqYLrdLeuX&>99$S3j3^P68<18Ie5&=>C`dC0y`hMb#lw()8vlSFr;kYK6>NfM@OJh44$rjF6*2U Fng9d-GjRX_ literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/skill.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_categories/league4/skill.png new file mode 100644 index 0000000000000000000000000000000000000000..5e30ce50ec7a506d769bae93845ecbfbff75c61d GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4xHBpZ(Y^aoOk0X`wF{~7)tc)RBFvDF^;^6zCP zX0)G^Sy#NoF-k7&7AQ?%OHZmScX9{Lpez6nD0)Yeg;oh KKbLh*2~7ZMb~v{H literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/easy.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/easy.png new file mode 100644 index 0000000000000000000000000000000000000000..58073170f0ed51b3236d48db1ed145a4c07df514 GIT binary patch literal 13481 zcmeHtcUV*1)@P{Fi}a=;6e$Kmhft*VNS7{zBy}AK2cY#i! z>*V3L$en}8i;mQ~I!Zs;%Zs8<1(fx5)VUWOdfRn6Nn=~92d~?wcKu1Va`KO~H}`tR z?+DQr8u)PSqW4?U7yK-`5+l9MH@e3qxౡy*4m$DuVe)e_iTMGFl@}}t0a{c`F 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 D7TFP` literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/elite.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/elite.png new file mode 100644 index 0000000000000000000000000000000000000000..7506bc9a87d2e699f68ff00796ce4f1bee44a7b6 GIT binary patch literal 1018 zcmVEX>4Tx0C=2zkv&MmKp2MKrfNkh9jqYYkfAzR5EXHhDi*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRRbq^ok@1i`*yYA1?ujEYz_(bA4rW+RV2Jy_M zrE}gV4zrS^5T6r|8+1Y9N3P2*zi}=)Ebz>*kx9)Hhl#~v2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}md3AdV#@kc0>sHIz|-g(&SBDJD{M9`o=IJN_iOWO9|k z$gzMbR7j2={11Nj)+|g-x=Fzp(EVcDAEQ8E7iiXP`}^3on+e)kfB~J-v9@P zz*v#8*FE0d+u65&Yg+yL0Xy|_k>lVXON-;u8F+xHY7#I)`5C8xHR>r#~0000IbW%=J00000 z000000000000000005P9JwE^d0cS}>K~#9!ZP9^pqA(B!;1!K2_0A(v({ed9D3l_! zn)<*0v)ybE59s}~GnwR@g(c8>{VQbMMRm(ha1y%XM3KI}q%R=#zaX{05AFbZzUOHW zyr!AU_xVGI_?2dVoXbz|y^cE5EOYMQYAoqHTI)C!mP=Yq0-I%pbNhF!RxA234y?_J z;&dp%yU73M?4`_I3mHP)(&^3G=6cCpeaDgBY}WW|Rekgr5A)J+vdv)ODGMX;{+zAkF?Cs7p9Vlki3=L=ZNSk_qFPdFr! z@T0A5yl<=a@y;*`37C!6x-Gr`Y?=WwvzpyTe=@cs3@U)#Mqhk;nP5;UCCpZ9|M3k4 z-v*AxZ1o$|#8?T(;~7S4Rw6FGVItNJVRoz}RVYb9DH4L9k7vXL7}mZ75s?xEW1wPE o63IYAuM(616f_yB5`vWY3nhd)ksvbtHvj+t07*qoM6N<$g7LJn{r~^~ literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/grandmaster.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/grandmaster.png new file mode 100644 index 0000000000000000000000000000000000000000..97abbc307d7420245c0042153b07fb0f5c3ec133 GIT binary patch literal 987 zcmV<110?*3P)EX>4Tx0C=2zkv&MmKp2MKrfNkh9jqYYkfAzR5EXHhDi*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRRbq^ok@1i`*yYA1?ujEYz_(bA4rW+RV2Jy_M zrE}gV4zrS^5T6r|8+1Y9N3P2*zi}=)Ebz>*kx9)Hhl#~v2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}md3AdV#@kc0>sHIz|-g(&SBDJD{M9`o=IJN_iOWO9|k z$gzMbR7j2={11Nj)+|g-x=Fzp(EVcDAEQ8E7iiXP`}^3on+e)kfB~J-v9@P zz*v#8*FE0d+u65&Yg+yL0Xy|_k>lGDbZz7Z?~8 z7Z(5k0kyTgt*y1CrM0A`t(u&rl$4x|jFg0g!Gw&IgoKoYgp7N8wR?SxdwYa)b9-}h zb7O0qYio0BYina;lv-niV`FPlT6F0C*Awi#b+U@3e;4Vv+L-*2eUOZ?V<@ zLAM8EFAy6nN?*N0+Ztnr2ytJXq?FKYYrB^ap~S!R!mV*bgnG!Kcix)|WBSKSb#HUF zl;VNAy>ahDj1iLgojvG-bA61!%x6KK3tO{2J6DJxSq3Tqf#VT`eQTV{ICCl-eFres z2kSj!Vu6}D!8=%RarYJ?n6P5ugwR>Et$iuInPjwlD1z_y2iu2`lj-&zv08_tFV1P% zmkoa%hK%u$nG5cr^3!kW3P|aj z4{VyQy5q{u@Az_8$Xu2B>s8B_s%n~_rmmZ&sq4DBMV+Ins$X_TDV>CIY?}Z8002ov JPDHLkV1mw}!d?IX literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/hard.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/hard.png new file mode 100644 index 0000000000000000000000000000000000000000..9b2645a29faf93b2ba194cbbcc42661aac566901 GIT binary patch literal 1047 zcmeAS@N?(olHy`uVBq!ia0vp^vOp}u!3-o<3A)T+U|?*`baoE#baqxKD9TUE%t>Wn zsF+hb(bnUzgGAf?Wm&FTvTGC`O;~6ZDIi)nMN90UQ>ONoR|!Qg@+-t^t?85Ra6uDWT;xMEi7l1GMy zv!g|s*K;VeIL@*aGXCmPWypN~V=2G<{r8J(PVatq{^I&4zHC2S9$7Av7W>Hd@yxQN z#rM?tHcwh2FkiS|y2SC3`?}M5`wR7~bUvKfFwyg@aC_&`B=*y?XS~|HYE+d9?cdS+yGU0*Pi|FFt*Q}1e zWS+1vPWL_od;5WiiJPw5-n)DI=JxvRsK37%t-lsceEg2*6EI%p2l#}z{-0^Qde!WC zbNgmY@18uVy{EgTt+k@LxxA_}r?4n3KR-1iJt`$7GASuMGRi+G)HO8JGbX?>Fwot{ z$0g9s(%0F{&DGA+!NkMf*u&1)&DO}mP~F5p%~VfKPg7A#MOIo`P(Xl#pP!$L3z#|> zf!6+i&+z^l!}Dtl_l_}KJI2rwWSAV}mF=bJ)(kkY6y6CIWn9u$lnWSm5d67*cU7_44boCIcRZ2X7wTIqK`ExZp(IlH$_M;-CMW z-!@rfO725`+xwog)fjxex3Z)jty-&pUP$NS!M{2$e;>W-aZ{G>-GK?ySN*WtIrkN} zprG~hJ>Q!bz3`D(z3Nf(Bl)?$*+*7cE%TADa=m)`^-YnDk5cRkSWiz9n5;Ley` zcWeXWbNgG5WNnQSb!F`KV_z%L9FrRRy^+K6Hh;2D2v1DE(#pSo3<^(7@nRHGdZJpq zo>@uoO4O=z)8C$(A$aPs#{%*y@$)3VQ8sC1gZ*JRVHbld;% z%kHT$lClg!8*bJY#~REX>4Tx0C=2zkv&MmKp2MKrfNkh9jqYYkfAzR5EXHhDi*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRRbq^ok@1i`*yYA1?ujEYz_(bA4rW+RV2Jy_M zrE}gV4zrS^5T6r|8+1Y9N3P2*zi}=)Ebz>*kx9)Hhl#~v2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}md3AdV#@kc0>sHIz|-g(&SBDJD{M9`o=IJN_iOWO9|k z$gzMbR7j2={11Nj)+|g-x=Fzp(EVcDAEQ8E7iiXP`}^3on+e)kfB~J-v9@P zz*v#8*FE0d+u65&Yg+yL0Xy|_k>l000Se zQchC<0000000000b7Y&L0003FNklLg!QT;cgE+{7!R2Kfk5K8=l=I#b;X*;xFgo5S?eS6jZyKnfZo3#9{E^$?~Y8;V>ve?E2X6TWQHxW>N-}cU4khd z)OGv@S#})*Fsny2PbhO28m^apUa;oL@>0WE{{YWv93}1>JxKrn002ovPDHLkV1jkd BW%d97 literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/medium.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/combat/medium.png new file mode 100644 index 0000000000000000000000000000000000000000..b868bcc46d049963ddf21f5be813f703abfc4c86 GIT binary patch literal 1102 zcmeAS@N?(olHy`uVBq!ia0vp^vOp}u!3-o<3A)T+U|?*`baoE#baqxKD9TUE%t>Wn zsF+hb(bnUzgGAf?Wm&FTvTGC`O;~6ZDIi)nMN90UQ>ONoR|!Qg@+-t^t?85Ra6uDWT;xMEi7l1GMy zv!g|s*K;VeIL@*aGXCmPWypN~V=2G<{r8J(PVatq{^I&4zHC2S9$7Av7W>Hd@yxQN z#rM?tHcwh2FkiS|y2SC3`?}M5`wR7~bUvKfFwyg@aC_&`B=*y?XS~|HYE+d9?cdS+yGU0*Pi|FFt*Q}1e zWS+1vPWL_od;5WiiJPw5-n)DI=JxvRsK37%t-lsceEg2*6EI$u2Ka=y{(sN#{u;yc zYYg{}F7srr_TfXNmmfbSoVM_>1I=0I&Li)Dq%p|VPWc6cflrR3azttx*KS=hzOZ<}f z1w{{^-CnnkZ~iT`3~w z&v5nqo@-IhC0C~C7DkKse_;NF-n@`O6lJ{HSu|>AFdNSGQ6(08P+(4Ilet9A>1ah zK6%DY_11`xh9%o<=bte>md&9ewRYp=fS?83{ F1OT~>+^7Hm literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/easy.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/easy.png new file mode 100644 index 0000000000000000000000000000000000000000..40ede02ab32112a64b37ce2c9c4975527b58bdfb GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRp!3HFQtmCqP)KpIw$B>G+j*5l`ZJNha`w|&8Db=tYcw)eFV5z`ti7;`6 z-=dMsW@jS~GuR&ZkSSNfw%{3i5|6`W9_9%G>T=Eu4B|6OBlJY<9f1yH@O1TaS?83{ F1ORu3MwkEq literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/elite.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/elite.png new file mode 100644 index 0000000000000000000000000000000000000000..1502161661cd4247c33330729a0ebc0e1a3ed54d GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRp!VDxAh&o3CDTx4|5LY0bx_Hs{pxs_&4ILUA z^ka)ewQU#}7(W<b%7 literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/hard.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/hard.png new file mode 100644 index 0000000000000000000000000000000000000000..adf50ef895f3b058f0e0418157ffdd0697be9433 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRp!VDxAh&o3CDTx4|5LX}_KGne@%5;Z>go?X< zp1e7~f;BtGkCiC KxvXGe{Z$ literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/master.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/master.png new file mode 100644 index 0000000000000000000000000000000000000000..2219b9fc4270fcb16be7df2d552adc4d575e84a8 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh-3?!F4n4AKnBm#UwT!Hle|NjrJ*!0pxWru`B zp1iq|xB&wLnxaneet30^E5^QQZ@$BRJ4W_9j~_gb^D3-=T%{}cFqi#psLL%=M@J|R*UR$+A>RKh|`W5SYlOE4l5bSQa_wp6fwVPKxi_c%S_@CyTNOgu!H;ynb*D~0@ UW8DfCpaU2@UHx3vIVCg!02<|08UO$Q literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/medium.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/task_tiers/league3/medium.png new file mode 100644 index 0000000000000000000000000000000000000000..b157c3c31ba37c488e4b8a460085d853d22fd6cd GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRp!VDxAh&o3CDbWC*5LX}_KGh*8D99qp)WpP0 zT1J6^fzfty;WVHGV@Z%-FoVOh8)-mJx~Gd{NX4z(QyY1Y81OJVguT1`hoNnupiRSp zKP-*e?v=&=1(|l%F){HiVGzk=a_EfG6@B}=nAz*`--Z4M^478HL)78&qol`;+07lP3 Ay#N3J literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/tracked_and_untracked_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/tracked_and_untracked_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8dad9aad791ccd398fe9d24d7a5d2841d1a42d39 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!ptGloV~B-+@*jQ{9*+h=4hNH67K#U0lpJO` zGUzy|F-W`cGMI5Z;T2%$?qMov5d8au>D4hQQ--TNQpXZZz6&$)@G!h^Q{lRBGp-hB O9D}E;pUXO@geCxDxh$Um literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/tracked_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/tracked_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4915f067cc0b86ff351c111db0b7195f38b9244b GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!pp~bKV~B-+@*jQ{9*+h=4hNH67K#U0lpJO` zGUzy|F-W`cGMI5Z;T2%$?qMo1kov1E)#GNbc;Em71J7{-F25Sz9H1!-p00i_>zopr E0HP)*kN^Mx literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/untracked_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel/components/tracked_button/untracked_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bc705cc0c59a8c30f620c38ff1201b57679154ef GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!pt7fnV~B-+@*jQ{o)89~fLTcfKX{~$C7Aqj hH<9SAb82X0WSF1C!}aljbv#fxgQu&X%Q~loCIGuEBya!# literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel_icon.png b/tasks-tracker-plugin-master/src/main/resources/net/reldo/taskstracker/panel_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7dc707feef8aa5d5458eee957cefe34c64ba6c45 GIT binary patch literal 15928 zcmeHuWmKF?)^6kO?u|P%?(PuW8VfW{#G!P^ZGz3k8%OyEy zX3pGi?)TkU>)w9@tDCo~p8f1yPwidR@9LKrEe$0sbaHe60Dz^UEU)wU7xehzKt+Ch zrzaM(0RX6z{q(?|IuIYAtGkP}og)nB`OFmtg!$T80|36uwJ&VEVXf{F4^TpCcpX|y z6gSSPzQK!Dw{Zg>HSV#Xvg_224N8E1vmBh~?(_Sbj)%=xBRZxn%c|VN-!uG=+)mWK z7uKIrhfN)y2A0=nT|Sr>__zgj<33}%JAH4^v-WToaOoB@mDKNYJ{z&T7%_1BGoqlk zZat_|>y|u6v26WfG2(hLLg%&%w}%TPIeS*#di|Yn=jwK4UDvVjxdu-Ao#;2IyXUZ< zEpCN`UsPzHQA1-3;TY~PxUhok#3SdP(I?#&=xp=!wnk2c-p-3&6)J8AB~2u)2gOa0 zJ*P_7lRS16KRvL_q>*|!yRzt&c<_?B$h_PYH!|-XHZM$yJ#d-5y7xJDK3e;pvpN>R z$Z4K-U9fqRAmpBsw>FBtp7DI-_-CL`&kK5g6592ktMLnT3HbqcIc%|b*TH8}hG0yd zQ37@;s41fARI?s5iT#;UUSrQ-LPoR`9qBAxmx}fcPv*(dr}aYv+ZA7wH8KPe3{|9; zQ4d2GQeD&Cs#my8**0CxmZsV{!yrNti8C&E{39RgC6mv`$Lqa>i}HwvoF357!Ko?6 ziirATwxk&h&&`C9Db3o5j$W=ld^Z;;h!zjdT((gww4P@H7d)yMfJ|b~vn`A!1B!d0 zJ(8zGE6n+IEubRuV$JsSJAbwUkb?-ls;VqnANk{0VJ?LTKZlXv8_W4zFAByC)0%Xh zxIULTJ&$H%``X%bQDggdBlg;s9b=c$(E@Iu)X;N+&ow;@&3|BUNw(?{z3ZwIM_wHr3?kQV)qnC*h zxeHg~k|(s{z7lMYQfN>#V5KbgUU*b&f^inyYw>fE(4CJl6q|=TR$E@Z>+fxfYJQ^i zVw+4e!9XdYJ;{4vG9gEF{ZlS2=bG^Qo^CfZ3#%y!rW{u~nf!cGdjAKX?8t1nX3mR+`&y+3E~wlQqx z^Y}G3+xP=M#4tz7b04-i@_+KMYk}ex7-cySI+}B?m?`VQoR|WiRmNJsKe#kHwA~(g zuU49LMRy)R8PJ4P(cqoy4931~Dz*Hp|m|$q3_KEH4n2lK#q%rOE*lwvVfAfZ! z_Z8J99&pUADpL~tP1g{3$LmF1I^tsw=Ch9_8Dc)1IT19d4r(Uj3mzgGZ};@`E>hSJ ztktYbp)Q)1oCH!>3@(lhF^uAgg>jcZ8%mq$Oe62ik~h<_9*uaWYUy^&#jhESM3aqI&e(7>(6|<{{ z?c$fh02fuyamWrwVa7vYXS&4bWYR9FXzSnIwUjmb|iBfhSOQ{*(c}GOXqVne%7wKPaLOHi^IFej9j! zeQu@@<)u?T{9&bF;U>}UY+
@DVXm`3E8nER-5&#&Xc7&8*6-t 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 literal 0 HcmV?d00001 diff --git a/tasks-tracker-plugin-master/src/test/java/net/reldo/taskstracker/TasksTrackerPluginTest.java b/tasks-tracker-plugin-master/src/test/java/net/reldo/taskstracker/TasksTrackerPluginTest.java new file mode 100644 index 00000000..abca993c --- /dev/null +++ b/tasks-tracker-plugin-master/src/test/java/net/reldo/taskstracker/TasksTrackerPluginTest.java @@ -0,0 +1,13 @@ +package net.reldo.taskstracker; + +import net.runelite.client.RuneLite; +import net.runelite.client.externalplugins.ExternalPluginManager; + +public class TasksTrackerPluginTest +{ + public static void main(String[] args) throws Exception + { + ExternalPluginManager.loadBuiltin(TasksTrackerPlugin.class); + RuneLite.main(args); + } +} \ No newline at end of file