physis/
race.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use binrw::binrw;
5
6use crate::Error;
7
8/// The playable genders in the game.
9#[binrw]
10#[brw(repr = u8)]
11#[derive(PartialEq, Eq, Clone, Debug)]
12#[repr(u8)]
13pub enum Gender {
14    Male = 0,
15    Female = 1,
16}
17
18impl std::convert::TryFrom<u8> for Gender {
19    type Error = crate::Error;
20
21    fn try_from(value: u8) -> Result<Self, Error> {
22        match value {
23            0 => Ok(Self::Male),
24            1 => Ok(Self::Female),
25            _ => Err(Error {}),
26        }
27    }
28}
29
30/// The playable tribes in the game.
31/// Each race has two similar-looking tribes, with the exception of Highlander Hyur which are visually distinct.
32#[binrw]
33#[brw(repr = u8)]
34#[derive(PartialEq, Eq, Clone, Copy, Debug)]
35#[repr(u8)]
36pub enum Tribe {
37    Midlander = 1,
38    Highlander = 2,
39    Wildwood = 3,
40    Duskwight = 4,
41    Plainsfolk = 5,
42    Dunesfolk = 6,
43    Seeker = 7,
44    Keeper = 8,
45    SeaWolf = 9,
46    Hellsguard = 10,
47    Raen = 11,
48    Xaela = 12,
49    Hellion = 13,
50    Lost = 14,
51    Rava = 15,
52    Veena = 16,
53}
54
55impl std::convert::TryFrom<u8> for Tribe {
56    type Error = crate::Error;
57
58    fn try_from(value: u8) -> Result<Self, Error> {
59        match value {
60            1 => Ok(Self::Midlander),
61            2 => Ok(Self::Highlander),
62            3 => Ok(Self::Wildwood),
63            4 => Ok(Self::Duskwight),
64            5 => Ok(Self::Plainsfolk),
65            6 => Ok(Self::Dunesfolk),
66            7 => Ok(Self::Seeker),
67            8 => Ok(Self::Keeper),
68            9 => Ok(Self::SeaWolf),
69            10 => Ok(Self::Hellsguard),
70            11 => Ok(Self::Raen),
71            12 => Ok(Self::Xaela),
72            13 => Ok(Self::Hellion),
73            14 => Ok(Self::Lost),
74            15 => Ok(Self::Rava),
75            16 => Ok(Self::Veena),
76            _ => Err(Error {}),
77        }
78    }
79}
80
81/// The playable races in the game.
82#[binrw]
83#[brw(repr = u8)]
84#[derive(PartialEq, Eq, Clone, Copy, Debug)]
85#[repr(u8)]
86pub enum Race {
87    Hyur = 1,
88    Elezen = 2,
89    Lalafell = 3,
90    Miqote = 4,
91    Roegadyn = 5,
92    AuRa = 6,
93    Hrothgar = 7,
94    Viera = 8,
95}
96
97impl std::convert::TryFrom<u8> for Race {
98    type Error = crate::Error;
99
100    fn try_from(value: u8) -> Result<Self, Error> {
101        match value {
102            1 => Ok(Self::Hyur),
103            2 => Ok(Self::Elezen),
104            3 => Ok(Self::Lalafell),
105            4 => Ok(Self::Miqote),
106            5 => Ok(Self::Roegadyn),
107            6 => Ok(Self::AuRa),
108            7 => Ok(Self::Hrothgar),
109            8 => Ok(Self::Viera),
110            _ => Err(Error {}),
111        }
112    }
113}
114
115/// Gets a proper race identifier (such as 101, for Hyur-Midlander-Males) given a race, a tribe,
116/// and a gender.
117pub fn get_race_id(race: Race, tribe: Tribe, gender: Gender) -> Option<i32> {
118    if !get_supported_tribes(race).contains(&tribe) {
119        return None;
120    }
121
122    match race {
123        Race::Hyur => match tribe {
124            Tribe::Midlander => match gender {
125                Gender::Male => Some(101),
126                Gender::Female => Some(201),
127            },
128            Tribe::Highlander => match gender {
129                Gender::Male => Some(301),
130                Gender::Female => Some(401),
131            },
132            _ => None,
133        },
134        Race::Elezen => match gender {
135            Gender::Male => Some(501),
136            Gender::Female => Some(601),
137        },
138        Race::Lalafell => match gender {
139            Gender::Male => Some(501),
140            Gender::Female => Some(601),
141        },
142        Race::Miqote => match gender {
143            Gender::Male => Some(701),
144            Gender::Female => Some(801),
145        },
146        Race::Roegadyn => match gender {
147            Gender::Male => Some(901),
148            Gender::Female => Some(1001),
149        },
150        Race::AuRa => match gender {
151            Gender::Male => Some(1301),
152            Gender::Female => Some(1401),
153        },
154        Race::Hrothgar => match gender {
155            Gender::Male => Some(1501),
156            Gender::Female => Some(1601),
157        },
158        Race::Viera => match gender {
159            Gender::Male => Some(1701),
160            Gender::Female => Some(1801),
161        },
162    }
163}
164
165/// Builds the path to the skeleton (sklb) file for a given `race`, `tribe` and `gender`.
166pub fn build_skeleton_path(race: Race, tribe: Tribe, gender: Gender) -> String {
167    format!(
168        "chara/human/c{0:04}/skeleton/base/b0001/skl_c{0:04}b0001.sklb",
169        get_race_id(race, tribe, gender).unwrap()
170    )
171}
172
173/// Returns the two tribes associated with a given `race`. For example, `Hyur` would return `[Midlander, Highlander]`.
174pub fn get_supported_tribes(race: Race) -> [Tribe; 2] {
175    match race {
176        Race::Hyur => [Tribe::Midlander, Tribe::Highlander],
177        Race::Elezen => [Tribe::Wildwood, Tribe::Duskwight],
178        Race::Lalafell => [Tribe::Plainsfolk, Tribe::Dunesfolk],
179        Race::Miqote => [Tribe::Seeker, Tribe::Keeper],
180        Race::Roegadyn => [Tribe::SeaWolf, Tribe::Hellsguard],
181        Race::AuRa => [Tribe::Raen, Tribe::Xaela],
182        Race::Hrothgar => [Tribe::Hellion, Tribe::Lost],
183        Race::Viera => [Tribe::Raen, Tribe::Veena],
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_convert_race_num() {
193        // valid
194        assert_eq!(
195            get_race_id(Race::Roegadyn, Tribe::SeaWolf, Gender::Male),
196            Some(901)
197        );
198        // invalid
199        assert_eq!(
200            get_race_id(Race::Roegadyn, Tribe::Midlander, Gender::Male),
201            None
202        );
203    }
204}