diff --git a/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java b/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java index e89d11f4dd..ecb337780f 100644 --- a/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java +++ b/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java @@ -138,6 +138,8 @@ public class DiscordService implements AutoCloseable discordRichPresence.joinSecret = discordPresence.getJoinSecret(); discordRichPresence.spectateSecret = discordPresence.getSpectateSecret(); discordRichPresence.instance = (byte) (discordPresence.isInstance() ? 1 : 0); + + log.debug("Sending presence update {}", discordPresence); discordRPC.Discord_UpdatePresence(discordRichPresence); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordAreaType.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordAreaType.java new file mode 100644 index 0000000000..7beb6810aa --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordAreaType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018, PandahRS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.discord; + +enum DiscordAreaType +{ + BOSSES, + CITIES, + DUNGEONS, + MINIGAMES; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordConfig.java index fd992b7d77..cee0ddffdc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordConfig.java @@ -34,7 +34,8 @@ public interface DiscordConfig extends Config @ConfigItem( keyName = "actionTimeout", name = "Action timeout (minutes)", - description = "Configures after how long of not updating status will be reset (in minutes)" + description = "Configures after how long of not updating status will be reset (in minutes)", + position = 1 ) default int actionTimeout() { @@ -42,12 +43,57 @@ public interface DiscordConfig extends Config } @ConfigItem( - keyName = "actionDelay", - name = "New action delay (seconds)", - description = "Configures the delay before new action will be considered as valid" + keyName = "showSkillActivity", + name = "Show activity while skilling", + description = "Configures if your activity while training skills should be shown.", + position = 2 ) - default int actionDelay() + default boolean showSkillingActivity() { - return 10; + return true; + } + + @ConfigItem( + keyName = "showBossActivity", + name = "Show activity at bosses", + description = "Configures if your activity at bosses should be shown.", + position = 3 + ) + default boolean showBossActivity() + { + return true; + } + + @ConfigItem( + keyName = "showCityActivity", + name = "Show activity at cities", + description = "Configures if your activity at cities should be shown.", + position = 4 + ) + default boolean showCityActivity() + { + return true; + } + + @ConfigItem( + keyName = "showDungeonActivity", + name = "Show activity at dungeons", + description = "Configures if your activity at dungeons should be shown.", + position = 5 + ) + default boolean showDungeonActivity() + { + return true; + } + + @ConfigItem( + keyName = "showMinigameActivity", + name = "Show activity at minigames", + description = "Configures if your activity at minigames should be shown.", + position = 6 + ) + default boolean showMinigameActivity() + { + return true; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java index d5b2d936bb..4f4bfd8cad 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, PandahRS * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,103 +25,274 @@ */ package net.runelite.client.plugins.discord; -import com.google.common.collect.ImmutableSet; -import java.util.Set; -import java.util.function.Function; +import java.util.HashMap; +import java.util.Map; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.RequiredArgsConstructor; import net.runelite.api.Skill; -import static net.runelite.api.Skill.AGILITY; -import static net.runelite.api.Skill.ATTACK; -import static net.runelite.api.Skill.CONSTRUCTION; -import static net.runelite.api.Skill.COOKING; -import static net.runelite.api.Skill.CRAFTING; -import static net.runelite.api.Skill.DEFENCE; -import static net.runelite.api.Skill.FARMING; -import static net.runelite.api.Skill.FIREMAKING; -import static net.runelite.api.Skill.FISHING; -import static net.runelite.api.Skill.FLETCHING; -import static net.runelite.api.Skill.HERBLORE; -import static net.runelite.api.Skill.HITPOINTS; -import static net.runelite.api.Skill.HUNTER; -import static net.runelite.api.Skill.MAGIC; -import static net.runelite.api.Skill.MINING; -import static net.runelite.api.Skill.PRAYER; -import static net.runelite.api.Skill.RANGED; -import static net.runelite.api.Skill.RUNECRAFT; -import static net.runelite.api.Skill.SLAYER; -import static net.runelite.api.Skill.SMITHING; -import static net.runelite.api.Skill.STRENGTH; -import static net.runelite.api.Skill.THIEVING; -import static net.runelite.api.Skill.WOODCUTTING; -@RequiredArgsConstructor @AllArgsConstructor @Getter -public enum DiscordGameEventType +enum DiscordGameEventType { - IN_GAME("In Game", false), - IN_MENU("In Menu", false), - TRAINING_ATTACK(ATTACK, DiscordGameEventType::combatSkillChanged), - TRAINING_DEFENCE(DEFENCE, DiscordGameEventType::combatSkillChanged), - TRAINING_STRENGTH(STRENGTH, DiscordGameEventType::combatSkillChanged), - TRAINING_HITPOINTS(HITPOINTS, DiscordGameEventType::combatSkillChanged), - TRAINING_SLAYER(SLAYER, 1, DiscordGameEventType::combatSkillChanged), - TRAINING_RANGED(RANGED, DiscordGameEventType::combatSkillChanged), - TRAINING_MAGIC(MAGIC, DiscordGameEventType::combatSkillChanged), - TRAINING_PRAYER(PRAYER), - TRAINING_COOKING(COOKING), - TRAINING_WOODCUTTING(WOODCUTTING), - TRAINING_FLETCHING(FLETCHING), - TRAINING_FISHING(FISHING), - TRAINING_FIREMAKING(FIREMAKING), - TRAINING_CRAFTING(CRAFTING), - TRAINING_SMITHING(SMITHING), - TRAINING_MINING(MINING), - TRAINING_HERBLORE(HERBLORE), - TRAINING_AGILITY(AGILITY), - TRAINING_THIEVING(THIEVING), - TRAINING_FARMING(FARMING), - TRAINING_RUNECRAFT(RUNECRAFT), - TRAINING_HUNTER(HUNTER), - TRAINING_CONSTRUCTION(CONSTRUCTION); - private static final Set COMBAT_SKILLS = ImmutableSet.of(ATTACK, STRENGTH, DEFENCE, HITPOINTS, SLAYER, RANGED, MAGIC); + IN_GAME("In Game", -3), + IN_MENU("In Menu", -3), + TRAINING_ATTACK(Skill.ATTACK), + TRAINING_DEFENCE(Skill.DEFENCE), + TRAINING_STRENGTH(Skill.STRENGTH), + TRAINING_HITPOINTS(Skill.HITPOINTS, -1), + TRAINING_SLAYER(Skill.SLAYER, 1), + TRAINING_RANGED(Skill.RANGED), + TRAINING_MAGIC(Skill.MAGIC), + TRAINING_PRAYER(Skill.PRAYER), + TRAINING_COOKING(Skill.COOKING), + TRAINING_WOODCUTTING(Skill.WOODCUTTING), + TRAINING_FLETCHING(Skill.FLETCHING), + TRAINING_FISHING(Skill.FISHING), + TRAINING_FIREMAKING(Skill.FIREMAKING), + TRAINING_CRAFTING(Skill.CRAFTING), + TRAINING_SMITHING(Skill.SMITHING), + TRAINING_MINING(Skill.MINING), + TRAINING_HERBLORE(Skill.HERBLORE), + TRAINING_AGILITY(Skill.AGILITY), + TRAINING_THIEVING(Skill.THIEVING), + TRAINING_FARMING(Skill.FARMING), + TRAINING_RUNECRAFT(Skill.RUNECRAFT), + TRAINING_HUNTER(Skill.HUNTER), + TRAINING_CONSTRUCTION(Skill.CONSTRUCTION), - private final String state; - private final String imageKey; + // Bosses + BOSS_ABYSSAL_SIRE("Abyssal Sire", DiscordAreaType.BOSSES, 11851, 11850, 12363, 12362), + BOSS_CERBERUS("Cerberus", DiscordAreaType.BOSSES, 4883, 5140, 5395), + BOSS_COMMANDER_ZILYANA("Commander Zilyana", DiscordAreaType.BOSSES, 11602), + BOSS_DKS("Dagannoth Kings", DiscordAreaType.BOSSES, 11588, 11589), + BOSS_GENERAL_GRAARDOR("General Graardor", DiscordAreaType.BOSSES, 11347), + BOSS_GIANT_MOLE("Giant Mole", DiscordAreaType.BOSSES, 6993, 6992), + BOSS_GROTESQUE_GUARDIANS("Grotesque Guardians", DiscordAreaType.BOSSES, 6727), + BOSS_KQ("Kalphite Queen", DiscordAreaType.BOSSES, 13972), + BOSS_KRAKEN("Kraken", DiscordAreaType.BOSSES, 9116), + BOSS_KREEARRA("Kree'arra", DiscordAreaType.BOSSES, 11346), + BOSS_KRIL_TSUTSAROTH("K'ril Tsutsaroth", DiscordAreaType.BOSSES, 11603), + BOSS_SKOTIZO("Skotizo", DiscordAreaType.BOSSES, 6810), + BOSS_SMOKE_DEVIL("Thermonuclear smoke devil", DiscordAreaType.BOSSES, 9363, 9619), + BOSS_VORKATH("Vorkath", DiscordAreaType.BOSSES, 9023), + BOSS_ZULRAH("Zulrah", DiscordAreaType.BOSSES, 9007), + + // Cities + CITY_AL_KHARID("Al Kharid" , DiscordAreaType.CITIES, 13105, 13106), + CITY_APE_ATOLL("Ape Atoll" , DiscordAreaType.CITIES, 10795, 11051, 10974, 11050), + CITY_ARCEUUS_HOUSE("Arceuus House" , DiscordAreaType.CITIES, 6459, 6715, 6458, 6714), + CITY_ARDOUGNE("Ardougne" , DiscordAreaType.CITIES, 10548, 10547, 10292, 10291, 10036, 10035, 9780, 9779), + CITY_BARBARIAN_VILLAGE("Barbarian Village" , DiscordAreaType.CITIES, 12341), + CITY_BANDIT_CAMP("Bandit Camp" , DiscordAreaType.CITIES, 12591), + CITY_BEDABIN_CAMP("Bedabin Camp" , DiscordAreaType.CITIES, 12590), + CITY_BRIMHAVEN("Brimhaven" , DiscordAreaType.CITIES, 11057, 11058), + CITY_BURGH_DE_ROTT("Burgh de Rott" , DiscordAreaType.CITIES, 13874, 13873, 14130, 14129), + CITY_BURTHOPE("Burthope" , DiscordAreaType.CITIES, 11319, 11575), + CITY_CANIFIS("Canifis" , DiscordAreaType.CITIES, 13878), + CITY_CATHERBY("Catherby" , DiscordAreaType.CITIES, 11317, 11318, 11061), + CITY_CORSAIR_CAVE("Corsair Cove" , DiscordAreaType.CITIES, 10028, 10284), + CITY_DORGESH_KAAN("Dorgesh-Kaan" , DiscordAreaType.CITIES, 10835, 10834), + CITY_DRAYNOR("Draynor" , DiscordAreaType.CITIES, 12338), + CITY_EDGEVILLE("Edgeville" , DiscordAreaType.CITIES, 12342), + CITY_ENTRANA("Entrana" , DiscordAreaType.CITIES, 11060, 11316), + CITY_FALADOR("Falador" , DiscordAreaType.CITIES, 11828, 11572, 11571, 11827, 12084), + CITY_GOBLIN_VILLAGE("Goblin Village" , DiscordAreaType.CITIES, 11830), + CITY_GUTANOTH("Gu'Tanoth" , DiscordAreaType.CITIES, 10031), + CITY_HOSIDIUS_HOUSE("Hosidius House" , DiscordAreaType.CITIES, 6713, 6712, 6455, 6711, 6710, 6965, 6966, 7222, 7223, 6967), + CITY_JATISZO("Jatizso" , DiscordAreaType.CITIES, 9531), + CITY_JIGGIG("Jiggig" , DiscordAreaType.CITIES, 9775), + CITY_KARAMJA("Karamja" , DiscordAreaType.CITIES, 11569, 11568, 11567, 11566, 11313, 11312, 11311), + CITY_KELDAGRIM("Keldagrim" , DiscordAreaType.CITIES, 11423, 11422, 11679, 11678), + CITY_LLETYA("Lletya" , DiscordAreaType.CITIES, 9265), + CITY_LOVAKENGJ_HOUSE("Lovakengj House" , DiscordAreaType.CITIES, 5692, 5948, 5691, 5947, 6203, 6202, 5690, 5946), + CITY_LUMBRIDGE("Lumbridge" , DiscordAreaType.CITIES, 12850), + CITY_LUNAR_ISLE("Lunar Isle" , DiscordAreaType.CITIES, 8253, 8252, 8509, 8508), + CITY_MEIYERDITCH("Meiyerditch" , DiscordAreaType.CITIES, 14132, 14388, 14387, 14386, 14385), + CITY_MISCELLANIA("Miscellania" , DiscordAreaType.CITIES, 10044, 10300), + CITY_MOS_LE_HARMLESS("Mos Le'Harmless" , DiscordAreaType.CITIES, 14638), + CITY_MORTTON("Mort'ton" , DiscordAreaType.CITIES, 13875), + CITY_MOR_UI_REK("Mor UI Rek" , DiscordAreaType.CITIES, 9808, 9807, 10064, 10063), + CITY_NARDAH("Nardah" , DiscordAreaType.CITIES, 13613), + CITY_NEITIZNOT("Neitiznot" , DiscordAreaType.CITIES, 9275), + CITY_PISCATORIS("Piscatoris" , DiscordAreaType.CITIES, 9273), + CITY_POLLNIVNEACH("Pollnivneach" , DiscordAreaType.CITIES, 13358), + CITY_PORT_KHAZARD("Port Khazard" , DiscordAreaType.CITIES, 10545), + CITY_PORT_PHASMATYS("Port Phasmatys" , DiscordAreaType.CITIES, 14646), + CITY_PORT_SARIM("Port Sarim" , DiscordAreaType.CITIES, 12082), + CITY_PISCARILIUS_HOUSE("Piscarilius House" , DiscordAreaType.CITIES, 6971, 7227, 6970, 7226), + CITY_RELLEKKA("Rellekka" , DiscordAreaType.CITIES, 10553), + CITY_RIMMINGTON("Rimmington" , DiscordAreaType.CITIES, 11826, 11570), + CITY_SEERS_VILLAGE("Seers' Village" , DiscordAreaType.CITIES, 10806), + CITY_SHAYZIEN_HOUSE("Shayzien House" , DiscordAreaType.CITIES, 5944, 5943, 6200, 6199, 5688), + CITY_SHILO_VILLAGE("Shilo Village" , DiscordAreaType.CITIES, 11310), + CITY_SOPHANEM("Sophanem" , DiscordAreaType.CITIES, 13099), + CITY_TAI_BWO_WANNAI("Tai Bwo Wannai" , DiscordAreaType.CITIES, 11056, 11055), + CITY_TAVERLEY("Taverley" , DiscordAreaType.CITIES, 11574, 11573), + CITY_TREE_GNOME_STRONGHOLD("Tree Gnome Stronghold" , DiscordAreaType.CITIES, 9782, 9781), + CITY_TREE_GNOME_VILLAGE("Tree Gnome Village" , DiscordAreaType.CITIES, 10033), + CITY_TROLL_STRONGHOLD("Troll Stronghold" , DiscordAreaType.CITIES, 11321), + CITY_TYRAS_CAMP("Tyras Camp" , DiscordAreaType.CITIES, 8753, 8752), + CITY_UZER("Uzer" , DiscordAreaType.CITIES, 13872), + CITY_VARROCK("Varrock" , DiscordAreaType.CITIES, 12596, 12597, 12598, 12852, 12853, 12854, 13108, 13109, 13110), + CITY_WITCHHAVEN("Witchaven" , DiscordAreaType.CITIES, 10803), + CITY_YANILLE("Yanille" , DiscordAreaType.CITIES, 10288, 10032), + CITY_ZANARIS("Zanaris" , DiscordAreaType.CITIES, 9285, 9541, 9540, 9797), + CITY_ZULANDRA("Zul-Andra" , DiscordAreaType.CITIES, 8751), + + // Dungeons + DUNGEON_ABANDONED_MINE("Abandoned Mine", DiscordAreaType.DUNGEONS, 13718, 11079, 11078, 11077, 10823, 10822, 10821), + DUNGEON_AH_ZA_RHOON("Ah Za Rhoon", DiscordAreaType.DUNGEONS, 11666), + DUNGEON_ANCIENT_CAVERN("Ancient Cavern", DiscordAreaType.DUNGEONS, 6483, 6995), + DUNGEON_APE_ATOLL("Ape Atoll Dungeon", DiscordAreaType.DUNGEONS, 11150, 10894), + DUNGEON_ARDY_SEWERS("Ardougne Sewers", DiscordAreaType.DUNGEONS, 10136), + DUNGEON_ASGARNIAN_ICE_CAVES("Asgarnian Ice Caves", DiscordAreaType.DUNGEONS, 12181), + DUNGEON_BRIMHAVEN("Brimhaven Dungeon", DiscordAreaType.DUNGEONS, 10901, 10900, 10899, 10645, 10644, 10643), + DUNGEON_BRINE_RAT_CAVERN("Brine Rat Cavern", DiscordAreaType.DUNGEONS, 10910), + DUNGEON_CATACOMBS_OF_KOUREND("Catacombs of Kourend", DiscordAreaType.DUNGEONS, 6557, 6556, 6813, 6812), + DUNGEON_CHASM_OF_FIRE("Chasm of Fire", DiscordAreaType.DUNGEONS, 5789), + DUNGEON_CLOCK_TOWER("Clock Tower Basement", DiscordAreaType.DUNGEONS, 10390), + DUNGEON_CORSAIR_COVE("Corsair Cove Dungeon", DiscordAreaType.DUNGEONS, 8076, 8332), + DUNGEON_CRABCLAW_CAVES("Crabclaw Caves", DiscordAreaType.DUNGEONS, 6553, 6809), + DUNGEON_DIGSITE("Digsite Dungeon", DiscordAreaType.DUNGEONS, 13465), + DUNGEON_DORGESHKAAN("Dorgesh-Kaan South Dungeon", DiscordAreaType.DUNGEONS, 10833), + DUNGEON_DORGESHUUN_MINES("Dorgeshuun Mines", DiscordAreaType.DUNGEONS, 12950, 13206), + DUNGEON_DRAYNOR_SEWERS("Draynor Sewers", DiscordAreaType.DUNGEONS, 12439, 12438), + DUNGEON_DWARVEN_MINES("Dwarven Mines", DiscordAreaType.DUNGEONS, 12185, 12184, 12183), + DUNGEON_EAGLES_PEAK("Eagles' Peak Dungeon", DiscordAreaType.DUNGEONS, 8013), + DUNGEON_EDGEVILLE("Edgeville Dungeon", DiscordAreaType.DUNGEONS, 12441, 12442, 12443, 12698), + DUNGEON_ELEMENTAL_WORKSHOP("Elemental Workshop", DiscordAreaType.DUNGEONS, 10906, 7760), + DUNGEON_ENAKHRAS_TEMPLE("Enakhra's Temple", DiscordAreaType.DUNGEONS, 12423), + DUNGEON_ENTRANA("Entrana Dungeon", DiscordAreaType.DUNGEONS, 11416), + DUNGEON_EVIL_CHICKENS_LAIR("Evil Chicken's Lair", DiscordAreaType.DUNGEONS, 9796), + DUNGEON_EXPERIMENT_CAVE("Experiment Cave", DiscordAreaType.DUNGEONS, 14235, 13979), + DUNGEON_FREMENNIK_SLAYER("Fremennik Slayer Dungeon", DiscordAreaType.DUNGEONS, 10908, 11164), + DUNGEON_GOBLIN_CAVE("Goblin Cave", DiscordAreaType.DUNGEONS, 10393), + DUNGEON_GRAND_TREE_TUNNELS("Grand Tree Tunnels", DiscordAreaType.DUNGEONS, 9882), + DUNGEON_HAM("H.A.M Dungeon", DiscordAreaType.DUNGEONS, 12694, 10321), + DUNGEON_JATIZSO_MINES("Jatizo Mines", DiscordAreaType.DUNGEONS, 9631), + DUNGEON_JIGGIG_BURIAL_TOMB("Jiggig Burial Tomb", DiscordAreaType.DUNGEONS, 9875, 9874), + DUNGEON_JOGRE("Jogre Dungeon", DiscordAreaType.DUNGEONS, 11412), + DUNGEON_KARAMJA_VOLCANO("Karamja Volcano", DiscordAreaType.DUNGEONS, 11413, 11414), + DUNGEON_KHARAZI("Khazari Dungeon", DiscordAreaType.DUNGEONS, 11153), + DUNGEON_LIGHTHOUSE("Lighthouse", DiscordAreaType.DUNGEONS, 10140), + DUNGEON_LIZARDMAN_CAVES("Lizardman Caves", DiscordAreaType.DUNGEONS, 5275), + DUNGEON_LUMBRIDGE_SWAMP_CAVES("Lumbridge Swamp Caves", DiscordAreaType.DUNGEONS, 12693, 12949), + DUNGEON_LUNAR_ISLE_MINE("Lunar Isle Mine", DiscordAreaType.DUNGEONS, 9377), + DUNGEON_MISCELLANIA("Miscellania Dungeon", DiscordAreaType.DUNGEONS, 10144, 10400), + DUNGEON_MOGRE_CAMP("Mogre Camp", DiscordAreaType.DUNGEONS, 11924), + DUNGEON_MOS_LE_HARMLESS_CAVES("Mos Le'Harmless Caves", DiscordAreaType.DUNGEONS, 14994, 14995, 15251), + DUNGEON_MOUSE_HOLE("Mouse Hole", DiscordAreaType.DUNGEONS, 9046), + DUNGEON_OBSERVATORY("Observatory Dungeon", DiscordAreaType.DUNGEONS, 9362), + DUNGEON_OGRE_ENCLAVE("Ogre Enclave", DiscordAreaType.DUNGEONS, 10387), + DUNGEON_QUIDAMORTEM_CAVE("Quidamortem Cave", DiscordAreaType.DUNGEONS, 4763), + DUNGEON_RASHILIYIAS_TOMB("Rashiliyta's Tomb", DiscordAreaType.DUNGEONS, 11668), + DUNGEON_SARADOMINSHRINE("Saradomin Shrine (Paterdomus)", DiscordAreaType.DUNGEONS, 13722), + DUNGEON_SHADE_CATACOMBS("Shade Catacombs", DiscordAreaType.DUNGEONS, 13975), + DUNGEON_SHAYZIEN_CRYPTS("Shayzien Crypts", DiscordAreaType.DUNGEONS, 6043), + DUNGEON_SMOKE("Smoke Dungeon", DiscordAreaType.DUNGEONS, 12946, 13202), + DUNGEON_SOPHANEM("Sophanem Dungeon", DiscordAreaType.DUNGEONS, 13200), + DUNGEON_STRONGHOLD_SECURITY("Stronghold of Security", DiscordAreaType.DUNGEONS, 7505, 8017, 8530, 9297), + DUNGEON_TARNS_LAIR("Tarn's Lair", DiscordAreaType.DUNGEONS, 12616, 12615), + DUNGEON_TAVERLEY("Taverley Dungeon", DiscordAreaType.DUNGEONS, 11673, 11672, 11929, 11928, 11417), + DUNGEON_TEMPLE_OF_IKOV("Temple of Ikov", DiscordAreaType.DUNGEONS, 10649, 10905, 10650), + DUNGEON_TEMPLE_OF_MARIMBO("Temple of Marimbo", DiscordAreaType.DUNGEONS, 11151), + DUNGEON_THE_WARRENS("The Warrens", DiscordAreaType.DUNGEONS, 7070, 7326), + DUNGEON_TOLNA("Dungeon of Tolna", DiscordAreaType.DUNGEONS, 13209), + DUNGEON_TOWER_OF_LIFE("Tower of Life Basement", DiscordAreaType.DUNGEONS, 12100), + DUNGEON_TUNNEL_OF_CHAOS("Tunnel of Chaos", DiscordAreaType.DUNGEONS, 12625), + DUNGEON_UNDERGROUND_PASS("Underground Pass", DiscordAreaType.DUNGEONS, 9369, 9370), + DUNGEON_VARROCKSEWERS("Varrock Sewers", DiscordAreaType.DUNGEONS, 12954, 13210), + DUNGEON_WATER_RAVINE("Water Ravine", DiscordAreaType.DUNGEONS, 13461), + DUNGEON_WATERBIRTH("Waterbirth Dungeon", DiscordAreaType.DUNGEONS, 9886, 10142, 7492, 7748), + DUNGEON_WATERFALL("Waterfall Dungeon", DiscordAreaType.DUNGEONS, 10394), + DUNGEON_WHITE_WOLF_MOUNTAIN_CAVES("White Wolf Mountain Caves", DiscordAreaType.DUNGEONS, 11418, 11419, 11675), + DUNGEON_WITCHAVEN_SHRINE("Witchhaven Shrine Dungeon", DiscordAreaType.DUNGEONS, 10903), + DUNGEON_YANILLE_AGILITY("Yanile Agility Dungeon", DiscordAreaType.DUNGEONS, 10388), + + // Minigames + MG_BARBARIAN_ASSAULT("Barbarian Assault", DiscordAreaType.MINIGAMES, 10332), + MG_BARROWS("Barrows", DiscordAreaType.MINIGAMES, 14131, 14231), + MG_BLAST_FURNACE("Blast Furnace", DiscordAreaType.MINIGAMES, 7757), + MG_BRIMHAVEN_AGILITY_ARENA("Brimhaven Agility Arena", DiscordAreaType.MINIGAMES, 11157), + MG_BURTHOPE_GAMES_ROOM("Burthope Games Room", DiscordAreaType.MINIGAMES, 8781), + MG_CASTLE_WARS("Castle Wars", DiscordAreaType.MINIGAMES, 9520), + MG_CLAN_WARS("Clan Wars", DiscordAreaType.MINIGAMES, 13135, 13134, 13133, 13131, 13130, 13387, 13386), + MG_DUEL_ARENA("Duel Arena", DiscordAreaType.MINIGAMES, 13362), + MG_FISHING_TRAWLER("Fishing Trawler", DiscordAreaType.MINIGAMES, 7499), + MG_INFERNO("The Inferno", DiscordAreaType.MINIGAMES, 9043), + MG_LAST_MAN_STANDING("Last Man Standing", DiscordAreaType.MINIGAMES, 13660, 13659, 13658, 13916, 13915, 13914), + MG_MAGE_TRAINING_ARENA("Mage Training Arena", DiscordAreaType.MINIGAMES, 13462, 13463), + MG_NIGHTMARE_ZONE("Nightmare Zone", DiscordAreaType.MINIGAMES, 9033), + MG_PEST_CONTROL("Pest Control", DiscordAreaType.MINIGAMES, 10536), + MG_PYRAMID_PLUNDER("Pyramid Plunder", DiscordAreaType.MINIGAMES, 7749), + MG_ROGUES_DEN("Rogues' Den", DiscordAreaType.MINIGAMES, 11855, 11854, 12111, 12110), + MG_SORCERESS_GARDEN("Sorceress's Garden", DiscordAreaType.MINIGAMES, 11605), + MG_TEMPLE_TREKKING("Temple Trekking", DiscordAreaType.MINIGAMES, 8014, 8270, 8256, 8782, 9038, 9294, 9550, 9806), + MG_TITHE_FARM("Tithe Farm", DiscordAreaType.MINIGAMES, 6968), + MG_TROUBLE_BREWING("Trouble Brewing", DiscordAreaType.MINIGAMES, 15150), + MG_TZHAAR_FIGHT_CAVES("Tzhaar Fight Caves", DiscordAreaType.MINIGAMES, 9551), + MG_TZHAAR_FIGHT_PITS("Tzhaar Fight Pits", DiscordAreaType.MINIGAMES, 9552), + MG_VOLCANIC_MINE("Volcanic Mine", DiscordAreaType.MINIGAMES, 15263, 15262); + + private static final Map FROM_REGION = new HashMap<>(); + + static + { + for (DiscordGameEventType discordGameEventType : DiscordGameEventType.values()) + { + if (discordGameEventType.getRegionIds() == null) + { + continue; + } + + for (int region : discordGameEventType.getRegionIds()) + { + assert !FROM_REGION.containsKey(region); + FROM_REGION.put(region, discordGameEventType); + } + } + } + + private String imageKey; + private String state; private String details; - private boolean considerDelay = true; - private Function isChanged = (l) -> true; - private int priority = 0; + private int priority; + private boolean shouldClear; + private boolean shouldTimeout; - DiscordGameEventType(String state, boolean considerDelay) - { - this.state = state; - this.imageKey = "default"; - this.considerDelay = considerDelay; - } - - DiscordGameEventType(Skill skill, int priority, Function isChanged) - { - this.state = training(skill); - this.imageKey = imageKeyOf(skill); - this.priority = priority; - this.isChanged = isChanged; - } - - DiscordGameEventType(Skill skill, Function isChanged) - { - this.state = training(skill); - this.imageKey = imageKeyOf(skill); - this.isChanged = isChanged; - } + private DiscordAreaType discordAreaType; + private int[] regionIds; DiscordGameEventType(Skill skill) + { + this(skill, 0); + } + + DiscordGameEventType(Skill skill, int priority) { this.state = training(skill); + this.priority = priority; this.imageKey = imageKeyOf(skill); + this.priority = priority; + this.shouldTimeout = true; + } + + DiscordGameEventType(String areaName, DiscordAreaType areaType, int... regionIds) + { + this.details = exploring(areaType, areaName); + this.priority = -2; + this.discordAreaType = areaType; + this.regionIds = regionIds; + this.shouldClear = true; + } + + DiscordGameEventType(String state, int priority) + { + this.details = state; + this.priority = priority; + this.shouldClear = true; } private static String training(final Skill skill) @@ -143,17 +315,21 @@ public enum DiscordGameEventType return "icon_" + what; } - private static boolean combatSkillChanged(final DiscordGameEventType l) + private static String exploring(DiscordAreaType areaType, String areaName) { - for (Skill skill : Skill.values()) + switch (areaType) { - if (l.getState().contains(skill.getName())) - { - return !COMBAT_SKILLS.contains(skill); - } + case BOSSES: + return "Fighting: " + areaName; + case DUNGEONS: + return "Exploring: " + areaName; + case CITIES: + return "Location: " + areaName; + case MINIGAMES: + return "Playing: " + areaName; } - return true; + return ""; } public static DiscordGameEventType fromSkill(final Skill skill) @@ -185,4 +361,9 @@ public enum DiscordGameEventType default: return null; } } + + public static DiscordGameEventType fromRegion(final int regionId) + { + return FROM_REGION.get(regionId); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java index c542c76191..def5c6beda 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, PandahRS * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,17 +30,22 @@ import com.google.inject.Inject; import com.google.inject.Provides; import java.awt.image.BufferedImage; import java.time.temporal.ChronoUnit; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import net.runelite.api.Client; +import static net.runelite.api.Constants.CHUNK_SIZE; import net.runelite.api.GameState; import net.runelite.api.Skill; +import net.runelite.api.WorldType; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.ExperienceChanged; import net.runelite.api.events.GameStateChanged; import net.runelite.client.RuneLiteProperties; import net.runelite.client.config.ConfigManager; -import net.runelite.client.discord.DiscordService; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.task.Schedule; @@ -60,19 +66,18 @@ public class DiscordPlugin extends Plugin @Inject private DiscordConfig config; - @Inject - private DiscordService discordService; - @Inject private TitleToolbar titleToolbar; @Inject private RuneLiteProperties properties; - private final DiscordState discordState = new DiscordState(); + @Inject + private DiscordState discordState; + private Map skillExp = new HashMap<>(); - private boolean loggedIn = false; private NavigationButton discordButton; + private boolean loginFlag; @Provides private DiscordConfig provideConfig(ConfigManager configManager) @@ -96,21 +101,48 @@ public class DiscordPlugin extends Plugin .build(); titleToolbar.addNavigation(discordButton); - updateGameStatus(client.getGameState(), true); + checkForGameStateUpdate(); } @Override protected void shutDown() throws Exception { titleToolbar.removeNavigation(discordButton); - discordService.clearPresence(); discordState.reset(); } @Subscribe public void onGameStateChanged(GameStateChanged event) { - updateGameStatus(event.getGameState(), false); + switch (event.getGameState()) + { + case LOGIN_SCREEN: + checkForGameStateUpdate(); + return; + case LOGGING_IN: + loginFlag = true; + break; + case LOGGED_IN: + if (loginFlag) + { + loginFlag = false; + checkForGameStateUpdate(); + } + + break; + } + + checkForAreaUpdate(); + } + + @Subscribe + public void configChanged(ConfigChanged event) + { + if (event.getGroup().equalsIgnoreCase("discord")) + { + checkForGameStateUpdate(); + checkForAreaUpdate(); + } } @Subscribe @@ -126,9 +158,9 @@ public class DiscordPlugin extends Plugin final DiscordGameEventType discordGameEventType = DiscordGameEventType.fromSkill(event.getSkill()); - if (discordGameEventType != null) + if (discordGameEventType != null && config.showSkillingActivity()) { - discordState.triggerEvent(discordGameEventType, config.actionDelay()); + discordState.triggerEvent(discordGameEventType); } } @@ -138,33 +170,97 @@ public class DiscordPlugin extends Plugin ) public void checkForValidStatus() { - if (discordState.checkForTimeout(config.actionTimeout())) - { - updateGameStatus(client.getGameState(), true); - } + discordState.checkForTimeout(); } - @Schedule( - period = 1, - unit = ChronoUnit.SECONDS - ) - public void flushDiscordStatus() + private void checkForGameStateUpdate() { - discordState.flushEvent(discordService); + // Game state update does also full reset of discord state + discordState.reset(); + discordState.triggerEvent(client.getGameState() == GameState.LOGGED_IN + ? DiscordGameEventType.IN_GAME + : DiscordGameEventType.IN_MENU); } - private void updateGameStatus(GameState gameState, boolean force) + private void checkForAreaUpdate() { - if (gameState == GameState.LOGIN_SCREEN) + if (client.getLocalPlayer() == null) { - skillExp.clear(); - loggedIn = false; - discordState.triggerEvent(DiscordGameEventType.IN_MENU, config.actionDelay()); + return; } - else if (client.getGameState() == GameState.LOGGED_IN && (force || !loggedIn)) + + final int playerRegionID = getCurrentRegion(); + + if (playerRegionID == 0) { - loggedIn = true; - discordState.triggerEvent(DiscordGameEventType.IN_GAME, config.actionDelay()); + return; } + + final DiscordGameEventType discordGameEventType = DiscordGameEventType.fromRegion(playerRegionID); + + if (discordGameEventType == null) + { + // Unknown region, reset to default in-game + discordState.triggerEvent(DiscordGameEventType.IN_GAME); + return; + } + + if (!showArea(discordGameEventType)) + { + return; + } + + discordState.triggerEvent(discordGameEventType); } + + private boolean showArea(final DiscordGameEventType event) + { + if (event == null) + { + return false; + } + + final EnumSet worldType = client.getWorldType(); + + // Do not show location in PVP activities + if (worldType.contains(WorldType.SEASONAL_DEADMAN) || + worldType.contains(WorldType.DEADMAN) || + worldType.contains(WorldType.PVP) || + worldType.contains(WorldType.PVP_HIGH_RISK)) + { + return false; + } + + switch (event.getDiscordAreaType()) + { + case BOSSES: return config.showBossActivity(); + case CITIES: return config.showCityActivity(); + case DUNGEONS: return config.showDungeonActivity(); + case MINIGAMES: return config.showMinigameActivity(); + } + + return false; + } + + private int getCurrentRegion() + { + if (!client.isInInstancedRegion()) + { + return client.getLocalPlayer().getWorldLocation().getRegionID(); + } + + // get chunk data of current chunk + final LocalPoint localPoint = client.getLocalPlayer().getLocalLocation(); + final int[][][] instanceTemplateChunks = client.getInstanceTemplateChunks(); + final int z = client.getPlane(); + final int chunkData = instanceTemplateChunks[z][localPoint.getRegionX() / CHUNK_SIZE][localPoint.getRegionY() / CHUNK_SIZE]; + + // extract world point from chunk data + final int chunkY = (chunkData >> 3 & 0x7FF) * CHUNK_SIZE; + final int chunkX = (chunkData >> 14 & 0x3FF) * CHUNK_SIZE; + + final WorldPoint worldPoint = new WorldPoint(chunkX, chunkY, z); + return worldPoint.getRegionID(); + } + } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java index 6573b633d3..6da66d34e9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java @@ -24,109 +24,136 @@ */ package net.runelite.client.plugins.discord; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ComparisonChain; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; +import java.util.Optional; +import javax.inject.Inject; +import lombok.Data; import net.runelite.client.discord.DiscordPresence; import net.runelite.client.discord.DiscordService; -public class DiscordState +/** + * This class contains data about currently active discord state. + */ +class DiscordState { - private final List lastQueue = new ArrayList<>(); - private DiscordGameEventType lastEvent; - private Instant startOfAction; - private Instant lastAction; - private DiscordPresence lastPresence; - private boolean needsFlush; + @Data + private class EventWithTime + { + private final DiscordGameEventType type; + private final Instant start; + private Instant updated; + } + private final List events = new ArrayList<>(); + private final DiscordService discordService; + private final DiscordConfig config; + private DiscordPresence lastPresence; + + @Inject + private DiscordState(final DiscordService discordService, final DiscordConfig config) + { + this.discordService = discordService; + this.config = config; + } + + /** + * Reset state. + */ void reset() { - lastQueue.clear(); - lastEvent = null; - startOfAction = null; - lastAction = null; + discordService.clearPresence(); + events.clear(); lastPresence = null; - needsFlush = false; } - void flushEvent(DiscordService discordService) + /** + * Trigger new discord state update. + * + * @param eventType discord event type + */ + void triggerEvent(final DiscordGameEventType eventType) { - if (lastPresence != null && needsFlush) - { - needsFlush = false; - discordService.updatePresence(lastPresence); - } - } + final Optional foundEvent = events.stream().filter(e -> e.type == eventType).findFirst(); + EventWithTime event; - void triggerEvent(final DiscordGameEventType eventType, int delay) - { - final boolean first = startOfAction == null; - final boolean changed = eventType != lastEvent && eventType.getIsChanged().apply(lastEvent); - boolean reset = false; - - if (first) + if (foundEvent.isPresent()) { - reset = true; + event = foundEvent.get(); } - else if (changed) + else { - if (eventType.isConsiderDelay()) + event = new EventWithTime(eventType, Instant.now()); + events.add(event); + } + + event.setUpdated(Instant.now()); + + if (event.getType().isShouldClear()) + { + events.removeIf(e -> e.getType() != eventType && e.getType().isShouldClear()); + } + + events.sort((a, b) -> ComparisonChain.start() + .compare(b.getType().getPriority(), a.getType().getPriority()) + .compare(b.getUpdated(), a.getUpdated()) + .result()); + + event = events.get(0); + + String imageKey = null; + String state = null; + String details = null; + + for (EventWithTime eventWithTime : events) + { + if (imageKey == null) { - final Duration actionDelay = Duration.ofSeconds(delay); - final Duration sinceLastAction = Duration.between(lastAction, Instant.now()); - - if (sinceLastAction.compareTo(actionDelay) >= 0) - { - reset = true; - } + imageKey = eventWithTime.getType().getImageKey(); } - else + + if (details == null) { - reset = true; + details = eventWithTime.getType().getDetails(); + } + + if (state == null) + { + state = eventWithTime.getType().getState(); + } + + if (imageKey != null && details != null && state != null) + { + break; } } - if (reset) + final DiscordPresence presence = DiscordPresence.builder() + .state(MoreObjects.firstNonNull(state, "")) + .details(MoreObjects.firstNonNull(details, "")) + .startTimestamp(event.getStart()) + .smallImageKey(MoreObjects.firstNonNull(imageKey, "default")) + .build(); + + // This is to reduce amount of RPC calls + if (!presence.equals(lastPresence)) { - lastQueue.clear(); - startOfAction = Instant.now(); - } - - if (!lastQueue.contains(eventType)) - { - lastQueue.add(eventType); - lastQueue.sort(Comparator.comparingInt(DiscordGameEventType::getPriority)); - } - - lastAction = Instant.now(); - final DiscordGameEventType newEvent = lastQueue.get(lastQueue.size() - 1); - - if (lastEvent != newEvent) - { - lastEvent = newEvent; - - lastPresence = DiscordPresence.builder() - .state(lastEvent.getState()) - .details(lastEvent.getDetails()) - .startTimestamp(startOfAction) - .smallImageKey(newEvent.getImageKey()) - .build(); - - needsFlush = true; + lastPresence = presence; + discordService.updatePresence(presence); } } - boolean checkForTimeout(final int timeout) + /** + * Check for current state timeout and act upon it. + */ + void checkForTimeout() { - if (lastAction == null) - { - return false; - } - - final Duration actionTimeout = Duration.ofMinutes(timeout); - - return Instant.now().isAfter(lastAction.plus(actionTimeout)); + final Duration actionTimeout = Duration.ofMinutes(config.actionTimeout()); + events.removeIf(event -> event.getType().isShouldTimeout() && + event.getUpdated().isAfter(event.getStart().plus(actionTimeout))); } }