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