Skip to main content

openstranded_map_tool/
types.rs

1// openstranded-map-tool — types for the .s2/.osmap map formats
2// Copyright (C) 2026  OpenStranded contributors
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Data types for the OpenStranded Map (`.osmap`) format.
18
19use std::collections::HashMap;
20
21use serde::{Deserialize, Serialize};
22
23// ===========================================================================
24// .osmap types
25// ===========================================================================
26
27/// Root type for an `.osmap` file.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct OpenStrandedMap {
30    /// Map metadata.
31    pub meta: MapMeta,
32    /// Terrain heightmap data.
33    pub terrain: TerrainData,
34    /// Spawned entities (objects, buildings, items, units, infos).
35    #[serde(default)]
36    pub entities: Vec<MapEntity>,
37    /// The player spawn point.
38    #[serde(default)]
39    pub player_spawn: Option<PlayerSpawn>,
40    /// Environment/global variables set on this map.
41    #[serde(default)]
42    pub environment: HashMap<String, String>,
43    /// Colormap (terrain colour texture) — pixel data in row-major RGB.
44    #[serde(default)]
45    pub colormap: Option<ColormapData>,
46    /// Grass layer — (colormap_dim+1)² bytes, column-major, 0/1 values.
47    #[serde(default)]
48    pub grass: Option<GrassData>,
49    /// Map password (decoded).
50    #[serde(default)]
51    pub password: String,
52    /// Embedded scripts from .s2 infos.
53    #[serde(default)]
54    pub scripts: Vec<ScriptData>,
55}
56
57/// Map metadata.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct MapMeta {
60    /// Human-readable map name.
61    pub name: String,
62    /// Engine version that created this map.
63    pub engine_version: String,
64    /// Map format semver (bumped on non-breaking additions).
65    pub map_version: String,
66    /// Original .s2 source file (if converted).
67    #[serde(default)]
68    pub source_file: String,
69    /// Original .s2 header fields.
70    #[serde(default)]
71    pub s2_header: Option<S2Header>,
72    /// When the map was converted or last saved (ISO 8601).
73    #[serde(default)]
74    pub created_at: String,
75}
76
77/// Original .s2 header lines.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct S2Header {
80    pub version: String,
81    pub date: String,
82    pub time: String,
83    pub author: String,
84    pub map_type: String,
85    /// Type-format string: "tfobject tfunit tfitem" concatenated (e.g. "001", "100").
86    /// Empty string means all 0 (all types are u8).
87    /// Each char is '0' (u8) or '1' (u16).
88    #[serde(default)]
89    pub type_format: String,
90}
91
92/// Terrain heightmap data.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct TerrainData {
95    /// Grid size (number of cells — terrain_size in .s2).
96    pub size: u32,
97    /// Elevation values; row-major, [z * (size+1) + x].
98    /// Range: 0.0–1.0 (normalised).
99    pub heights: Vec<f32>,
100    /// Sea/water level (Y below which = underwater).
101    #[serde(default = "default_seaground")]
102    pub seaground_level: f32,
103}
104
105fn default_seaground() -> f32 {
106    -2.0
107}
108
109/// Colormap (terrain colour texture) data.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ColormapData {
112    /// Dimension (width = height = dim).
113    pub dim: u32,
114    /// RGB pixel data, row-major, length = dim * dim * 3.
115    pub pixels: Vec<u8>,
116}
117
118/// Grass layer data.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct GrassData {
121    /// Dimension (width = height = dim).
122    pub dim: u32,
123    /// Binary grass data, column-major, length = dim * dim.
124    /// 0 = no grass, 1 = grass.
125    pub values: Vec<u8>,
126}
127
128/// An entity placed on the map.
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct MapEntity {
131    /// Entity class: "object", "building", "item", "unit", "info".
132    pub class: String,
133    /// Type ID as defined in the content pack.
134    pub type_id: u32,
135    /// Unique numeric ID within the map.
136    pub id: u32,
137    /// World position. Y is explicit.
138    pub position: (f32, f32, f32),
139    /// Yaw rotation in radians.
140    #[serde(default)]
141    pub rotation_yaw: f32,
142    /// Pitch rotation in radians (from FRAP extension).
143    #[serde(default)]
144    pub rotation_pitch: f32,
145    /// Roll rotation in radians (from FRAP extension).
146    #[serde(default)]
147    pub rotation_roll: f32,
148    /// Current health.
149    #[serde(default)]
150    pub health: f32,
151    /// Maximum health.
152    #[serde(default)]
153    pub health_max: f32,
154    /// Initial state flags.
155    #[serde(default)]
156    pub states: Vec<String>,
157    /// Embedded script override.
158    #[serde(default)]
159    pub script: Option<String>,
160    /// Item-specific: stack size.
161    #[serde(default)]
162    pub amount: u32,
163    /// Parent entity ID (for items inside containers).
164    #[serde(default)]
165    pub parent_id: u32,
166    /// Parent class (0=none, 1=obj, 2=unit, 3=item, 4=info).
167    #[serde(default)]
168    pub parent_class: u8,
169    /// Linked entity IDs.
170    #[serde(default)]
171    pub links: Vec<u32>,
172    /// Building-specific: construction progress 0–100.
173    #[serde(default)]
174    pub build_progress: u32,
175    /// Unit-specific AI data.
176    #[serde(default)]
177    pub unit_data: Option<UnitData>,
178    /// Additional custom data (extensions).
179    #[serde(default)]
180    pub extensions: HashMap<String, String>,
181}
182
183/// Unit-specific AI data.
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct UnitData {
186    #[serde(default)]
187    pub hunger: f32,
188    #[serde(default)]
189    pub thirst: f32,
190    #[serde(default)]
191    pub exhaustion: f32,
192    #[serde(default)]
193    pub ai_center_x: f32,
194    #[serde(default)]
195    pub ai_center_z: f32,
196    #[serde(default)]
197    pub day_timer: f32,
198}
199
200/// Player spawn point.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct PlayerSpawn {
203    pub position: (f32, f32, f32),
204    pub rotation_yaw: f32,
205}
206
207/// Embedded script data (from .s2 infos or global briefing).
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct ScriptData {
210    pub text: String,
211    pub entity_id: u32,
212}
213
214// ===========================================================================
215// .s2 raw types (intermediate — not part of .osmap output)
216// ===========================================================================
217
218/// Fully parsed `.s2` file data.
219#[derive(Debug, Clone)]
220pub struct S2Map {
221    pub header: S2Header,
222    pub minimap: Vec<u8>,
223    pub password: S2Password,
224    pub env_vars: S2EnvVars,
225    pub quickslots: Vec<String>,
226    pub colormap_dim: u32,
227    pub colormap: Vec<u8>,
228    pub terrain_size: u32,
229    pub heights: Vec<f32>,
230    pub grass: Vec<u8>,
231    pub objects: Vec<S2Object>,
232    pub units: Vec<S2Unit>,
233    pub items: Vec<S2Item>,
234    pub infos: Vec<S2Info>,
235    pub states: Vec<S2State>,
236    pub extensions: Vec<S2Extension>,
237    pub trailer: S2Trailer,
238}
239
240/// Password header between minimap and env vars.
241/// From `e_save_map.bb`: WriteByte(pwkey) + WriteLine(encodedpw$)
242#[derive(Debug, Clone)]
243pub struct S2Password {
244    /// XOR key byte (random 5–250).
245    pub key: u8,
246    /// XOR-encoded password string.
247    pub encoded: String,
248    /// Decoded password (key XOR each byte).
249    pub decoded: String,
250}
251
252#[derive(Debug, Clone)]
253pub struct S2EnvVars {
254    pub day: u32,
255    pub hour: u8,
256    pub minute: u8,
257    pub freezetime: u8,
258    pub skybox: String,
259    pub multiplayer: u8,
260    pub climate: u8,
261    pub music: String,
262    pub briefing: String,
263    pub fog: [u8; 4],
264    pub extra: u8,
265}
266
267/// Object record (source e_save_map.bb lines 137–148).
268#[derive(Debug, Clone)]
269pub struct S2Object {
270    pub id: u32,
271    pub typ: u32,
272    pub x: f32,
273    pub z: f32,
274    pub yaw: f32,
275    pub health: f32,
276    pub health_max: f32,
277    pub day_timer: u32,
278}
279
280/// Unit record (source e_save_map.bb lines 166–182).
281/// NOTE: unit sub-data (states, items, scripts) is NOT stored inline in the
282/// unit section; it's stored as Extensions.
283#[derive(Debug, Clone)]
284pub struct S2Unit {
285    pub id: u32,
286    pub typ: u32,
287    pub x: f32,
288    pub y: f32,
289    pub z: f32,
290    pub yaw: f32,
291    pub health: f32,
292    pub health_max: f32,
293    pub hunger: f32,
294    pub thirst: f32,
295    pub exhaustion: f32,
296    pub ai_center_x: f32,
297    pub ai_center_z: f32,
298}
299
300/// Item record (source e_save_map.bb lines 192–207).
301#[derive(Debug, Clone)]
302pub struct S2Item {
303    pub id: u32,
304    pub typ: u32,
305    pub x: f32,
306    pub y: f32,
307    pub z: f32,
308    pub yaw: f32,
309    pub health: f32,
310    pub count: u32,
311    pub parent_class: u8,
312    pub parent_mode: u8,
313    pub parent_id: u32,
314}
315
316/// Info record (source e_save_map.bb lines 243–251).
317#[derive(Debug, Clone)]
318pub struct S2Info {
319    pub id: u32,
320    pub typ: u8,
321    pub x: f32,
322    pub y: f32,
323    pub z: f32,
324    pub pitch: f32,
325    pub yaw: f32,
326    pub vars: String,
327}
328
329/// State record (source e_save_map.bb lines 260–272).
330#[derive(Debug, Clone)]
331pub struct S2State {
332    pub typ: u32,
333    pub parent_class: u32,
334    pub parent_id: u32,
335    pub x: f32,
336    pub y: f32,
337    pub z: f32,
338    pub fx: f32,
339    pub fy: f32,
340    pub fz: f32,
341    pub value: u32,
342    pub value_f: f32,
343    pub value_s: String,
344}
345
346/// Extension record (source e_save_map.bb lines 314–327).
347#[derive(Debug, Clone)]
348pub struct S2Extension {
349    pub typ: u8,
350    pub parent_class: u8,
351    pub parent_id: u32,
352    pub mode: u32,
353    pub key: String,
354    pub value: String,
355    pub stuff: String,
356}
357
358/// Trailer lines: WriteLine("") + WriteLine("### EOF Map File") + WriteLine("www.unrealsoftware.de")
359#[derive(Debug, Clone)]
360pub struct S2Trailer {
361    pub blank: String,
362    pub eof_marker: String,
363    pub url: String,
364}