use crate::context::StatContext;
use crate::error::StatError;
use crate::numeric::{StatNumeric, StatValue};
use crate::stat_id::StatId;
use crate::transform::{
AdditiveTransform, ClampTransform, MultiplicativeTransform, ScalingTransform, StackRule,
StatTransform, TransformPhase,
};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum BonusAction {
AddFlat { value: f64 },
ScaleFrom { source: StatId, factor: f64 },
Multiply { multiplier: f64 },
Override { value: f64 },
ClampMin { value: f64 },
ClampMax { value: f64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bonus {
pub target: StatId,
pub action: BonusAction,
pub phase: TransformPhase,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub condition: Option<crate::condition::ConditionDef>,
}
pub struct AddBonusBuilder {
target: StatId,
}
pub struct ScaleBonusBuilder {
target: StatId,
source: StatId,
}
pub struct MulBonusBuilder {
target: StatId,
}
pub struct AddBonusBuilderWithValue {
target: StatId,
value: f64,
}
pub struct ScaleBonusBuilderWithFactor {
target: StatId,
source: StatId,
factor: f64,
}
pub struct MulBonusBuilderWithValue {
target: StatId,
multiplier: f64,
}
impl Bonus {
pub fn add(target: StatId) -> AddBonusBuilder {
AddBonusBuilder { target }
}
pub fn scale(target: StatId, source: StatId) -> ScaleBonusBuilder {
ScaleBonusBuilder { target, source }
}
pub fn mul(target: StatId) -> MulBonusBuilder {
MulBonusBuilder { target }
}
pub fn r#override(target: StatId, value: f64) -> OverrideBonusBuilder {
OverrideBonusBuilder { target, value }
}
pub fn clamp_min(target: StatId, value: f64) -> ClampMinBonusBuilder {
ClampMinBonusBuilder { target, value }
}
pub fn clamp_max(target: StatId, value: f64) -> ClampMaxBonusBuilder {
ClampMaxBonusBuilder { target, value }
}
pub fn with_condition(mut self, condition: crate::condition::ConditionDef) -> Self {
self.condition = Some(condition);
self
}
}
impl AddBonusBuilder {
pub fn flat(self, value: f64) -> AddBonusBuilderWithValue {
AddBonusBuilderWithValue {
target: self.target,
value,
}
}
}
impl ScaleBonusBuilder {
pub fn factor(self, factor: f64) -> ScaleBonusBuilderWithFactor {
ScaleBonusBuilderWithFactor {
target: self.target,
source: self.source,
factor,
}
}
}
impl MulBonusBuilder {
pub fn percent(self, value: f64) -> MulBonusBuilderWithValue {
MulBonusBuilderWithValue {
target: self.target,
multiplier: 1.0 + value,
}
}
pub fn multiplier(self, multiplier: f64) -> MulBonusBuilderWithValue {
MulBonusBuilderWithValue {
target: self.target,
multiplier,
}
}
}
impl AddBonusBuilderWithValue {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
action: BonusAction::AddFlat { value: self.value },
phase,
condition: None,
}
}
}
impl ScaleBonusBuilderWithFactor {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
action: BonusAction::ScaleFrom {
source: self.source,
factor: self.factor,
},
phase,
condition: None,
}
}
}
impl MulBonusBuilderWithValue {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
action: BonusAction::Multiply {
multiplier: self.multiplier,
},
phase,
condition: None,
}
}
}
pub struct OverrideBonusBuilder {
target: StatId,
value: f64,
}
impl OverrideBonusBuilder {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
action: BonusAction::Override { value: self.value },
phase,
condition: None,
}
}
}
pub struct ClampMinBonusBuilder {
target: StatId,
value: f64,
}
impl ClampMinBonusBuilder {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
action: BonusAction::ClampMin { value: self.value },
phase,
condition: None,
}
}
}
pub struct ClampMaxBonusBuilder {
target: StatId,
value: f64,
}
impl ClampMaxBonusBuilder {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
action: BonusAction::ClampMax { value: self.value },
phase,
condition: None,
}
}
}
#[derive(Debug, Clone)]
pub struct CompiledBonus<N: StatNumeric> {
pub stat: StatId,
pub phase: TransformPhase,
pub stack_rule: StackRule,
pub condition: Option<crate::condition::ConditionDef>,
transform_data: TransformData,
_phantom: std::marker::PhantomData<N>,
}
#[derive(Debug, Clone)]
enum TransformData {
AdditiveFlat(f64),
ScaleFrom(StatId, f64),
Multiplicative(f64),
Override(f64),
ClampMin(f64),
ClampMax(f64),
}
pub fn compile_bonus<N: StatNumeric>(bonus: &Bonus) -> CompiledBonus<N> {
let (transform_data, stack_rule) = match &bonus.action {
BonusAction::AddFlat { value } => {
(TransformData::AdditiveFlat(*value), StackRule::Additive)
}
BonusAction::ScaleFrom { source, factor } => (
TransformData::ScaleFrom(source.clone(), *factor),
StackRule::Additive,
),
BonusAction::Multiply { multiplier } => (
TransformData::Multiplicative(*multiplier),
StackRule::Multiplicative,
),
BonusAction::Override { value } => (TransformData::Override(*value), StackRule::Override),
BonusAction::ClampMin { value } => (TransformData::ClampMin(*value), StackRule::MinMax),
BonusAction::ClampMax { value } => (TransformData::ClampMax(*value), StackRule::MinMax),
};
CompiledBonus {
stat: bonus.target.clone(),
phase: bonus.phase,
stack_rule,
condition: bonus.condition.clone(),
transform_data,
_phantom: std::marker::PhantomData,
}
}
impl<N: StatNumeric> CompiledBonus<N> {
fn to_transform(&self) -> Box<dyn StatTransform> {
let inner: Box<dyn StatTransform> = match &self.transform_data {
TransformData::AdditiveFlat(value) => Box::new(AdditiveTransform::new(*value)),
TransformData::ScaleFrom(source, factor) => {
Box::new(ScalingTransform::new(source.clone(), *factor))
}
TransformData::Multiplicative(multiplier) => {
Box::new(MultiplicativeTransform::new(*multiplier))
}
TransformData::Override(value) => Box::new(OverrideTransform::new(*value)),
TransformData::ClampMin(min_value) => {
Box::new(ClampTransform::with_min(StatValue::from_f64(*min_value)))
}
TransformData::ClampMax(max_value) => {
Box::new(ClampTransform::with_max(StatValue::from_f64(*max_value)))
}
};
if let Some(ref cond) = self.condition {
let cond = cond.clone();
let desc = format!("conditional -> {}", inner.description());
Box::new(crate::transform::ConditionalTransform::new(
move |ctx| cond.evaluate(ctx),
inner,
desc,
))
} else {
inner
}
}
}
#[inline]
pub fn apply_compiled_bonus<N: StatNumeric>(
resolver: &mut crate::resolver::StatResolver,
compiled: &CompiledBonus<N>,
) {
resolver.register_transform_with_rule(
compiled.stat.clone(),
compiled.phase,
compiled.stack_rule,
compiled.to_transform(),
);
}
pub fn apply_compiled_bonuses<N: StatNumeric>(
resolver: &mut crate::resolver::StatResolver,
compiled: &[CompiledBonus<N>],
) {
for bonus in compiled {
apply_compiled_bonus(resolver, bonus);
}
}
#[derive(Clone)]
struct OverrideTransform {
absolute_value: f64,
}
impl OverrideTransform {
fn new(absolute_value: f64) -> Self {
Self { absolute_value }
}
}
impl StatTransform for OverrideTransform {
fn depends_on(&self) -> Vec<StatId> {
Vec::new()
}
fn phase(&self) -> TransformPhase {
TransformPhase::Additive
}
fn apply(
&self,
_input: StatValue,
_dependencies: &FxHashMap<StatId, StatValue>,
_context: &StatContext,
) -> Result<StatValue, StatError> {
Ok(StatValue::from_f64(self.absolute_value))
}
fn description(&self) -> String {
format!("override({:.2})", self.absolute_value)
}
}