use crate::vpx::biff;
use crate::vpx::biff::{BiffRead, BiffReader, BiffWrite};
use crate::vpx::gameitem::select::TimerData;
use crate::vpx::gameitem::vertex2d::Vertex2D;
use log::warn;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(test, derive(fake::Dummy))]
pub enum VisibilityMask {
Playfield = 0x0001,
Scoreview = 0x0002,
Backglass = 0x0004,
Topper = 0x0008,
ApronLeft = 0x0010,
ApronRight = 0x0020,
MixedReality = 0x0040,
VirtualReality = 0x0080,
}
impl From<u32> for VisibilityMask {
fn from(value: u32) -> Self {
match value {
0x0001 => VisibilityMask::Playfield,
0x0002 => VisibilityMask::Scoreview,
0x0004 => VisibilityMask::Backglass,
0x0008 => VisibilityMask::Topper,
0x0010 => VisibilityMask::ApronLeft,
0x0020 => VisibilityMask::ApronRight,
0x0040 => VisibilityMask::MixedReality,
0x0080 => VisibilityMask::VirtualReality,
_ => panic!("Unknown visibility mask value: {value}"),
}
}
}
impl From<VisibilityMask> for u32 {
fn from(value: VisibilityMask) -> Self {
match value {
VisibilityMask::Playfield => 0x0001,
VisibilityMask::Scoreview => 0x0002,
VisibilityMask::Backglass => 0x0004,
VisibilityMask::Topper => 0x0008,
VisibilityMask::ApronLeft => 0x0010,
VisibilityMask::ApronRight => 0x0020,
VisibilityMask::MixedReality => 0x0040,
VisibilityMask::VirtualReality => 0x0080,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(test, derive(fake::Dummy))]
pub enum SpaceReference {
Inherit,
Room,
CabinetFeet,
Cabinet,
Playfield,
}
impl From<u32> for SpaceReference {
fn from(value: u32) -> Self {
match value {
0 => SpaceReference::Playfield,
1 => SpaceReference::Cabinet,
2 => SpaceReference::CabinetFeet,
3 => SpaceReference::Room,
4 => SpaceReference::Inherit,
_ => panic!("Unknown space reference value: {value}"),
}
}
}
impl From<&SpaceReference> for u32 {
fn from(value: &SpaceReference) -> Self {
match value {
SpaceReference::Playfield => 0,
SpaceReference::Cabinet => 1,
SpaceReference::CabinetFeet => 2,
SpaceReference::Room => 3,
SpaceReference::Inherit => 4,
}
}
}
impl Serialize for SpaceReference {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let value = match self {
SpaceReference::Inherit => "inherit",
SpaceReference::Room => "room",
SpaceReference::CabinetFeet => "cabinet_feet",
SpaceReference::Cabinet => "cabinet",
SpaceReference::Playfield => "playfield",
};
serializer.serialize_str(value)
}
}
impl<'de> Deserialize<'de> for SpaceReference {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
match value.as_str() {
"inherit" => Ok(SpaceReference::Inherit),
"room" => Ok(SpaceReference::Room),
"cabinet_feet" => Ok(SpaceReference::CabinetFeet),
"cabinet" => Ok(SpaceReference::Cabinet),
"playfield" => Ok(SpaceReference::Playfield),
_ => Err(serde::de::Error::custom(format!(
"Unknown space reference value: {value}"
))),
}
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(test, derive(fake::Dummy))]
pub struct PartGroup {
pub name: String,
pub center: Vertex2D,
pub timer: TimerData,
pub backglass: bool,
pub visibility_mask: Option<u32>,
pub space_reference: SpaceReference,
pub player_mode_visibility_mask: Option<u32>,
pub is_locked: bool,
pub editor_layer_name: Option<String>,
pub editor_layer_visibility: Option<bool>,
}
impl Default for PartGroup {
fn default() -> Self {
PartGroup {
name: Default::default(),
center: Vertex2D::default(),
timer: TimerData::default(),
backglass: false,
visibility_mask: None,
space_reference: SpaceReference::Inherit,
player_mode_visibility_mask: None,
is_locked: false,
editor_layer_name: None,
editor_layer_visibility: None,
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct PartGroupJson {
name: String,
center: Vertex2D,
#[serde(flatten)]
pub timer: TimerData,
backglass: bool,
#[serde(skip_serializing_if = "Option::is_none")]
visibility_mask: Option<u32>,
space_reference: SpaceReference,
#[serde(skip_serializing_if = "Option::is_none")]
player_mode_visibility_mask: Option<u32>,
is_locked: bool,
editor_layer_name: Option<String>,
editor_layer_visibility: Option<bool>,
}
impl PartGroupJson {
pub fn from_part_group(part_group: &PartGroup) -> Self {
PartGroupJson {
name: part_group.name.clone(),
center: part_group.center,
timer: part_group.timer.clone(),
backglass: part_group.backglass,
visibility_mask: part_group.visibility_mask,
space_reference: part_group.space_reference.clone(),
player_mode_visibility_mask: part_group.player_mode_visibility_mask,
is_locked: part_group.is_locked,
editor_layer_name: part_group.editor_layer_name.clone(),
editor_layer_visibility: part_group.editor_layer_visibility,
}
}
pub fn to_part_group(&self) -> PartGroup {
PartGroup {
name: self.name.clone(),
center: self.center,
timer: self.timer.clone(),
backglass: self.backglass,
visibility_mask: self.visibility_mask,
space_reference: self.space_reference.clone(),
player_mode_visibility_mask: self.player_mode_visibility_mask,
is_locked: self.is_locked,
editor_layer_name: self.editor_layer_name.clone(),
editor_layer_visibility: self.editor_layer_visibility,
}
}
}
impl Serialize for PartGroup {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let part_group_json = PartGroupJson::from_part_group(self);
part_group_json.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for PartGroup {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let part_group_json = PartGroupJson::deserialize(deserializer)?;
Ok(part_group_json.to_part_group())
}
}
impl BiffRead for PartGroup {
fn biff_read(reader: &mut BiffReader<'_>) -> Self {
let mut part_group = PartGroup::default();
loop {
reader.next(biff::WARN);
if reader.is_eof() {
break;
}
let tag = reader.tag();
let tag_str = tag.as_str();
match tag_str {
"NAME" => part_group.name = reader.get_wide_string(),
"VCEN" => part_group.center = Vertex2D::biff_read(reader),
"BGLS" => {
part_group.backglass = reader.get_bool();
}
"VMSK" => {
part_group.visibility_mask = Some(reader.get_u32());
}
"SPRF" => {
part_group.space_reference = reader.get_u32().into();
}
"PMSK" => {
part_group.player_mode_visibility_mask = Some(reader.get_u32());
}
"LOCK" => {
part_group.is_locked = reader.get_bool();
}
"LANR" => {
part_group.editor_layer_name = Some(reader.get_string());
}
"LVIS" => {
part_group.editor_layer_visibility = Some(reader.get_bool());
}
_ => {
if !part_group.timer.biff_read_tag(tag_str, reader) {
warn!(
"Unknown tag {} for {}",
tag_str,
std::any::type_name::<Self>()
);
reader.skip_tag();
}
}
}
}
part_group
}
}
impl BiffWrite for PartGroup {
fn biff_write(&self, writer: &mut biff::BiffWriter) {
writer.write_tagged_wide_string("NAME", &self.name);
writer.write_tagged("VCEN", &self.center);
self.timer.biff_write(writer);
writer.write_tagged_bool("BGLS", self.backglass);
if let Some(vmsk) = self.visibility_mask {
writer.write_tagged_u32("VMSK", vmsk);
}
if let Some(pmsk) = self.player_mode_visibility_mask {
writer.write_tagged_u32("PMSK", pmsk);
}
writer.write_tagged_u32("SPRF", (&self.space_reference).into());
writer.write_tagged_bool("LOCK", self.is_locked);
if let Some(editor_layer_visibility) = self.editor_layer_visibility {
writer.write_tagged_bool("LVIS", editor_layer_visibility);
}
if let Some(editor_layer_name) = &self.editor_layer_name {
writer.write_tagged_string("LANR", editor_layer_name);
}
writer.close(true);
}
}
#[cfg(test)]
mod tests {
use crate::vpx::biff::BiffWriter;
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_write_read() {
let part_group = PartGroup {
name: "Test".to_string(),
center: Vertex2D::new(1.0, 2.0),
timer: TimerData {
is_enabled: true,
interval: 1000,
},
backglass: true,
visibility_mask: Some(VisibilityMask::Playfield.into()),
space_reference: SpaceReference::Cabinet,
player_mode_visibility_mask: Some(0x00FF),
is_locked: true,
editor_layer_name: Some("Layer 1".to_string()),
editor_layer_visibility: Some(true),
};
let mut writer = BiffWriter::new();
PartGroup::biff_write(&part_group, &mut writer);
let gate_read = PartGroup::biff_read(&mut BiffReader::new(writer.get_data()));
assert_eq!(part_group, gate_read);
}
}