dis-rust 0.2.10

A rust implementation of the DIS simulation protocol.
Documentation
//     dis-rust - A rust implementation of the DIS simulation protocol.
//     Copyright (C) 2022 Thomas Mann
// 
//     This software is dual-licensed. It is available under the conditions of
//     the GNU Affero General Public License (see the LICENSE file included) 
//     or under a commercial license (email contact@coffeebreakdevs.com for
//     details).

use std::any::Any;

use bytes::{BytesMut, BufMut, Buf};

use crate::common::{entity_coordinate_vector_record::EntityCoordinateVectorRecord, entity_id_record::EntityIDRecord, event_id_record::EventIDRecord, pdu::PDU, pdu_header_record::{PDUHeaderRecord, PDUType, ProtocolFamily}, velocity_vector_record::VelocityVectorRecord, world_coordinate_record::WorldCoordinateRecord, dis_error::DISError};

use super::burst_descriptor_record::BurstDescriptorRecord;

#[derive(Copy, Clone, Debug,)]
/// Detonation PDU as defined in IEEE 1278.1 standard. Used to communicate a munition detonating during the simulation.
pub struct DetonationPDU {
    pub pdu_header_record: PDUHeaderRecord,
    pub firing_entity_id_record: EntityIDRecord,
    pub target_entity_id_record: EntityIDRecord,
    pub munition_id_record: EntityIDRecord,
    pub event_id_record: EventIDRecord,
    pub velocity_record: VelocityVectorRecord,
    pub location_in_world_record: WorldCoordinateRecord,
    pub burst_descriptor_record: BurstDescriptorRecord,
    pub location_in_entity_coordinates_record: EntityCoordinateVectorRecord,
    pub detonation_result_field: DetonationResult,
    pub number_of_articulation_parameters_field: u8,
    pub padding: u16,
    pub articulation_parameter_record: f32 // TODO: Needs a proper struct
    
}

impl DetonationPDU {
    /// Provides a blank DetonationPDU with all IDs set to 1 (as 0 is not allowed within the DIS protocol).
    /// 
    /// # Examples
    /// 
    /// Creating a blank DetonationPDU:
    /// 
    /// ```
    /// let detonation_pdu = DetonationPDU::default();
    /// ```
    /// 
    /// Populating a bank DetonationPDU's firing_entity_id_record (this should match the
    /// firing_entity_id_record in the FirePDU used to launch the munition):
    /// 
    /// ```
    /// let mut detonation_pdu = DetonationPDU::default();
    /// detonation_pdu.firing_entity_id_record = EntityIDRecord::default(2);
    /// ```
    /// 
    pub fn default() -> Self {
        DetonationPDU {
            pdu_header_record: PDUHeaderRecord::default(PDUType::Detonation, ProtocolFamily::Warfare, 768/8), //TODO: Length needs calculating dynamically
            firing_entity_id_record: EntityIDRecord::default(1),
            target_entity_id_record: EntityIDRecord::default(2),
            munition_id_record: EntityIDRecord::default(3),
            event_id_record: EventIDRecord::default(1),
            location_in_world_record: WorldCoordinateRecord::new(0.0, 0.0, 0.0),
            burst_descriptor_record: BurstDescriptorRecord::default(),
            velocity_record: VelocityVectorRecord::new(570.0, 0.0, 0.0),
            location_in_entity_coordinates_record: EntityCoordinateVectorRecord::new(0.0, 0.0, 0.0),
            detonation_result_field: DetonationResult::AirBurst,
            number_of_articulation_parameters_field:0,
            padding: 0,
            articulation_parameter_record: 0.0 
        }
    }
}

impl PDU for DetonationPDU {
    /// Fills a BytesMut struct with a FirePDU serialised into binary. This buffer is then ready to be sent via
    /// UDP to the simluation network.
    fn serialise(&self, buf: &mut BytesMut) {
        self.pdu_header_record.serialize(buf);
        self.firing_entity_id_record.serialize(buf);
        self.target_entity_id_record.serialize(buf);
        self.munition_id_record.serialize(buf);
        self.event_id_record.serialize(buf);
        self.velocity_record.serialize(buf);
        self.location_in_world_record.serialize(buf);        
        self.burst_descriptor_record.serialize(buf);
        self.location_in_entity_coordinates_record.serialize(buf);
        buf.put_u8(self.detonation_result_field as u8);
        buf.put_u8(self.number_of_articulation_parameters_field);
        buf.put_u16(self.padding);
    }

    fn deserialise(mut buffer: BytesMut) -> Result<Self, crate::common::dis_error::DISError> where Self: Sized {
        let pdu_header = PDUHeaderRecord::decode(&mut buffer);
        if pdu_header.pdu_type == PDUType::Detonation {
            let firing_entity_id = EntityIDRecord::decode(&mut buffer);
            let target_entity_id = EntityIDRecord::decode(&mut buffer);
            let munition_entity_id = EntityIDRecord::decode(&mut buffer);
            let event_id = EventIDRecord::decode(&mut buffer);
            let velocity = VelocityVectorRecord::decode(&mut buffer);
            let location = WorldCoordinateRecord::decode(&mut buffer);
            let burst_descriptor = BurstDescriptorRecord::decode(&mut buffer);
            let location_in_entity_coordinates = EntityCoordinateVectorRecord::decode(&mut buffer);
            let detonation_result = DetonationResult::from_u8(buffer.get_u8());
            let num_articulation_params = buffer.get_u8();
            let padding = buffer.get_u16();
            return Ok(DetonationPDU {
                pdu_header_record: pdu_header,
                firing_entity_id_record: firing_entity_id,
                target_entity_id_record: target_entity_id,
                munition_id_record: munition_entity_id,
                event_id_record: event_id,
                velocity_record: velocity,
                location_in_world_record: location,
                burst_descriptor_record: burst_descriptor,
                location_in_entity_coordinates_record: location_in_entity_coordinates,
                detonation_result_field: detonation_result,
                number_of_articulation_parameters_field: num_articulation_params,
                padding: padding,
                articulation_parameter_record: 0.0,
            })
        } else {
            Err(DISError::InvalidDISHeader)
        }
    }

    fn as_any(&self) -> &dyn Any {
        self
      }

    fn deserialise_without_header(mut buffer: BytesMut, pdu_header: PDUHeaderRecord) -> Result<Self, DISError> where Self: Sized {
        let firing_entity_id = EntityIDRecord::decode(&mut buffer);
            let target_entity_id = EntityIDRecord::decode(&mut buffer);
            let munition_entity_id = EntityIDRecord::decode(&mut buffer);
            let event_id = EventIDRecord::decode(&mut buffer);
            let velocity = VelocityVectorRecord::decode(&mut buffer);
            let location = WorldCoordinateRecord::decode(&mut buffer);
            let burst_descriptor = BurstDescriptorRecord::decode(&mut buffer);
            let location_in_entity_coordinates = EntityCoordinateVectorRecord::decode(&mut buffer);
            let detonation_result = DetonationResult::from_u8(buffer.get_u8());
            let num_articulation_params = buffer.get_u8();
            let padding = buffer.get_u16();
            return Ok(DetonationPDU {
                pdu_header_record: pdu_header,
                firing_entity_id_record: firing_entity_id,
                target_entity_id_record: target_entity_id,
                munition_id_record: munition_entity_id,
                event_id_record: event_id,
                velocity_record: velocity,
                location_in_world_record: location,
                burst_descriptor_record: burst_descriptor,
                location_in_entity_coordinates_record: location_in_entity_coordinates,
                detonation_result_field: detonation_result,
                number_of_articulation_parameters_field: num_articulation_params,
                padding: padding,
                articulation_parameter_record: 0.0,
            })
    }
}

#[derive(Debug, Clone, Copy)]
/// The result of the detonation. Note that damage caused by the detonation is
/// the responability of the entity being damaged.
pub enum DetonationResult {
    Other = 0,
    EntityImpact = 1,
    ArmourPiercingHit = 10,
    SmallDirtBlast = 11,
    MediumDirtBlast = 12,
    LargeDirtBlast = 13,
    SmallWaterBlast = 14,
    MediumWaterBlast = 15,
    LargeWaterBlast = 16,
    AirHit = 17,
    SmallBuildingHit = 18,
    MediumBuildingHit = 19,
    EntityProximateDetonation = 2,
    LargeBuildingHit = 20,
    MineClearingLineCharge = 21,
    EnvironmentalObjectImpact = 22,
    EnvironmentalObjectProximateDetonation = 23,
    WaterImpact = 24,
    AirBurst = 25,
    GroundImpact = 3,
    GroundImpactDetonation = 4,
    Detonation = 5,
    None = 6,
    SmallHighExplosiveHit = 7,
    MediumHighExplosiveHit = 8,
    LargeHighExplosiveHit = 9
}

impl DetonationResult {
    pub fn from_u8(value: u8) -> DetonationResult {
        match value {
            0 => DetonationResult::Other,
            1 => DetonationResult::EntityImpact,
            2 => DetonationResult::EntityProximateDetonation,
            3 => DetonationResult::GroundImpact,
            4 => DetonationResult::GroundImpactDetonation,
            5 => DetonationResult::Detonation,
            6 => DetonationResult::None,
            7 => DetonationResult::SmallHighExplosiveHit,
            8 => DetonationResult::MediumHighExplosiveHit,
            9 => DetonationResult::LargeHighExplosiveHit,
            10 => DetonationResult::ArmourPiercingHit,
            11 => DetonationResult::SmallDirtBlast,
            12 => DetonationResult::MediumDirtBlast,
            13 => DetonationResult::LargeDirtBlast,
            14 => DetonationResult::SmallWaterBlast,
            15 => DetonationResult::MediumWaterBlast,
            16 => DetonationResult::LargeWaterBlast,
            17 => DetonationResult::AirHit,
            18 => DetonationResult::SmallBuildingHit,
            19 => DetonationResult::MediumBuildingHit,
            20 => DetonationResult::LargeBuildingHit,
            21 => DetonationResult::MineClearingLineCharge,
            22 => DetonationResult::EnvironmentalObjectImpact,
            23 => DetonationResult::EntityProximateDetonation,
            24 => DetonationResult::WaterImpact,
            25 => DetonationResult::AirBurst,
            _ => DetonationResult::Other
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{common::pdu_header_record::{PDUHeaderRecord, PDUType, ProtocolFamily}, warfare::detonation_pdu::DetonationPDU};

    #[test]
    fn header_creation() {
        let detonation_pdu = DetonationPDU::default();
        let header = PDUHeaderRecord::default(PDUType::Detonation, ProtocolFamily::Warfare, 768/8);
        assert_eq!(header.protocol_version, detonation_pdu.pdu_header_record.protocol_version);
        assert_eq!(header.exercise_id, detonation_pdu.pdu_header_record.exercise_id);
        assert_eq!(header.pdu_type, detonation_pdu.pdu_header_record.pdu_type);
        assert_eq!(header.protocol_family, detonation_pdu.pdu_header_record.protocol_family);
        assert_eq!(header.timestamp, detonation_pdu.pdu_header_record.timestamp);
        assert_eq!(header.length, detonation_pdu.pdu_header_record.length);
        assert_eq!(header.padding, detonation_pdu .pdu_header_record.padding);
    }
}