1use crate::abilities::AbilityId;
2use crate::items::AttributeId;
3use crate::prelude::*;
4
5use std::collections::BTreeMap;
6
7#[declare]
8pub type PetId = Uuid;
9
10#[declare]
11pub type PetSlotId = usize;
12
13#[declare]
14pub type PetRarityId = Uuid;
15
16#[derive(
17 Default,
18 PartialEq,
19 Eq,
20 Hash,
21 Debug,
22 Clone,
23 Serialize,
24 Deserialize,
25 Tsify,
26 JsonSchema,
27 CustomType,
28)]
29pub struct PetRarity {
30 #[schemars(schema_with = "id_schema")]
31 pub id: PetRarityId,
32
33 #[schemars(title = "Название редкости")]
34 pub name: i18n::I18nString,
35
36 #[schemars(title = "Сортировка")]
37 pub order: u64,
38
39 #[schemars(title = "Цвет редкости", schema_with = "color_schema")]
40 pub color: String,
41
42 #[schemars(title = "Цвет заднего фона редкости", schema_with = "color_schema")]
43 pub bg_color: String,
44
45 #[schemars(title = "Рамка", schema_with = "asset_pet_rarity_icon_schema")]
46 pub icon_path: String,
47
48 #[schemars(
49 title = "Квадратная рамка",
50 schema_with = "asset_pet_rarity_square_icon_schema"
51 )]
52 pub square_icon_path: String,
53}
54
55#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema, CustomType)]
56pub struct PetSecondaryStat {
57 #[schemars(title = "Id атрибута", schema_with = "attribute_link_id_schema")]
58 pub attribute_id: AttributeId,
59 #[schemars(title = "Базовое значение")]
60 pub base_value: i64,
61 #[schemars(title = "Значение за уровень")]
62 pub per_level_value: i64,
63}
64
65#[derive(
66 Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema, CustomType, Default,
67)]
68#[tsify(from_wasm_abi)]
69pub struct PetTemplate {
70 #[schemars(schema_with = "id_schema")]
71 pub id: PetId,
72
73 #[schemars(title = "Имя пета")]
74 pub name: i18n::I18nString,
75
76 #[schemars(title = "Иконка", schema_with = "asset_pet_icon_schema")]
77 pub icon_path: String,
78
79 #[schemars(title = "Спайн пета", schema_with = "asset_unit_spine_skin")]
80 pub spine_path: String,
81
82 #[schemars(title = "Id редкости", schema_with = "pet_rarity_link_id_schema")]
83 pub rarity_id: PetRarityId,
84
85 #[schemars(title = "Статы пета")]
86 pub stats: Vec<PetSecondaryStat>,
87
88 #[schemars(
89 title = "Id активной способности",
90 schema_with = "option_ability_link_id_schema"
91 )]
92 pub active_ability_id: Option<AbilityId>,
93
94 #[schemars(
95 title = "Id пассивной способности",
96 schema_with = "option_ability_link_id_schema"
97 )]
98 pub passive_ability_id: Option<AbilityId>,
99
100 #[schemars(title = "Скорость зарядки при нанесении урона (basis points)")]
101 pub charge_rate_on_damage_dealt: i64,
102 #[schemars(title = "Скорость зарядки при получении урона (basis points)")]
103 pub charge_rate_on_damage_taken: i64,
104 #[schemars(title = "Скорость зарядки при использовании скилла (basis points)")]
105 pub charge_rate_on_skill_use: i64,
106
107 #[schemars(title = "Максимальный заряд способности")]
108 pub max_charge: i64,
109
110 #[schemars(title = "Показывается в окне гачи, умеет выпадать из гачи")]
111 pub is_gacha_pet: bool,
112}
113
114#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema, CustomType)]
115pub struct PetComputedSecondaryStat {
116 pub attribute_id: AttributeId,
117 pub value: i64,
118}
119
120#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, CustomType)]
121#[tsify(from_wasm_abi)]
122pub struct Pet {
123 pub template_id: PetId,
124 pub name: i18n::I18nString,
125 pub icon_path: String,
126 pub rarity: PetRarity,
127 pub level: i64,
128 pub shards_amount: i64,
129 pub active_ability_id: Option<AbilityId>,
130 pub passive_ability_id: Option<AbilityId>,
131 pub stats: Vec<PetComputedSecondaryStat>,
132}
133
134impl Pet {
135 pub fn from_template(
136 template: &PetTemplate,
137 rarity: PetRarity,
138 level: i64,
139 shards_amount: i64,
140 ) -> Self {
141 let stats = template
142 .stats
143 .iter()
144 .map(|s| PetComputedSecondaryStat {
145 attribute_id: s.attribute_id,
146 value: s.base_value + s.per_level_value * (level - 1),
147 })
148 .collect();
149
150 Pet {
151 template_id: template.id,
152 name: template.name.clone(),
153 icon_path: template.icon_path.clone(),
154 rarity,
155 level,
156 shards_amount,
157 active_ability_id: template.active_ability_id,
158 passive_ability_id: template.passive_ability_id,
159 stats,
160 }
161 }
162
163 pub fn recompute_stats(&mut self, template: &PetTemplate) {
164 self.stats = template
165 .stats
166 .iter()
167 .map(|s| PetComputedSecondaryStat {
168 attribute_id: s.attribute_id,
169 value: s.base_value + s.per_level_value * (self.level - 1),
170 })
171 .collect();
172 }
173}
174
175#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, Default)]
176#[tsify(from_wasm_abi)]
177pub struct EquippedPets {
178 pub slotted: BTreeMap<PetSlotId, Pet>,
179}
180
181impl CustomType for EquippedPets {
182 fn build(mut builder: TypeBuilder<Self>) {
183 builder
184 .with_name("EquippedPets")
185 .with_get("slotted", |e: &mut EquippedPets| -> Array {
186 e.slotted
187 .values()
188 .cloned()
189 .map(Dynamic::from)
190 .collect::<Array>()
191 });
192 }
193}
194
195impl EquippedPets {
196 pub fn new() -> Self {
197 Self {
198 slotted: BTreeMap::new(),
199 }
200 }
201
202 pub fn leader(&self) -> Option<&Pet> {
203 self.slotted.get(&0)
204 }
205
206 pub fn supports(&self) -> Vec<&Pet> {
207 self.slotted
208 .iter()
209 .filter(|(slot_id, _)| **slot_id != 0)
210 .map(|(_, pet)| pet)
211 .collect()
212 }
213
214 pub fn all_pets(&self) -> Vec<&Pet> {
215 self.slotted.values().collect()
216 }
217
218 pub fn slot_type(slot_id: PetSlotId) -> PetSlotType {
219 if slot_id == 0 {
220 PetSlotType::Leader
221 } else {
222 PetSlotType::Support
223 }
224 }
225
226 pub fn has_pet(&self, pet_id: PetId) -> bool {
227 self.slotted.values().any(|p| p.template_id == pet_id)
228 }
229
230 pub fn get_mut_by_id(&mut self, pet_id: PetId) -> Option<&mut Pet> {
231 self.slotted.values_mut().find(|p| p.template_id == pet_id)
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Tsify)]
236#[tsify(namespace)]
237pub enum PetSlotType {
238 Leader,
239 Support,
240}
241
242#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
243pub struct UpgradedPetsMap(pub std::collections::HashMap<PetId, (i64, i64)>);
244
245impl UpgradedPetsMap {
246 pub fn iter(&self) -> rhai::Array {
247 self.0
248 .iter()
249 .map(|(id, (a, b))| {
250 rhai::Dynamic::from(rhai::Array::from(vec![
251 id.to_string().into(),
252 (*a).into(),
253 (*b).into(),
254 ]))
255 })
256 .collect()
257 }
258
259 pub fn insert(&mut self, id: PetId, levels: (i64, i64)) {
260 self.0.insert(id, levels);
261 }
262}
263
264impl CustomType for UpgradedPetsMap {
265 fn build(mut builder: TypeBuilder<Self>) {
266 builder
267 .with_name("UpgradedPetsMap")
268 .with_indexer_get(|ea: &mut UpgradedPetsMap, idx: Uuid| -> rhai::Dynamic {
269 match ea.0.get(&idx) {
270 Some((a, b)) => rhai::Array::from(vec![(*a).into(), (*b).into()]).into(),
271 None => ().into(),
272 }
273 })
274 .with_fn("UpgradedPetsMap", Self::default)
275 .with_fn("iter", |map: &mut UpgradedPetsMap| -> rhai::Array {
276 map.iter()
277 });
278 }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Tsify)]
283#[tsify(namespace)]
284pub enum PetCaseRollType {
285 Small,
286 Big,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub struct PetShard {
291 pub pet_id: PetId,
292 pub shards_amount: i64,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, CustomType)]
296pub struct PetDrop {
297 pub template: PetTemplate,
298 pub is_new: bool,
299 pub is_checkpoint: bool,
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use uuid::uuid;
306
307 const RARITY_ID: PetRarityId = uuid!("00000000-0000-0000-0000-000000000001");
308 const ATTR_ID_1: AttributeId = uuid!("00000000-0000-0000-0000-00000000000a");
309 const ATTR_ID_2: AttributeId = uuid!("00000000-0000-0000-0000-00000000000b");
310 const PET_ID_1: PetId = uuid!("10000000-0000-0000-0000-000000000001");
311 const PET_ID_2: PetId = uuid!("10000000-0000-0000-0000-000000000002");
312 const PET_ID_3: PetId = uuid!("10000000-0000-0000-0000-000000000003");
313 const RANDOM_ID: PetId = uuid!("ffffffff-ffff-ffff-ffff-ffffffffffff");
314
315 fn make_test_rarity() -> PetRarity {
316 PetRarity {
317 id: RARITY_ID,
318 name: Default::default(),
319 order: 1,
320 color: "#FFD700".to_string(),
321 bg_color: "#1A1A2E".to_string(),
322 icon_path: "rarity/common.png".to_string(),
323 square_icon_path: "rarity/common_square.png".to_string(),
324 }
325 }
326
327 fn make_test_template(id: PetId, stats: Vec<PetSecondaryStat>) -> PetTemplate {
328 PetTemplate {
329 id,
330 name: Default::default(),
331 icon_path: "pet/icon.png".to_string(),
332 spine_path: "pet/spine.json".to_string(),
333 rarity_id: RARITY_ID,
334 stats,
335 active_ability_id: None,
336 passive_ability_id: None,
337 charge_rate_on_damage_dealt: 100,
338 charge_rate_on_damage_taken: 50,
339 charge_rate_on_skill_use: 75,
340 max_charge: 1000,
341 is_gacha_pet: false,
342 }
343 }
344
345 fn make_two_stats() -> Vec<PetSecondaryStat> {
346 vec![
347 PetSecondaryStat {
348 attribute_id: ATTR_ID_1,
349 base_value: 10,
350 per_level_value: 2,
351 },
352 PetSecondaryStat {
353 attribute_id: ATTR_ID_2,
354 base_value: 50,
355 per_level_value: 5,
356 },
357 ]
358 }
359
360 fn make_pet(id: PetId) -> Pet {
361 let template = make_test_template(id, make_two_stats());
362 Pet::from_template(&template, make_test_rarity(), 1, 0)
363 }
364
365 #[test]
366 fn test_pet_from_template() {
367 let stats = make_two_stats();
368 let template = make_test_template(PET_ID_1, stats);
369 let pet = Pet::from_template(&template, make_test_rarity(), 1, 0);
370
371 assert_eq!(pet.template_id, PET_ID_1);
372 assert_eq!(pet.level, 1);
373 assert_eq!(pet.stats.len(), 2);
374 assert_eq!(pet.stats[0].attribute_id, ATTR_ID_1);
376 assert_eq!(pet.stats[0].value, 10);
377 assert_eq!(pet.stats[1].attribute_id, ATTR_ID_2);
378 assert_eq!(pet.stats[1].value, 50);
379 }
380
381 #[test]
382 fn test_pet_from_template_higher_level() {
383 let stats = make_two_stats();
384 let template = make_test_template(PET_ID_1, stats);
385 let pet = Pet::from_template(&template, make_test_rarity(), 5, 10);
386
387 assert_eq!(pet.level, 5);
388 assert_eq!(pet.shards_amount, 10);
389 assert_eq!(pet.stats[0].value, 10 + 2 * 4); assert_eq!(pet.stats[1].value, 50 + 5 * 4); }
393
394 #[test]
395 fn test_equipped_pets_leader_and_supports() {
396 let mut equipped = EquippedPets::new();
397 equipped.slotted.insert(0, make_pet(PET_ID_1));
398 equipped.slotted.insert(1, make_pet(PET_ID_2));
399 equipped.slotted.insert(2, make_pet(PET_ID_3));
400
401 let leader = equipped.leader().expect("should have a leader");
402 assert_eq!(leader.template_id, PET_ID_1);
403
404 let supports = equipped.supports();
405 assert_eq!(supports.len(), 2);
406 assert_eq!(supports[0].template_id, PET_ID_2);
407 assert_eq!(supports[1].template_id, PET_ID_3);
408 }
409
410 #[test]
411 fn test_equipped_pets_has_pet_and_get_mut() {
412 let mut equipped = EquippedPets::new();
413 equipped.slotted.insert(0, make_pet(PET_ID_1));
414
415 assert!(equipped.has_pet(PET_ID_1));
416 assert!(!equipped.has_pet(RANDOM_ID));
417
418 assert!(equipped.get_mut_by_id(PET_ID_1).is_some());
419 assert!(equipped.get_mut_by_id(RANDOM_ID).is_none());
420 }
421
422 #[test]
423 fn test_equipped_pets_multiple_pets() {
424 let mut equipped = EquippedPets::new();
425 equipped.slotted.insert(0, make_pet(PET_ID_1));
426 equipped.slotted.insert(1, make_pet(PET_ID_2));
427 equipped.slotted.insert(2, make_pet(PET_ID_3));
428
429 assert_eq!(equipped.all_pets().len(), 3);
430 assert_eq!(equipped.leader().unwrap().template_id, PET_ID_1);
431 assert_eq!(equipped.supports().len(), 2);
432 }
433}