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