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