Skip to main content

esi_openapi/groups/
cosmetics.rs

1use crate::prelude::*;
2
3/// Endpoints for Cosmetics
4pub struct CosmeticsGroup<'a> {
5    pub(crate) esi: &'a Esi,
6}
7
8/// How a pattern is blended with the layers below it.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum PatternBlendMode {
12    /// Normal blending.
13    Normal,
14    /// Subtract.
15    Subtract,
16    /// Exclusion.
17    Exclusion,
18    /// Nested.
19    Nested,
20    /// Nested, inverted.
21    NestedInverted,
22    /// A mode this version of the crate does not know about.
23    #[serde(other)]
24    Unrecognized,
25}
26
27/// A 3-component vector.
28#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
29#[allow(missing_docs)]
30pub struct Vector3 {
31    pub x: f64,
32    pub y: f64,
33    pub z: f64,
34}
35
36/// A 4-component vector (a quaternion for rotations).
37#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
38#[allow(missing_docs)]
39pub struct Vector4 {
40    pub w: f64,
41    pub x: f64,
42    pub y: f64,
43    pub z: f64,
44}
45
46/// Which slots a pattern is projected onto.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
48#[allow(missing_docs)]
49pub struct PatternProjection {
50    pub slot1: bool,
51    pub slot2: bool,
52    pub slot3: bool,
53    pub slot4: bool,
54}
55
56/// Transformation applied to a pattern.
57#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
58#[allow(missing_docs)]
59pub struct PatternTransform {
60    pub position: Vector3,
61    pub rotation: Vector4,
62    pub scaling: Vector3,
63}
64
65/// Configuration of a pattern component.
66#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
67#[allow(missing_docs)]
68pub struct PatternConfiguration {
69    pub mirrored: bool,
70    pub projection: PatternProjection,
71    pub transform: PatternTransform,
72}
73
74/// A nanocoating component (see the SDE for details).
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
76#[allow(missing_docs)]
77pub struct NanocoatingComponent {
78    pub id: i64,
79}
80
81/// A pattern component (see the SDE for details).
82#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
83#[allow(missing_docs)]
84pub struct PatternComponent {
85    pub configuration: PatternConfiguration,
86    pub id: i64,
87}
88
89/// What a slot of a SKINR design holds.
90#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
91#[serde(untagged)]
92#[allow(missing_docs)]
93pub enum SlotConfiguration {
94    Nanocoating { nanocoating: NanocoatingComponent },
95    Pattern { pattern: PatternComponent },
96}
97
98/// A slot of a SKINR design.
99#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
100#[allow(missing_docs)]
101pub struct SkinrSlot {
102    pub configuration: SlotConfiguration,
103    pub id: i64,
104}
105
106/// Layout of a SKINR design.
107#[derive(Debug, Clone, PartialEq, Deserialize)]
108#[allow(missing_docs)]
109pub struct SkinrLayout {
110    pub pattern_blend_mode: PatternBlendMode,
111    pub slots: Vec<SkinrSlot>,
112}
113
114/// Tier of a SKINR design.
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
116#[allow(missing_docs)]
117pub struct SkinrTier {
118    pub level: i64,
119}
120
121/// A SKINR design.
122#[derive(Debug, Clone, PartialEq, Deserialize)]
123#[allow(missing_docs)]
124pub struct Skinr {
125    /// Character ID of the creator.
126    pub creator_id: i64,
127    pub id: String,
128    pub layout: SkinrLayout,
129    pub line: Option<String>,
130    pub name: String,
131    pub ship_type_id: i64,
132    pub tier: SkinrTier,
133}
134
135impl CosmeticsGroup<'_> {
136    api_get!(
137        /// Get a SKINR design.
138        get_skinr,
139        "GetCosmeticsSkinr",
140        RequestType::Public,
141        Skinr,
142        (skinr_id: &str) => "{skinr_id}"
143    );
144}
145
146#[cfg(test)]
147mod tests {
148    use super::{Skinr, SlotConfiguration};
149    use serde_json::json;
150
151    #[test]
152    fn test_parse_skinr_with_both_slot_kinds() {
153        let vec3 = json!({"x": 0.0, "y": 0.0, "z": 0.0});
154        let value = json!({
155            "creator_id": 2_112_000_000_i64,
156            "id": "abc",
157            "name": "Design",
158            "ship_type_id": 587,
159            "tier": {"level": 2},
160            "layout": {
161                "pattern_blend_mode": "nested_inverted",
162                "slots": [
163                    {"id": 1, "configuration": {"nanocoating": {"id": 10}}},
164                    {"id": 2, "configuration": {"pattern": {
165                        "id": 20,
166                        "configuration": {
167                            "mirrored": false,
168                            "projection": {
169                                "slot1": true, "slot2": false, "slot3": false, "slot4": false
170                            },
171                            "transform": {
172                                "position": vec3,
173                                "scaling": vec3,
174                                "rotation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0}
175                            }
176                        }
177                    }}}
178                ]
179            }
180        });
181        let skinr: Skinr = serde_json::from_value(value).unwrap();
182        assert!(skinr.line.is_none());
183        assert!(matches!(
184            skinr.layout.slots[0].configuration,
185            SlotConfiguration::Nanocoating { .. }
186        ));
187        assert!(matches!(
188            skinr.layout.slots[1].configuration,
189            SlotConfiguration::Pattern { .. }
190        ));
191    }
192}
193
194/// A SKINR license of a character.
195#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
196pub struct CharacterSkinrLicense {
197    /// Whether the license is activated.
198    pub activated: bool,
199    /// ID of the SKINR design.
200    pub skinr_id: String,
201    /// Number of unactivated licenses.
202    pub unactivated: i64,
203}
204
205#[derive(Debug, Deserialize)]
206struct SkinrLicenses {
207    licenses: Vec<CharacterSkinrLicense>,
208}
209
210/// Kind of a SKINR component.
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
212#[serde(rename_all = "snake_case")]
213pub enum SkinrComponentType {
214    /// A nanocoating.
215    Nanocoating,
216    /// A pattern.
217    Pattern,
218    /// A kind this version of the crate does not know about.
219    #[serde(other)]
220    Unrecognized,
221}
222
223/// How many times a component can be used: either `remaining` or `unlimited` is set.
224#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
225pub struct ComponentRuns {
226    /// Remaining uses.
227    pub remaining: Option<i64>,
228    /// Whether the component has unlimited uses.
229    pub unlimited: Option<bool>,
230}
231
232/// A SKINR component license of a character.
233#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
234#[allow(missing_docs)]
235pub struct SkinrComponentLicense {
236    pub component_id: i64,
237    pub runs: ComponentRuns,
238    #[serde(rename = "type")]
239    pub component_type: SkinrComponentType,
240}
241
242#[derive(Debug, Deserialize)]
243struct SkinrComponentLicenses {
244    licenses: Vec<SkinrComponentLicense>,
245}
246
247impl CosmeticsGroup<'_> {
248    /// List the SKINR licenses of a character.
249    pub async fn get_character_skinr(
250        &self,
251        character_id: i64,
252    ) -> EsiResult<Vec<CharacterSkinrLicense>> {
253        let path = self
254            .esi
255            .get_endpoint_for_op_id("GetCharactersCosmeticsSkinr")?
256            .replace("{character_id}", &character_id.to_string());
257        let wrapper: SkinrLicenses = self
258            .esi
259            .query("GET", RequestType::Authenticated, &path, None, None)
260            .await?;
261        Ok(wrapper.licenses)
262    }
263
264    /// List the SKINR component licenses of a character.
265    pub async fn get_character_skinr_components(
266        &self,
267        character_id: i64,
268    ) -> EsiResult<Vec<SkinrComponentLicense>> {
269        let path = self
270            .esi
271            .get_endpoint_for_op_id("GetCharactersCosmeticsSkinrComponents")?
272            .replace("{character_id}", &character_id.to_string());
273        let wrapper: SkinrComponentLicenses = self
274            .esi
275            .query("GET", RequestType::Authenticated, &path, None, None)
276            .await?;
277        Ok(wrapper.licenses)
278    }
279}
280
281#[cfg(test)]
282mod license_tests {
283    use super::*;
284
285    #[test]
286    fn test_parse_component_licenses() {
287        let w: SkinrComponentLicenses = serde_json::from_str(
288            r#"{"licenses":[{"component_id":1,"type":"pattern","runs":{"unlimited":true}},
289            {"component_id":2,"type":"x","runs":{"remaining":3}}]}"#,
290        )
291        .unwrap();
292        assert_eq!(w.licenses[0].runs.unlimited, Some(true));
293        assert_eq!(w.licenses[1].runs.remaining, Some(3));
294        assert_eq!(
295            w.licenses[1].component_type,
296            SkinrComponentType::Unrecognized
297        );
298    }
299}