From 084bf4a11e2d397d53539f7a40bb960077bfdfd8 Mon Sep 17 00:00:00 2001 From: alexFickle Date: Tue, 5 Jan 2021 19:49:56 -0600 Subject: [PATCH 1/7] Add dungeon info types and data --- src/dungeon.rs | 197 +++++++++++++++++++++++ src/dungeon/info.rs | 378 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 576 insertions(+) create mode 100644 src/dungeon.rs create mode 100644 src/dungeon/info.rs diff --git a/src/dungeon.rs b/src/dungeon.rs new file mode 100644 index 0000000..931222d --- /dev/null +++ b/src/dungeon.rs @@ -0,0 +1,197 @@ +mod info; +use info::{DungeonInfo, PathInfo, Rewards, DUNGEONS}; + +/// Information about a dungeon. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Dungeon { + info: &'static DungeonInfo, +} + +/// Information about a path within a dungeon. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Path { + info: &'static PathInfo, + dungeon: &'static DungeonInfo, +} + +impl Dungeon { + /// Gets every dungeon. + pub fn all() -> Vec { + DUNGEONS.iter().map(|x| Self { info: x }).collect() + } + + /// Gets the name of the dungeon. + pub fn name(&self) -> &str { + self.info.long_name + } + + /// Gets a short name for this dungeon. + pub fn short_name(&self) -> &str { + self.info.short_name + } + + /// Gets the achievement id of the skin collection achievement for this dungeon. + pub fn collection_id(&self) -> u32 { + self.info.collection_id + } + + /// Gets the wallet currency id of the dungeon token for this dungeon. + pub fn currency_id(&self) -> u32 { + self.info.currency_id + } + + /// Gets all paths in this dungeon. + pub fn paths(&self) -> Vec { + self.info + .paths + .iter() + .map(|path| Path { + info: path, + dungeon: self.info, + }) + .collect() + } +} + +impl Path { + /// Gets every dungeon path. + pub fn all() -> Vec { + Dungeon::all().iter().flat_map(Dungeon::paths).collect() + } + + /// Looks up a path by its id. + pub fn from_id(path_id: &str) -> Option { + for path in Self::all() { + if path_id == path.id() { + return Some(path); + } + } + None + } + + /// Looks up a path by its index into the dungeon frequenter bits list. + pub fn from_dungeon_frequenter_index(index: u8) -> Option { + for path in Self::all() { + if Some(index) == path.dungeon_frequenter_index() { + return Some(path); + } + } + None + } + + /// Gets the dungeon that contains this path. + pub fn dungeon(&self) -> Dungeon { + Dungeon { info: self.dungeon } + } + + /// Gets the unique dungeon path id used by the GW2 API for this path. + pub fn id(&self) -> &str { + self.info.id + } + + /// Gets the name of this path. + pub fn name(&self) -> &str { + self.info.long_name + } + + /// Gets a short name for this path. This is typically used in LFG. + pub fn short_name(&self) -> &str { + self.info.short_name + } + + /// Gets the index into the dungeon frequenter achievement bits array + /// in the GW2 achievement API for this dungeon path. + pub fn dungeon_frequenter_index(&self) -> Option { + self.info.dungeon_frequenter_index + } + + /// The number of coins gotten by doing this dungeon path the first + /// time in a day. + pub fn coins(&self) -> u32 { + match self.info.rewards { + Rewards::Story { coins } => coins, + Rewards::Explorable { bonus_coins } => 26_00 + bonus_coins, + } + } + + /// The number of coins gotten by doing this dungeon repeatedly in a day. + pub fn repeat_coins(&self) -> u32 { + match self.info.rewards { + Rewards::Story { coins } => coins, + Rewards::Explorable { .. } => 26_00, + } + } + + /// The number of tokens gotten by doing this dungeon path the first + /// time in a day. + pub fn tokens(&self) -> u32 { + match self.info.rewards { + Rewards::Story { .. } => 0, + Rewards::Explorable { .. } => 100, + } + } + + /// The number of tokens gotten by doing this dungeon repeatedly in a day. + pub fn repeat_tokens(&self) -> u32 { + match self.info.rewards { + Rewards::Story { .. } => 0, + Rewards::Explorable { .. } => 20, + } + } +} + +#[cfg(test)] +/// NOTE: Many tests have hard coded constants that must be changed if dungeon rewards +/// are reworked or more dungeons are added. +mod test { + use super::{Dungeon, Path}; + + #[test] + fn all_dungeons() { + assert_eq!(Dungeon::all().len(), 8); + } + + #[test] + fn all_paths() { + assert_eq!(Path::all().len(), 33); + } + + #[test] + fn from_id() { + assert_eq!(Path::from_id("coe_story").unwrap().id(), "coe_story"); + assert!(Path::from_id("bad_id").is_none()); + } + + #[test] + fn from_dungeon_frequenter_index() { + assert_eq!( + Path::from_dungeon_frequenter_index(5) + .unwrap() + .dungeon_frequenter_index(), + Some(5) + ); + assert!(Path::from_dungeon_frequenter_index(100).is_none()); + } + + #[test] + fn ac_story_rewards() { + let path = Path::from_id("ac_story").unwrap(); + + assert_eq!(13_00, path.coins()); + assert_eq!(13_00, path.repeat_coins()); + + assert_eq!(0, path.tokens()); + assert_eq!(0, path.repeat_tokens()); + } + + #[test] + fn ac_p1_rewards() { + let path = Path::from_id("hodgins").unwrap(); + + assert_eq!(50_00 + 26_00, path.coins()); + assert_eq!(26_00, path.repeat_coins()); + + assert_eq!(100, path.tokens()); + assert_eq!(20, path.repeat_tokens()); + } +} diff --git a/src/dungeon/info.rs b/src/dungeon/info.rs new file mode 100644 index 0000000..1c3ee40 --- /dev/null +++ b/src/dungeon/info.rs @@ -0,0 +1,378 @@ +//! Contains information about dungeons that are not avaliable from the GW2 API. +//! This information would have to be updated if dungeon rewards are reworked +//! or if more dungeons are added. +//! +//! TODO: The amount of gold rewards are known for some story paths. +//! The missing story paths are currently using 0. + +/// Information about a dungeon. +#[derive(Debug, PartialEq, Eq)] +pub struct DungeonInfo { + /// The id used by the /v2/dungeons endpoint. + pub id: &'static str, + /// A user friendly short name. + pub short_name: &'static str, + /// A user friendly long name. + pub long_name: &'static str, + /// id of the achievement for collection all skins. + pub collection_id: u32, + /// id of the token used by the /v2/account/currencies endpoint. + pub currency_id: u32, + /// information about every path in this dungeon. + pub paths: &'static [PathInfo], +} + +/// Information about a path within a dungeon. +#[derive(Debug, PartialEq, Eq)] +pub struct PathInfo { + /// The id used by the /v2/account/dungeons endpoint to indicate + /// if a user has done this path today. + pub id: &'static str, + /// A user friendly short name. + pub short_name: &'static str, + /// A longer user friendly name. + pub long_name: &'static str, + /// The index inserted into the bits array for the dungeon + /// frequenter achievement when this path is done. + pub dungeon_frequenter_index: Option, + /// The rewards for doing this path + pub rewards: Rewards, +} + +/// Describes the rewards for a path. +#[derive(Debug, PartialEq, Eq)] +pub enum Rewards { + /// Story mission. No token rewards. Fixed coin reward. + Story { coins: u32 }, + /// Explorable path. 100 tokens on first per day, 20 on repeat. + /// bonus_coins + 26s on first per day. 26s on repeat. + Explorable { bonus_coins: u32 }, +} + +static AC_PATHS: [PathInfo; 4] = [ + PathInfo { + id: "ac_story", + short_name: "story", + long_name: "Story", + dungeon_frequenter_index: Some(4), + rewards: Rewards::Story { coins: 13_00 }, + }, + PathInfo { + id: "hodgins", + short_name: "p1", + long_name: "Hodgins (p1)", + dungeon_frequenter_index: Some(5), + rewards: Rewards::Explorable { bonus_coins: 50_00 }, + }, + PathInfo { + id: "detha", + short_name: "p2", + long_name: "Detha (p2)", + dungeon_frequenter_index: Some(6), + rewards: Rewards::Explorable { bonus_coins: 50_00 }, + }, + PathInfo { + id: "tzark", + short_name: "p3", + long_name: "Tzark (p3)", + dungeon_frequenter_index: Some(7), + rewards: Rewards::Explorable { bonus_coins: 50_00 }, + }, +]; + +static CM_PATHS: [PathInfo; 4] = [ + PathInfo { + id: "cm_story", + short_name: "story", + long_name: "Story", + dungeon_frequenter_index: Some(12), + rewards: Rewards::Story { coins: 0 }, + }, + PathInfo { + id: "asura", + short_name: "p1", + long_name: "Asura (p1)", + dungeon_frequenter_index: Some(13), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "seraph", + short_name: "p2", + long_name: "Seraph (p2)", + dungeon_frequenter_index: Some(14), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "butler", + short_name: "p3", + long_name: "Butler (p3)", + dungeon_frequenter_index: Some(15), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, +]; + +static TA_PATHS: [PathInfo; 4] = [ + PathInfo { + id: "ta_story", + short_name: "story", + long_name: "Story", + dungeon_frequenter_index: Some(20), + rewards: Rewards::Story { coins: 0 }, + }, + PathInfo { + id: "leurent", + short_name: "up", + long_name: "Leurent (Up)", + dungeon_frequenter_index: Some(21), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "vevina", + short_name: "forward", + long_name: "Vevina (Forward)", + dungeon_frequenter_index: Some(22), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "aetherpath", + short_name: "aetherpath", + long_name: "Aetherpath", + dungeon_frequenter_index: Some(23), + rewards: Rewards::Explorable { bonus_coins: 66_00 }, + }, +]; + +static SE_PATHS: [PathInfo; 4] = [ + PathInfo { + id: "se_story", + short_name: "story", + long_name: "Story", + dungeon_frequenter_index: Some(16), + rewards: Rewards::Story { coins: 0 }, + }, + PathInfo { + id: "fergg", + short_name: "p1", + long_name: "Fergg (p1)", + dungeon_frequenter_index: Some(17), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "rasalov", + short_name: "p2", + long_name: "Rasolov (p2)", + dungeon_frequenter_index: Some(18), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "koptev", + short_name: "p3", + long_name: "Koptev (p3)", + dungeon_frequenter_index: Some(19), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, +]; + +static COF_PATHS: [PathInfo; 4] = [ + PathInfo { + id: "cof_story", + short_name: "story", + long_name: "Story", + dungeon_frequenter_index: Some(28), + rewards: Rewards::Story { coins: 0 }, + }, + PathInfo { + id: "ferrah", + short_name: "p1", + long_name: "Ferrah (p1)", + dungeon_frequenter_index: Some(29), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "magg", + short_name: "p2", + long_name: "Magg (p2)", + dungeon_frequenter_index: Some(30), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "rhiannon", + short_name: "p3", + long_name: "Rhiannon (p3)", + dungeon_frequenter_index: Some(31), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, +]; + +static HOTW_PATHS: [PathInfo; 4] = [ + PathInfo { + id: "hotw_story", + short_name: "story", + long_name: "Story", + dungeon_frequenter_index: Some(24), + rewards: Rewards::Story { coins: 0 }, + }, + PathInfo { + id: "butcher", + short_name: "p1", + long_name: "Butcher (p1)", + dungeon_frequenter_index: Some(25), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "plunderer", + short_name: "p2", + long_name: "Plunderer (p2)", + dungeon_frequenter_index: Some(26), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "zealot", + short_name: "p3", + long_name: "Zealot (p3)", + dungeon_frequenter_index: Some(27), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, +]; + +static COE_PATHS: [PathInfo; 4] = [ + PathInfo { + id: "coe_story", + short_name: "story", + long_name: "Story", + dungeon_frequenter_index: Some(0), + rewards: Rewards::Story { coins: 0 }, + }, + PathInfo { + id: "submarine", + short_name: "p1", + long_name: "Submarine (p1)", + dungeon_frequenter_index: Some(1), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "teleporter", + short_name: "p2", + long_name: "Teleporter (p2)", + dungeon_frequenter_index: Some(2), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, + PathInfo { + id: "front_door", + short_name: "p3", + long_name: "Front Door (p3)", + dungeon_frequenter_index: Some(3), + rewards: Rewards::Explorable { bonus_coins: 35_00 }, + }, +]; + +static ARAH_PATHS: [PathInfo; 5] = [ + PathInfo { + id: "arah_story", + short_name: "story", + long_name: "Story", + dungeon_frequenter_index: None, + rewards: Rewards::Story { coins: 0 }, + }, + PathInfo { + id: "jotun", + short_name: "p1", + long_name: "Jotun (p1)", + dungeon_frequenter_index: Some(8), + rewards: Rewards::Explorable { + bonus_coins: 1_80_00, + }, + }, + PathInfo { + id: "mursaat", + short_name: "p2", + long_name: "Mursaat (p2)", + dungeon_frequenter_index: Some(9), + rewards: Rewards::Explorable { + bonus_coins: 1_05_00, + }, + }, + PathInfo { + id: "forgotten", + short_name: "p3", + long_name: "Forgotten (p3)", + dungeon_frequenter_index: Some(10), + rewards: Rewards::Explorable { bonus_coins: 50_00 }, + }, + PathInfo { + id: "seer", + short_name: "p4", + long_name: "Seer (p4)", + dungeon_frequenter_index: Some(11), + rewards: Rewards::Explorable { + bonus_coins: 1_80_00, + }, + }, +]; + +pub static DUNGEONS: [DungeonInfo; 8] = [ + DungeonInfo { + id: "ascalonian_catacombs", + short_name: "ac", + long_name: "Ascalonian Catacombs", + collection_id: 1725, + currency_id: 5, + paths: &AC_PATHS, + }, + DungeonInfo { + id: "caudecus_manor", + currency_id: 9, + short_name: "cm", + long_name: "Caudecus's Manor", + collection_id: 1723, + paths: &CM_PATHS, + }, + DungeonInfo { + id: "twilight_arbor", + short_name: "ta", + long_name: "Twilight Arbor", + collection_id: 1721, + currency_id: 11, + paths: &TA_PATHS, + }, + DungeonInfo { + id: "sorrows_embrace", + short_name: "se", + long_name: "Sorrow's Embrace", + collection_id: 1722, + currency_id: 10, + paths: &SE_PATHS, + }, + DungeonInfo { + id: "citadel_of_flame", + short_name: "cof", + long_name: "Citadel of Flame", + collection_id: 1714, + currency_id: 13, + paths: &COF_PATHS, + }, + DungeonInfo { + id: "honor_of_the_waves", + short_name: "hotw", + long_name: "Honor of the Waves", + collection_id: 1718, + currency_id: 12, + paths: &HOTW_PATHS, + }, + DungeonInfo { + id: "crucible_of_eternity", + short_name: "coe", + long_name: "Crucible of Eternity", + collection_id: 1719, + currency_id: 14, + paths: &COE_PATHS, + }, + DungeonInfo { + id: "ruined_city_of_arah", + short_name: "arah", + long_name: "The Ruined City of Arah", + collection_id: 1724, + currency_id: 6, + paths: &ARAH_PATHS, + }, +]; diff --git a/src/lib.rs b/src/lib.rs index e69de29..5a9eb43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod dungeon; From be3b1879ac39c7f2db51ec7241e7d279abdccde4 Mon Sep 17 00:00:00 2001 From: alexFickle Date: Tue, 5 Jan 2021 23:17:22 -0600 Subject: [PATCH 2/7] Add dungeon::UserProgress to track dungeons ran, API calls stubbed out --- src/dungeon.rs | 3 + src/dungeon/user.rs | 141 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 src/dungeon/user.rs diff --git a/src/dungeon.rs b/src/dungeon.rs index 931222d..38dcf69 100644 --- a/src/dungeon.rs +++ b/src/dungeon.rs @@ -1,6 +1,9 @@ mod info; use info::{DungeonInfo, PathInfo, Rewards, DUNGEONS}; +mod user; +pub use user::UserProgress; + /// Information about a dungeon. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Dungeon { diff --git a/src/dungeon/user.rs b/src/dungeon/user.rs new file mode 100644 index 0000000..fefb480 --- /dev/null +++ b/src/dungeon/user.rs @@ -0,0 +1,141 @@ +use super::Path; +use std::collections::HashSet; + +/// Tracks what dungeons a user has ran since the last daily reset +/// and from the last Dungeon Frequenter achievement completion. +pub struct UserProgress { + dungeon_frequenter_progress: HashSet, + dungeons_ran_today: HashSet, +} + +impl UserProgress { + /// Fetches a user's dungeon progress from the GW2 API. + pub fn from_api_key(_key: &str) -> Self { + todo!() + } + + /// Modifies a player info as if the player ran a dungeon path. + pub fn run_path(&mut self, path: &Path) { + // handle dungeon frequenter achievement update + if let Some(index) = path.dungeon_frequenter_index() { + self.dungeon_frequenter_progress.insert(index); + if self.dungeon_frequenter_progress.len() == 8 { + self.dungeon_frequenter_progress.clear(); + } + } + + // handle daily update + self.dungeons_ran_today.insert(path.id().to_owned()); + } + + /// Modifies a player info as if daily reset happened. + pub fn daily_reset(&mut self) { + self.dungeons_ran_today.clear(); + } + + /// Gets if a dungeon path has been ran today. + pub fn has_ran_today(&self, path: &Path) -> bool { + self.dungeons_ran_today.contains(path.id()) + } + + /// Gets if a dungeon path gives dungeon frequenter credit if it was + /// ran next. + pub fn gives_dungeon_frequenter_credit(&self, path: &Path) -> bool { + match path.dungeon_frequenter_index() { + Some(index) => !self.dungeon_frequenter_progress.contains(&index), + None => false, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + /// Create a new player info with no paths ever ran for testing. + fn empty() -> UserProgress { + UserProgress { + dungeon_frequenter_progress: Default::default(), + dungeons_ran_today: Default::default(), + } + } + + #[test] + fn ran_none() { + let info = empty(); + for path in Path::all() { + // all paths that can give progress do + assert_eq!( + path.dungeon_frequenter_index().is_some(), + info.gives_dungeon_frequenter_credit(&path), + "path: {}", + path.id() + ); + // not ran any paths today + assert!(!info.has_ran_today(&path), "path: {}", path.id()); + } + } + + #[test] + fn ran_one() { + for path in Path::all() { + let mut info = empty(); + info.run_path(&path); + + assert!( + !info.gives_dungeon_frequenter_credit(&path), + "path: {}", + path.id() + ); + assert!(info.has_ran_today(&path), "path: {}", path.id()); + if path.dungeon_frequenter_index().is_some() { + assert_eq!(1, info.dungeon_frequenter_progress.len()); + } else { + assert_eq!(0, info.dungeon_frequenter_progress.len()); + } + } + } + + #[test] + fn ran_one_yesterday() { + let mut info = empty(); + let ran_path = Path::from_id("ac_story").unwrap(); + info.run_path(&ran_path); + info.daily_reset(); + + assert!(!info.has_ran_today(&ran_path)); + assert!(!info.gives_dungeon_frequenter_credit(&ran_path)); + } + + #[test] + fn frequenter_finished() { + let mut info = empty(); + [ + "ac_story", "hodgins", "detha", "tzark", "cm_story", "asura", "seraph", "butler", + "ta_story", + ] + .iter() + .map(|id| Path::from_id(id).unwrap()) + .for_each(|path| info.run_path(&path)); + + // dungeon frequenter should have completed right before ta_story, + // so only ta_story should be in the dungeon frequenter set. + assert_eq!(1, info.dungeon_frequenter_progress.len()); + assert!(info.dungeon_frequenter_progress.contains( + &Path::from_id("ta_story") + .unwrap() + .dungeon_frequenter_index() + .unwrap() + )); + } + + #[test] + fn daily_reset() { + let mut info = empty(); + let path = Path::from_id("ac_story").unwrap(); + info.run_path(&path); + info.daily_reset(); + + assert!(!info.has_ran_today(&path)); + } +} From c21817eaf5ea0bc9ec657c3074e5fcacd7e257e3 Mon Sep 17 00:00:00 2001 From: alexFickle Date: Wed, 6 Jan 2021 20:16:16 -0600 Subject: [PATCH 3/7] Add dungeon::UserProgress::from_api_key() --- Cargo.toml | 3 +++ src/dungeon/user.rs | 47 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c66d594..97690d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +reqwest = { version = "0.11", features = ["json", "blocking"] } +serde_derive = "^1.0" +serde = "^1.0" diff --git a/src/dungeon/user.rs b/src/dungeon/user.rs index fefb480..351d827 100644 --- a/src/dungeon/user.rs +++ b/src/dungeon/user.rs @@ -1,17 +1,60 @@ use super::Path; use std::collections::HashSet; +extern crate serde_derive; +use serde_derive::Deserialize; + /// Tracks what dungeons a user has ran since the last daily reset /// and from the last Dungeon Frequenter achievement completion. +#[derive(Debug, Clone)] pub struct UserProgress { dungeon_frequenter_progress: HashSet, dungeons_ran_today: HashSet, } +#[derive(Deserialize)] +struct AchievementProgress { + id: u32, + bits: Option>, +} + +const DUNGEON_FREQUENTER_ID: u32 = 2963; + impl UserProgress { /// Fetches a user's dungeon progress from the GW2 API. - pub fn from_api_key(_key: &str) -> Self { - todo!() + /// The API key requires the progression permission. + pub fn from_api_key(key: &str) -> reqwest::Result { + let mut new = Self { + dungeon_frequenter_progress: Default::default(), + dungeons_ran_today: Default::default(), + }; + + // fetch the user's achievement progress to update the Dungeon Frequenter progress + // note: gets {"text": "requires scope progression"} on permission error with 403 error. + // could improve this error message in this case, currently gets a deserialization error. + let achievement_progress: Vec = reqwest::blocking::Client::new() + .get("https://api.guildwars2.com/v2/account/achievements") + .header("Authorization", "Bearer ".to_owned() + key) + .send()? + .json()?; + for progress in achievement_progress { + if progress.id == DUNGEON_FREQUENTER_ID { + if let Some(bits) = progress.bits { + new.dungeon_frequenter_progress.extend(bits.into_iter()); + } + } + } + + // fetch the dungeons that have been ran today + let dungeons_ran_today: Vec = reqwest::blocking::Client::new() + .get("https://api.guildwars2.com/v2/account/dungeons") + .header("Authorization", "Bearer ".to_owned() + key) + .send()? + .json()?; + new.dungeons_ran_today + .extend(dungeons_ran_today.into_iter()); + + Ok(new) } /// Modifies a player info as if the player ran a dungeon path. From 5dfbe6a89b3c12e732f968ca45844b34c63b7490 Mon Sep 17 00:00:00 2001 From: alexFickle Date: Thu, 7 Jan 2021 23:30:04 -0600 Subject: [PATCH 4/7] Add and improve comments in dungeon module --- src/dungeon/info.rs | 6 +++++- src/dungeon/user.rs | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dungeon/info.rs b/src/dungeon/info.rs index 1c3ee40..006ef8f 100644 --- a/src/dungeon/info.rs +++ b/src/dungeon/info.rs @@ -1,4 +1,4 @@ -//! Contains information about dungeons that are not avaliable from the GW2 API. +//! Contains information about dungeons that are not available from the GW2 API. //! This information would have to be updated if dungeon rewards are reworked //! or if more dungeons are added. //! @@ -34,6 +34,8 @@ pub struct PathInfo { pub long_name: &'static str, /// The index inserted into the bits array for the dungeon /// frequenter achievement when this path is done. + /// Is optional because some dungeons do not give dungeon + /// frequenter progress (currently only Arah story does not). pub dungeon_frequenter_index: Option, /// The rewards for doing this path pub rewards: Rewards, @@ -158,6 +160,8 @@ static SE_PATHS: [PathInfo; 4] = [ rewards: Rewards::Explorable { bonus_coins: 35_00 }, }, PathInfo { + // NOTE: The id is misspelled in the GW2 API. + // The spelling difference between the id and long_name is intentional. id: "rasalov", short_name: "p2", long_name: "Rasolov (p2)", diff --git a/src/dungeon/user.rs b/src/dungeon/user.rs index 351d827..e49b305 100644 --- a/src/dungeon/user.rs +++ b/src/dungeon/user.rs @@ -18,6 +18,7 @@ struct AchievementProgress { bits: Option>, } +/// The achievement id of the Dungeon Frequenter achievement. const DUNGEON_FREQUENTER_ID: u32 = 2963; impl UserProgress { From f0482dafcbd4e99c0b2fefc59df7ead0f5e29384 Mon Sep 17 00:00:00 2001 From: alexFickle Date: Thu, 7 Jan 2021 23:51:42 -0600 Subject: [PATCH 5/7] Add Coins addition --- Cargo.toml | 1 + src/coins.rs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 12f912b..0961e96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ edition = "2018" reqwest = { version = "0.11", features = ["json", "blocking"] } serde_derive = "^1.0" serde = "^1.0" +derive_more = "0.99.11" diff --git a/src/coins.rs b/src/coins.rs index 567496a..a31daad 100644 --- a/src/coins.rs +++ b/src/coins.rs @@ -1,3 +1,6 @@ +extern crate derive_more; +use derive_more::Add; + /// Represents a monetary amount. /// /// The inner value is the total number of copper coins. @@ -6,7 +9,7 @@ /// one silver coin and every 100 silver coins is one gold coin. /// /// The Display trait uses this in game format. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Add)] pub struct Coins(i32); impl Coins { From a5642fb3ea88a2b2857629d6b35351b53534a792 Mon Sep 17 00:00:00 2001 From: alexFickle Date: Thu, 7 Jan 2021 23:53:41 -0600 Subject: [PATCH 6/7] Change Coins::from_*() to const functions --- src/coins.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coins.rs b/src/coins.rs index a31daad..0a3463e 100644 --- a/src/coins.rs +++ b/src/coins.rs @@ -19,8 +19,8 @@ impl Coins { } /// Creates an amount of currency from a number of gold coins. - pub fn from_gold(gold: impl Into) -> Self { - Coins(gold.into() * 1_00_00) + pub const fn from_gold(gold: i32) -> Self { + Coins(gold * 1_00_00) } /// The number of silver coins. @@ -29,8 +29,8 @@ impl Coins { } /// Creates an amount of currency from a number of silver coins. - pub fn from_silver(silver: impl Into) -> Self { - Coins(silver.into() * 1_00) + pub const fn from_silver(silver: i32) -> Self { + Coins(silver * 1_00) } /// The number of copper coins. @@ -39,8 +39,8 @@ impl Coins { } /// Creates an amount of currency from a number of copper coins. - pub fn from_copper(copper: impl Into) -> Self { - Coins(copper.into()) + pub const fn from_copper(copper: i32) -> Self { + Coins(copper) } } From c0d6ddaa553e653bfca37da895b3a3feb34b75af Mon Sep 17 00:00:00 2001 From: alexFickle Date: Thu, 7 Jan 2021 23:55:23 -0600 Subject: [PATCH 7/7] Change dungeon module to use Coins instead of u32 --- src/dungeon.rs | 23 +++++--- src/dungeon/info.rs | 132 ++++++++++++++++++++++++++++++++------------ 2 files changed, 111 insertions(+), 44 deletions(-) diff --git a/src/dungeon.rs b/src/dungeon.rs index 38dcf69..26307f5 100644 --- a/src/dungeon.rs +++ b/src/dungeon.rs @@ -4,6 +4,8 @@ use info::{DungeonInfo, PathInfo, Rewards, DUNGEONS}; mod user; pub use user::UserProgress; +use crate::Coins; + /// Information about a dungeon. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Dungeon { @@ -110,18 +112,18 @@ impl Path { /// The number of coins gotten by doing this dungeon path the first /// time in a day. - pub fn coins(&self) -> u32 { + pub fn coins(&self) -> Coins { match self.info.rewards { Rewards::Story { coins } => coins, - Rewards::Explorable { bonus_coins } => 26_00 + bonus_coins, + Rewards::Explorable { bonus_coins } => Coins::from_silver(26) + bonus_coins, } } /// The number of coins gotten by doing this dungeon repeatedly in a day. - pub fn repeat_coins(&self) -> u32 { + pub fn repeat_coins(&self) -> Coins { match self.info.rewards { Rewards::Story { coins } => coins, - Rewards::Explorable { .. } => 26_00, + Rewards::Explorable { .. } => Coins::from_silver(26), } } @@ -147,7 +149,7 @@ impl Path { /// NOTE: Many tests have hard coded constants that must be changed if dungeon rewards /// are reworked or more dungeons are added. mod test { - use super::{Dungeon, Path}; + use super::*; #[test] fn all_dungeons() { @@ -180,8 +182,8 @@ mod test { fn ac_story_rewards() { let path = Path::from_id("ac_story").unwrap(); - assert_eq!(13_00, path.coins()); - assert_eq!(13_00, path.repeat_coins()); + assert_eq!(Coins::from_silver(13), path.coins()); + assert_eq!(Coins::from_silver(13), path.repeat_coins()); assert_eq!(0, path.tokens()); assert_eq!(0, path.repeat_tokens()); @@ -191,8 +193,11 @@ mod test { fn ac_p1_rewards() { let path = Path::from_id("hodgins").unwrap(); - assert_eq!(50_00 + 26_00, path.coins()); - assert_eq!(26_00, path.repeat_coins()); + assert_eq!( + Coins::from_silver(50) + Coins::from_silver(26), + path.coins() + ); + assert_eq!(Coins::from_silver(26), path.repeat_coins()); assert_eq!(100, path.tokens()); assert_eq!(20, path.repeat_tokens()); diff --git a/src/dungeon/info.rs b/src/dungeon/info.rs index 006ef8f..3a4abc2 100644 --- a/src/dungeon/info.rs +++ b/src/dungeon/info.rs @@ -5,6 +5,8 @@ //! TODO: The amount of gold rewards are known for some story paths. //! The missing story paths are currently using 0. +use crate::Coins; + /// Information about a dungeon. #[derive(Debug, PartialEq, Eq)] pub struct DungeonInfo { @@ -45,10 +47,10 @@ pub struct PathInfo { #[derive(Debug, PartialEq, Eq)] pub enum Rewards { /// Story mission. No token rewards. Fixed coin reward. - Story { coins: u32 }, + Story { coins: Coins }, /// Explorable path. 100 tokens on first per day, 20 on repeat. /// bonus_coins + 26s on first per day. 26s on repeat. - Explorable { bonus_coins: u32 }, + Explorable { bonus_coins: Coins }, } static AC_PATHS: [PathInfo; 4] = [ @@ -57,28 +59,36 @@ static AC_PATHS: [PathInfo; 4] = [ short_name: "story", long_name: "Story", dungeon_frequenter_index: Some(4), - rewards: Rewards::Story { coins: 13_00 }, + rewards: Rewards::Story { + coins: Coins::from_silver(13), + }, }, PathInfo { id: "hodgins", short_name: "p1", long_name: "Hodgins (p1)", dungeon_frequenter_index: Some(5), - rewards: Rewards::Explorable { bonus_coins: 50_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(50), + }, }, PathInfo { id: "detha", short_name: "p2", long_name: "Detha (p2)", dungeon_frequenter_index: Some(6), - rewards: Rewards::Explorable { bonus_coins: 50_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(50), + }, }, PathInfo { id: "tzark", short_name: "p3", long_name: "Tzark (p3)", dungeon_frequenter_index: Some(7), - rewards: Rewards::Explorable { bonus_coins: 50_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(50), + }, }, ]; @@ -88,28 +98,36 @@ static CM_PATHS: [PathInfo; 4] = [ short_name: "story", long_name: "Story", dungeon_frequenter_index: Some(12), - rewards: Rewards::Story { coins: 0 }, + rewards: Rewards::Story { + coins: Coins::from_silver(0), + }, }, PathInfo { id: "asura", short_name: "p1", long_name: "Asura (p1)", dungeon_frequenter_index: Some(13), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "seraph", short_name: "p2", long_name: "Seraph (p2)", dungeon_frequenter_index: Some(14), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "butler", short_name: "p3", long_name: "Butler (p3)", dungeon_frequenter_index: Some(15), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, ]; @@ -119,28 +137,36 @@ static TA_PATHS: [PathInfo; 4] = [ short_name: "story", long_name: "Story", dungeon_frequenter_index: Some(20), - rewards: Rewards::Story { coins: 0 }, + rewards: Rewards::Story { + coins: Coins::from_silver(0), + }, }, PathInfo { id: "leurent", short_name: "up", long_name: "Leurent (Up)", dungeon_frequenter_index: Some(21), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "vevina", short_name: "forward", long_name: "Vevina (Forward)", dungeon_frequenter_index: Some(22), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "aetherpath", short_name: "aetherpath", long_name: "Aetherpath", dungeon_frequenter_index: Some(23), - rewards: Rewards::Explorable { bonus_coins: 66_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(66), + }, }, ]; @@ -150,14 +176,18 @@ static SE_PATHS: [PathInfo; 4] = [ short_name: "story", long_name: "Story", dungeon_frequenter_index: Some(16), - rewards: Rewards::Story { coins: 0 }, + rewards: Rewards::Story { + coins: Coins::from_silver(0), + }, }, PathInfo { id: "fergg", short_name: "p1", long_name: "Fergg (p1)", dungeon_frequenter_index: Some(17), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { // NOTE: The id is misspelled in the GW2 API. @@ -166,14 +196,18 @@ static SE_PATHS: [PathInfo; 4] = [ short_name: "p2", long_name: "Rasolov (p2)", dungeon_frequenter_index: Some(18), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "koptev", short_name: "p3", long_name: "Koptev (p3)", dungeon_frequenter_index: Some(19), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, ]; @@ -183,28 +217,36 @@ static COF_PATHS: [PathInfo; 4] = [ short_name: "story", long_name: "Story", dungeon_frequenter_index: Some(28), - rewards: Rewards::Story { coins: 0 }, + rewards: Rewards::Story { + coins: Coins::from_silver(0), + }, }, PathInfo { id: "ferrah", short_name: "p1", long_name: "Ferrah (p1)", dungeon_frequenter_index: Some(29), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "magg", short_name: "p2", long_name: "Magg (p2)", dungeon_frequenter_index: Some(30), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "rhiannon", short_name: "p3", long_name: "Rhiannon (p3)", dungeon_frequenter_index: Some(31), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, ]; @@ -214,28 +256,36 @@ static HOTW_PATHS: [PathInfo; 4] = [ short_name: "story", long_name: "Story", dungeon_frequenter_index: Some(24), - rewards: Rewards::Story { coins: 0 }, + rewards: Rewards::Story { + coins: Coins::from_silver(0), + }, }, PathInfo { id: "butcher", short_name: "p1", long_name: "Butcher (p1)", dungeon_frequenter_index: Some(25), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "plunderer", short_name: "p2", long_name: "Plunderer (p2)", dungeon_frequenter_index: Some(26), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "zealot", short_name: "p3", long_name: "Zealot (p3)", dungeon_frequenter_index: Some(27), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, ]; @@ -245,28 +295,36 @@ static COE_PATHS: [PathInfo; 4] = [ short_name: "story", long_name: "Story", dungeon_frequenter_index: Some(0), - rewards: Rewards::Story { coins: 0 }, + rewards: Rewards::Story { + coins: Coins::from_silver(0), + }, }, PathInfo { id: "submarine", short_name: "p1", long_name: "Submarine (p1)", dungeon_frequenter_index: Some(1), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "teleporter", short_name: "p2", long_name: "Teleporter (p2)", dungeon_frequenter_index: Some(2), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, PathInfo { id: "front_door", short_name: "p3", long_name: "Front Door (p3)", dungeon_frequenter_index: Some(3), - rewards: Rewards::Explorable { bonus_coins: 35_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(35), + }, }, ]; @@ -276,7 +334,9 @@ static ARAH_PATHS: [PathInfo; 5] = [ short_name: "story", long_name: "Story", dungeon_frequenter_index: None, - rewards: Rewards::Story { coins: 0 }, + rewards: Rewards::Story { + coins: Coins::from_silver(0), + }, }, PathInfo { id: "jotun", @@ -284,7 +344,7 @@ static ARAH_PATHS: [PathInfo; 5] = [ long_name: "Jotun (p1)", dungeon_frequenter_index: Some(8), rewards: Rewards::Explorable { - bonus_coins: 1_80_00, + bonus_coins: Coins::from_silver(180), }, }, PathInfo { @@ -293,7 +353,7 @@ static ARAH_PATHS: [PathInfo; 5] = [ long_name: "Mursaat (p2)", dungeon_frequenter_index: Some(9), rewards: Rewards::Explorable { - bonus_coins: 1_05_00, + bonus_coins: Coins::from_silver(105), }, }, PathInfo { @@ -301,7 +361,9 @@ static ARAH_PATHS: [PathInfo; 5] = [ short_name: "p3", long_name: "Forgotten (p3)", dungeon_frequenter_index: Some(10), - rewards: Rewards::Explorable { bonus_coins: 50_00 }, + rewards: Rewards::Explorable { + bonus_coins: Coins::from_silver(50), + }, }, PathInfo { id: "seer", @@ -309,7 +371,7 @@ static ARAH_PATHS: [PathInfo; 5] = [ long_name: "Seer (p4)", dungeon_frequenter_index: Some(11), rewards: Rewards::Explorable { - bonus_coins: 1_80_00, + bonus_coins: Coins::from_silver(180), }, }, ];