overlord_event_system/
state.rs

1use std::collections::HashMap;
2
3use configs::game_config::GameConfig;
4use essences::abilities::{
5    Ability, AbilityRarity, AbilityTemplate, EquippedAbilities, UpgradedAbilitiesMap,
6};
7use essences::arena::Arena;
8use essences::autochest::AutoChest;
9use essences::bots::Bot;
10use essences::bundles::{BundleElement, BundleId};
11use essences::character_state::CharacterState;
12use essences::characters::Character;
13use essences::currency::CurrencyUnit;
14use essences::dungeons::Dungeons;
15use essences::effect::Effect;
16use essences::entity::{Entity, EntityAttributes};
17use essences::fighting::{ActiveDungeon, ActiveFight, EntityTeam};
18use essences::game::{Chapter, EntityAttribute};
19use essences::gift::Gift;
20use essences::items::{Attribute, Item, ItemAttribute, ItemRarity, ItemType};
21use essences::mail::Mail;
22use essences::offers::OffersInfo;
23use essences::opponents::RatingCalculationData;
24use essences::pets::{
25    EquippedPets, Pet, PetComputedSecondaryStat, PetRarity, PetTemplate, UpgradedPetsMap,
26};
27use essences::prelude::*;
28use essences::pvp::PVPState;
29use essences::quest::{QuestInstance, QuestsGroups};
30use essences::referrals::Patron;
31use essences::talent_tree::TalentLevelsMap;
32use essences::types::CustomValuesMap;
33use essences::vassals::{Suzerain, Vassal, VassalTask};
34
35use event_system::state::State;
36
37use crate::bundles::{bundle_raw_afk_step_to_element, bundle_raw_step_to_element};
38use crate::event::OverlordEvent;
39use crate::game_config_helpers::GameConfigLookup;
40use crate::party::Party;
41use crate::script::ScriptRunner;
42use schemars::JsonSchema;
43use strum::{EnumIter, IntoEnumIterator};
44
45/// Status value stored in the `user_accesses` table (e.g. admin). Used when querying DB.
46#[derive(
47    Clone, Copy, PartialEq, Eq, Debug, Tsify, Serialize, Deserialize, JsonSchema, EnumIter,
48)]
49#[serde(rename_all = "lowercase")]
50pub enum UserStatus {
51    Admin,
52    Banned,
53}
54
55impl UserStatus {
56    /// String stored in the database.
57    pub fn as_str(self) -> &'static str {
58        match self {
59            UserStatus::Admin => "admin",
60            UserStatus::Banned => "banned",
61        }
62    }
63}
64
65/// Permission flags for the current user (e.g. cheats, logout).
66/// Populated from the `user_accesses` table when state is loaded; e.g. status `admin` grants all.
67#[derive(Clone, PartialEq, Eq, Debug, Tsify, Serialize, Deserialize, JsonSchema, EnumIter)]
68pub enum UserPermissions {
69    CheatsAccess,
70    LogOutAccess,
71}
72
73impl UserPermissions {
74    /// All permission variants. Admin users receive this full list so new variants are included automatically.
75    pub fn all() -> Vec<Self> {
76        Self::iter().collect()
77    }
78}
79
80#[derive(
81    Clone, PartialEq, Eq, Default, Debug, CustomType, Tsify, Serialize, Deserialize, JsonSchema,
82)]
83#[tsify(into_wasm_abi)]
84pub struct OverlordState {
85    pub character_state: CharacterState,
86    pub blocked_character_ids: Vec<uuid::Uuid>,
87    pub active_fight: Option<ActiveFight>,
88    pub pvp_state: Option<PVPState>,
89    pub connection_store: HashMap<String, i64>,
90    pub quest_groups: QuestsGroups,
91    pub incoming_gifts: Vec<Gift>,
92    pub incoming_mails: Vec<Mail>,
93    pub patron: Option<Patron>,
94    pub referral_daily_reward_claimed: Option<bool>,
95    pub auto_chest: AutoChest,
96    pub arena: Arena,
97    pub dungeons: Dungeons,
98    pub offers_info: OffersInfo,
99    pub permissions: Vec<UserPermissions>,
100    pub party: Party,
101}
102
103impl State for OverlordState {
104    /// Compares version from database with local
105    fn cmp_db_updated(&self, db_updated: &Self) -> bool {
106        self.character_state.character == db_updated.character_state.character
107            && self.character_state.inventory == db_updated.character_state.inventory
108    }
109
110    fn register_rhai_type(engine: &mut rhai::Engine) {
111        engine.build_type::<Self>();
112        engine.register_iterator::<Vec<QuestInstance>>();
113        engine.register_iterator::<Vec<Ability>>();
114        engine.register_iterator::<Vec<CurrencyUnit>>();
115        engine.register_iterator::<Vec<Entity>>();
116        engine.register_iterator::<Vec<EntityAttribute>>();
117        engine.register_iterator::<Vec<Gift>>();
118        engine.register_iterator::<Vec<Mail>>();
119        engine.register_iterator::<Vec<Item>>();
120        engine.register_iterator::<Vec<ItemAttribute>>();
121        engine.register_iterator::<Vec<Suzerain>>();
122        engine.register_iterator::<Vec<Vassal>>();
123        engine.register_iterator::<Vec<VassalTask>>();
124        engine.register_iterator::<Vec<Pet>>();
125        engine.register_iterator::<Vec<PetComputedSecondaryStat>>();
126
127        engine.register_indexer_get::<Vec<Entity>, i64, false, Entity, true>(
128            |this: &mut Vec<Entity>, idx: i64| -> Result<Entity, Box<rhai::EvalAltResult>> {
129                match this.get(idx as usize) {
130                    Some(value) => Ok(value.clone()),
131                    None => Err(Box::new(
132                        format!("index out of bounds: {} > {}", idx, this.len()).into(),
133                    )),
134                }
135            },
136        );
137        engine.register_get::<Vec<Entity>, false, i64, false>(
138            "len",
139            |this: &mut Vec<Entity>| -> i64 { this.len() as i64 },
140        );
141        engine.register_get::<Vec<Item>, false, i64, false>("len", |this: &mut Vec<Item>| -> i64 {
142            this.len() as i64
143        });
144
145        engine.build_type::<Ability>();
146        engine.build_type::<AbilityRarity>();
147        engine.build_type::<ActiveFight>();
148        engine.build_type::<ActiveDungeon>();
149        engine.build_type::<QuestInstance>();
150        engine.build_type::<QuestsGroups>();
151        engine.build_type::<Attribute>();
152        engine.build_type::<Chapter>();
153        engine.build_type::<CurrencyUnit>();
154        engine.build_type::<Effect>();
155        engine.build_type::<Entity>();
156        engine.build_type::<EntityAttribute>();
157        engine.build_type::<EntityAttributes>();
158        engine.build_type::<EntityTeam>();
159        engine.build_type::<EquippedAbilities>();
160        engine.build_type::<CustomValuesMap>();
161        engine.build_type::<Gift>();
162        engine.build_type::<Mail>();
163        engine.build_type::<Item>();
164        engine.build_type::<ItemType>();
165        engine.build_type::<ItemAttribute>();
166        engine.build_type::<ItemRarity>();
167        engine.build_type::<Patron>();
168        engine.build_type::<Suzerain>();
169        engine.build_type::<Character>();
170        engine.build_type::<CharacterState>();
171        engine.build_type::<Vassal>();
172        engine.build_type::<VassalTask>();
173        engine.build_type::<RatingCalculationData>();
174        engine.build_type::<Bot>();
175        engine.build_type::<UpgradedAbilitiesMap>();
176        engine.build_type::<Pet>();
177        engine.build_type::<PetRarity>();
178        engine.build_type::<PetTemplate>();
179        engine.build_type::<EquippedPets>();
180        engine.build_type::<UpgradedPetsMap>();
181        engine.build_type::<PetComputedSecondaryStat>();
182        engine.build_type::<Party>();
183        engine.build_type::<TalentLevelsMap>();
184    }
185
186    fn is_ticker_paused(&self) -> bool {
187        self.active_fight.as_ref().is_some_and(|fight| fight.paused)
188    }
189}
190
191impl OverlordState {
192    pub fn claim_vassal_reward(
193        &mut self,
194        vassal_id: uuid::Uuid,
195        claim_time: chrono::DateTime<chrono::Utc>,
196    ) -> anyhow::Result<()> {
197        match self
198            .character_state
199            .vassals
200            .iter_mut()
201            .find(|vassal| vassal.character_id == vassal_id)
202        {
203            Some(vassal) => {
204                vassal.claim_reward(claim_time);
205                Ok(())
206            }
207            None => anyhow::bail!("No vassal with given id={}", vassal_id),
208        }
209    }
210
211    pub fn claim_suzerain_reward(
212        &mut self,
213        claim_time: chrono::DateTime<chrono::Utc>,
214    ) -> anyhow::Result<()> {
215        let Some(mut suzerain) = self.character_state.suzerain.clone() else {
216            anyhow::bail!("No suzerain to update")
217        };
218        suzerain.claim_reward(claim_time);
219        Ok(())
220    }
221
222    pub fn compute_description_values_for_ability(
223        &self,
224        ability: &Ability,
225        script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
226        config: &GameConfig,
227    ) -> Vec<f64> {
228        let Some(template) = config.ability_template(ability.template_id) else {
229            tracing::error!(
230                "Failed to get template for ability template_id={}",
231                ability.template_id
232            );
233            return vec![];
234        };
235
236        let Some(ref description_script) = template.description_values_script else {
237            return vec![];
238        };
239
240        script_runner.run_description_values_calculate(
241            |scope_setter| scope_setter,
242            description_script,
243            ability.level,
244        )
245    }
246
247    pub fn compute_description_values_for_talent(
248        &self,
249        talent_template: &essences::talent_tree::TalentTemplate,
250        level: i64,
251        script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
252    ) -> Vec<f64> {
253        let Some(ref description_script) = talent_template.description_values_script else {
254            return vec![];
255        };
256
257        script_runner.run_talent_description_values_calculate(
258            |scope_setter| scope_setter,
259            description_script,
260            level,
261        )
262    }
263
264    pub fn compute_description_values_template(
265        &self,
266        ability_template: &AbilityTemplate,
267        script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
268        config: &GameConfig,
269    ) -> Vec<f64> {
270        let ability = Ability::from_template(ability_template, None, None);
271
272        self.compute_description_values_for_ability(&ability, script_runner, config)
273    }
274
275    pub fn compute_afk_reward(
276        &self,
277        game_config: &GameConfig,
278        script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
279    ) -> Vec<BundleElement> {
280        let mut elements = vec![];
281
282        let Some(bundle) = game_config.bundle(game_config.afk_rewards_settings.bundle_id) else {
283            tracing::error!(
284                "Couldn't find afk bundle with id = {}",
285                game_config.afk_rewards_settings.bundle_id
286            );
287            return vec![];
288        };
289
290        let now = ::time::utc_now();
291
292        for step in &bundle.steps {
293            elements.push(bundle_raw_afk_step_to_element(
294                step,
295                &self.character_state,
296                now,
297                script_runner,
298                game_config,
299            ));
300        }
301
302        elements
303    }
304
305    pub fn compute_afk_instant_reward(
306        &self,
307        game_config: &GameConfig,
308        script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
309    ) -> Vec<BundleElement> {
310        let mut elements = vec![];
311
312        let Some(bundle) = game_config.bundle(game_config.afk_rewards_settings.bundle_id) else {
313            tracing::error!(
314                "Couldn't find afk bundle with id = {}",
315                game_config.afk_rewards_settings.bundle_id
316            );
317            return vec![];
318        };
319
320        let afk_settings = &game_config.afk_rewards_settings;
321        let duration_sec = afk_settings
322            .instant_reward_duration_sec
323            .min(afk_settings.max_possible_time_sec);
324        let fake_now = self.character_state.character.last_afk_reward_claimed_at
325            + chrono::Duration::seconds(duration_sec as i64);
326
327        for step in &bundle.steps {
328            elements.push(bundle_raw_afk_step_to_element(
329                step,
330                &self.character_state,
331                fake_now,
332                script_runner,
333                game_config,
334            ));
335        }
336
337        elements
338    }
339
340    pub fn compute_bundle_reward(
341        &self,
342        game_config: &GameConfig,
343        script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
344        bundle_id: BundleId,
345    ) -> Vec<BundleElement> {
346        let mut elements = vec![];
347
348        let Some(bundle) = game_config.bundle(bundle_id) else {
349            tracing::error!(
350                "Couldn't find bundle with id = {}",
351                game_config.afk_rewards_settings.bundle_id
352            );
353            return vec![];
354        };
355
356        for step in &bundle.steps {
357            elements.push(bundle_raw_step_to_element(
358                step,
359                &self.character_state,
360                script_runner,
361                game_config,
362            ));
363        }
364
365        elements
366    }
367
368    pub fn calculate_item_power(
369        &self,
370        item: Item,
371        game_config: &GameConfig,
372        script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
373    ) -> anyhow::Result<i64> {
374        let Ok(power) = script_runner.run_expression(
375            |mut scope_setter| {
376                scope_setter.set_const("CharacterState", self.character_state.clone());
377                scope_setter.set_const("Item", item);
378                scope_setter
379            },
380            &game_config.game_settings.item_power_calculate_script,
381        ) else {
382            anyhow::bail!("Couldn't calculate item power");
383        };
384        Ok(power)
385    }
386
387    pub fn get_active_fight_player(&self) -> Option<&Entity> {
388        self.active_fight
389            .as_ref()
390            .and_then(|fight| fight.get_player())
391    }
392
393    pub fn get_active_fight_player_mut(&mut self) -> Option<&mut Entity> {
394        self.active_fight
395            .as_mut()
396            .and_then(|fight| fight.get_player_mut())
397    }
398}