use crate::context::StatContext;
use crate::numeric::StatNumeric;
use crate::resolver::StatResolver;
use crate::stat_id::StatId;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum TriggerCondition {
Below(f64),
Above(f64),
Empty,
Full,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ThresholdTrigger {
pub condition: TriggerCondition,
pub event_name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TimeEffect {
pub name: String,
pub amount_per_tick: f64,
pub ticks_remaining: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResourceEvent {
pub event_name: String,
}
#[derive(Debug, Clone)]
pub struct ResourcePool {
pub max_stat_id: StatId,
pub current_value: f64,
pub active_effects: Vec<TimeEffect>,
pub triggers: Vec<ThresholdTrigger>,
}
impl ResourcePool {
pub fn new(max_stat_id: StatId, resolver: &mut StatResolver, context: &StatContext) -> Self {
let max_val = resolver
.resolve(&max_stat_id, context)
.map(|r| r.value.to_f64())
.unwrap_or(0.0);
Self {
max_stat_id,
current_value: max_val,
active_effects: Vec::new(),
triggers: Vec::new(),
}
}
pub fn add_effect(&mut self, effect: TimeEffect) {
self.active_effects.push(effect);
}
pub fn add_trigger(&mut self, trigger: ThresholdTrigger) {
self.triggers.push(trigger);
}
pub fn apply_damage(
&mut self,
amount: f64,
resolver: &mut StatResolver,
context: &StatContext,
) -> Vec<ResourceEvent> {
self.change_value(-amount, resolver, context)
}
pub fn apply_heal(
&mut self,
amount: f64,
resolver: &mut StatResolver,
context: &StatContext,
) -> Vec<ResourceEvent> {
self.change_value(amount, resolver, context)
}
pub fn tick(
&mut self,
resolver: &mut StatResolver,
context: &StatContext,
) -> Vec<ResourceEvent> {
let mut total_change = 0.0;
self.active_effects.retain_mut(|effect| {
if effect.ticks_remaining > 0 {
total_change += effect.amount_per_tick;
effect.ticks_remaining -= 1;
effect.ticks_remaining > 0
} else {
false
}
});
if total_change != 0.0 {
self.change_value(total_change, resolver, context)
} else {
self.change_value(0.0, resolver, context)
}
}
fn change_value(
&mut self,
delta: f64,
resolver: &mut StatResolver,
context: &StatContext,
) -> Vec<ResourceEvent> {
let max_val = resolver
.resolve(&self.max_stat_id, context)
.map(|r| r.value.to_f64())
.unwrap_or(0.0);
self.current_value = (self.current_value + delta).clamp(0.0, max_val);
self.check_triggers(resolver, context)
}
fn check_triggers(
&self,
resolver: &mut StatResolver,
context: &StatContext,
) -> Vec<ResourceEvent> {
let mut events = Vec::new();
let max_val = resolver
.resolve(&self.max_stat_id, context)
.map(|r| r.value.to_f64())
.unwrap_or(0.0);
for trigger in &self.triggers {
let is_met = match trigger.condition {
TriggerCondition::Below(threshold) => self.current_value < threshold,
TriggerCondition::Above(threshold) => self.current_value > threshold,
TriggerCondition::Empty => self.current_value <= 0.0,
TriggerCondition::Full => self.current_value >= max_val,
};
if is_met {
events.push(ResourceEvent {
event_name: trigger.event_name.clone(),
});
}
}
events
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::source::ConstantSource;
#[test]
fn test_resource_pool_tick_and_triggers() {
let mut resolver = StatResolver::new();
let context = StatContext::new();
let max_hp_id = StatId::from("MAX_HP");
resolver.register_source(max_hp_id.clone(), Box::new(ConstantSource(100.0)));
let mut pool = ResourcePool::new(max_hp_id, &mut resolver, &context);
assert_eq!(pool.current_value, 100.0);
pool.add_trigger(ThresholdTrigger {
condition: TriggerCondition::Empty,
event_name: "DEATH".to_string(),
});
pool.add_effect(TimeEffect {
name: "Poison".to_string(),
amount_per_tick: -10.0,
ticks_remaining: 3,
});
let events = pool.tick(&mut resolver, &context);
assert_eq!(pool.current_value, 90.0);
assert!(events.is_empty());
pool.tick(&mut resolver, &context);
assert_eq!(pool.current_value, 80.0);
pool.tick(&mut resolver, &context);
assert_eq!(pool.current_value, 70.0);
pool.tick(&mut resolver, &context);
assert_eq!(pool.current_value, 70.0);
let events = pool.apply_damage(100.0, &mut resolver, &context);
assert_eq!(pool.current_value, 0.0);
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_name, "DEATH");
}
#[test]
fn test_resource_pool_healing_and_full() {
let mut resolver = StatResolver::new();
let context = StatContext::new();
let max_hp_id = StatId::from("MAX_HP");
resolver.register_source(max_hp_id.clone(), Box::new(ConstantSource(100.0)));
let mut pool = ResourcePool::new(max_hp_id, &mut resolver, &context);
pool.apply_damage(50.0, &mut resolver, &context); assert_eq!(pool.current_value, 50.0);
pool.add_trigger(ThresholdTrigger {
condition: TriggerCondition::Full,
event_name: "FULLY_HEALED".to_string(),
});
pool.add_effect(TimeEffect {
name: "Regen".to_string(),
amount_per_tick: 20.0,
ticks_remaining: 3,
});
pool.tick(&mut resolver, &context);
pool.tick(&mut resolver, &context);
let events = pool.tick(&mut resolver, &context);
assert_eq!(pool.current_value, 100.0);
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_name, "FULLY_HEALED");
}
}