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#[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 pub fn as_str(self) -> &'static str {
58 match self {
59 UserStatus::Admin => "admin",
60 UserStatus::Banned => "banned",
61 }
62 }
63}
64
65#[derive(Clone, PartialEq, Eq, Debug, Tsify, Serialize, Deserialize, JsonSchema, EnumIter)]
68pub enum UserPermissions {
69 CheatsAccess,
70 LogOutAccess,
71}
72
73impl UserPermissions {
74 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 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}