1
0
Fork 0
mirror of https://codeberg.org/icewind/vbsp.git synced 2026-06-03 10:44:07 +02:00

some entity parsing

This commit is contained in:
Robin Appelman 2022-02-21 22:08:23 +01:00
commit 26a606db55
5 changed files with 406 additions and 93 deletions

View file

@ -2,7 +2,13 @@ fn main() -> Result<(), vbsp::BspError> {
let mut args = std::env::args();
let _ = args.next();
let data = std::fs::read(args.next().expect("No demo file provided"))?;
vbsp::Bsp::read(&data)?;
let props = vbsp::Bsp::read(&data)?.entities;
for prop in props.iter() {
match prop.parse() {
Ok(prop) => println!("{:#?}", prop),
Err(e) => println!("Failed parsing {:#?}: {:#}", prop, e),
}
}
Ok(())
}

360
src/data/entity.rs Normal file
View file

@ -0,0 +1,360 @@
use crate::error::EntityParseError;
use crate::Vector;
use std::fmt;
use std::fmt::Debug;
use std::str::FromStr;
#[derive(Clone)]
pub struct Entities {
pub entities: String,
}
impl fmt::Debug for Entities {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct Entities<'a> {
#[allow(dead_code)]
entities: Vec<RawEntity<'a>>,
}
Entities {
entities: self.iter().collect(),
}
.fmt(f)
}
}
impl Entities {
pub fn iter(&self) -> impl Iterator<Item = RawEntity<'_>> {
struct Iter<'a> {
buf: &'a str,
}
impl<'a> Iterator for Iter<'a> {
type Item = RawEntity<'a>;
fn next(&mut self) -> Option<Self::Item> {
let start = self.buf.find('{')? + 1;
let end = start + self.buf[start..].find('}')?;
let out = &self.buf[start..end];
self.buf = &self.buf[end + 1..];
Some(RawEntity { buf: out })
}
}
Iter {
buf: &self.entities,
}
}
}
#[derive(Clone)]
pub struct RawEntity<'a> {
buf: &'a str,
}
impl fmt::Debug for RawEntity<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use std::collections::HashMap;
self.properties().collect::<HashMap<_, _>>().fmt(f)
}
}
impl<'a> RawEntity<'a> {
pub fn properties(&self) -> impl Iterator<Item = (&'a str, &'a str)> {
struct Iter<'a> {
buf: &'a str,
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
let start = self.buf.find('"')? + 1;
let end = start + self.buf[start..].find('"')?;
let key = &self.buf[start..end];
let rest = &self.buf[end + 1..];
let start = rest.find('"')? + 1;
let end = start + rest[start..].find('"')?;
let value = &rest[start..end];
self.buf = &rest[end + 1..];
Some((key, value))
}
}
Iter { buf: self.buf }
}
pub fn prop(&self, key: &'static str) -> Result<&'a str, EntityParseError> {
self.properties()
.find_map(|(prop_key, value)| (key == prop_key).then(|| value))
.ok_or(EntityParseError::NoSuchProperty(key))
}
fn prop_parse<T: FromStr>(&self, key: &'static str) -> Result<T, EntityParseError>
where
EntityParseError: From<<T as FromStr>::Err>,
{
Ok(self.prop(key)?.parse()?)
}
fn prop_parse_space_seperated<T: FromStr + Default, const N: usize>(
&self,
key: &'static str,
) -> Result<[T; N], EntityParseError>
where
EntityParseError: From<<T as FromStr>::Err>,
[T; N]: Default,
{
let prop = self.prop(key)?;
let mut values = prop.split(" ").map(T::from_str);
let mut result = <[T; N]>::default();
for i in 0..N {
result[i] = values.next().ok_or(EntityParseError::ElementCount)??;
}
Ok(result)
}
pub fn parse(&self) -> Result<Entity<'a>, EntityParseError> {
let class = self.prop("classname")?;
match class {
"prop_dynamic" => Ok(Entity::PropDynamic(PropDynamic {
angles: self.prop_parse_space_seperated("angles")?,
disable_receive_shadows: self
.prop_parse::<u8>("disablereceiveshadows")
.unwrap_or_default()
> 0,
disable_shadows: self
.prop_parse::<u8>("disablereceiveshadows")
.unwrap_or_default()
> 0,
scale: self.prop_parse("modelscale")?,
model: self.prop("model")?,
origin: self.prop_parse("angles")?,
color: self.prop_parse_space_seperated("rendercolor")?,
name: self.prop("targetname").ok(),
parent: self.prop("parentname").ok(),
})),
"prop_physics_multiplayer" => Ok(Entity::PropPhysics(PropDynamic {
angles: self.prop_parse_space_seperated("angles")?,
disable_receive_shadows: self
.prop_parse::<u8>("disablereceiveshadows")
.unwrap_or_default()
> 0,
disable_shadows: self
.prop_parse::<u8>("disablereceiveshadows")
.unwrap_or_default()
> 0,
scale: self.prop_parse("modelscale")?,
model: self.prop("model")?,
origin: self.prop_parse("angles")?,
color: self.prop_parse_space_seperated("rendercolor")?,
name: self.prop("targetname").ok(),
parent: self.prop("parentname").ok(),
})),
"light_spot" => Ok(Entity::SpotLight(SpotLight {
origin: self.prop_parse("origin")?,
angles: self.prop_parse_space_seperated("angles")?,
color: self.prop_parse_space_seperated("_light")?,
cone: self.prop_parse("_cone")?,
})),
"point_spotlight" => Ok(Entity::SpotLight(SpotLight {
origin: self.prop_parse("origin")?,
angles: self.prop_parse_space_seperated("angles")?,
color: self.prop_parse_space_seperated("rendercolor")?,
cone: self.prop_parse("spotlightwidth")?,
})),
"env_sprite" => Ok(Entity::EnvSprite(EnvSprite {
origin: self.prop_parse("origin")?,
scale: self.prop_parse("scale")?,
model: self.prop("model")?,
color: self.prop_parse_space_seperated("rendercolor")?,
})),
"info_player_teamspawn" => Ok(Entity::Spawn(Spawn {
origin: self.prop_parse("origin")?,
angles: self.prop_parse_space_seperated("angles")?,
target: self.prop("targetname").ok(),
control_point: self.prop("controlpoint").ok(),
start_disabled: self.prop_parse::<u8>("StartDisabled").unwrap_or_default() > 0,
team: self.prop_parse("TeamNum")?,
})),
"func_door" => Ok(Entity::Door(Door {
origin: self.prop_parse("origin")?,
target: self.prop("targetname")?,
speed: self.prop_parse("speed")?,
force_closed: self.prop_parse::<u8>("forceclosed").unwrap_or_default() > 0,
move_direction: self.prop_parse("movedir")?,
model: self.prop("model")?,
})),
"item_ammopack_small" => Ok(Entity::AmmoPack(AmmoPack {
origin: self.prop_parse("origin")?,
ty: PackType::Small,
})),
"item_ammopack_medium" => Ok(Entity::AmmoPack(AmmoPack {
origin: self.prop_parse("origin")?,
ty: PackType::Medium,
})),
"item_ammopack_large" => Ok(Entity::AmmoPack(AmmoPack {
origin: self.prop_parse("origin")?,
ty: PackType::Large,
})),
"item_healthkit_small" => Ok(Entity::HealthPack(HealthPack {
origin: self.prop_parse("origin")?,
ty: PackType::Small,
})),
"item_healthkit_medium" => Ok(Entity::HealthPack(HealthPack {
origin: self.prop_parse("origin")?,
ty: PackType::Medium,
})),
"item_healthkit_large" => Ok(Entity::HealthPack(HealthPack {
origin: self.prop_parse("origin")?,
ty: PackType::Large,
})),
"worldspawn" => Ok(Entity::WorldSpawn(WorldSpawn {
min: self.prop_parse("world_mins")?,
max: self.prop_parse("world_maxs")?,
detail_vbsp: self.prop("detailvbsp")?,
detail_material: self.prop("detailmaterial")?,
comment: self.prop("comment").ok(),
skybox: self.prop("skyname")?,
version: self.prop_parse("mapversion")?,
})),
"info_observer_point" => Ok(Entity::ObserverPoint(ObserverPoint {
start_disabled: self.prop_parse::<u8>("StartDisabled").unwrap_or_default() > 0,
angles: self.prop_parse_space_seperated("angles")?,
origin: self.prop_parse("origin")?,
target: self.prop("targetname").ok(),
parent: self.prop("parentname").ok(),
})),
"func_brush" => Ok(Entity::Brush(BrushEntity {
model: self.prop("model")?,
start_disabled: self.prop_parse::<u8>("StartDisabled").unwrap_or_default() > 0,
origin: self.prop_parse("origin")?,
color: self.prop_parse_space_seperated("rendercolor")?,
})),
_ => Ok(Entity::Unknown(self.clone())),
}
}
}
#[derive(Debug, Clone)]
pub enum Entity<'a> {
SpotLight(SpotLight),
PropDynamic(PropDynamic<'a>),
PropPhysics(PropDynamic<'a>),
EnvSprite(EnvSprite<'a>),
Spawn(Spawn<'a>),
Door(Door<'a>),
AmmoPack(AmmoPack),
HealthPack(HealthPack),
WorldSpawn(WorldSpawn<'a>),
ObserverPoint(ObserverPoint<'a>),
Brush(BrushEntity<'a>),
Unknown(RawEntity<'a>),
}
#[derive(Debug, Clone)]
pub struct SpotLight {
pub origin: Vector,
pub angles: [f32; 3],
pub color: [u8; 3],
pub cone: u8,
}
#[derive(Debug, Clone)]
pub struct PropDynamic<'a> {
pub angles: [f32; 3],
pub disable_receive_shadows: bool,
pub disable_shadows: bool,
pub scale: f32,
pub model: &'a str,
pub origin: Vector,
pub color: [u8; 3],
pub name: Option<&'a str>,
pub parent: Option<&'a str>,
}
#[derive(Debug, Clone)]
pub struct EnvSprite<'a> {
pub origin: Vector,
pub scale: f32,
pub model: &'a str,
pub color: [u8; 3],
}
#[derive(Debug, Clone)]
pub struct Spawn<'a> {
pub origin: Vector,
pub angles: [f32; 3],
pub target: Option<&'a str>,
pub control_point: Option<&'a str>,
pub start_disabled: bool,
pub team: u8,
}
#[derive(Debug, Clone)]
pub struct Door<'a> {
pub origin: Vector,
pub target: &'a str,
pub speed: f32,
pub force_closed: bool,
pub move_direction: Vector,
pub model: &'a str,
}
#[derive(Debug, Clone)]
pub struct AmmoPack {
pub origin: Vector,
pub ty: PackType,
}
#[derive(Debug, Clone)]
pub struct HealthPack {
pub origin: Vector,
pub ty: PackType,
}
#[derive(Debug, Clone)]
pub enum PackType {
Small,
Medium,
Large,
}
#[derive(Debug, Clone)]
pub struct WorldSpawn<'a> {
pub min: Vector,
pub max: Vector,
pub detail_vbsp: &'a str,
pub detail_material: &'a str,
pub comment: Option<&'a str>,
pub skybox: &'a str,
pub version: u32,
}
#[derive(Debug, Clone)]
pub struct ObserverPoint<'a> {
pub start_disabled: bool,
pub angles: [f32; 3],
pub origin: Vector,
pub target: Option<&'a str>,
pub parent: Option<&'a str>,
}
#[derive(Debug, Clone)]
pub struct BrushEntity<'a> {
pub model: &'a str,
pub origin: Vector,
pub start_disabled: bool,
pub color: [f32; 3],
}

View file

@ -1,7 +1,9 @@
mod displacement;
mod entity;
mod vector;
pub use self::displacement::*;
pub use self::entity::*;
pub use self::vector::*;
use crate::bspfile::LumpType;
use crate::StringError;
@ -73,98 +75,6 @@ pub struct LeafFace {
pub face: u16,
}
#[derive(Clone)]
pub struct Entities {
pub entities: String,
}
impl fmt::Debug for Entities {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct Entities<'a> {
#[allow(dead_code)]
entities: Vec<Entity<'a>>,
}
Entities {
entities: self.iter().collect(),
}
.fmt(f)
}
}
impl Entities {
pub fn iter(&self) -> impl Iterator<Item = Entity<'_>> {
struct Iter<'a> {
buf: &'a str,
}
impl<'a> Iterator for Iter<'a> {
type Item = Entity<'a>;
fn next(&mut self) -> Option<Self::Item> {
let start = self.buf.find('{')? + 1;
let end = start + self.buf[start..].find('}')?;
let out = &self.buf[start..end];
self.buf = &self.buf[end + 1..];
Some(Entity { buf: out })
}
}
Iter {
buf: &self.entities,
}
}
}
#[derive(Clone)]
pub struct Entity<'a> {
buf: &'a str,
}
impl fmt::Debug for Entity<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use std::collections::HashMap;
self.properties().collect::<HashMap<_, _>>().fmt(f)
}
}
impl<'a> Entity<'a> {
pub fn properties(&self) -> impl Iterator<Item = (&'a str, &'a str)> {
struct Iter<'a> {
buf: &'a str,
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
let start = self.buf.find('"')? + 1;
let end = start + self.buf[start..].find('"')?;
let key = &self.buf[start..end];
let rest = &self.buf[end + 1..];
let start = rest.find('"')? + 1;
let end = start + rest[start..].find('"')?;
let value = &rest[start..end];
self.buf = &rest[end + 1..];
Some((key, value))
}
}
Iter { buf: self.buf }
}
}
bitflags! {
#[derive(BinRead)]
pub struct TextureFlags: u32 {

View file

@ -1,7 +1,9 @@
use crate::error::EntityParseError;
use binrw::BinRead;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::ops::{Add, Mul, Sub};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, BinRead)]
pub struct Vector {
@ -74,8 +76,30 @@ impl From<Vector> for [f32; 3] {
}
}
impl From<[f32; 3]> for Vector {
fn from(vector: [f32; 3]) -> Self {
Vector {
x: vector[0],
y: vector[1],
z: vector[2],
}
}
}
impl From<&Vector> for [f32; 3] {
fn from(vector: &Vector) -> Self {
[vector.x, vector.y, vector.z]
}
}
impl FromStr for Vector {
type Err = EntityParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut floats = s.split(" ").map(f32::from_str);
let x = floats.next().ok_or(EntityParseError::ElementCount)??;
let y = floats.next().ok_or(EntityParseError::ElementCount)??;
let z = floats.next().ok_or(EntityParseError::ElementCount)??;
Ok(Vector { x, y, z })
}
}

View file

@ -1,4 +1,5 @@
use crate::data::*;
use std::num::{ParseFloatError, ParseIntError};
use thiserror::Error;
#[derive(Debug, Error)]
@ -76,3 +77,15 @@ pub enum ValidationError {
size: usize,
},
}
#[derive(Debug, Error)]
pub enum EntityParseError {
#[error("no such property: {0}")]
NoSuchProperty(&'static str),
#[error("wrong number of elements")]
ElementCount,
#[error(transparent)]
Float(#[from] ParseFloatError),
#[error(transparent)]
Int(#[from] ParseIntError),
}