1mod momentcap;
2
3pub mod dbg;
4mod download_texture;
5pub mod encoding;
6#[cfg(feature = "ffmpeg")]
7pub mod ff;
8pub mod sprite_renderer;
9pub mod tripod;
10
11pub use download_texture::*;
12
13use twgpu::{image, twgame, twmap, twsnap, vek, wgpu};
14
15use std::collections::HashSet;
16use std::error::Error;
17use std::io::{BufReader, Read};
18use std::path::Path;
19use std::str::FromStr;
20use std::sync::Arc;
21use std::{fs, mem, ops};
22
23use image::{ImageFormat, RgbaImage};
24use teehistorian::ThStream;
25use teehistorian_replayer::{teehistorian, twgame_core};
26use twgame::Map;
27use twgame_core::Snapper;
28use twgpu::blit::Blit;
29use twgpu::buffer::GpuVecBuffer;
30use twgpu::map::{GpuMapDataDyn, GpuMapDataStatic, GpuMapRender, GpuMapStatic};
31use twgpu::shared::{Clock, Rng};
32use twgpu::sprites::{
33 AtlasToken, Particles, SpriteRenderContext, SpriteTextures, SpriteVertex, TeeSprite,
34 TeeStorage, TextureToken,
35};
36use twgpu::textures::Samplers;
37use twgpu::GpuCamera;
38use twmap::{LoadMultiple, TwMap};
39use twsnap::compat::ddnet::{DemoChunk, DemoMapHash, DemoReader};
40use vek::Vec2;
41use wgpu::{BufferBinding, Device, Queue, Texture};
42
43pub type SpriteBuffer = GpuVecBuffer<[SpriteVertex; 4]>;
44pub type TextureBuffer = GpuVecBuffer<TextureToken>;
45
46pub fn parse_tuple<T: FromStr, const SEPARATOR: char>(
47 s: &str,
48) -> Result<(T, T), Box<dyn Error + Send + Sync + 'static>>
49where
50 T::Err: Error + Send + Sync + 'static,
51{
52 if s.matches(SEPARATOR).count() != 1 {
53 return Err(format!("Expected 2 values separated by '{SEPARATOR}'").into());
54 }
55 let mut values = s.split(SEPARATOR).map(|str| str.parse::<T>());
56 Ok((values.next().unwrap()?, values.next().unwrap()?))
57}
58
59pub fn gpu_map_from_path(
60 path: &Path,
61 map_static: &GpuMapStatic,
62 camera: &GpuCamera,
63 samplers: &Samplers,
64 device: &Device,
65 queue: &Queue,
66) -> Result<(twmap::TwMap, GpuMapDataStatic, GpuMapDataDyn, GpuMapRender), Box<dyn Error>> {
67 println!("Loading map");
68 let mut map = twmap::TwMap::parse_path(path)?;
69 map.embed_images_auto()?;
70 map.images.load()?;
71 map.groups
72 .load_conditionally(|layer| layer.kind() == twmap::LayerKind::Tiles)?;
73 println!("Uploading map data to GPU");
74 let map_static_data = GpuMapDataStatic::upload(&map, device, queue);
75 let map_dyn_data = GpuMapDataDyn::upload(&map, device);
76 let map_render = map_static.prepare_render(
77 &map,
78 &map_static_data,
79 &map_dyn_data,
80 camera,
81 samplers,
82 device,
83 );
84 Ok((map, map_static_data, map_dyn_data, map_render))
85}
86
87pub fn load_png(path: &str, version: twmap::Version) -> Result<RgbaImage, Box<dyn Error>> {
88 let file = twstorage::read_file(path, version.into())?;
89 let reader = image::io::Reader::with_format(BufReader::new(file), ImageFormat::Png);
90 Ok(reader.decode()?.into_rgba8())
91}
92
93pub fn init_sprite_textures(
94 textures: &mut SpriteTextures,
95 version: twmap::Version,
96 camera_buffer: BufferBinding,
97 device: &Device,
98 queue: &Queue,
99) -> Result<(), Box<dyn Error>> {
100 println!("Loading sprite textures from filesystem");
101 let game = load_png("game.png", version)?;
102 let particles = load_png("particles.png", version)?;
103 let emoticons = load_png("emoticons.png", version)?;
104 let extras = load_png("extras.png", version)?;
105 textures.game_skin = textures.register_atlas_image(&game, camera_buffer.clone(), device, queue);
106 textures.particles =
107 textures.register_atlas_image(&particles, camera_buffer.clone(), device, queue);
108 textures.emoticons =
109 textures.register_atlas_image(&emoticons, camera_buffer.clone(), device, queue);
110 textures.extras = textures.register_atlas_image(&extras, camera_buffer, device, queue);
111 Ok(())
112}
113
114type SkinResult = Result<(String, Vec<Texture>), (String, String)>;
115
116pub struct SkinManager {
117 known: HashSet<String>,
118 handles: Vec<std::thread::JoinHandle<SkinResult>>,
119 blit: Arc<Blit>,
120 device: Device,
121 queue: Queue,
122}
123
124fn load_skin(
125 name: &str,
126 blit: &Blit,
127 device: &Device,
128 queue: &Queue,
129) -> Result<Vec<Texture>, Box<dyn Error>> {
130 let options = sanitize_filename::OptionsForCheck {
131 windows: true,
132 truncate: true,
133 };
134 if !sanitize_filename::is_sanitized_with_options(name, options) {
135 return Err(format!("Skin name '{name}' is not sanitized").into());
136 }
137 let path = format!("skins/{name}.png");
138 let path2 = format!("downloadedskins/{name}.png");
139 let image = match load_png(&path, twmap::Version::DDNet06) {
140 Err(_) => load_png(&path2, twmap::Version::DDNet06)?,
141 Ok(img) => img,
142 };
143
144 let texture = blit.upload_mipmapped_atlas::<TeeSprite>(&image, device, queue);
145 Ok(texture)
146}
147
148impl SkinManager {
149 pub fn new(
150 blit: Arc<Blit>,
151 textures: &mut SpriteTextures,
152 camera_buffer: BufferBinding,
153 device: Device,
154 queue: Queue,
155 ) -> Self {
156 let mut manager = Self {
157 known: HashSet::new(),
158 handles: Vec::new(),
159 blit,
160 device,
161 queue,
162 };
163 if let Some(default) = manager.load_skin("default", textures, camera_buffer.clone()) {
164 textures.default_skin = default;
165 }
166 if let Some(ninja) = manager.load_skin("x_ninja", textures, camera_buffer) {
167 textures.ninja_skin = ninja;
168 }
169 manager
170 }
171
172 pub fn load_skin(
173 &mut self,
174 name: &str,
175 textures: &mut SpriteTextures,
176 camera_buffer: BufferBinding,
177 ) -> Option<AtlasToken<TeeSprite>> {
178 let texture = match load_skin(name, &self.blit, &self.device, &self.queue) {
179 Ok(texture) => texture,
180 Err(err) => {
181 println!("Error loading skin '{name}': {err}");
182 return None;
183 }
184 };
185 self.known.insert(name.to_string());
186 let token =
187 textures.register_skin_texture(texture, name.to_string(), camera_buffer, &self.device);
188 Some(token)
189 }
190
191 pub fn queue_load_skin(&mut self, name: String) {
192 let device = self.device.clone();
193 let queue = self.queue.clone();
194 let blit = self.blit.clone();
195 let handle = std::thread::spawn(move || match load_skin(&name, &blit, &device, &queue) {
196 Err(err) => Err((name, err.to_string())),
197 Ok(texture) => Ok((name, texture)),
198 });
199 self.handles.push(handle);
200 }
201
202 pub fn wait_for_queued(&mut self, textures: &mut SpriteTextures, camera_buffer: BufferBinding) {
203 while let Some(handle) = self.handles.pop() {
204 match handle.join().unwrap() {
205 Ok((name, texture)) => {
206 textures.register_skin_texture(
207 texture,
208 name,
209 camera_buffer.clone(),
210 &self.device,
211 );
212 }
213 Err((name, err)) => println!("Error with skin '{name}': {err}"),
214 }
215 }
216 }
217
218 pub fn poll_queued(&mut self, textures: &mut SpriteTextures, camera_buffer: BufferBinding) {
219 while !self.handles.is_empty() {
220 if self.handles[0].is_finished() {
221 match self.handles.remove(0).join().unwrap() {
222 Ok((name, texture)) => {
223 textures.register_skin_texture(
224 texture,
225 name,
226 camera_buffer.clone(),
227 &self.device,
228 );
229 }
230 Err((name, err)) => println!("Error with skin '{name}': {err}"),
231 }
232 } else {
233 return;
234 }
235 }
236 }
237
238 pub fn queue_snap_skins(&mut self, snap: &twsnap::Snap) {
239 for player in snap.players.values() {
240 if !self.known.contains(player.skin.as_str()) {
241 println!("New skin: '{}'", player.skin);
242 self.known.insert(player.skin.to_string());
243 self.queue_load_skin(player.skin.to_string());
244 }
245 }
246 }
247}
248
249pub struct DemoProcessor {
250 demo: Box<dyn DemoLike>,
251 clock: Clock,
252 map: Arc<Map>,
253 from_snap: twsnap::Snap,
254 to_snap: twsnap::Snap,
255 tees: TeeStorage,
256 new_snap: bool,
257}
258
259fn maps(data: &[u8]) -> Result<(Arc<TwMap>, Arc<Map>), Box<dyn Error>> {
260 let mut twmap_map = TwMap::parse(data)?;
261 twmap_map.embed_images_auto()?;
262 twmap_map.load()?;
263 let twgame_map = twgame::Map::try_from(&mut twmap_map)?;
264 Ok((Arc::new(twmap_map), Arc::new(twgame_map)))
265}
266
267fn file_path_to_vec(path: &Path) -> Result<Vec<u8>, Box<dyn Error>> {
268 let mut buf = Vec::new();
269 let mut file = fs::File::open(path)?;
270 file.read_to_end(&mut buf)?;
271 Ok(buf)
272}
273
274fn retrieve_maps(name: &str, sha: Option<&str>) -> Result<(Arc<TwMap>, Arc<Map>), Box<dyn Error>> {
275 let version = twstorage::Version::DDNet06;
276 if let Some(sha) = sha {
277 let path = format!("downloadedmaps/{name}_{sha}.map");
278 if let Ok(mut file) = twstorage::read_file(&path, version) {
279 let mut data = Vec::new();
280 file.read_to_end(&mut data)?;
281 let map = maps(&data)?;
282 return Ok(map);
283 }
284 }
285 let path = format!("maps/{name}.map");
286 if let Ok(mut file) = twstorage::read_file(&path, version) {
287 let mut data = Vec::new();
288 file.read_to_end(&mut data)?;
289 let map = maps(&data)?;
290 return Ok(map);
291 }
292 Err("Map not embedded in demo and its file couldn't be located".into())
293}
294
295impl DemoProcessor {
296 pub fn new_by_extension(
297 path: &Path,
298 map: Option<&Path>,
299 pre: Option<(Arc<TwMap>, Arc<Map>)>,
300 ) -> Result<(Self, Arc<TwMap>, Arc<Map>), Box<dyn Error>> {
301 let file = fs::File::open(path)?;
302 let map = match pre {
303 Some(maps) => Some(maps),
304 None => match map {
305 None => None,
306 Some(path) => Some(maps(&file_path_to_vec(path)?)?),
307 },
308 };
309 let extension = path.extension().and_then(|os| os.to_str());
310 if extension == Some("teehistorian") {
311 let (demo, twmap, map) = TeehistorianDemo::new(file, map)?;
312 let demo = Self::new(Box::new(demo), map.clone())?;
313 Ok((demo, twmap, map))
314 } else if extension == Some("poses") {
315 let demo = MomentcapDemo::new(file)?;
316 let (twmap, map) = map.ok_or("Need explicit map file for momentcap file")?;
317 let demo = Self::new(Box::new(demo), map.clone())?;
318 Ok((demo, twmap, map))
319 } else {
320 if extension != Some("demo") {
322 println!("Unrecognized demo file extension, defaulting to normal demo");
323 }
324 let demo = DemoReader::new(file)?;
325 let (twmap, map) = match map {
326 Some(map) => map,
327 None => {
328 if let Some(data) = demo.map_data() {
329 maps(data)?
330 } else {
331 let map_name = demo.map_name();
332 let map_sha = match demo.map_hash() {
333 DemoMapHash::Crc(_) => None,
334 DemoMapHash::Sha256(sha) => {
335 Some(sha.map(|byte| format!("{byte:02x}")).join(""))
336 }
337 };
338 retrieve_maps(map_name, map_sha.as_deref())?
339 }
340 }
341 };
342 let demo = Self::new(Box::new(demo), map.clone())?;
343 Ok((demo, twmap, map))
344 }
345 }
346
347 pub fn new(mut demo: Box<dyn DemoLike>, map: Arc<Map>) -> Result<Self, Box<dyn Error>> {
348 let mut snap = twsnap::Snap::default();
349 let clock = loop {
350 match demo.next_demo_chunk(&mut snap)? {
351 None => break Clock::default(),
352 Some(DemoChunk::NetMsg) => continue,
353 Some(DemoChunk::Snapshot(tick)) => break Clock::new(tick.snap_tick()),
354 }
355 };
356 Ok(Self {
357 demo,
358 clock,
359 map,
360 from_snap: twsnap::Snap::default(),
361 to_snap: snap,
362 tees: TeeStorage::default(),
363 new_snap: true,
364 })
365 }
366
367 pub fn focused_player(&mut self) -> Option<u32> {
368 for (player_id, player) in self.from_snap.players.iter() {
369 if player.local {
370 return Some(player_id.0);
371 }
372 }
373 None
374 }
375
376 pub fn position_of_player(&mut self, id: u32) -> Option<Vec2<f32>> {
377 if let Some(tees) = self.tees.lerp_tee(
378 twsnap::SnapId(id),
379 &self.clock,
380 &self.from_snap,
381 &self.to_snap,
382 &self.map,
383 ) {
384 Some(self.clock.lerp_tee_vec(tees, |tee| tee.pos))
385 } else {
386 None
387 }
388 }
389
390 pub fn start_tick(&self) -> i32 {
391 self.clock.start_tick()
392 }
393
394 pub fn current_time(&self) -> f64 {
395 self.clock.current_time()
396 }
397
398 pub fn to_tick(
400 &mut self,
401 f_tick: f64,
402 safe_tick_threshold: u32,
403 ) -> Result<Option<bool>, Box<dyn Error>> {
404 if self.clock.update(f_tick) {
405 let new_tick = self.demo.snaps_for_tick(
406 &mut self.clock,
407 safe_tick_threshold,
408 &mut self.from_snap,
409 &mut self.to_snap,
410 )?;
411 if new_tick.is_some() {
412 self.new_snap = true;
413 }
414 Ok(new_tick.map(|_| true))
415 } else {
416 Ok(Some(false))
417 }
418 }
419
420 pub fn new_snap(&self) -> Option<&twsnap::Snap> {
421 self.new_snap.then_some(&self.to_snap)
422 }
423
424 pub fn render_ctx<'a>(
425 &'a mut self,
426 particles: &'a mut Particles,
427 textures: &'a SpriteTextures,
428 rng: &'a mut Rng,
429 ) -> SpriteRenderContext<'a> {
430 particles.update(&self.clock, &self.map, rng);
431 let mut ctx = SpriteRenderContext {
432 clock: &self.clock,
433 from_snap: &self.from_snap,
434 to_snap: &self.to_snap,
435 tees: &mut self.tees,
436 map: &self.map,
437 particles,
438 textures,
439 rng,
440 };
441 if self.new_snap {
442 ctx.process_events();
443 self.new_snap = false;
444 }
445 ctx
446 }
447}
448
449pub trait DemoLike {
450 fn next_demo_chunk(
451 &mut self,
452 snap: &mut twsnap::Snap,
453 ) -> Result<Option<DemoChunk>, Box<dyn Error>>;
454
455 fn next_demo_chunk_after(
459 &mut self,
460 snap: &mut twsnap::Snap,
461 after: i32,
462 ) -> Result<Option<DemoChunk>, Box<dyn Error>> {
463 loop {
464 match self.next_demo_chunk(snap)? {
465 None => break Ok(None),
466 Some(DemoChunk::Snapshot(tick)) => {
467 if tick.snap_tick() < after {
468 continue;
469 } else {
470 break Ok(Some(DemoChunk::Snapshot(tick)));
471 }
472 }
473 Some(_) => continue,
474 }
475 }
476 }
477
478 fn snaps_for_tick(
483 &mut self,
484 clock: &mut Clock,
485 safe_tick_threshold: u32,
486 from: &mut twsnap::Snap,
487 to: &mut twsnap::Snap,
488 ) -> Result<Option<i32>, Box<dyn Error>> {
489 if !clock.update(clock.current_tick()) {
490 panic!("Already on appropriate tick!");
491 }
492 assert!(!clock.current_tick().is_nan());
493 assert!(clock.current_tick().is_sign_positive());
494 let diff = clock.current_tick() - clock.end_tick() as f64;
495 assert!(diff.is_sign_positive());
496 let mut skipped = false;
497 let mut snaps_after_skip = 0;
498 if diff as u32 > safe_tick_threshold {
499 skipped = true;
500 mem::swap(from, to);
501 let skip_to = clock.current_tick() as u32 - safe_tick_threshold;
502 match self.next_demo_chunk_after(to, skip_to.try_into().unwrap())? {
503 None => return Ok(None),
504 Some(DemoChunk::NetMsg) => {}
505 Some(DemoChunk::Snapshot(tick)) => {
506 clock.next_tick(tick.snap_tick());
507 snaps_after_skip += 1;
508 }
509 }
510 }
511 while clock.need_next_tick() {
512 match self.next_demo_chunk(from)? {
513 None => return Ok(None),
514 Some(DemoChunk::NetMsg) => continue,
515 Some(DemoChunk::Snapshot(tick)) => {
516 mem::swap(from, to);
517 snaps_after_skip += 1;
518 clock.next_tick(tick.snap_tick());
519 }
520 }
521 }
522 if skipped && snaps_after_skip < 2 {
523 return Err(
524 "Too optimistic tick skip: too few snaps after skip. Adjust the skip_threshold!"
525 .into(),
526 );
527 }
528 Ok(Some(clock.end_tick()))
529 }
530}
531
532impl DemoLike for DemoReader {
533 fn next_demo_chunk(
534 &mut self,
535 snap: &mut twsnap::Snap,
536 ) -> Result<Option<DemoChunk>, Box<dyn Error>> {
537 self.next_chunk(snap).map_err(Into::into)
538 }
539}
540
541pub struct TeehistorianDemo {
542 th: teehistorian::ThCompat<teehistorian::ThBufReader<fs::File>>,
543 replayer: teehistorian_replayer::ThReplayer,
544 world: twgame::DdnetReplayerWorld,
545}
546impl TeehistorianDemo {
547 pub fn new(
548 file: fs::File,
549 maps: Option<(Arc<TwMap>, Arc<twgame::Map>)>,
550 ) -> Result<(Self, Arc<TwMap>, Arc<Map>), Box<dyn Error>> {
551 let buf_read = teehistorian::ThBufReader::new(file);
552 let mut th = teehistorian::ThCompat::parse(buf_read)?;
553 let th_header = th.header()?;
554 let (twmap, map) = match maps {
555 Some(maps) => maps,
556 None => {
557 let th_header: serde_json::Value = serde_json::from_slice(th_header)?;
558 let map_name = th_header
559 .get("map_name")
560 .and_then(|val| val.as_str())
561 .ok_or("teehistorian header with no map name")?;
562 let map_sha = th_header
563 .get("map_sha256")
564 .and_then(|val| val.as_str())
565 .ok_or("teehistorian header with no map sha")?;
566 retrieve_maps(map_name, Some(map_sha))?
567 }
568 };
569
570 let mut world = twgame::DdnetReplayerWorld::new(map.clone(), true);
571 let replayer = teehistorian_replayer::ThReplayer::new(th_header, &mut world);
572 Ok((
573 Self {
574 th,
575 world,
576 replayer,
577 },
578 twmap,
579 map,
580 ))
581 }
582}
583
584impl DemoLike for TeehistorianDemo {
585 fn next_demo_chunk(
586 &mut self,
587 snap: &mut twsnap::Snap,
588 ) -> Result<Option<DemoChunk>, Box<dyn Error>> {
589 let last_tick = self.replayer.cur_time();
590 loop {
591 let chunk = match self.th.next_chunk() {
592 Ok(chunk) => Ok(Some(chunk)),
593 Err(teehistorian::Error::Eof) => Ok(None),
594 Err(err) => Err(err),
595 }?;
596 let teehistorian_ended = chunk.is_none();
597 self.replayer
598 .replay_next_chunk(&mut self.world, chunk, None);
599 let tick = self.replayer.cur_time();
600 if tick > last_tick {
601 snap.clear();
602 self.world.snap(snap);
603 break Ok(Some(DemoChunk::Snapshot(tick)));
604 }
605 if teehistorian_ended {
606 break Ok(None);
607 }
608 }
609 }
610}
611
612pub struct MomentcapDemo {
613 data: Vec<u8>,
614 ticks: ops::Range<i32>,
615}
616
617impl MomentcapDemo {
618 pub fn new(mut file: fs::File) -> Result<Self, Box<dyn Error>> {
619 let mut result = Self {
620 data: Vec::new(),
621 ticks: 49..10000 + 1,
622 };
623 file.read_to_end(&mut result.data)?;
624 result.poses()?;
625 Ok(result)
626 }
627 fn poses(&self) -> Result<&[momentcap::Pose], Box<dyn Error>> {
628 Ok(momentcap::read_poses(&self.data)?)
629 }
630}
631
632impl DemoLike for MomentcapDemo {
633 fn next_demo_chunk(
634 &mut self,
635 snap: &mut twsnap::Snap,
636 ) -> Result<Option<DemoChunk>, Box<dyn Error>> {
637 Ok(self.ticks.next().map(|tick| {
638 let tick = twsnap::time::Instant::from_snap_tick(tick);
639 momentcap::to_snap(self.poses().unwrap(), snap, tick);
640 DemoChunk::Snapshot(tick)
641 }))
642 }
643}