Skip to main content

esf_dogma_engine/calculate/
output.rs

1use std::collections::BTreeMap;
2
3use serde::Serialize;
4
5#[cfg(feature = "typescript")]
6use tsify::Tsify;
7
8use esf_data::Info;
9
10use super::Objects;
11use super::item::{EffectOperator, Item, Object};
12use super::outgoing::outgoing;
13use crate::fit::State;
14use crate::projection::{ProjectedBuff, Projection};
15use crate::validate::Violation;
16
17/// The result of [`calculate()`](crate::calculate).
18#[cfg_attr(feature = "typescript", derive(Tsify))]
19#[derive(Serialize, Debug)]
20pub struct Calculation {
21    /// The ship.
22    pub ship: ItemResult,
23    /// The active mode, if the ship has one.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub mode: Option<ItemResult>,
26    /// Index-parallel to `Fit::items`: same length, same order.
27    pub items: Vec<ItemResult>,
28    /// The character.
29    pub character: ItemResult,
30    /// The buffs that landed, ordered by id: those of `Fit::incoming`, plus
31    /// the ones the fit's own bursts hand out. What is missing lost to another
32    /// source of the same buff, or the SDE has no such buff.
33    #[serde(skip_serializing_if = "Vec::is_empty")]
34    #[cfg_attr(feature = "typescript", tsify(optional))]
35    pub buffs: Vec<ProjectedBuff>,
36    /// All outgoing projections (effects and buffs).
37    #[serde(skip_serializing_if = "Projection::is_empty")]
38    #[cfg_attr(feature = "typescript", tsify(optional))]
39    pub outgoing: Projection,
40    /// The fitting rules the fit breaks; only with `Options::validate`.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    #[cfg_attr(feature = "typescript", tsify(optional))]
43    pub violations: Option<Vec<Violation>>,
44}
45
46/// The calculated attributes of the ship, its mode, the character, or one item.
47#[cfg_attr(feature = "typescript", derive(Tsify))]
48#[derive(Serialize, Debug)]
49pub struct ItemResult {
50    /// Every attribute, by attribute id.
51    pub attributes: BTreeMap<i32, AttributeValue>,
52    /// The state actually reached, which may be below what was requested.
53    pub state: State,
54    /// The highest state the item can reach.
55    pub max_state: State,
56    /// The charge loaded in the module, if any.
57    pub charge: Option<Box<ItemResult>>,
58}
59
60/// One attribute, before and after the effects on it.
61#[cfg_attr(feature = "typescript", derive(Tsify))]
62#[derive(Serialize, Debug)]
63pub struct AttributeValue {
64    /// The value from the SDE.
65    pub base: f64,
66    /// The value after every effect is applied.
67    pub value: f64,
68    /// In the order pass 3 applied them; empty unless `Options::sources` is set.
69    #[serde(skip_serializing_if = "Vec::is_empty")]
70    #[cfg_attr(feature = "typescript", tsify(optional))]
71    pub sources: Vec<Source>,
72}
73
74/// One modifier on an attribute, and where it came from.
75#[cfg_attr(feature = "typescript", derive(Tsify))]
76#[derive(Serialize, Debug, Clone)]
77pub struct Source {
78    /// The object the effect belongs to.
79    pub from: SourceRef,
80    /// The effect that holds the modifier; `None` for a buff, which has none.
81    pub effect_id: Option<i32>,
82    /// The attribute on the source that holds `value`; `None` for a buff,
83    /// which carries its own strength.
84    pub source_attribute_id: Option<i32>,
85    /// How `value` changes the attribute.
86    pub operator: EffectOperator,
87    /// The source attribute's value, as pass 3 used it.
88    pub value: f64,
89    /// A penalised stack is split into one entry per item, as each gets its own penalty.
90    pub quantity: u32,
91    /// The stacking penalty factor applied; `None` when not penalised.
92    pub penalty: Option<f64>,
93    /// False when the source's state is too low for the effect.
94    pub applied: bool,
95}
96
97/// `Item` and `Charge` index into `Fit::items`, `Projected` into
98/// `Fit::incoming.effects`. A skill and a buff are not in the result, so they
99/// carry their own id.
100#[cfg_attr(feature = "typescript", derive(Tsify))]
101#[derive(Serialize, Debug, Clone, Copy, PartialEq)]
102#[serde(tag = "type", rename_all = "snake_case")]
103pub enum SourceRef {
104    /// The ship.
105    Ship,
106    /// The active mode of the ship.
107    Mode,
108    /// The character.
109    Character,
110    /// An item of the fit.
111    Item {
112        /// The position in `Fit::items`.
113        index: usize,
114    },
115    /// The charge in an item of the fit.
116    Charge {
117        /// The position in `Fit::items` of the item holding the charge.
118        index: usize,
119    },
120    /// A skill of the character.
121    Skill {
122        /// The type id of the skill.
123        type_id: i32,
124    },
125    /// An effect aimed at the fit.
126    Projected {
127        /// The position in `Fit::incoming.effects`.
128        index: usize,
129    },
130    /// A buff handed to the fit.
131    Buff {
132        /// The id of the buff.
133        id: i32,
134    },
135}
136
137impl SourceRef {
138    pub(super) fn new(object: Object, type_id: i32) -> SourceRef {
139        match object {
140            Object::Ship => SourceRef::Ship,
141            Object::Mode => SourceRef::Mode,
142            Object::Char => SourceRef::Character,
143            Object::Item(index) => SourceRef::Item { index },
144            Object::Charge(index) => SourceRef::Charge { index },
145            Object::Skill(_) => SourceRef::Skill { type_id },
146            Object::Projected(index) => SourceRef::Projected { index },
147        }
148    }
149}
150
151impl ItemResult {
152    fn new(item: &Item) -> ItemResult {
153        ItemResult {
154            attributes: item
155                .attributes
156                .iter()
157                .map(|(attribute_id, attribute)| {
158                    let value = AttributeValue {
159                        base: attribute.base_value,
160                        value: attribute.value.get().unwrap_or(attribute.base_value),
161                        sources: attribute.sources.borrow().clone(),
162                    };
163                    (*attribute_id, value)
164                })
165                .collect(),
166            state: item.state.into(),
167            max_state: item.max_state.into(),
168            charge: item
169                .charge
170                .as_deref()
171                .map(|charge| Box::new(ItemResult::new(charge))),
172        }
173    }
174}
175
176impl Calculation {
177    pub(super) fn new(info: &impl Info, objects: &Objects) -> Calculation {
178        Calculation {
179            ship: ItemResult::new(&objects.ship),
180            mode: objects.mode.as_ref().map(ItemResult::new),
181            items: objects.items.iter().map(ItemResult::new).collect(),
182            character: ItemResult::new(&objects.char),
183            buffs: objects.buffs.clone(),
184            outgoing: outgoing(info, objects),
185            violations: None,
186        }
187    }
188}