1use std::collections::HashMap;
7
8use super::runtime::{
9 Alter, AlterPresenceState, AnimaState, FrontingState, PluralSystem, RealityLayer,
10 SwitchResult, 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(player_system))];
44 combatants.extend(enemies.into_iter().map(Combatant::Enemy));
45
46 let mut state = Self {
47 combatants,
48 turn_order: Vec::new(),
49 current_turn: 0,
50 round: 1,
51 environment: CombatEnvironment::default(),
52 effects: Vec::new(),
53 phase: CombatPhase::PreCombat,
54 is_over: false,
55 result: None,
56 };
57
58 state.calculate_turn_order();
59 state
60 }
61
62 fn calculate_turn_order(&mut self) {
64 let mut order: Vec<(usize, f32)> = self
65 .combatants
66 .iter()
67 .enumerate()
68 .map(|(i, c)| (i, c.initiative()))
69 .collect();
70
71 order.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
73
74 self.turn_order = order.into_iter().map(|(i, _)| i).collect();
75 }
76
77 pub fn current_combatant(&self) -> Option<&Combatant> {
79 self.turn_order
80 .get(self.current_turn)
81 .and_then(|&idx| self.combatants.get(idx))
82 }
83
84 pub fn current_combatant_mut(&mut self) -> Option<&mut Combatant> {
86 let idx = *self.turn_order.get(self.current_turn)?;
87 self.combatants.get_mut(idx)
88 }
89
90 pub fn next_turn(&mut self) {
92 self.current_turn += 1;
93 if self.current_turn >= self.turn_order.len() {
94 self.current_turn = 0;
95 self.round += 1;
96 self.phase = CombatPhase::Combat;
97 }
98 }
99
100 pub fn check_combat_end(&mut self) {
102 let player_alive = self.combatants.iter().any(|c| {
103 matches!(c, Combatant::Player(p) if p.health > 0.0)
104 });
105
106 let enemies_alive = self.combatants.iter().any(|c| {
107 matches!(c, Combatant::Enemy(e) if e.health > 0.0)
108 });
109
110 if !player_alive {
111 self.is_over = true;
112 self.result = Some(CombatResult::Defeat);
113 } else if !enemies_alive {
114 self.is_over = true;
115 self.result = Some(CombatResult::Victory);
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq)]
122pub enum CombatPhase {
123 PreCombat,
125 Combat,
127 Event,
129 PostCombat,
131}
132
133#[derive(Debug, Clone, PartialEq)]
135pub enum CombatResult {
136 Victory,
137 Defeat,
138 Flee,
139 Negotiated,
140}
141
142#[derive(Debug, Clone)]
148pub enum Combatant {
149 Player(PlayerCombatant),
150 Enemy(Enemy),
151}
152
153impl Combatant {
154 pub fn initiative(&self) -> f32 {
156 match self {
157 Combatant::Player(p) => p.initiative(),
158 Combatant::Enemy(e) => e.initiative,
159 }
160 }
161
162 pub fn name(&self) -> &str {
164 match self {
165 Combatant::Player(p) => &p.fronting_alter,
166 Combatant::Enemy(e) => &e.name,
167 }
168 }
169}
170
171#[derive(Debug, Clone)]
173pub struct PlayerCombatant {
174 pub fronting_alter: String,
176 pub health: f32,
178 pub max_health: f32,
180 pub psyche: f32,
182 pub max_psyche: f32,
184 pub anima: AnimaState,
186 pub reality: RealityLayer,
188 pub abilities: Vec<CombatAbility>,
190 pub status_effects: Vec<StatusEffect>,
192 pub switch_cooldown: u32,
194 pub available_alters: Vec<String>,
196 pub active_triggers: Vec<String>,
198}
199
200impl PlayerCombatant {
201 pub fn from_system(system: &PluralSystem) -> Self {
203 let fronting = match &system.fronting {
204 FrontingState::Single(id) => id.clone(),
205 FrontingState::Blended(ids) => ids.first().cloned().unwrap_or_default(),
206 _ => "Unknown".to_string(),
207 };
208
209 let fronter = system.alters.get(&fronting);
210 let abilities = fronter
211 .map(|a| generate_combat_abilities(a))
212 .unwrap_or_default();
213
214 let available: Vec<String> = system
215 .alters
216 .values()
217 .filter(|a| {
218 !matches!(a.state, AlterPresenceState::Dormant | AlterPresenceState::Dissociating)
219 })
220 .map(|a| a.id.clone())
221 .collect();
222
223 Self {
224 fronting_alter: fronting,
225 health: 100.0,
226 max_health: 100.0,
227 psyche: 50.0,
228 max_psyche: 50.0,
229 anima: system.anima.clone(),
230 reality: system.reality_layer.clone(),
231 abilities,
232 status_effects: Vec::new(),
233 switch_cooldown: 0,
234 available_alters: available,
235 active_triggers: Vec::new(),
236 }
237 }
238
239 pub fn initiative(&self) -> f32 {
241 let base = 10.0;
242 let arousal_bonus = self.anima.arousal * 5.0; let dominance_bonus = self.anima.dominance * 2.0;
244 base + arousal_bonus + dominance_bonus
245 }
246
247 pub fn execute_ability(&mut self, ability_index: usize, target: &mut Combatant) -> AbilityResult {
249 if ability_index >= self.abilities.len() {
250 return AbilityResult::Failed("Invalid ability".to_string());
251 }
252
253 let ability = &self.abilities[ability_index].clone();
254
255 if self.psyche < ability.psyche_cost {
257 return AbilityResult::Failed("Not enough psyche".to_string());
258 }
259
260 if let Some(ref required) = ability.reality_requirement {
262 if &self.reality != required {
263 return AbilityResult::Failed("Wrong reality layer".to_string());
264 }
265 }
266
267 self.psyche -= ability.psyche_cost;
269
270 let mut damage = ability.base_damage;
272
273 match ability.damage_type {
275 DamageType::Physical => {
276 damage *= 1.0 + self.anima.dominance * 0.3;
277 }
278 DamageType::Psychic => {
279 damage *= 1.0 + self.anima.arousal * 0.3;
280 }
281 DamageType::Emotional => {
282 damage *= 1.0 + (1.0 - self.anima.stability) * 0.5;
283 }
284 DamageType::Reality => {
285 if matches!(self.reality, RealityLayer::Fractured | RealityLayer::Shattered) {
286 damage *= 1.5;
287 }
288 }
289 }
290
291 match target {
293 Combatant::Enemy(e) => {
294 e.health = (e.health - damage).max(0.0);
295 }
296 Combatant::Player(p) => {
297 p.health = (p.health - damage).max(0.0);
298 }
299 }
300
301 for effect in &ability.applies_effects {
303 match target {
304 Combatant::Enemy(e) => e.status_effects.push(effect.clone()),
305 Combatant::Player(p) => p.status_effects.push(effect.clone()),
306 }
307 }
308
309 AbilityResult::Success { damage, effects: ability.applies_effects.clone() }
310 }
311
312 pub fn combat_switch(&mut self, target_alter: &str, system: &mut PluralSystem) -> CombatSwitchResult {
314 if self.switch_cooldown > 0 {
315 return CombatSwitchResult::OnCooldown(self.switch_cooldown);
316 }
317
318 if !self.available_alters.contains(&target_alter.to_string()) {
319 return CombatSwitchResult::NotAvailable;
320 }
321
322 let urgency = 0.5 + self.anima.arousal * 0.3; let result = system.request_switch(target_alter, urgency, false);
325
326 match result {
327 SwitchResult::Success => {
328 self.fronting_alter = target_alter.to_string();
329 self.switch_cooldown = 2; if let Some(alter) = system.alters.get(target_alter) {
333 self.abilities = generate_combat_abilities(alter);
334 self.anima = alter.anima.clone();
335 }
336
337 if let Some(alter) = system.alters.get(target_alter) {
339 self.reality = alter.preferred_reality.clone();
340 }
341
342 CombatSwitchResult::Success
343 }
344 SwitchResult::Resisted { resistance } => {
345 CombatSwitchResult::Resisted(resistance)
346 }
347 SwitchResult::Failed(reason) => {
348 CombatSwitchResult::Failed(format!("{:?}", reason))
349 }
350 SwitchResult::InProgress { eta } => {
351 CombatSwitchResult::InProgress(eta)
352 }
353 }
354 }
355
356 pub fn process_combat_trigger(&mut self, trigger: Trigger, system: &mut PluralSystem) -> CombatTriggerResult {
358 let trigger_result = system.process_trigger(trigger.clone());
359
360 match trigger_result {
361 TriggerResult::ForcedSwitch(alter_id) => {
362 self.switch_cooldown = 0;
364 let switch_result = self.combat_switch(&alter_id, system);
365 CombatTriggerResult::ForcedSwitch(alter_id, switch_result)
366 }
367 TriggerResult::Activation(alters) => {
368 for alter_id in &alters {
370 if !self.available_alters.contains(alter_id) {
371 self.available_alters.push(alter_id.clone());
372 }
373 }
374 CombatTriggerResult::AltersActivated(alters)
375 }
376 TriggerResult::Dissociation => {
377 self.anima.apply_trauma_response(0.5);
378 CombatTriggerResult::Dissociation
379 }
380 TriggerResult::NoResponse => {
381 CombatTriggerResult::NoEffect
382 }
383 }
384 }
385}
386
387#[derive(Debug, Clone)]
389pub enum CombatSwitchResult {
390 Success,
391 OnCooldown(u32),
392 NotAvailable,
393 Resisted(f32),
394 Failed(String),
395 InProgress(u64),
396}
397
398#[derive(Debug, Clone)]
400pub enum CombatTriggerResult {
401 NoEffect,
402 ForcedSwitch(String, CombatSwitchResult),
403 AltersActivated(Vec<String>),
404 Dissociation,
405}
406
407#[derive(Debug, Clone)]
413pub struct Enemy {
414 pub id: String,
416 pub name: String,
418 pub enemy_type: EnemyType,
420 pub health: f32,
422 pub max_health: f32,
424 pub initiative: f32,
426 pub abilities: Vec<CombatAbility>,
428 pub status_effects: Vec<StatusEffect>,
430 pub trigger_behaviors: Vec<TriggerBehavior>,
432 pub reality_visibility: Vec<RealityLayer>,
434}
435
436#[derive(Debug, Clone, PartialEq)]
438pub enum EnemyType {
439 Human,
441 Manifestation,
443 Hazard,
445 Internal,
447 Entity,
449}
450
451#[derive(Debug, Clone)]
453pub struct TriggerBehavior {
454 pub trigger_id: String,
456 pub condition: TriggerCondition,
458 pub intensity: f32,
460}
461
462#[derive(Debug, Clone)]
464pub enum TriggerCondition {
465 OnTurn,
467 OnAttack,
469 HealthBelow(f32),
471 PlayerLowHealth,
473 OnAbility(String),
475 Random(f32),
477}
478
479#[derive(Debug, Clone)]
485pub struct CombatAbility {
486 pub id: String,
488 pub name: String,
490 pub description: String,
492 pub base_damage: f32,
494 pub damage_type: DamageType,
496 pub psyche_cost: f32,
498 pub cooldown: u32,
500 pub current_cooldown: u32,
502 pub reality_requirement: Option<RealityLayer>,
504 pub alter_requirement: Option<String>,
506 pub applies_effects: Vec<StatusEffect>,
508 pub is_defensive: bool,
510}
511
512#[derive(Debug, Clone, PartialEq)]
514pub enum DamageType {
515 Physical,
516 Psychic,
517 Emotional,
518 Reality, }
520
521#[derive(Debug, Clone)]
523pub enum AbilityResult {
524 Success { damage: f32, effects: Vec<StatusEffect> },
525 Failed(String),
526 Blocked,
527}
528
529#[derive(Debug, Clone)]
535pub struct StatusEffect {
536 pub id: String,
538 pub name: String,
540 pub duration: Option<u32>,
542 pub effect_type: StatusEffectType,
544 pub intensity: f32,
546}
547
548#[derive(Debug, Clone)]
550pub enum StatusEffectType {
551 Bleed,
553 PsycheDrain,
555 SwitchLocked,
557 RealityLocked(RealityLayer),
559 Shielded,
561 Empowered,
563 Disoriented,
565 Silenced,
567 Triggered(String),
569 Regenerating,
571}
572
573#[derive(Debug, Clone, Default)]
579pub struct CombatEnvironment {
580 pub reality_layers: Vec<RealityLayer>,
582 pub hazards: Vec<EnvironmentHazard>,
584 pub defensive_positions: u32,
586 pub light_level: f32,
588 pub ambient_trigger_intensity: f32,
590}
591
592#[derive(Debug, Clone)]
594pub struct EnvironmentHazard {
595 pub name: String,
596 pub damage_per_turn: f32,
597 pub affects_reality: Option<RealityLayer>,
598}
599
600#[derive(Debug, Clone)]
606pub struct CombatEffect {
607 pub name: String,
608 pub duration: u32,
609 pub effect: CombatEffectType,
610}
611
612#[derive(Debug, Clone)]
613pub enum CombatEffectType {
614 RealityFlux,
616 SystemInstability,
618 EnvironmentChange(String),
620}
621
622fn generate_combat_abilities(alter: &Alter) -> Vec<CombatAbility> {
628 let mut abilities = Vec::new();
629
630 abilities.push(CombatAbility {
632 id: "basic_attack".to_string(),
633 name: "Strike".to_string(),
634 description: "A basic physical attack".to_string(),
635 base_damage: 10.0,
636 damage_type: DamageType::Physical,
637 psyche_cost: 0.0,
638 cooldown: 0,
639 current_cooldown: 0,
640 reality_requirement: None,
641 alter_requirement: None,
642 applies_effects: Vec::new(),
643 is_defensive: false,
644 });
645
646 for ability_name in &alter.abilities {
648 match ability_name.as_str() {
649 "combat" | "combat_master" => {
650 abilities.push(CombatAbility {
651 id: "power_strike".to_string(),
652 name: "Power Strike".to_string(),
653 description: "A devastating physical attack".to_string(),
654 base_damage: 25.0,
655 damage_type: DamageType::Physical,
656 psyche_cost: 10.0,
657 cooldown: 2,
658 current_cooldown: 0,
659 reality_requirement: None,
660 alter_requirement: None,
661 applies_effects: Vec::new(),
662 is_defensive: false,
663 });
664 }
665 "perception" | "perceive" => {
666 abilities.push(CombatAbility {
667 id: "reality_sight".to_string(),
668 name: "Reality Sight".to_string(),
669 description: "Pierce the veil between layers".to_string(),
670 base_damage: 0.0,
671 damage_type: DamageType::Reality,
672 psyche_cost: 15.0,
673 cooldown: 3,
674 current_cooldown: 0,
675 reality_requirement: None,
676 alter_requirement: None,
677 applies_effects: Vec::new(),
678 is_defensive: true,
679 });
680 }
681 "protection" | "shield" => {
682 abilities.push(CombatAbility {
683 id: "protective_barrier".to_string(),
684 name: "Protective Barrier".to_string(),
685 description: "Shield the system from harm".to_string(),
686 base_damage: 0.0,
687 damage_type: DamageType::Psychic,
688 psyche_cost: 20.0,
689 cooldown: 3,
690 current_cooldown: 0,
691 reality_requirement: None,
692 alter_requirement: None,
693 applies_effects: vec![StatusEffect {
694 id: "shielded".to_string(),
695 name: "Shielded".to_string(),
696 duration: Some(2),
697 effect_type: StatusEffectType::Shielded,
698 intensity: 0.5,
699 }],
700 is_defensive: true,
701 });
702 }
703 "trauma_processing" => {
704 abilities.push(CombatAbility {
705 id: "trauma_strike".to_string(),
706 name: "Trauma Strike".to_string(),
707 description: "Channel trauma into devastating force".to_string(),
708 base_damage: 35.0,
709 damage_type: DamageType::Emotional,
710 psyche_cost: 25.0,
711 cooldown: 4,
712 current_cooldown: 0,
713 reality_requirement: Some(RealityLayer::Fractured),
714 alter_requirement: None,
715 applies_effects: Vec::new(),
716 is_defensive: false,
717 });
718 }
719 _ => {}
720 }
721 }
722
723 if matches!(alter.preferred_reality, RealityLayer::Fractured | RealityLayer::Shattered) {
725 abilities.push(CombatAbility {
726 id: "fractured_assault".to_string(),
727 name: "Fractured Assault".to_string(),
728 description: "Attack from the fractured realm".to_string(),
729 base_damage: 20.0,
730 damage_type: DamageType::Reality,
731 psyche_cost: 15.0,
732 cooldown: 2,
733 current_cooldown: 0,
734 reality_requirement: Some(RealityLayer::Fractured),
735 alter_requirement: None,
736 applies_effects: Vec::new(),
737 is_defensive: false,
738 });
739 }
740
741 abilities
742}
743
744#[cfg(test)]
749mod tests {
750 use super::*;
751 use std::collections::HashSet;
752
753 fn create_test_system() -> PluralSystem {
754 let mut system = PluralSystem::new("Test System");
755
756 let alter = Alter {
757 id: "protector".to_string(),
758 name: "Protector".to_string(),
759 category: super::super::runtime::AlterCategory::Council,
760 state: AlterPresenceState::Fronting,
761 anima: AnimaState::new(0.0, 0.5, 0.7),
762 base_arousal: 0.5,
763 base_dominance: 0.7,
764 time_since_front: 0,
765 triggers: vec!["threat".to_string()],
766 abilities: HashSet::from(["combat".to_string(), "protection".to_string()]),
767 preferred_reality: RealityLayer::Grounded,
768 memory_access: super::super::runtime::MemoryAccess::Full,
769 };
770
771 system.add_alter(alter);
772 system.fronting = FrontingState::Single("protector".to_string());
773 system
774 }
775
776 #[test]
777 fn test_combat_state_creation() {
778 let system = create_test_system();
779 let enemy = Enemy {
780 id: "enemy1".to_string(),
781 name: "Shadow".to_string(),
782 enemy_type: EnemyType::Manifestation,
783 health: 50.0,
784 max_health: 50.0,
785 initiative: 8.0,
786 abilities: Vec::new(),
787 status_effects: Vec::new(),
788 trigger_behaviors: Vec::new(),
789 reality_visibility: vec![RealityLayer::Fractured],
790 };
791
792 let combat = CombatState::new(&system, vec![enemy]);
793 assert_eq!(combat.combatants.len(), 2);
794 assert_eq!(combat.round, 1);
795 assert!(!combat.is_over);
796 }
797
798 #[test]
799 fn test_ability_execution() {
800 let system = create_test_system();
801 let mut player = PlayerCombatant::from_system(&system);
802 let mut enemy = Combatant::Enemy(Enemy {
803 id: "enemy1".to_string(),
804 name: "Target".to_string(),
805 enemy_type: EnemyType::Human,
806 health: 100.0,
807 max_health: 100.0,
808 initiative: 5.0,
809 abilities: Vec::new(),
810 status_effects: Vec::new(),
811 trigger_behaviors: Vec::new(),
812 reality_visibility: vec![RealityLayer::Grounded],
813 });
814
815 let result = player.execute_ability(0, &mut enemy);
817 assert!(matches!(result, AbilityResult::Success { .. }));
818
819 if let Combatant::Enemy(e) = &enemy {
821 assert!(e.health < 100.0);
822 }
823 }
824}