mirror of
https://codeberg.org/icewind/vbspview.git
synced 2026-06-03 10:14:10 +02:00
demo playing
This commit is contained in:
parent
ec62b4e509
commit
90a5966682
8 changed files with 502 additions and 36 deletions
|
|
@ -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
74
src/demo.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
51
src/main.rs
51
src/main.rs
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue