use std::cell::{Cell, RefCell};
use std::collections::{BTreeMap, BTreeSet};
use serde::Serialize;
use strum_macros::EnumIter;
#[cfg(feature = "typescript")]
use tsify::Tsify;
use super::output::Source;
use crate::fit::{FitItem, Slot, State};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum EffectCategory {
Passive,
Online,
Active,
Overload,
Target,
Area,
Dungeon,
System,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ItemState {
Passive,
Online,
Active,
Overload,
AlwaysOn,
}
#[cfg_attr(feature = "typescript", derive(Tsify))]
#[derive(Serialize, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
#[serde(rename_all = "snake_case")]
pub enum EffectOperator {
PreAssign,
PreMul,
PreDiv,
ModAdd,
ModSub,
PostMul,
PostDiv,
PostPercent,
PostAssign,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Object {
Ship,
Mode,
Item(usize),
Charge(usize),
Skill(usize),
Char,
Projected(usize),
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Origin {
Effect {
effect_id: i32,
source: Object,
source_category: EffectCategory,
attribute_id: i32,
},
Buff { buff_id: i32, value: f64 },
}
#[derive(Debug)]
pub struct Effect {
pub origin: Origin,
pub operator: EffectOperator,
pub penalty: bool,
pub quantity: u32,
pub resistance: Option<i32>,
}
impl Origin {
pub fn effect_id(self) -> Option<i32> {
match self {
Origin::Effect { effect_id, .. } => Some(effect_id),
Origin::Buff { .. } => None,
}
}
pub fn source_attribute_id(self) -> Option<i32> {
match self {
Origin::Effect { attribute_id, .. } => Some(attribute_id),
Origin::Buff { .. } => None,
}
}
}
#[derive(Debug)]
pub struct Attribute {
pub base_value: f64,
pub value: Cell<Option<f64>>,
pub effects: Vec<Effect>,
pub sources: RefCell<Vec<Source>>,
}
#[derive(Debug)]
pub struct Item {
pub type_id: i32,
pub group_id: i32,
pub category_id: i32,
pub slot: Option<Slot>,
pub quantity: u32,
pub charge: Option<Box<Item>>,
pub state: ItemState,
pub max_state: ItemState,
pub attributes: BTreeMap<i32, Attribute>,
pub effects: Vec<i32>,
pub fighter_abilities: Option<BTreeSet<i32>>,
pub booster_side_effects: BTreeSet<i32>,
pub mutation_base: Option<i32>,
}
impl Attribute {
pub fn new(value: f64) -> Attribute {
Attribute {
base_value: value,
value: Cell::new(None),
effects: Vec::new(),
sources: RefCell::new(Vec::new()),
}
}
}
impl ItemState {
pub fn is_active(self) -> bool {
self >= ItemState::Active
}
}
impl EffectCategory {
pub fn required_state(self) -> Option<ItemState> {
match self {
EffectCategory::Passive => Some(ItemState::Passive),
EffectCategory::Online => Some(ItemState::Online),
EffectCategory::Active => Some(ItemState::Active),
EffectCategory::Overload => Some(ItemState::Overload),
EffectCategory::Target
| EffectCategory::Area
| EffectCategory::Dungeon
| EffectCategory::System => None,
}
}
pub fn runs_at(self, state: ItemState) -> bool {
match self.required_state() {
Some(required) => state >= required,
None => state.is_active(),
}
}
}
impl From<State> for ItemState {
fn from(state: State) -> ItemState {
match state {
State::Offline => ItemState::Passive,
State::Online => ItemState::Online,
State::Active => ItemState::Active,
State::Overload => ItemState::Overload,
}
}
}
impl From<ItemState> for State {
fn from(state: ItemState) -> State {
match state {
ItemState::Passive => State::Offline,
ItemState::Online => State::Online,
ItemState::Active => State::Active,
ItemState::Overload => State::Overload,
ItemState::AlwaysOn => unreachable!("an always-on item has no fit state"),
}
}
}
impl Item {
pub fn is_module(&self) -> bool {
matches!(
self.slot,
Some(
Slot::High(_) | Slot::Medium(_) | Slot::Low(_) | Slot::Rig(_) | Slot::Subsystem(_)
)
)
}
pub fn is_in_ship(&self) -> bool {
self.is_module() || matches!(self.slot, Some(Slot::Service(_)))
}
pub fn is_fighter(&self) -> bool {
matches!(self.slot, Some(Slot::FighterTube(_) | Slot::FighterBay))
}
pub fn is_on_char(&self) -> bool {
matches!(self.slot, Some(Slot::Implant(_) | Slot::Booster(_)))
}
pub fn is_calculated(&self) -> bool {
self.is_in_ship()
|| self.is_fighter()
|| self.is_on_char()
|| self.slot == Some(Slot::DroneBay)
}
pub fn new_charge(type_id: i32) -> Item {
Item {
type_id,
group_id: 0,
category_id: 0,
slot: None,
quantity: 1,
charge: None,
state: ItemState::Active,
max_state: ItemState::Active,
attributes: BTreeMap::new(),
effects: Vec::new(),
fighter_abilities: None,
booster_side_effects: BTreeSet::new(),
mutation_base: None,
}
}
pub fn new_fit(fit_item: &FitItem) -> Item {
let mut item = Item {
type_id: fit_item.type_id,
group_id: 0,
category_id: 0,
slot: Some(fit_item.slot),
quantity: fit_item.quantity,
charge: fit_item
.charge
.as_ref()
.map(|charge| Box::new(Item::new_charge(charge.type_id))),
state: fit_item.state.into(),
max_state: ItemState::Passive,
attributes: BTreeMap::new(),
effects: Vec::new(),
fighter_abilities: fit_item.fighter_abilities.clone(),
booster_side_effects: fit_item.booster_side_effects.clone(),
mutation_base: fit_item.mutation.as_ref().map(|mutation| mutation.base),
};
match item.slot {
Some(Slot::DroneBay | Slot::FighterTube(_)) => {
if item.state != ItemState::Passive {
item.state = ItemState::Active;
}
item.max_state = ItemState::Active;
}
Some(Slot::FighterBay) => item.state = ItemState::Passive,
_ if !item.is_calculated() => item.state = ItemState::Passive,
_ => {}
}
item
}
pub fn new_projected(type_id: i32) -> Item {
Item {
state: ItemState::AlwaysOn,
max_state: ItemState::AlwaysOn,
..Item::new_fake(type_id)
}
}
pub fn new_fake(type_id: i32) -> Item {
Item {
type_id,
group_id: 0,
category_id: 0,
slot: None,
quantity: 1,
charge: None,
state: ItemState::Active,
max_state: ItemState::Active,
attributes: BTreeMap::new(),
effects: Vec::new(),
fighter_abilities: None,
booster_side_effects: BTreeSet::new(),
mutation_base: None,
}
}
}
#[cfg(test)]
mod tests {
use super::EffectOperator;
use strum::IntoEnumIterator;
#[test]
fn effect_operator_iterates_in_application_order() {
assert_eq!(
EffectOperator::iter().collect::<Vec<_>>(),
[
EffectOperator::PreAssign,
EffectOperator::PreMul,
EffectOperator::PreDiv,
EffectOperator::ModAdd,
EffectOperator::ModSub,
EffectOperator::PostMul,
EffectOperator::PostDiv,
EffectOperator::PostPercent,
EffectOperator::PostAssign,
]
);
}
}