1use crate::abilities::AbilityId;
2use crate::autochest::AutoChestFilterId;
3use crate::class::ClassId;
4use crate::pets::PetId;
5use crate::talent_tree::TalentId;
6
7use rand::Rng;
8
9use super::types::CustomValuesMap;
10
11use crate::prelude::*;
12use strum_macros::{Display, EnumString};
13
14#[tsify_next::declare]
15pub type CharacterId = uuid::Uuid;
16
17#[derive(
18 Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, EnumString, Display,
19)]
20pub enum FightClass {
21 Mage,
22 Warrior,
23 Archer,
24}
25
26impl CustomType for FightClass {
27 fn build(mut builder: TypeBuilder<Self>) {
28 builder
29 .with_name("FightClass")
30 .with_get("str", |vts: &mut FightClass| FightClass::to_string(vts));
31 }
32}
33
34#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, Tsify, CustomType)]
35pub struct Character {
36 pub id: CharacterId,
37 pub user_id: uuid::Uuid,
38 pub created_at: chrono::DateTime<chrono::Utc>,
39 pub class: ClassId,
40 pub character_level: i64,
41 pub character_experience: i64,
42 pub patron_level: i64,
43 pub patron_experience: i64,
44 pub cases: i64, pub current_chapter_level: i64,
46 pub current_fight_number: i64,
47 pub max_vassal_slots_count: i64,
48 pub custom_values: CustomValuesMap,
49 pub item_case_level: i64,
50 pub ability_case_level: i64,
52 pub opened_ability_cases: i64,
55 pub ability_gacha_wishlist: Vec<AbilityId>,
57 pub ability_slot_levels: Vec<i64>,
59 #[cfg_attr(target_arch = "wasm32", schemars(skip))]
60 pub item_case_upgrade_finish_at: Option<chrono::DateTime<chrono::Utc>>,
61 pub power: i64,
62 pub fight_class: Option<FightClass>,
63 pub arena_rating: i64,
64 pub arena_stars: i64,
65 #[cfg_attr(target_arch = "wasm32", schemars(skip))]
66 pub arena_last_pvp_at: Option<chrono::DateTime<chrono::Utc>>,
67 pub last_boss_fight_won: bool,
68 pub auto_chest_enabled: bool,
69 pub last_filter_used_id: Option<AutoChestFilterId>,
70 #[cfg_attr(target_arch = "wasm32", schemars(skip))]
71 pub last_item_case_speedup_at: Option<chrono::DateTime<chrono::Utc>>,
72 #[cfg_attr(target_arch = "wasm32", schemars(skip))]
73 pub last_afk_reward_claimed_at: chrono::DateTime<chrono::Utc>,
74 pub afk_reward_seed: u64,
75 #[cfg_attr(target_arch = "wasm32", schemars(skip))]
76 pub last_gacha_ad_roll_at: Option<chrono::DateTime<chrono::Utc>>,
77 pub speedup_ad_daily_count: i64,
79 pub active_loop_task_id: Option<uuid::Uuid>,
80 pub purchases_banned: bool,
81 pub completed_tutorials: Vec<i16>,
82 pub pet_slot_levels: Vec<i64>,
84 pub pet_case_level: i64,
86 pub opened_pet_cases: i64,
88 pub pet_gacha_wishlist: Vec<PetId>,
90 pub party_character_id: Option<uuid::Uuid>,
92 pub talent_upgrading_id: Option<TalentId>,
95 #[cfg_attr(target_arch = "wasm32", schemars(skip))]
97 pub talent_upgrade_finish_at: Option<chrono::DateTime<chrono::Utc>>,
98 pub last_online_at: chrono::DateTime<chrono::Utc>,
100 pub instant_reward_gems_press_count: i64,
102 pub afk_boost_pending_stacks: i64,
105 #[cfg_attr(target_arch = "wasm32", schemars(skip))]
109 pub bird_cooldown_until: Option<chrono::DateTime<chrono::Utc>>,
110}
111
112impl PartialEq for Character {
113 fn eq(&self, other: &Self) -> bool {
114 fn eq_system_time(
117 a: &chrono::DateTime<chrono::Utc>,
118 b: &chrono::DateTime<chrono::Utc>,
119 ) -> bool {
120 a.signed_duration_since(*b).abs() < chrono::TimeDelta::microseconds(1)
121 }
122
123 fn eq_opt_time(
124 a: &Option<chrono::DateTime<chrono::Utc>>,
125 b: &Option<chrono::DateTime<chrono::Utc>>,
126 ) -> bool {
127 match (a, b) {
128 (Some(x), Some(y)) => eq_system_time(x, y),
129 (None, None) => true,
130 _ => false,
131 }
132 }
133
134 let Character {
135 id,
136 user_id,
137 class,
138 character_level,
139 character_experience,
140 patron_level,
141 patron_experience,
142 cases,
143 current_chapter_level,
144 current_fight_number,
145 max_vassal_slots_count,
146 custom_values,
147 item_case_level,
148 ability_case_level,
149 opened_ability_cases,
150 item_case_upgrade_finish_at,
151 power,
152 fight_class,
153 arena_rating,
154 arena_stars,
155 arena_last_pvp_at,
156 last_boss_fight_won,
157 auto_chest_enabled,
158 last_filter_used_id,
159 last_item_case_speedup_at,
160 last_afk_reward_claimed_at,
161 afk_reward_seed,
162 last_gacha_ad_roll_at,
163 speedup_ad_daily_count,
164 active_loop_task_id,
165 purchases_banned,
166 created_at,
167 ability_gacha_wishlist,
168 ability_slot_levels,
169 completed_tutorials,
170 pet_slot_levels,
171 pet_case_level,
172 opened_pet_cases,
173 pet_gacha_wishlist,
174 party_character_id,
175 talent_upgrading_id,
176 talent_upgrade_finish_at,
177 last_online_at,
178 instant_reward_gems_press_count,
179 afk_boost_pending_stacks,
180 bird_cooldown_until,
181 } = self;
182
183 let Character {
184 id: o_id,
185 user_id: o_user_id,
186 class: o_class,
187 character_level: o_character_level,
188 character_experience: o_character_experience,
189 patron_level: o_patron_level,
190 patron_experience: o_patron_experience,
191 cases: o_cases,
192 current_chapter_level: o_current_chapter_level,
193 current_fight_number: o_current_fight_number,
194 max_vassal_slots_count: o_max_vassal_slots_count,
195 custom_values: o_custom_values,
196 item_case_level: o_item_case_level,
197 ability_case_level: o_ability_case_level,
198 opened_ability_cases: o_opened_ability_cases,
199 item_case_upgrade_finish_at: o_item_case_upgrade_finish_at,
200 power: o_power,
201 fight_class: o_fight_class,
202 arena_rating: o_arena_rating,
203 arena_stars: o_arena_stars,
204 arena_last_pvp_at: o_arena_last_pvp_at,
205 last_boss_fight_won: o_last_boss_fight_won,
206 auto_chest_enabled: o_auto_chest_enabled,
207 last_filter_used_id: o_last_filter_used_id,
208 last_item_case_speedup_at: o_last_item_case_speedup_at,
209 last_afk_reward_claimed_at: o_last_afk_reward_claimed_at,
210 afk_reward_seed: o_afk_reward_seed,
211 last_gacha_ad_roll_at: o_last_gacha_ad_roll_at,
212 speedup_ad_daily_count: o_speedup_ad_daily_count,
213 active_loop_task_id: o_active_loop_task_id,
214 purchases_banned: o_purchases_banned,
215 created_at: o_created_at,
216 ability_gacha_wishlist: o_ability_gacha_wishlist,
217 ability_slot_levels: o_ability_slot_levels,
218 completed_tutorials: o_completed_tutorials,
219 pet_slot_levels: o_pet_slot_levels,
220 pet_case_level: o_pet_case_level,
221 opened_pet_cases: o_opened_pet_cases,
222 pet_gacha_wishlist: o_pet_gacha_wishlist,
223 party_character_id: o_party_character_id,
224 talent_upgrading_id: o_talent_upgrading_id,
225 talent_upgrade_finish_at: o_talent_upgrade_finish_at,
226 last_online_at: o_last_online_at,
227 instant_reward_gems_press_count: o_instant_reward_gems_press_count,
228 afk_boost_pending_stacks: o_afk_boost_pending_stacks,
229 bird_cooldown_until: o_bird_cooldown_until,
230 } = other;
231
232 id == o_id
233 && user_id == o_user_id
234 && class == o_class
235 && character_level == o_character_level
236 && character_experience == o_character_experience
237 && patron_level == o_patron_level
238 && patron_experience == o_patron_experience
239 && cases == o_cases
240 && current_chapter_level == o_current_chapter_level
241 && current_fight_number == o_current_fight_number
242 && max_vassal_slots_count == o_max_vassal_slots_count
243 && custom_values == o_custom_values
244 && item_case_level == o_item_case_level
245 && ability_case_level == o_ability_case_level
246 && opened_ability_cases == o_opened_ability_cases
247 && eq_opt_time(item_case_upgrade_finish_at, o_item_case_upgrade_finish_at)
248 && power == o_power
249 && fight_class == o_fight_class
250 && arena_rating == o_arena_rating
251 && arena_stars == o_arena_stars
252 && eq_opt_time(arena_last_pvp_at, o_arena_last_pvp_at)
253 && last_boss_fight_won == o_last_boss_fight_won
254 && auto_chest_enabled == o_auto_chest_enabled
255 && last_filter_used_id == o_last_filter_used_id
256 && eq_opt_time(last_item_case_speedup_at, o_last_item_case_speedup_at)
257 && eq_system_time(last_afk_reward_claimed_at, o_last_afk_reward_claimed_at)
258 && afk_reward_seed == o_afk_reward_seed
259 && eq_opt_time(last_gacha_ad_roll_at, o_last_gacha_ad_roll_at)
260 && speedup_ad_daily_count == o_speedup_ad_daily_count
261 && active_loop_task_id == o_active_loop_task_id
262 && purchases_banned == o_purchases_banned
263 && completed_tutorials == o_completed_tutorials
264 && pet_slot_levels == o_pet_slot_levels
265 && pet_case_level == o_pet_case_level
266 && opened_pet_cases == o_opened_pet_cases
267 && pet_gacha_wishlist == o_pet_gacha_wishlist
268 && party_character_id == o_party_character_id
269 && talent_upgrading_id == o_talent_upgrading_id
270 && eq_opt_time(talent_upgrade_finish_at, o_talent_upgrade_finish_at)
271 && eq_system_time(last_online_at, o_last_online_at)
272 && eq_system_time(created_at, o_created_at)
273 && ability_gacha_wishlist == o_ability_gacha_wishlist
274 && ability_slot_levels == o_ability_slot_levels
275 && instant_reward_gems_press_count == o_instant_reward_gems_press_count
276 && afk_boost_pending_stacks == o_afk_boost_pending_stacks
277 && eq_opt_time(bird_cooldown_until, o_bird_cooldown_until)
278 }
279}
280
281impl Eq for Character {}
282
283impl Default for Character {
284 fn default() -> Self {
285 Self {
286 id: Default::default(),
287 user_id: Default::default(),
288 created_at: ::time::utc_now(),
289 class: Default::default(),
290 character_level: 0,
291 character_experience: 0,
292 patron_level: 0,
293 patron_experience: 0,
294 cases: 0,
295 current_chapter_level: 0,
296 current_fight_number: 0,
297 max_vassal_slots_count: 0,
298 custom_values: Default::default(),
299 item_case_level: 0,
300 ability_case_level: 0,
301 opened_ability_cases: 0,
302 ability_gacha_wishlist: Vec::new(),
303 ability_slot_levels: Vec::new(),
304 item_case_upgrade_finish_at: None,
305 power: 0,
306 fight_class: None,
307 arena_rating: 0,
308 arena_stars: 0,
309 arena_last_pvp_at: None,
310 last_boss_fight_won: false,
311 auto_chest_enabled: false,
312 last_filter_used_id: None,
313 last_item_case_speedup_at: None,
314 last_afk_reward_claimed_at: ::time::utc_now(),
315 afk_reward_seed: 0,
316 last_gacha_ad_roll_at: None,
317 speedup_ad_daily_count: 0,
318 active_loop_task_id: None,
319 purchases_banned: false,
320 completed_tutorials: Default::default(),
321 pet_slot_levels: Vec::new(),
322 pet_case_level: 1,
323 opened_pet_cases: 0,
324 pet_gacha_wishlist: Vec::new(),
325 party_character_id: None,
326 talent_upgrading_id: None,
327 talent_upgrade_finish_at: None,
328 last_online_at: ::time::utc_now(),
329 instant_reward_gems_press_count: 0,
330 afk_boost_pending_stacks: 0,
331 bird_cooldown_until: None,
332 }
333 }
334}
335
336pub fn generate_new_afk_seed() -> u64 {
338 let mut rng = rand::rng();
339 rng.random_range(0..=i64::MAX as u64)
340}
341
342#[derive(Default, Serialize, Deserialize)]
343pub struct CharacterBuilder {
344 character: Character,
345}
346
347impl CharacterBuilder {
348 pub fn new() -> Self {
349 Self {
350 character: Character {
351 id: Uuid::now_v7(),
352 character_level: 1,
353 patron_level: 1,
354 item_case_level: 1,
355 ability_case_level: 1,
356 pet_case_level: 1,
357 max_vassal_slots_count: 2,
358 last_boss_fight_won: true,
359 power: 1,
360 afk_reward_seed: generate_new_afk_seed(),
361 ..Default::default()
362 },
363 }
364 }
365
366 pub fn with_id(mut self, id: CharacterId) -> Self {
367 self.character.id = id;
368 self
369 }
370
371 pub fn with_user_id(mut self, id: Uuid) -> Self {
372 self.character.user_id = id;
373 self
374 }
375
376 pub fn with_power(mut self, power: i64) -> Self {
377 self.character.power = power;
378 self
379 }
380
381 pub fn with_max_vassal_slots_count(mut self, slots_count: i64) -> Self {
382 self.character.max_vassal_slots_count = slots_count;
383 self
384 }
385
386 pub fn with_arena_rating(mut self, rating: i64) -> Self {
387 self.character.arena_rating = rating;
388 self
389 }
390
391 pub fn with_arena_stars(mut self, stars: i64) -> Self {
392 self.character.arena_stars = stars;
393 self
394 }
395
396 pub fn with_character_level(mut self, level: i64) -> Self {
397 self.character.character_level = level;
398 self
399 }
400
401 pub fn with_arena_last_pvp_at(mut self, time: Option<chrono::DateTime<chrono::Utc>>) -> Self {
402 self.character.arena_last_pvp_at = time;
403 self
404 }
405
406 pub fn with_last_boss_fight_won(mut self, won: bool) -> Self {
407 self.character.last_boss_fight_won = won;
408 self
409 }
410
411 pub fn with_cases(mut self, cases: i64) -> Self {
412 self.character.cases = cases;
413 self
414 }
415
416 pub fn with_item_case_level(mut self, level: i64) -> Self {
417 self.character.item_case_level = level;
418 self
419 }
420
421 pub fn with_class(mut self, class_id: Uuid) -> Self {
422 self.character.class = class_id;
423 self
424 }
425
426 pub fn with_custom_values(mut self, custom_values: CustomValuesMap) -> Self {
427 self.character.custom_values = custom_values;
428 self
429 }
430
431 pub fn with_ability_slot_levels(mut self, ability_slot_levels: Vec<i64>) -> Self {
432 self.character.ability_slot_levels = ability_slot_levels;
433 self
434 }
435
436 pub fn with_ability_gacha_wishlist(mut self, ability_gacha_wishlist: Vec<AbilityId>) -> Self {
437 self.character.ability_gacha_wishlist = ability_gacha_wishlist;
438 self
439 }
440
441 pub fn with_seed(mut self, seed: u64) -> Self {
442 self.character.afk_reward_seed = seed;
443 self
444 }
445
446 pub fn with_current_chapter_level(mut self, chapter_level: i64) -> Self {
447 self.character.current_chapter_level = chapter_level;
448 self
449 }
450
451 pub fn build(self) -> Character {
452 self.character
453 }
454}