use crate::context::StatContext;
use crate::error::StatError;
use crate::numeric::{StatNumeric, StatValue};
use crate::stat_id::StatId;
use crate::transform::{
AdditiveTransform, ClampTransform, MultiplicativeTransform, StackRule, StatTransform,
TransformPhase,
};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BonusOp {
Add,
Multiply,
Override,
ClampMin,
ClampMax,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum BonusValue {
Flat(f64),
Percent(f64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bonus {
pub target: StatId,
pub operation: BonusOp,
pub value: BonusValue,
pub phase: TransformPhase,
}
pub struct AddBonusBuilder {
target: StatId,
}
pub struct MulBonusBuilder {
target: StatId,
}
pub struct AddBonusBuilderWithValue {
target: StatId,
value: BonusValue,
}
pub struct MulBonusBuilderWithValue {
target: StatId,
value: BonusValue,
}
impl Bonus {
pub fn add(target: StatId) -> AddBonusBuilder {
AddBonusBuilder { target }
}
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 }
}
}
impl AddBonusBuilder {
pub fn flat(self, value: f64) -> AddBonusBuilderWithValue {
AddBonusBuilderWithValue {
target: self.target,
value: BonusValue::Flat(value),
}
}
pub fn percent(self, value: f64) -> AddBonusBuilderWithValue {
AddBonusBuilderWithValue {
target: self.target,
value: BonusValue::Percent(value),
}
}
}
impl MulBonusBuilder {
pub fn percent(self, value: f64) -> MulBonusBuilderWithValue {
MulBonusBuilderWithValue {
target: self.target,
value: BonusValue::Percent(value),
}
}
}
impl AddBonusBuilderWithValue {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
operation: BonusOp::Add,
value: self.value,
phase,
}
}
}
impl MulBonusBuilderWithValue {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
operation: BonusOp::Multiply,
value: self.value,
phase,
}
}
}
pub struct OverrideBonusBuilder {
target: StatId,
value: f64,
}
impl OverrideBonusBuilder {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
operation: BonusOp::Override,
value: BonusValue::Flat(self.value),
phase,
}
}
}
pub struct ClampMinBonusBuilder {
target: StatId,
value: f64,
}
impl ClampMinBonusBuilder {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
operation: BonusOp::ClampMin,
value: BonusValue::Flat(self.value),
phase,
}
}
}
pub struct ClampMaxBonusBuilder {
target: StatId,
value: f64,
}
impl ClampMaxBonusBuilder {
pub fn in_phase(self, phase: TransformPhase) -> Bonus {
Bonus {
target: self.target,
operation: BonusOp::ClampMax,
value: BonusValue::Flat(self.value),
phase,
}
}
}
#[derive(Debug, Clone)]
pub struct CompiledBonus<N: StatNumeric> {
pub stat: StatId,
pub phase: TransformPhase,
pub stack_rule: StackRule,
transform_data: TransformData,
_phantom: std::marker::PhantomData<N>,
}
#[derive(Debug, Clone)]
enum TransformData {
AdditiveFlat(f64),
AdditivePercent(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.operation {
BonusOp::Add => match bonus.value {
BonusValue::Flat(value) => (TransformData::AdditiveFlat(value), StackRule::Additive),
BonusValue::Percent(percent) => (
TransformData::AdditivePercent(bonus.target.clone(), percent),
StackRule::Additive,
),
},
BonusOp::Multiply => {
let multiplier = match bonus.value {
BonusValue::Percent(percent) => 1.0 + percent,
BonusValue::Flat(v) => v,
};
(
TransformData::Multiplicative(multiplier),
StackRule::Multiplicative,
)
}
BonusOp::Override => {
let value = f64::from(bonus.value);
(TransformData::Override(value), StackRule::Override)
}
BonusOp::ClampMin => {
let min_value = f64::from(bonus.value);
(TransformData::ClampMin(min_value), StackRule::MinMax)
}
BonusOp::ClampMax => {
let max_value = f64::from(bonus.value);
(TransformData::ClampMax(max_value), StackRule::MinMax)
}
};
CompiledBonus {
stat: bonus.target.clone(),
phase: bonus.phase,
stack_rule,
transform_data,
_phantom: std::marker::PhantomData,
}
}
impl<N: StatNumeric> CompiledBonus<N> {
fn to_transform(&self) -> Box<dyn StatTransform> {
match &self.transform_data {
TransformData::AdditiveFlat(value) => Box::new(AdditiveTransform::new(*value)),
TransformData::AdditivePercent(dep, percent) => {
Box::new(PercentAdditiveTransform::new(dep.clone(), *percent))
}
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)))
}
}
}
}
#[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);
}
}
struct PercentAdditiveTransform {
dependency: StatId,
percent: f64,
}
impl PercentAdditiveTransform {
fn new(dependency: StatId, percent: f64) -> Self {
Self {
dependency,
percent,
}
}
}
impl StatTransform for PercentAdditiveTransform {
fn depends_on(&self) -> Vec<StatId> {
vec![self.dependency.clone()]
}
fn phase(&self) -> TransformPhase {
TransformPhase::Additive
}
fn apply(
&self,
input: StatValue,
dependencies: &FxHashMap<StatId, StatValue>,
_context: &StatContext,
) -> Result<StatValue, StatError> {
let dep_value = dependencies
.get(&self.dependency)
.ok_or_else(|| StatError::MissingDependency(self.dependency.clone()))?;
let bonus = *dep_value * StatValue::from_f64(self.percent);
Ok(input + bonus)
}
fn description(&self) -> String {
format!("+{:.1}% (additive)", self.percent * 100.0)
}
}
impl Clone for PercentAdditiveTransform {
fn clone(&self) -> Self {
Self {
dependency: self.dependency.clone(),
percent: self.percent,
}
}
}
#[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)
}
}
impl From<BonusValue> for f64 {
fn from(value: BonusValue) -> Self {
match value {
BonusValue::Flat(v) => v,
BonusValue::Percent(v) => v,
}
}
}