use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "tsify")]
use tsify::Tsify;
#[cfg(feature = "tsify")]
use wasm_bindgen::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "tsify", derive(Tsify))]
#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Position {
pub x: u8,
pub y: u8,
}
impl Position {
#[must_use]
pub const fn new(x: u8, y: u8) -> Self {
Self { x, y }
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "tsify", derive(Tsify))]
#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))]
#[repr(u8)]
pub enum CaptainAbility {
Delay = 100,
RallyingCry = 101,
ArrowVolley { target: Position } = 102,
BattleCry = 103,
CatapultsVolley { target: Position } = 104,
Unknown(u8),
}
impl CaptainAbility {
pub fn from_bytes(ability_id: u8, extra_data: &[u8]) -> Result<Self, String> {
match ability_id {
100 => Ok(Self::Delay),
101 => Ok(Self::RallyingCry),
102 => {
if extra_data.len() < 2 {
return Err("Arrow Volley requires target coordinates".into());
}
Ok(Self::ArrowVolley {
target: Position::new(extra_data[0], extra_data[1]),
})
}
103 => Ok(Self::BattleCry),
104 => {
if extra_data.len() < 2 {
return Err("Catapults Volley requires target coordinates".into());
}
Ok(Self::CatapultsVolley {
target: Position::new(extra_data[0], extra_data[1]),
})
}
other => Ok(Self::Unknown(other)),
}
}
#[must_use]
pub const fn has_target_for_id(ability_id: u8) -> bool {
matches!(ability_id, 102 | 104)
}
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Delay => "Delay",
Self::RallyingCry => "Rallying Cry",
Self::ArrowVolley { .. } => "Arrow Volley",
Self::BattleCry => "Battle Cry",
Self::CatapultsVolley { .. } => "Catapults Volley",
Self::Unknown(_) => "Unknown Ability",
}
}
#[must_use]
pub const fn target(&self) -> Option<&Position> {
match self {
Self::ArrowVolley { target } | Self::CatapultsVolley { target } => Some(target),
_ => None,
}
}
}
impl fmt::Display for CaptainAbility {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.target() {
Some(target) => write!(f, "{} targeting {}", self.name(), target),
None => write!(f, "{}", self.name()),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "tsify", derive(Tsify))]
#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))]
pub enum UnitType {
Archer,
Pikeman,
Catapult { target: Position },
Captain {
ability: CaptainAbility,
wait_time: u8,
},
Unknown(u8),
}
impl UnitType {
pub fn from_bytes(unit_type_raw: u8, extra_data: &[u8]) -> Result<Self, String> {
match unit_type_raw {
92 => Ok(Self::Archer),
93 => Ok(Self::Pikeman),
94 => {
if extra_data.len() < 2 {
return Err("Catapult requires target coordinates".into());
}
Ok(Self::Catapult {
target: Position::new(extra_data[0], extra_data[1]),
})
}
100..=105 => {
if extra_data.is_empty() {
return Err("Captain requires wait time".into());
}
let wait_time = extra_data[0];
if CaptainAbility::has_target_for_id(unit_type_raw) {
if extra_data.len() < 3 {
return Err("Captain with target requires target coordinates".into());
}
let ability = CaptainAbility::from_bytes(unit_type_raw, &extra_data[1..])?;
Ok(Self::Captain { ability, wait_time })
} else {
let ability = CaptainAbility::from_bytes(unit_type_raw, &[])?;
Ok(Self::Captain { ability, wait_time })
}
}
other => Ok(Self::Unknown(other)),
}
}
#[must_use]
pub const fn record_size_for_raw_type(unit_type_raw: u8) -> usize {
match unit_type_raw {
94 => 5, 100..=105 => {
if CaptainAbility::has_target_for_id(unit_type_raw) {
6 } else {
4 }
}
_ => 3, }
}
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Archer => "Archer",
Self::Pikeman => "Pikeman",
Self::Catapult { .. } => "Catapult",
Self::Captain { .. } => "Captain",
Self::Unknown(_) => "Unknown Unit",
}
}
}
impl fmt::Display for UnitType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Archer => write!(f, "Archer"),
Self::Pikeman => write!(f, "Pikeman"),
Self::Catapult { target } => write!(f, "Catapult targeting {target}"),
Self::Captain { ability, wait_time } => {
write!(f, "Captain using {ability} for {wait_time} seconds")
}
Self::Unknown(id) => write!(f, "Unknown unit (type {id})"),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "tsify", derive(Tsify))]
#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))]
pub struct UnitRecord {
pub position: Position,
pub unit_type: UnitType,
}
impl UnitRecord {
#[must_use]
pub const fn new(x: u8, y: u8, unit_type: UnitType) -> Self {
Self {
position: Position::new(x, y),
unit_type,
}
}
#[must_use]
pub const fn position(&self) -> &Position {
&self.position
}
#[must_use]
pub const fn unit_type(&self) -> &UnitType {
&self.unit_type
}
}
impl fmt::Display for UnitRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} at {}", self.unit_type, self.position)
}
}