aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2019-12-20 18:04:24 +0100
committerDaniel Schadt <kingdread@gmx.de>2019-12-20 18:04:24 +0100
commit9f4a4eaa06f3d0136de9088c0e60a0c077248c91 (patch)
tree4ab0e0c9f5910643ac6404c390e6aa6fad4ca470
parent087580b3c269dbc5b2298ff6f4590f010279d339 (diff)
downloadkondou-9f4a4eaa06f3d0136de9088c0e60a0c077248c91.tar.gz
kondou-9f4a4eaa06f3d0136de9088c0e60a0c077248c91.tar.bz2
kondou-9f4a4eaa06f3d0136de9088c0e60a0c077248c91.zip
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.
-rw-r--r--Cargo.toml1
-rw-r--r--make_table.py38
-rw-r--r--src/api/mod.rs12
-rw-r--r--src/api/professions.rs51
-rw-r--r--src/bt.rs111
-rw-r--r--src/main.rs16
-rw-r--r--src/output.rs2
-rw-r--r--src/skill_palette.json1
8 files changed, 97 insertions, 135 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 89b5c10..8949851 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,4 +21,3 @@ base64 = "0.11"
termcolor = "1.0"
num_enum = "0.4"
num-traits = "0.2"
-lazy_static = "1.4"
diff --git a/make_table.py b/make_table.py
deleted file mode 100644
index 444211d..0000000
--- a/make_table.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""This script is used to downlaod the skill palette data.
-
-The Guild Wars 2 chat links for build templates use a different skill ID than
-the API, so skills need to be mapped from their "skill ID" to the corresponding
-"palette ID".
-
-Some game updates may require this mapping to be updated as well, otherwise the
-chat link parsing/generating functionality might be broken.
-
-To use this script, pipe the output to src/skill_palette.json and rebuild
-kondou.
-"""
-import requests
-import json
-from lxml import html, etree
-
-data = requests.get("https://wiki.guildwars2.com/wiki/Chat_link_format")
-parsed = html.fromstring(data.content)
-body = parsed.find(".//table")
-iterator = iter(body)
-next(iterator)
-result = []
-for row in iterator:
- if "-" in row[3].text:
- continue
- # For some reason, some skills have multiple IDs. It is not quite clear
- # what they are for, it sometimes seems to be the traited version, and
- # sometimes the underwater version. Then again, some of the IDs are also
- # invalid API IDs, which makes the whole thing a bit weird. Since we don't
- # care about underwater combat at the moment (and so does nobody else in
- # GW2) nor about the traited version of skills, we'll just stick with the
- # first ID. For rendering it shouldn't matter anyway, as all of them
- # usually share the same icon.
- ids = row[3].text.strip().split(";")
- palette_id = int(row[4].text)
- skill_id = int(ids[0])
- result.append((skill_id, palette_id))
-print(json.dumps(result))
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<Vec<Profession>, 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<u32>,
/// List of skills.
pub skills: Vec<Skill>,
+ /// Conversion of palette ID to skill ID.
+ pub skills_by_palette: Vec<PaletteEntry>,
+}
+
+impl Profession {
+ /// Resolves a given palette ID to the corresponding skill ID.
+ pub fn palette_id_to_skill_id(&self, palette_id: u32) -> Option<u32> {
+ 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<u32> {
+ 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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ (self.palette_id, self.skill_id).serialize(serializer)
+ }
+}
+
+impl<'de> Deserialize<'de> for PaletteEntry {
+ fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ 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<Profession> => ChatlinkError::MalformedInput,
_err: num_enum::TryFromPrimitiveError<TraitChoice> => ChatlinkError::MalformedInput,
_err: num_enum::TryFromPrimitiveError<Legend> => 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<Self, Self::Err> {
- 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>; SKILL_COUNT] = [None, None, None, None, None];
+pub const EMPTY_TRAITLINES: [Option<Traitline>; 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<Profession, ChatlinkError> {
+ 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<BuildTemplat
.expect("clap handles missing argument");
let profession = find_profession(api, requested_profession)?;
- let prof_enum = profession
- .name
- .parse()
- .expect("Profession object has unparseable name");
let legends = matches
.values_of("legend")
@@ -196,7 +190,7 @@ fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult<BuildTemplat
.map(Result::unwrap)
.collect::<Vec<_>>();
- 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<BuildTemplat
ExtraData::None
};
- let skills = if prof_enum != bt::Profession::Revenant {
+ let skills = if profession.code != CODE_REVENANT {
matches
.values_of("skill")
.map(Iterator::collect::<Vec<_>>)
@@ -240,7 +234,7 @@ fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult<BuildTemplat
"got too many traitlines"
);
- let build = BuildTemplate::new(prof_enum, &skills, &traitlines, extra_data)
+ let build = BuildTemplate::new(profession, &skills, &traitlines, extra_data)
.expect("BuildTemplate could not be constructed");
Ok(build)
@@ -368,7 +362,7 @@ fn run() -> 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]]