use esf_data::Info;
use super::{Context, GroupLimit, Item, Rule, SlotKind, Violation};
use crate::fit::State;
const SHIP_TYPES: [&str; 13] = [
"fitsToShipType",
"canFitShipType1",
"canFitShipType2",
"canFitShipType3",
"canFitShipType4",
"canFitShipType5",
"canFitShipType6",
"canFitShipType7",
"canFitShipType8",
"canFitShipType9",
"canFitShipType10",
"canFitShipType11",
"canFitShipType12",
];
const SHIP_GROUPS: [&str; 20] = [
"canFitShipGroup01",
"canFitShipGroup02",
"canFitShipGroup03",
"canFitShipGroup04",
"canFitShipGroup05",
"canFitShipGroup06",
"canFitShipGroup07",
"canFitShipGroup08",
"canFitShipGroup09",
"canFitShipGroup10",
"canFitShipGroup11",
"canFitShipGroup12",
"canFitShipGroup13",
"canFitShipGroup14",
"canFitShipGroup15",
"canFitShipGroup16",
"canFitShipGroup17",
"canFitShipGroup18",
"canFitShipGroup19",
"canFitShipGroup20",
];
const GROUP_LIMITS: [(GroupLimit, &str); 3] = [
(GroupLimit::Fitted, "maxGroupFitted"),
(GroupLimit::Online, "maxGroupOnline"),
(GroupLimit::Active, "maxGroupActive"),
];
const CAPITAL_VOLUME: f64 = 3500.0;
const STRUCTURE_CATEGORY_ID: i32 = 65;
const STRUCTURE_MODULE_CATEGORY_ID: i32 = 66;
const STANDUP_FIGHTERS: [&str; 3] = [
"fighterSquadronIsStandupLight",
"fighterSquadronIsStandupSupport",
"fighterSquadronIsStandupHeavy",
];
pub(super) fn validate<I: Info>(context: &Context<'_, I>, found: &mut Vec<Violation>) {
rig_size(context, found);
ship_restricted(context, found);
capital_item(context, found);
standup(context, found);
group_limits(context, found);
type_limit(context, found);
}
fn rig_size<I: Info>(context: &Context<'_, I>, found: &mut Vec<Violation>) {
let Some(attribute_id) = context.attribute_id("rigSize") else {
return;
};
let Some(ship) = context.value(context.ship(), attribute_id) else {
return;
};
for item in &context.items {
let Some(size) = context.value(item.result, attribute_id) else {
continue;
};
if item.rack() == Some(SlotKind::Rig) && size != ship {
found.push(item.violation(Rule::RigSize {
ship: ship as u8,
item: size as u8,
}));
}
}
}
fn ship_restricted<I: Info>(context: &Context<'_, I>, found: &mut Vec<Violation>) {
let type_limits: Vec<i32> = SHIP_TYPES
.iter()
.filter_map(|name| context.attribute_id(name))
.collect();
let group_limits: Vec<i32> = SHIP_GROUPS
.iter()
.filter_map(|name| context.attribute_id(name))
.collect();
let ship_type_id = context.fit.ship.type_id;
let ship_group_id = context.ship_group_id();
for item in &context.items {
if !item.is_fitted() {
continue;
}
let types = listed(context, item, &type_limits);
let groups = listed(context, item, &group_limits);
if types.is_empty() && groups.is_empty() {
continue;
}
if !types.contains(&ship_type_id) && !groups.contains(&ship_group_id) {
found.push(item.violation(Rule::ShipRestricted));
}
}
}
fn listed<I: Info>(context: &Context<'_, I>, item: &Item<'_>, attribute_ids: &[i32]) -> Vec<i32> {
attribute_ids
.iter()
.filter_map(|attribute_id| context.value(item.result, *attribute_id))
.map(|value| value as i32)
.collect()
}
fn capital_item<I: Info>(context: &Context<'_, I>, found: &mut Vec<Violation>) {
let (Some(volume), Some(capital)) = (
context.attribute_id("volume"),
context.attribute_id("isCapitalSize"),
) else {
return;
};
if context.value(context.ship(), capital).unwrap_or(0.0) != 0.0 {
return;
}
if context.ship_category_id() == STRUCTURE_CATEGORY_ID {
return;
}
for item in &context.items {
if !item.is_fitted() || item.rack() == Some(SlotKind::Rig) {
continue;
}
if context.amount(item.result, volume) > CAPITAL_VOLUME {
found.push(item.violation(Rule::CapitalItem));
}
}
}
fn standup<I: Info>(context: &Context<'_, I>, found: &mut Vec<Violation>) {
let markers: Vec<i32> = STANDUP_FIGHTERS
.iter()
.filter_map(|name| context.attribute_id(name))
.collect();
let structure = context.ship_category_id() == STRUCTURE_CATEGORY_ID;
for item in &context.items {
if !item.is_on_hull() {
continue;
}
let standup = item.category_id == STRUCTURE_MODULE_CATEGORY_ID
|| markers
.iter()
.any(|attribute_id| context.amount(item.result, *attribute_id) != 0.0);
match (structure, standup) {
(false, true) => found.push(item.violation(Rule::StructureItem)),
(true, false) => found.push(item.violation(Rule::ShipItem)),
_ => {}
}
}
}
fn group_limits<I: Info>(context: &Context<'_, I>, found: &mut Vec<Violation>) {
for (limit, attribute_name) in GROUP_LIMITS {
let Some(attribute_id) = context.attribute_id(attribute_name) else {
continue;
};
for item in &context.items {
let Some(allowed) = context.value(item.result, attribute_id) else {
continue;
};
if !item.is_fitted() {
continue;
}
let used = context
.items
.iter()
.filter(|other| {
other.is_fitted() && other.group_id == item.group_id && counts(other, limit)
})
.count() as u32;
let allowed = allowed as u32;
if used > allowed {
found.push(item.violation(Rule::MaxGroup {
group_id: item.group_id,
limit,
used,
allowed,
}));
}
}
}
}
fn type_limit<I: Info>(context: &Context<'_, I>, found: &mut Vec<Violation>) {
let Some(attribute_id) = context.attribute_id("maxTypeFitted") else {
return;
};
for item in &context.items {
let Some(allowed) = context.value(item.result, attribute_id) else {
continue;
};
if !item.is_fitted() {
continue;
}
let used = context
.items
.iter()
.filter(|other| other.is_fitted() && other.fit.type_id == item.fit.type_id)
.count() as u32;
let allowed = allowed as u32;
if used > allowed {
found.push(item.violation(Rule::MaxType {
type_id: item.fit.type_id,
used,
allowed,
}));
}
}
}
fn counts(item: &Item<'_>, limit: GroupLimit) -> bool {
match limit {
GroupLimit::Fitted => true,
GroupLimit::Online => item.result.state >= State::Online,
GroupLimit::Active => item.result.state >= State::Active,
}
}