use bevy::prelude::*;
use issun_core::mechanics::combat::{CombatConfig, CombatInput};
use issun_core::mechanics::{ExecutionHint, Mechanic};
use std::marker::PhantomData;
use super::systems::{damage_system, log_combat_events};
use super::types::{
Attack, CombatConfigResource, CombatEventWrapper, DamageApplied, DamageRequested, Defense,
ElementType, Health,
};
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub struct CombatSequentialSet;
pub struct CombatPluginV2<M>
where
M: Mechanic<
Config = CombatConfig,
State = issun_core::mechanics::combat::CombatState,
Input = CombatInput,
Event = issun_core::mechanics::combat::CombatEvent,
>,
{
pub config: CombatConfig,
_phantom: PhantomData<M>,
}
impl<M> Default for CombatPluginV2<M>
where
M: Mechanic<
Config = CombatConfig,
State = issun_core::mechanics::combat::CombatState,
Input = CombatInput,
Event = issun_core::mechanics::combat::CombatEvent,
>,
{
fn default() -> Self {
Self {
config: CombatConfig::default(),
_phantom: PhantomData,
}
}
}
impl<M> CombatPluginV2<M>
where
M: Mechanic<
Config = CombatConfig,
State = issun_core::mechanics::combat::CombatState,
Input = CombatInput,
Event = issun_core::mechanics::combat::CombatEvent,
>,
{
pub fn with_config(config: CombatConfig) -> Self {
Self {
config,
_phantom: PhantomData,
}
}
}
impl<M> Plugin for CombatPluginV2<M>
where
M: Mechanic<
Config = CombatConfig,
State = issun_core::mechanics::combat::CombatState,
Input = CombatInput,
Event = issun_core::mechanics::combat::CombatEvent,
> + Send
+ Sync
+ 'static,
{
fn build(&self, app: &mut App) {
app.insert_resource(CombatConfigResource::new(self.config.clone()));
app.register_type::<CombatConfigResource>();
app.register_type::<Health>();
app.register_type::<Attack>();
app.register_type::<Defense>();
app.register_type::<ElementType>();
app.add_message::<DamageRequested>();
app.add_message::<DamageApplied>();
app.add_message::<CombatEventWrapper>();
if M::Execution::PARALLEL_SAFE {
app.add_systems(Update, (damage_system::<M>, log_combat_events));
info!(
"CombatPluginV2 initialized with mechanic: {} (parallel-safe)",
std::any::type_name::<M>()
);
} else {
if let Some(schedule) = M::Execution::PREFERRED_SCHEDULE {
info!(
"CombatPluginV2 initialized with mechanic: {} (sequential, schedule: {})",
std::any::type_name::<M>(),
schedule
);
warn!(
"PREFERRED_SCHEDULE '{}' specified but not yet implemented, using Update",
schedule
);
app.add_systems(Update, damage_system::<M>.in_set(CombatSequentialSet));
} else {
info!(
"CombatPluginV2 initialized with mechanic: {} (sequential)",
std::any::type_name::<M>()
);
app.add_systems(Update, damage_system::<M>.in_set(CombatSequentialSet));
}
app.add_systems(Update, log_combat_events);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use issun_core::mechanics::combat::prelude::*;
type TestCombat = CombatMechanic;
#[test]
fn test_plugin_builds() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
app.add_plugins(CombatPluginV2::<TestCombat>::default());
assert!(app.world().contains_resource::<CombatConfigResource>());
}
#[test]
fn test_plugin_with_custom_config() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
let config = CombatConfig {
min_damage: 5,
critical_multiplier: 3.0,
};
app.add_plugins(CombatPluginV2::<TestCombat>::with_config(config.clone()));
let resource = app.world().resource::<CombatConfigResource>();
assert_eq!(resource.config.min_damage, 5);
assert_eq!(resource.config.critical_multiplier, 3.0);
}
#[test]
fn test_full_combat_flow() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
app.add_plugins(CombatPluginV2::<TestCombat>::default());
let attacker = app
.world_mut()
.spawn((Attack { power: 50 }, Name::new("Knight")))
.id();
let target = app
.world_mut()
.spawn((Health::new(100), Defense { value: 15 }, Name::new("Goblin")))
.id();
app.world_mut()
.write_message(DamageRequested { attacker, target });
app.update();
let health = app.world().get::<Health>(target).unwrap();
assert_eq!(health.current, 65); }
#[test]
fn test_elemental_combat_plugin() {
type ElementalCombat =
CombatMechanic<LinearDamageCalculation, SubtractiveDefense, ElementalAffinity>;
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
app.add_plugins(CombatPluginV2::<ElementalCombat>::default());
use issun_core::mechanics::combat::Element;
let attacker = app
.world_mut()
.spawn((
Attack { power: 50 },
ElementType {
element: Element::Fire,
},
Name::new("Fire Mage"),
))
.id();
let target = app
.world_mut()
.spawn((
Health::new(100),
Defense { value: 10 },
ElementType {
element: Element::Ice,
},
Name::new("Ice Golem"),
))
.id();
app.world_mut()
.write_message(DamageRequested { attacker, target });
app.update();
let health = app.world().get::<Health>(target).unwrap();
assert_eq!(health.current, 20); }
}