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