use crate::block::payload::read_payload;
use crate::content::content_enum;
use crate::data::command::UnitCommand;
use crate::data::dynamic::DynData;
use crate::data::entity_mapping::UnitClass;
use crate::data::{DataRead, ReadError};
use crate::item::Type as Item;
use crate::modifier::Type as Status;
use crate::team::Team;
use crate::utils::ImageHolder;
use crate::Serializable;
macro_rules! units {
($($unit:literal,)+ $(,)?) => { paste::paste! {
content_enum! (pub enum Type / Unit for u16 | TryFromU16Error {
$($unit,)+
});
impl Type {
fn draw(self, s: crate::data::renderer::Scale) -> ImageHolder<4> {
match self {
$(Type::[<$unit:camel>] => units!(@help $unit + s),)+
}
}
}
} };
(@help "block" + $s:expr) => { crate::data::renderer::load!("empty4", $s) };
(@help $v:literal + $s:expr) => { crate::data::renderer::load!($v, $s) };
}
units! {
"dagger",
"mace",
"fortress",
"scepter",
"reign",
"nova",
"pulsar",
"quasar",
"vela",
"corvus",
"crawler",
"atrax",
"spiroct",
"arkyid",
"toxopid",
"flare",
"horizon",
"zenith",
"antumbra",
"eclipse",
"mono",
"poly",
"mega",
"quad",
"oct",
"risso",
"minke",
"bryde",
"sei",
"omura",
"retusa",
"oxynoe",
"cyerce",
"aegires",
"navanax",
"alpha",
"beta",
"gamma",
"stell",
"locus",
"precept",
"vanquish",
"conquer",
"merui",
"cleroi",
"anthicus",
"anthicus-missile",
"tecta",
"collaris",
"elude",
"avert",
"obviate",
"quell",
"quell-missile",
"disrupt",
"disrupt-missile",
"renale",
"latum",
"evoke",
"incite",
"emanate",
"block",
"manifold",
"assembly-drone",
"scathe-missile",
}
#[derive(Default, Debug)]
pub struct UnitState {
pub ammo: f32,
pub elevation: f32,
pub flag: i64,
pub health: f32,
pub is_shooting: bool,
pub rotation: f32,
pub shield: f32,
pub stack: (Option<Item>, u32),
pub status: [Status; 3],
pub team: Team,
pub velocity: (f32, f32),
pub position: (f32, f32),
pub controller: Controller,
}
#[derive(Default, Debug)]
pub enum Controller {
Player(i32),
Logic(i32),
Command {
target: Option<i32>,
pos: Option<(f32, f32)>,
command: Option<UnitCommand>,
},
#[default]
Assembler,
}
impl Controller {
fn read(buff: &mut DataRead) -> Result<Controller, ReadError> {
Ok(match buff.read_u8()? {
0 => Controller::Player(buff.read_i32()?),
3 => Controller::Logic(buff.read_i32()?),
6 => {
let has_attack = buff.read_bool()?;
let pos = if buff.read_bool()? {
Some((buff.read_f32()?, buff.read_f32()?))
} else {
None
};
let target = if has_attack {
buff.skip(1)?;
Some(buff.read_i32()?)
} else {
None
};
let n = buff.read_i8()?;
let command = if let Ok(n) = u8::try_from(n)
&& let Ok(u) = UnitCommand::try_from(n)
{
Some(u)
} else {
None
};
Controller::Command {
target,
pos,
command,
}
}
_ => Controller::Assembler,
})
}
}
#[derive(Debug)]
pub struct Unit {
pub state: UnitState,
pub ty: Type,
}
impl UnitClass {
pub fn read(self, buff: &mut DataRead) -> Result<Unit, ReadError> {
buff.skip(2)?;
let mut state = UnitState::default();
read_abilities(buff)?;
state.ammo = buff.read_f32()?;
match self {
Self::Block
| Self::Legs
| Self::Elevated
| Self::Crawl
| Self::Boat
| Self::Tank
| Self::Air => {
state.controller = Controller::read(buff)?;
state.elevation = buff.read_f32()?;
state.flag = buff.read_i64()?;
state.health = buff.read_f32()?;
state.is_shooting = buff.read_bool()?;
read_tile(buff)?;
read_mounts(buff)?;
}
Self::Mech => {
buff.skip(4)?; state.controller = Controller::read(buff)?;
state.elevation = buff.read_f32()?;
state.flag = buff.read_i64()?;
state.health = buff.read_f32()?;
state.is_shooting = buff.read_bool()?;
read_tile(buff)?;
read_mounts(buff)?;
}
Self::Payload => {
state.controller = Controller::read(buff)?;
state.elevation = buff.read_f32()?;
state.flag = buff.read_i64()?;
state.health = buff.read_f32()?;
state.is_shooting = buff.read_bool()?;
read_tile(buff)?;
read_mounts(buff)?;
for _ in 0..buff.read_i32()? {
let _ = read_payload(buff);
}
}
Self::Bomb => {
state.controller = Controller::read(buff)?;
state.elevation = buff.read_f32()?;
state.flag = buff.read_i64()?;
state.health = buff.read_f32()?;
state.is_shooting = buff.read_bool()?;
buff.skip(4)?; read_tile(buff)?;
read_mounts(buff)?;
}
Self::Tethered => {
buff.skip(4)?;
state.controller = Controller::read(buff)?;
state.elevation = buff.read_f32()?;
state.flag = buff.read_i64()?;
state.health = buff.read_f32()?;
state.is_shooting = buff.read_bool()?;
read_tile(buff)?;
read_mounts(buff)?;
for _ in 0..buff.read_i32()? {
read_payload(buff)?;
}
}
}
read_plans(buff)?;
state.rotation = buff.read_f32()?;
state.shield = buff.read_f32()?;
buff.skip(1)?; state.stack = read_stack(buff)?;
state.status = read_status(buff)?;
state.team = Team::of(buff.read_u8()?);
if self == Self::Bomb {
buff.skip(4)?; }
let ty = Type::try_from(buff.read_u16()?).unwrap();
buff.skip(1)?; state.velocity = (buff.read_f32()?, buff.read_f32()?);
state.position = (
(buff.read_f32()? / 8.0).floor(),
(buff.read_f32()? / 8.0).floor(),
);
Ok(Unit { state, ty })
}
}
fn read_abilities(buff: &mut DataRead) -> Result<(), ReadError> {
let n = buff.read_u8()? as usize;
buff.skip(n * 4)
}
fn read_tile(buff: &mut DataRead) -> Result<(), ReadError> {
buff.skip(4)
}
fn read_mounts(buff: &mut DataRead) -> Result<(), ReadError> {
let n = buff.read_u8()? as usize;
buff.skip(n * 9)
}
fn read_plans(buff: &mut DataRead) -> Result<(), ReadError> {
let used = buff.read_i32()?;
if used == -1 {
return Ok(());
}
for _ in 0..used {
read_plan(buff)?;
}
Ok(())
}
fn read_plan(buff: &mut DataRead) -> Result<(), ReadError> {
let ty = buff.read_u8()?;
buff.skip(4)?;
if ty != 1 {
buff.skip(4)?;
let _ = DynData::deserialize(buff).unwrap();
}
Ok(())
}
fn read_stack(buff: &mut DataRead) -> Result<(Option<Item>, u32), ReadError> {
let n = buff.read_i16()?;
Ok((
(n != -1).then(|| Item::try_from(n as u16).unwrap()),
buff.read_u32()?,
))
}
fn read_status(buff: &mut DataRead) -> Result<[Status; 3], ReadError> {
let mut status = [Status::None, Status::None, Status::None];
for i in 0..buff.read_i32()? {
let this = Status::try_from(buff.read_u16()?);
buff.skip(4)?;
if let Ok(s) = this
&& i < 3
{
status[i as usize] = s;
}
}
Ok(status)
}
impl Unit {
#[inline]
pub fn draw(&self, s: crate::data::renderer::Scale) -> ImageHolder<4> {
self.ty.draw(s)
}
}