1use std::collections::HashMap;
2
3use configs::game_config::GameConfig;
4use essences::abilities::{Ability, AbilityTemplate};
5use essences::arena::Arena;
6use essences::autochest::AutoChest;
7use essences::bundles::{BundleElement, BundleId};
8use essences::character_state::CharacterState;
9use essences::dungeons::Dungeons;
10use essences::entity::Entity;
11use essences::fight_breakdown::FightBreakdown;
12use essences::fighting::ActiveFight;
13use essences::flip::FlipState;
14use essences::gift::Gift;
15use essences::items::Item;
16use essences::mail::Mail;
17use essences::offers::OffersInfo;
18use essences::prelude::*;
19use essences::progress_pass::ProgressPassState;
20use essences::pvp::PVPState;
21use essences::quest::QuestsGroups;
22use essences::ratings::RatingRewardAvailability;
23use essences::referrals::Patron;
24
25use event_system::state::State;
26
27use crate::BehaviorRegistry;
28use crate::bundles::{bundle_raw_afk_step_to_element, bundle_raw_step_to_element};
29use crate::game_config_helpers::GameConfigLookup;
30use crate::party::Party;
31use schemars::JsonSchema;
32use strum::{EnumIter, IntoEnumIterator};
33
34#[derive(
36 Clone, Copy, PartialEq, Eq, Debug, Tsify, Serialize, Deserialize, JsonSchema, EnumIter,
37)]
38#[serde(rename_all = "lowercase")]
39pub enum UserStatus {
40 Admin,
41 Banned,
42}
43
44impl UserStatus {
45 pub fn as_str(self) -> &'static str {
47 match self {
48 UserStatus::Admin => "admin",
49 UserStatus::Banned => "banned",
50 }
51 }
52}
53
54#[derive(Clone, PartialEq, Eq, Debug, Tsify, Serialize, Deserialize, JsonSchema, EnumIter)]
57pub enum UserPermissions {
58 CheatsAccess,
59 LogOutAccess,
60}
61
62impl UserPermissions {
63 pub fn all() -> Vec<Self> {
65 Self::iter().collect()
66 }
67}
68
69#[derive(Clone, PartialEq, Eq, Default, Debug, Tsify, Serialize, Deserialize, JsonSchema)]
70#[tsify(into_wasm_abi)]
71pub struct OverlordState {
72 pub character_state: CharacterState,
73 pub blocked_character_ids: Vec<uuid::Uuid>,
74 pub flip_state: FlipState,
76 pub active_fight: Option<ActiveFight>,
77 pub pvp_state: Option<PVPState>,
78 pub connection_store: HashMap<String, i64>,
79 pub quest_groups: QuestsGroups,
80 pub incoming_gifts: Vec<Gift>,
81 pub incoming_mails: Vec<Mail>,
82 pub patron: Option<Patron>,
83 pub referral_daily_reward_claimed: Option<bool>,
84 pub auto_chest: AutoChest,
85 pub arena: Arena,
86 pub dungeons: Dungeons,
87 pub offers_info: OffersInfo,
88 pub permissions: Vec<UserPermissions>,
89 pub party: Party,
90 pub progress_pass: ProgressPassState,
91 pub rating_reward_availability: Vec<RatingRewardAvailability>,
95 pub last_fight_breakdown: Option<FightBreakdown>,
108}
109
110impl State for OverlordState {
111 fn cmp_db_updated(&self, db_updated: &Self) -> bool {
113 self.character_state.character == db_updated.character_state.character
114 && self.character_state.inventory == db_updated.character_state.inventory
115 }
116
117 fn is_ticker_paused(&self) -> bool {
118 self.active_fight.as_ref().is_some_and(|fight| fight.paused)
119 }
120}
121
122impl OverlordState {
123 pub fn claim_vassal_reward(
124 &mut self,
125 vassal_id: uuid::Uuid,
126 claim_time: chrono::DateTime<chrono::Utc>,
127 ) -> anyhow::Result<()> {
128 match self
129 .character_state
130 .vassals
131 .iter_mut()
132 .find(|vassal| vassal.character_id == vassal_id)
133 {
134 Some(vassal) => {
135 vassal.claim_reward(claim_time);
136 Ok(())
137 }
138 None => anyhow::bail!("No vassal with given id={}", vassal_id),
139 }
140 }
141
142 pub fn claim_suzerain_reward(
143 &mut self,
144 claim_time: chrono::DateTime<chrono::Utc>,
145 ) -> anyhow::Result<()> {
146 let Some(suzerain) = self.character_state.suzerain.as_mut() else {
147 anyhow::bail!("No suzerain to update")
148 };
149 suzerain.claim_reward(claim_time);
150 Ok(())
151 }
152
153 pub fn compute_description_values_for_ability(
154 &self,
155 ability: &Ability,
156 behaviors: &BehaviorRegistry,
157 config: &GameConfig,
158 ) -> Vec<f64> {
159 let Some(template) = config.ability_template(ability.template_id) else {
160 tracing::error!(
161 "Failed to get template for ability template_id={}",
162 ability.template_id
163 );
164 return vec![];
165 };
166
167 let Some(ref description_script) = template.description_values_script else {
168 return vec![];
169 };
170
171 match crate::behaviors::ui_values::description_values(
172 &crate::behaviors::ui_values::DescriptionValuesCtx {
173 ability_level: ability.level,
174 ability_template_id: ability.template_id,
175 script: description_script,
176 config,
177 lookups: behaviors.lookups(),
178 },
179 ) {
180 Ok(values) => values,
181 Err(err) => {
182 tracing::error!("Error computing description values: {err}");
183 vec![]
184 }
185 }
186 }
187
188 pub fn compute_description_values_for_talent(
189 &self,
190 talent_template: &essences::talent_tree::TalentTemplate,
191 level: i64,
192 ) -> Vec<f64> {
193 let Some(ref description_script) = talent_template.description_values_script else {
194 return vec![];
195 };
196
197 match crate::behaviors::ui_values::talent_description_values(
198 &crate::behaviors::ui_values::TalentDescriptionValuesCtx {
199 talent_level: level,
200 script: description_script,
201 },
202 ) {
203 Ok(values) => values,
204 Err(err) => {
205 tracing::error!("Error computing talent description values: {err}");
206 vec![]
207 }
208 }
209 }
210
211 pub fn compute_description_values_template(
212 &self,
213 ability_template: &AbilityTemplate,
214 behaviors: &BehaviorRegistry,
215 config: &GameConfig,
216 ) -> Vec<f64> {
217 let ability = Ability::from_template(ability_template, None, None);
218
219 self.compute_description_values_for_ability(&ability, behaviors, config)
220 }
221
222 pub fn compute_afk_reward(
223 &self,
224 game_config: &GameConfig,
225 behaviors: &BehaviorRegistry,
226 ) -> Vec<BundleElement> {
227 let mut elements = vec![];
228
229 let Some(bundle) = game_config.bundle(game_config.afk_rewards_settings.bundle_id) else {
230 tracing::error!(
231 "Couldn't find afk bundle with id = {}",
232 game_config.afk_rewards_settings.bundle_id
233 );
234 return vec![];
235 };
236
237 let now = ::time::utc_now();
238
239 for step in &bundle.steps {
240 elements.push(bundle_raw_afk_step_to_element(
241 step,
242 &self.character_state,
243 now,
244 true,
247 behaviors,
248 game_config,
249 ));
250 }
251
252 elements
253 }
254
255 pub fn compute_afk_instant_reward(
256 &self,
257 game_config: &GameConfig,
258 behaviors: &BehaviorRegistry,
259 ) -> Vec<BundleElement> {
260 let mut elements = vec![];
261
262 let Some(bundle) = game_config.bundle(game_config.afk_rewards_settings.bundle_id) else {
263 tracing::error!(
264 "Couldn't find afk bundle with id = {}",
265 game_config.afk_rewards_settings.bundle_id
266 );
267 return vec![];
268 };
269
270 let afk_settings = &game_config.afk_rewards_settings;
271 let duration_sec = afk_settings
272 .instant_reward_duration_sec
273 .min(afk_settings.max_possible_time_sec);
274 let fake_now = self.character_state.character.last_afk_reward_claimed_at
275 + chrono::Duration::seconds(duration_sec as i64);
276
277 for step in &bundle.steps {
278 elements.push(bundle_raw_afk_step_to_element(
279 step,
280 &self.character_state,
281 fake_now,
282 false,
285 behaviors,
286 game_config,
287 ));
288 }
289
290 elements
291 }
292
293 pub fn compute_bundle_reward(
294 &self,
295 game_config: &GameConfig,
296 behaviors: &BehaviorRegistry,
297 bundle_id: BundleId,
298 ) -> Vec<BundleElement> {
299 let mut elements = vec![];
300
301 let Some(bundle) = game_config.bundle(bundle_id) else {
302 tracing::error!(
303 "Couldn't find bundle with id = {}",
304 game_config.afk_rewards_settings.bundle_id
305 );
306 return vec![];
307 };
308
309 for step in &bundle.steps {
310 elements.push(bundle_raw_step_to_element(
311 step,
312 &self.character_state,
313 behaviors,
314 game_config,
315 ));
316 }
317
318 elements
319 }
320
321 pub fn calculate_item_power(
322 &self,
323 item: Item,
324 game_config: &GameConfig,
325 behaviors: &BehaviorRegistry,
326 ) -> anyhow::Result<i64> {
327 Self::calculate_item_power_for_character_state(
328 &self.character_state,
329 item,
330 game_config,
331 behaviors,
332 )
333 }
334
335 pub fn calculate_item_power_for_character_state(
340 character_state: &CharacterState,
341 item: Item,
342 game_config: &GameConfig,
343 behaviors: &BehaviorRegistry,
344 ) -> anyhow::Result<i64> {
345 crate::behaviors::power::item_power(&crate::behaviors::power::ItemPowerCtx {
346 character: character_state,
347 item: &item,
348 config: game_config,
349 lookups: behaviors.lookups(),
350 })
351 }
352
353 pub fn calculate_power_with_item_equipped(
366 &self,
367 item_id: Uuid,
368 game_config: &GameConfig,
369 behaviors: &BehaviorRegistry,
370 ) -> anyhow::Result<i64> {
371 let item = self
372 .character_state
373 .inventory
374 .iter()
375 .find(|item| item.id == item_id)
376 .ok_or_else(|| anyhow::anyhow!("item {item_id} not found in inventory"))?;
377
378 self.calculate_power_with_candidate_equipped(item, game_config, behaviors)
379 }
380
381 pub fn calculate_power_with_candidate_equipped(
386 &self,
387 candidate: &Item,
388 game_config: &GameConfig,
389 behaviors: &BehaviorRegistry,
390 ) -> anyhow::Result<i64> {
391 let equipment_slot_key = candidate.equipment_slot_key();
392
393 let mut character_state = self.character_state.clone();
394 for inv_item in &mut character_state.inventory {
395 if inv_item.equipment_slot_key() == equipment_slot_key {
396 inv_item.is_equipped = false;
397 }
398 }
399
400 match character_state
401 .inventory
402 .iter_mut()
403 .find(|inv_item| inv_item.id == candidate.id)
404 {
405 Some(inv_item) => inv_item.is_equipped = true,
406 None => {
407 let mut candidate = candidate.clone();
408 candidate.is_equipped = true;
409 character_state.inventory.push(candidate);
410 }
411 }
412
413 crate::behaviors::power::character_power(&crate::behaviors::power::CharacterPowerCtx {
414 character: &character_state,
415 config: game_config,
416 lookups: behaviors.lookups(),
417 })
418 }
419
420 pub fn calculate_current_power(
423 &self,
424 game_config: &GameConfig,
425 behaviors: &BehaviorRegistry,
426 ) -> anyhow::Result<i64> {
427 crate::behaviors::power::character_power(&crate::behaviors::power::CharacterPowerCtx {
428 character: &self.character_state,
429 config: game_config,
430 lookups: behaviors.lookups(),
431 })
432 }
433
434 pub fn auto_chest_power_maps(
451 &self,
452 candidates: &[Item],
453 game_config: &GameConfig,
454 behaviors: &BehaviorRegistry,
455 ) -> anyhow::Result<(
456 HashMap<Uuid, i64>,
457 HashMap<essences::items::EquipmentSlotKey, i64>,
458 )> {
459 let mut candidate_powers = HashMap::new();
460 for candidate in candidates {
461 let power =
462 self.calculate_power_with_candidate_equipped(candidate, game_config, behaviors)?;
463 candidate_powers.insert(candidate.id, power);
464 }
465
466 let baseline = self.calculate_current_power(game_config, behaviors)?;
467 let mut equipped_powers = HashMap::new();
468 for item in &self.character_state.inventory {
469 if item.is_equipped {
470 equipped_powers.insert(item.equipment_slot_key(), baseline);
471 }
472 }
473
474 Ok((candidate_powers, equipped_powers))
475 }
476
477 pub fn calculate_equip_power_delta(
482 &self,
483 item_id: Uuid,
484 game_config: &GameConfig,
485 behaviors: &BehaviorRegistry,
486 ) -> anyhow::Result<i64> {
487 let power_after =
488 self.calculate_power_with_item_equipped(item_id, game_config, behaviors)?;
489 Ok(power_after - self.character_state.character.power)
490 }
491
492 pub fn calculate_item_power_full(
501 &self,
502 item_id: Uuid,
503 game_config: &GameConfig,
504 behaviors: &BehaviorRegistry,
505 ) -> anyhow::Result<i64> {
506 let equipment_slot_key = self
507 .character_state
508 .inventory
509 .iter()
510 .find(|item| item.id == item_id)
511 .map(Item::equipment_slot_key)
512 .ok_or_else(|| anyhow::anyhow!("item {item_id} not found in inventory"))?;
513
514 let power_with =
515 self.calculate_power_with_item_equipped(item_id, game_config, behaviors)?;
516
517 let mut character_state = self.character_state.clone();
518 for inv_item in &mut character_state.inventory {
519 if inv_item.equipment_slot_key() == equipment_slot_key {
520 inv_item.is_equipped = false;
521 }
522 }
523 let power_slot_empty = crate::behaviors::power::character_power(
524 &crate::behaviors::power::CharacterPowerCtx {
525 character: &character_state,
526 config: game_config,
527 lookups: behaviors.lookups(),
528 },
529 )?;
530
531 Ok(power_with - power_slot_empty)
532 }
533
534 pub fn get_active_fight_player(&self) -> Option<&Entity> {
535 self.active_fight
536 .as_ref()
537 .and_then(|fight| fight.get_player())
538 }
539
540 pub fn get_active_fight_player_mut(&mut self) -> Option<&mut Entity> {
541 self.active_fight
542 .as_mut()
543 .and_then(|fight| fight.get_player_mut())
544 }
545}