deepwoken 0.3.11

A library for interacting with Deepwoken data in a more convenient format, with a few added utilities.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
// Types that wrap the structures found in pocamind/data

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::Stat;
use crate::error::{DeepError, Result};
use crate::model::enums::{EquipmentSlot, ItemRarity, MantraType, RangeType, TalentRarity, WeaponType};
use crate::model::formula::{StatContributions, StatFormula};
use crate::model::req::Requirement;
use crate::util::name_to_identifier;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AspectVariantInfo {
    name: String,
    unlock: Option<String>,
    /// All colors are in hexadecimal format #RRGGBB
    colors: HashMap<String, String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Aspect {
    pub name: String,
    pub desc: String,
    pub innate: HashMap<Stat, i64>,
    pub is_pathfinder: bool,
    pub variants: HashMap<String, AspectVariantInfo>,
    #[serde(default)]
    pub talent: Vec<String>,
    #[serde(default)]
    pub exclude_cosmetics: Vec<String>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StatValue {
    pub value: StatFormula,
    pub percentage: bool,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Outfit {
    pub name: String,
    #[serde(default)]
    pub pants_id: Option<String>,
    #[serde(default)]
    pub shirt_id: Option<String>,
    pub category: String,
    pub durability: i64,
    pub resistances: HashMap<String, f64>,
    pub extra_percents: HashMap<String, i64>,
    pub talent: Option<String>,
    #[serde(default)]
    pub variants: Vec<String>,
    pub reqs: Requirement,
    pub mats: HashMap<String, i64>,
    pub notes: i64,
    #[serde(default)]
    pub voi: bool,
    #[serde(default)]
    pub voi_only: bool,
    pub desc: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Equipment {
    pub name: String,
    pub equippable: bool,
    #[serde(rename = "type")]
    pub equipment_type: EquipmentSlot,
    pub rarity: ItemRarity,
    pub set: Option<String>,
    #[serde(default)]
    pub variants: Vec<String>,
    #[serde(default)]
    pub talents: Vec<String>,
    #[serde(default)]
    pub innates: HashMap<String, StatValue>,
    #[serde(default)]
    pub pips: HashMap<String, i64>,
    pub reqs: Requirement,
    pub voi: bool,
    #[serde(default)]
    pub voi_only: bool,
    pub desc: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Talent {
    pub name: String,
    pub desc: String,
    pub rarity: TalentRarity,
    pub category: String,
    pub reqs: Requirement,
    pub count_towards_talent_total: bool,
    pub vaulted: bool,
    pub voi: bool,
    #[serde(default)]
    pub voi_only: bool,
    /// Whether this talent is implicitly granted as a byproduct of meeting its
    /// stat requirements (e.g. attunement milestones like Adept/Master), rather
    /// than chosen. Absent in the data unless true.
    #[serde(default)]
    pub implicit: bool,
    #[serde(default)]
    pub exclusive: Vec<String>,
    #[serde(flatten)]
    pub contributions: StatContributions,
    #[serde(default)]
    pub additional_info: Option<String>,
    #[serde(default)]
    pub icon: Option<String>,
    #[serde(default)]
    pub roll2able: Option<bool>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Weapon {
    pub name: String,
    #[serde(rename = "type")]
    pub weapon_type: WeaponType,
    pub rarity: ItemRarity,
    pub damage: Option<f64>,
    pub posture_damage: Option<f64>,
    pub range: Option<f64>,
    pub reqs: Requirement,
    pub enchantable: bool,
    pub equip_motifs: bool,
    pub voi: bool,
    #[serde(default)]
    pub voi_only: bool,
    pub desc: String,
    #[serde(default)]
    pub damage_types: Vec<String>,
    #[serde(default)]
    pub range_type: Option<RangeType>,
    #[serde(default)]
    pub attack_duration: Option<f64>,
    #[serde(default)]
    pub endlag: Option<f64>,
    #[serde(default)]
    pub swing_speed: Option<f64>,
    #[serde(default)]
    pub scaling: HashMap<String, f64>,
    #[serde(default)]
    pub bleed_damage: Option<f64>,
    #[serde(default)]
    pub chip_damage: Option<f64>,
    #[serde(default)]
    pub penetration: Option<f64>,
    #[serde(default)]
    pub posture_max: Option<f64>,
    #[serde(default)]
    pub posture_restoration: Option<f64>,
    #[serde(default)]
    pub talents: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MantraDamageLevel {
    pub level: String,
    pub damage: f64,
    pub posture_damage: Option<f64>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MantraDamageVariant {
    pub variant: Option<String>,
    pub levels: Vec<MantraDamageLevel>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Mantra {
    pub name: String,
    pub desc: String,
    pub stars: i64,
    pub category: String,
    #[serde(rename = "type")]
    pub mantra_type: MantraType,
    pub attributes: Vec<String>,
    pub reqs: Requirement,
    pub vaulted: bool,
    pub voi: bool,
    #[serde(default)]
    pub voi_only: bool,
    #[serde(default)]
    pub damage: Vec<MantraDamageVariant>,
    #[serde(default)]
    pub scaling: HashMap<String, f64>,
    #[serde(flatten)]
    pub contributions: StatContributions,
    #[serde(default)]
    pub modifiers: Vec<String>,
    #[serde(default)]
    pub sparks: Vec<String>,
    #[serde(default)]
    pub related_talents: Vec<String>,
    #[serde(default)]
    pub shared_cooldowns: Vec<String>,
    #[serde(default)]
    pub miscellaneous: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Enchant {
    pub name: String,
    pub category: String,
    pub info: String,
    #[serde(default)]
    pub in_game_desc: Option<String>,
    #[serde(default)]
    pub obtainable_in: Option<String>,
    #[serde(flatten)]
    pub contributions: StatContributions,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Preset {
    pub name: String,
    pub desc: String,
    /// A reqfile segment, i.e. the `Free:` and `Post:` blocks, applied as an
    /// optional reqfile when this preset is selected.
    pub opts: String,
}

/// A struct mirroring the structure of the 'all.json'
/// bundle found on [pocamind/data releases](https://github.com/pocamind/data/releases).
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct DeepData {
    aspects: HashMap<String, Aspect>,
    talents: HashMap<String, Talent>,
    mantras: HashMap<String, Mantra>,
    weapons: HashMap<String, Weapon>,
    outfits: HashMap<String, Outfit>,
    equipment: HashMap<String, Equipment>,
    enchants: HashMap<String, Enchant>,
    presets: HashMap<String, Preset>,

    /// The raw json payload used to construct the object, which may be more up-to-date.
    /// The shape is guarenteed to have at least the fields that `DeepData` has.
    #[serde(skip, default)]
    raw: String,
}

impl DeepData {
    pub fn from_json(json: &str) -> Result<DeepData> {
        let mut ret: DeepData = serde_json::from_str(json).map_err(DeepError::from)?;

        ret.raw = json.to_string();
        ret.validate_formulas()?;

        Ok(ret)
    }

    fn validate_formulas(&self) -> Result<()> {
        let named = |item: &str, stat: &str, e: DeepError| {
            DeepError::Formula(format!("{item} / {stat}: {e}"))
        };

        let named_sources = self
            .talents
            .values()
            .map(|t| (&t.name, &t.contributions))
            .chain(self.mantras.values().map(|m| (&m.name, &m.contributions)));

        for (item, contributions) in named_sources {
            for map in contributions.all() {
                for (stat, formula) in map {
                    formula.validate().map_err(|e| named(item, stat, e))?;
                }
            }
        }

        for equip in self.equipment.values() {
            for (stat, innate) in &equip.innates {
                innate
                    .value
                    .validate()
                    .map_err(|e| named(&equip.name, stat, e))?;
            }
        }

        Ok(())
    }

    /// Retrieve Deepwoken data that was bundled with this release. This may be severely out of date and should not be relied on for up-to-date info, prefer DeepData::latest_release + from_release instead.
    #[cfg(feature = "static")]
    pub fn bundled() -> DeepData {
        DeepData::from_json(include_str!("../../assets/all.json"))
            .expect("bundled all.json failed to parse")
    }

    /// Retrieve the raw JSON used to construct the data schema. 
    /// 
    /// We expose this functionality because the data schema may be
    /// frequently updated, though it is a guarentee that the data must be
    /// parsable into the current DeepData structure and it's strongly-typed definitions.
    pub fn raw(&self) -> &String {
        &self.raw
    }

    /// Retrieve a talent by it's name.
    ///
    /// The passed in name can be it's in-game name, or the
    /// internal map key
    #[must_use]
    pub fn get_talent(&self, name: &str) -> Option<&Talent> {
        self.talents.get(&name_to_identifier(name))
    }

    /// Retrieve a mantra by it's name.
    ///
    /// The passed in name can be it's in-game name, or the
    /// internal map key
    #[must_use]
    pub fn get_mantra(&self, name: &str) -> Option<&Mantra> {
        self.mantras.get(&name_to_identifier(name))
    }

    /// Retrieve a weapon by it's name.
    ///
    /// The passed in name can be it's in-game name, or the
    /// internal map key
    #[must_use]
    pub fn get_weapon(&self, name: &str) -> Option<&Weapon> {
        self.weapons.get(&name_to_identifier(name))
    }

    /// Retrieve an outfit by it's name.
    ///
    /// The passed in name can be it's in-game name, or the
    /// internal map key
    #[must_use]
    pub fn get_outfit(&self, name: &str) -> Option<&Outfit> {
        self.outfits.get(&name_to_identifier(name))
    }

    /// Retrieve an equipment piece by it's name.
    ///
    /// The passed in name can be it's in-game name, or the
    /// internal map key
    #[must_use]
    pub fn get_equipment(&self, name: &str) -> Option<&Equipment> {
        self.equipment.get(&name_to_identifier(name))
    }

    /// Retrieve an aspect by it's name.
    ///
    /// The passed in name can be it's in-game name, or the
    /// internal map key
    #[must_use]
    pub fn get_aspect(&self, name: &str) -> Option<&Aspect> {
        self.aspects.get(&name_to_identifier(name))
    }

    /// Retrieve an enchant by it's name.
    ///
    /// The passed in name can be it's in-game name, or the
    /// internal map key
    #[must_use]
    pub fn get_enchant(&self, name: &str) -> Option<&Enchant> {
        self.enchants.get(&name_to_identifier(name))
    }

    /// Retrieve a preset by it's name.
    ///
    /// The passed in name can be it's in-game name, or the
    /// internal map key
    #[must_use]
    pub fn get_preset(&self, name: &str) -> Option<&Preset> {
        self.presets.get(&name_to_identifier(name))
    }

    /// Retrieve an iterator of talents
    pub fn talents(&self) -> impl Iterator<Item = &Talent> {
        self.talents.values()
    }

    /// Retrieve an iterator of talents
    pub fn mantras(&self) -> impl Iterator<Item = &Mantra> {
        self.mantras.values()
    }

    /// Retrieve an iterator of talents
    pub fn weapons(&self) -> impl Iterator<Item = &Weapon> {
        self.weapons.values()
    }

    /// Retrieve an iterator of outfits
    pub fn outfits(&self) -> impl Iterator<Item = &Outfit> {
        self.outfits.values()
    }

    /// Retrieve an iterator of equipment
    pub fn equipment(&self) -> impl Iterator<Item = &Equipment> {
        self.equipment.values()
    }

    /// Retrieve an iterator of aspects
    pub fn aspects(&self) -> impl Iterator<Item = &Aspect> {
        self.aspects.values()
    }

    /// Retrieve an iterator of enchants
    pub fn enchants(&self) -> impl Iterator<Item = &Enchant> {
        self.enchants.values()
    }

    /// Retrieve an iterator of presets
    pub fn presets(&self) -> impl Iterator<Item = &Preset> {
        self.presets.values()
    }
}