1use crate::items::AttributeId;
2use crate::prelude::*;
3
4use std::collections::BTreeMap;
5
6#[declare]
7pub type PetId = Uuid;
8
9#[declare]
10pub type PetSlotId = usize;
11
12#[declare]
13pub type PetRarityId = Uuid;
14
15#[derive(Default, PartialEq, Eq, Hash, Debug, Clone, Serialize, Deserialize, Tsify, JsonSchema)]
16pub struct PetRarity {
17 #[schemars(schema_with = "id_schema")]
18 pub id: PetRarityId,
19
20 #[schemars(title = "Название редкости")]
21 pub name: i18n::I18nString,
22
23 #[schemars(title = "Сортировка")]
24 pub order: u64,
25
26 #[schemars(title = "Цвет редкости", schema_with = "color_schema")]
27 pub color: String,
28
29 #[schemars(title = "Цвет заднего фона редкости", schema_with = "color_schema")]
30 pub bg_color: String,
31
32 #[schemars(title = "Рамка", schema_with = "asset_pet_rarity_icon_schema")]
33 pub icon_path: String,
34
35 #[schemars(
36 title = "Квадратная рамка",
37 schema_with = "asset_pet_rarity_square_icon_schema"
38 )]
39 pub square_icon_path: String,
40}
41
42#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
43pub struct PetSecondaryStat {
44 #[schemars(title = "Id атрибута", schema_with = "attribute_link_id_schema")]
45 pub attribute_id: AttributeId,
46 #[schemars(title = "Базовое значение")]
47 pub base_value: i64,
48 #[schemars(title = "Значение за уровень")]
49 pub per_level_value: i64,
50}
51
52#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema, Default)]
53#[tsify(from_wasm_abi)]
54pub struct PetTemplate {
55 #[schemars(schema_with = "id_schema")]
56 pub id: PetId,
57
58 #[schemars(title = "Имя пета")]
59 pub name: i18n::I18nString,
60
61 #[schemars(title = "Иконка", schema_with = "asset_pet_icon_schema")]
62 pub icon_path: String,
63
64 #[schemars(title = "Спайн пета", schema_with = "asset_unit_spine_skin")]
65 pub spine_path: String,
66
67 #[schemars(title = "Id редкости", schema_with = "pet_rarity_link_id_schema")]
68 pub rarity_id: PetRarityId,
69
70 #[schemars(title = "Статы пета")]
71 pub stats: Vec<PetSecondaryStat>,
72
73 #[schemars(title = "Показывается в окне гачи, умеет выпадать из гачи")]
74 pub is_gacha_pet: bool,
75
76 #[schemars(title = "Грань мира Real")]
79 pub real_facet: crate::pet_facets::PetFacet,
80
81 #[schemars(title = "Грань мира Fantasy")]
83 pub fantasy_facet: crate::pet_facets::PetFacet,
84}
85
86#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
87pub struct PetComputedSecondaryStat {
88 pub attribute_id: AttributeId,
89 pub value: i64,
90}
91
92#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
93#[tsify(from_wasm_abi)]
94pub struct Pet {
95 pub template_id: PetId,
96 pub name: i18n::I18nString,
97 pub icon_path: String,
98 pub rarity: PetRarity,
99 pub level: i64,
100 pub shards_amount: i64,
101 pub stats: Vec<PetComputedSecondaryStat>,
102}
103
104impl Pet {
105 pub fn from_template(
106 template: &PetTemplate,
107 rarity: PetRarity,
108 level: i64,
109 shards_amount: i64,
110 ) -> Self {
111 let stats = template
112 .stats
113 .iter()
114 .map(|s| PetComputedSecondaryStat {
115 attribute_id: s.attribute_id,
116 value: s.base_value + s.per_level_value * (level - 1),
117 })
118 .collect();
119
120 Pet {
121 template_id: template.id,
122 name: template.name.clone(),
123 icon_path: template.icon_path.clone(),
124 rarity,
125 level,
126 shards_amount,
127 stats,
128 }
129 }
130
131 pub fn recompute_stats(&mut self, template: &PetTemplate) {
132 self.stats = template
133 .stats
134 .iter()
135 .map(|s| PetComputedSecondaryStat {
136 attribute_id: s.attribute_id,
137 value: s.base_value + s.per_level_value * (self.level - 1),
138 })
139 .collect();
140 }
141}
142
143#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, Default)]
144#[tsify(from_wasm_abi)]
145pub struct EquippedPets {
146 pub slotted: BTreeMap<PetSlotId, Pet>,
147}
148
149impl EquippedPets {
150 pub fn new() -> Self {
151 Self {
152 slotted: BTreeMap::new(),
153 }
154 }
155
156 pub fn all_pets(&self) -> Vec<&Pet> {
157 self.slotted.values().collect()
158 }
159
160 pub fn has_pet(&self, pet_id: PetId) -> bool {
161 self.slotted.values().any(|p| p.template_id == pet_id)
162 }
163
164 pub fn get_mut_by_id(&mut self, pet_id: PetId) -> Option<&mut Pet> {
165 self.slotted.values_mut().find(|p| p.template_id == pet_id)
166 }
167}
168
169#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
170pub struct UpgradedPetsMap(pub std::collections::HashMap<PetId, (i64, i64)>);
171
172impl UpgradedPetsMap {
173 pub fn insert(&mut self, id: PetId, levels: (i64, i64)) {
174 self.0.insert(id, levels);
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Tsify)]
180#[tsify(namespace)]
181pub enum PetCaseRollType {
182 Small,
183 Big,
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct PetShard {
188 pub pet_id: PetId,
189 pub shards_amount: i64,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
193pub struct PetDrop {
194 pub template: PetTemplate,
195 pub is_new: bool,
196 pub is_checkpoint: bool,
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use uuid::uuid;
203
204 const RARITY_ID: PetRarityId = uuid!("00000000-0000-0000-0000-000000000001");
205 const ATTR_ID_1: AttributeId = uuid!("00000000-0000-0000-0000-00000000000a");
206 const ATTR_ID_2: AttributeId = uuid!("00000000-0000-0000-0000-00000000000b");
207 const PET_ID_1: PetId = uuid!("10000000-0000-0000-0000-000000000001");
208 const PET_ID_2: PetId = uuid!("10000000-0000-0000-0000-000000000002");
209 const PET_ID_3: PetId = uuid!("10000000-0000-0000-0000-000000000003");
210 const RANDOM_ID: PetId = uuid!("ffffffff-ffff-ffff-ffff-ffffffffffff");
211
212 fn make_test_rarity() -> PetRarity {
213 PetRarity {
214 id: RARITY_ID,
215 name: Default::default(),
216 order: 1,
217 color: "#FFD700".to_string(),
218 bg_color: "#1A1A2E".to_string(),
219 icon_path: "rarity/common.png".to_string(),
220 square_icon_path: "rarity/common_square.png".to_string(),
221 }
222 }
223
224 fn make_test_template(id: PetId, stats: Vec<PetSecondaryStat>) -> PetTemplate {
225 PetTemplate {
226 id,
227 name: Default::default(),
228 icon_path: "pet/icon.png".to_string(),
229 spine_path: "pet/spine.json".to_string(),
230 rarity_id: RARITY_ID,
231 stats,
232 is_gacha_pet: false,
233 real_facet: crate::pet_facets::PetFacet::HeadStart,
234 fantasy_facet: crate::pet_facets::PetFacet::SecondSpark,
235 }
236 }
237
238 fn make_two_stats() -> Vec<PetSecondaryStat> {
239 vec![
240 PetSecondaryStat {
241 attribute_id: ATTR_ID_1,
242 base_value: 10,
243 per_level_value: 2,
244 },
245 PetSecondaryStat {
246 attribute_id: ATTR_ID_2,
247 base_value: 50,
248 per_level_value: 5,
249 },
250 ]
251 }
252
253 fn make_pet(id: PetId) -> Pet {
254 let template = make_test_template(id, make_two_stats());
255 Pet::from_template(&template, make_test_rarity(), 1, 0)
256 }
257
258 #[test]
259 fn test_pet_from_template() {
260 let stats = make_two_stats();
261 let template = make_test_template(PET_ID_1, stats);
262 let pet = Pet::from_template(&template, make_test_rarity(), 1, 0);
263
264 assert_eq!(pet.template_id, PET_ID_1);
265 assert_eq!(pet.level, 1);
266 assert_eq!(pet.stats.len(), 2);
267 assert_eq!(pet.stats[0].attribute_id, ATTR_ID_1);
269 assert_eq!(pet.stats[0].value, 10);
270 assert_eq!(pet.stats[1].attribute_id, ATTR_ID_2);
271 assert_eq!(pet.stats[1].value, 50);
272 }
273
274 #[test]
275 fn test_pet_from_template_higher_level() {
276 let stats = make_two_stats();
277 let template = make_test_template(PET_ID_1, stats);
278 let pet = Pet::from_template(&template, make_test_rarity(), 5, 10);
279
280 assert_eq!(pet.level, 5);
281 assert_eq!(pet.shards_amount, 10);
282 assert_eq!(pet.stats[0].value, 10 + 2 * 4); assert_eq!(pet.stats[1].value, 50 + 5 * 4); }
286
287 #[test]
288 fn test_equipped_pets_has_pet_and_get_mut() {
289 let mut equipped = EquippedPets::new();
290 equipped.slotted.insert(0, make_pet(PET_ID_1));
291
292 assert!(equipped.has_pet(PET_ID_1));
293 assert!(!equipped.has_pet(RANDOM_ID));
294
295 assert!(equipped.get_mut_by_id(PET_ID_1).is_some());
296 assert!(equipped.get_mut_by_id(RANDOM_ID).is_none());
297 }
298
299 #[test]
300 fn test_equipped_pets_multiple_pets() {
301 let mut equipped = EquippedPets::new();
302 equipped.slotted.insert(0, make_pet(PET_ID_1));
303 equipped.slotted.insert(1, make_pet(PET_ID_2));
304 equipped.slotted.insert(2, make_pet(PET_ID_3));
305
306 assert_eq!(equipped.all_pets().len(), 3);
307 }
308}