peppi 1.0.0-alpha

Parser for Slippi replay files
Documentation
use serde::{
	Serialize,
	ser::SerializeStruct,
};

use super::{
	action_state,
	attack,
	buttons,
	character,
	item,
	primitives::{Direction, Port, Position, Velocity},
	triggers,
};

frame_data!(Buttons {
	logical: buttons::Logical,
	physical: buttons::Physical,
}, { });

frame_data!(Triggers {
	logical: triggers::Logical,
	physical: triggers::Physical,
}, { });

pseudo_bitmask!(StateFlags: u64 {
	1u64 << 04 => REFLECT,
	1u64 << 10 => UNTOUCHABLE,
	1u64 << 11 => FAST_FALL,
	1u64 << 13 => HIT_LAG,
	1u64 << 23 => SHIELD,
	1u64 << 25 => HIT_STUN,
	1u64 << 26 => SHIELD_TOUCH,
	1u64 << 29 => POWER_SHIELD,
	1u64 << 35 => FOLLOWER,
	1u64 << 36 => SLEEP,
	1u64 << 38 => DEAD,
	1u64 << 39 => OFF_SCREEN,
});

pseudo_enum!(HurtboxState: u8 {
	0 => VULNERABLE,
	1 => INVULNERABLE,
	2 => INTANGIBLE,
});

frame_data!(Start {
	random_seed: u32,
}, { });

frame_data!(End {
}, {
	latest_finalized_frame: i32,
});

frame_data!(Velocities {
	autogenous: Velocity,
	knockback: Velocity,
}, { });

frame_data!(Pre {
	position: Position,
	direction: Direction,
	joystick: Position,
	cstick: Position,
	triggers: Triggers,
	random_seed: u32,
	buttons: Buttons,
	state: action_state::State,
}, {
	// v1.2
	raw_analog_x: u8,
	// v1.4
	damage: f32,
});

frame_data!(Post {
	position: Position,
	direction: Direction,
	damage: f32,
	shield: f32,
	state: action_state::State,
	character: character::Internal,
	last_attack_landed: Option<attack::Attack>,
	combo_count: u8,
	last_hit_by: Option<Port>,
	stocks: u8,
}, {
	// v0.2
	state_age: f32,
	// v2.0
	flags: StateFlags,
	misc_as: f32,
	airborne: bool,
	ground: u16,
	jumps: u8,
	l_cancel: Option<bool>,
	// v2.1
	hurtbox_state: HurtboxState,
	// v3.5
	velocities: Velocities,
	// v3.8
	hitlag: f32,
});

frame_data!(Data {
	pre: Pre,
	post: Post,
}, { });

#[derive(Debug, PartialEq, Serialize)]
pub struct PortData {
	pub leader: Data,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub follower: Option<Box<Data>>,
}

frame_data!(Item {
	id: u32,
	r#type: item::Item,
	state: u8,
	direction: Direction,
	position: Position,
	velocity: Velocity,
	damage: u16,
	timer: f32,
}, {
	// v3.2
	misc: [u8; 4],
	// v3.6
	owner: Option<Port>,
});

#[derive(Debug, PartialEq)]
pub struct Frame<const N: usize> {
	pub ports: [PortData; N],
	pub start: Option<Start>,
	pub end: Option<End>,
	pub items: Option<Vec<Item>>,
}

// workaround for Serde not supporting const generics
impl<const N: usize> Serialize for Frame<N> {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
		let mut state = serializer.serialize_struct("Frame", 1)?;

		if let Some(start) = self.start {
			state.serialize_field("start", &start)?;
		}

		if let Some(end) = self.end {
			state.serialize_field("end", &end)?;
		}

		state.serialize_field("ports", &self.ports[..])?;

		if let Some(items) = &self.items {
			state.serialize_field("items", &items)?;
		}

		state.end()
	}
}

pub type Frame1 = Frame<1>;
pub type Frame2 = Frame<2>;
pub type Frame3 = Frame<3>;
pub type Frame4 = Frame<4>;