use std::collections::BTreeSet;
use crate::envelope::Campaign;
use crate::healthbar::HealthBar;
use crate::stages::{
Actor, EncounterTier, Objective, OnKill, QuestEffect, Verb, Wave, for_each_campaign_effect,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FightKind {
Wave,
Actor,
}
impl FightKind {
pub fn word(self) -> &'static str {
match self {
FightKind::Wave => "wave",
FightKind::Actor => "actor",
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum Fight<'a> {
Wave(&'a Wave),
Actor(&'a Actor),
}
impl<'a> Fight<'a> {
pub fn kind(&self) -> FightKind {
match self {
Fight::Wave(_) => FightKind::Wave,
Fight::Actor(_) => FightKind::Actor,
}
}
pub fn word(&self) -> &'static str {
self.kind().word()
}
pub fn tier(&self) -> Option<EncounterTier> {
match self {
Fight::Wave(w) => w.tier,
Fight::Actor(a) => a.tier,
}
}
pub fn health_bar(&self) -> Option<&'a HealthBar> {
match self {
Fight::Wave(w) => w.health_bar.as_ref(),
Fight::Actor(a) => a.health_bar.as_ref(),
}
}
pub fn own_name(&self) -> Option<&'a str> {
match self {
Fight::Wave(w) => match w.mobs.as_slice() {
[only] => only.name.as_deref(),
_ => None,
},
Fight::Actor(a) => a.name.as_deref(),
}
}
pub fn entries(&self) -> usize {
match self {
Fight::Wave(w) => w.mobs.len(),
Fight::Actor(_) => 1,
}
}
pub fn vulnerable(&self) -> bool {
match self {
Fight::Wave(_) => true,
Fight::Actor(a) => a.vulnerable,
}
}
pub fn id(&self) -> &'a str {
match self {
Fight::Wave(w) => w.id.as_str(),
Fight::Actor(a) => a.id.as_str(),
}
}
pub fn on_kill(&self) -> Option<&'a OnKill> {
match self {
Fight::Wave(w) => w.on_kill.as_ref(),
Fight::Actor(a) => a.on_kill.as_ref(),
}
}
}
pub fn fights(c: &Campaign) -> Vec<(String, Fight<'_>)> {
let content = &c.quests.content;
let mut out = Vec::new();
for (i, w) in content.waves.iter().enumerate() {
out.push((format!("/content/waves/{i}"), Fight::Wave(w)));
}
for (i, a) in content.actors.iter().enumerate() {
out.push((format!("/content/actors/{i}"), Fight::Actor(a)));
}
out
}
pub fn unleashed_actors(c: &Campaign) -> BTreeSet<&str> {
let mut out = BTreeSet::new();
for_each_campaign_effect(c, &mut |_, _, eff| {
if let Verb::UnleashActor { actor, .. } = &eff.verb {
out.insert(actor.as_str());
}
});
out
}
pub fn quest_area<'a>(c: &'a Campaign, quest_id: &str) -> Option<&'a str> {
c.quest_plan
.content
.quests
.iter()
.find(|q| q.id.as_str() == quest_id)
.map(|q| q.area.as_str())
}
fn fires_wave<'a>(effs: impl IntoIterator<Item = &'a QuestEffect>, wave_id: &str) -> bool {
let mut found = false;
for e in effs {
e.visit_deep(&mut |x| {
if matches!(x.spawn_wave(), Some(w) if w.as_str() == wave_id) {
found = true;
}
});
}
found
}
pub fn wave_area<'a>(campaign: &'a Campaign, wave_id: &str) -> Option<&'a str> {
wave_area_seen(campaign, wave_id, &mut BTreeSet::new())
}
fn wave_area_seen<'a>(
campaign: &'a Campaign,
wave_id: &str,
seen: &mut BTreeSet<String>,
) -> Option<&'a str> {
if !seen.insert(wave_id.to_string()) {
return None;
}
let content = &campaign.quests.content;
for q in &content.quests {
if fires_wave(
q.on_objective_complete
.values()
.flatten()
.chain(&q.on_complete),
wave_id,
) {
return quest_area(campaign, q.id.as_str());
}
}
let (single_area, sole_area): (bool, Option<&str>) =
match crate::placement::Placement::of(campaign) {
crate::placement::Placement::SitePlan => (true, Some(crate::siteplan::SITE_AREA)),
crate::placement::Placement::Prefabs => (
campaign.world.content.areas.len() == 1,
campaign.world.content.areas.first().map(|a| a.id.as_str()),
),
crate::placement::Placement::NoMap => (false, None),
};
let sole_area = || sole_area;
if single_area
&& (content
.triggers
.iter()
.any(|t| fires_wave(&t.effects, wave_id))
|| content
.traps
.iter()
.any(|t| fires_wave(&t.payload, wave_id)))
{
return sole_area();
}
for w in &content.waves {
if let Some(ok) = &w.on_kill
&& fires_wave(&ok.effects, wave_id)
&& let Some(area) = wave_area_seen(campaign, w.id.as_str(), seen)
{
return Some(area);
}
}
if single_area
&& content.actors.iter().any(|a| {
a.on_kill
.as_ref()
.is_some_and(|ok| fires_wave(&ok.effects, wave_id))
})
{
return sole_area();
}
for q in &content.quests {
if q.objectives
.iter()
.any(|o| matches!(o, Objective::Kill { wave, .. } if wave.as_str() == wave_id))
{
return quest_area(campaign, q.id.as_str());
}
}
None
}