use super::dragpoint::DragPoint;
use crate::impl_shared_attributes;
use crate::vpx::biff::{self, BiffRead, BiffReader, BiffWrite};
use crate::vpx::gameitem::select::{TimerData, WriteSharedAttributes};
use log::warn;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, PartialEq)]
#[cfg_attr(test, derive(fake::Dummy))]
pub struct Rubber {
pub height: f32,
pub hit_height: Option<f32>, pub thickness: i32,
pub hit_event: bool,
pub material: String,
pub name: String,
pub image: String,
pub elasticity: f32,
pub elasticity_falloff: f32,
pub friction: f32,
pub scatter: f32,
pub is_collidable: bool,
pub is_visible: bool,
pub radb: Option<f32>, pub static_rendering: bool,
pub show_in_editor: bool,
pub rot_x: f32,
pub rot_y: f32,
pub rot_z: f32,
pub is_reflection_enabled: Option<bool>,
pub physics_material: Option<String>, pub overwrite_physics: Option<bool>,
pub timer: TimerData,
pub is_locked: bool,
pub editor_layer: Option<u32>,
pub editor_layer_name: Option<String>,
pub editor_layer_visibility: Option<bool>,
pub part_group_name: Option<String>,
pub drag_points: Vec<DragPoint>,
}
impl_shared_attributes!(Rubber);
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct RubberJson {
height: f32,
hit_height: Option<f32>,
thickness: i32,
hit_event: bool,
material: String,
#[serde(flatten)]
pub timer: TimerData,
name: String,
image: String,
elasticity: f32,
elasticity_falloff: f32,
friction: f32,
scatter: f32,
is_collidable: bool,
is_visible: bool,
radb: Option<f32>,
static_rendering: bool,
show_in_editor: bool,
rot_x: f32,
rot_y: f32,
rot_z: f32,
is_reflection_enabled: Option<bool>,
physics_material: Option<String>,
overwrite_physics: Option<bool>,
drag_points: Vec<DragPoint>,
#[serde(skip_serializing_if = "Option::is_none")]
part_group_name: Option<String>,
}
impl RubberJson {
fn from_rubber(rubber: &Rubber) -> Self {
RubberJson {
height: rubber.height,
hit_height: rubber.hit_height,
thickness: rubber.thickness,
hit_event: rubber.hit_event,
material: rubber.material.clone(),
timer: rubber.timer.clone(),
name: rubber.name.clone(),
image: rubber.image.clone(),
elasticity: rubber.elasticity,
elasticity_falloff: rubber.elasticity_falloff,
friction: rubber.friction,
scatter: rubber.scatter,
is_collidable: rubber.is_collidable,
is_visible: rubber.is_visible,
radb: rubber.radb,
static_rendering: rubber.static_rendering,
show_in_editor: rubber.show_in_editor,
rot_x: rubber.rot_x,
rot_y: rubber.rot_y,
rot_z: rubber.rot_z,
is_reflection_enabled: rubber.is_reflection_enabled,
physics_material: rubber.physics_material.clone(),
overwrite_physics: rubber.overwrite_physics,
part_group_name: rubber.part_group_name.clone(),
drag_points: rubber.drag_points.clone(),
}
}
fn to_rubber(&self) -> Rubber {
Rubber {
height: self.height,
hit_height: self.hit_height,
thickness: self.thickness,
hit_event: self.hit_event,
material: self.material.clone(),
timer: self.timer.clone(),
name: self.name.clone(),
image: self.image.clone(),
elasticity: self.elasticity,
elasticity_falloff: self.elasticity_falloff,
friction: self.friction,
scatter: self.scatter,
is_collidable: self.is_collidable,
is_visible: self.is_visible,
radb: self.radb,
static_rendering: self.static_rendering,
show_in_editor: self.show_in_editor,
rot_x: self.rot_x,
rot_y: self.rot_y,
rot_z: self.rot_z,
is_reflection_enabled: self.is_reflection_enabled,
physics_material: self.physics_material.clone(),
overwrite_physics: self.overwrite_physics,
is_locked: false,
editor_layer: None,
editor_layer_name: None,
editor_layer_visibility: None,
part_group_name: self.part_group_name.clone(),
drag_points: self.drag_points.clone(),
}
}
}
impl Default for Rubber {
fn default() -> Self {
let height: f32 = 25.0;
let hit_height: Option<f32> = None; let thickness: i32 = 8;
let hit_event: bool = false;
let material: String = Default::default();
let timer = TimerData::default();
let name: String = Default::default();
let image: String = Default::default();
let elasticity: f32 = Default::default();
let elasticity_falloff: f32 = Default::default();
let friction: f32 = Default::default();
let scatter: f32 = Default::default();
let is_collidable: bool = true;
let is_visible: bool = true;
let radb: Option<f32> = None; let static_rendering: bool = true;
let show_in_editor: bool = true;
let rot_x: f32 = 0.0;
let rot_y: f32 = 0.0;
let rot_z: f32 = 0.0;
let is_reflection_enabled: Option<bool> = None; let physics_material: Option<String> = None;
let overwrite_physics: Option<bool> = None;
let is_locked: bool = false;
let editor_layer: Option<u32> = None;
let editor_layer_name: Option<String> = None;
let editor_layer_visibility: Option<bool> = None;
let points: Vec<DragPoint> = Default::default();
Rubber {
height,
hit_height,
thickness,
hit_event,
material,
timer,
name,
image,
elasticity,
elasticity_falloff,
friction,
scatter,
is_collidable,
is_visible,
radb,
static_rendering,
show_in_editor,
rot_x,
rot_y,
rot_z,
is_reflection_enabled,
physics_material,
overwrite_physics,
is_locked,
editor_layer,
editor_layer_name,
editor_layer_visibility,
part_group_name: None,
drag_points: points,
}
}
}
impl Serialize for Rubber {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
RubberJson::from_rubber(self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Rubber {
fn deserialize<D>(deserializer: D) -> Result<Rubber, D::Error>
where
D: Deserializer<'de>,
{
let rubber_json = RubberJson::deserialize(deserializer)?;
Ok(rubber_json.to_rubber())
}
}
impl BiffRead for Rubber {
fn biff_read(reader: &mut BiffReader<'_>) -> Self {
let mut rubber = Rubber::default();
loop {
reader.next(biff::WARN);
if reader.is_eof() {
break;
}
let tag = reader.tag();
let tag_str = tag.as_str();
match tag_str {
"HTTP" => {
rubber.height = reader.get_f32();
}
"HTHI" => {
rubber.hit_height = Some(reader.get_f32());
}
"WDTP" => {
rubber.thickness = reader.get_i32();
}
"HTEV" => {
rubber.hit_event = reader.get_bool();
}
"MATR" => {
rubber.material = reader.get_string();
}
"NAME" => {
rubber.name = reader.get_wide_string();
}
"IMAG" => {
rubber.image = reader.get_string();
}
"ELAS" => {
rubber.elasticity = reader.get_f32();
}
"ELFO" => {
rubber.elasticity_falloff = reader.get_f32();
}
"RFCT" => {
rubber.friction = reader.get_f32();
}
"RSCT" => {
rubber.scatter = reader.get_f32();
}
"CLDR" => {
rubber.is_collidable = reader.get_bool();
}
"RVIS" => {
rubber.is_visible = reader.get_bool();
}
"RADB" => {
rubber.radb = Some(reader.get_f32());
}
"ESTR" => {
rubber.static_rendering = reader.get_bool();
}
"ESIE" => {
rubber.show_in_editor = reader.get_bool();
}
"ROTX" => {
rubber.rot_x = reader.get_f32();
}
"ROTY" => {
rubber.rot_y = reader.get_f32();
}
"ROTZ" => {
rubber.rot_z = reader.get_f32();
}
"REEN" => {
rubber.is_reflection_enabled = Some(reader.get_bool());
}
"MAPH" => {
rubber.physics_material = Some(reader.get_string());
}
"OVPH" => {
rubber.overwrite_physics = Some(reader.get_bool());
}
"PNTS" => {
}
"DPNT" => {
let point = DragPoint::biff_read(reader);
rubber.drag_points.push(point);
}
_ => {
if !rubber.timer.biff_read_tag(tag_str, reader)
&& !rubber.read_shared_attribute(tag_str, reader)
{
warn!(
"Unknown tag {} for {}",
tag_str,
std::any::type_name::<Self>()
);
reader.skip_tag();
}
}
}
}
rubber
}
}
impl BiffWrite for Rubber {
fn biff_write(&self, writer: &mut biff::BiffWriter) {
writer.write_tagged_f32("HTTP", self.height);
if let Some(hthi) = self.hit_height {
writer.write_tagged_f32("HTHI", hthi);
}
writer.write_tagged_i32("WDTP", self.thickness);
writer.write_tagged_bool("HTEV", self.hit_event);
writer.write_tagged_string("MATR", &self.material);
self.timer.biff_write(writer);
writer.write_tagged_wide_string("NAME", &self.name);
writer.write_tagged_string("IMAG", &self.image);
writer.write_tagged_f32("ELAS", self.elasticity);
writer.write_tagged_f32("ELFO", self.elasticity_falloff);
writer.write_tagged_f32("RFCT", self.friction);
writer.write_tagged_f32("RSCT", self.scatter);
writer.write_tagged_bool("CLDR", self.is_collidable);
writer.write_tagged_bool("RVIS", self.is_visible);
if let Some(radb) = self.radb {
writer.write_tagged_f32("RADB", radb);
}
writer.write_tagged_bool("ESTR", self.static_rendering);
writer.write_tagged_bool("ESIE", self.show_in_editor);
writer.write_tagged_f32("ROTX", self.rot_x);
writer.write_tagged_f32("ROTY", self.rot_y);
writer.write_tagged_f32("ROTZ", self.rot_z);
if let Some(is_reflection_enabled) = self.is_reflection_enabled {
writer.write_tagged_bool("REEN", is_reflection_enabled);
}
if let Some(physics_material) = &self.physics_material {
writer.write_tagged_string("MAPH", physics_material);
}
if let Some(overwrite_physics) = self.overwrite_physics {
writer.write_tagged_bool("OVPH", overwrite_physics);
}
self.write_shared_attributes(writer);
writer.write_marker_tag("PNTS");
for point in &self.drag_points {
writer.write_tagged("DPNT", point)
}
writer.close(true);
}
}
#[cfg(test)]
mod tests {
use crate::vpx::biff::BiffWriter;
use super::*;
use crate::vpx::gameitem::tests::RandomOption;
use pretty_assertions::assert_eq;
use rand::RngExt;
#[test]
fn test_write_read() {
let mut rng = rand::rng();
let rubber: Rubber = Rubber {
height: 1.0,
hit_height: Some(2.0),
thickness: 3,
hit_event: rng.random(),
material: "material".to_string(),
timer: TimerData {
is_enabled: rng.random(),
interval: rng.random(),
},
name: "name".to_string(),
image: "image".to_string(),
elasticity: 5.0,
elasticity_falloff: 6.0,
friction: 7.0,
scatter: 8.0,
is_collidable: rng.random(),
is_visible: rng.random(),
radb: Some(9.0),
static_rendering: rng.random(),
show_in_editor: rng.random(),
rot_x: 9.0,
rot_y: 10.0,
rot_z: 11.0,
is_reflection_enabled: rng.random_option(),
physics_material: Some("physics_material".to_string()),
overwrite_physics: rng.random_option(),
is_locked: rng.random(),
editor_layer: Some(12),
editor_layer_name: Some("editor_layer_name".to_string()),
editor_layer_visibility: rng.random_option(),
part_group_name: Some("part_group_name".to_string()),
drag_points: vec![DragPoint::default()],
};
let mut writer = BiffWriter::new();
Rubber::biff_write(&rubber, &mut writer);
let rubber_read = Rubber::biff_read(&mut BiffReader::new(writer.get_data()));
assert_eq!(rubber, rubber_read);
}
}