mirror of
https://codeberg.org/icewind/vbsp.git
synced 2026-06-03 10:44:07 +02:00
derive macros for entity parsing
This commit is contained in:
parent
db9ca18975
commit
6ced393424
7 changed files with 384 additions and 158 deletions
|
|
@ -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],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue