use super::api::{Skill, Specialization}; use std::{fmt, str::FromStr}; /// 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)] 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)] pub enum TraitChoice { None = 0, Top = 1, Middle = 2, Bottom = 3, } impl FromStr for TraitChoice { type Err = (); fn from_str(s: &str) -> Result { let lower = s.to_lowercase(); match &lower as &str { "" | "none" => Ok(TraitChoice::None), "top" => Ok(TraitChoice::Top), "mid" | "middle" => Ok(TraitChoice::Middle), "bot" | "bottom" => Ok(TraitChoice::Bottom), _ => Err(()), } } } /// 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 { 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 { 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. 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. /// /// This struct is made with the same limitations as the game imposes. That is, even though the /// renderer can support more than three traitlines, this template will only allow you to take /// three. #[derive(Debug)] pub struct BuildTemplate { /// Profession of this build. profession: Profession, /// The skills of the build. /// /// Each slot can either contain a slot or be empty. A maximum of 5 skills are allowed (heal, 3 /// utilities and elite). skills: [Option; SKILL_COUNT], /// The traitlines of the build. traitlines: [Option; TRAITLINE_COUNT], /// Extra data, such as revenant legends or ranger pets. extra_data: ExtraData, } impl BuildTemplate { /// Returns a template without any skills or traitlines. pub fn empty(profession: Profession) -> BuildTemplate { BuildTemplate { profession, skills: [None, None, None, None, None], traitlines: [None, None, None], extra_data: ExtraData::None, } } /// Creates a template with the given skills and traitlines. /// /// If there are more than 5 skills or 3 traitlines given, the function will return `None`. pub fn new( profession: Profession, skills: &[Skill], traitlines: &[Traitline], extra_data: ExtraData, ) -> Option { if skills.len() > SKILL_COUNT { return None; } if traitlines.len() > TRAITLINE_COUNT { return None; } let mut skill_array = [None, None, None, None, None]; for (i, skill) in skills.iter().enumerate() { skill_array[i] = Some(skill.clone()); } let mut trait_array = [None, None, None]; for (i, traitline) in traitlines.iter().enumerate() { trait_array[i] = Some(traitline.clone()); } Some(BuildTemplate { profession, skills: skill_array, traitlines: trait_array, extra_data, }) } /// Returns the profession of this build. pub fn profession(&self) -> Profession { self.profession } /// Returns the skills of this build. pub fn skills(&self) -> &[Option] { &self.skills } /// Returns the number of actually equipped skills. pub fn skill_count(&self) -> u32 { self.skills.iter().filter(|x| x.is_some()).count() as u32 } /// Returns the traitlines of this build. pub fn traitlines(&self) -> &[Option] { &self.traitlines } /// Returns the number of actually equipped specializations. 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: &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 }