use crate::battle::luck::Luck;
use crate::battle::{Battle, BattleResult};
use crate::continent::Coord;
use crate::error::Result;
use crate::infrastructure::building::level::{BuildingLevel, BuildingLevelDiff};
use crate::infrastructure::building::{Building, BuildingId};
use crate::military::army::{Army, ArmyState};
use crate::military::maneuver::{Maneuver, ManeuverDirection, ManeuverHaul, ManeuverKind};
use crate::military::unit::stats::haul::Haul;
use crate::player::PlayerId;
use crate::report::battle::BattleReport;
use crate::report::support::SupportReport;
use crate::resources::Resources;
use crate::ruler::Ruler;
use crate::world::World;
use itertools::Itertools;
use nil_util::vec::VecExt;
use num_traits::ToPrimitive;
use tap::Pipe;
impl World {
pub(super) fn process_maneuvers(&mut self) -> Result<()> {
for mut maneuver in self.military.advance_maneuvers()? {
debug_assert!(maneuver.is_done());
match maneuver.direction() {
ManeuverDirection::Going => self.process_going_maneuver(maneuver)?,
ManeuverDirection::Returning => self.process_returning_maneuver(&mut maneuver)?,
}
}
Ok(())
}
fn process_going_maneuver(&mut self, maneuver: Maneuver) -> Result<()> {
match maneuver.kind() {
ManeuverKind::Attack => self.process_going_attack_maneuver(maneuver),
ManeuverKind::Support => self.process_going_support_maneuver(&maneuver),
}
}
fn process_going_attack_maneuver(&mut self, mut maneuver: Maneuver) -> Result<()> {
let army_id = maneuver.army();
let origin = maneuver.origin();
let destination = maneuver.destination();
let rulers = ManeuverRulers::new(self, &maneuver)?;
let battle_result = perform_battle(self, &maneuver)?;
*self
.military
.army_mut(army_id)?
.personnel_mut() = battle_result
.attacker_surviving_personnel()
.clone();
let mut defender_dead_armies = Vec::new();
let defender_surviving_ratio = battle_result.defender_surviving_personnel_ratio();
for army in self.military.idle_armies_mut_at(destination) {
let owner = army.owner();
if owner == &rulers.destination_ruler
|| rulers
.destination_army_owners
.contains(owner)
{
*army *= defender_surviving_ratio;
if army.is_empty() {
defender_dead_armies.push(army.id());
}
}
}
self
.military
.remove_armies(defender_dead_armies)?;
let haul = self.military.army(army_id)?.haul();
let mut hauled_resources = calculate_hauled_resources(self, destination, haul)?;
self.take_resources_of(rulers.destination_ruler.clone(), &mut hauled_resources)?;
if self.military.army(army_id)?.is_empty() {
self.military.remove_army(army_id)?;
} else {
maneuver.reverse()?;
*maneuver.hauled_resources_mut() = ManeuverHaul::builder()
.ruler(rulers.destination_ruler.clone())
.resources(hauled_resources.clone())
.build()
.pipe(Some);
self.military.insert_maneuver(maneuver);
}
update_wall_level(self, &battle_result, destination)?;
let players = rulers.players();
let report = BattleReport::builder()
.attacker(rulers.sender)
.defender(rulers.destination_ruler)
.origin(origin)
.destination(destination)
.result(battle_result)
.hauled_resources(hauled_resources)
.round(self.round.id())
.build();
self.emit_battle_report(&report)?;
self
.report_manager
.manage(report.into(), players);
Ok(())
}
fn process_going_support_maneuver(&mut self, maneuver: &Maneuver) -> Result<()> {
let army_id = maneuver.army();
let origin = maneuver.origin();
let destination = maneuver.destination();
let rulers = ManeuverRulers::new(self, maneuver)?;
let mut players = Vec::new();
players.try_push(rulers.sender.player().cloned());
players.try_push(rulers.destination_ruler.player().cloned());
let personnel = self.military.personnel(army_id).cloned()?;
let report = SupportReport::builder()
.sender(rulers.sender)
.receiver(rulers.destination_ruler)
.origin(origin)
.destination(destination)
.personnel(personnel)
.round(self.round.id())
.build();
self.emit_support_report(&report)?;
self
.report_manager
.manage(report.into(), players);
self
.military
.relocate_army(army_id, destination)?;
Ok(())
}
fn process_returning_maneuver(&mut self, maneuver: &mut Maneuver) -> Result<()> {
let army_id = maneuver.army();
if let ManeuverKind::Attack = maneuver.kind()
&& let Some(hauled) = maneuver.hauled_resources_mut().take()
&& !hauled.resources().is_empty()
{
let ruler = self.military.army(army_id)?.owner().clone();
*self.ruler_mut(&ruler)?.resources_mut() += Resources::from(hauled);
}
let army = self.military.army_mut(army_id)?;
*army.state_mut() = ArmyState::Idle;
Ok(())
}
}
struct ManeuverRulers {
sender: Ruler,
destination_ruler: Ruler,
destination_army_owners: Box<[Ruler]>,
}
impl ManeuverRulers {
fn new(world: &World, maneuver: &Maneuver) -> Result<Self> {
let sender = world
.military
.army(maneuver.army())?
.owner()
.clone();
let destination_ruler = world
.city(maneuver.destination())?
.owner()
.clone();
let mut destination_army_owners = Vec::new();
if let ManeuverKind::Attack = maneuver.kind() {
let owners = world
.military
.idle_armies_at(maneuver.destination())
.map(Army::owner)
.unique()
.cloned();
destination_army_owners.extend(owners);
}
destination_army_owners.retain(|it| it != &sender && it != &destination_ruler);
Ok(Self {
sender,
destination_ruler,
destination_army_owners: destination_army_owners.into_boxed_slice(),
})
}
fn players(&self) -> Vec<PlayerId> {
let mut players = Vec::new();
players.try_push(self.sender.player().cloned());
players.try_push(self.destination_ruler.player().cloned());
players
}
}
fn perform_battle(world: &World, maneuver: &Maneuver) -> Result<BattleResult> {
let attacker = world.military.squads(maneuver.army())?;
let defender = world
.military
.idle_squads_at(maneuver.destination());
let wall = world
.infrastructure(maneuver.destination())?
.wall()
.level();
let wall_stats = (wall > BuildingLevel::ZERO)
.then(|| world.stats.infrastructure.wall().get(wall))
.transpose()?;
Battle::builder()
.attacker(&attacker)
.defender(&defender)
.luck(Luck::random())
.maybe_wall(wall_stats)
.infrastructure_stats(&world.stats.infrastructure)
.build()
.result()
}
fn calculate_hauled_resources(world: &World, target: Coord, base: Haul) -> Result<Resources> {
if base == 0u32 {
return Ok(Resources::splat(0));
}
let resources = world.get_weighted_resources(target)?;
let silo_resources = resources.sum_silo();
let warehouse_resources = resources.sum_warehouse();
let mut hauled = Resources::new();
let mut silo_haul = base * Haul::SILO_RATIO;
let mut warehouse_haul = base * Haul::WAREHOUSE_RATIO;
if silo_haul > silo_resources {
silo_haul = Haul::new(silo_resources);
}
if warehouse_haul > warehouse_resources {
warehouse_haul = Haul::new(warehouse_resources);
}
macro_rules! set {
($total:expr, $res:ident, $haul:expr) => {
let total = f64::from($total);
if total.is_normal() {
let ratio = f64::from(resources.$res) / total;
let resource = (f64::from($haul) * ratio)
.round()
.to_u32()
.unwrap_or_default();
hauled.$res = resource.min(*resources.$res).into();
}
};
}
set!(silo_resources, food, silo_haul);
set!(warehouse_resources, iron, warehouse_haul);
set!(warehouse_resources, stone, warehouse_haul);
set!(warehouse_resources, wood, warehouse_haul);
Ok(hauled)
}
fn update_wall_level(world: &mut World, result: &BattleResult, coord: Coord) -> Result<()> {
let diff = result.downgraded_wall_level();
if diff < BuildingLevelDiff::ZERO {
let level = result.wall_level() + diff;
world.set_building_level(coord, BuildingId::Wall, level)?;
}
Ok(())
}