mirror of
https://codeberg.org/icewind/vbsp.git
synced 2026-06-03 18:54:05 +02:00
split off common types into a separate crate for easier semver management
This commit is contained in:
parent
57e1eac4b6
commit
76918206da
19 changed files with 394 additions and 316 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -1,6 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
|
|
@ -930,11 +930,22 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
|
"vbsp-common",
|
||||||
"vbsp-entities-tf2",
|
"vbsp-entities-tf2",
|
||||||
"vdf-reader 0.3.0",
|
"vdf-reader 0.3.0",
|
||||||
"zip-lzma",
|
"zip-lzma",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vbsp-common"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"binrw",
|
||||||
|
"cgmath",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vbsp-entities-tf2"
|
name = "vbsp-entities-tf2"
|
||||||
version = "0.1.0-egg"
|
version = "0.1.0-egg"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ exclude = ["*.bsp"]
|
||||||
rust-version = "1.85.0"
|
rust-version = "1.85.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
vbsp-common = { version = "0.1.0", path = "common" }
|
||||||
arrayvec = "0.7.6"
|
arrayvec = "0.7.6"
|
||||||
bitflags = "2.8.0"
|
bitflags = "2.8.0"
|
||||||
bv = "0.11.1"
|
bv = "0.11.1"
|
||||||
|
|
@ -39,3 +40,6 @@ opt-level = 2
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bench"
|
name = "bench"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["common"]
|
||||||
7
common/Cargo.lock
generated
Normal file
7
common/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types"
|
||||||
|
version = "0.1.0"
|
||||||
16
common/Cargo.toml
Normal file
16
common/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "vbsp-common"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||||
|
homepage = "https://github.com/icewind1991/vbsp"
|
||||||
|
repository = "https://github.com/icewind1991/vbsp"
|
||||||
|
description = "Common types and helpers for valve bsp files."
|
||||||
|
license = "MIT"
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.85.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
binrw = "0.14.1"
|
||||||
|
serde = { version = "1.0.218", features = ["derive"] }
|
||||||
|
cgmath = "0.18.0"
|
||||||
|
thiserror = "2.0.11"
|
||||||
45
common/src/angle.rs
Normal file
45
common/src/angle.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::EntityParseError;
|
||||||
|
use binrw::BinRead;
|
||||||
|
use cgmath::{Deg, Quaternion, Rotation3};
|
||||||
|
use serde::de::{Error, Unexpected};
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, BinRead, Default)]
|
||||||
|
pub struct Angles {
|
||||||
|
pub pitch: f32,
|
||||||
|
pub yaw: f32,
|
||||||
|
pub roll: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Angles {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let str = <&str>::deserialize(deserializer)?;
|
||||||
|
str.parse()
|
||||||
|
.map_err(|_| D::Error::invalid_value(Unexpected::Other(str), &"a list of angles"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Angles {
|
||||||
|
type Err = EntityParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut floats = s.split_whitespace().map(f32::from_str);
|
||||||
|
let pitch = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||||
|
let yaw = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||||
|
let roll = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||||
|
Ok(Angles { pitch, yaw, roll })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Angles {
|
||||||
|
pub fn as_quaternion(&self) -> Quaternion<f32> {
|
||||||
|
// angles are applied in roll, pitch, yaw order
|
||||||
|
Quaternion::from_angle_y(Deg(self.yaw))
|
||||||
|
* Quaternion::from_angle_x(Deg(self.pitch))
|
||||||
|
* Quaternion::from_angle_z(Deg(self.roll))
|
||||||
|
}
|
||||||
|
}
|
||||||
38
common/src/bool.rs
Normal file
38
common/src/bool.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
use serde::Deserializer;
|
||||||
|
use serde::de::{Error, Unexpected};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
struct BoolVisitor;
|
||||||
|
impl serde::de::Visitor<'_> for BoolVisitor {
|
||||||
|
type Value = bool;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "bool value")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: Error,
|
||||||
|
{
|
||||||
|
match v {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => Err(E::invalid_value(Unexpected::Signed(v), &"0 or 1")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: Error,
|
||||||
|
{
|
||||||
|
match v {
|
||||||
|
"0" | "no" => Ok(false),
|
||||||
|
"1" | "yes" => Ok(true),
|
||||||
|
other => Err(E::invalid_value(Unexpected::Str(other), &"bool")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_bool<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
|
||||||
|
deserializer.deserialize_any(BoolVisitor)
|
||||||
|
}
|
||||||
38
common/src/color.rs
Normal file
38
common/src/color.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
use crate::{EntityParseError, EntityProp};
|
||||||
|
use serde::de::{Error, Unexpected};
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Color {
|
||||||
|
pub r: u8,
|
||||||
|
pub g: u8,
|
||||||
|
pub b: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Color {
|
||||||
|
type Err = EntityParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut floats = s.split_whitespace().map(u8::from_str);
|
||||||
|
let r = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||||
|
let g = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||||
|
let b = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||||
|
if floats.next().is_some() {
|
||||||
|
return Err(EntityParseError::ElementCount);
|
||||||
|
}
|
||||||
|
Ok(Self { r, g, b })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Color {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let str = <&str>::deserialize(deserializer)?;
|
||||||
|
let [r, g, b] = <[u8; 3]>::parse(str)
|
||||||
|
.map_err(|_| D::Error::invalid_value(Unexpected::Other(str), &"a list of 3 numbers"))?;
|
||||||
|
Ok(Color { r, g, b })
|
||||||
|
}
|
||||||
|
}
|
||||||
17
common/src/lib.rs
Normal file
17
common/src/lib.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
mod angle;
|
||||||
|
mod bool;
|
||||||
|
mod color;
|
||||||
|
mod lightcolor;
|
||||||
|
mod negated;
|
||||||
|
mod prop;
|
||||||
|
mod property;
|
||||||
|
mod vector;
|
||||||
|
|
||||||
|
pub use angle::Angles;
|
||||||
|
pub use bool::deserialize_bool;
|
||||||
|
pub use color::Color;
|
||||||
|
pub use lightcolor::LightColor;
|
||||||
|
pub use negated::Negated;
|
||||||
|
pub use prop::{AsPropPlacement, PropPlacement};
|
||||||
|
pub use property::{EntityParseError, EntityProp, FromStrProp};
|
||||||
|
pub use vector::Vector;
|
||||||
55
common/src/lightcolor.rs
Normal file
55
common/src/lightcolor.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::EntityParseError;
|
||||||
|
use serde::de::{Error, Unexpected};
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LightColor {
|
||||||
|
pub r: u8,
|
||||||
|
pub g: u8,
|
||||||
|
pub b: u8,
|
||||||
|
pub intensity: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for LightColor {
|
||||||
|
type Err = EntityParseError;
|
||||||
|
|
||||||
|
fn from_str(str: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut values = str.split_whitespace();
|
||||||
|
let r = values
|
||||||
|
.next()
|
||||||
|
.ok_or(EntityParseError::ElementCount)?
|
||||||
|
.parse()
|
||||||
|
.map_err(EntityParseError::Int)?;
|
||||||
|
let g = values
|
||||||
|
.next()
|
||||||
|
.ok_or(EntityParseError::ElementCount)?
|
||||||
|
.parse()
|
||||||
|
.map_err(EntityParseError::Int)?;
|
||||||
|
let b = values
|
||||||
|
.next()
|
||||||
|
.ok_or(EntityParseError::ElementCount)?
|
||||||
|
.parse()
|
||||||
|
.map_err(EntityParseError::Int)?;
|
||||||
|
let intensity = values
|
||||||
|
.next()
|
||||||
|
.ok_or(EntityParseError::ElementCount)?
|
||||||
|
.parse()
|
||||||
|
.map_err(EntityParseError::Int)?;
|
||||||
|
if values.next().is_some() {
|
||||||
|
return Err(EntityParseError::ElementCount);
|
||||||
|
}
|
||||||
|
Ok(LightColor { r, g, b, intensity })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for LightColor {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let str = <&str>::deserialize(deserializer)?;
|
||||||
|
str.parse()
|
||||||
|
.map_err(|_| D::Error::invalid_value(Unexpected::Str(str), &"a list of 4 integers"))
|
||||||
|
}
|
||||||
|
}
|
||||||
61
common/src/negated.rs
Normal file
61
common/src/negated.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
use serde::de::{Error, Unexpected};
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Negated {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
MatchingCriteria,
|
||||||
|
}
|
||||||
|
pub struct NegatedParseErr;
|
||||||
|
impl FromStr for Negated {
|
||||||
|
type Err = NegatedParseErr;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"1" => Ok(Negated::Yes),
|
||||||
|
"0" => Ok(Negated::No),
|
||||||
|
"allow entities that match criteria" => Ok(Negated::MatchingCriteria),
|
||||||
|
_ => Err(NegatedParseErr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NegatedVisitor;
|
||||||
|
impl serde::de::Visitor<'_> for NegatedVisitor {
|
||||||
|
type Value = Negated;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "Negated value")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: Error,
|
||||||
|
{
|
||||||
|
match v {
|
||||||
|
0 => Ok(Negated::No),
|
||||||
|
1 => Ok(Negated::Yes),
|
||||||
|
_ => Err(E::invalid_value(Unexpected::Signed(v), &"0 or 1")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: Error,
|
||||||
|
{
|
||||||
|
v.parse()
|
||||||
|
.map_err(|_| E::invalid_value(Unexpected::Str(v), &"Negated"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Negated {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(NegatedVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
16
common/src/prop.rs
Normal file
16
common/src/prop.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::Vector;
|
||||||
|
use cgmath::Quaternion;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PropPlacement<'a> {
|
||||||
|
pub model: &'a str,
|
||||||
|
pub rotation: Quaternion<f32>,
|
||||||
|
pub scale: f32,
|
||||||
|
pub origin: Vector,
|
||||||
|
pub skin: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Abstraction for various ways props are placed in a bsp
|
||||||
|
pub trait AsPropPlacement<'a> {
|
||||||
|
fn as_prop_placement(&self) -> PropPlacement<'a>;
|
||||||
|
}
|
||||||
74
common/src/property.rs
Normal file
74
common/src/property.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
use crate::{Angles, Color, LightColor, Negated, Vector};
|
||||||
|
use std::num::{ParseFloatError, ParseIntError};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub trait EntityProp<'a>: Sized {
|
||||||
|
fn parse(raw: &'a str) -> Result<Self, EntityParseError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Properties that can be parsed with their FromStr implementation
|
||||||
|
pub trait FromStrProp: FromStr {}
|
||||||
|
|
||||||
|
impl FromStrProp for u8 {}
|
||||||
|
impl FromStrProp for u16 {}
|
||||||
|
impl FromStrProp for f32 {}
|
||||||
|
impl FromStrProp for u32 {}
|
||||||
|
impl FromStrProp for i32 {}
|
||||||
|
impl FromStrProp for Color {}
|
||||||
|
impl FromStrProp for Angles {}
|
||||||
|
impl FromStrProp for Vector {}
|
||||||
|
impl FromStrProp for LightColor {}
|
||||||
|
impl FromStrProp for Negated {}
|
||||||
|
|
||||||
|
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_whitespace().map(T::from_str);
|
||||||
|
let mut result = <[T; N]>::default();
|
||||||
|
for item in result.iter_mut() {
|
||||||
|
*item = values.next().ok_or(EntityParseError::ElementCount)??;
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EntityProp<'a> for &'a str {
|
||||||
|
fn parse(raw: &'a str) -> Result<Self, EntityParseError> {
|
||||||
|
Ok(raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntityProp<'_> for bool {
|
||||||
|
fn parse(raw: &'_ str) -> Result<Self, EntityParseError> {
|
||||||
|
Ok(raw != "0" && raw != "no")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Error)]
|
||||||
|
pub enum EntityParseError {
|
||||||
|
#[error("wrong number of elements")]
|
||||||
|
ElementCount,
|
||||||
|
#[error(transparent)]
|
||||||
|
Float(#[from] ParseFloatError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Int(#[from] ParseIntError),
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::error::EntityParseError;
|
use crate::EntityParseError;
|
||||||
use binrw::BinRead;
|
use binrw::BinRead;
|
||||||
use cgmath::Vector3;
|
use cgmath::Vector3;
|
||||||
use serde::de::{Error, Unexpected};
|
use serde::de::{Error, Unexpected};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::vector::Vector;
|
|
||||||
use crate::data::try_read_enum;
|
use crate::data::try_read_enum;
|
||||||
use crate::error::InvalidNeighbourError;
|
use crate::error::InvalidNeighbourError;
|
||||||
|
use crate::Vector;
|
||||||
use binrw::{BinRead, BinResult, Endian};
|
use binrw::{BinRead, BinResult, Endian};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use crate::error::EntityParseError;
|
use crate::error::EntityParseError;
|
||||||
use crate::{Angles, Vector};
|
use crate::EntityProp;
|
||||||
use serde::de::{Error, Unexpected};
|
use serde::Deserialize;
|
||||||
use serde::{Deserialize, Deserializer};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::str::FromStr;
|
|
||||||
use vdf_reader::entry::Entry;
|
use vdf_reader::entry::Entry;
|
||||||
use vdf_reader::VdfError;
|
use vdf_reader::VdfError;
|
||||||
|
|
||||||
|
|
@ -130,243 +128,6 @@ impl<'a> RawEntity<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EntityProp<'a>: Sized {
|
|
||||||
fn parse(raw: &'a str) -> Result<Self, EntityParseError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
trait FromStrProp: FromStr {}
|
|
||||||
|
|
||||||
impl FromStrProp for u8 {}
|
|
||||||
impl FromStrProp for u16 {}
|
|
||||||
impl FromStrProp for f32 {}
|
|
||||||
impl FromStrProp for u32 {}
|
|
||||||
impl FromStrProp for i32 {}
|
|
||||||
impl FromStrProp for Color {}
|
|
||||||
impl FromStrProp for Angles {}
|
|
||||||
impl FromStrProp for Vector {}
|
|
||||||
impl FromStrProp for LightColor {}
|
|
||||||
|
|
||||||
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_whitespace().map(T::from_str);
|
|
||||||
let mut result = <[T; N]>::default();
|
|
||||||
for item in result.iter_mut() {
|
|
||||||
*item = values.next().ok_or(EntityParseError::ElementCount)??;
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> EntityProp<'a> for &'a str {
|
|
||||||
fn parse(raw: &'a str) -> Result<Self, EntityParseError> {
|
|
||||||
Ok(raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
|
||||||
pub struct Color {
|
|
||||||
pub r: u8,
|
|
||||||
pub g: u8,
|
|
||||||
pub b: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Color {
|
|
||||||
type Err = EntityParseError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut floats = s.split_whitespace().map(u8::from_str);
|
|
||||||
let r = floats.next().ok_or(EntityParseError::ElementCount)??;
|
|
||||||
let g = floats.next().ok_or(EntityParseError::ElementCount)??;
|
|
||||||
let b = floats.next().ok_or(EntityParseError::ElementCount)??;
|
|
||||||
if floats.next().is_some() {
|
|
||||||
return Err(EntityParseError::ElementCount);
|
|
||||||
}
|
|
||||||
Ok(Self { r, g, b })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Color {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let str = <&str>::deserialize(deserializer)?;
|
|
||||||
let [r, g, b] = <[u8; 3]>::parse(str)
|
|
||||||
.map_err(|_| D::Error::invalid_value(Unexpected::Other(str), &"a list of 3 numbers"))?;
|
|
||||||
Ok(Color { r, g, b })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct LightColor {
|
|
||||||
pub r: u8,
|
|
||||||
pub g: u8,
|
|
||||||
pub b: u8,
|
|
||||||
pub intensity: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for LightColor {
|
|
||||||
type Err = EntityParseError;
|
|
||||||
|
|
||||||
fn from_str(str: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut values = str.split_whitespace();
|
|
||||||
let r = values
|
|
||||||
.next()
|
|
||||||
.ok_or(EntityParseError::ElementCount)?
|
|
||||||
.parse()
|
|
||||||
.map_err(EntityParseError::Int)?;
|
|
||||||
let g = values
|
|
||||||
.next()
|
|
||||||
.ok_or(EntityParseError::ElementCount)?
|
|
||||||
.parse()
|
|
||||||
.map_err(EntityParseError::Int)?;
|
|
||||||
let b = values
|
|
||||||
.next()
|
|
||||||
.ok_or(EntityParseError::ElementCount)?
|
|
||||||
.parse()
|
|
||||||
.map_err(EntityParseError::Int)?;
|
|
||||||
let intensity = values
|
|
||||||
.next()
|
|
||||||
.ok_or(EntityParseError::ElementCount)?
|
|
||||||
.parse()
|
|
||||||
.map_err(EntityParseError::Int)?;
|
|
||||||
if values.next().is_some() {
|
|
||||||
return Err(EntityParseError::ElementCount);
|
|
||||||
}
|
|
||||||
Ok(LightColor { r, g, b, intensity })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for LightColor {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let str = <&str>::deserialize(deserializer)?;
|
|
||||||
str.parse()
|
|
||||||
.map_err(|_| D::Error::invalid_value(Unexpected::Str(str), &"a list of 4 integers"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Negated {
|
|
||||||
Yes,
|
|
||||||
No,
|
|
||||||
MatchingCriteria,
|
|
||||||
}
|
|
||||||
pub struct NegatedParseErr;
|
|
||||||
impl FromStr for Negated {
|
|
||||||
type Err = NegatedParseErr;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"1" => Ok(Negated::Yes),
|
|
||||||
"0" => Ok(Negated::No),
|
|
||||||
"allow entities that match criteria" => Ok(Negated::MatchingCriteria),
|
|
||||||
_ => Err(NegatedParseErr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStrProp for Negated {}
|
|
||||||
|
|
||||||
struct NegatedVisitor;
|
|
||||||
impl serde::de::Visitor<'_> for NegatedVisitor {
|
|
||||||
type Value = Negated;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(formatter, "Negated value")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: Error,
|
|
||||||
{
|
|
||||||
match v {
|
|
||||||
0 => Ok(Negated::No),
|
|
||||||
1 => Ok(Negated::Yes),
|
|
||||||
_ => Err(E::invalid_value(Unexpected::Signed(v), &"0 or 1")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: Error,
|
|
||||||
{
|
|
||||||
v.parse()
|
|
||||||
.map_err(|_| E::invalid_value(Unexpected::Str(v), &"Negated"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Negated {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_any(NegatedVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BoolVisitor;
|
|
||||||
impl serde::de::Visitor<'_> for BoolVisitor {
|
|
||||||
type Value = bool;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(formatter, "bool value")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: Error,
|
|
||||||
{
|
|
||||||
match v {
|
|
||||||
0 => Ok(false),
|
|
||||||
1 => Ok(true),
|
|
||||||
_ => Err(E::invalid_value(Unexpected::Signed(v), &"0 or 1")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: Error,
|
|
||||||
{
|
|
||||||
match v {
|
|
||||||
"0" | "no" => Ok(false),
|
|
||||||
"1" | "yes" => Ok(true),
|
|
||||||
other => Err(E::invalid_value(Unexpected::Str(other), &"bool")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn deserialize_bool<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
|
|
||||||
deserializer.deserialize_any(BoolVisitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct GenericEntity {
|
pub struct GenericEntity {
|
||||||
#[serde(rename = "classname")]
|
#[serde(rename = "classname")]
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,19 @@ mod entity;
|
||||||
mod game;
|
mod game;
|
||||||
mod leaves;
|
mod leaves;
|
||||||
mod prop;
|
mod prop;
|
||||||
mod vector;
|
|
||||||
|
|
||||||
pub use self::displacement::*;
|
pub use self::displacement::*;
|
||||||
pub use self::entity::*;
|
pub use self::entity::*;
|
||||||
pub use self::game::*;
|
pub use self::game::*;
|
||||||
pub use self::leaves::*;
|
pub use self::leaves::*;
|
||||||
pub use self::prop::PropPlacement;
|
|
||||||
pub use self::vector::*;
|
|
||||||
use crate::bspfile::LumpType;
|
use crate::bspfile::LumpType;
|
||||||
use crate::error::EntityParseError;
|
|
||||||
use crate::{BspResult, StringError};
|
use crate::{BspResult, StringError};
|
||||||
use arrayvec::ArrayString;
|
use arrayvec::ArrayString;
|
||||||
use binrw::error::CustomError;
|
use binrw::error::CustomError;
|
||||||
use binrw::{BinRead, BinResult, Endian};
|
use binrw::{BinRead, BinResult, Endian};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use bv::BitVec;
|
use bv::BitVec;
|
||||||
use cgmath::{Deg, Quaternion, Rotation3};
|
|
||||||
use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
|
use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
|
||||||
use serde::de::{Error, Unexpected};
|
|
||||||
use serde::{Deserialize, Deserializer};
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -30,8 +23,8 @@ use std::fmt::{Debug, Display, Formatter};
|
||||||
use std::io::{Cursor, Read, Seek};
|
use std::io::{Cursor, Read, Seek};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
pub use vbsp_common::{Angles, Color, EntityProp, LightColor, Negated, PropPlacement, Vector};
|
||||||
use zip::result::ZipError;
|
use zip::result::ZipError;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
|
@ -535,42 +528,3 @@ where
|
||||||
err: Box::new(e),
|
err: Box::new(e),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, BinRead, Default)]
|
|
||||||
pub struct Angles {
|
|
||||||
pitch: f32,
|
|
||||||
yaw: f32,
|
|
||||||
roll: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Angles {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let str = <&str>::deserialize(deserializer)?;
|
|
||||||
str.parse()
|
|
||||||
.map_err(|_| D::Error::invalid_value(Unexpected::Other(str), &"a list of angles"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Angles {
|
|
||||||
type Err = EntityParseError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut floats = s.split_whitespace().map(f32::from_str);
|
|
||||||
let pitch = floats.next().ok_or(EntityParseError::ElementCount)??;
|
|
||||||
let yaw = floats.next().ok_or(EntityParseError::ElementCount)??;
|
|
||||||
let roll = floats.next().ok_or(EntityParseError::ElementCount)??;
|
|
||||||
Ok(Angles { pitch, yaw, roll })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Angles {
|
|
||||||
fn as_quaternion(&self) -> Quaternion<f32> {
|
|
||||||
// angles are applied in roll, pitch, yaw order
|
|
||||||
Quaternion::from_angle_y(Deg(self.yaw))
|
|
||||||
* Quaternion::from_angle_x(Deg(self.pitch))
|
|
||||||
* Quaternion::from_angle_z(Deg(self.roll))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,8 @@
|
||||||
use crate::{Handle, StaticPropLump, Vector};
|
use crate::{Handle, StaticPropLump};
|
||||||
use cgmath::Quaternion;
|
use vbsp_common::{AsPropPlacement, PropPlacement};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl<'a> AsPropPlacement<'a> for Handle<'a, StaticPropLump> {
|
||||||
pub struct PropPlacement<'a> {
|
fn as_prop_placement(&self) -> PropPlacement<'a> {
|
||||||
pub model: &'a str,
|
|
||||||
pub rotation: Quaternion<f32>,
|
|
||||||
pub scale: f32,
|
|
||||||
pub origin: Vector,
|
|
||||||
pub skin: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Handle<'a, StaticPropLump> {
|
|
||||||
pub fn as_prop_placement(&self) -> PropPlacement<'a> {
|
|
||||||
PropPlacement {
|
PropPlacement {
|
||||||
model: self.model(),
|
model: self.model(),
|
||||||
rotation: self.rotation(),
|
rotation: self.rotation(),
|
||||||
|
|
|
||||||
12
src/error.rs
12
src/error.rs
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::bspfile::LumpType;
|
use crate::bspfile::LumpType;
|
||||||
use crate::data::*;
|
use crate::data::*;
|
||||||
use std::num::{ParseFloatError, ParseIntError};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
pub use vbsp_common::EntityParseError;
|
||||||
use zip::result::ZipError;
|
use zip::result::ZipError;
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
|
@ -121,13 +121,3 @@ pub enum InvalidNeighbourError {
|
||||||
#[error("Invalid neighbour orientation")]
|
#[error("Invalid neighbour orientation")]
|
||||||
InvalidNeighbourOrientation(u8),
|
InvalidNeighbourOrientation(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum EntityParseError {
|
|
||||||
#[error("wrong number of elements")]
|
|
||||||
ElementCount,
|
|
||||||
#[error(transparent)]
|
|
||||||
Float(#[from] ParseFloatError),
|
|
||||||
#[error(transparent)]
|
|
||||||
Int(#[from] ParseIntError),
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ 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::*;
|
pub use crate::data::*;
|
||||||
use crate::error::ValidationError;
|
use crate::error::ValidationError;
|
||||||
pub use crate::handle::Handle;
|
pub use crate::handle::Handle;
|
||||||
|
|
@ -18,6 +17,7 @@ use lzma_rs::decompress::{Options, UnpackedSize};
|
||||||
use reader::LumpReader;
|
use reader::LumpReader;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
pub use vbsp_common::deserialize_bool;
|
||||||
|
|
||||||
pub type BspResult<T> = Result<T, BspError>;
|
pub type BspResult<T> = Result<T, BspError>;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue