1
0
Fork 0
mirror of https://codeberg.org/icewind/vbsp.git synced 2026-06-03 18:54:05 +02:00

derive macros for entity parsing

This commit is contained in:
Robin Appelman 2022-02-27 17:40:01 +01:00
commit 6ced393424
7 changed files with 384 additions and 158 deletions

View file

@ -3,6 +3,7 @@ use crate::Vector;
use std::fmt;
use std::fmt::Debug;
use std::str::FromStr;
use vbsp_derive::Entity;
#[derive(Clone)]
pub struct Entities {
@ -101,260 +102,227 @@ impl<'a> RawEntity<'a> {
.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<T: EntityProp<'a>>(&self, key: &'static str) -> Result<T, EntityParseError> {
T::parse(self.prop(key)?)
}
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);
pub fn parse(&self) -> Result<Entity<'a>, EntityParseError> {
self.clone().try_into()
}
}
trait EntityProp<'a>: Sized {
fn parse(raw: &'a str) -> Result<Self, EntityParseError>;
}
trait FromStrProp: FromStr {}
impl FromStrProp for u8 {}
impl FromStrProp for f32 {}
impl FromStrProp for u32 {}
impl FromStrProp for Vector {}
impl<T: FromStrProp> EntityProp<'_> for T
where
EntityParseError: From<<T as FromStr>::Err>,
{
fn parse(raw: &'_ str) -> Result<Self, EntityParseError> {
Ok(raw.parse()?)
}
}
impl<T: FromStrProp, const N: usize> EntityProp<'_> for [T; N]
where
EntityParseError: From<<T as FromStr>::Err>,
[T; N]: Default,
{
fn parse(raw: &'_ str) -> Result<Self, EntityParseError> {
let mut values = raw.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())),
}
impl<'a> EntityProp<'a> for &'a str {
fn parse(raw: &'a str) -> Result<Self, EntityParseError> {
Ok(raw)
}
}
#[derive(Debug, Clone)]
impl EntityProp<'_> for bool {
fn parse(raw: &'_ str) -> Result<Self, EntityParseError> {
Ok(raw != "0")
}
}
impl<'a, T: EntityProp<'a>> EntityProp<'a> for Option<T> {
fn parse(raw: &'a str) -> Result<Self, EntityParseError> {
Ok(Some(T::parse(raw)?))
}
}
#[derive(Debug, Clone, Entity)]
pub enum Entity<'a> {
#[entity(name = "point_spotlight")]
SpotLight(SpotLight),
#[entity(name = "light_spot")]
LightSpot(LightSpot),
#[entity(name = "prop_dynamic")]
PropDynamic(PropDynamic<'a>),
#[entity(name = "prop_physics_multiplayer")]
PropPhysics(PropDynamic<'a>),
#[entity(name = "env_sprite")]
EnvSprite(EnvSprite<'a>),
#[entity(name = "info_player_teamspawn")]
Spawn(Spawn<'a>),
#[entity(name = "func_door")]
Door(Door<'a>),
AmmoPack(AmmoPack),
HealthPack(HealthPack),
#[entity(name = "worldspawn")]
WorldSpawn(WorldSpawn<'a>),
#[entity(name = "info_observer_point")]
ObserverPoint(ObserverPoint<'a>),
#[entity(name = "func_brush")]
Brush(BrushEntity<'a>),
#[entity(name = "item_ammopack_small")]
AmmoPackSmall(AmmoPack),
#[entity(name = "item_ammopack_medium")]
AmmoPackMedium(AmmoPack),
#[entity(name = "item_ammopack_large")]
HealthPackLarge(HealthPack),
#[entity(name = "item_healthkit_small")]
HealthPackSmall(HealthPack),
#[entity(name = "item_healthkit_medium")]
HealthPackMedium(HealthPack),
#[entity(name = "item_healthkit_large")]
AmmoPackLarge(AmmoPack),
#[entity(default)]
Unknown(RawEntity<'a>),
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct SpotLight {
pub origin: Vector,
pub angles: [f32; 3],
#[entity(name = "rendercolor")]
pub color: [u8; 3],
#[entity(name = "spotlightwidth")]
pub cone: u8,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct LightSpot {
pub origin: Vector,
pub angles: [f32; 3],
#[entity(name = "_light")]
pub color: [u8; 3],
#[entity(name = "_cone")]
pub cone: u8,
}
#[derive(Debug, Clone, Entity)]
pub struct PropDynamic<'a> {
pub angles: [f32; 3],
#[entity(name = "disablereceiveshadows", default)]
pub disable_receive_shadows: bool,
#[entity(name = "disableshadows", default)]
pub disable_shadows: bool,
#[entity(name = "modelscale")]
pub scale: f32,
pub model: &'a str,
pub origin: Vector,
#[entity(name = "rendercolor")]
pub color: [u8; 3],
#[entity(name = "targetname", default)]
pub name: Option<&'a str>,
#[entity(name = "parentname", default)]
pub parent: Option<&'a str>,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct EnvSprite<'a> {
pub origin: Vector,
pub scale: f32,
pub model: &'a str,
#[entity(name = "rendercolor")]
pub color: [u8; 3],
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct Spawn<'a> {
pub origin: Vector,
pub angles: [f32; 3],
#[entity(name = "targetname", default)]
pub target: Option<&'a str>,
#[entity(name = "controlpoint", default)]
pub control_point: Option<&'a str>,
#[entity(name = "StartDisabled", default)]
pub start_disabled: bool,
#[entity(name = "TeamNum")]
pub team: u8,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct Door<'a> {
pub origin: Vector,
#[entity(name = "targetname", default)]
pub target: &'a str,
pub speed: f32,
#[entity(name = "forceclosed", default)]
pub force_closed: bool,
#[entity(name = "movedir")]
pub move_direction: Vector,
pub model: &'a str,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct AmmoPack {
pub origin: Vector,
pub ty: PackType,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct HealthPack {
pub origin: Vector,
pub ty: PackType,
}
#[derive(Debug, Clone)]
pub enum PackType {
Small,
Medium,
Large,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct WorldSpawn<'a> {
#[entity(name = "world_mins")]
pub min: Vector,
#[entity(name = "world_mins")]
pub max: Vector,
#[entity(name = "detailvbsp")]
pub detail_vbsp: &'a str,
#[entity(name = "detailmaterial")]
pub detail_material: &'a str,
#[entity(default)]
pub comment: Option<&'a str>,
#[entity(name = "skyname")]
pub skybox: &'a str,
#[entity(name = "mapversion")]
pub version: u32,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct ObserverPoint<'a> {
#[entity(name = "StartDisabled", default)]
pub start_disabled: bool,
pub angles: [f32; 3],
pub origin: Vector,
#[entity(name = "targetname", default)]
pub target: Option<&'a str>,
#[entity(name = "parentname", default)]
pub parent: Option<&'a str>,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Entity)]
pub struct BrushEntity<'a> {
pub model: &'a str,
pub origin: Vector,
#[entity(name = "StartDisabled", default)]
pub start_disabled: bool,
#[entity(name = "rendercolor")]
pub color: [f32; 3],
}

View file

@ -1,13 +1,13 @@
mod bspfile;
pub mod data;
mod error;
pub mod error;
mod handle;
mod reader;
use crate::bspfile::LumpType;
pub use crate::data::TextureFlags;
pub use crate::data::Vector;
use crate::data::*;
pub use crate::data::*;
use crate::error::ValidationError;
pub use crate::handle::Handle;
use binrw::io::Cursor;