aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--make_table.py19
-rw-r--r--src/api/legends.rs23
-rw-r--r--src/api/mod.rs11
-rw-r--r--src/bt.rs129
-rw-r--r--src/main.rs79
-rw-r--r--src/skill_palette.json1
7 files changed, 248 insertions, 15 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 05dc674..679b398 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,3 +18,4 @@ xdg = "2.2.0"
quick-error = "1.2.0"
itertools = "0.8.0"
md5 = "0.7"
+base64 = "0.11"
diff --git a/make_table.py b/make_table.py
new file mode 100644
index 0000000..18a30fa
--- /dev/null
+++ b/make_table.py
@@ -0,0 +1,19 @@
+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
+ ids = row[3].text.strip().split(";")
+ palette_id = int(row[4].text)
+ for skill_id in ids:
+ skill_id = int(skill_id)
+ result.append((skill_id, palette_id))
+print(json.dumps(result))
diff --git a/src/api/legends.rs b/src/api/legends.rs
new file mode 100644
index 0000000..395d6b9
--- /dev/null
+++ b/src/api/legends.rs
@@ -0,0 +1,23 @@
+//! Struct definitions for the legends API endpoint.
+//!
+//! * [Example](https://api.guildwars2.com/v2/legends/Legend2)
+//! * [Wiki](https://wiki.guildwars2.com/wiki/API:2/legends)
+
+use super::HasId;
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Debug, Clone)]
+pub struct Legend {
+ /// The legend id.
+ pub id: String,
+ pub heal: u32,
+ pub elite: u32,
+ pub utilities: Vec<u32>,
+}
+
+impl HasId for Legend {
+ type Id = String;
+ fn get_id(&self) -> String {
+ self.id.clone()
+ }
+}
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 6e1b457..41034ec 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -3,13 +3,15 @@
//! It contains `Deserialize`able definitions for the required API responses, as well as a wrapper
//! around the HTTP library. Note that only the required fields are modelled here, which means this
//! does not provide a full-featured mapping for all API types.
+pub mod legends;
pub mod professions;
pub mod skills;
pub mod specializations;
pub mod traits;
pub use self::{
- professions::Profession, skills::Skill, specializations::Specialization, traits::Trait,
+ legends::Legend, professions::Profession, skills::Skill, specializations::Specialization,
+ traits::Trait,
};
use image::DynamicImage;
@@ -187,6 +189,13 @@ impl Api {
self.get_multiple_cached("traits", "traits/", ids)
}
+ /// Retrieve detailed information about the given legends.
+ ///
+ /// Traits that are found in the cache are taken from there.
+ pub fn get_legends(&mut self, ids: &[String]) -> Result<Vec<Legend>, ApiError> {
+ self.get_multiple_cached("legends", "legends/", ids)
+ }
+
/// Loads the image from the given URL.
///
/// This automatically caches and also decodes the resulting data.
diff --git a/src/bt.rs b/src/bt.rs
index 3485448..8bfd70a 100644
--- a/src/bt.rs
+++ b/src/bt.rs
@@ -68,6 +68,60 @@ impl FromStr for TraitChoice {
}
}
+/// Represents a revenenant legend.
+#[repr(u8)]
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
+pub enum Legend {
+ None = 0,
+ Dragon = 13,
+ Assassin = 14,
+ Dwarf = 15,
+ Demon = 16,
+ Renegade = 17,
+ Centaur = 18,
+}
+
+impl Legend {
+ pub fn get_api_id(self) -> Option<String> {
+ Some(
+ match self {
+ Legend::None => return None,
+ Legend::Dragon => "Legend1",
+ Legend::Assassin => "Legend2",
+ Legend::Dwarf => "Legend3",
+ Legend::Demon => "Legend4",
+ Legend::Renegade => "Legend5",
+ Legend::Centaur => "Legend6",
+ }
+ .to_owned(),
+ )
+ }
+}
+
+impl FromStr for Legend {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let lower = s.to_lowercase();
+ match &lower as &str {
+ "" | "none" => Ok(Legend::None),
+ "dragon" | "glint" => Ok(Legend::Dragon),
+ "assassin" | "shiro" => Ok(Legend::Assassin),
+ "dwarf" | "jalis" => Ok(Legend::Dwarf),
+ "demon" | "mallyx" => Ok(Legend::Demon),
+ "renegade" | "kalla" => Ok(Legend::Renegade),
+ "centaur" | "ventari" => Ok(Legend::Centaur),
+ _ => Err(()),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum ExtraData {
+ None,
+ Legends([Legend; 4]),
+}
+
/// Represents a traitline.
///
/// A traitline consists of the chosen specialization including 3 possible trait choices.
@@ -75,6 +129,7 @@ pub type Traitline = (Specialization, [TraitChoice; 3]);
pub const SKILL_COUNT: usize = 5;
pub const TRAITLINE_COUNT: usize = 3;
+pub const LEGEND_COUNT: usize = 4;
/// Represents a build template.
///
@@ -92,6 +147,8 @@ pub struct BuildTemplate {
skills: [Option<Skill>; SKILL_COUNT],
/// The traitlines of the build.
traitlines: [Option<Traitline>; TRAITLINE_COUNT],
+ /// Extra data, such as revenant legends or ranger pets.
+ extra_data: ExtraData,
}
impl BuildTemplate {
@@ -101,6 +158,7 @@ impl BuildTemplate {
profession,
skills: [None, None, None, None, None],
traitlines: [None, None, None],
+ extra_data: ExtraData::None,
}
}
@@ -111,6 +169,7 @@ impl BuildTemplate {
profession: Profession,
skills: &[Skill],
traitlines: &[Traitline],
+ extra_data: ExtraData,
) -> Option<BuildTemplate> {
if skills.len() > SKILL_COUNT {
return None;
@@ -130,6 +189,7 @@ impl BuildTemplate {
profession,
skills: skill_array,
traitlines: trait_array,
+ extra_data,
})
}
@@ -157,4 +217,73 @@ impl BuildTemplate {
pub fn traitline_count(&self) -> u32 {
self.traitlines.iter().filter(|x| x.is_some()).count() as u32
}
+
+ pub fn extra_data(&self) -> &ExtraData {
+ &self.extra_data
+ }
+
+ pub fn chatlink(&self) -> String {
+ let mut bytes = vec![0x0Du8];
+ let prof_byte = self.profession() as u8;
+ bytes.push(prof_byte);
+
+ for traitline in self.traitlines().iter() {
+ if let Some((spec, choices)) = traitline {
+ bytes.push(spec.id as u8);
+ let selected = choices[0] as u8 | (choices[1] as u8) << 2 | (choices[2] as u8) << 4;
+ bytes.push(selected);
+ } else {
+ bytes.push(0);
+ bytes.push(0);
+ }
+ }
+
+ for skill in self.skills() {
+ // Terrestric
+ match skill {
+ None => {
+ bytes.push(0);
+ bytes.push(0);
+ }
+ Some(s) => {
+ let palette_id = skill_id_to_palette_id(s.id);
+ bytes.push((palette_id & 0xFF) as u8);
+ bytes.push(((palette_id >> 8) & 0xFF) as u8);
+ }
+ }
+ // Aquatic
+ bytes.push(0);
+ bytes.push(0);
+ }
+
+ match *self.extra_data() {
+ ExtraData::None => {
+ bytes.extend_from_slice(&[0, 0, 0, 0]);
+ }
+ ExtraData::Legends(ref legends) => {
+ bytes.extend(legends.iter().map(|l| *l as u8));
+ }
+ }
+
+ // Weird padding, achieved by looking at other chat links.
+ for _ in 0..12 {
+ bytes.push(0);
+ }
+
+ println!("Length: {}", bytes.len());
+ println!("{:?}", bytes);
+ format!("[&{}]", base64::encode(&bytes))
+ }
+}
+
+static JSON_PALETTE: &'static str = include_str!("skill_palette.json");
+
+fn skill_id_to_palette_id(input: u32) -> u32 {
+ let lookup_table: Vec<(u32, u32)> = serde_json::from_str(JSON_PALETTE).unwrap();
+ for (skill, palette) in &lookup_table {
+ if *skill == input {
+ return *palette;
+ }
+ }
+ 0
}
diff --git a/src/main.rs b/src/main.rs
index 9ffa1eb..36d7a21 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+extern crate base64;
extern crate clap;
extern crate image;
extern crate imageproc;
@@ -20,7 +21,7 @@ mod render;
use clap::{App, Arg, ArgMatches};
use api::{Api, Profession, Skill};
-use bt::{BuildTemplate, TraitChoice, Traitline};
+use bt::{BuildTemplate, ExtraData, Legend, TraitChoice, Traitline};
const APP_NAME: &str = "kondou";
@@ -121,14 +122,51 @@ 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 skills = matches
- .values_of("skill")
+ let legends = matches
+ .values_of("legend")
.map(Iterator::collect::<Vec<_>>)
.unwrap_or_default()
.into_iter()
- .map(|s| resolve_skill(api, &profession, s))
- .collect::<Result<Vec<_>, _>>()?;
+ .map(str::parse)
+ .map(Result::unwrap)
+ .collect::<Vec<_>>();
+
+ let extra_data = if prof_enum == bt::Profession::Revenant {
+ let mut array_legends = [Legend::None; 4];
+ for (i, l) in legends.iter().enumerate() {
+ array_legends[i] = *l;
+ }
+ ExtraData::Legends(array_legends)
+ } else {
+ ExtraData::None
+ };
+
+ let skills = if prof_enum != bt::Profession::Revenant {
+ matches
+ .values_of("skill")
+ .map(Iterator::collect::<Vec<_>>)
+ .unwrap_or_default()
+ .into_iter()
+ .map(|s| resolve_skill(api, &profession, s))
+ .collect::<Result<Vec<_>, _>>()?
+ } else {
+ if let Some(l) = legends.first() {
+ let l = api.get_legends(&[l.get_api_id().unwrap()])?.remove(0);
+ let mut result = Vec::new();
+ for skill_id in (&[l.heal]).iter().chain(&l.utilities).chain(&[l.elite]) {
+ let skill = api.get_skills(&[*skill_id])?.remove(0);
+ result.push(skill);
+ }
+ result
+ } else {
+ Vec::new()
+ }
+ };
let traitlines = matches
.values_of("traitline")
@@ -144,15 +182,8 @@ fn run_searching(api: &mut Api, matches: &ArgMatches) -> MainResult<BuildTemplat
"got too many traitlines"
);
- let build = BuildTemplate::new(
- profession
- .name
- .parse()
- .expect("Profession object has unparseable name"),
- &skills,
- &traitlines,
- )
- .expect("BuildTemplate could not be constructed");
+ let build = BuildTemplate::new(prof_enum, &skills, &traitlines, extra_data)
+ .expect("BuildTemplate could not be constructed");
Ok(build)
}
@@ -176,6 +207,13 @@ fn validate_traitline_format(input: String) -> Result<(), String> {
Ok(())
}
+fn validate_legend(input: String) -> Result<(), String> {
+ input
+ .parse::<Legend>()
+ .map(|_| ())
+ .map_err(|_| "invalid legend name".to_owned())
+}
+
fn run() -> MainResult<()> {
let matches = App::new(APP_NAME)
.version("0.1")
@@ -208,6 +246,17 @@ fn run() -> MainResult<()> {
.max_values(bt::TRAITLINE_COUNT as u64),
)
.arg(
+ Arg::with_name("legend")
+ .help("Selects a revenant legend.")
+ .takes_value(true)
+ .number_of_values(1)
+ .long("legend")
+ .short("l")
+ .multiple(true)
+ .max_values(bt::LEGEND_COUNT as u64)
+ .validator(validate_legend),
+ )
+ .arg(
Arg::with_name("chatlink")
.help("Specifies a chat link to parse.")
.short("c")
@@ -231,6 +280,8 @@ fn run() -> MainResult<()> {
true => unimplemented!(),
};
+ println!("Chat code: {}", build.chatlink());
+
let mut renderer = render::Renderer::new(&mut api, Default::default());
let img = renderer.render_buildtemplate(&build).unwrap();
let filename = matches.value_of("outfile").unwrap();
diff --git a/src/skill_palette.json b/src/skill_palette.json
new file mode 100644
index 0000000..62e7200
--- /dev/null
+++ b/src/skill_palette.json
@@ -0,0 +1 @@
+[[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], [9241, 254], [9175, 329], [9248, 260], [9253, 331], [9125, 256], [9247, 327], [9246, 441], [9187, 332], [9128, 278], [9182, 330], [9242, 326], [9150, 326], [9163, 306], [9243, 306], [9151, 305], [9244, 305], [9245, 376], [9168, 328], [9251, 255], [30553, 4740], [30871, 4858], [30364, 4746], [29786, 4862], [46148, 5909], [45460, 5971], [40915, 5754], [44266, 5754], [44080, 5827], [29965, 4745], [9154, 311], [30461, 4721], [30273, 4789], [43357, 5656], [26937, 4572], [29148, 4572], [28219, 4572], [27220, 4572], [27372, 4572], [56661, 4572], [45686, 4572], [27107, 4564], [29209, 4614], [28231, 4651], [26821, 4614], [29310, 4614], [27025, 4651], [29082, 4651], [27715, 4564], [29197, 4564], [27917, 4564], [31100, 4564], [27322, 4614], [27505, 4651], [26644, 4564], [28379, 4614], [27014, 4651], [26557, 4564], [56752, 4564], [28516, 4614], [56841, 4614], [26679, 4651], [56662, 4651], [41220, 4564], [42949, 4614], [40485, 4651], [28406, 4554], [31294, 4554], [27356, 4554], [29114, 4554], [28287, 4554], [27760, 4554], [27975, 4554], [56773, 4554], [45773, 4554], [21659, 3882], [30881, 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], [6020, 134], [5837, 291], [5811, 136], [29991, 136], [5818, 163], [5910, 397], [29522, 397], [5912, 398], [5825, 275], [30828, 275], [6161, 294], [30337, 294], [5838, 292], [5904, 394], [5865, 352], [29591, 352], [31248, 4903], [30101, 4878], [29739, 4812], [29921, 4782], [44646, 5685], [42842, 5719], [43739, 5861], [41218, 5679], [30800, 4857], [5832, 274], [20451, 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], [5703, 246], [5638, 334], [22572, 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], [15795, 235], [5572, 284], [5571, 145], [5542, 203], [5570, 143], [30432, 4724], [30047, 4726], [30662, 4803], [29948, 4773], [29844, 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], [10670, 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], [10671, 371], [10607, 320], [10672, 320], [10609, 372], [10673, 372], [10546, 129], [10674, 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], [10377, 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]]