twmap/map/
mod.rs

1use crate::datafile;
2
3use bitflags::bitflags;
4use fixed::types::{I17F15, I22F10, I27F5};
5use image::RgbaImage;
6use ndarray::Array2;
7use serde::{Deserialize, Serialize};
8use structview::View;
9use thiserror::Error;
10use vek::{Disk, Extent2, Rect, Rgba, Uv, Vec2};
11
12use std::io;
13
14mod checks;
15/// Complex methods for map structs
16pub mod edit;
17/// Simple impl blocks for map structs and traits
18mod impls;
19mod load;
20/// MapDir format implementation
21mod map_dir;
22mod parse;
23mod save;
24
25pub use checks::MapError;
26pub use load::{Load, LoadMultiple};
27pub use map_dir::MapDirParseError;
28pub use parse::MapParseError;
29
30#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
31#[serde(rename_all = "lowercase", tag = "type")]
32pub enum Version {
33    DDNet06,
34    Teeworlds07,
35}
36
37/// The TwMap struct represents a Teeworlds 0.7 map or a DDNet 0.6 map.
38/// Which one of those it is will always be determined during the parsing process and is stored in the `version` field.
39///
40/// The library cares a lot about the integrity of the struct.
41/// The [`check`](struct.TwMap.html#method.check) method verifies that all limitations are met.
42///
43/// # Parsing
44///
45/// TwMap has several different parsing methods: [`parse`](struct.TwMap.html#method.parse), [`parse_path`](struct.TwMap.html#method.parse_path), [`parse_file`](struct.TwMap.html#method.parse_file), [`parse_dir`](struct.TwMap.html#method.parse_dir), [`parse_datafile`](struct.TwMap.html#method.parse_datafile)
46///
47/// Each of them execute the [`check`](struct.TwMap.html#method.check) method to finalize the process.
48///
49/// If you want to leave out the checks, you can use the `_unchecked` variation of that parsing method if it is provided.
50/// Note that the `_unchecked` variation might also exclude some common fixes.
51///
52/// # Saving
53///
54/// TwMap can save maps in the binary format ([`save`](struct.TwMap.html#method.save), [`save_file`](struct.TwMap.html#method.save_file)) and in the MapDir format ([`save_dir`](struct.TwMap.html#method.save_dir)).
55///
56/// Each saving method will first execute [`check`](struct.TwMap.html#method.check), if the map fails the check, it will not be saved.
57///
58/// # Loading
59///
60/// When loading a map from the binary format, a lot of data will be decompressed in the process.
61/// Since this is the main slowdown factor, some larger data chunks will be left compressed.
62/// The compressed parts are the `data` field in [`Image`](struct.Image.html)s and [`Sound`](struct.Sound.html)s and the `tiles` field in tilemap layers.
63/// If you want to save the map at the end anyways, then you can simply use the [`load`](struct.TwMap.html#method.load) method on the entire map.
64/// If not, use the `load` method only on the images, sounds and tilemap layers that you want to use.
65///
66/// **Note:**
67/// - you can also use `load` on slices and vectors of Images, Sounds, Layers, Groups with layers,
68/// - some methods rely on having parts of the map loaded, especially more abstract methods like [`mirror`](struct.TwMap.html#method.mirror) and [`rotate_right`](struct.TwMap.html#method.rotate_right)
69/// - if you want to leave out the checks on the decompressed data, you can use the `load_unchecked` methods
70///
71/// # Fixed Point Integers
72///
73/// In many parts of the struct fixed point integers are used.
74/// They are represented using the crate `fixed`.
75/// Position and size fixed point integers are always chosen so that 1 always translates to the width of 1 tile.
76/// The other usages are usually chosen so that 0 = 0%, 1 = 100%.
77#[derive(Debug, Clone, Eq, PartialEq)]
78pub struct TwMap {
79    /// Determines what version this map should be saved as
80    pub version: Version,
81    /// Metadata
82    pub info: Info,
83    /// Textures
84    pub images: Vec<Image>,
85    /// Animation strips
86    pub envelopes: Vec<Envelope>,
87    /// Collections of layers
88    pub groups: Vec<Group>,
89    /// Sfx (not available in Teeworlds07 maps)
90    pub sounds: Vec<Sound>,
91}
92
93#[derive(Error, Debug)]
94#[non_exhaustive]
95pub enum Error {
96    #[error("Map - {0}")]
97    Map(#[from] MapError),
98    #[error("Map from Datafile - {0}")]
99    MapParse(#[from] parse::MapParseError),
100    #[error("Datafile saving - {0}")]
101    DatafileSave(#[from] datafile::DatafileSaveError),
102    #[error("Datafile parsing - {0}")]
103    DatafileParse(#[from] datafile::DatafileParseError),
104    #[error("IO - {0}")]
105    Io(#[from] io::Error),
106    #[error("MapDir - {0}")]
107    MapDirParse(#[from] map_dir::MapDirParseError),
108}
109
110#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
111// identifier for all relevant types of layers
112pub enum LayerKind {
113    Game,
114    Tiles,
115    Quads,
116    Front,
117    Tele,
118    Speedup,
119    Switch,
120    Tune,
121    Sounds,
122    Invalid(InvalidLayerKind),
123}
124
125#[derive(Debug, Eq, PartialOrd, PartialEq, Copy, Clone, Hash)]
126pub enum InvalidLayerKind {
127    Unknown(i32),        // unknown value of 'LAYERTYPE' identifier
128    UnknownTilemap(i32), // 'LAYERTYPE' identified a tile layer, unknown value of 'TILESLAYERFLAG' identifier
129    NoType,              // layer item too short to get 'LAYERTYPE' identifier
130    NoTypeTilemap, // 'LAYERTYPE' identified a tile layer, layer item too short to get 'TILESLAYERFLAG' identifier
131}
132
133#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
134/// Holds compressed data. Use [`Load`] or [`LoadMultiple`] to decompress this.
135/// Too construct with the already loaded data, simply use `CompressedData::from`.
136pub enum CompressedData<T, U> {
137    Compressed(Vec<u8>, usize, U),
138    Loaded(T),
139}
140
141/// Note that all strings have size limits, check the constants associated with this struct.
142#[derive(Debug, Default, Eq, PartialEq, Clone, Serialize, Deserialize)]
143pub struct Info {
144    pub author: String,
145    pub version: String,
146    pub credits: String,
147    pub license: String,
148    pub settings: Vec<String>,
149}
150
151#[derive(Debug, Eq, PartialEq, Clone)]
152pub enum Image {
153    /// Image data should be shipped with the client.
154    External(ExternalImage),
155    /// Image data stored in map file.
156    Embedded(EmbeddedImage),
157}
158
159#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
160pub struct ExternalImage {
161    /// The name with the file extension `.png` is considered a file name and must be sanitized.
162    #[serde(skip)]
163    pub name: String,
164
165    pub size: Extent2<u32>,
166}
167
168#[derive(Debug, Eq, PartialEq, Copy, Clone)]
169pub struct ImageLoadInfo {
170    pub size: Extent2<u32>,
171}
172
173#[derive(Debug, Eq, PartialEq, Clone)]
174pub struct EmbeddedImage {
175    /// The name with the file extension `.png` is considered a file name and must be sanitized.
176    pub name: String,
177
178    pub image: CompressedData<RgbaImage, ImageLoadInfo>, // None if the image is external (not embedded)
179}
180
181const PARALLAX_DIVISOR: Vec2<i32> = Vec2::new(100, 100);
182
183#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
184pub struct Group {
185    pub name: String,
186    /// Offsets the layers from the upper left corner of the map (in tiles)
187    pub offset: Vec2<I27F5>,
188    /// How fast this group moves relative to the physics group (100, 100) is the same speed, (0, 0) is static.
189    pub parallax: Vec2<i32>,
190    #[serde(skip)]
191    pub layers: Vec<Layer>,
192    /// Enables the clip.
193    pub clipping: bool,
194    /// Defines a rectangle outside of which the group is invisible.
195    /// The rectangle is not affected by parallax or offset.
196    pub clip: Rect<I27F5, I27F5>,
197}
198
199#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
200pub struct Sound {
201    /// Translates directly to a filename ({name}.opus).
202    /// The file name must be sanitized.
203    pub name: String,
204    pub data: CompressedData<Vec<u8>, ()>,
205}
206
207#[derive(Default, Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
208pub struct AutomapperConfig {
209    /// Automappers can have multiple configs to choose from.
210    /// This selects the config with a simple index.
211    pub config: Option<u16>,
212    /// 0 => random seed.
213    pub seed: u32,
214    /// Whether the editor should run the automapper after every change automatically.
215    pub automatic: bool,
216}
217
218#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
219pub struct BezierCurve<T> {
220    pub handle_l: Vec2<T>,
221    pub handle_r: Vec2<T>,
222}
223
224#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize, Default)]
225#[serde(rename_all = "snake_case", tag = "type")]
226/// Belongs to a envelope point.
227/// Describes the change in value from this point to the next.
228///
229/// For math: `frac` is where the interpolate between the two points (0 is at first point, 1 at second, 0.5 halfway),
230/// The resulting value then mixes the two values: `left + (right - left) * result`.
231pub enum CurveKind<T> {
232    /// Value of first point until second point, abrupt change there.
233    Step,
234    /// Linear interpolation between the two points.
235    /// Math: `frac`
236    #[default]
237    Linear,
238    /// First slow, later much faster value change.
239    /// Math: `frac^3`
240    Slow,
241    /// First fast, later much slower value change.
242    /// Math: `1 - (1 - frac)^3`
243    Fast,
244    /// Slow, faster then once more slow value change.
245    /// Math: `3 * frac^2 - 2 * frac^3`
246    Smooth,
247    /// Very flexible curve, each channel individually.
248    /// Uses bezier curves for interpolation.
249    /// Only kind of curve where every channel gets their own interpolation values.
250    Bezier(BezierCurve<T>),
251    /// For compatibility, will error on check.
252    Unknown(i32),
253}
254
255#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
256pub struct EnvPoint<T> {
257    /// Time stamp of this point in milliseconds, must not be negative.
258    pub time: i32,
259    pub content: T,
260    /// Interpolation mode between this point and the next one.
261    #[serde(flatten)]
262    pub curve: CurveKind<T>,
263}
264
265/// Default bezier tangent values are all zeroes.
266/// This trait implements the constructors for those values.
267/// only I32Color needs to implement this manually, since volume (i32) and Position are already all zeroes in their default values.
268pub trait BezierDefault: Default {
269    fn bezier_default() -> Self {
270        Default::default()
271    }
272}
273
274#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize, Default)]
275pub struct Position {
276    #[serde(flatten)]
277    pub offset: Vec2<I17F15>,
278    /// Rotation in degrees. 90 would be a 90° clockwise rotation
279    pub rotation: I22F10,
280}
281
282#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
283pub struct Volume(pub I22F10);
284
285#[derive(Default, Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
286pub struct Env<T: Copy> {
287    pub name: String,
288    /// Whether the envelope should be synchronized across clients by using server time.
289    /// Will use client time otherwise
290    pub synchronized: bool,
291    /// Must be in chronological order based on their time
292    pub points: Vec<EnvPoint<T>>,
293}
294
295#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
296#[serde(rename_all = "snake_case", tag = "type")]
297pub enum Envelope {
298    Position(Env<Position>),
299    Color(Env<Rgba<I22F10>>),
300    /// Not available in Teeworlds07 maps.
301    Sound(Env<Volume>),
302}
303
304#[derive(Debug, Clone, Eq, PartialEq)]
305pub struct TilesLoadInfo {
306    pub size: Extent2<u32>,
307    pub compression: bool,
308}
309
310unsafe impl View for TileFlags {} // deriving directly didn't work
311bitflags! {
312    /// These bitflags are found in many of the tile types found in tilemap layers.
313    /// Note that the naming of the flags is very confusing and it is almost **never** a good idea to edit them directly.
314    /// Use the helper methods instead, as they will have much more expected behavior!
315    ///
316    /// The big issue is that while [`Tile`]s from [`TilesLayer`]s have 8 valid orientations,
317    /// directional tiles from physics layers (that contain `TileFlag`s) only have 4 valid: Up, Down Left and Right.
318    /// Up is the default orientation of a physics tile.
319    /// While this could've been no issue, directional physics tiles actually have broken physics when in another state.
320    #[repr(C)]
321    #[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, Hash, Eq, PartialEq)]
322    #[serde(into = "map_dir::DirTileFlags", try_from = "map_dir::DirTileFlags")]
323    pub struct TileFlags: u8 {
324        /// Mirrors the tile horizontally, switching Left <-> Right.
325        ///
326        /// Formerly, this flag was called `FLIP_V`.
327        const FLIP_X = 0b0001;
328        /// Mirrors the tile vertically, switching Top <-> Bottom.
329        ///
330        /// Formerly, this flag was called `FLIP_V`.
331        const FLIP_Y = 0b0010;
332        /// Hints that this tile has no (significant) transparency.
333        const OPAQUE = 0b0100;
334        /// Rotates the tile clockwise.
335        /// The rotation happens **after** the flip operations!
336        const ROTATE = 0b1000;
337    }
338}
339
340#[derive(Debug, Copy, Clone, View, Eq, PartialEq, Default, Serialize, Deserialize)]
341#[repr(C)]
342pub struct Tile {
343    // for TilesLayer
344    pub id: u8,
345    #[serde(flatten)]
346    pub flags: TileFlags,
347    #[serde(skip)]
348    pub(crate) skip: u8, // used for 0.7 tile compression
349    #[serde(skip)]
350    pub(crate) unused: u8,
351}
352
353#[derive(Debug, Copy, Clone, View, Eq, PartialEq, Default, Serialize, Deserialize)]
354#[repr(C)]
355pub struct GameTile {
356    // for GameLayer and FrontLayer
357    pub id: u8,
358    #[serde(flatten)]
359    pub flags: TileFlags,
360    #[serde(skip)]
361    pub(crate) skip: u8, // used for 0.7 tile compression
362    #[serde(skip)]
363    pub(crate) unused: u8,
364}
365
366#[derive(Debug, Copy, Clone, View, Eq, PartialEq, Default, Serialize, Deserialize)]
367#[repr(C)]
368pub struct Tele {
369    pub number: u8,
370    pub id: u8,
371}
372
373#[derive(Debug, Copy, Clone, View, Eq, PartialEq, Default, Serialize, Deserialize)]
374#[repr(C)]
375#[serde(into = "i16", from = "i16")]
376/// Required to make the Speedup tile struct 1-byte-aligned
377pub struct I16 {
378    pub(crate) bytes: [u8; 2],
379}
380
381#[derive(Debug, Copy, Clone, View, Eq, PartialEq, Default, Serialize, Deserialize)]
382#[repr(C)]
383pub struct Speedup {
384    pub force: u8,
385    pub max_speed: u8,
386    pub id: u8,
387    #[serde(skip)]
388    pub(crate) unused_padding: u8,
389    /// Angle, value between 0 and (exclusive) 360
390    /// 0 points right, angle increases clock-wise
391    pub angle: I16,
392}
393
394#[derive(Debug, Copy, Clone, View, Eq, PartialEq, Default, Serialize, Deserialize)]
395#[repr(C)]
396pub struct Switch {
397    pub number: u8,
398    pub id: u8,
399    #[serde(flatten)]
400    pub flags: TileFlags,
401    pub delay: u8,
402}
403
404#[derive(Debug, Copy, Clone, View, Eq, PartialEq, Default, Serialize, Deserialize)]
405#[repr(C)]
406pub struct Tune {
407    pub number: u8,
408    pub id: u8,
409}
410
411#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
412pub struct GameLayer {
413    #[serde(flatten, with = "map_dir::tiles_serialization")]
414    pub tiles: CompressedData<Array2<GameTile>, TilesLoadInfo>,
415}
416
417#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
418pub struct FrontLayer {
419    #[serde(flatten, with = "map_dir::tiles_serialization")]
420    pub tiles: CompressedData<Array2<GameTile>, TilesLoadInfo>,
421}
422
423#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
424pub struct TeleLayer {
425    #[serde(flatten, with = "map_dir::tiles_serialization")]
426    pub tiles: CompressedData<Array2<Tele>, TilesLoadInfo>,
427}
428
429#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
430pub struct SpeedupLayer {
431    #[serde(flatten, with = "map_dir::tiles_serialization")]
432    pub tiles: CompressedData<Array2<Speedup>, TilesLoadInfo>,
433}
434
435#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
436pub struct SwitchLayer {
437    #[serde(flatten, with = "map_dir::tiles_serialization")]
438    pub tiles: CompressedData<Array2<Switch>, TilesLoadInfo>,
439}
440
441#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
442pub struct TuneLayer {
443    #[serde(flatten, with = "map_dir::tiles_serialization")]
444    pub tiles: CompressedData<Array2<Tune>, TilesLoadInfo>,
445}
446
447#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
448pub struct TilesLayer {
449    pub name: String,
450    pub detail: bool,
451    pub color: Rgba<u8>,
452    #[serde(with = "map_dir::envelope_index_serialization")]
453    pub color_env: Option<u16>,
454    pub color_env_offset: i32,
455    #[serde(with = "map_dir::image_index_serialization")]
456    pub image: Option<u16>,
457    #[serde(flatten, with = "map_dir::tiles_serialization")]
458    pub tiles: CompressedData<Array2<Tile>, TilesLoadInfo>,
459    pub automapper_config: AutomapperConfig,
460}
461
462#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
463pub struct Quad {
464    /// Position of the 4 corners (top-left -> top-right -> bottom-left -> bottom-right).
465    /// The positions are **NOT** relative to the position of the quad itself.
466    pub corners: [Vec2<I17F15>; 4],
467    /// Position of the quad, around which it rotates.
468    pub position: Vec2<I17F15>,
469    pub colors: [Rgba<u8>; 4],
470    pub texture_coords: [Uv<I22F10>; 4], // represents the stretching done by shift+dragging of corners in the editor
471    #[serde(with = "map_dir::envelope_index_serialization")]
472    pub position_env: Option<u16>,
473    pub position_env_offset: i32,
474    #[serde(with = "map_dir::envelope_index_serialization")]
475    pub color_env: Option<u16>,
476    pub color_env_offset: i32,
477}
478
479#[derive(Default, Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
480pub struct QuadsLayer {
481    pub name: String,
482    pub detail: bool,
483    pub quads: Vec<Quad>,
484    #[serde(with = "map_dir::image_index_serialization")]
485    pub image: Option<u16>, // index to its image
486}
487
488#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
489#[serde(rename_all = "snake_case", tag = "type")]
490pub enum SoundArea {
491    Rectangle(Rect<I17F15, I17F15>),
492    Circle(Disk<I17F15, I27F5>),
493}
494
495#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
496pub struct SoundSource {
497    pub area: SoundArea,
498    pub looping: bool,
499    pub panning: bool,
500    pub delay: i32,
501    pub falloff: u8,
502    #[serde(with = "map_dir::envelope_index_serialization")]
503    pub position_env: Option<u16>,
504    pub position_env_offset: i32,
505    #[serde(with = "map_dir::envelope_index_serialization")]
506    pub sound_env: Option<u16>,
507    pub sound_env_offset: i32,
508}
509
510#[derive(Default, Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
511pub struct SoundsLayer {
512    pub name: String,
513    pub detail: bool,
514    pub sources: Vec<SoundSource>,
515    #[serde(with = "map_dir::sound_index_serialization")]
516    pub sound: Option<u16>,
517}
518
519#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
520#[serde(rename_all = "snake_case", tag = "type")]
521pub enum Layer {
522    Game(GameLayer),
523    Tiles(TilesLayer),
524    Quads(QuadsLayer),
525    Front(FrontLayer),
526    Tele(TeleLayer),
527    Speedup(SpeedupLayer),
528    Switch(SwitchLayer),
529    Tune(TuneLayer),
530    Sounds(SoundsLayer),
531    #[serde(skip)]
532    Invalid(InvalidLayerKind),
533}
534
535pub trait TilemapLayer: AnyLayer {
536    type TileType: AnyTile;
537
538    fn tiles(&self) -> &CompressedData<Array2<Self::TileType>, TilesLoadInfo>;
539
540    fn tiles_mut(&mut self) -> &mut CompressedData<Array2<Self::TileType>, TilesLoadInfo>;
541}
542
543pub trait AnyTile: Default + PartialEq + Copy + Clone + checks::TileChecking + View {
544    fn id(&self) -> u8;
545
546    fn id_mut(&mut self) -> &mut u8;
547
548    fn flags(&self) -> Option<TileFlags>;
549
550    fn flags_mut(&mut self) -> Option<&mut TileFlags>;
551}
552
553pub trait AnyLayer: Sized {
554    fn kind() -> LayerKind;
555
556    fn get(layer: &Layer) -> Option<&Self>;
557
558    fn get_mut(layer: &mut Layer) -> Option<&mut Self>;
559}
560
561/// Marker trait, implemented for all physics layers
562pub trait PhysicsLayer: TilemapLayer {}