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::{ScriptRunner, 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 script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
75) -> anyhow::Result<entity::Entity> {
76 let entity_stats = calculate_player_entity_stats_without_zeroes(
77 &EntityState::Character(character_state),
78 game_config,
79 script_runner,
80 )?;
81
82 let mut entity_abilities = make_active_abilities_from_equipped(
83 &character_state.equipped_abilities,
84 game_config,
85 false,
86 );
87
88 if let Some(leader_pet) = character_state.equipped_pets.leader()
90 && let Some(ability_template) = leader_pet
91 .active_ability_id
92 .and_then(|id| game_config.ability_template(id))
93 {
94 let pet_ability = abilities::Ability::from_template(ability_template, None, None);
95 entity_abilities.push(abilities::ActiveAbility {
96 ability: pet_ability,
97 deadline: None,
98 slot_id: None,
99 });
100 }
101
102 Ok(entity::Entity {
103 id: entity_id,
104 max_hp: entity_stats.max_hp,
105 hp: entity_stats.max_hp,
106 abilities: entity_abilities,
107 actions_queue: EntityActionsQueue::new(entity_id),
108 attributes: entity_stats.attributes,
109 effect_ids: Default::default(),
110 coordinates: game_config.fight_settings.player_start_position.clone(),
111 being_moved: false,
112 width: 1, rewards: None,
114 class_id: Some(character_state.character.class),
115 team: fighting::EntityTeam::Ally,
116 has_big_hp_bar: false,
117 entity_template_id: None,
118 })
119}
120
121pub fn create_party_entity(
122 character_state: &character_state::CharacterState,
123 entity_id: uuid::Uuid,
124 game_config: &game_config::GameConfig,
125 script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
126) -> anyhow::Result<entity::Entity> {
127 let mut entity = create_player_entity(character_state, entity_id, game_config, script_runner)?;
128 entity.coordinates = game_config.fight_settings.party_start_position.clone();
129 Ok(entity)
130}
131
132pub fn create_pve_entity(
133 entity_id: uuid::Uuid,
134 fight_entity: &fighting::FightEntity,
135 game_config: &game_config::GameConfig,
136 entity_attributes: Option<EntityAttributes>,
137) -> anyhow::Result<entity::Entity> {
138 let entity_template_id = match fight_entity.entity_type {
139 fighting::EntityType::PVEEntity { entity_template_id } => entity_template_id,
140 fighting::EntityType::PVPEntity => {
141 anyhow::bail!(
142 "Wanted to create a PVE entity, but got a PVP entity = {:?}",
143 fight_entity
144 );
145 }
146 };
147
148 let Some(entity) = game_config.entity_template(entity_template_id).cloned() else {
149 anyhow::bail!(
150 "Failed to find entity_template with id={}",
151 entity_template_id
152 )
153 };
154
155 let entity_abilities = entity
156 .ability_ids
157 .iter()
158 .filter_map(|&ability_id| {
159 let Some(entity_ability_template) = game_config.ability_template(ability_id) else {
160 tracing::error!("Failed to get ability with ability_id={}", ability_id);
161 return None;
162 };
163
164 let enemy_ability = abilities::Ability::from_template(
165 entity_ability_template,
166 None,
167 None,
168 );
169
170 Some(abilities::ActiveAbility {
171 ability: enemy_ability,
172 deadline: None,
173 slot_id: None,
174 })
175 })
176 .collect();
177
178 let entity_stats = if let Some(attributes) = entity_attributes {
179 let mut max_hp = 0;
180 let Some(config_max_hp_attribute) =
181 game_config.attribute(game_config.game_settings.hp_attribute_id)
182 else {
183 anyhow::bail!(
184 "Couldn't find max_hp attribute with id = {}",
185 game_config.game_settings.hp_attribute_id
186 );
187 };
188
189 for (code, value) in &attributes.0 {
190 if *code == config_max_hp_attribute.code {
191 max_hp += *value as u64
192 }
193 }
194 EntityStats { attributes, max_hp }
195 } else {
196 let mut attributes_deltas = AttributeDeltas::new();
197 for attribute in entity.attributes {
198 *attributes_deltas.entry(attribute.attribute_id).or_insert(0) += attribute.value as i64;
199 }
200
201 calculate_entity_stats(game_config, attributes_deltas)?
202 };
203
204 Ok(entity::Entity {
205 id: entity_id,
206 max_hp: entity_stats.max_hp,
207 hp: entity_stats.max_hp,
208 abilities: entity_abilities,
209 actions_queue: EntityActionsQueue::new(entity_id),
210 attributes: entity_stats.attributes,
211 effect_ids: Default::default(),
212 coordinates: fight_entity.position.clone(),
213 being_moved: false,
214 width: entity.width,
215 rewards: Some(entity.rewards),
216 class_id: None,
217 team: fight_entity.team.clone(),
218 has_big_hp_bar: fight_entity.has_big_hp_bar,
219 entity_template_id: Some(entity_template_id),
220 })
221}
222
223pub fn create_pvp_entity(
224 entity_state: &EntityState,
225 fight_entity: &fighting::FightEntity,
226 game_config: &game_config::GameConfig,
227 script_runner: &ScriptRunner<OverlordEvent, OverlordState>,
228) -> anyhow::Result<entity::Entity> {
229 if fight_entity.entity_type != fighting::EntityType::PVPEntity {
230 anyhow::bail!(
231 "Wanted to create a PVP entity, but got a PVE entity = {:?}",
232 fight_entity
233 );
234 };
235
236 let entity_stats =
237 calculate_player_entity_stats_without_zeroes(entity_state, game_config, script_runner)?;
238
239 Ok(entity::Entity {
240 id: entity_state.id(),
241 max_hp: entity_stats.max_hp,
242 hp: entity_stats.max_hp,
243 abilities: make_active_abilities_from_equipped(
244 entity_state.equipped_abilities(),
245 game_config,
246 false,
247 ),
248 actions_queue: EntityActionsQueue::new(entity_state.id()),
249 attributes: entity_stats.attributes,
250 effect_ids: Default::default(),
251 coordinates: fight_entity.position.clone(),
252 being_moved: false,
253 width: 1, rewards: None,
255 class_id: Some(entity_state.class()),
256 team: fight_entity.team.clone(),
257 has_big_hp_bar: fight_entity.has_big_hp_bar,
258 entity_template_id: None,
259 })
260}
261
262pub fn event_from_entity_action(
263 action: EntityAction,
264 entity_id: EntityId,
265) -> EventPluginized<OverlordEvent, OverlordState> {
266 match action {
267 EntityAction::CastEffect {
268 entity_id,
269 effect_id,
270 } => EventPluginized::now(OverlordEvent::CastEffect {
271 entity_id,
272 effect_id,
273 }),
274 EntityAction::CastAbility {
275 ability_id,
276 target_entity_id,
277 } => EventPluginized::now(OverlordEvent::CastAbility {
278 by_entity_id: entity_id,
279 to_entity_id: target_entity_id,
280 ability_id,
281 }),
282 EntityAction::CastBasicAbility {
283 ability_id,
284 target_entity_id,
285 } => EventPluginized::now(OverlordEvent::CastAbility {
286 by_entity_id: entity_id,
287 to_entity_id: target_entity_id,
288 ability_id,
289 }),
290 EntityAction::StartCastAbility {
291 ability_id,
292 by_entity_id,
293 pet_id,
294 } => EventPluginized::now(OverlordEvent::StartCastAbility {
295 by_entity_id,
296 ability_id,
297 pet_id,
298 }),
299 }
300}