use crate::unit_action::UnitAction;
use crate::unit_type::UnitBaseClass;
use crate::Result;
use crate::{ObjectID, PlayerID};
use arrayvec::ArrayVec;
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
use genie_dat::UnitType;
pub use genie_dat::{AttributeCost, SpriteID, TerrainID};
use genie_support::{read_opt_u32, ReadSkipExt};
pub use genie_support::{StringKey, UnitTypeID};
use std::convert::TryInto;
use std::io::{Read, Write};
#[derive(Debug, Clone)]
pub struct Unit {
pub unit_base_class: UnitBaseClass,
pub static_: StaticUnitAttributes,
pub animated: Option<AnimatedUnitAttributes>,
pub moving: Option<MovingUnitAttributes>,
pub action: Option<ActionUnitAttributes>,
pub base_combat: Option<BaseCombatUnitAttributes>,
pub missile: Option<MissileUnitAttributes>,
pub combat: Option<CombatUnitAttributes>,
pub building: Option<BuildingUnitAttributes>,
}
impl Unit {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Option<Self>> {
let raw_class = input.read_u8()?;
if raw_class == 0 {
return Ok(None);
}
let unit_base_class = raw_class.try_into().unwrap();
let static_ = StaticUnitAttributes::read_from(&mut input, version)?;
let mut unit = Self {
unit_base_class,
static_,
animated: None,
moving: None,
action: None,
base_combat: None,
missile: None,
combat: None,
building: None,
};
if unit_base_class >= UnitBaseClass::Animated {
unit.animated = Some(AnimatedUnitAttributes::read_from(&mut input)?);
}
if unit_base_class >= UnitBaseClass::Moving {
unit.moving = Some(MovingUnitAttributes::read_from(&mut input, version)?);
}
if unit_base_class >= UnitBaseClass::Action {
unit.action = Some(ActionUnitAttributes::read_from(&mut input, version)?);
}
if unit_base_class >= UnitBaseClass::BaseCombat {
unit.base_combat = Some(BaseCombatUnitAttributes::read_from(&mut input, version)?);
}
if unit_base_class >= UnitBaseClass::Missile {
unit.missile = Some(MissileUnitAttributes::read_from(&mut input, version)?);
}
if unit_base_class >= UnitBaseClass::Combat {
unit.combat = Some(CombatUnitAttributes::read_from(&mut input, version)?);
}
if unit_base_class >= UnitBaseClass::Building {
unit.building = Some(BuildingUnitAttributes::read_from(&mut input, version)?);
}
Ok(Some(unit))
}
pub fn write_to(&self, mut output: impl Write, version: f32) -> Result<()> {
let raw_class = self.unit_base_class as u8;
output.write_u8(raw_class)?;
self.static_.write_to(&mut output, version)?;
if let Some(animated) = &self.animated {
animated.write_to(&mut output)?;
}
if let Some(moving) = &self.moving {
moving.write_to(&mut output)?;
}
if let Some(action) = &self.action {
action.write_to(&mut output, version)?;
}
if let Some(base_combat) = &self.base_combat {
base_combat.write_to(&mut output, version)?;
}
if let Some(missile) = &self.missile {
missile.write_to(&mut output, version)?;
}
if let Some(combat) = &self.combat {
combat.write_to(&mut output, version)?;
}
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct SpriteNodeAnimation {
pub animate_interval: u32,
pub animate_last: u32,
pub last_frame: u16,
pub frame_changed: u8,
pub frame_looped: u8,
pub animate_flag: u8,
pub last_speed: f32,
}
impl SpriteNodeAnimation {
pub fn read_from(mut input: impl Read) -> Result<Self> {
let mut animation = Self::default();
animation.animate_interval = input.read_u32::<LE>()?;
animation.animate_last = input.read_u32::<LE>()?;
animation.last_frame = input.read_u16::<LE>()?;
animation.frame_changed = input.read_u8()?;
animation.frame_looped = input.read_u8()?;
animation.animate_flag = input.read_u8()?;
animation.last_speed = input.read_f32::<LE>()?;
Ok(animation)
}
pub fn write_to(&self, mut output: impl Write) -> Result<()> {
output.write_u32::<LE>(self.animate_interval)?;
output.write_u32::<LE>(self.animate_last)?;
output.write_u16::<LE>(self.last_frame)?;
output.write_u8(self.frame_changed)?;
output.write_u8(self.frame_looped)?;
output.write_u8(self.animate_flag)?;
output.write_f32::<LE>(self.last_speed)?;
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct SpriteNode {
pub id: SpriteID,
pub x: u32,
pub y: u32,
pub frame: u16,
pub invisible: bool,
pub animation: Option<SpriteNodeAnimation>,
pub order: u8,
pub flag: u8,
pub count: u8,
}
impl SpriteNode {
pub fn read_from(mut input: impl Read) -> Result<Option<Self>> {
let ty = input.read_u8()?;
if ty == 0 {
return Ok(None);
}
let mut node = Self::default();
node.id = input.read_u16::<LE>()?.into();
node.x = input.read_u32::<LE>()?;
node.y = input.read_u32::<LE>()?;
node.frame = input.read_u16::<LE>()?;
node.invisible = input.read_u8()? != 0;
if ty == 2 {
node.animation = Some(SpriteNodeAnimation::read_from(&mut input)?);
}
node.order = input.read_u8()?;
node.flag = input.read_u8()?;
node.count = input.read_u8()?;
Ok(Some(node))
}
pub fn write_to(&self, mut output: impl Write) -> Result<()> {
let ty = if self.animation.is_some() { 2 } else { 1 };
output.write_u8(ty)?;
output.write_u16::<LE>(self.id.into())?;
output.write_u32::<LE>(self.x)?;
output.write_u32::<LE>(self.y)?;
output.write_u16::<LE>(self.frame)?;
output.write_u8(if self.invisible { 1 } else { 0 })?;
if let Some(animation) = &self.animation {
animation.write_to(&mut output)?;
}
output.write_u8(self.order)?;
output.write_u8(self.flag)?;
output.write_u8(self.count)?;
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct SpriteList {
pub sprites: Vec<SpriteNode>,
}
impl SpriteList {
pub fn read_from(mut input: impl Read) -> Result<Self> {
let mut sprites = vec![];
while let Some(node) = SpriteNode::read_from(&mut input)? {
sprites.push(node);
}
Ok(Self { sprites })
}
pub fn write_to(&self, mut output: impl Write, _version: f32) -> Result<()> {
for sprite in &self.sprites {
sprite.write_to(&mut output)?;
}
output.write_u8(0)?;
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct StaticUnitAttributes {
pub owner_id: PlayerID,
pub unit_type_id: UnitTypeID,
pub sprite_id: SpriteID,
pub garrisoned_in_id: Option<ObjectID>,
pub hit_points: f32,
pub object_state: u8,
pub sleep_flag: bool,
pub dopple_flag: bool,
pub go_to_sleep_flag: bool,
pub id: ObjectID,
pub facet: u8,
pub position: (f32, f32, f32),
pub screen_offset: (u16, u16),
pub shadow_offset: (u16, u16),
pub selected_group: Option<u8>,
pub attribute_type_held: u16,
pub attribute_amount_held: f32,
pub worker_count: u8,
pub current_damage: u8,
pub damaged_lately_timer: u8,
pub under_attack: bool,
pub pathing_group_members: Vec<ObjectID>,
pub group_id: Option<u32>,
pub roo_already_called: u8,
pub sprite_list: Option<SpriteList>,
}
impl StaticUnitAttributes {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut attrs = Self::default();
attrs.owner_id = input.read_u8()?.into();
attrs.unit_type_id = input.read_u16::<LE>()?.into();
attrs.sprite_id = input.read_u16::<LE>()?.into();
attrs.garrisoned_in_id = read_opt_u32(&mut input)?;
attrs.hit_points = input.read_f32::<LE>()?;
attrs.object_state = input.read_u8()?;
attrs.sleep_flag = input.read_u8()? != 0;
attrs.dopple_flag = input.read_u8()? != 0;
attrs.go_to_sleep_flag = input.read_u8()? != 0;
attrs.id = input.read_u32::<LE>()?.into();
attrs.facet = input.read_u8()?;
attrs.position = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
attrs.screen_offset = (input.read_u16::<LE>()?, input.read_u16::<LE>()?);
attrs.shadow_offset = (input.read_u16::<LE>()?, input.read_u16::<LE>()?);
if version < 11.58 {
attrs.selected_group = match input.read_i8()? {
-1 => None,
id => Some(id.try_into().unwrap()),
};
}
attrs.attribute_type_held = input.read_u16::<LE>()?;
attrs.attribute_amount_held = input.read_f32::<LE>()?;
attrs.worker_count = input.read_u8()?;
attrs.current_damage = input.read_u8()?;
attrs.damaged_lately_timer = input.read_u8()?;
attrs.under_attack = input.read_u8()? != 0;
attrs.pathing_group_members = {
let num_members = input.read_u32::<LE>()?;
let mut members = vec![ObjectID(0); num_members.try_into().unwrap()];
for m in members.iter_mut() {
*m = input.read_u32::<LE>()?.into();
}
members
};
attrs.group_id = read_opt_u32(&mut input)?;
attrs.roo_already_called = input.read_u8()?;
if input.read_u8()? != 0 {
attrs.sprite_list = Some(SpriteList::read_from(&mut input)?);
}
Ok(attrs)
}
pub fn write_to(&self, mut output: impl Write, _version: f32) -> Result<()> {
output.write_u8(self.owner_id.into())?;
output.write_u16::<LE>(self.unit_type_id.into())?;
output.write_u16::<LE>(self.sprite_id.into())?;
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct AnimatedUnitAttributes {
pub speed: f32,
}
impl AnimatedUnitAttributes {
pub fn read_from(mut input: impl Read) -> Result<Self> {
let speed = input.read_f32::<LE>()?;
Ok(Self { speed })
}
pub fn write_to(&self, mut output: impl Write) -> Result<()> {
output.write_f32::<LE>(self.speed)?;
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct PathData {
pub id: u32,
pub linked_path_type: u32,
pub waypoint_level: u32,
pub path_id: u32,
pub waypoint: u32,
pub disable_flags: Option<u32>,
pub enable_flags: Option<u32>,
pub state: u32,
pub range: f32,
pub target_id: u32,
pub pause_time: f32,
pub continue_counter: u32,
pub flags: u32,
}
impl PathData {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut path = Self::default();
path.id = input.read_u32::<LE>()?;
path.linked_path_type = input.read_u32::<LE>()?;
path.waypoint_level = input.read_u32::<LE>()?;
path.path_id = input.read_u32::<LE>()?;
path.waypoint = input.read_u32::<LE>()?;
if version < 10.25 {
path.disable_flags = Some(input.read_u32::<LE>()?);
if version >= 10.20 {
path.enable_flags = Some(input.read_u32::<LE>()?);
}
}
path.state = input.read_u32::<LE>()?;
path.range = input.read_f32::<LE>()?;
path.target_id = input.read_u32::<LE>()?;
path.pause_time = input.read_f32::<LE>()?;
path.continue_counter = input.read_u32::<LE>()?;
path.flags = input.read_u32::<LE>()?;
Ok(path)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct MovementData {
pub velocity: (f32, f32, f32),
pub acceleration: (f32, f32, f32),
}
impl MovementData {
pub fn read_from(mut input: impl Read) -> Result<Self> {
let velocity = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
let acceleration = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
Ok(Self {
velocity,
acceleration,
})
}
pub fn write_to(&self, mut output: impl Write) -> Result<()> {
output.write_f32::<LE>(self.velocity.0)?;
output.write_f32::<LE>(self.velocity.1)?;
output.write_f32::<LE>(self.velocity.2)?;
output.write_f32::<LE>(self.acceleration.0)?;
output.write_f32::<LE>(self.acceleration.1)?;
output.write_f32::<LE>(self.acceleration.2)?;
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct MovingUnitAttributes {
pub trail_remainder: u32,
pub velocity: (f32, f32, f32),
pub angle: f32,
pub turn_towards_time: u32,
pub turn_timer: u32,
pub continue_counter: u32,
pub current_terrain_exception: (Option<u32>, Option<u32>),
pub waiting_to_move: u8,
pub wait_delays_count: u8,
pub on_ground: u8,
pub path_data: Vec<PathData>,
pub future_path_data: Option<PathData>,
pub movement_data: Option<MovementData>,
pub position: (f32, f32, f32),
pub orientation_forward: (f32, f32, f32),
pub orientation_right: (f32, f32, f32),
pub last_move_time: u32,
pub user_defined_waypoints: Vec<(f32, f32, f32)>,
pub substitute_position: Option<(f32, f32, f32)>,
pub consecutive_substitute_count: u32,
}
impl MovingUnitAttributes {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut attrs = Self::default();
attrs.trail_remainder = input.read_u32::<LE>()?;
attrs.velocity = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
attrs.angle = input.read_f32::<LE>()?;
attrs.turn_towards_time = input.read_u32::<LE>()?;
attrs.turn_timer = input.read_u32::<LE>()?;
attrs.continue_counter = input.read_u32::<LE>()?;
attrs.current_terrain_exception = (read_opt_u32(&mut input)?, read_opt_u32(&mut input)?);
attrs.waiting_to_move = input.read_u8()?;
attrs.wait_delays_count = input.read_u8()?;
attrs.on_ground = input.read_u8()?;
attrs.path_data = {
let num_paths = input.read_u32::<LE>()?;
let mut paths = vec![];
for _ in 0..num_paths {
paths.push(PathData::read_from(&mut input, version)?);
}
paths
};
if input.read_u32::<LE>()? != 0 {
attrs.future_path_data = Some(PathData::read_from(&mut input, version)?);
}
if input.read_u32::<LE>()? != 0 {
attrs.movement_data = Some(MovementData::read_from(&mut input)?);
}
attrs.position = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
attrs.orientation_forward = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
attrs.orientation_right = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
attrs.last_move_time = input.read_u32::<LE>()?;
attrs.user_defined_waypoints = {
let num_waypoints = input.read_i32::<LE>()?.max(0);
let mut waypoints = vec![];
for _ in 0..num_waypoints {
waypoints.push((
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
));
}
waypoints
};
attrs.substitute_position = {
let exists = input.read_u32::<LE>()? != 0;
let x = input.read_f32::<LE>()?;
let y = input.read_f32::<LE>()?;
let z = input.read_f32::<LE>()?;
if exists {
Some((x, y, z))
} else {
None
}
};
attrs.consecutive_substitute_count = input.read_u32::<LE>()?;
Ok(attrs)
}
pub fn write_to(&self, _output: impl Write) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct ActionUnitAttributes {
pub waiting: bool,
pub command_flag: u8,
pub selected_group_info: u16,
pub actions: Vec<UnitAction>,
}
impl ActionUnitAttributes {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut attrs = Self::default();
attrs.waiting = input.read_u8()? != 0;
if version >= 6.5 {
attrs.command_flag = input.read_u8()?;
}
if version >= 11.58 {
attrs.selected_group_info = input.read_u16::<LE>()?;
}
attrs.actions = UnitAction::read_list_from(input, version)?;
Ok(attrs)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct BaseCombatUnitAttributes {
pub formation_id: u8,
pub formation_row: u8,
pub formation_column: u8,
pub attack_timer: f32,
pub capture_flag: u8,
pub multi_unified_points: u8,
pub large_object_radius: u8,
pub attack_count: u32,
}
impl BaseCombatUnitAttributes {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut attrs = Self::default();
if version >= 9.05 {
attrs.formation_id = input.read_u8()?;
attrs.formation_row = input.read_u8()?;
attrs.formation_column = input.read_u8()?;
}
attrs.attack_timer = input.read_f32::<LE>()?;
if version >= 2.01 {
attrs.capture_flag = input.read_u8()?;
}
if version >= 9.09 {
attrs.multi_unified_points = input.read_u8()?;
attrs.large_object_radius = input.read_u8()?;
}
if version >= 10.02 {
attrs.attack_count = input.read_u32::<LE>()?;
}
Ok(attrs)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct MissileUnitAttributes {
pub max_range: f32,
pub fired_from_id: ObjectID,
pub own_base: Option<UnitType>,
}
impl MissileUnitAttributes {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut attrs = Self::default();
attrs.max_range = input.read_f32::<LE>()?;
attrs.fired_from_id = input.read_u32::<LE>()?.into();
attrs.own_base = {
if input.read_u8()? == 0 {
None
} else {
Some(UnitType::read_from(&mut input, version)?)
}
};
Ok(attrs)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct UnitAIOrder {
issuer: u32,
order_type: u32,
priority: u32,
target_id: ObjectID,
target_player: PlayerID,
target_location: (f32, f32, f32),
range: f32,
}
impl UnitAIOrder {
pub fn read_from(mut input: impl Read) -> Result<Self> {
let mut order = Self::default();
order.issuer = input.read_u32::<LE>()?;
order.order_type = input.read_u32::<LE>()?;
order.priority = input.read_u32::<LE>()?;
order.target_id = input.read_u32::<LE>()?.into();
order.target_player = input.read_u32::<LE>()?.try_into().unwrap();
order.target_location = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
order.range = input.read_f32::<LE>()?;
Ok(order)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct UnitAINotification {
pub caller: u32,
pub recipient: u32,
pub notification_type: u32,
pub params: (u32, u32, u32),
}
impl UnitAINotification {
pub fn read_from(mut input: impl Read) -> Result<Self> {
let mut notify = Self::default();
notify.caller = input.read_u32::<LE>()?;
notify.recipient = input.read_u32::<LE>()?;
notify.notification_type = input.read_u32::<LE>()?;
notify.params = (
input.read_u32::<LE>()?,
input.read_u32::<LE>()?,
input.read_u32::<LE>()?,
);
Ok(notify)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct UnitAIOrderHistory {
order: u32,
action: u32,
time: u32,
position: (f32, f32, f32),
target_id: ObjectID,
target_attack_category: Option<u32>,
target_position: (f32, f32, f32),
}
impl UnitAIOrderHistory {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut order = Self::default();
order.order = input.read_u32::<LE>()?;
order.action = input.read_u32::<LE>()?;
order.time = input.read_u32::<LE>()?;
order.position = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
order.target_id = input.read_u32::<LE>()?.into();
if version >= 10.50 {
order.target_attack_category = read_opt_u32(&mut input)?;
}
order.target_position = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
Ok(order)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct UnitAIRetargetEntry {
pub target_id: ObjectID,
pub retarget_timeout: u32,
}
impl UnitAIRetargetEntry {
pub fn read_from(mut input: impl Read) -> Result<Self> {
let target_id = input.read_u32::<LE>()?.into();
let retarget_timeout = input.read_u32::<LE>()?;
Ok(Self {
target_id,
retarget_timeout,
})
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct Waypoint {
pub location: (f32, f32, f32),
pub facet_to_next_waypoint: u8,
}
impl Waypoint {
pub fn read_from(mut input: impl Read) -> Result<Self> {
let mut waypoint = Self::default();
waypoint.location = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
waypoint.facet_to_next_waypoint = input.read_u8()?;
let _padding = input.read_u8()?;
let _padding = input.read_u8()?;
let _padding = input.read_u8()?;
Ok(waypoint)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct PatrolPath {}
impl PatrolPath {
pub fn read_from(_input: impl Read) -> Result<Self> {
todo!()
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct UnitAI {
mood: Option<u32>,
current_order: Option<u32>,
current_order_priority: Option<u32>,
current_action: Option<u32>,
current_target: Option<u32>,
current_target_type: Option<u32>,
current_target_location: (f32, f32, f32),
desired_target_distance: f32,
last_action: Option<u32>,
last_order: Option<u32>,
last_target: Option<u32>,
last_target_type: Option<u32>,
last_update_type: Option<u32>,
idle_timer: u32,
idle_timeout: u32,
adjusted_idle_timeout: u32,
secondary_timer: u32,
lookaround_timer: u32,
lookaround_timeout: u32,
defend_target: Option<ObjectID>,
defense_buffer: f32,
last_world_position: Waypoint,
orders: Vec<UnitAIOrder>,
notifications: Vec<UnitAINotification>,
attacking_units: Vec<ObjectID>,
stop_after_target_killed: bool,
state: u8,
state_position: (f32, f32),
time_since_enemy_sighting: u32,
alert_mode: u8,
alert_mode_object_id: Option<ObjectID>,
patrol_path: Option<PatrolPath>,
patrol_current_waypoint: u32,
order_history: Vec<UnitAIOrderHistory>,
last_retarget_time: u32,
randomized_retarget_timer: u32,
retarget_entries: Vec<UnitAIRetargetEntry>,
best_unit_to_attack: Option<u32>,
formation_type: u8,
}
impl UnitAI {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut ai = Self::default();
ai.mood = read_opt_u32(&mut input)?;
ai.current_order = read_opt_u32(&mut input)?;
ai.current_order_priority = read_opt_u32(&mut input)?;
ai.current_action = read_opt_u32(&mut input)?;
ai.current_target = read_opt_u32(&mut input)?;
ai.current_target_type = match input.read_u16::<LE>()? {
0xFFFF => None,
id => Some(id.try_into().unwrap()),
};
input.skip(2)?;
ai.current_target_location = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
ai.desired_target_distance = input.read_f32::<LE>()?;
ai.last_action = read_opt_u32(&mut input)?;
ai.last_order = read_opt_u32(&mut input)?;
ai.last_target = read_opt_u32(&mut input)?;
ai.last_target_type = read_opt_u32(&mut input)?;
ai.last_update_type = read_opt_u32(&mut input)?;
ai.idle_timer = input.read_u32::<LE>()?;
ai.idle_timeout = input.read_u32::<LE>()?;
ai.adjusted_idle_timeout = input.read_u32::<LE>()?;
ai.secondary_timer = input.read_u32::<LE>()?;
ai.lookaround_timer = input.read_u32::<LE>()?;
ai.lookaround_timeout = input.read_u32::<LE>()?;
ai.defend_target = read_opt_u32(&mut input)?;
ai.defense_buffer = input.read_f32::<LE>()?;
ai.last_world_position = Waypoint::read_from(&mut input)?;
ai.orders = {
let num_orders = input.read_u32::<LE>()?;
let mut orders = vec![];
for _ in 0..num_orders {
orders.push(UnitAIOrder::read_from(&mut input)?);
}
orders
};
ai.notifications = {
let num_notifications = input.read_u32::<LE>()?;
let mut notifications = vec![];
for _ in 0..num_notifications {
notifications.push(UnitAINotification::read_from(&mut input)?);
}
notifications
};
ai.attacking_units = {
let num_units = input.read_u32::<LE>()?;
let mut units = vec![];
for _ in 0..num_units {
units.push(input.read_u32::<LE>()?.into());
}
units
};
ai.stop_after_target_killed = input.read_u8()? != 0;
ai.state = input.read_u8()?;
ai.state_position = (input.read_f32::<LE>()?, input.read_f32::<LE>()?);
ai.time_since_enemy_sighting = input.read_u32::<LE>()?;
ai.alert_mode = input.read_u8()?;
ai.alert_mode_object_id = read_opt_u32(&mut input)?;
ai.patrol_path = {
let has_path = input.read_u32::<LE>()? != 0;
if has_path {
Some(PatrolPath::read_from(&mut input)?)
} else {
None
}
};
ai.patrol_current_waypoint = input.read_u32::<LE>()?;
if version >= 10.48 {
ai.order_history = {
let num_orders = input.read_u32::<LE>()?;
let mut orders = vec![];
for _ in 0..num_orders {
orders.push(UnitAIOrderHistory::read_from(&mut input, version)?);
}
orders
};
}
if version >= 10.50 {
ai.last_retarget_time = input.read_u32::<LE>()?;
}
if version >= 11.04 {
ai.randomized_retarget_timer = input.read_u32::<LE>()?;
}
if version >= 11.05 {
ai.retarget_entries = {
let num_entries = input.read_u32::<LE>()?;
let mut entries = vec![];
for _ in 0..num_entries {
entries.push(UnitAIRetargetEntry::read_from(&mut input)?);
}
entries
};
}
if version >= 11.14 {
ai.best_unit_to_attack = read_opt_u32(&mut input)?;
}
if version >= 11.44 {
ai.formation_type = input.read_u8()?;
}
Ok(ai)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Default, Clone)]
pub struct CombatUnitAttributes {
pub next_volley: u8,
pub using_special_attack_animation: u8,
pub own_base: Option<UnitType>,
pub attribute_amounts: [u16; 6],
pub decay_timer: u16,
pub raider_build_countdown: u32,
pub locked_down_count: u32,
pub inside_garrison_count: u8,
pub unit_ai: Option<UnitAI>,
pub town_bell_flag: i8,
pub town_bell_target_id: Option<ObjectID>,
pub town_bell_target_location: Option<(f32, f32)>,
pub town_bell_target_id_2: Option<ObjectID>,
pub town_bell_target_type: u32,
pub town_bell_action: u32,
pub berserker_timer: f32,
pub num_builders: u8,
pub num_healers: u8,
}
impl CombatUnitAttributes {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut attrs = Self::default();
attrs.next_volley = input.read_u8()?;
attrs.using_special_attack_animation = input.read_u8()?;
attrs.own_base = {
if input.read_u8()? == 0 {
None
} else {
Some(UnitType::read_from(&mut input, version)?)
}
};
for amount in attrs.attribute_amounts.iter_mut() {
*amount = input.read_u16::<LE>()?;
}
if version >= 9.16 {
attrs.decay_timer = input.read_u16::<LE>()?;
}
if version >= 9.61 {
attrs.raider_build_countdown = input.read_u32::<LE>()?;
}
if version >= 9.65 {
attrs.locked_down_count = input.read_u32::<LE>()?;
}
if version >= 11.56 {
attrs.inside_garrison_count = input.read_u8()?;
}
attrs.unit_ai = {
let has_ai = input.read_u32::<LE>()? != 0;
if has_ai {
Some(UnitAI::read_from(&mut input, version)?)
} else {
None
}
};
if version >= 10.30 {
attrs.town_bell_flag = input.read_i8()?;
attrs.town_bell_target_id = read_opt_u32(&mut input)?;
attrs.town_bell_target_location = {
let location = (input.read_f32::<LE>()?, input.read_f32::<LE>()?);
if location.0 >= 0.0 {
Some(location)
} else {
None
}
};
}
if version >= 11.71 {
attrs.town_bell_target_id_2 = read_opt_u32(&mut input)?;
attrs.town_bell_target_type = input.read_u32::<LE>()?;
}
if version >= 11.74 {
attrs.town_bell_action = input.read_u32::<LE>()?;
}
if version >= 10.42 {
attrs.berserker_timer = input.read_f32::<LE>()?;
}
if version >= 10.46 {
attrs.num_builders = input.read_u8()?;
}
if version >= 11.69 {
attrs.num_healers = input.read_u8()?;
}
Ok(attrs)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}
#[derive(Debug, Clone)]
pub enum GatherPoint {
Location { x: f32, y: f32, z: f32 },
Object { id: ObjectID, unit_type: UnitTypeID },
}
#[derive(Debug, Default, Clone)]
pub struct ProductionQueueEntry {
pub unit_type_id: UnitTypeID,
pub count: u16,
}
impl ProductionQueueEntry {
fn read_from(mut input: impl Read) -> Result<Self> {
let unit_type_id = input.read_u16::<LE>()?.into();
let count = input.read_u16::<LE>()?;
Ok(Self {
unit_type_id,
count,
})
}
}
#[derive(Debug, Default, Clone)]
pub struct BuildingUnitAttributes {
pub built: bool,
pub build_points: f32,
pub unique_build_id: Option<u32>,
pub culture: u8,
pub burning: u8,
pub last_burn_time: u32,
pub last_garrison_time: u32,
pub relic_count: u32,
pub specific_relic_count: u32,
pub gather_point: Option<GatherPoint>,
pub desolid_flag: bool,
pub pending_order: u32,
pub linked_owner: Option<ObjectID>,
pub linked_children: ArrayVec<[ObjectID; 4]>,
pub captured_unit_count: u8,
pub extra_actions: Vec<UnitAction>,
pub research_actions: Vec<UnitAction>,
pub production_queue: Vec<ProductionQueueEntry>,
pub production_queue_total_units: u16,
pub production_queue_enabled: bool,
pub production_queue_actions: Vec<UnitAction>,
pub endpoint: (f32, f32, f32),
pub gate_locked: u32,
pub first_update: u32,
pub close_timer: u32,
pub terrain_type: Option<TerrainID>,
pub semi_asleep: bool,
pub snow_flag: bool,
}
impl BuildingUnitAttributes {
pub fn read_from(mut input: impl Read, version: f32) -> Result<Self> {
let mut attrs = Self::default();
attrs.built = input.read_u8()? != 0;
attrs.build_points = input.read_f32::<LE>()?;
attrs.unique_build_id = read_opt_u32(&mut input)?;
attrs.culture = input.read_u8()?;
attrs.burning = input.read_u8()?;
attrs.last_burn_time = input.read_u32::<LE>()?;
attrs.last_garrison_time = input.read_u32::<LE>()?;
attrs.relic_count = input.read_u32::<LE>()?;
attrs.specific_relic_count = input.read_u32::<LE>()?;
attrs.gather_point = {
let exists = input.read_u32::<LE>()? != 0;
let location = GatherPoint::Location {
x: input.read_f32::<LE>()?,
y: input.read_f32::<LE>()?,
z: input.read_f32::<LE>()?,
};
let object_id = input.read_i32::<LE>()?;
let unit_type_id = input.read_i16::<LE>()?;
match (exists, object_id, unit_type_id) {
(false, _, _) => None,
(true, -1, -1) => Some(location),
(true, id, unit_type_id) => Some(GatherPoint::Object {
id: id.try_into().unwrap(),
unit_type: unit_type_id.try_into().unwrap(),
}),
}
};
attrs.desolid_flag = input.read_u8()? != 0;
if version >= 10.54 {
attrs.pending_order = input.read_u32::<LE>()?;
}
attrs.linked_owner = read_opt_u32(&mut input)?;
attrs.linked_children = {
let mut children: ArrayVec<[ObjectID; 4]> = Default::default();
for _ in 0..4 {
let id = input.read_i32::<LE>()?;
if id != -1 {
children.push(id.try_into().unwrap());
}
}
children
};
attrs.captured_unit_count = input.read_u8()?;
attrs.extra_actions = UnitAction::read_list_from(&mut input, version)?;
attrs.research_actions = UnitAction::read_list_from(&mut input, version)?;
attrs.production_queue = {
let capacity = input.read_u16::<LE>()?;
let mut queue = vec![ProductionQueueEntry::default(); capacity as usize];
for entry in queue.iter_mut() {
*entry = ProductionQueueEntry::read_from(&mut input)?;
}
let _size = input.read_u16::<LE>()?;
queue
};
attrs.production_queue_total_units = input.read_u16::<LE>()?;
attrs.production_queue_enabled = input.read_u8()? != 0;
attrs.production_queue_actions = UnitAction::read_list_from(&mut input, version)?;
if version >= 10.65 {
attrs.endpoint = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
attrs.endpoint = (
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
input.read_f32::<LE>()?,
);
attrs.gate_locked = input.read_u32::<LE>()?;
attrs.first_update = input.read_u32::<LE>()?;
attrs.close_timer = input.read_u32::<LE>()?;
}
if version >= 10.67 {
attrs.terrain_type = Some(input.read_u8()?.into());
}
if version >= 11.43 {
attrs.semi_asleep = input.read_u8()? != 0;
}
if version >= 11.54 {
attrs.snow_flag = input.read_u8()? != 0;
}
Ok(attrs)
}
pub fn write_to(&self, _output: impl Write, _version: f32) -> Result<()> {
todo!()
}
}