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:
parent
db9ca18975
commit
6ced393424
7 changed files with 384 additions and 158 deletions
|
|
@ -19,6 +19,7 @@ lzma-rs = "0.2.0"
|
||||||
binrw = "0.8.0"
|
binrw = "0.8.0"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
num_enum = "0.5.6"
|
num_enum = "0.5.6"
|
||||||
|
vbsp-derive = { path = "derive", version = "*" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
obj = "0.10"
|
obj = "0.10"
|
||||||
|
|
|
||||||
21
derive/Cargo.toml
Normal file
21
derive/Cargo.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "vbsp-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||||
|
homepage = "https://github.com/icewind1991/vbsp"
|
||||||
|
repository = "https://github.com/icewind1991/vbsp"
|
||||||
|
description = "Derive macros used by vbsp."
|
||||||
|
license = "MIT"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "1.0.82"
|
||||||
|
quote = "1.0.10"
|
||||||
|
proc-macro2 = "1.0.33"
|
||||||
|
syn_util = "0.4.2"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
__vbsp_as_self = []
|
||||||
190
derive/src/entity.rs
Normal file
190
derive/src/entity.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
use proc_macro2::{Ident, TokenStream};
|
||||||
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
|
use syn::{
|
||||||
|
Data, DataEnum, DataStruct, DeriveInput, Field, GenericParam, Path, PathArguments, PathSegment,
|
||||||
|
Type, TypePath, Variant,
|
||||||
|
};
|
||||||
|
use syn_util::{contains_attribute, get_attribute_value};
|
||||||
|
|
||||||
|
type Result<T, E = String> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
pub fn derive_entity(input: DeriveInput) -> Result<proc_macro2::TokenStream> {
|
||||||
|
if input.generics.lifetimes().count() > 1 {
|
||||||
|
return Err("Can't derive Entity on structs or entities with more than 1 lifetime".into());
|
||||||
|
}
|
||||||
|
let source_lifetime = input
|
||||||
|
.generics
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.find(|param| matches!(param, GenericParam::Lifetime(_)));
|
||||||
|
|
||||||
|
match &input.data {
|
||||||
|
Data::Struct(data) => derive_entity_struct(&input, data, source_lifetime),
|
||||||
|
Data::Enum(data) => derive_entity_enum(&input, data, source_lifetime),
|
||||||
|
_ => Err("Can only derive Entity for structs and enums".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_entity_enum(
|
||||||
|
input: &DeriveInput,
|
||||||
|
data: &DataEnum,
|
||||||
|
source_lifetime: Option<&GenericParam>,
|
||||||
|
) -> Result<proc_macro2::TokenStream> {
|
||||||
|
let variants = data
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.filter(|variant| !contains_attribute(&variant.attrs, &["entity", "default"]))
|
||||||
|
.map(EntityVariant::try_from)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let default = &data
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.find(|variant| contains_attribute(&variant.attrs, &["entity", "default"]))
|
||||||
|
.ok_or("Enum must have one variant with `#[entity(default)]` set")?
|
||||||
|
.ident;
|
||||||
|
|
||||||
|
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
let name = &input.ident;
|
||||||
|
let lifetime_or_default = LifetimeOrAnonymous(source_lifetime);
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
impl #impl_generics TryFrom<crate::RawEntity<#lifetime_or_default>> for #name #ty_generics #where_clause {
|
||||||
|
type Error = crate::error::EntityParseError;
|
||||||
|
|
||||||
|
fn try_from(raw: crate::RawEntity<#source_lifetime>) -> Result<Self, Self::Error> {
|
||||||
|
let class = raw.prop("classname")?;
|
||||||
|
Ok(match class {
|
||||||
|
#(#variants)*
|
||||||
|
_ => Self::#default(raw),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_entity_struct(
|
||||||
|
input: &DeriveInput,
|
||||||
|
data: &DataStruct,
|
||||||
|
source_lifetime: Option<&GenericParam>,
|
||||||
|
) -> Result<proc_macro2::TokenStream> {
|
||||||
|
let fields = data
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(EntityField::try_from)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
let name = &input.ident;
|
||||||
|
let lifetime_or_default = LifetimeOrAnonymous(source_lifetime);
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
impl #impl_generics TryFrom<crate::RawEntity<#lifetime_or_default>> for #name #ty_generics #where_clause {
|
||||||
|
type Error = crate::error::EntityParseError;
|
||||||
|
|
||||||
|
fn try_from(raw: crate::RawEntity<#source_lifetime>) -> Result<Self, Self::Error> {
|
||||||
|
Ok(#name {
|
||||||
|
#(#fields)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LifetimeOrAnonymous<'a>(Option<&'a GenericParam>);
|
||||||
|
|
||||||
|
impl ToTokens for LifetimeOrAnonymous<'_> {
|
||||||
|
fn to_tokens(&self, stream: &mut TokenStream) {
|
||||||
|
match &self.0 {
|
||||||
|
Some(params) => params.to_tokens(stream),
|
||||||
|
None => stream.append_all(quote! {'_}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EntityField<'a> {
|
||||||
|
field: &'a Ident,
|
||||||
|
name: String,
|
||||||
|
default: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a Field> for EntityField<'a> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(field: &'a Field) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let ident = &field
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| format!("Can't derive Entity on structs with unnamed fields"))?;
|
||||||
|
let name = get_attribute_value(&field.attrs, &["entity", "name"])
|
||||||
|
.unwrap_or_else(|| ident.to_string());
|
||||||
|
let default = contains_attribute(&field.attrs, &["entity", "default"]);
|
||||||
|
Ok(EntityField {
|
||||||
|
field: ident,
|
||||||
|
name,
|
||||||
|
default,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for EntityField<'_> {
|
||||||
|
fn to_tokens(&self, stream: &mut TokenStream) {
|
||||||
|
let EntityField { field, name, .. } = &self;
|
||||||
|
let tokens = if self.default {
|
||||||
|
quote! {#field: raw.prop_parse(#name).unwrap_or_default(),}
|
||||||
|
} else {
|
||||||
|
quote! {#field: raw.prop_parse(#name)?,}
|
||||||
|
};
|
||||||
|
stream.append_all(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EntityVariant<'a> {
|
||||||
|
name: String,
|
||||||
|
variant: &'a Ident,
|
||||||
|
ty: &'a Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a Variant> for EntityVariant<'a> {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: &'a Variant) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let name = get_attribute_value(&value.attrs, &["entity", "name"]).ok_or_else(|| {
|
||||||
|
"All variants must have the `#[entity(name)]` or `#[entity(default)]` attribute set"
|
||||||
|
.to_string()
|
||||||
|
})?;
|
||||||
|
if value.fields.len() != 1 {
|
||||||
|
return Err("All enum variants must have exactly one field".into());
|
||||||
|
}
|
||||||
|
let field = value.fields.iter().next().unwrap();
|
||||||
|
let path = match &field.ty {
|
||||||
|
Type::Path(TypePath { path, .. }) => path,
|
||||||
|
_ => return Err("Varients can only contain plain types".into()),
|
||||||
|
};
|
||||||
|
Ok(EntityVariant {
|
||||||
|
name,
|
||||||
|
variant: &value.ident,
|
||||||
|
ty: &path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for EntityVariant<'_> {
|
||||||
|
fn to_tokens(&self, stream: &mut TokenStream) {
|
||||||
|
let EntityVariant { name, variant, ty } = &self;
|
||||||
|
|
||||||
|
// strip lifetime params
|
||||||
|
let ty = Path {
|
||||||
|
leading_colon: ty.leading_colon.clone(),
|
||||||
|
segments: ty
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.map(|segment| PathSegment {
|
||||||
|
ident: segment.ident.clone(),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
stream.append_all(quote! {#name => Self::#variant(#ty::try_from(raw)?),});
|
||||||
|
}
|
||||||
|
}
|
||||||
19
derive/src/lib.rs
Normal file
19
derive/src/lib.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
|
mod entity;
|
||||||
|
|
||||||
|
#[proc_macro_derive(Entity, attributes(entity))]
|
||||||
|
pub fn derive_entity(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
derive_wrapper(input, entity::derive_entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_wrapper<F: Fn(DeriveInput) -> Result<proc_macro2::TokenStream, String>>(
|
||||||
|
input: proc_macro::TokenStream,
|
||||||
|
derive: F,
|
||||||
|
) -> proc_macro::TokenStream {
|
||||||
|
match derive(parse_macro_input!(input as DeriveInput)) {
|
||||||
|
Ok(tokens) => tokens.into(),
|
||||||
|
Err(e) => quote!(compile_error!(#e)).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
27
derive/tests/expand.rs
Normal file
27
derive/tests/expand.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use vbsp::RawEntity;
|
||||||
|
use vbsp_derive::Entity;
|
||||||
|
|
||||||
|
#[derive(Entity)]
|
||||||
|
pub struct SpotLight {
|
||||||
|
pub angles: [f32; 3],
|
||||||
|
#[entity(name = "render_color")]
|
||||||
|
pub color: [u8; 3],
|
||||||
|
pub cone: u8,
|
||||||
|
#[entity(default)]
|
||||||
|
pub optional: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Entity)]
|
||||||
|
pub struct Lifetime<'a> {
|
||||||
|
pub model: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Entity)]
|
||||||
|
pub enum Entity<'a> {
|
||||||
|
// #[entity(name = "spot")]
|
||||||
|
// SpotLight(SpotLight),
|
||||||
|
#[entity(name = "bar")]
|
||||||
|
Foo(Lifetime<'a>),
|
||||||
|
#[entity(default)]
|
||||||
|
Unknown(RawEntity<'a>),
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ use crate::Vector;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use vbsp_derive::Entity;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Entities {
|
pub struct Entities {
|
||||||
|
|
@ -101,260 +102,227 @@ impl<'a> RawEntity<'a> {
|
||||||
.ok_or(EntityParseError::NoSuchProperty(key))
|
.ok_or(EntityParseError::NoSuchProperty(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prop_parse<T: FromStr>(&self, key: &'static str) -> Result<T, EntityParseError>
|
fn prop_parse<T: EntityProp<'a>>(&self, key: &'static str) -> Result<T, EntityParseError> {
|
||||||
where
|
T::parse(self.prop(key)?)
|
||||||
EntityParseError: From<<T as FromStr>::Err>,
|
|
||||||
{
|
|
||||||
Ok(self.prop(key)?.parse()?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prop_parse_space_seperated<T: FromStr + Default, const N: usize>(
|
pub fn parse(&self) -> Result<Entity<'a>, EntityParseError> {
|
||||||
&self,
|
self.clone().try_into()
|
||||||
key: &'static str,
|
}
|
||||||
) -> Result<[T; N], EntityParseError>
|
}
|
||||||
where
|
|
||||||
|
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>,
|
EntityParseError: From<<T as FromStr>::Err>,
|
||||||
[T; N]: Default,
|
[T; N]: Default,
|
||||||
{
|
{
|
||||||
let prop = self.prop(key)?;
|
fn parse(raw: &'_ str) -> Result<Self, EntityParseError> {
|
||||||
let mut values = prop.split(" ").map(T::from_str);
|
let mut values = raw.split(" ").map(T::from_str);
|
||||||
let mut result = <[T; N]>::default();
|
let mut result = <[T; N]>::default();
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
result[i] = values.next().ok_or(EntityParseError::ElementCount)??;
|
result[i] = values.next().ok_or(EntityParseError::ElementCount)??;
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse(&self) -> Result<Entity<'a>, EntityParseError> {
|
impl<'a> EntityProp<'a> for &'a str {
|
||||||
let class = self.prop("classname")?;
|
fn parse(raw: &'a str) -> Result<Self, EntityParseError> {
|
||||||
match class {
|
Ok(raw)
|
||||||
"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)]
|
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> {
|
pub enum Entity<'a> {
|
||||||
|
#[entity(name = "point_spotlight")]
|
||||||
SpotLight(SpotLight),
|
SpotLight(SpotLight),
|
||||||
|
#[entity(name = "light_spot")]
|
||||||
|
LightSpot(LightSpot),
|
||||||
|
#[entity(name = "prop_dynamic")]
|
||||||
PropDynamic(PropDynamic<'a>),
|
PropDynamic(PropDynamic<'a>),
|
||||||
|
#[entity(name = "prop_physics_multiplayer")]
|
||||||
PropPhysics(PropDynamic<'a>),
|
PropPhysics(PropDynamic<'a>),
|
||||||
|
#[entity(name = "env_sprite")]
|
||||||
EnvSprite(EnvSprite<'a>),
|
EnvSprite(EnvSprite<'a>),
|
||||||
|
#[entity(name = "info_player_teamspawn")]
|
||||||
Spawn(Spawn<'a>),
|
Spawn(Spawn<'a>),
|
||||||
|
#[entity(name = "func_door")]
|
||||||
Door(Door<'a>),
|
Door(Door<'a>),
|
||||||
AmmoPack(AmmoPack),
|
#[entity(name = "worldspawn")]
|
||||||
HealthPack(HealthPack),
|
|
||||||
WorldSpawn(WorldSpawn<'a>),
|
WorldSpawn(WorldSpawn<'a>),
|
||||||
|
#[entity(name = "info_observer_point")]
|
||||||
ObserverPoint(ObserverPoint<'a>),
|
ObserverPoint(ObserverPoint<'a>),
|
||||||
|
#[entity(name = "func_brush")]
|
||||||
Brush(BrushEntity<'a>),
|
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>),
|
Unknown(RawEntity<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub struct SpotLight {
|
pub struct SpotLight {
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
pub angles: [f32; 3],
|
pub angles: [f32; 3],
|
||||||
|
#[entity(name = "rendercolor")]
|
||||||
pub color: [u8; 3],
|
pub color: [u8; 3],
|
||||||
|
#[entity(name = "spotlightwidth")]
|
||||||
pub cone: u8,
|
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 struct PropDynamic<'a> {
|
||||||
pub angles: [f32; 3],
|
pub angles: [f32; 3],
|
||||||
|
#[entity(name = "disablereceiveshadows", default)]
|
||||||
pub disable_receive_shadows: bool,
|
pub disable_receive_shadows: bool,
|
||||||
|
#[entity(name = "disableshadows", default)]
|
||||||
pub disable_shadows: bool,
|
pub disable_shadows: bool,
|
||||||
|
#[entity(name = "modelscale")]
|
||||||
pub scale: f32,
|
pub scale: f32,
|
||||||
pub model: &'a str,
|
pub model: &'a str,
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
|
#[entity(name = "rendercolor")]
|
||||||
pub color: [u8; 3],
|
pub color: [u8; 3],
|
||||||
|
#[entity(name = "targetname", default)]
|
||||||
pub name: Option<&'a str>,
|
pub name: Option<&'a str>,
|
||||||
|
#[entity(name = "parentname", default)]
|
||||||
pub parent: Option<&'a str>,
|
pub parent: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub struct EnvSprite<'a> {
|
pub struct EnvSprite<'a> {
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
pub scale: f32,
|
pub scale: f32,
|
||||||
pub model: &'a str,
|
pub model: &'a str,
|
||||||
|
#[entity(name = "rendercolor")]
|
||||||
pub color: [u8; 3],
|
pub color: [u8; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub struct Spawn<'a> {
|
pub struct Spawn<'a> {
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
pub angles: [f32; 3],
|
pub angles: [f32; 3],
|
||||||
|
#[entity(name = "targetname", default)]
|
||||||
pub target: Option<&'a str>,
|
pub target: Option<&'a str>,
|
||||||
|
#[entity(name = "controlpoint", default)]
|
||||||
pub control_point: Option<&'a str>,
|
pub control_point: Option<&'a str>,
|
||||||
|
#[entity(name = "StartDisabled", default)]
|
||||||
pub start_disabled: bool,
|
pub start_disabled: bool,
|
||||||
|
#[entity(name = "TeamNum")]
|
||||||
pub team: u8,
|
pub team: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub struct Door<'a> {
|
pub struct Door<'a> {
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
|
#[entity(name = "targetname", default)]
|
||||||
pub target: &'a str,
|
pub target: &'a str,
|
||||||
pub speed: f32,
|
pub speed: f32,
|
||||||
|
#[entity(name = "forceclosed", default)]
|
||||||
pub force_closed: bool,
|
pub force_closed: bool,
|
||||||
|
#[entity(name = "movedir")]
|
||||||
pub move_direction: Vector,
|
pub move_direction: Vector,
|
||||||
pub model: &'a str,
|
pub model: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub struct AmmoPack {
|
pub struct AmmoPack {
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
pub ty: PackType,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub struct HealthPack {
|
pub struct HealthPack {
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
pub ty: PackType,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub enum PackType {
|
|
||||||
Small,
|
|
||||||
Medium,
|
|
||||||
Large,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct WorldSpawn<'a> {
|
pub struct WorldSpawn<'a> {
|
||||||
|
#[entity(name = "world_mins")]
|
||||||
pub min: Vector,
|
pub min: Vector,
|
||||||
|
#[entity(name = "world_mins")]
|
||||||
pub max: Vector,
|
pub max: Vector,
|
||||||
|
#[entity(name = "detailvbsp")]
|
||||||
pub detail_vbsp: &'a str,
|
pub detail_vbsp: &'a str,
|
||||||
|
#[entity(name = "detailmaterial")]
|
||||||
pub detail_material: &'a str,
|
pub detail_material: &'a str,
|
||||||
|
#[entity(default)]
|
||||||
pub comment: Option<&'a str>,
|
pub comment: Option<&'a str>,
|
||||||
|
#[entity(name = "skyname")]
|
||||||
pub skybox: &'a str,
|
pub skybox: &'a str,
|
||||||
|
#[entity(name = "mapversion")]
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub struct ObserverPoint<'a> {
|
pub struct ObserverPoint<'a> {
|
||||||
|
#[entity(name = "StartDisabled", default)]
|
||||||
pub start_disabled: bool,
|
pub start_disabled: bool,
|
||||||
pub angles: [f32; 3],
|
pub angles: [f32; 3],
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
|
#[entity(name = "targetname", default)]
|
||||||
pub target: Option<&'a str>,
|
pub target: Option<&'a str>,
|
||||||
|
#[entity(name = "parentname", default)]
|
||||||
pub parent: Option<&'a str>,
|
pub parent: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Entity)]
|
||||||
pub struct BrushEntity<'a> {
|
pub struct BrushEntity<'a> {
|
||||||
pub model: &'a str,
|
pub model: &'a str,
|
||||||
pub origin: Vector,
|
pub origin: Vector,
|
||||||
|
#[entity(name = "StartDisabled", default)]
|
||||||
pub start_disabled: bool,
|
pub start_disabled: bool,
|
||||||
|
#[entity(name = "rendercolor")]
|
||||||
pub color: [f32; 3],
|
pub color: [f32; 3],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
mod bspfile;
|
mod bspfile;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
mod error;
|
pub mod error;
|
||||||
mod handle;
|
mod handle;
|
||||||
mod reader;
|
mod reader;
|
||||||
|
|
||||||
use crate::bspfile::LumpType;
|
use crate::bspfile::LumpType;
|
||||||
pub use crate::data::TextureFlags;
|
pub use crate::data::TextureFlags;
|
||||||
pub use crate::data::Vector;
|
pub use crate::data::Vector;
|
||||||
use crate::data::*;
|
pub use crate::data::*;
|
||||||
use crate::error::ValidationError;
|
use crate::error::ValidationError;
|
||||||
pub use crate::handle::Handle;
|
pub use crate::handle::Handle;
|
||||||
use binrw::io::Cursor;
|
use binrw::io::Cursor;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue