1use ordered_hash_map::OrderedHashMap;
4
5use crate::lua::LuaObject;
6use crate::version::Version;
7
8use std::collections::HashMap;
9
10#[derive(Debug)]
12pub struct Replay<V: Version> {
13 pub header: ReplayHeader,
14 pub body: ReplayBody<V>,
15}
16
17impl<V: Version> Clone for Replay<V> {
19 #[inline]
20 fn clone(&self) -> Self {
21 Self {
22 header: self.header.clone(),
23 body: self.body.clone(),
24 }
25 }
26}
27
28impl<V: Version> PartialEq for Replay<V> {
30 #[inline]
31 fn eq(&self, other: &Replay<V>) -> bool {
32 self.header == other.header && self.body == other.body
33 }
34}
35
36#[derive(Clone, Debug, PartialEq)]
38pub struct ReplayHeader {
39 pub scfa_version: String,
40 pub replay_version: String,
41 pub map_file: String,
42 pub mods: LuaObject,
43 pub scenario: LuaObject,
44 pub players: OrderedHashMap<String, i32>,
45 pub cheats_enabled: bool,
46 pub armies: OrderedHashMap<u8, LuaObject>,
47 pub seed: u32,
48}
49
50#[derive(Debug)]
53pub struct ReplayBody<V: Version> {
54 pub commands: Vec<V::Command>,
55 pub sim: SimData,
56}
57
58impl<V: Version> Clone for ReplayBody<V> {
60 fn clone(&self) -> Self {
61 Self {
62 commands: self.commands.clone(),
63 sim: self.sim.clone(),
64 }
65 }
66}
67
68impl<V: Version> PartialEq for ReplayBody<V> {
70 #[inline]
71 fn eq(&self, other: &ReplayBody<V>) -> bool {
72 self.commands == other.commands && self.sim == other.sim
73 }
74}
75
76#[derive(Clone, Debug, PartialEq)]
78pub struct SimData {
79 pub tick: u32,
81 pub command_source: u8,
83 pub players_last_tick: HashMap<u8, u32>,
85 pub checksum: [u8; 16],
87 pub checksum_tick: Option<u32>,
89 pub desync_tick: Option<u32>,
91 pub desync_ticks: Option<Vec<u32>>,
93}
94impl SimData {
95 pub fn new() -> SimData {
96 SimData {
97 tick: 0,
98 command_source: 0,
99 players_last_tick: HashMap::new(),
100 checksum: [0; 16],
101 checksum_tick: None,
102 desync_tick: None,
103 desync_ticks: None,
104 }
105 }
106}
107
108#[derive(Clone, Debug, PartialEq)]
112pub struct ReplayCommandFrame<V: Version> {
113 pub command_id: V::CommandId,
114 pub size: u16,
116 pub data: Vec<u8>,
118}
119
120#[derive(Debug, PartialEq)]
122pub struct ReplayCommandFrameSpan<'a> {
123 pub cmd: u8,
124 pub size: u16,
125 pub data: &'a [u8],
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use pretty_assertions::assert_eq;
132
133 use crate::scfa::replay::*;
134 use crate::scfa::SCFA;
135
136 #[test]
137 fn test_struct_traits() {
138 let replay = Replay::<SCFA> {
139 header: ReplayHeader {
140 scfa_version: "Supreme Commander v1.50.3698".to_string(),
141 replay_version: "Replay v1.9".to_string(),
142 map_file: "/maps/map1/map1.scmap".to_string(),
143 mods: LuaObject::Nil,
144 scenario: LuaObject::Nil,
145 armies: OrderedHashMap::new(),
146 cheats_enabled: false,
147 players: OrderedHashMap::new(),
148 seed: 0,
149 },
150 body: ReplayBody {
151 sim: SimData {
152 tick: 0,
153 command_source: 0,
154 players_last_tick: HashMap::new(),
155 checksum: [0; 16],
156 checksum_tick: None,
157 desync_tick: None,
158 desync_ticks: None,
159 },
160 commands: vec![],
161 },
162 };
163 let replay2 = replay.clone();
164
165 let _ = format!("{:?}", &replay);
166 assert_eq!(replay, replay2);
167 }
168
169 #[test]
170 fn test_replay_command_display() {
171 use crate::lua::LuaTable;
172 use ReplayCommand::*;
173
174 assert_eq!(
175 format!(
176 "{}",
177 VerifyChecksum {
178 digest: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
179 tick: 10
180 }
181 ),
182 "VerifyChecksum { digest: 000102030405060708090a0b0c0d0e0f, tick: 10 }"
183 );
184 assert_eq!(
185 format!(
186 "{}",
187 CreateProp {
188 blueprint: "abcd".into(),
189 position: Position {
190 x: 0.0,
191 y: 10.0,
192 z: -100.0
193 }
194 }
195 ),
196 "CreateProp { blueprint: \"abcd\", position: Position { x: 0, y: 10, z: -100 } }"
197 );
198 assert_eq!(
199 format!(
200 "{}",
201 ExecuteLuaInSim {
202 code: "CallSomething(10 + 100)".into()
203 }
204 ),
205 "ExecuteLuaInSim { code: \"CallSomething(10 + 100)\" }"
206 );
207 assert_eq!(
208 format!("{}", SetCommandType { id: 1234, type_: 4 }),
209 "SetCommandType { id: 1234, type: FormMove }"
210 );
211 assert_eq!(
212 format!(
213 "{}",
214 SetCommandType {
215 id: 1234,
216 type_: 99
217 }
218 ),
219 "SetCommandType { id: 1234, type: 99 }"
220 );
221 assert_eq!(
222 format!(
223 "{}",
224 SetCommandTarget {
225 id: 1234,
226 target: Target::None
227 }
228 ),
229 "SetCommandTarget { id: 1234, target: None }"
230 );
231 assert_eq!(
232 format!(
233 "{}",
234 SetCommandTarget {
235 id: 1234,
236 target: Target::Entity { id: 1234 }
237 }
238 ),
239 "SetCommandTarget { id: 1234, target: Entity { id: 1234 } }"
240 );
241 assert_eq!(
242 format!(
243 "{}",
244 SetCommandTarget {
245 id: 1234,
246 target: Target::Position(Position {
247 x: 1.,
248 y: 2.,
249 z: 3.
250 })
251 }
252 ),
253 "SetCommandTarget { id: 1234, target: Position { x: 1, y: 2, z: 3 } }"
254 );
255
256 let mut upgrades_table = LuaTable::new();
257 upgrades_table.insert(
258 LuaObject::Unicode("Name".into()),
259 LuaObject::Unicode("Mazor".into()),
260 );
261 assert_eq!(
262 format!("{}", IssueCommand(GameCommand {
263 entity_ids: vec![1, 2],
264 id: 12345,
265 coordinated_attack_cmd_id: 0,
266 type_: 0,
267 arg2: -1,
268 target: Target::Position(Position {
269 x: 1.,
270 y: 2.,
271 z: 3.
272 }),
273 arg3: 0,
274 formation: Some(Formation {
275 a: 1.,
276 b: 2.,
277 c: 3.,
278 d: 4.,
279 scale: 2.,
280 }),
281 blueprint: "abcd".into(),
282 arg4: 0,
283 arg5: 1,
284 arg6: 2,
285 upgrades: LuaObject::Table(upgrades_table),
286 clear_queue: None,
287 })),
288 "IssueCommand(GameCommand { entity_ids: [1, 2], id: 12345, coordinated_attack_cmd_id: 0, type: None, arg2: -1, target: Position { x: 1, y: 2, z: 3 }, arg3: 0, formation: Formation { a: 1, b: 2, c: 3, d: 4, scale: 2 }, blueprint: \"abcd\", arg4: 0, arg5: 1, arg6: 2, upgrades: {\"Name\": \"Mazor\"}, clear_queue: None })"
289 );
290 }
291}