demo playing

This commit is contained in:
Robin Appelman 2022-04-03 00:22:29 +02:00
commit 90a5966682
8 changed files with 502 additions and 36 deletions

View file

@ -7,10 +7,10 @@ use vmdl::mdl::Mdl;
use vmdl::vtx::Vtx;
use vmdl::vvd::Vvd;
pub fn load_map(data: &[u8]) -> Result<Vec<CPUMesh>, Error> {
pub fn load_map(data: &[u8], loader: &mut Loader) -> Result<Vec<CPUMesh>, Error> {
let (cpu_mesh, bsp) = load_world(data)?;
let loader = Loader::new(bsp.pack.clone())?;
let merged_props = load_props(&loader, bsp.static_props())?;
loader.set_pack(bsp.pack.clone());
let merged_props = load_props(loader, bsp.static_props())?;
Ok(vec![cpu_mesh, merged_props])
}
@ -19,7 +19,7 @@ fn apply_transform(coord: [f32; 3], transform: Mat4) -> [f32; 3] {
[coord.x, coord.y, coord.z]
}
fn map_coords<C: Into<[f32; 3]>>(vec: C) -> [f32; 3] {
pub fn map_coords<C: Into<[f32; 3]>>(vec: C) -> [f32; 3] {
let vec = vec.into();
[
vec[1] * UNIT_SCALE,

74
src/demo.rs Normal file
View file

@ -0,0 +1,74 @@
use crate::bsp::map_coords;
use crate::Error;
use std::fs;
use std::path::Path;
use tf_demo_parser::demo::parser::gamestateanalyser::{GameState, GameStateAnalyser};
use tf_demo_parser::{Demo, DemoParser};
use three_d::{vec3, Vec3};
use tracing::{error, info};
pub struct DemoInfo {
pub map: String,
pub positions: Vec<(Vec3, f32, f32)>,
pub start_tick: u32,
}
impl DemoInfo {
pub fn new(demo_path: impl AsRef<Path>, name: &str) -> Result<Self, Error> {
let file = fs::read(demo_path)?;
let demo = Demo::new(&file);
let parser = DemoParser::new_with_analyser(demo.get_stream(), GameStateAnalyser::new());
let (header, mut ticker) = parser.ticker()?;
let mut positions = Vec::with_capacity(header.ticks as usize);
let mut user_id = None;
let mut start_tick = 0;
while let Some(tick) = ticker.next()? {
let state: &GameState = tick.state;
if user_id.is_none() {
if let Some(found) = state
.players
.iter()
.enumerate()
.filter_map(|(i, player)| Some((i, player.info.as_ref()?)))
.find_map(|(i, player)| {
player.name.to_ascii_lowercase().contains(name).then(|| i)
})
{
info!(user_id = found, "found user");
start_tick = tick.tick;
user_id = Some(found);
}
}
if let Some(user_id) = user_id {
let player = &state.players[user_id];
let coords = map_coords(player.position);
positions.push((
vec3(coords[0], coords[1], coords[2]),
player.view_angle,
player.pitch_angle,
))
}
}
if user_id.is_none() {
let found = ticker
.into_state()
.players
.into_iter()
.filter_map(|player| Some(player.info?.name))
.collect::<Vec<_>>();
error!(
"User {} not found in demo, found: {}",
name,
found.join(", ")
);
return Err("Failed to find user in demo".into());
}
Ok(DemoInfo {
map: header.map,
positions,
start_tick,
})
}
}

View file

@ -1,5 +1,6 @@
use crate::Error;
use std::fmt::{Debug, Formatter};
use std::fs;
use std::path::PathBuf;
use steamlocate::SteamDir;
use tracing::{debug, error};
@ -7,8 +8,9 @@ use vbsp::Packfile;
use vpk::VPK;
pub struct Loader {
pack: Packfile,
pack: Option<Packfile>,
tf_dir: PathBuf,
download: PathBuf,
vpks: Vec<VPK>,
}
@ -21,13 +23,23 @@ impl Debug for Loader {
}
impl Loader {
pub fn new(pack: Packfile) -> Result<Self, Error> {
pub fn new() -> Result<Self, Error> {
Self::with_opt_pack(None)
}
#[allow(dead_code)]
pub fn with_pak(pack: Packfile) -> Result<Self, Error> {
Self::with_opt_pack(Some(pack))
}
pub fn with_opt_pack(pack: Option<Packfile>) -> Result<Self, Error> {
let tf_dir = SteamDir::locate()
.ok_or("Can't find steam directory")?
.app(&440)
.ok_or("Can't find tf2 directory")?
.path
.join("tf");
let download = tf_dir.join("download");
let vpks = tf_dir
.read_dir()?
.filter_map(|item| item.ok())
@ -37,15 +49,36 @@ impl Loader {
.filter_map(|res| res.ok())
.collect();
Ok(Loader { pack, tf_dir, vpks })
Ok(Loader {
pack,
tf_dir,
download,
vpks,
})
}
pub fn set_pack(&mut self, pack: Packfile) {
self.pack = Some(pack);
}
#[tracing::instrument]
pub fn load(&self, name: &str) -> Result<Vec<u8>, Error> {
debug!("loading {}", name);
if let Some(data) = self.pack.get(name)? {
debug!("got {} bytes from packfile", data.len());
return Ok(data);
let path = self.tf_dir.join(name);
if path.exists() {
debug!("found in tf2 dir");
return Ok(fs::read(path)?);
}
let path = self.download.join(name);
if path.exists() {
debug!("found in download dir");
return Ok(fs::read(path)?);
}
if let Some(pack) = &self.pack {
if let Some(data) = pack.get(name)? {
debug!("got {} bytes from packfile", data.len());
return Ok(data);
}
}
for vpk in self.vpks.iter() {
if let Some(entry) = vpk.tree.get(name) {

View file

@ -1,20 +1,32 @@
mod bsp;
mod camera;
mod demo;
mod loader;
mod renderer;
mod ui;
use clap::Parser;
use crate::bsp::load_map;
use crate::demo::DemoInfo;
use crate::renderer::Renderer;
use crate::ui::DebugUI;
use camera::FirstPerson;
use loader::Loader;
use std::env::args;
use thiserror::Error;
use three_d::*;
use tracing_subscriber::{prelude::*, EnvFilter};
use tracing_tree::HierarchicalLayer;
/// View a demo file
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// Path of the demo file
demo: String,
/// Name of the player to follow
player: String,
}
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
@ -27,6 +39,8 @@ pub enum Error {
Vpk(#[from] vpk::Error),
#[error(transparent)]
Mdl(#[from] vmdl::ModelError),
#[error(transparent)]
Demo(#[from] tf_demo_parser::ParseError),
#[error("{0}")]
Other(&'static str),
}
@ -53,26 +67,21 @@ fn setup() {
fn main() -> Result<(), Error> {
setup();
let mut args = args();
let bin = args.next().unwrap();
let file = match args.next() {
Some(file) => file,
None => {
eprintln!("usage: {} <file.bsp>", bin);
return Ok(());
}
};
let args = Args::parse();
let window = Window::new(WindowSettings {
title: file.clone(),
title: args.demo.clone(),
max_size: Some((1920, 1080)),
..Default::default()
})?;
let demo = DemoInfo::new(args.demo, &args.player)?;
let mut loader = Loader::new()?;
let map = loader.load(&format!("maps/{}.bsp", demo.map))?;
let mut renderer = Renderer::new(&window)?;
let map = std::fs::read(&file)?;
let meshes = load_map(&map)?;
let meshes = load_map(&map, &mut loader)?;
let material = PhysicalMaterial {
albedo: Color {
r: 128,
@ -88,7 +97,21 @@ fn main() -> Result<(), Error> {
.map(|mesh| Model::new_with_material(&renderer.context, &mesh, material.clone()))
.collect::<Result<_, _>>()?;
window.render_loop(move |frame_input| renderer.render(frame_input).unwrap())?;
let mut positions = demo.positions.into_iter();
let forward = vec4(0.0, 0.0, 1.0, 1.0);
window.render_loop(move |frame_input| {
if let Some((position, angle, pitch)) = positions.next() {
let angle_transform =
Mat4::from_angle_y(degrees(angle)) * Mat4::from_angle_x(degrees(pitch));
let target = position + (angle_transform * forward).truncate();
renderer
.camera
.set_view(position, target, vec3(0.0, 1.0, 0.0))
.unwrap();
}
renderer.render(frame_input).unwrap()
})?;
Ok(())
}

View file

@ -8,7 +8,7 @@ pub struct Renderer {
pub context: Context,
pipeline: ForwardPipeline,
control: FirstPerson,
camera: Camera,
pub camera: Camera,
}
impl Renderer {
@ -21,7 +21,7 @@ impl Renderer {
vec3(9.0, 4.0, 5.0),
vec3(0.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
degrees(90.0),
degrees(60.0),
0.1,
30.0,
)?;