physis/
equipment.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use crate::race::{Gender, Race, Tribe, get_race_id};
5
6#[repr(u8)]
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8/// The slot the item is for.
9pub enum Slot {
10    /// The head slot. Shorthand is "met".
11    Head,
12    /// The hands slot. Shorthand is "glv".
13    Hands,
14    /// The legs slot. Shorthand is "dwn".
15    Legs,
16    /// The feet slot. Shorthand is "sho".
17    Feet,
18    /// The body or chest slot. Shorthand is "top".
19    Body,
20    /// The earrings slot. Shorthand is "ear".
21    Earring,
22    /// The neck slot. Shorthand is "nek".
23    Neck,
24    /// The wrists slot. Shorthand is "wrs".
25    Wrists,
26    /// The right ring slot. Shorthand is "ril".
27    RingLeft,
28    /// The left ring slot. Shorthand is "rir".
29    RingRight,
30}
31
32/// Returns the shorthand abbreviation of `slot`. For example, Body's shorthand is "top".
33pub fn get_slot_abbreviation(slot: Slot) -> &'static str {
34    match slot {
35        Slot::Head => "met",
36        Slot::Hands => "glv",
37        Slot::Legs => "dwn",
38        Slot::Feet => "sho",
39        Slot::Body => "top",
40        Slot::Earring => "ear",
41        Slot::Neck => "nek",
42        Slot::Wrists => "wrs",
43        Slot::RingLeft => "ril",
44        Slot::RingRight => "rir",
45    }
46}
47
48/// Determines the correct slot from an id. This can fail, so a None is returned when no slot matches
49/// that id.
50pub fn get_slot_from_id(id: i32) -> Option<Slot> {
51    match id {
52        3 => Some(Slot::Head),
53        4 => Some(Slot::Body),
54        5 => Some(Slot::Hands),
55        7 => Some(Slot::Legs),
56        8 => Some(Slot::Feet),
57        9 => Some(Slot::Earring),
58        10 => Some(Slot::Neck),
59        11 => Some(Slot::Wrists),
60        12 => Some(Slot::RingLeft),
61        13 => Some(Slot::RingRight),
62        _ => None,
63    }
64}
65
66/// Determines the correct slot from an id. This can fail, so a None is returned when no slot matches
67/// that id.
68pub fn get_slot_from_abbreviation(abrev: &str) -> Option<Slot> {
69    match abrev {
70        "met" => Some(Slot::Head),
71        "glv" => Some(Slot::Hands),
72        "dwn" => Some(Slot::Legs),
73        "sho" => Some(Slot::Feet),
74        "top" => Some(Slot::Body),
75        "ear" => Some(Slot::Earring),
76        "nek" => Some(Slot::Neck),
77        "wrs" => Some(Slot::Wrists),
78        "ril" => Some(Slot::RingLeft),
79        "rir" => Some(Slot::RingRight),
80        _ => None,
81    }
82}
83
84/// Builds a game path to the equipment specified.
85pub fn build_equipment_path(
86    model_id: i32,
87    race: Race,
88    tribe: Tribe,
89    gender: Gender,
90    slot: Slot,
91) -> String {
92    format!(
93        "chara/equipment/e{:04}/model/c{:04}e{:04}_{}.mdl",
94        model_id,
95        get_race_id(race, tribe, gender).unwrap(),
96        model_id,
97        get_slot_abbreviation(slot)
98    )
99}
100
101#[repr(u8)]
102#[derive(Clone, Copy)]
103pub enum CharacterCategory {
104    Body,
105    Hair,
106    Face,
107    Tail,
108    Ear,
109}
110
111pub fn get_character_category_path(category: CharacterCategory) -> &'static str {
112    match category {
113        CharacterCategory::Body => "body",
114        CharacterCategory::Hair => "hair",
115        CharacterCategory::Face => "face",
116        CharacterCategory::Tail => "tail",
117        CharacterCategory::Ear => "zear",
118    }
119}
120
121pub fn get_character_category_abbreviation(category: CharacterCategory) -> &'static str {
122    match category {
123        CharacterCategory::Body => "top",
124        CharacterCategory::Hair => "hir",
125        CharacterCategory::Face => "fac",
126        CharacterCategory::Tail => "til",
127        CharacterCategory::Ear => "zer",
128    }
129}
130
131pub fn get_character_category_prefix(category: CharacterCategory) -> &'static str {
132    match category {
133        CharacterCategory::Body => "b",
134        CharacterCategory::Hair => "h",
135        CharacterCategory::Face => "f",
136        CharacterCategory::Tail => "t",
137        CharacterCategory::Ear => "z",
138    }
139}
140
141/// Builds a game path to the equipment specified.
142pub fn build_character_path(
143    category: CharacterCategory,
144    body_ver: i32,
145    race: Race,
146    tribe: Tribe,
147    gender: Gender,
148) -> String {
149    let category_path = get_character_category_path(category);
150    let race_id = get_race_id(race, tribe, gender).unwrap();
151    let category_abbreviation = get_character_category_abbreviation(category);
152    let category_prefix = get_character_category_prefix(category);
153    format!(
154        "chara/human/c{race_id:04}/obj/{category_path}/{category_prefix}{body_ver:04}/model/c{race_id:04}{category_prefix}{body_ver:04}_{category_abbreviation}.mdl"
155    )
156}
157
158/// Builds a material path for a specific gear
159pub fn build_gear_material_path(gear_id: i32, gear_version: i32, material_name: &str) -> String {
160    format!("chara/equipment/e{gear_id:04}/material/v{gear_version:04}{material_name}")
161}
162
163/// Builds a skin material path for a character
164pub fn build_skin_material_path(race_code: i32, body_code: i32, material_name: &str) -> String {
165    format!("chara/human/c{race_code:04}/obj/body/b{body_code:04}/material/v0001{material_name}")
166}
167
168/// Builds a face material path for a character
169pub fn build_face_material_path(race_code: i32, face_code: i32, material_name: &str) -> String {
170    format!("chara/human/c{race_code:04}/obj/face/f{face_code:04}/material{material_name}")
171}
172
173/// Builds a hair material path for a character
174pub fn build_hair_material_path(race_code: i32, hair_code: i32, material_name: &str) -> String {
175    format!("chara/human/c{race_code:04}/obj/hair/h{hair_code:04}/material/v0001{material_name}")
176}
177
178/// Builds a ear material path for a character
179pub fn build_ear_material_path(race_code: i32, ear_code: i32, material_name: &str) -> String {
180    format!("chara/human/c{race_code:04}/obj/ear/e{ear_code:04}/material/v0001{material_name}")
181}
182
183/// Builds a tail material path for a character
184pub fn build_tail_material_path(race_code: i32, tail_code: i32, material_name: &str) -> String {
185    format!("chara/human/c{race_code:04}/obj/tail/t{tail_code:04}/material/v0001{material_name}")
186}
187
188pub fn deconstruct_equipment_path(path: &str) -> Option<(i32, Slot)> {
189    let model_id = &path[6..10];
190    let slot_name = &path[11..14];
191
192    Some((
193        model_id.parse().ok()?,
194        get_slot_from_abbreviation(slot_name)?,
195    ))
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_equipment_path() {
204        assert_eq!(
205            build_equipment_path(0, Race::Hyur, Tribe::Midlander, Gender::Male, Slot::Body),
206            "chara/equipment/e0000/model/c0101e0000_top.mdl"
207        );
208    }
209
210    #[test]
211    fn test_deconstruct() {
212        assert_eq!(
213            deconstruct_equipment_path("c0101e0000_top.mdl"),
214            Some((0, Slot::Body))
215        );
216    }
217}