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    /// Current health.
143    #[serde(default)]
144    pub health: f32,
145    /// Maximum health.
146    #[serde(default)]
147    pub health_max: f32,
148    /// Initial state flags.
149    #[serde(default)]
150    pub states: Vec<String>,
151    /// Embedded script override.
152    #[serde(default)]
153    pub script: Option<String>,
154    /// Item-specific: stack size.
155    #[serde(default)]
156    pub amount: u32,
157    /// Parent entity ID (for items inside containers).
158    #[serde(default)]
159    pub parent_id: u32,
160    /// Parent class (0=none, 1=obj, 2=unit, 3=item, 4=info).
161    #[serde(default)]
162    pub parent_class: u8,
163    /// Linked entity IDs.
164    #[serde(default)]
165    pub links: Vec<u32>,
166    /// Building-specific: construction progress 0–100.
167    #[serde(default)]
168    pub build_progress: u32,
169    /// Unit-specific AI data.
170    #[serde(default)]
171    pub unit_data: Option<UnitData>,
172    /// Additional custom data (extensions).
173    #[serde(default)]
174    pub extensions: HashMap<String, String>,
175}
176
177/// Unit-specific AI data.
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct UnitData {
180    #[serde(default)]
181    pub hunger: f32,
182    #[serde(default)]
183    pub thirst: f32,
184    #[serde(default)]
185    pub exhaustion: f32,
186    #[serde(default)]
187    pub ai_center_x: f32,
188    #[serde(default)]
189    pub ai_center_z: f32,
190    #[serde(default)]
191    pub day_timer: f32,
192}
193
194/// Player spawn point.
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct PlayerSpawn {
197    pub position: (f32, f32, f32),
198    pub rotation_yaw: f32,
199}
200
201/// Embedded script data (from .s2 infos or global briefing).
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ScriptData {
204    pub text: String,
205    pub entity_id: u32,
206}
207
208// ===========================================================================
209// .s2 raw types (intermediate — not part of .osmap output)
210// ===========================================================================
211
212/// Fully parsed `.s2` file data.
213#[derive(Debug, Clone)]
214pub struct S2Map {
215    pub header: S2Header,
216    pub minimap: Vec<u8>,
217    pub password: S2Password,
218    pub env_vars: S2EnvVars,
219    pub quickslots: Vec<String>,
220    pub colormap_dim: u32,
221    pub colormap: Vec<u8>,
222    pub terrain_size: u32,
223    pub heights: Vec<f32>,
224    pub grass: Vec<u8>,
225    pub objects: Vec<S2Object>,
226    pub units: Vec<S2Unit>,
227    pub items: Vec<S2Item>,
228    pub infos: Vec<S2Info>,
229    pub states: Vec<S2State>,
230    pub extensions: Vec<S2Extension>,
231    pub trailer: S2Trailer,
232}
233
234/// Password header between minimap and env vars.
235/// From `e_save_map.bb`: WriteByte(pwkey) + WriteLine(encodedpw$)
236#[derive(Debug, Clone)]
237pub struct S2Password {
238    /// XOR key byte (random 5–250).
239    pub key: u8,
240    /// XOR-encoded password string.
241    pub encoded: String,
242    /// Decoded password (key XOR each byte).
243    pub decoded: String,
244}
245
246#[derive(Debug, Clone)]
247pub struct S2EnvVars {
248    pub day: u32,
249    pub hour: u8,
250    pub minute: u8,
251    pub freezetime: u8,
252    pub skybox: String,
253    pub multiplayer: u8,
254    pub climate: u8,
255    pub music: String,
256    pub briefing: String,
257    pub fog: [u8; 4],
258    pub extra: u8,
259}
260
261/// Object record (source e_save_map.bb lines 137–148).
262#[derive(Debug, Clone)]
263pub struct S2Object {
264    pub id: u32,
265    pub typ: u32,
266    pub x: f32,
267    pub z: f32,
268    pub yaw: f32,
269    pub health: f32,
270    pub health_max: f32,
271    pub day_timer: u32,
272}
273
274/// Unit record (source e_save_map.bb lines 166–182).
275/// NOTE: unit sub-data (states, items, scripts) is NOT stored inline in the
276/// unit section; it's stored as Extensions.
277#[derive(Debug, Clone)]
278pub struct S2Unit {
279    pub id: u32,
280    pub typ: u32,
281    pub x: f32,
282    pub y: f32,
283    pub z: f32,
284    pub yaw: f32,
285    pub health: f32,
286    pub health_max: f32,
287    pub hunger: f32,
288    pub thirst: f32,
289    pub exhaustion: f32,
290    pub ai_center_x: f32,
291    pub ai_center_z: f32,
292}
293
294/// Item record (source e_save_map.bb lines 192–207).
295#[derive(Debug, Clone)]
296pub struct S2Item {
297    pub id: u32,
298    pub typ: u32,
299    pub x: f32,
300    pub y: f32,
301    pub z: f32,
302    pub yaw: f32,
303    pub health: f32,
304    pub count: u32,
305    pub parent_class: u8,
306    pub parent_mode: u8,
307    pub parent_id: u32,
308}
309
310/// Info record (source e_save_map.bb lines 243–251).
311#[derive(Debug, Clone)]
312pub struct S2Info {
313    pub id: u32,
314    pub typ: u8,
315    pub x: f32,
316    pub y: f32,
317    pub z: f32,
318    pub pitch: f32,
319    pub yaw: f32,
320    pub vars: String,
321}
322
323/// State record (source e_save_map.bb lines 260–272).
324#[derive(Debug, Clone)]
325pub struct S2State {
326    pub typ: u32,
327    pub parent_class: u32,
328    pub parent_id: u32,
329    pub x: f32,
330    pub y: f32,
331    pub z: f32,
332    pub fx: f32,
333    pub fy: f32,
334    pub fz: f32,
335    pub value: u32,
336    pub value_f: f32,
337    pub value_s: String,
338}
339
340/// Extension record (source e_save_map.bb lines 314–327).
341#[derive(Debug, Clone)]
342pub struct S2Extension {
343    pub typ: u8,
344    pub parent_class: u8,
345    pub parent_id: u32,
346    pub mode: u32,
347    pub key: String,
348    pub value: String,
349    pub stuff: String,
350}
351
352/// Trailer lines: WriteLine("") + WriteLine("### EOF Map File") + WriteLine("www.unrealsoftware.de")
353#[derive(Debug, Clone)]
354pub struct S2Trailer {
355    pub blank: String,
356    pub eof_marker: String,
357    pub url: String,
358}