diff --git a/gen/resources/frames.json b/gen/resources/frames.json index b58f27a..a659e83 100644 --- a/gen/resources/frames.json +++ b/gen/resources/frames.json @@ -75,7 +75,11 @@ { "name": "hitlag", "type": "f32", "version": "3.8" }, { "name": "animation_index", "type": "u32", - "version": "3.11" } + "version": "3.11" }, + { "name": "last_hit_by_instance", "type": "u16", + "version": "3.16" }, + { "name": "instance_id", "type": "u16", + "version": "3.16" } ], "Start": [ { "name": "random_seed", "type": "u32" }, @@ -104,6 +108,8 @@ { "name": "misc", "type": "ItemMisc", "version": "3.2" }, { "name": "owner", "type": "i8", - "version": "3.6" } + "version": "3.6" }, + { "name": "instance_id", "type": "u16", + "version": "3.16" } ] } diff --git a/src/frame/immutable/mod.rs b/src/frame/immutable/mod.rs index 9fdf9db..287cdf8 100644 --- a/src/frame/immutable/mod.rs +++ b/src/frame/immutable/mod.rs @@ -191,6 +191,7 @@ pub struct Item { pub id: PrimitiveArray, pub misc: Option, pub owner: Option>, + pub instance_id: Option>, pub validity: Option, } @@ -207,6 +208,7 @@ impl Item { id: self.id.values()[i], misc: self.misc.as_ref().map(|x| x.transpose_one(i, version)), owner: self.owner.as_ref().map(|x| x.values()[i]), + instance_id: self.instance_id.as_ref().map(|x| x.values()[i]), } } } @@ -224,6 +226,7 @@ impl From for Item { id: x.id.into(), misc: x.misc.map(|x| x.into()), owner: x.owner.map(|x| x.into()), + instance_id: x.instance_id.map(|x| x.into()), validity: x.validity.map(|v| v.into()), } } @@ -303,6 +306,8 @@ pub struct Post { pub velocities: Option, pub hitlag: Option>, pub animation_index: Option>, + pub last_hit_by_instance: Option>, + pub instance_id: Option>, pub validity: Option, } @@ -336,6 +341,8 @@ impl Post { .map(|x| x.transpose_one(i, version)), hitlag: self.hitlag.as_ref().map(|x| x.values()[i]), animation_index: self.animation_index.as_ref().map(|x| x.values()[i]), + last_hit_by_instance: self.last_hit_by_instance.as_ref().map(|x| x.values()[i]), + instance_id: self.instance_id.as_ref().map(|x| x.values()[i]), } } } @@ -364,6 +371,8 @@ impl From for Post { velocities: x.velocities.map(|x| x.into()), hitlag: x.hitlag.map(|x| x.into()), animation_index: x.animation_index.map(|x| x.into()), + last_hit_by_instance: x.last_hit_by_instance.map(|x| x.into()), + instance_id: x.instance_id.map(|x| x.into()), validity: x.validity.map(|v| v.into()), } } diff --git a/src/frame/immutable/peppi.rs b/src/frame/immutable/peppi.rs index f91ae56..e909da9 100644 --- a/src/frame/immutable/peppi.rs +++ b/src/frame/immutable/peppi.rs @@ -308,7 +308,10 @@ impl Item { if version.gte(3, 2) { fields.push(Field::new("misc", ItemMisc::data_type(version), false)); if version.gte(3, 6) { - fields.push(Field::new("owner", DataType::Int8, false)) + fields.push(Field::new("owner", DataType::Int8, false)); + if version.gte(3, 16) { + fields.push(Field::new("instance_id", DataType::UInt16, false)) + } } } }; @@ -328,7 +331,10 @@ impl Item { if version.gte(3, 2) { values.push(self.misc.unwrap().into_struct_array(version).boxed()); if version.gte(3, 6) { - values.push(self.owner.unwrap().boxed()) + values.push(self.owner.unwrap().boxed()); + if version.gte(3, 16) { + values.push(self.instance_id.unwrap().boxed()) + } } }; StructArray::new(Self::data_type(version), values, self.validity) @@ -395,6 +401,12 @@ impl Item { .unwrap() .clone() }), + instance_id: values.get(10).map(|x| { + x.as_any() + .downcast_ref::>() + .unwrap() + .clone() + }), validity: validity, } } @@ -531,7 +543,19 @@ impl Post { "animation_index", DataType::UInt32, false, - )) + )); + if version.gte(3, 16) { + fields.push(Field::new( + "last_hit_by_instance", + DataType::UInt16, + false, + )); + fields.push(Field::new( + "instance_id", + DataType::UInt16, + false, + )) + } } } } @@ -570,7 +594,11 @@ impl Post { if version.gte(3, 8) { values.push(self.hitlag.unwrap().boxed()); if version.gte(3, 11) { - values.push(self.animation_index.unwrap().boxed()) + values.push(self.animation_index.unwrap().boxed()); + if version.gte(3, 16) { + values.push(self.last_hit_by_instance.unwrap().boxed()); + values.push(self.instance_id.unwrap().boxed()) + } } } } @@ -702,6 +730,18 @@ impl Post { .unwrap() .clone() }), + last_hit_by_instance: values.get(21).map(|x| { + x.as_any() + .downcast_ref::>() + .unwrap() + .clone() + }), + instance_id: values.get(22).map(|x| { + x.as_any() + .downcast_ref::>() + .unwrap() + .clone() + }), validity: validity, } } diff --git a/src/frame/immutable/slippi.rs b/src/frame/immutable/slippi.rs index c13e8b9..9e0f2a4 100644 --- a/src/frame/immutable/slippi.rs +++ b/src/frame/immutable/slippi.rs @@ -201,7 +201,10 @@ impl Item { if version.gte(3, 2) { self.misc.as_ref().unwrap().write(w, version, i)?; if version.gte(3, 6) { - w.write_i8(self.owner.as_ref().unwrap().value(i))? + w.write_i8(self.owner.as_ref().unwrap().value(i))?; + if version.gte(3, 16) { + w.write_u16::(self.instance_id.as_ref().unwrap().value(i))? + } } }; Ok(()) @@ -220,7 +223,10 @@ impl Item { if version.gte(3, 2) { size += ItemMisc::size(version); if version.gte(3, 6) { - size += size_of::() + size += size_of::(); + if version.gte(3, 16) { + size += size_of::() + } } }; size @@ -295,7 +301,13 @@ impl Post { if version.gte(3, 8) { w.write_f32::(self.hitlag.as_ref().unwrap().value(i))?; if version.gte(3, 11) { - w.write_u32::(self.animation_index.as_ref().unwrap().value(i))? + w.write_u32::(self.animation_index.as_ref().unwrap().value(i))?; + if version.gte(3, 16) { + w.write_u16::( + self.last_hit_by_instance.as_ref().unwrap().value(i), + )?; + w.write_u16::(self.instance_id.as_ref().unwrap().value(i))? + } } } } @@ -333,7 +345,11 @@ impl Post { if version.gte(3, 8) { size += size_of::(); if version.gte(3, 11) { - size += size_of::() + size += size_of::(); + if version.gte(3, 16) { + size += size_of::(); + size += size_of::() + } } } } diff --git a/src/frame/mutable.rs b/src/frame/mutable.rs index fb7c0c6..6c40bba 100644 --- a/src/frame/mutable.rs +++ b/src/frame/mutable.rs @@ -206,6 +206,7 @@ pub struct Item { pub id: MutablePrimitiveArray, pub misc: Option, pub owner: Option>, + pub instance_id: Option>, pub validity: Option, } @@ -226,6 +227,9 @@ impl Item { owner: version .gte(3, 6) .then(|| MutablePrimitiveArray::::with_capacity(capacity)), + instance_id: version + .gte(3, 16) + .then(|| MutablePrimitiveArray::::with_capacity(capacity)), validity: None, } } @@ -250,7 +254,10 @@ impl Item { if version.gte(3, 2) { self.misc.as_mut().unwrap().push_null(version); if version.gte(3, 6) { - self.owner.as_mut().unwrap().push_null() + self.owner.as_mut().unwrap().push_null(); + if version.gte(3, 16) { + self.instance_id.as_mut().unwrap().push_null() + } } } } @@ -268,7 +275,11 @@ impl Item { self.misc.as_mut().unwrap().read_push(r, version)?; if version.gte(3, 6) { r.read_i8() - .map(|x| self.owner.as_mut().unwrap().push(Some(x)))? + .map(|x| self.owner.as_mut().unwrap().push(Some(x)))?; + if version.gte(3, 16) { + r.read_u16::() + .map(|x| self.instance_id.as_mut().unwrap().push(Some(x)))? + } } }; self.validity.as_mut().map(|v| v.push(true)); @@ -287,6 +298,7 @@ impl Item { id: self.id.values()[i], misc: self.misc.as_ref().map(|x| x.transpose_one(i, version)), owner: self.owner.as_ref().map(|x| x.values()[i]), + instance_id: self.instance_id.as_ref().map(|x| x.values()[i]), } } } @@ -402,6 +414,8 @@ pub struct Post { pub velocities: Option, pub hitlag: Option>, pub animation_index: Option>, + pub last_hit_by_instance: Option>, + pub instance_id: Option>, pub validity: Option, } @@ -451,6 +465,12 @@ impl Post { animation_index: version .gte(3, 11) .then(|| MutablePrimitiveArray::::with_capacity(capacity)), + last_hit_by_instance: version + .gte(3, 16) + .then(|| MutablePrimitiveArray::::with_capacity(capacity)), + instance_id: version + .gte(3, 16) + .then(|| MutablePrimitiveArray::::with_capacity(capacity)), validity: None, } } @@ -490,7 +510,11 @@ impl Post { if version.gte(3, 8) { self.hitlag.as_mut().unwrap().push_null(); if version.gte(3, 11) { - self.animation_index.as_mut().unwrap().push_null() + self.animation_index.as_mut().unwrap().push_null(); + if version.gte(3, 16) { + self.last_hit_by_instance.as_mut().unwrap().push_null(); + self.instance_id.as_mut().unwrap().push_null() + } } } } @@ -534,8 +558,16 @@ impl Post { r.read_f32::() .map(|x| self.hitlag.as_mut().unwrap().push(Some(x)))?; if version.gte(3, 11) { - r.read_u32::() - .map(|x| self.animation_index.as_mut().unwrap().push(Some(x)))? + r.read_u32::().map(|x| { + self.animation_index.as_mut().unwrap().push(Some(x)) + })?; + if version.gte(3, 16) { + r.read_u16::().map(|x| { + self.last_hit_by_instance.as_mut().unwrap().push(Some(x)) + })?; + r.read_u16::() + .map(|x| self.instance_id.as_mut().unwrap().push(Some(x)))? + } } } } @@ -575,6 +607,8 @@ impl Post { .map(|x| x.transpose_one(i, version)), hitlag: self.hitlag.as_ref().map(|x| x.values()[i]), animation_index: self.animation_index.as_ref().map(|x| x.values()[i]), + last_hit_by_instance: self.last_hit_by_instance.as_ref().map(|x| x.values()[i]), + instance_id: self.instance_id.as_ref().map(|x| x.values()[i]), } } } diff --git a/src/frame/transpose.rs b/src/frame/transpose.rs index cc4857e..b0c3df8 100644 --- a/src/frame/transpose.rs +++ b/src/frame/transpose.rs @@ -41,6 +41,7 @@ pub struct Item { pub id: u32, pub misc: Option, pub owner: Option, + pub instance_id: Option, } #[derive(PartialEq, Clone, Copy, Debug, Default)] @@ -75,6 +76,8 @@ pub struct Post { pub velocities: Option, pub hitlag: Option, pub animation_index: Option, + pub last_hit_by_instance: Option, + pub instance_id: Option, } #[derive(PartialEq, Clone, Copy, Debug, Default)] diff --git a/src/io/slippi/mod.rs b/src/io/slippi/mod.rs index ba780ac..0dcce05 100644 --- a/src/io/slippi/mod.rs +++ b/src/io/slippi/mod.rs @@ -12,7 +12,7 @@ pub use ser::write; /// We can read replays with higher versions than this, but that discards information. /// We don't support writing these replays as a result, though this restriction may be /// relaxed in the future. -pub const MAX_SUPPORTED_VERSION: Version = Version(3, 15, 0); +pub const MAX_SUPPORTED_VERSION: Version = Version(3, 16, 0); /// Every .slp file will start with a UBJSON opening brace, `raw` key & type: "{U\x03raw[$U#l" pub const FILE_SIGNATURE: [u8; 11] = [ diff --git a/tests/data/v3.16.slp b/tests/data/v3.16.slp new file mode 100644 index 0000000..794f04c Binary files /dev/null and b/tests/data/v3.16.slp differ diff --git a/tests/peppi.rs b/tests/peppi.rs index b420605..ad3083a 100644 --- a/tests/peppi.rs +++ b/tests/peppi.rs @@ -1,4 +1,4 @@ -use std::{fs, io::Cursor, path::Path}; +use std::{collections::HashSet, fs, io::Cursor, path::Path}; use pretty_assertions::assert_eq; use serde_json::json; @@ -11,7 +11,7 @@ use peppi::{ Rollbacks, }, game::{ - immutable::Game, shift_jis::MeleeString, Bytes, DashBack, End, EndMethod, Language, + immutable::Game, shift_jis::MeleeString, Bytes, DashBack, End, EndMethod, Language, Match, Netplay, Player, PlayerEnd, PlayerType, Port, Scene, ShieldDrop, Start, Ucf, }, io::{ @@ -659,6 +659,78 @@ fn v3_13() { ); } +#[test] +fn v3_14() { + let game = game("v3.16"); + assert_eq!( + game.start.r#match, + Some(Match { + id: String::from("mode.unranked-2024-02-15T14:37:23.22-0"), + game: 1, + tiebreaker: 0 + }) + ) +} + +#[test] +fn v3_15() { + let game = game("v3.16"); + assert_eq!( + game.frames + .transpose_one(200, game.start.slippi.version) + .ports[0] + .leader + .pre + .raw_analog_y, + Some(-21) + ) +} + +#[test] +fn v3_16() { + let game = game("v3.16"); + let player1_ids = game.frames.ports[0] + .leader + .post + .instance_id + .as_ref() + .unwrap(); + let player2_ids = game.frames.ports[1] + .leader + .post + .instance_id + .as_ref() + .unwrap(); + let mut id_set: HashSet = player1_ids.values_iter().cloned().collect(); + id_set.remove(&0); + + // player1 and player2 ids should not share any instance ids + assert!(player2_ids.values_iter().all(|id| !id_set.contains(id))); + + let player2_hit_bys = game.frames.ports[1] + .leader + .post + .last_hit_by_instance + .as_ref() + .unwrap(); + + // player2 should be hit by player1 ids + assert!(player2_hit_bys + .values_iter() + .all(|id| *id == 0 || id_set.contains(id))); + + let items = game.frames.item.as_ref().unwrap(); + let item_instance_ids = items.instance_id.as_ref().unwrap(); + let item_types = &items.r#type; + + // Shy guy (210) should have instance id of 0 + // The laser/gun should share instance id with the Fox player (P1) + assert!(item_instance_ids + .values_iter() + .zip(item_types.values_iter()) + .all(|(&id, &r#type)| (r#type == 210 && id == 0) || id_set.contains(&id))); +} + #[test] fn unknown_event() { // shouldn't panic @@ -701,6 +773,7 @@ fn items() { velocity: transpose::Velocity { x: 0.0, y: 0.0 }, misc: Some(transpose::ItemMisc(5, 5, 5, 5)), owner: Some(0), + instance_id: None, }]) ); assert_eq!( @@ -721,6 +794,7 @@ fn items() { velocity: transpose::Velocity { x: 0.0, y: 0.0 }, misc: Some(transpose::ItemMisc(5, 0, 5, 5)), owner: Some(0), + instance_id: None, }]) ); assert_eq!( @@ -741,6 +815,7 @@ fn items() { velocity: transpose::Velocity { x: 0.0, y: 0.0 }, misc: Some(transpose::ItemMisc(5, 0, 5, 5)), owner: Some(0), + instance_id: None, }]) ); }