1use lib_efo::entity::{EffectApplication, EffectBounds, EntityDuration, EntityIdentifier};
66use lib_efo::epoch::epoch_traits::{ConfigWritable, Diffable};
67use lib_efo::naming::NAMING;
68use lib_efo::stats::{Position, Symbols};
69
70use clap::{Args, Subcommand};
71use lib_efo::Displayable;
72use lib_efo::utils::RngLevel;
73use lib_efo::{
74 entity::Entity,
75 epoch::EpochConfig,
76 error::EpochError,
77 stats::{EntityType, StatPair},
78};
79use log::info;
80
81use crate::error::AppError;
82use crate::success::display_success;
83
84#[derive(Subcommand, Debug)]
89pub enum EntitySubCommands {
90 Add(EntityArgs),
92 Del(EntityArgs),
94 Edit(EntityArgs),
96 Find(EntityArgs),
98 Show(EntityArgs),
100 Diff(EntityArgs),
102}
103
104#[derive(Args, Debug)]
109pub struct EntityArgs {
110 pub identifier: Option<EntityIdentifier>,
112
113 #[arg(long, short, action = clap::ArgAction::SetTrue, default_value_t = false)]
118 pub delta: bool,
119
120 #[arg(value_enum, long, default_value_t = RngLevel::Zero)]
122 pub rng: RngLevel,
123
124 #[arg(long, group = "position")]
126 pub pos_exact: Option<Position>,
127
128 #[arg(long, group = "position")]
130 pub pos: Option<Position>,
131
132 #[arg(long)]
134 pub duration: Option<EntityDuration>,
135
136 #[arg(long)]
138 pub bounds: Option<EffectBounds>,
139
140 #[arg(long)]
142 pub effect: Option<EffectApplication>,
143
144 #[arg(short, long)]
146 pub stats: Vec<StatPair>,
147}
148
149pub fn handle_entity_commands(
162 entity_sub_commands: &EntitySubCommands,
163 e_type: &EntityType,
164) -> Result<(), AppError> {
165 let mut main_epoch = EpochConfig::new_from_path(None)?;
166 match entity_sub_commands {
167 EntitySubCommands::Add(entity_args) => {
168 handle_create_entity(entity_args, e_type, &mut main_epoch)
169 }
170 EntitySubCommands::Del(entity_args) => {
171 handle_delete_entity(entity_args, e_type, &mut main_epoch)
172 }
173 EntitySubCommands::Edit(entity_args) => {
174 handle_edit_entity(entity_args, e_type, &mut main_epoch)
175 }
176 EntitySubCommands::Find(entity_args) => {
177 handle_find_entity(entity_args, e_type, &mut main_epoch)
178 }
179 EntitySubCommands::Show(entity_args) => {
180 handle_show_entity(entity_args, e_type, &mut main_epoch)
181 }
182 EntitySubCommands::Diff(entity_args) => {
183 handle_diff_entity(entity_args, e_type, &mut main_epoch)
184 }
185 }?;
186 Ok(())
187}
188
189fn handle_create_entity(
191 entity_args: &EntityArgs,
192 e_type: &EntityType,
193 main_epoch: &mut EpochConfig,
194) -> Result<(), EpochError> {
195 let epoch_info = main_epoch.try_load_current_as_mut()?;
196
197 if find_entity(entity_args, e_type, epoch_info).is_ok() {
198 Err(EpochError::EntityAlreadyExist(
199 entity_args.identifier.clone(),
200 ))?;
201 };
202
203 let vec_entities: &mut Vec<Entity> = vec_entities_mut(e_type, epoch_info);
204
205 let mut entity = Entity::create_rng(e_type, &entity_args.rng)?;
206 update_stats(entity_args, &mut entity);
207 update_defaults(entity_args, &mut entity);
211
212 info!("Created character: {:#?}", entity);
213 entity.print_full();
214 let message = &crate::success::SuccessMessages::CreatedEntity(
215 entity.name.clone(),
216 entity.entity_type.to_shortest(),
217 entity.id.to_string(),
218 );
219 vec_entities.push(entity);
220
221 epoch_info.save_with_context("Created entity", &lib_efo::epoch::SaveMode::Erase)?;
223 display_success(message);
224
225 Ok(())
226}
227
228fn handle_delete_entity(
230 entity_args: &EntityArgs,
231 e_type: &EntityType,
232 main_epoch: &mut EpochConfig,
233) -> Result<(), EpochError> {
234 let epoch_info = main_epoch.try_load_current_as_mut()?;
235
236 let vec_entities: &mut Vec<Entity> = vec_entities_mut(e_type, epoch_info);
237 let index = vec_entities
238 .iter()
239 .position(|e| find_by_args(e, entity_args))
240 .ok_or(EpochError::EntityNotFound(
241 e_type.clone(),
242 entity_args.identifier.clone(),
243 ))?;
244
245 let entity = vec_entities.swap_remove(index);
246
247 epoch_info.save_with_context("Deleted entity", &lib_efo::epoch::SaveMode::Erase)?;
248 display_success(&crate::success::SuccessMessages::DeletedEntity(
249 entity.name,
250 entity.entity_type.to_shortest(),
251 entity.id.to_string(),
252 ));
253 Ok(())
254}
255
256fn handle_diff_entity(
258 entity_args: &EntityArgs,
259 e_type: &EntityType,
260 main_epoch: &mut EpochConfig,
261) -> Result<(), EpochError> {
262 let id = main_epoch.epoch_id;
264 let id2 = main_epoch.epoch_id.add_to_epoch(-1);
265 main_epoch.try_load(&id)?;
266 main_epoch.try_load(&id2)?;
267
268 let epoch_current = unsafe { main_epoch.try_load_id(id) }?;
269 let epoch_past = unsafe { main_epoch.try_load_id(id2) }?;
270 let current_entity = find_entity(entity_args, e_type, epoch_current)?;
271 let past_entity = find_entity(entity_args, e_type, epoch_past)?;
272
273 let diff = current_entity.delta(past_entity);
274 display_success(&crate::success::SuccessMessages::DiffBetweenEpoch(id, id2));
275 diff.print_full();
276
277 Ok(())
278}
279
280fn handle_show_entity(
283 entity_args: &EntityArgs,
284 e_type: &EntityType,
285 main_epoch: &mut EpochConfig,
286) -> Result<(), EpochError> {
287 let epoch_info = main_epoch.try_load_current()?;
288 if entity_args.identifier.is_none() {
289 for entity in vec_entities(e_type, epoch_info) {
290 entity.print_oneline();
291 }
292 } else {
293 let entity = find_entity(entity_args, e_type, epoch_info)?;
294 entity.print_full();
295 }
296 Ok(())
297}
298
299fn handle_find_entity(
301 entity_args: &EntityArgs,
302 e_type: &EntityType,
303 main_epoch: &mut EpochConfig,
304) -> Result<(), EpochError> {
305 handle_show_entity(entity_args, e_type, main_epoch)
306}
307
308fn handle_edit_entity(
310 entity_args: &EntityArgs,
311 e_type: &EntityType,
312 main_epoch: &mut EpochConfig,
313) -> Result<(), EpochError> {
314 let epoch_info = main_epoch.try_load_current_as_mut()?;
315 let vec_entities = vec_entities_mut(e_type, epoch_info);
316 let entity = vec_entities
317 .iter_mut()
318 .find(|e| find_by_args(e, entity_args))
319 .ok_or(EpochError::EntityNotFound(
320 e_type.clone(),
321 entity_args.identifier.clone(),
322 ))?;
323
324 for stat in &entity_args.stats {
325 entity.change_stat(stat.clone());
326 }
327 if let Some(duration) = &entity_args.duration {
328 entity.modify_duration(duration.clone());
329 }
330 if let Some(effect) = &entity_args.effect {
337 entity.modify_effect_application(effect.clone());
338 }
339 if let Some(bounds) = &entity_args.bounds {
340 entity.modify_effect_bounds(bounds.clone());
341 }
342
343 entity.print_full();
344 let message = &crate::success::SuccessMessages::EditedEntity(
345 entity.name.clone(),
346 entity.entity_type.to_shortest(),
347 entity.id.to_string(),
348 );
349 epoch_info.save_with_context("Deleted entity", &lib_efo::epoch::SaveMode::Erase)?;
350
351 display_success(message);
352 Ok(())
353}
354
355fn update_stats(entity_args: &EntityArgs, e: &mut Entity) {
358 if entity_args.delta {
359 for stat in &entity_args.stats {
360 e.delta_stat(stat.clone());
361 }
362 } else {
363 for stat in &entity_args.stats {
364 e.change_stat(stat.clone());
365 }
366 }
367}
368
369fn find_entity<'a>(
384 entity_args: &'a EntityArgs,
385 e_type: &'a EntityType,
386 epoch_info: &'a lib_efo::epoch::EpochInfo,
387) -> Result<&'a Entity, EpochError> {
388 vec_entities(e_type, epoch_info)
389 .iter()
390 .find(|&e| find_by_args(e, entity_args))
391 .ok_or(EpochError::EntityNotFound(
392 e_type.clone(),
393 entity_args.identifier.clone(),
394 ))
395}
396
397#[inline]
411fn vec_entities<'a>(
412 e_type: &'a EntityType,
413 epoch_info: &'a lib_efo::epoch::EpochInfo,
414) -> &'a Vec<Entity> {
415 match e_type {
416 EntityType::Character => &epoch_info.characters,
417 EntityType::Player => todo!(),
418 EntityType::Town => &epoch_info.towns,
419 EntityType::Monster => &epoch_info.calamities,
420 EntityType::Blocker => &epoch_info.blockers,
421 EntityType::Event => &epoch_info.events,
422 }
423}
424
425#[inline]
426fn vec_entities_mut<'a>(
440 e_type: &'a EntityType,
441 epoch_info: &'a mut lib_efo::epoch::EpochInfo,
442) -> &'a mut Vec<Entity> {
443 match e_type {
444 EntityType::Character => &mut epoch_info.characters,
445 EntityType::Player => todo!(),
446 EntityType::Town => &mut epoch_info.towns,
447 EntityType::Monster => &mut epoch_info.calamities,
448 EntityType::Blocker => &mut epoch_info.blockers,
449 EntityType::Event => &mut epoch_info.events,
450 }
451}
452
453fn find_by_args(entity: &Entity, entity_args: &EntityArgs) -> bool {
455 match entity_args.identifier {
456 Some(EntityIdentifier::Name(ref name)) => entity.name.starts_with(name),
457 Some(EntityIdentifier::Id(ref id)) => entity.id.hyphenated().to_string().starts_with(id),
458 None => false,
459 }
460}
461
462#[allow(dead_code)]
464fn find_by_id<'a>(entities: &'a [Entity], id: &'a String) -> Option<&'a Entity> {
465 entities
466 .iter()
467 .find(|e| e.id.hyphenated().to_string().starts_with(id))
468}
469
470#[allow(dead_code)]
472fn find_by_name<'a>(entities: &'a [Entity], id: &'a String) -> Option<&'a Entity> {
473 entities.iter().find(|e| e.name.starts_with(id))
474}
475
476fn update_defaults(entity_args: &EntityArgs, e: &mut Entity) {
478 let entity_type = &e.entity_type;
479
480 let (duration, effect, bounds, name) = match entity_type {
481 EntityType::Character => (
482 lib_efo::entity::EntityDuration::Permanent,
483 lib_efo::entity::EffectApplication::WhenPresent,
484 EffectBounds { min: 20, max: 110 },
485 NAMING.generate_character_name(),
486 ),
487 EntityType::Player => (
488 lib_efo::entity::EntityDuration::Permanent,
489 lib_efo::entity::EffectApplication::WhenPresent,
490 EffectBounds { min: 0, max: 120 },
491 NAMING.generate_exotic_character_name(),
492 ),
493 EntityType::Town => (
494 lib_efo::entity::EntityDuration::Permanent,
495 lib_efo::entity::EffectApplication::WhenPresent,
496 EffectBounds { min: 20, max: 60 },
497 NAMING.generate_town_name(),
498 ),
499 EntityType::Monster => (
500 lib_efo::entity::EntityDuration::Permanent,
501 lib_efo::entity::EffectApplication::WhenPresent,
502 EffectBounds { min: 20, max: 80 },
503 NAMING.generate_calamity_name(),
504 ),
505 EntityType::Blocker => (
506 lib_efo::entity::EntityDuration::Permanent,
507 lib_efo::entity::EffectApplication::WhenPresent,
508 EffectBounds { min: 0, max: 120 },
509 NAMING.generate_blocker_name(),
510 ),
511 EntityType::Event => (
512 lib_efo::entity::EntityDuration::Duration(1),
513 lib_efo::entity::EffectApplication::PermanentEffectToApply,
514 EffectBounds { min: 10, max: 80 },
515 NAMING.random_event_name(),
516 ),
517 };
518 if let Some(duration) = &entity_args.duration {
520 e.modify_duration(duration.clone());
521 } else {
522 e.modify_duration(duration);
523 }
524
525 if let Some(EntityIdentifier::Name(name)) = &entity_args.identifier {
526 e.name = name.clone()
527 } else {
528 e.name = name
529 }
530
531 if let Some(effect) = &entity_args.effect {
533 e.modify_effect_application(effect.clone());
534 } else {
535 e.modify_effect_application(effect);
536 }
537
538 if let Some(bounds) = &entity_args.bounds {
540 e.modify_effect_bounds(bounds.clone());
541 } else {
542 e.modify_effect_bounds(bounds);
543 }
544}