{{ const dotUtils = require('./dotUtils.js'); const champions = require('./.champion.json') .filter(({ id }) => id > 0) .sortBy(({ name }) => name); const constName = name => dotUtils.changeCase.constantCase(name).replace(/[^_A-Z0-9]+/g, ''); const constNamePad = 12; const hashFactor = 256; const strHash = (str) => { let h = 0; for (const c of str) h = hashFactor * h + c.charCodeAt(0); return h; }; }}{{= dotUtils.preamble() }} newtype_enum! { /// A League of Legends champion. /// /// This newtype acts as a C-like enum; each variant corresponds to an /// integer value. Using a newtype allows _unknown_ variants to be /// represented. This is important when Riot adds new champions. /// /// Field | Name | Identifier | Id /// ---|---|---|--- {{ for (const { id, alias, name } of champions) { }} /// `{{= constName(name) }}` | "{{= name }}" | "{{= alias }}" | {{= id }} {{ } }} #[derive(serde::Serialize, serde::Deserialize)] #[serde(transparent)] pub newtype_enum Champion(i16) { {{ for (const { id, alias, name } of champions) { }} /// `{{= id }}`. {{= constName(name) }} = {{= id }}, {{ } }} } } impl Champion { /// The champion's name (`en_US` localization). pub const fn name(self) -> Option<&'static str> { match self { {{ for (const { name } of champions) { }} Self::{{= constName(name).padEnd(constNamePad) }} => Some("{{= name }}"), {{ } }} _ => None, } } /// The champion's identifier key. Somtimes called "key", "identifier", or "alias". /// This is mainly used in DDragon paths. /// /// This is generally the `en_US` name with spaces and punctuation removed, /// capitalization preserved, however the follow are exceptions: /// /// Field | Name | Identifier | Id /// ---|---|---|--- {{ for (const { id, alias, name } of champions) { if (name.replace(/[^a-zA-Z0-9]+/, '') !== alias) { }} /// `{{= constName(name) }}` | "{{= name }}" | "{{= alias }}" | {{= id }} {{ } } }} pub const fn identifier(self) -> Option<&'static str> { match self { {{ for (const { name, alias } of champions) { }} Self::{{= constName(name).padEnd(constNamePad) }} => Some("{{= alias }}"), {{ } }} _ => None, } } /// https://github.com/MingweiSamuel/Riven/issues/36 pub(crate) fn serialize_result( val: &Result, serializer: S, ) -> Result where S: serde::ser::Serializer, { use serde::ser::Serialize; val.unwrap_or(Champion(-1)).serialize(serializer) } /// https://github.com/MingweiSamuel/Riven/issues/36 pub(crate) fn deserialize_result<'de, D>( deserializer: D, ) -> Result, D::Error> where D: serde::de::Deserializer<'de>, { use std::convert::TryInto; ::deserialize(deserializer).map(|id| id.try_into().map(Self)) } } impl std::str::FromStr for Champion { type Err = (); fn from_str(s: &str) -> Result { match s.chars() .take(4) .filter(|c| c.is_ascii_alphanumeric()) .fold(0_u32, |hash, next| hash * {{= hashFactor }} + u32::from(next)) { {{ const keyStrings = (name, alias) => new Set([].concat(...[ name, alias ].map(s => s.toUpperCase()) .map(s => [ s.replace(/[^A-Z0-9]+/, '').substring(0, 4), s.split(/[^A-Z0-9]/, 1)[0].substring(0, 4), s.split(/[^A-Z]/, 1)[0].substring(0, 4), ]))); for (const { id, alias, name } of champions) { for (const prefix of keyStrings(name, alias)) { }} 0x{{= strHash(prefix).toString(16).padEnd(8) }} /* {{= prefix.padEnd(4) }} */ => Ok(Champion::{{= constName(name) }}), {{ } } }} _ => Err(()), } } } impl std::convert::TryFrom<&str> for Champion { type Error = ::Err; fn try_from(value: &str) -> Result { ::from_str(value) } }