1use twsnap::time::Duration;
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4pub struct FinishTee {
5 pub name: String,
7 pub time: Duration,
8}
9
10impl FinishTee {
11 pub fn new_from_str(name: &str, time: Duration) -> Self {
12 Self {
16 name: name.to_owned(),
17 time,
18 }
19 }
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct FinishTeam {
24 pub team: i32,
25 pub names: Vec<String>,
26 pub time: Duration,
27}
28
29#[derive(Clone, Debug)]
30pub struct SaveTeam {
31 buf: String,
32}
33
34impl SaveTeam {
35 pub fn compat_eq(&self, expected_compat: &Self) -> bool {
36 let mut got = self.buf.split('\n');
37 let mut expected = expected_compat.buf.split('\n');
38
39 let (g, e) = match (got.next(), expected.next()) {
40 (Some(g), Some(e)) => (g, e),
41 (None, None) => return true,
42 _ => return false,
43 };
44 SaveTeam::compare_variables("header", 0, g, e, &TEAM_DESC);
45 let (g_tees, g_switchers) = SaveTeam::parse_head(g);
46 let (e_tees, e_switchers) = SaveTeam::parse_head(e);
47 if g_tees != e_tees || g_switchers != e_switchers {
48 return false;
49 }
50 for _ in 0..g_tees {
51 let g = got.next().unwrap_or("");
52 let e = expected.next().unwrap_or("");
53 if !SaveTeam::are_tees_same_compat(g, e) {
54 return false;
55 }
56 }
57 for _ in 0..g_switchers {
58 let g = got.next().unwrap_or("");
59 let e = expected.next().unwrap_or("");
60 if !SaveTeam::are_switchers_same_compat(g, e) {
61 return false;
62 }
63 }
64 true
65 }
66}
67
68impl SaveTeam {
69 pub fn buf(&self) -> &str {
70 &self.buf
71 }
72}
73
74#[derive(Clone, Debug)]
75pub enum DatabaseWrite {
76 FinishTee(FinishTee),
77 FinishTeam(FinishTeam),
78 Save(i32, SaveTeam),
80 Load(i32),
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub enum Finishes {
85 FinishTee(FinishTee),
86 FinishTeam(FinishTeam),
88}
89
90const TEAM_DESC: [&str; 5] = [
91 "m_TeamState",
92 "m_MembersCount",
93 "m_HighestSwitchNumber",
94 "m_TeamLocked",
95 "m_Practice",
96];
97
98const TEE_DESC: [&str; 115] = [
99 "m_aName",
100 "m_Alive",
101 "m_Paused",
102 "m_NeededFaketuning",
103 "m_TeeFinished",
104 "m_IsSolo",
105 "m_aWeapons[0].m_AmmoRegenStart",
106 "m_aWeapons[0].m_Ammo",
107 "m_aWeapons[0].m_Ammocost",
108 "m_aWeapons[0].m_Got",
109 "m_aWeapons[1].m_AmmoRegenStart",
110 "m_aWeapons[1].m_Ammo",
111 "m_aWeapons[1].m_Ammocost",
112 "m_aWeapons[1].m_Got",
113 "m_aWeapons[2].m_AmmoRegenStart",
114 "m_aWeapons[2].m_Ammo",
115 "m_aWeapons[2].m_Ammocost",
116 "m_aWeapons[2].m_Got",
117 "m_aWeapons[3].m_AmmoRegenStart",
118 "m_aWeapons[3].m_Ammo",
119 "m_aWeapons[3].m_Ammocost",
120 "m_aWeapons[3].m_Got",
121 "m_aWeapons[4].m_AmmoRegenStart",
122 "m_aWeapons[4].m_Ammo",
123 "m_aWeapons[4].m_Ammocost",
124 "m_aWeapons[4].m_Got",
125 "m_aWeapons[5].m_AmmoRegenStart",
126 "m_aWeapons[5].m_Ammo",
127 "m_aWeapons[5].m_Ammocost",
128 "m_aWeapons[5].m_Got",
129 "m_LastWeapon",
130 "m_QueuedWeapon",
131 "m_EndlessJump",
133 "m_Jetpack",
134 "m_NinjaJetpack",
135 "m_FreezeTime",
136 "m_FreezeStart",
137 "m_DeepFrozen",
138 "m_EndlessHook",
139 "m_DDRaceState",
140 "m_HitDisabledFlags",
141 "m_CollisionEnabled",
142 "m_TuneZone",
143 "m_TuneZoneOld",
144 "m_HookHitEnabled",
145 "m_Time",
146 "(int)m_Pos.x",
147 "(int)m_Pos.y",
148 "(int)m_PrevPos.x",
149 "(int)m_PrevPos.y",
150 "m_TeleCheckpoint",
151 "m_LastPenalty",
152 "(int)m_CorePos.x",
153 "(int)m_CorePos.y",
154 "m_Vel.x",
155 "m_Vel.y",
156 "m_ActiveWeapon",
157 "m_Jumped",
158 "m_JumpedTotal",
159 "m_Jumps",
160 "(int)m_HookPos.x",
161 "(int)m_HookPos.y",
162 "m_HookDir.x",
163 "m_HookDir.y",
164 "(int)m_HookTeleBase.x",
165 "(int)m_HookTeleBase.y",
166 "m_HookTick",
167 "m_HookState",
168 "m_TimeCpBroadcastEndTime",
169 "m_LastTimeCp",
170 "m_LastTimeCpBroadcasted",
171 "m_aCurrentTimeCp[0]",
172 "m_aCurrentTimeCp[1]",
173 "m_aCurrentTimeCp[2]",
174 "m_aCurrentTimeCp[3]",
175 "m_aCurrentTimeCp[4]",
176 "m_aCurrentTimeCp[5]",
177 "m_aCurrentTimeCp[6]",
178 "m_aCurrentTimeCp[7]",
179 "m_aCurrentTimeCp[8]",
180 "m_aCurrentTimeCp[9]",
181 "m_aCurrentTimeCp[10]",
182 "m_aCurrentTimeCp[11]",
183 "m_aCurrentTimeCp[12]",
184 "m_aCurrentTimeCp[13]",
185 "m_aCurrentTimeCp[14]",
186 "m_aCurrentTimeCp[15]",
187 "m_aCurrentTimeCp[16]",
188 "m_aCurrentTimeCp[17]",
189 "m_aCurrentTimeCp[18]",
190 "m_aCurrentTimeCp[19]",
191 "m_aCurrentTimeCp[20]",
192 "m_aCurrentTimeCp[21]",
193 "m_aCurrentTimeCp[22]",
194 "m_aCurrentTimeCp[23]",
195 "m_aCurrentTimeCp[24]",
196 "m_NotEligibleForFinish",
197 "m_HasTelegunGun",
198 "m_HasTelegunLaser",
199 "m_HasTelegunGrenade",
200 "m_aGameUuid",
201 "HookedPlayer",
202 "m_NewHook",
203 "m_InputDirection",
204 "m_InputJump",
205 "m_InputFire",
206 "m_InputHook",
207 "m_ReloadTimer",
208 "m_TeeStarted",
209 "m_LiveFrozen",
210 "m_Ninja.m_ActivationDir.x",
211 "m_Ninja.m_ActivationDir.y",
212 "m_Ninja.m_ActivationTick",
213 "m_Ninja.m_CurrentMoveTime",
214 "m_Ninja.m_OldVelAmount",
215];
216
217const SWITCHER_DESC: [&str; 3] = [
218 "m_pSwitchers[i].m_Status",
219 "m_pSwitchers[i].m_EndTime",
220 "m_pSwitchers[i].m_Type",
221];
222
223impl SaveTeam {
224 pub fn from_buf(buf: &[u8]) -> Self {
225 Self {
226 buf: String::from_utf8_lossy(buf).to_string(),
227 }
228 }
229
230 pub fn from_string(buf: String) -> Self {
231 Self { buf }
232 }
233
234 fn print_variables(part: &str, id: u32, values: &str, variables: &[&str]) {
235 let mut vals = values.split('\t');
236 for (i, var) in variables.iter().enumerate() {
237 let v = vals.next();
238 println!("{part}[{id}] {var} ({i}): {v:?}");
239 }
240 }
241
242 fn compare_format_variables(
243 part: &str,
244 id: u32,
245 got: &str,
246 expected: &str,
247 variables: &[&str],
248 ) -> Vec<String> {
249 let mut strings = Vec::new();
250 let mut got = got.split('\t');
251 let mut expected = expected.split('\t');
252 for (i, var) in variables.iter().enumerate() {
253 let g = got.next();
254 let e = expected.next();
255 if g != e {
256 strings.push(format!(
257 "{part}[{id}] {var} ({i}): (got) {g:?} != {e:?} (expected)"
258 ));
259 }
260 }
261 strings
262 }
263
264 fn compare_variables(part: &str, id: u32, got: &str, expected: &str, variables: &[&str]) {
265 for msg in Self::compare_format_variables(part, id, got, expected, variables) {
266 println!("{msg}");
267 }
268 }
269
270 fn are_tees_same_compat(got: &str, expected: &str) -> bool {
271 let mut hook_tick_correct = true;
273 let mut got = got.split('\t');
274 let expected = expected.split('\t');
275 for (el, e) in expected.enumerate() {
276 let g = got.next();
277 if matches!(
278 el,
279 3 | 7 | 11 | 15 | 19 | 23 | 27 | 68 ) {
283 continue;
284 }
285 if el == 67 && !hook_tick_correct && e == "4" {
286 return false;
288 }
289 if el == 101 && !hook_tick_correct && e != "-1" {
290 return false;
292 }
293 if g.map(|g| g != e).unwrap_or(true) {
294 if el == 66 {
296 hook_tick_correct = false;
299 continue;
300 }
301 return false;
302 }
303 }
304 true
305 }
306
307 fn are_switchers_same_compat(got: &str, expected: &str) -> bool {
308 let mut got = got.split('\t');
309 let expected = expected.split('\t');
310 for e in expected {
311 let g = got.next();
312 if g.map(|g| g != e).unwrap_or(true) {
313 return false;
314 }
315 }
316 true
317 }
318
319 fn parse_head(head: &str) -> (u32, u32) {
320 let mut head = head.split('\t');
321 let _ = head.next();
322 let num_tees = head
323 .next()
324 .map(|el| el.parse::<u32>().unwrap_or(0))
325 .unwrap_or(0);
326 let num_switchers = head
327 .next()
328 .map(|el| el.parse::<u32>().unwrap_or(0))
329 .unwrap_or(0);
330 (num_tees, num_switchers)
331 }
332
333 pub fn format_diff(&self, expected: &Self) -> Vec<String> {
334 let mut diffs = Vec::new();
335 let mut got = self.buf.split('\n');
336 let mut expected = expected.buf.split('\n');
337
338 let (g, e) = match (got.next(), expected.next()) {
339 (Some(g), Some(e)) => (g, e),
340 (Some(g), None) => {
341 diffs.push(format!("expected no header got {g}"));
342 return diffs;
343 }
344 (None, Some(e)) => {
345 diffs.push(format!("expected header {e}, got nothing"));
346 return diffs;
347 }
348 (None, None) => {
349 return diffs;
350 }
351 };
352 diffs.extend(SaveTeam::compare_format_variables(
353 "header", 0, g, e, &TEAM_DESC,
354 ));
355 let (mut g_tees, mut g_switchers) = SaveTeam::parse_head(g);
356 let (mut e_tees, mut e_switchers) = SaveTeam::parse_head(e);
357 let mut tee = 0;
358 while g_tees > 0 || e_tees > 0 {
359 let g = if g_tees > 0 {
360 let g = got.next().unwrap_or("");
361 g_tees -= 1;
362 g
363 } else {
364 ""
365 };
366 let e = if e_tees > 0 {
367 let e = expected.next().unwrap_or("");
368 e_tees -= 1;
369 e
370 } else {
371 ""
372 };
373 diffs.extend(SaveTeam::compare_format_variables(
374 "tee", tee, g, e, &TEE_DESC,
375 ));
376 tee += 1;
377 }
378 let mut switchers = 0;
379 while g_switchers > 0 || e_switchers > 0 {
380 let g = if g_switchers > 0 {
381 let g = got.next().unwrap_or("");
382 g_switchers -= 1;
383 g
384 } else {
385 ""
386 };
387 let e = if e_switchers > 0 {
388 let e = expected.next().unwrap_or("");
389 e_switchers -= 1;
390 e
391 } else {
392 ""
393 };
394 diffs.extend(SaveTeam::compare_format_variables(
395 "switcher",
396 switchers,
397 g,
398 e,
399 &SWITCHER_DESC,
400 ));
401 switchers += 1;
402 }
403 if let Some(remaining) = expected.next() {
404 diffs.push(format!(
405 "unexpected remaining line(s) in expected: {}",
406 remaining
407 ));
408 }
409 if let Some(remaining) = got.next() {
410 diffs.push(format!(
411 "unexpected remaining line(s) in got: {}",
412 remaining
413 ));
414 }
415 diffs
416 }
417
418 pub fn print_diff(&self, expected: &Self) {
419 for diff in self.format_diff(expected) {
420 println!("{diff}");
421 }
422 }
423
424 pub fn pretty_print(&self) {
425 let mut parts = self.buf.split('\n');
426 let Some(header) = parts.next() else {
427 return;
428 };
429 SaveTeam::print_variables("header", 0, header, &TEAM_DESC);
430 let (num_tees, num_switchers) = SaveTeam::parse_head(header);
431 for tee_id in 0..num_tees {
432 SaveTeam::print_variables("tee", tee_id, parts.next().unwrap_or(""), &TEE_DESC);
433 }
434 for switch_id in 0..num_switchers {
435 SaveTeam::print_variables(
436 "switcher",
437 switch_id,
438 parts.next().unwrap_or(""),
439 &SWITCHER_DESC,
440 );
441 }
442 }
443}
444
445#[derive(Clone, Debug)]
446pub enum DatabaseResult {
447 LoadSuccess(i32, SaveTeam),
448 LoadFailure(i32),
449 SaveSuccess(i32),
450 SaveFailure(i32),
451}
452
453impl DatabaseResult {
454 pub fn team(&self) -> i32 {
455 match self {
456 DatabaseResult::LoadSuccess(team, _) => *team,
457 DatabaseResult::LoadFailure(team) => *team,
458 DatabaseResult::SaveSuccess(team) => *team,
459 DatabaseResult::SaveFailure(team) => *team,
460 }
461 }
462}
463
464impl DatabaseWrite {
465 pub fn finish_tee(name: &str, time: Duration) -> Self {
466 Self::FinishTee(FinishTee::new_from_str(name, time))
467 }
468}