twmap/map/edit/
tiles.rs

1use crate::*;
2
3impl TileFlags {
4    /// Mirrors the tile, switches left <-> right.
5    /// **Doesn't work with directional physics tiles!**
6    ///
7    /// Use [TileFlips::flip_x] instead!
8    pub fn flip_x(&mut self) {
9        match self.contains(TileFlags::ROTATE) {
10            false => self.toggle(TileFlags::FLIP_X),
11            true => self.toggle(TileFlags::FLIP_Y),
12        }
13    }
14
15    /// Mirrors the tile vertically, switches up <-> down.
16    /// **Doesn't work with directional physics tiles!**
17    ///
18    /// Use [TileFlips::flip_y] instead!
19    pub fn flip_y(&mut self) {
20        match self.contains(TileFlags::ROTATE) {
21            false => self.toggle(TileFlags::FLIP_Y),
22            true => self.toggle(TileFlags::FLIP_X),
23        }
24    }
25
26    /// Performs a 90° clockwise rotation.
27    ///
28    /// For generic programming, use [TileFlips::rotate_cw] instead!
29    pub fn rotate_cw(&mut self) {
30        if self.contains(TileFlags::ROTATE) {
31            self.toggle(TileFlags::FLIP_Y | TileFlags::FLIP_X);
32        }
33        self.toggle(TileFlags::ROTATE);
34    }
35
36    /// Performs a 90° counterclockwise rotation.
37    ///
38    /// For generic programming, use [TileFlips::rotate_ccw] instead!
39    pub fn rotate_ccw(&mut self) {
40        if !self.contains(TileFlags::ROTATE) {
41            self.toggle(TileFlags::FLIP_Y | TileFlags::FLIP_X);
42        }
43        self.toggle(TileFlags::ROTATE);
44    }
45
46    /// In contrast to the [`TilesLayer`], the directional tiles of physics layers only have 4 valid rotation.
47    /// The rotate methods keep the correct state, `flip_h` and `flip_v` on the other hand don't.
48    pub fn flip_x_physics(&mut self) {
49        if self.contains(TileFlags::ROTATE) {
50            self.toggle(TileFlags::FLIP_Y | TileFlags::FLIP_X);
51        }
52    }
53
54    /// In contrast to the [`TilesLayer`], the directional tiles of physics layers only have 4 valid rotation.
55    /// The rotate methods keep the correct state, `flip_h` and `flip_v` on the other hand don't.
56    pub fn flip_y_physics(&mut self) {
57        if !self.contains(TileFlags::ROTATE) {
58            self.toggle(TileFlags::FLIP_Y | TileFlags::FLIP_X);
59        }
60    }
61}
62
63trait TilePrivate {
64    fn mirror_id(id: u8) -> u8 {
65        id
66    }
67    fn directional_physics_tile(_id: u8) -> bool {
68        false
69    }
70}
71
72impl TilePrivate for Tile {}
73
74fn mirror_lasers(mut id: u8) -> u8 {
75    if (203..=205).contains(&id) {
76        id += (206 - id) * 2;
77    } else if (207..=209).contains(&id) {
78        id -= (id - 206) * 2;
79    }
80    id
81}
82
83impl TilePrivate for GameTile {
84    fn mirror_id(id: u8) -> u8 {
85        mirror_lasers(id)
86    }
87
88    fn directional_physics_tile(id: u8) -> bool {
89        matches!(id, 60 | 61 | 64 | 65 | 67 | 224 | 225)
90    }
91}
92impl TilePrivate for Tele {}
93impl TilePrivate for Switch {
94    fn mirror_id(id: u8) -> u8 {
95        mirror_lasers(id)
96    }
97    fn directional_physics_tile(id: u8) -> bool {
98        matches!(id, 224 | 225)
99    }
100}
101impl TilePrivate for Tune {}
102
103/// Generic tile flips and rotates for every tile type.
104pub trait TileFlips: AnyTile {
105    /// Left <-> Right
106    fn flip_x(&mut self);
107    /// Up <-> Down
108    fn flip_y(&mut self);
109    /// Rotates clockwise
110    fn rotate_cw(&mut self);
111    /// Rotates counterclockwise
112    fn rotate_ccw(&mut self);
113}
114
115impl<T: AnyTile + TilePrivate> TileFlips for T {
116    fn flip_x(&mut self) {
117        let id = self.id();
118        if let Some(flags) = self.flags_mut() {
119            if T::directional_physics_tile(id) {
120                flags.flip_x_physics();
121            } else {
122                flags.flip_x();
123            }
124            *self.id_mut() = T::mirror_id(*self.id_mut());
125        }
126    }
127
128    fn flip_y(&mut self) {
129        let id = self.id();
130        if let Some(flags) = self.flags_mut() {
131            if T::directional_physics_tile(id) {
132                flags.flip_y_physics();
133            } else {
134                flags.flip_y();
135            }
136            *self.id_mut() = T::mirror_id(*self.id_mut());
137        }
138    }
139
140    fn rotate_cw(&mut self) {
141        if let Some(flags) = self.flags_mut() {
142            flags.rotate_cw();
143        }
144    }
145
146    fn rotate_ccw(&mut self) {
147        if let Some(flags) = self.flags_mut() {
148            flags.rotate_ccw();
149        }
150    }
151}
152
153impl TileFlips for Speedup {
154    fn flip_x(&mut self) {
155        let mut angle = i16::from(self.angle);
156        angle = 180 - angle;
157        if angle < 0 {
158            angle += 360;
159        }
160        self.angle = angle.into();
161    }
162
163    fn flip_y(&mut self) {
164        let mut angle = i16::from(self.angle);
165        angle = 180 - angle;
166        angle *= -1;
167        angle = 180 - angle;
168        angle %= 360;
169        self.angle = angle.into();
170    }
171
172    fn rotate_cw(&mut self) {
173        let mut angle = i16::from(self.angle);
174        angle += 90;
175        angle %= 360;
176        self.angle = angle.into();
177    }
178
179    fn rotate_ccw(&mut self) {
180        let mut angle = i16::from(self.angle);
181        angle += 270;
182        angle %= 360;
183        self.angle = angle.into();
184    }
185}
186
187pub trait EditTile {
188    fn tile(_tile: &mut Tile) {}
189    fn game_tile(_tile: &mut GameTile) {}
190    fn tele(_tele: &mut Tele) {}
191    fn speedup(_speedup: &mut Speedup) {}
192    fn switch(_switch: &mut Switch) {}
193    fn tune(_tune: &mut Tune) {}
194}
195
196fn edit_tilemap<T: TilemapLayer, F: Fn(&mut T::TileType)>(layer: &mut T, f: F) {
197    layer.tiles_mut().unwrap_mut().iter_mut().for_each(f)
198}
199
200impl TwMap {
201    /// Requires the tiles to be loaded
202    pub fn edit_tiles<T: EditTile>(&mut self) {
203        for group in &mut self.groups {
204            for layer in &mut group.layers {
205                match layer {
206                    Layer::Game(l) => edit_tilemap(l, T::game_tile),
207                    Layer::Tiles(l) => edit_tilemap(l, T::tile),
208                    Layer::Front(l) => edit_tilemap(l, T::game_tile),
209                    Layer::Tele(l) => edit_tilemap(l, T::tele),
210                    Layer::Speedup(l) => edit_tilemap(l, T::speedup),
211                    Layer::Switch(l) => edit_tilemap(l, T::switch),
212                    Layer::Tune(l) => edit_tilemap(l, T::tune),
213                    Layer::Quads(_) | Layer::Sounds(_) | Layer::Invalid(_) => {}
214                }
215            }
216        }
217    }
218}
219
220pub struct ZeroUnusedParts;
221
222impl TileFlags {
223    fn clear_unused(&mut self) -> bool {
224        let cleared = *self & TileFlags::all();
225        let changed = cleared != *self;
226        *self = cleared;
227        changed
228    }
229
230    fn clear_unused_and_opaque(&mut self) -> bool {
231        let cleared = *self & (TileFlags::all() - TileFlags::OPAQUE);
232        let changed = cleared != *self;
233        *self = cleared;
234        changed
235    }
236}
237
238impl EditTile for ZeroUnusedParts {
239    fn tile(tile: &mut Tile) {
240        tile.flags.clear_unused();
241        tile.unused = 0;
242        tile.skip = 0;
243    }
244    fn game_tile(tile: &mut GameTile) {
245        tile.flags.clear_unused_and_opaque();
246        tile.unused = 0;
247        tile.skip = 0;
248    }
249    fn speedup(speedup: &mut Speedup) {
250        speedup.unused_padding = 0;
251    }
252    fn switch(switch: &mut Switch) {
253        switch.flags.clear_unused_and_opaque();
254    }
255}
256
257fn zero_air_tile<T: AnyTile>(tile: &mut T) {
258    if tile.id() == 0 {
259        *tile = T::default()
260    }
261}
262
263/// To zero out tiles with id 0 completely
264pub struct ZeroAir;
265
266impl EditTile for ZeroAir {
267    fn tile(tile: &mut Tile) {
268        zero_air_tile(tile)
269    }
270    fn game_tile(tile: &mut GameTile) {
271        zero_air_tile(tile)
272    }
273    fn tele(tele: &mut Tele) {
274        zero_air_tile(tele)
275    }
276    fn speedup(speedup: &mut Speedup) {
277        zero_air_tile(speedup)
278    }
279    fn switch(switch: &mut Switch) {
280        zero_air_tile(switch)
281    }
282    fn tune(tune: &mut Tune) {
283        zero_air_tile(tune)
284    }
285}
286
287fn reset_unnecessary_rotation<T: TilePrivate + AnyTile>(tile: &mut T) {
288    if !T::directional_physics_tile(tile.id()) {
289        if let Some(flags) = tile.flags_mut() {
290            flags.remove(TileFlags::ROTATE | TileFlags::FLIP_X | TileFlags::FLIP_Y);
291        }
292    }
293}
294
295/// DDNet specific!
296/// Resets the rotation of physics tile that are not effected by rotations.
297pub struct DDNetNormalizePhysicsRotation;
298
299// We ignore Tiles layers as non-physics tiles.
300// We ignore Speedup tiles as they do not have tile flags.
301impl EditTile for DDNetNormalizePhysicsRotation {
302    fn game_tile(tile: &mut GameTile) {
303        reset_unnecessary_rotation(tile);
304    }
305    fn tele(tele: &mut Tele) {
306        reset_unnecessary_rotation(tele);
307    }
308    fn switch(switch: &mut Switch) {
309        reset_unnecessary_rotation(switch);
310    }
311    fn tune(tune: &mut Tune) {
312        reset_unnecessary_rotation(tune);
313    }
314}
315
316fn fix_broken_physics_rotation<T: TilePrivate + AnyTile>(tile: &mut T) {
317    if T::directional_physics_tile(tile.id()) {
318        let Some(flags) = tile.flags_mut() else {
319            return;
320        };
321        if flags.contains(TileFlags::FLIP_X) ^ flags.contains(TileFlags::FLIP_Y) {
322            if flags.contains(TileFlags::ROTATE) {
323                flags.insert(TileFlags::FLIP_X | TileFlags::FLIP_Y);
324            } else {
325                flags.remove(TileFlags::FLIP_X | TileFlags::FLIP_Y);
326            }
327        }
328    }
329}
330
331/// Fixes incorrect orientation of directional physics tiles.
332pub struct DDNetFixPhysicsRotation;
333
334impl EditTile for DDNetFixPhysicsRotation {
335    fn game_tile(tile: &mut GameTile) {
336        fix_broken_physics_rotation(tile);
337    }
338    fn tele(tele: &mut Tele) {
339        fix_broken_physics_rotation(tele);
340    }
341    fn switch(switch: &mut Switch) {
342        fix_broken_physics_rotation(switch);
343    }
344    fn tune(tune: &mut Tune) {
345        fix_broken_physics_rotation(tune);
346    }
347}
348
349/// Migrates tile id's to a newer replacement.
350/// Only updates tiles that have a drop-in replacement.
351pub struct DDNetMigrateSpeedup;
352
353impl EditTile for DDNetMigrateSpeedup {
354    fn speedup(speedup: &mut Speedup) {
355        if speedup.id == 28 {
356            speedup.id = 29;
357            if (1..5).contains(&speedup.max_speed) {
358                speedup.max_speed = 5;
359            }
360        }
361    }
362}