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(
16 Default,
17 PartialEq,
18 Eq,
19 Hash,
20 Debug,
21 Clone,
22 Serialize,
23 Deserialize,
24 Tsify,
25 JsonSchema,
26 i18n::Translate,
27)]
28pub struct PetRarity {
29 #[schemars(schema_with = "id_schema")]
30 pub id: PetRarityId,
31
32 #[schemars(title = "Название редкости")]
33 #[translate]
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)]
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, Default, i18n::Translate,
67)]
68#[tsify(from_wasm_abi)]
69pub struct PetTemplate {
70 #[schemars(schema_with = "id_schema")]
71 pub id: PetId,
72
73 #[schemars(title = "Имя пета")]
74 #[translate]
75 pub name: i18n::I18nString,
76
77 #[schemars(title = "Иконка", schema_with = "asset_pet_icon_schema")]
78 pub icon_path: String,
79
80 #[schemars(title = "Спайн пета", schema_with = "asset_unit_spine_skin")]
81 pub spine_path: String,
82
83 #[schemars(title = "Id редкости", schema_with = "pet_rarity_link_id_schema")]
84 pub rarity_id: PetRarityId,
85
86 #[schemars(title = "Статы пета")]
87 pub stats: Vec<PetSecondaryStat>,
88
89 #[schemars(title = "Показывается в окне гачи, умеет выпадать из гачи")]
90 pub is_gacha_pet: bool,
91
92 #[schemars(title = "Грань мира Real")]
95 pub real_facet: crate::pet_facets::PetFacet,
96
97 #[schemars(title = "Грань мира Fantasy")]
99 pub fantasy_facet: crate::pet_facets::PetFacet,
100}
101
102#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
103pub struct PetComputedSecondaryStat {
104 pub attribute_id: AttributeId,
105 pub value: i64,
106}
107
108#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
109#[tsify(from_wasm_abi)]
110pub struct Pet {
111 pub template_id: PetId,
112 pub name: i18n::I18nString,
113 pub icon_path: String,
114 pub rarity: PetRarity,
115 pub level: i64,
116 pub shards_amount: i64,
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 stats,
144 }
145 }
146
147 pub fn recompute_stats(&mut self, template: &PetTemplate) {
148 self.stats = template
149 .stats
150 .iter()
151 .map(|s| PetComputedSecondaryStat {
152 attribute_id: s.attribute_id,
153 value: s.base_value + s.per_level_value * (self.level - 1),
154 })
155 .collect();
156 }
157}
158
159#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, Default)]
160#[tsify(from_wasm_abi)]
161pub struct EquippedPets {
162 pub slotted: BTreeMap<PetSlotId, Pet>,
163}
164
165impl EquippedPets {
166 pub fn new() -> Self {
167 Self {
168 slotted: BTreeMap::new(),
169 }
170 }
171
172 pub fn all_pets(&self) -> Vec<&Pet> {
173 self.slotted.values().collect()
174 }
175
176 pub fn has_pet(&self, pet_id: PetId) -> bool {
177 self.slotted.values().any(|p| p.template_id == pet_id)
178 }
179
180 pub fn get_mut_by_id(&mut self, pet_id: PetId) -> Option<&mut Pet> {
181 self.slotted.values_mut().find(|p| p.template_id == pet_id)
182 }
183}
184
185#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
186pub struct UpgradedPetsMap(pub std::collections::HashMap<PetId, (i64, i64)>);
187
188impl UpgradedPetsMap {
189 pub fn insert(&mut self, id: PetId, levels: (i64, i64)) {
190 self.0.insert(id, levels);
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Tsify)]
196#[tsify(namespace)]
197pub enum PetCaseRollType {
198 Small,
199 Big,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
203pub struct PetShard {
204 pub pet_id: PetId,
205 pub shards_amount: i64,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
209pub struct PetDrop {
210 pub template: PetTemplate,
211 pub is_new: bool,
212 pub is_checkpoint: bool,
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use uuid::uuid;
219
220 const RARITY_ID: PetRarityId = uuid!("00000000-0000-0000-0000-000000000001");
221 const ATTR_ID_1: AttributeId = uuid!("00000000-0000-0000-0000-00000000000a");
222 const ATTR_ID_2: AttributeId = uuid!("00000000-0000-0000-0000-00000000000b");
223 const PET_ID_1: PetId = uuid!("10000000-0000-0000-0000-000000000001");
224 const PET_ID_2: PetId = uuid!("10000000-0000-0000-0000-000000000002");
225 const PET_ID_3: PetId = uuid!("10000000-0000-0000-0000-000000000003");
226 const RANDOM_ID: PetId = uuid!("ffffffff-ffff-ffff-ffff-ffffffffffff");
227
228 fn make_test_rarity() -> PetRarity {
229 PetRarity {
230 id: RARITY_ID,
231 name: Default::default(),
232 order: 1,
233 color: "#FFD700".to_string(),
234 bg_color: "#1A1A2E".to_string(),
235 icon_path: "rarity/common.png".to_string(),
236 square_icon_path: "rarity/common_square.png".to_string(),
237 }
238 }
239
240 fn make_test_template(id: PetId, stats: Vec<PetSecondaryStat>) -> PetTemplate {
241 PetTemplate {
242 id,
243 name: Default::default(),
244 icon_path: "pet/icon.png".to_string(),
245 spine_path: "pet/spine.json".to_string(),
246 rarity_id: RARITY_ID,
247 stats,
248 is_gacha_pet: false,
249 real_facet: crate::pet_facets::PetFacet::HeadStart,
250 fantasy_facet: crate::pet_facets::PetFacet::SecondSpark,
251 }
252 }
253
254 fn make_two_stats() -> Vec<PetSecondaryStat> {
255 vec![
256 PetSecondaryStat {
257 attribute_id: ATTR_ID_1,
258 base_value: 10,
259 per_level_value: 2,
260 },
261 PetSecondaryStat {
262 attribute_id: ATTR_ID_2,
263 base_value: 50,
264 per_level_value: 5,
265 },
266 ]
267 }
268
269 fn make_pet(id: PetId) -> Pet {
270 let template = make_test_template(id, make_two_stats());
271 Pet::from_template(&template, make_test_rarity(), 1, 0)
272 }
273
274 #[test]
275 fn test_pet_from_template() {
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(), 1, 0);
279
280 assert_eq!(pet.template_id, PET_ID_1);
281 assert_eq!(pet.level, 1);
282 assert_eq!(pet.stats.len(), 2);
283 assert_eq!(pet.stats[0].attribute_id, ATTR_ID_1);
285 assert_eq!(pet.stats[0].value, 10);
286 assert_eq!(pet.stats[1].attribute_id, ATTR_ID_2);
287 assert_eq!(pet.stats[1].value, 50);
288 }
289
290 #[test]
291 fn test_pet_from_template_higher_level() {
292 let stats = make_two_stats();
293 let template = make_test_template(PET_ID_1, stats);
294 let pet = Pet::from_template(&template, make_test_rarity(), 5, 10);
295
296 assert_eq!(pet.level, 5);
297 assert_eq!(pet.shards_amount, 10);
298 assert_eq!(pet.stats[0].value, 10 + 2 * 4); assert_eq!(pet.stats[1].value, 50 + 5 * 4); }
302
303 #[test]
304 fn test_equipped_pets_has_pet_and_get_mut() {
305 let mut equipped = EquippedPets::new();
306 equipped.slotted.insert(0, make_pet(PET_ID_1));
307
308 assert!(equipped.has_pet(PET_ID_1));
309 assert!(!equipped.has_pet(RANDOM_ID));
310
311 assert!(equipped.get_mut_by_id(PET_ID_1).is_some());
312 assert!(equipped.get_mut_by_id(RANDOM_ID).is_none());
313 }
314
315 #[test]
316 fn test_equipped_pets_multiple_pets() {
317 let mut equipped = EquippedPets::new();
318 equipped.slotted.insert(0, make_pet(PET_ID_1));
319 equipped.slotted.insert(1, make_pet(PET_ID_2));
320 equipped.slotted.insert(2, make_pet(PET_ID_3));
321
322 assert_eq!(equipped.all_pets().len(), 3);
323 }
324}