1use std::collections::HashMap;
7
8use super::runtime::{
9 Alter, AlterPresenceState, AnimaState, FrontingState, PluralSystem, RealityLayer, SwitchResult,
10 Trigger, TriggerCategory, TriggerResult,
11};
12
13#[derive(Debug, Clone)]
19pub struct CombatState {
20 pub combatants: Vec<Combatant>,
22 pub turn_order: Vec<usize>,
24 pub current_turn: usize,
26 pub round: u32,
28 pub environment: CombatEnvironment,
30 pub effects: Vec<CombatEffect>,
32 pub phase: CombatPhase,
34 pub is_over: bool,
36 pub result: Option<CombatResult>,
38}
39
40impl CombatState {
41 pub fn new(player_system: &PluralSystem, enemies: Vec<Enemy>) -> Self {
43 let mut combatants = vec![Combatant::Player(PlayerCombatant::from_system(
44 player_system,
45 ))];
46 combatants.extend(enemies.into_iter().map(Combatant::Enemy));
47
48 let mut state = Self {
49 combatants,
50 turn_order: Vec::new(),
51 current_turn: 0,
52 round: 1,
53 environment: CombatEnvironment::default(),
54 effects: Vec::new(),
55 phase: CombatPhase::PreCombat,
56 is_over: false,
57 result: None,
58 };
59
60 state.calculate_turn_order();
61 state
62 }
63
64 fn calculate_turn_order(&mut self) {
66 let mut order: Vec<(usize, f32)> = self
67 .combatants
68 .iter()
69 .enumerate()
70 .map(|(i, c)| (i, c.initiative()))
71 .collect();
72
73 order.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
75
76 self.turn_order = order.into_iter().map(|(i, _)| i).collect();
77 }
78
79 pub fn current_combatant(&self) -> Option<&Combatant> {
81 self.turn_order
82 .get(self.current_turn)
83 .and_then(|&idx| self.combatants.get(idx))
84 }
85
86 pub fn current_combatant_mut(&mut self) -> Option<&mut Combatant> {
88 let idx = *self.turn_order.get(self.current_turn)?;
89 self.combatants.get_mut(idx)
90 }
91
92 pub fn next_turn(&mut self) {
94 self.current_turn += 1;
95 if self.current_turn >= self.turn_order.len() {
96 self.current_turn = 0;
97 self.round += 1;
98 self.phase = CombatPhase::Combat;
99 }
100 }
101
102 pub fn check_combat_end(&mut self) {
104 let player_alive = self
105 .combatants
106 .iter()
107 .any(|c| matches!(c, Combatant::Player(p) if p.health > 0.0));
108
109 let enemies_alive = self
110 .combatants
111 .iter()
112 .any(|c| matches!(c, Combatant::Enemy(e) if e.health > 0.0));
113
114 if !player_alive {
115 self.is_over = true;
116 self.result = Some(CombatResult::Defeat);
117 } else if !enemies_alive {
118 self.is_over = true;
119 self.result = Some(CombatResult::Victory);
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq)]
126pub enum CombatPhase {
127 PreCombat,
129 Combat,
131 Event,
133 PostCombat,
135}
136
137#[derive(Debug, Clone, PartialEq)]
139pub enum CombatResult {
140 Victory,
141 Defeat,
142 Flee,
143 Negotiated,
144}
145
146#[derive(Debug, Clone)]
152pub enum Combatant {
153 Player(PlayerCombatant),
154 Enemy(Enemy),
155}
156
157impl Combatant {
158 pub fn initiative(&self) -> f32 {
160 match self {
161 Combatant::Player(p) => p.initiative(),
162 Combatant::Enemy(e) => e.initiative,
163 }
164 }
165
166 pub fn name(&self) -> &str {
168 match self {
169 Combatant::Player(p) => &p.fronting_alter,
170 Combatant::Enemy(e) => &e.name,
171 }
172 }
173}
174
175#[derive(Debug, Clone)]
177pub struct PlayerCombatant {
178 pub fronting_alter: String,
180 pub health: f32,
182 pub max_health: f32,
184 pub psyche: f32,
186 pub max_psyche: f32,
188 pub anima: AnimaState,
190 pub reality: RealityLayer,
192 pub abilities: Vec<CombatAbility>,
194 pub status_effects: Vec<StatusEffect>,
196 pub switch_cooldown: u32,
198 pub available_alters: Vec<String>,
200 pub active_triggers: Vec<String>,
202}
203
204impl PlayerCombatant {
205 pub fn from_system(system: &PluralSystem) -> Self {
207 let fronting = match &system.fronting {
208 FrontingState::Single(id) => id.clone(),
209 FrontingState::Blended(ids) => ids.first().cloned().unwrap_or_default(),
210 _ => "Unknown".to_string(),
211 };
212
213 let fronter = system.alters.get(&fronting);
214 let abilities = fronter
215 .map(|a| generate_combat_abilities(a))
216 .unwrap_or_default();
217
218 let available: Vec<String> = system
219 .alters
220 .values()
221 .filter(|a| {
222 !matches!(
223 a.state,
224 AlterPresenceState::Dormant | AlterPresenceState::Dissociating
225 )
226 })
227 .map(|a| a.id.clone())
228 .collect();
229
230 Self {
231 fronting_alter: fronting,
232 health: 100.0,
233 max_health: 100.0,
234 psyche: 50.0,
235 max_psyche: 50.0,
236 anima: system.anima.clone(),
237 reality: system.reality_layer.clone(),
238 abilities,
239 status_effects: Vec::new(),
240 switch_cooldown: 0,
241 available_alters: available,
242 active_triggers: Vec::new(),
243 }
244 }
245
246 pub fn initiative(&self) -> f32 {
248 let base = 10.0;
249 let arousal_bonus = self.anima.arousal * 5.0; let dominance_bonus = self.anima.dominance * 2.0;
251 base + arousal_bonus + dominance_bonus
252 }
253
254 pub fn execute_ability(
256 &mut self,
257 ability_index: usize,
258 target: &mut Combatant,
259 ) -> AbilityResult {
260 if ability_index >= self.abilities.len() {
261 return AbilityResult::Failed("Invalid ability".to_string());
262 }
263
264 let ability = &self.abilities[ability_index].clone();
265
266 if self.psyche < ability.psyche_cost {
268 return AbilityResult::Failed("Not enough psyche".to_string());
269 }
270
271 if let Some(ref required) = ability.reality_requirement {
273 if &self.reality != required {
274 return AbilityResult::Failed("Wrong reality layer".to_string());
275 }
276 }
277
278 self.psyche -= ability.psyche_cost;
280
281 let mut damage = ability.base_damage;
283
284 match ability.damage_type {
286 DamageType::Physical => {
287 damage *= 1.0 + self.anima.dominance * 0.3;
288 }
289 DamageType::Psychic => {
290 damage *= 1.0 + self.anima.arousal * 0.3;
291 }
292 DamageType::Emotional => {
293 damage *= 1.0 + (1.0 - self.anima.stability) * 0.5;
294 }
295 DamageType::Reality => {
296 if matches!(
297 self.reality,
298 RealityLayer::Fractured | RealityLayer::Shattered
299 ) {
300 damage *= 1.5;
301 }
302 }
303 }
304
305 match target {
307 Combatant::Enemy(e) => {
308 e.health = (e.health - damage).max(0.0);
309 }
310 Combatant::Player(p) => {
311 p.health = (p.health - damage).max(0.0);
312 }
313 }
314
315 for effect in &ability.applies_effects {
317 match target {
318 Combatant::Enemy(e) => e.status_effects.push(effect.clone()),
319 Combatant::Player(p) => p.status_effects.push(effect.clone()),
320 }
321 }
322
323 AbilityResult::Success {
324 damage,
325 effects: ability.applies_effects.clone(),
326 }
327 }
328
329 pub fn combat_switch(
331 &mut self,
332 target_alter: &str,
333 system: &mut PluralSystem,
334 ) -> CombatSwitchResult {
335 if self.switch_cooldown > 0 {
336 return CombatSwitchResult::OnCooldown(self.switch_cooldown);
337 }
338
339 if !self.available_alters.contains(&target_alter.to_string()) {
340 return CombatSwitchResult::NotAvailable;
341 }
342
343 let urgency = 0.5 + self.anima.arousal * 0.3; let result = system.request_switch(target_alter, urgency, false);
346
347 match result {
348 SwitchResult::Success => {
349 self.fronting_alter = target_alter.to_string();
350 self.switch_cooldown = 2; if let Some(alter) = system.alters.get(target_alter) {
354 self.abilities = generate_combat_abilities(alter);
355 self.anima = alter.anima.clone();
356 }
357
358 if let Some(alter) = system.alters.get(target_alter) {
360 self.reality = alter.preferred_reality.clone();
361 }
362
363 CombatSwitchResult::Success
364 }
365 SwitchResult::Resisted { resistance } => CombatSwitchResult::Resisted(resistance),
366 SwitchResult::Failed(reason) => CombatSwitchResult::Failed(format!("{:?}", reason)),
367 SwitchResult::InProgress { eta } => CombatSwitchResult::InProgress(eta),
368 }
369 }
370
371 pub fn process_combat_trigger(
373 &mut self,
374 trigger: Trigger,
375 system: &mut PluralSystem,
376 ) -> CombatTriggerResult {
377 let trigger_result = system.process_trigger(trigger.clone());
378
379 match trigger_result {
380 TriggerResult::ForcedSwitch(alter_id) => {
381 self.switch_cooldown = 0;
383 let switch_result = self.combat_switch(&alter_id, system);
384 CombatTriggerResult::ForcedSwitch(alter_id, switch_result)
385 }
386 TriggerResult::Activation(alters) => {
387 for alter_id in &alters {
389 if !self.available_alters.contains(alter_id) {
390 self.available_alters.push(alter_id.clone());
391 }
392 }
393 CombatTriggerResult::AltersActivated(alters)
394 }
395 TriggerResult::Dissociation => {
396 self.anima.apply_trauma_response(0.5);
397 CombatTriggerResult::Dissociation
398 }
399 TriggerResult::NoResponse => CombatTriggerResult::NoEffect,
400 }
401 }
402}
403
404#[derive(Debug, Clone)]
406pub enum CombatSwitchResult {
407 Success,
408 OnCooldown(u32),
409 NotAvailable,
410 Resisted(f32),
411 Failed(String),
412 InProgress(u64),
413}
414
415#[derive(Debug, Clone)]
417pub enum CombatTriggerResult {
418 NoEffect,
419 ForcedSwitch(String, CombatSwitchResult),
420 AltersActivated(Vec<String>),
421 Dissociation,
422}
423
424#[derive(Debug, Clone)]
430pub struct Enemy {
431 pub id: String,
433 pub name: String,
435 pub enemy_type: EnemyType,
437 pub health: f32,
439 pub max_health: f32,
441 pub initiative: f32,
443 pub abilities: Vec<CombatAbility>,
445 pub status_effects: Vec<StatusEffect>,
447 pub trigger_behaviors: Vec<TriggerBehavior>,
449 pub reality_visibility: Vec<RealityLayer>,
451}
452
453#[derive(Debug, Clone, PartialEq)]
455pub enum EnemyType {
456 Human,
458 Manifestation,
460 Hazard,
462 Internal,
464 Entity,
466}
467
468#[derive(Debug, Clone)]
470pub struct TriggerBehavior {
471 pub trigger_id: String,
473 pub condition: TriggerCondition,
475 pub intensity: f32,
477}
478
479#[derive(Debug, Clone)]
481pub enum TriggerCondition {
482 OnTurn,
484 OnAttack,
486 HealthBelow(f32),
488 PlayerLowHealth,
490 OnAbility(String),
492 Random(f32),
494}
495
496#[derive(Debug, Clone)]
502pub struct CombatAbility {
503 pub id: String,
505 pub name: String,
507 pub description: String,
509 pub base_damage: f32,
511 pub damage_type: DamageType,
513 pub psyche_cost: f32,
515 pub cooldown: u32,
517 pub current_cooldown: u32,
519 pub reality_requirement: Option<RealityLayer>,
521 pub alter_requirement: Option<String>,
523 pub applies_effects: Vec<StatusEffect>,
525 pub is_defensive: bool,
527}
528
529#[derive(Debug, Clone, PartialEq)]
531pub enum DamageType {
532 Physical,
533 Psychic,
534 Emotional,
535 Reality, }
537
538#[derive(Debug, Clone)]
540pub enum AbilityResult {
541 Success {
542 damage: f32,
543 effects: Vec<StatusEffect>,
544 },
545 Failed(String),
546 Blocked,
547}
548
549#[derive(Debug, Clone)]
555pub struct StatusEffect {
556 pub id: String,
558 pub name: String,
560 pub duration: Option<u32>,
562 pub effect_type: StatusEffectType,
564 pub intensity: f32,
566}
567
568#[derive(Debug, Clone)]
570pub enum StatusEffectType {
571 Bleed,
573 PsycheDrain,
575 SwitchLocked,
577 RealityLocked(RealityLayer),
579 Shielded,
581 Empowered,
583 Disoriented,
585 Silenced,
587 Triggered(String),
589 Regenerating,
591}
592
593#[derive(Debug, Clone, Default)]
599pub struct CombatEnvironment {
600 pub reality_layers: Vec<RealityLayer>,
602 pub hazards: Vec<EnvironmentHazard>,
604 pub defensive_positions: u32,
606 pub light_level: f32,
608 pub ambient_trigger_intensity: f32,
610}
611
612#[derive(Debug, Clone)]
614pub struct EnvironmentHazard {
615 pub name: String,
616 pub damage_per_turn: f32,
617 pub affects_reality: Option<RealityLayer>,
618}
619
620#[derive(Debug, Clone)]
626pub struct CombatEffect {
627 pub name: String,
628 pub duration: u32,
629 pub effect: CombatEffectType,
630}
631
632#[derive(Debug, Clone)]
633pub enum CombatEffectType {
634 RealityFlux,
636 SystemInstability,
638 EnvironmentChange(String),
640}
641
642fn generate_combat_abilities(alter: &Alter) -> Vec<CombatAbility> {
648 let mut abilities = Vec::new();
649
650 abilities.push(CombatAbility {
652 id: "basic_attack".to_string(),
653 name: "Strike".to_string(),
654 description: "A basic physical attack".to_string(),
655 base_damage: 10.0,
656 damage_type: DamageType::Physical,
657 psyche_cost: 0.0,
658 cooldown: 0,
659 current_cooldown: 0,
660 reality_requirement: None,
661 alter_requirement: None,
662 applies_effects: Vec::new(),
663 is_defensive: false,
664 });
665
666 for ability_name in &alter.abilities {
668 match ability_name.as_str() {
669 "combat" | "combat_master" => {
670 abilities.push(CombatAbility {
671 id: "power_strike".to_string(),
672 name: "Power Strike".to_string(),
673 description: "A devastating physical attack".to_string(),
674 base_damage: 25.0,
675 damage_type: DamageType::Physical,
676 psyche_cost: 10.0,
677 cooldown: 2,
678 current_cooldown: 0,
679 reality_requirement: None,
680 alter_requirement: None,
681 applies_effects: Vec::new(),
682 is_defensive: false,
683 });
684 }
685 "perception" | "perceive" => {
686 abilities.push(CombatAbility {
687 id: "reality_sight".to_string(),
688 name: "Reality Sight".to_string(),
689 description: "Pierce the veil between layers".to_string(),
690 base_damage: 0.0,
691 damage_type: DamageType::Reality,
692 psyche_cost: 15.0,
693 cooldown: 3,
694 current_cooldown: 0,
695 reality_requirement: None,
696 alter_requirement: None,
697 applies_effects: Vec::new(),
698 is_defensive: true,
699 });
700 }
701 "protection" | "shield" => {
702 abilities.push(CombatAbility {
703 id: "protective_barrier".to_string(),
704 name: "Protective Barrier".to_string(),
705 description: "Shield the system from harm".to_string(),
706 base_damage: 0.0,
707 damage_type: DamageType::Psychic,
708 psyche_cost: 20.0,
709 cooldown: 3,
710 current_cooldown: 0,
711 reality_requirement: None,
712 alter_requirement: None,
713 applies_effects: vec![StatusEffect {
714 id: "shielded".to_string(),
715 name: "Shielded".to_string(),
716 duration: Some(2),
717 effect_type: StatusEffectType::Shielded,
718 intensity: 0.5,
719 }],
720 is_defensive: true,
721 });
722 }
723 "trauma_processing" => {
724 abilities.push(CombatAbility {
725 id: "trauma_strike".to_string(),
726 name: "Trauma Strike".to_string(),
727 description: "Channel trauma into devastating force".to_string(),
728 base_damage: 35.0,
729 damage_type: DamageType::Emotional,
730 psyche_cost: 25.0,
731 cooldown: 4,
732 current_cooldown: 0,
733 reality_requirement: Some(RealityLayer::Fractured),
734 alter_requirement: None,
735 applies_effects: Vec::new(),
736 is_defensive: false,
737 });
738 }
739 _ => {}
740 }
741 }
742
743 if matches!(
745 alter.preferred_reality,
746 RealityLayer::Fractured | RealityLayer::Shattered
747 ) {
748 abilities.push(CombatAbility {
749 id: "fractured_assault".to_string(),
750 name: "Fractured Assault".to_string(),
751 description: "Attack from the fractured realm".to_string(),
752 base_damage: 20.0,
753 damage_type: DamageType::Reality,
754 psyche_cost: 15.0,
755 cooldown: 2,
756 current_cooldown: 0,
757 reality_requirement: Some(RealityLayer::Fractured),
758 alter_requirement: None,
759 applies_effects: Vec::new(),
760 is_defensive: false,
761 });
762 }
763
764 abilities
765}
766
767#[cfg(test)]
772mod tests {
773 use super::*;
774 use std::collections::HashSet;
775
776 fn create_test_system() -> PluralSystem {
777 let mut system = PluralSystem::new("Test System");
778
779 let alter = Alter {
780 id: "protector".to_string(),
781 name: "Protector".to_string(),
782 category: super::super::runtime::AlterCategory::Council,
783 state: AlterPresenceState::Fronting,
784 anima: AnimaState::new(0.0, 0.5, 0.7),
785 base_arousal: 0.5,
786 base_dominance: 0.7,
787 time_since_front: 0,
788 triggers: vec!["threat".to_string()],
789 abilities: HashSet::from(["combat".to_string(), "protection".to_string()]),
790 preferred_reality: RealityLayer::Grounded,
791 memory_access: super::super::runtime::MemoryAccess::Full,
792 };
793
794 system.add_alter(alter);
795 system.fronting = FrontingState::Single("protector".to_string());
796 system
797 }
798
799 #[test]
800 fn test_combat_state_creation() {
801 let system = create_test_system();
802 let enemy = Enemy {
803 id: "enemy1".to_string(),
804 name: "Shadow".to_string(),
805 enemy_type: EnemyType::Manifestation,
806 health: 50.0,
807 max_health: 50.0,
808 initiative: 8.0,
809 abilities: Vec::new(),
810 status_effects: Vec::new(),
811 trigger_behaviors: Vec::new(),
812 reality_visibility: vec![RealityLayer::Fractured],
813 };
814
815 let combat = CombatState::new(&system, vec![enemy]);
816 assert_eq!(combat.combatants.len(), 2);
817 assert_eq!(combat.round, 1);
818 assert!(!combat.is_over);
819 }
820
821 #[test]
822 fn test_ability_execution() {
823 let system = create_test_system();
824 let mut player = PlayerCombatant::from_system(&system);
825 let mut enemy = Combatant::Enemy(Enemy {
826 id: "enemy1".to_string(),
827 name: "Target".to_string(),
828 enemy_type: EnemyType::Human,
829 health: 100.0,
830 max_health: 100.0,
831 initiative: 5.0,
832 abilities: Vec::new(),
833 status_effects: Vec::new(),
834 trigger_behaviors: Vec::new(),
835 reality_visibility: vec![RealityLayer::Grounded],
836 });
837
838 let result = player.execute_ability(0, &mut enemy);
840 assert!(matches!(result, AbilityResult::Success { .. }));
841
842 if let Combatant::Enemy(e) = &enemy {
844 assert!(e.health < 100.0);
845 }
846 }
847}