overlord_event_system/
state.rs

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::fighting::ActiveFight;
12use essences::gift::Gift;
13use essences::items::Item;
14use essences::mail::Mail;
15use essences::offers::OffersInfo;
16use essences::prelude::*;
17use essences::progress_pass::ProgressPassState;
18use essences::pvp::PVPState;
19use essences::quest::QuestsGroups;
20use essences::referrals::Patron;
21
22use event_system::state::State;
23
24use crate::BehaviorRegistry;
25use crate::bundles::{bundle_raw_afk_step_to_element, bundle_raw_step_to_element};
26use crate::game_config_helpers::GameConfigLookup;
27use crate::party::Party;
28use schemars::JsonSchema;
29use strum::{EnumIter, IntoEnumIterator};
30
31/// Status value stored in the `user_accesses` table (e.g. admin). Used when querying DB.
32#[derive(
33    Clone, Copy, PartialEq, Eq, Debug, Tsify, Serialize, Deserialize, JsonSchema, EnumIter,
34)]
35#[serde(rename_all = "lowercase")]
36pub enum UserStatus {
37    Admin,
38    Banned,
39}
40
41impl UserStatus {
42    /// String stored in the database.
43    pub fn as_str(self) -> &'static str {
44        match self {
45            UserStatus::Admin => "admin",
46            UserStatus::Banned => "banned",
47        }
48    }
49}
50
51/// Permission flags for the current user (e.g. cheats, logout).
52/// Populated from the `user_accesses` table when state is loaded; e.g. status `admin` grants all.
53#[derive(Clone, PartialEq, Eq, Debug, Tsify, Serialize, Deserialize, JsonSchema, EnumIter)]
54pub enum UserPermissions {
55    CheatsAccess,
56    LogOutAccess,
57}
58
59impl UserPermissions {
60    /// All permission variants. Admin users receive this full list so new variants are included automatically.
61    pub fn all() -> Vec<Self> {
62        Self::iter().collect()
63    }
64}
65
66#[derive(Clone, PartialEq, Eq, Default, Debug, Tsify, Serialize, Deserialize, JsonSchema)]
67#[tsify(into_wasm_abi)]
68pub struct OverlordState {
69    pub character_state: CharacterState,
70    pub blocked_character_ids: Vec<uuid::Uuid>,
71    pub active_fight: Option<ActiveFight>,
72    pub pvp_state: Option<PVPState>,
73    pub connection_store: HashMap<String, i64>,
74    pub quest_groups: QuestsGroups,
75    pub incoming_gifts: Vec<Gift>,
76    pub incoming_mails: Vec<Mail>,
77    pub patron: Option<Patron>,
78    pub referral_daily_reward_claimed: Option<bool>,
79    pub auto_chest: AutoChest,
80    pub arena: Arena,
81    pub dungeons: Dungeons,
82    pub offers_info: OffersInfo,
83    pub permissions: Vec<UserPermissions>,
84    pub party: Party,
85    pub progress_pass: ProgressPassState,
86}
87
88impl State for OverlordState {
89    /// Compares version from database with local
90    fn cmp_db_updated(&self, db_updated: &Self) -> bool {
91        self.character_state.character == db_updated.character_state.character
92            && self.character_state.inventory == db_updated.character_state.inventory
93    }
94
95    fn is_ticker_paused(&self) -> bool {
96        self.active_fight.as_ref().is_some_and(|fight| fight.paused)
97    }
98}
99
100impl OverlordState {
101    pub fn claim_vassal_reward(
102        &mut self,
103        vassal_id: uuid::Uuid,
104        claim_time: chrono::DateTime<chrono::Utc>,
105    ) -> anyhow::Result<()> {
106        match self
107            .character_state
108            .vassals
109            .iter_mut()
110            .find(|vassal| vassal.character_id == vassal_id)
111        {
112            Some(vassal) => {
113                vassal.claim_reward(claim_time);
114                Ok(())
115            }
116            None => anyhow::bail!("No vassal with given id={}", vassal_id),
117        }
118    }
119
120    pub fn claim_suzerain_reward(
121        &mut self,
122        claim_time: chrono::DateTime<chrono::Utc>,
123    ) -> anyhow::Result<()> {
124        let Some(suzerain) = self.character_state.suzerain.as_mut() else {
125            anyhow::bail!("No suzerain to update")
126        };
127        suzerain.claim_reward(claim_time);
128        Ok(())
129    }
130
131    pub fn compute_description_values_for_ability(
132        &self,
133        ability: &Ability,
134        behaviors: &BehaviorRegistry,
135        config: &GameConfig,
136    ) -> Vec<f64> {
137        let Some(template) = config.ability_template(ability.template_id) else {
138            tracing::error!(
139                "Failed to get template for ability template_id={}",
140                ability.template_id
141            );
142            return vec![];
143        };
144
145        let Some(ref description_script) = template.description_values_script else {
146            return vec![];
147        };
148
149        match crate::behaviors::ui_values::description_values(
150            &crate::behaviors::ui_values::DescriptionValuesCtx {
151                ability_level: ability.level,
152                ability_template_id: ability.template_id,
153                script: description_script,
154                config,
155                lookups: behaviors.lookups(),
156            },
157        ) {
158            Ok(values) => values,
159            Err(err) => {
160                tracing::error!("Error computing description values: {err}");
161                vec![]
162            }
163        }
164    }
165
166    pub fn compute_description_values_for_talent(
167        &self,
168        talent_template: &essences::talent_tree::TalentTemplate,
169        level: i64,
170    ) -> Vec<f64> {
171        let Some(ref description_script) = talent_template.description_values_script else {
172            return vec![];
173        };
174
175        match crate::behaviors::ui_values::talent_description_values(
176            &crate::behaviors::ui_values::TalentDescriptionValuesCtx {
177                talent_level: level,
178                script: description_script,
179            },
180        ) {
181            Ok(values) => values,
182            Err(err) => {
183                tracing::error!("Error computing talent description values: {err}");
184                vec![]
185            }
186        }
187    }
188
189    pub fn compute_description_values_template(
190        &self,
191        ability_template: &AbilityTemplate,
192        behaviors: &BehaviorRegistry,
193        config: &GameConfig,
194    ) -> Vec<f64> {
195        let ability = Ability::from_template(ability_template, None, None);
196
197        self.compute_description_values_for_ability(&ability, behaviors, config)
198    }
199
200    pub fn compute_afk_reward(
201        &self,
202        game_config: &GameConfig,
203        behaviors: &BehaviorRegistry,
204    ) -> Vec<BundleElement> {
205        let mut elements = vec![];
206
207        let Some(bundle) = game_config.bundle(game_config.afk_rewards_settings.bundle_id) else {
208            tracing::error!(
209                "Couldn't find afk bundle with id = {}",
210                game_config.afk_rewards_settings.bundle_id
211            );
212            return vec![];
213        };
214
215        let now = ::time::utc_now();
216
217        for step in &bundle.steps {
218            elements.push(bundle_raw_afk_step_to_element(
219                step,
220                &self.character_state,
221                now,
222                behaviors,
223                game_config,
224            ));
225        }
226
227        elements
228    }
229
230    pub fn compute_afk_instant_reward(
231        &self,
232        game_config: &GameConfig,
233        behaviors: &BehaviorRegistry,
234    ) -> Vec<BundleElement> {
235        let mut elements = vec![];
236
237        let Some(bundle) = game_config.bundle(game_config.afk_rewards_settings.bundle_id) else {
238            tracing::error!(
239                "Couldn't find afk bundle with id = {}",
240                game_config.afk_rewards_settings.bundle_id
241            );
242            return vec![];
243        };
244
245        let afk_settings = &game_config.afk_rewards_settings;
246        let duration_sec = afk_settings
247            .instant_reward_duration_sec
248            .min(afk_settings.max_possible_time_sec);
249        let fake_now = self.character_state.character.last_afk_reward_claimed_at
250            + chrono::Duration::seconds(duration_sec as i64);
251
252        for step in &bundle.steps {
253            elements.push(bundle_raw_afk_step_to_element(
254                step,
255                &self.character_state,
256                fake_now,
257                behaviors,
258                game_config,
259            ));
260        }
261
262        elements
263    }
264
265    pub fn compute_bundle_reward(
266        &self,
267        game_config: &GameConfig,
268        behaviors: &BehaviorRegistry,
269        bundle_id: BundleId,
270    ) -> Vec<BundleElement> {
271        let mut elements = vec![];
272
273        let Some(bundle) = game_config.bundle(bundle_id) else {
274            tracing::error!(
275                "Couldn't find bundle with id = {}",
276                game_config.afk_rewards_settings.bundle_id
277            );
278            return vec![];
279        };
280
281        for step in &bundle.steps {
282            elements.push(bundle_raw_step_to_element(
283                step,
284                &self.character_state,
285                behaviors,
286                game_config,
287            ));
288        }
289
290        elements
291    }
292
293    pub fn calculate_item_power(
294        &self,
295        item: Item,
296        game_config: &GameConfig,
297        behaviors: &BehaviorRegistry,
298    ) -> anyhow::Result<i64> {
299        crate::behaviors::power::item_power(&crate::behaviors::power::ItemPowerCtx {
300            character: &self.character_state,
301            item: &item,
302            config: game_config,
303            lookups: behaviors.lookups(),
304        })
305    }
306
307    pub fn get_active_fight_player(&self) -> Option<&Entity> {
308        self.active_fight
309            .as_ref()
310            .and_then(|fight| fight.get_player())
311    }
312
313    pub fn get_active_fight_player_mut(&mut self) -> Option<&mut Entity> {
314        self.active_fight
315            .as_mut()
316            .and_then(|fight| fight.get_player_mut())
317    }
318}