1use configs::game_config;
2use essences::{
3 abilities, character_state,
4 entity::{self, EntityAction, EntityActionsQueue, EntityAttributes, EntityId, EntityState},
5 fighting,
6};
7use event_system::event::EventPluginized;
8
9use crate::attributes::{
10 AttributeDeltas, EntityStats, calculate_entity_stats,
11 calculate_player_entity_stats_without_zeroes,
12};
13use crate::game_config_helpers::GameConfigLookup;
14
15use super::{TICKER_UNIT_DURATION_MS, event::OverlordEvent, state::OverlordState};
16
17fn make_ability_deadline(cooldown: u64) -> chrono::DateTime<chrono::Utc> {
18 ::time::utc_now()
19 + chrono::TimeDelta::milliseconds((cooldown as u128 * TICKER_UNIT_DURATION_MS) as i64)
20}
21
22pub fn sort_active_abilities(abilities: &mut [abilities::ActiveAbility]) {
23 abilities.sort_by(|a, b| {
24 a.slot_id
25 .cmp(&b.slot_id)
26 .then(a.ability.template_id.cmp(&b.ability.template_id))
27 });
28}
29
30pub fn make_active_abilities_from_equipped(
31 equipped_abilities: &abilities::EquippedAbilities,
32 game_config: &game_config::GameConfig,
33 begin_in_cooldown: bool,
34) -> Vec<abilities::ActiveAbility> {
35 let deadline_for = |template_id| {
36 if begin_in_cooldown {
37 let cooldown = game_config
38 .ability_template(template_id)
39 .map(|t| t.cooldown)
40 .unwrap_or(0);
41 Some(make_ability_deadline(cooldown))
42 } else {
43 None
44 }
45 };
46
47 let mut abilities: Vec<abilities::ActiveAbility> =
48 equipped_abilities
49 .unslotted
50 .iter()
51 .map(|a| abilities::ActiveAbility {
52 deadline: deadline_for(a.template_id),
53 ability: a.clone(),
54 slot_id: None,
55 })
56 .chain(equipped_abilities.slotted.iter().map(|(&slot_id, a)| {
57 abilities::ActiveAbility {
58 deadline: deadline_for(a.template_id),
59 ability: a.clone(),
60 slot_id: Some(slot_id),
61 }
62 }))
63 .collect();
64
65 sort_active_abilities(&mut abilities);
66
67 abilities
68}
69
70pub fn create_player_entity(
71 character_state: &character_state::CharacterState,
72 entity_id: uuid::Uuid,
73 game_config: &game_config::GameConfig,
74) -> anyhow::Result<entity::Entity> {
75 let entity_stats = calculate_player_entity_stats_without_zeroes(
76 &EntityState::Character(character_state),
77 game_config,
78 )?;
79
80 let mut entity_abilities = make_active_abilities_from_equipped(
81 &character_state.equipped_abilities,
82 game_config,
83 false,
84 );
85
86 if let Some(leader_pet) = character_state.equipped_pets.leader()
88 && let Some(ability_template) = leader_pet
89 .active_ability_id
90 .and_then(|id| game_config.ability_template(id))
91 {
92 let pet_ability = abilities::Ability::from_template(ability_template, None, None);
93 entity_abilities.push(abilities::ActiveAbility {
94 ability: pet_ability,
95 deadline: None,
96 slot_id: None,
97 });
98 }
99
100 Ok(entity::Entity {
101 id: entity_id,
102 max_hp: entity_stats.max_hp,
103 hp: entity_stats.max_hp,
104 abilities: entity_abilities,
105 actions_queue: EntityActionsQueue::new(entity_id),
106 attributes: entity_stats.attributes,
107 effect_ids: Default::default(),
108 coordinates: game_config.fight_settings.player_start_position.clone(),
109 move_target: None,
110 width: 1, rewards: None,
112 class_id: Some(character_state.character.class),
113 team: fighting::EntityTeam::Ally,
114 has_big_hp_bar: false,
115 entity_template_id: None,
116 })
117}
118
119pub fn create_party_entity(
120 character_state: &character_state::CharacterState,
121 entity_id: uuid::Uuid,
122 game_config: &game_config::GameConfig,
123) -> anyhow::Result<entity::Entity> {
124 let mut entity = create_player_entity(character_state, entity_id, game_config)?;
125 entity.coordinates = game_config.fight_settings.party_start_position.clone();
126 Ok(entity)
127}
128
129pub fn create_pve_entity(
130 entity_id: uuid::Uuid,
131 fight_entity: &fighting::FightEntity,
132 game_config: &game_config::GameConfig,
133 entity_attributes: Option<EntityAttributes>,
134) -> anyhow::Result<entity::Entity> {
135 let entity_template_id = match fight_entity.entity_type {
136 fighting::EntityType::PVEEntity { entity_template_id } => entity_template_id,
137 fighting::EntityType::PVPEntity => {
138 anyhow::bail!(
139 "Wanted to create a PVE entity, but got a PVP entity = {:?}",
140 fight_entity
141 );
142 }
143 };
144
145 let Some(entity) = game_config.entity_template(entity_template_id).cloned() else {
146 anyhow::bail!(
147 "Failed to find entity_template with id={}",
148 entity_template_id
149 )
150 };
151
152 let entity_abilities = entity
153 .ability_ids
154 .iter()
155 .filter_map(|&ability_id| {
156 let Some(entity_ability_template) = game_config.ability_template(ability_id) else {
157 tracing::error!("Failed to get ability with ability_id={}", ability_id);
158 return None;
159 };
160
161 let enemy_ability = abilities::Ability::from_template(
162 entity_ability_template,
163 None,
164 None,
165 );
166
167 Some(abilities::ActiveAbility {
168 ability: enemy_ability,
169 deadline: None,
170 slot_id: None,
171 })
172 })
173 .collect();
174
175 let entity_stats = if let Some(attributes) = entity_attributes {
176 let mut max_hp = 0;
177 let Some(config_max_hp_attribute) =
178 game_config.attribute(game_config.game_settings.hp_attribute_id)
179 else {
180 anyhow::bail!(
181 "Couldn't find max_hp attribute with id = {}",
182 game_config.game_settings.hp_attribute_id
183 );
184 };
185
186 for (code, value) in &attributes.0 {
187 if *code == config_max_hp_attribute.code {
188 max_hp += *value as u64
189 }
190 }
191 EntityStats { attributes, max_hp }
192 } else {
193 let mut attributes_deltas = AttributeDeltas::new();
194 for attribute in entity.attributes {
195 *attributes_deltas.entry(attribute.attribute_id).or_insert(0) += attribute.value as i64;
196 }
197
198 calculate_entity_stats(game_config, attributes_deltas)?
199 };
200
201 Ok(entity::Entity {
202 id: entity_id,
203 max_hp: entity_stats.max_hp,
204 hp: entity_stats.max_hp,
205 abilities: entity_abilities,
206 actions_queue: EntityActionsQueue::new(entity_id),
207 attributes: entity_stats.attributes,
208 effect_ids: Default::default(),
209 coordinates: fight_entity.position.clone(),
210 move_target: None,
211 width: entity.width,
212 rewards: Some(entity.rewards),
213 class_id: None,
214 team: fight_entity.team.clone(),
215 has_big_hp_bar: fight_entity.has_big_hp_bar,
216 entity_template_id: Some(entity_template_id),
217 })
218}
219
220pub fn create_pvp_entity(
221 entity_state: &EntityState,
222 fight_entity: &fighting::FightEntity,
223 game_config: &game_config::GameConfig,
224) -> anyhow::Result<entity::Entity> {
225 if fight_entity.entity_type != fighting::EntityType::PVPEntity {
226 anyhow::bail!(
227 "Wanted to create a PVP entity, but got a PVE entity = {:?}",
228 fight_entity
229 );
230 };
231
232 let entity_stats = calculate_player_entity_stats_without_zeroes(entity_state, game_config)?;
233
234 Ok(entity::Entity {
235 id: entity_state.id(),
236 max_hp: entity_stats.max_hp,
237 hp: entity_stats.max_hp,
238 abilities: make_active_abilities_from_equipped(
239 entity_state.equipped_abilities(),
240 game_config,
241 false,
242 ),
243 actions_queue: EntityActionsQueue::new(entity_state.id()),
244 attributes: entity_stats.attributes,
245 effect_ids: Default::default(),
246 coordinates: fight_entity.position.clone(),
247 move_target: None,
248 width: 1, rewards: None,
250 class_id: Some(entity_state.class()),
251 team: fight_entity.team.clone(),
252 has_big_hp_bar: fight_entity.has_big_hp_bar,
253 entity_template_id: None,
254 })
255}
256
257pub fn event_from_entity_action(
258 action: EntityAction,
259 entity_id: EntityId,
260) -> EventPluginized<OverlordEvent, OverlordState> {
261 match action {
262 EntityAction::CastEffect {
263 entity_id,
264 effect_id,
265 } => EventPluginized::now(OverlordEvent::CastEffect {
266 entity_id,
267 effect_id,
268 }),
269 EntityAction::CastAbility {
270 ability_id,
271 target_entity_id,
272 } => EventPluginized::now(OverlordEvent::CastAbility {
273 by_entity_id: entity_id,
274 to_entity_id: target_entity_id,
275 ability_id,
276 }),
277 EntityAction::CastBasicAbility {
278 ability_id,
279 target_entity_id,
280 } => EventPluginized::now(OverlordEvent::CastAbility {
281 by_entity_id: entity_id,
282 to_entity_id: target_entity_id,
283 ability_id,
284 }),
285 EntityAction::StartCastAbility {
286 ability_id,
287 by_entity_id,
288 pet_id,
289 } => EventPluginized::now(OverlordEvent::StartCastAbility {
290 by_entity_id,
291 ability_id,
292 pet_id,
293 }),
294 }
295}