issun 0.10.0

A mini game engine for logic-focused games - Build games in ISSUN (一寸) of time
Documentation
//! Room buff plugin implementation

use super::hook::{DefaultRoomBuffHook, RoomBuffHook};
use super::service::BuffService;
use super::system::BuffSystem;
use super::types::{ActiveBuffs, RoomBuffDatabase};
use crate::Plugin;
use std::sync::Arc;

/// Room buff plugin
///
/// Provides temporary buff management functionality with:
/// - Buff database for buff definitions
/// - Active buff tracking
/// - Turn-based duration management
/// - Customizable buff effects via hooks
/// - Event-driven architecture for loose coupling
///
/// # Hook Customization
///
/// You can provide a custom hook to add game-specific behavior:
/// - Apply buff effects to combatants (stat bonuses)
/// - Apply buff effects to drop rates
/// - Per-turn buff effects (HP regen, damage over time)
/// - Log buff events
///
/// # Example
///
/// ```ignore
/// use issun::builder::GameBuilder;
/// use issun::plugin::room_buff::{RoomBuffPlugin, RoomBuffHook, RoomBuffDatabase};
/// use async_trait::async_trait;
///
/// // Custom hook for buff effects
/// struct MyRoomBuffHook;
///
/// #[async_trait]
/// impl RoomBuffHook for MyRoomBuffHook {
///     async fn on_buff_applied(
///         &self,
///         buff: &ActiveBuff,
///         resources: &mut ResourceContext,
///     ) {
///         // Apply stat bonuses to player
///         match &buff.config.effect {
///             BuffEffect::AttackBonus(n) => { /* Increase attack */ }
///             _ => {}
///         }
///     }
/// }
///
/// let database = RoomBuffDatabase::new()
///     .with_buff("haste", BuffConfig { ... });
///
/// let game = GameBuilder::new()
///     .with_plugin(
///         RoomBuffPlugin::new()
///             .with_database(database)
///             .with_hook(MyRoomBuffHook)
///     )
///     .build()
///     .await?;
/// ```
#[derive(Plugin)]
#[plugin(name = "issun:room_buff")]
pub struct RoomBuffPlugin {
    #[plugin(skip)]
    hook: Arc<dyn RoomBuffHook>,

    #[resource]
    database: RoomBuffDatabase,

    #[state]
    active_buffs: ActiveBuffs,

    #[service]
    service: BuffService,

    #[system]
    system: BuffSystem,
}

impl RoomBuffPlugin {
    /// Create a new room buff plugin
    ///
    /// Uses the default hook (no-op) and empty database by default.
    /// Use `with_hook()` to add custom behavior and `with_database()` to add buff definitions.
    pub fn new() -> Self {
        let hook = Arc::new(DefaultRoomBuffHook);
        Self {
            hook: hook.clone(),
            database: RoomBuffDatabase::default(),
            active_buffs: ActiveBuffs::default(),
            service: BuffService::new(),
            system: BuffSystem::new(hook),
        }
    }

    /// Add a custom hook for room buff behavior
    ///
    /// The hook will be called when:
    /// - Buffs are applied (`on_buff_applied`) - **main effect application**
    /// - Buffs are removed (`on_buff_removed`)
    /// - Buffs expire (`on_buff_expired`)
    /// - Buffs tick each turn (`on_buff_tick`) - per-turn effects
    ///
    /// # Arguments
    ///
    /// * `hook` - Implementation of RoomBuffHook trait
    ///
    /// # Example
    ///
    /// ```ignore
    /// use issun::plugin::room_buff::{RoomBuffPlugin, RoomBuffHook};
    ///
    /// struct MyHook;
    ///
    /// #[async_trait]
    /// impl RoomBuffHook for MyHook {
    ///     async fn on_buff_applied(
    ///         &self,
    ///         buff: &ActiveBuff,
    ///         resources: &mut ResourceContext,
    ///     ) {
    ///         // Custom buff effects...
    ///     }
    /// }
    ///
    /// let plugin = RoomBuffPlugin::new().with_hook(MyHook);
    /// ```
    pub fn with_hook<H: RoomBuffHook + 'static>(mut self, hook: H) -> Self {
        let hook = Arc::new(hook);
        self.hook = hook.clone();
        self.system = BuffSystem::new(hook);
        self
    }

    /// Set buff database
    ///
    /// # Arguments
    ///
    /// * `database` - Buff definitions database
    ///
    /// # Example
    ///
    /// ```ignore
    /// use issun::plugin::room_buff::{RoomBuffPlugin, RoomBuffDatabase, BuffConfig, BuffDuration, BuffEffect};
    ///
    /// let database = RoomBuffDatabase::new()
    ///     .with_buff("haste", BuffConfig {
    ///         id: "haste".to_string(),
    ///         name: "Haste".to_string(),
    ///         duration: BuffDuration::Turns(5),
    ///         effect: BuffEffect::AttackBonus(10),
    ///     });
    ///
    /// let plugin = RoomBuffPlugin::new().with_database(database);
    /// ```
    pub fn with_database(mut self, database: RoomBuffDatabase) -> Self {
        self.database = database;
        self
    }
}

impl Default for RoomBuffPlugin {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::plugin::Plugin;

    #[test]
    fn test_plugin_creation() {
        let plugin = RoomBuffPlugin::new();
        assert_eq!(plugin.name(), "issun:room_buff");
    }

    #[test]
    fn test_plugin_with_custom_hook() {
        struct CustomHook;

        #[async_trait::async_trait]
        impl RoomBuffHook for CustomHook {}

        let plugin = RoomBuffPlugin::new().with_hook(CustomHook);
        assert_eq!(plugin.name(), "issun:room_buff");
    }

    #[test]
    fn test_plugin_with_database() {
        let database = RoomBuffDatabase::new();
        let plugin = RoomBuffPlugin::new().with_database(database);
        assert_eq!(plugin.name(), "issun:room_buff");
    }
}