use std::fmt::{self, Display, Write};
use serde::{Serialize, Deserialize};
use crate::{util::Position, Item, Inventory};
#[cfg(doc)]
use super::placement::Placement;
#[derive(Serialize, Deserialize, Clone)]
pub struct SeedSpoiler {
pub spawns: Vec<String>,
pub groups: Vec<SpoilerGroup>,
}
#[derive(Default, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SpoilerGroup {
pub reachable: Vec<SpoilerWorldReachable>,
pub forced_items: Inventory,
pub placements: Vec<SpoilerPlacement>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct SpoilerWorldReachable {
pub locations: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SpoilerPlacement {
pub origin_world_index: usize,
pub target_world_index: usize,
pub node_identifier: String,
pub node_position: Option<Position>,
pub item: Item,
pub item_name: String,
}
impl SeedSpoiler {
pub fn to_json(&self) -> String {
serde_json::to_string(&self).unwrap()
}
}
impl Display for SeedSpoiler {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let world_count = self.spawns.len();
let multiworld = world_count > 1;
if multiworld {
for (index, spawn) in self.spawns.iter().enumerate() {
writeln!(f, "Spawn for World [{index}]: {spawn}")?;
}
} else {
let spawn = &self.spawns[0];
writeln!(f, "Spawn: {spawn}")?;
}
writeln!(f)?;
writeln!(f)?;
let mut longest_pickup = 0;
let mut longest_location = 0;
let spoiler_groups = self.groups.iter().map(|spoiler_group| {
let placements = spoiler_group.placements.iter().map(|placement| {
let mut pickup = String::new();
if multiworld {
write!(pickup, "[{}] ", placement.target_world_index)?;
}
write!(pickup, "{}", placement.item_name)?;
if pickup.len() > longest_pickup { longest_pickup = pickup.len(); }
let mut location = String::new();
if multiworld {
write!(location , "[{}] ", placement.origin_world_index)?;
}
write!(location, "{}", placement.node_identifier)?;
if location.len() > longest_location { longest_location = location.len(); }
Ok((pickup, location, &placement.node_position))
}).collect::<Result<Vec<_>, _>>()?;
Ok((&spoiler_group.reachable, &spoiler_group.forced_items, placements))
}).collect::<Result<Vec<_>, _>>()?;
for (index, (reachable, forced_items, placements)) in spoiler_groups.into_iter().enumerate() {
write!(f, "Step {index}")?;
if reachable.is_empty() {
writeln!(f, " (priority placements)")?;
} else {
writeln!(f)?;
for (world_index, world_reachable) in reachable.iter().enumerate() {
if multiworld {
write!(f, " [{world_index}]: ")?;
} else {
write!(f, " ")?;
}
if world_reachable.locations.is_empty() {
writeln!(f, "No new reachables")?;
} else {
let locations = world_reachable.locations.join(", ");
let count = world_reachable.locations.len();
write!(f, "{count} new reachable")?;
if count > 1 { write!(f, "s")?; }
writeln!(f, ": {locations}")?;
}
}
}
if !forced_items.items.is_empty() {
writeln!(f, " Force placed: {forced_items}")?;
}
writeln!(f)?;
let placement_count = placements.len();
if placement_count > 0 {
for (pickup, location, position) in placements {
write!(f, " {pickup:<longest_pickup$} ")?;
match position {
Some(position) => writeln!(f, "{location:<longest_location$} {position}"),
None => writeln!(f, "{location}"),
}?;
}
}
writeln!(f)?;
}
Ok(())
}
}