From 9f4a4eaa06f3d0136de9088c0e60a0c077248c91 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Fri, 20 Dec 2019 18:04:24 +0100 Subject: remove hard coded palette IDs Now that the API actually returns the proper palette IDs, we can use those values instead of relying on the hard coded values. This also gets rid of the make_table script that was mostly hackish anyway, and the lazy static HashMap. --- src/api/mod.rs | 12 +++++- src/api/professions.rs | 51 ++++++++++++++++++++++- src/bt.rs | 111 +++++++++++++------------------------------------ src/main.rs | 16 +++---- src/output.rs | 2 +- src/skill_palette.json | 1 - 6 files changed, 97 insertions(+), 96 deletions(-) delete mode 100644 src/skill_palette.json (limited to 'src') diff --git a/src/api/mod.rs b/src/api/mod.rs index ffed5ea..c4aa1f7 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -113,7 +113,9 @@ impl Api { /// Combines the given endpoint with the `base_url` of this API. fn make_url(&self, endpoint: &str) -> Url { - self.base_url.join(endpoint).expect("Invalid API endpoint") + let mut result = self.base_url.join(endpoint).expect("Invalid API endpoint"); + result.set_query(Some("v=2019-19-12T0:00")); + result } /// Get and deserialize a cached value. @@ -208,6 +210,14 @@ impl Api { self.get_multiple_cached("professions", "professions/", ids) } + /// Retrieve all available professions. + /// + /// This is a shortcut around `get_profession_ids` and `get_professions`. + pub fn get_all_professions(&mut self) -> Result, ApiError> { + let ids = self.get_profession_ids()?; + self.get_professions(&ids) + } + /// Retrieve detailed information about the given skills. /// /// Skills that are found in the cache are taken from there. diff --git a/src/api/professions.rs b/src/api/professions.rs index 2716a1a..f3d1f94 100644 --- a/src/api/professions.rs +++ b/src/api/professions.rs @@ -4,7 +4,7 @@ //! * [Wiki](https://wiki.guildwars2.com/wiki/API:2/professions) use super::HasId; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Profession { @@ -12,10 +12,32 @@ pub struct Profession { pub id: String, /// The name of the profession. pub name: String, + /// The numeric code of the profession, e.g. for chat links. + pub code: u32, /// List of specialization ids. pub specializations: Vec, /// List of skills. pub skills: Vec, + /// Conversion of palette ID to skill ID. + pub skills_by_palette: Vec, +} + +impl Profession { + /// Resolves a given palette ID to the corresponding skill ID. + pub fn palette_id_to_skill_id(&self, palette_id: u32) -> Option { + self.skills_by_palette + .iter() + .find(|entry| entry.palette_id == palette_id) + .map(|entry| entry.skill_id) + } + + /// Resolves a given skill ID to the corresponding palette ID. + pub fn skill_id_to_palette_id(&self, skill_id: u32) -> Option { + self.skills_by_palette + .iter() + .find(|entry| entry.skill_id == skill_id) + .map(|entry| entry.palette_id) + } } impl HasId for Profession { @@ -34,3 +56,30 @@ pub struct Skill { #[serde(rename = "type")] pub typ: String, } + +#[derive(Debug, Clone)] +pub struct PaletteEntry { + /// The palette ID, as used in the chat link. + /// + /// Note that the actual palette only allows 2 bytes for this number, i.e. an `u16`. To stay + /// consistent with other integers that are handled here however, this struct uses a `u32`. + pub palette_id: u32, + /// The skill ID, as used in the API. + pub skill_id: u32, +} + +impl Serialize for PaletteEntry { + fn serialize(&self, serializer: S) -> Result { + (self.palette_id, self.skill_id).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for PaletteEntry { + fn deserialize>(deserializer: D) -> Result { + let (palette_id, skill_id) = Deserialize::deserialize(deserializer)?; + Ok(PaletteEntry { + palette_id, + skill_id, + }) + } +} diff --git a/src/bt.rs b/src/bt.rs index cf95f3c..8cda030 100644 --- a/src/bt.rs +++ b/src/bt.rs @@ -1,4 +1,4 @@ -use super::api::{Api, ApiError, Skill, Specialization}; +use super::api::{Api, ApiError, Profession, Skill, Specialization}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::{convert::TryFrom, error::Error, fmt, str::FromStr}; @@ -11,7 +11,6 @@ pub enum ChatlinkError { error_froms! { ChatlinkError, err: ApiError => ChatlinkError::ApiError(err), _err: base64::DecodeError => ChatlinkError::MalformedInput, - _err: num_enum::TryFromPrimitiveError => ChatlinkError::MalformedInput, _err: num_enum::TryFromPrimitiveError => ChatlinkError::MalformedInput, _err: num_enum::TryFromPrimitiveError => ChatlinkError::MalformedInput, } @@ -34,48 +33,6 @@ impl Error for ChatlinkError { } } -/// The profession of the template. -/// -/// Can be cast to an `u8` to get the right ID for building chat links. -#[repr(u8)] -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, IntoPrimitive, TryFromPrimitive)] -pub enum Profession { - Guardian = 1, - Warrior = 2, - Engineer = 3, - Ranger = 4, - Thief = 5, - Elementalist = 6, - Mesmer = 7, - Necromancer = 8, - Revenant = 9, -} - -impl fmt::Display for Profession { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self, f) - } -} - -impl FromStr for Profession { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "Guardian" => Ok(Profession::Guardian), - "Warrior" => Ok(Profession::Warrior), - "Engineer" => Ok(Profession::Engineer), - "Ranger" => Ok(Profession::Ranger), - "Thief" => Ok(Profession::Thief), - "Elementalist" => Ok(Profession::Elementalist), - "Mesmer" => Ok(Profession::Mesmer), - "Necromancer" => Ok(Profession::Necromancer), - "Revenant" => Ok(Profession::Revenant), - _ => Err(()), - } - } -} - /// Represents the selected trait. #[repr(u8)] #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, IntoPrimitive, TryFromPrimitive)] @@ -176,6 +133,12 @@ pub const SKILL_COUNT: usize = 5; pub const TRAITLINE_COUNT: usize = 3; pub const LEGEND_COUNT: usize = 4; +/// The code for the revenant profession. +pub const CODE_REVENANT: u32 = 9; + +pub const EMPTY_SKILLS: [Option; SKILL_COUNT] = [None, None, None, None, None]; +pub const EMPTY_TRAITLINES: [Option; TRAITLINE_COUNT] = [None,None, None]; + /// Represents a build template. /// /// This struct is made with the same limitations as the game imposes. That is, even though the @@ -201,8 +164,8 @@ impl BuildTemplate { pub fn empty(profession: Profession) -> BuildTemplate { BuildTemplate { profession, - skills: [None, None, None, None, None], - traitlines: [None, None, None], + skills: EMPTY_SKILLS, + traitlines: EMPTY_TRAITLINES, extra_data: ExtraData::None, } } @@ -239,8 +202,8 @@ impl BuildTemplate { } /// Returns the profession of this build. - pub fn profession(&self) -> Profession { - self.profession + pub fn profession(&self) -> &Profession { + &self.profession } /// Returns the skills of this build. @@ -273,7 +236,7 @@ impl BuildTemplate { /// The returned link is ready to be copy-and-pasted into Guild Wars 2. pub fn chatlink(&self) -> String { let mut bytes = vec![0x0Du8]; - let prof_byte = self.profession() as u8; + let prof_byte = self.profession().code as u8; bytes.push(prof_byte); for traitline in self.traitlines().iter() { @@ -295,7 +258,10 @@ impl BuildTemplate { bytes.push(0); } Some(s) => { - let palette_id = skill_id_to_palette_id(s.id); + let palette_id = self + .profession() + .skill_id_to_palette_id(s.id) + .unwrap_or(0); bytes.push((palette_id & 0xFF) as u8); bytes.push(((palette_id >> 8) & 0xFF) as u8); } @@ -338,9 +304,9 @@ impl BuildTemplate { } bytes.remove(0); - let profession = Profession::try_from(bytes.remove(0))?; + let profession = code_to_profession(api, bytes.remove(0) as u32)?; - let mut traitlines = BuildTemplate::empty(profession).traitlines; + let mut traitlines = EMPTY_TRAITLINES; for i in traitlines.iter_mut() { let spec_id = bytes.remove(0); let trait_choices = bytes.remove(0); @@ -355,14 +321,16 @@ impl BuildTemplate { *i = Some((spec, [c_0, c_1, c_2])); } - let mut skills = BuildTemplate::empty(profession).skills; + let mut skills = EMPTY_SKILLS; for i in skills.iter_mut() { // Terrestrial let byte_1 = bytes.remove(0); let byte_2 = bytes.remove(0); let palette_id = byte_1 as u32 | (byte_2 as u32) << 8; if palette_id != 0 { - let skill_id = palette_id_to_skill_id(palette_id); + let skill_id = profession + .palette_id_to_skill_id(palette_id) + .ok_or(ChatlinkError::MalformedInput)?; let skill = api.get_skills(&[skill_id])?.remove(0); *i = Some(skill); } @@ -372,8 +340,8 @@ impl BuildTemplate { bytes.remove(0); } - let extra_data = match profession { - Profession::Revenant => { + let extra_data = match profession.code { + CODE_REVENANT => { let mut legends = [Legend::None; LEGEND_COUNT]; for i in legends.iter_mut() { *i = Legend::try_from(bytes.remove(0))?; @@ -392,29 +360,10 @@ impl BuildTemplate { } } -lazy_static! { - static ref PALETTE_MAPPING: Vec<(u32, u32)> = - serde_json::from_str(include_str!("skill_palette.json")).unwrap(); -} - -// Those functions do linear searches, but the list only has about 400 items, which should be okay. -// If performance becomes an issue, we can always create hash tables or do a binary search, -// however, since we need both directions, we would need double the memory to keep the second map. - -fn skill_id_to_palette_id(input: u32) -> u32 { - for (skill, palette) in PALETTE_MAPPING.iter() { - if *skill == input { - return *palette; - } - } - 0 -} - -fn palette_id_to_skill_id(input: u32) -> u32 { - for (skill, palette) in PALETTE_MAPPING.iter() { - if *palette == input { - return *skill; - } - } - 0 +fn code_to_profession(api: &mut Api, code: u32) -> Result { + let professions = api.get_all_professions()?; + professions + .into_iter() + .find(|p| p.code == code) + .ok_or(ChatlinkError::MalformedInput) } diff --git a/src/main.rs b/src/main.rs index 395e683..47ccabd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,6 @@ extern crate reqwest; extern crate rusttype; extern crate termcolor; extern crate xdg; -#[macro_use] -extern crate lazy_static; use std::error::Error as StdError; use std::fmt; @@ -37,7 +35,7 @@ mod render; use clap::{App, Arg, ArgMatches}; use api::{Api, Profession, Skill}; -use bt::{BuildTemplate, ExtraData, Legend, TraitChoice, Traitline}; +use bt::{BuildTemplate, ExtraData, Legend, TraitChoice, Traitline, CODE_REVENANT}; use render::RenderError; /// The name of this application. @@ -182,10 +180,6 @@ fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult MainResult>(); - let extra_data = if prof_enum == bt::Profession::Revenant { + let extra_data = if profession.code == CODE_REVENANT { let mut array_legends = [Legend::None; 4]; for (i, l) in legends.iter().enumerate() { array_legends[i] = *l; @@ -206,7 +200,7 @@ fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult>) @@ -240,7 +234,7 @@ fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult MainResult<()> { if !matches.is_present("quiet") { println!("Image saved in {}", filename); } - }, + } Err(RenderError::EmptyBuild) => (), Err(err) => { eprintln!("Image could not be rendered:"); diff --git a/src/output.rs b/src/output.rs index 9406571..8d71909 100644 --- a/src/output.rs +++ b/src/output.rs @@ -36,7 +36,7 @@ pub fn show_build_template(build: &BuildTemplate) -> io::Result<()> { color_spec.set_fg(Some(HEADER_COLOR)); color_spec.set_bold(true); - let mut fields = vec![("Profession:", build.profession().to_string())]; + let mut fields = vec![("Profession:", build.profession().name.clone())]; fields.push(("Skills:", format_skill(&build.skills()[0]))); for skill in build.skills().iter().skip(1) { diff --git a/src/skill_palette.json b/src/skill_palette.json deleted file mode 100644 index 67ac962..0000000 --- a/src/skill_palette.json +++ /dev/null @@ -1 +0,0 @@ -[[14402, 166], [21815, 3881], [14389, 112], [14401, 167], [30189, 4850], [41100, 5959], [14409, 174], [14403, 168], [14575, 482], [14372, 106], [14412, 178], [14528, 429], [14407, 172], [14405, 170], [14408, 176], [14406, 171], [14516, 173], [14413, 179], [14392, 113], [14368, 105], [14502, 418], [14410, 175], [14404, 169], [14479, 317], [14388, 110], [14354, 10], [30258, 4823], [30074, 4828], [29613, 4769], [29941, 4804], [43123, 5671], [45380, 5904], [41919, 5738], [43745, 5750], [14419, 238], [14483, 380], [14355, 156], [30343, 4802], [45333, 5789], [9083, 127], [21664, 3878], [9102, 259], [9158, 312], [30025, 4796], [41714, 5963], [9152, 309], [9084, 301], [9085, 138], [9153, 310], [9093, 254], [9175, 329], [9248, 260], [9253, 331], [9125, 256], [9247, 327], [9246, 441], [9187, 332], [9128, 278], [9182, 330], [9242, 326], [9163, 306], [9151, 305], [9245, 376], [9168, 328], [9251, 255], [30553, 4740], [30871, 4858], [30364, 4746], [29786, 4862], [46148, 5909], [45460, 5971], [40915, 5754], [44080, 5827], [29965, 4745], [9154, 311], [30461, 4721], [30273, 4789], [43357, 5656], [26937, 4572], [29148, 4572], [28219, 4572], [27220, 4572], [27372, 4572], [45686, 4572], [27107, 4564], [29209, 4614], [28231, 4651], [26821, 4614], [27025, 4651], [27715, 4564], [27917, 4564], [27322, 4614], [27505, 4651], [26644, 4564], [28379, 4614], [27014, 4651], [26557, 4564], [28516, 4614], [26679, 4651], [41220, 4564], [42949, 4614], [40485, 4651], [28406, 4554], [27356, 4554], [28287, 4554], [27760, 4554], [27975, 4554], [45773, 4554], [21659, 3882], [5834, 276], [5857, 296], [5802, 132], [30357, 4825], [40507, 5717], [5812, 263], [5821, 257], [5860, 396], [5933, 405], [5968, 353], [5861, 350], [5862, 351], [5836, 290], [5927, 403], [5805, 134], [5837, 291], [5811, 136], [5818, 163], [5910, 397], [5912, 398], [5825, 275], [6161, 294], [5838, 292], [5904, 394], [5865, 352], [31248, 4903], [30101, 4878], [29739, 4812], [29921, 4782], [44646, 5685], [42842, 5719], [43739, 5861], [41218, 5679], [30800, 4857], [5832, 274], [5868, 393], [30815, 4739], [42009, 5616], [31914, 121], [12489, 161], [12483, 120], [21773, 3877], [31407, 4873], [44948, 5934], [12632, 183], [12631, 187], [34309, 180], [12633, 421], [12499, 190], [12497, 188], [12492, 181], [12494, 184], [12501, 193], [12550, 406], [12537, 191], [12502, 194], [12500, 427], [12542, 154], [12491, 428], [12476, 27], [12495, 185], [12493, 182], [12498, 189], [12496, 186], [31322, 4821], [31746, 4838], [31582, 4792], [30238, 4776], [45789, 5882], [45142, 5889], [45970, 5684], [40498, 5865], [12516, 237], [12580, 192], [12569, 407], [31677, 4788], [45717, 5678], [13027, 268], [13050, 133], [21778, 3876], [13021, 266], [30400, 4756], [45088, 5617], [13046, 307], [13044, 308], [13028, 269], [13093, 341], [13066, 347], [13096, 346], [13064, 344], [13057, 340], [13056, 339], [13038, 283], [13026, 267], [13035, 281], [13020, 137], [13117, 443], [13002, 88], [13062, 343], [13060, 342], [13055, 318], [13065, 345], [13037, 303], [30661, 4790], [30568, 4727], [30868, 4784], [30369, 4905], [41205, 5860], [41372, 5920], [41158, 5804], [46335, 5663], [13132, 270], [13085, 415], [13082, 40], [29516, 4846], [45508, 5693], [21656, 3879], [5507, 117], [5569, 279], [5503, 116], [29535, 4807], [44239, 5632], [5539, 336], [5635, 333], [5641, 246], [5638, 334], [5639, 335], [5535, 142], [5546, 230], [5540, 202], [5567, 261], [5624, 322], [5506, 115], [5502, 114], [5573, 285], [5734, 446], [5536, 144], [5554, 235], [5572, 284], [5571, 145], [5542, 203], [5570, 143], [30432, 4724], [30047, 4726], [30662, 4803], [29948, 4773], [40183, 5941], [44926, 5851], [45746, 5621], [44612, 5755], [5516, 151], [5666, 38], [5534, 150], [29968, 4761], [43638, 5906], [10548, 162], [21762, 3880], [10547, 155], [10527, 18], [30488, 4801], [43148, 5758], [10544, 128], [10689, 139], [10602, 304], [10606, 409], [10562, 245], [10622, 373], [10611, 367], [10612, 374], [10583, 250], [10620, 375], [10608, 364], [10685, 445], [10533, 118], [10541, 228], [10543, 302], [10589, 368], [10545, 371], [10607, 320], [10609, 372], [10546, 129], [29666, 4843], [30772, 4879], [30670, 4774], [29414, 4849], [42935, 5924], [42917, 5752], [41615, 5921], [40274, 5746], [10550, 378], [10549, 146], [10646, 149], [30105, 4867], [42355, 5984], [10176, 366], [10213, 365], [10177, 271], [21750, 3875], [30305, 4848], [40200, 5614], [10185, 280], [10200, 357], [10201, 358], [10302, 383], [10244, 388], [10237, 389], [10204, 359], [10211, 361], [10207, 360], [29578, 438], [10202, 363], [10203, 362], [10341, 390], [10267, 399], [10197, 356], [10232, 385], [10247, 386], [10236, 384], [10234, 387], [10187, 282], [30814, 4815], [30525, 4868], [29526, 4755], [29856, 4743], [41065, 5600], [45046, 5770], [42851, 5810], [43064, 5639], [10245, 410], [29519, 4845], [10311, 444], [30359, 4787], [45449, 5958], [12320, 12], [12319, 2333], [12318, 9], [12323, 14], [12324, 369], [12325, 17], [12338, 8], [12339, 33], [12337, 4], [12343, 1], [12344, 456], [12340, 3939], [12360, 210], [12361, 324], [12362, 337], [12373, 7], [12363, 338], [12367, 349], [12387, 13], [12417, 2], [12385, 152], [12403, 31], [12401, 29], [12391, 20], [12440, 21], [12453, 28], [12456, 30], [12447, 23], [12450, 25], [12457, 37]] -- cgit v1.2.3