rakata_formats/lyt/
mod.rs

1//! LYT ASCII reader and writer.
2//!
3//! LYT (layout) resources describe room placement and optional track/obstacle
4//! props for a module.
5//!
6//! ## Format Layout
7//! ```text
8//! beginlayout
9//!   roomcount <N>
10//!     <room_model> <x> <y> <z>
11//!   trackcount <N>
12//!     <track_model> <x> <y> <z>
13//!   obstaclecount <N>
14//!     <obstacle_model> <x> <y> <z>
15//!   doorhookcount <N>
16//!     <room_name> <door_name> <reserved> <x> <y> <z> <qx> <qy> <qz> <qw>
17//! donelayout
18//! ```
19//!
20//! The parser ignores non-section lines (such as comments and dependency
21//! metadata) and only consumes known count sections.
22//! Text is decoded/encoded as Windows-1252.
23
24mod reader;
25mod writer;
26
27pub use reader::{read_lyt, read_lyt_from_bytes};
28pub use writer::{write_lyt, write_lyt_to_vec};
29
30use thiserror::Error;
31
32use rakata_core::{DecodeTextError, EncodeTextError};
33
34use crate::binary::{DecodeBinary, EncodeBinary};
35
36/// In-memory LYT layout.
37#[derive(Debug, Clone, PartialEq, Default)]
38pub struct Lyt {
39    /// Room entries.
40    pub rooms: Vec<LytRoom>,
41    /// Track entries.
42    pub tracks: Vec<LytTrack>,
43    /// Obstacle entries.
44    pub obstacles: Vec<LytObstacle>,
45    /// Door-hook entries.
46    pub doorhooks: Vec<LytDoorHook>,
47}
48
49impl Lyt {
50    /// Creates an empty layout.
51    pub fn new() -> Self {
52        Self::default()
53    }
54}
55
56impl DecodeBinary for Lyt {
57    type Error = LytError;
58
59    fn decode_binary(bytes: &[u8]) -> Result<Self, Self::Error> {
60        read_lyt_from_bytes(bytes)
61    }
62}
63
64impl EncodeBinary for Lyt {
65    type Error = LytError;
66
67    fn encode_binary(&self) -> Result<Vec<u8>, Self::Error> {
68        write_lyt_to_vec(self)
69    }
70}
71
72/// One room entry in a LYT layout.
73#[derive(Debug, Clone, PartialEq)]
74pub struct LytRoom {
75    /// Room model name.
76    pub model: String,
77    /// Room world position.
78    pub position: Vec3,
79}
80
81/// One track entry in a LYT layout.
82#[derive(Debug, Clone, PartialEq)]
83pub struct LytTrack {
84    /// Track model name.
85    pub model: String,
86    /// Track world position.
87    pub position: Vec3,
88}
89
90/// One obstacle entry in a LYT layout.
91#[derive(Debug, Clone, PartialEq)]
92pub struct LytObstacle {
93    /// Obstacle model name.
94    pub model: String,
95    /// Obstacle world position.
96    pub position: Vec3,
97}
98
99/// One door-hook entry in a LYT layout.
100#[derive(Debug, Clone, PartialEq)]
101pub struct LytDoorHook {
102    /// Room name associated with this hook.
103    pub room: String,
104    /// Door identifier within the room.
105    pub door: String,
106    /// Door world position.
107    pub position: Vec3,
108    /// Door orientation quaternion.
109    pub orientation: Quaternion,
110}
111
112/// Three-dimensional vector value used by LYT entries.
113#[derive(Debug, Clone, Copy, PartialEq)]
114pub struct Vec3 {
115    /// X component.
116    pub x: f32,
117    /// Y component.
118    pub y: f32,
119    /// Z component.
120    pub z: f32,
121}
122
123impl Vec3 {
124    /// Creates a new 3D vector.
125    pub const fn new(x: f32, y: f32, z: f32) -> Self {
126        Self { x, y, z }
127    }
128}
129
130/// Quaternion value used by LYT door-hook orientation.
131#[derive(Debug, Clone, Copy, PartialEq)]
132pub struct Quaternion {
133    /// X component.
134    pub x: f32,
135    /// Y component.
136    pub y: f32,
137    /// Z component.
138    pub z: f32,
139    /// W component.
140    pub w: f32,
141}
142
143impl Quaternion {
144    /// Creates a new quaternion.
145    pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
146        Self { x, y, z, w }
147    }
148}
149
150/// Errors produced while parsing or serializing LYT ASCII data.
151#[derive(Debug, Error)]
152pub enum LytError {
153    /// I/O read/write failure.
154    #[error(transparent)]
155    Io(#[from] std::io::Error),
156    /// Text content is malformed.
157    #[error("invalid LYT data: {0}")]
158    InvalidData(String),
159    /// Text cannot be represented in Windows-1252 output.
160    #[error("LYT text encoding failed for {context}: {source}")]
161    TextEncoding {
162        /// Value context.
163        context: String,
164        /// Encoding error details.
165        #[source]
166        source: EncodeTextError,
167    },
168    /// Input bytes could not be decoded losslessly as Windows-1252.
169    #[error("LYT text decoding failed for {context}: {source}")]
170    TextDecoding {
171        /// Value context.
172        context: String,
173        /// Decoding error details.
174        #[source]
175        source: DecodeTextError,
176    },
177    /// A name token contains disallowed whitespace.
178    #[error("invalid LYT {field} token `{value}` (whitespace is not allowed)")]
179    InvalidName {
180        /// Field context.
181        field: &'static str,
182        /// Invalid name value.
183        value: String,
184    },
185}