1use crate::prelude::*;
2use log::error;
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::rc::Rc;
6
7#[derive(Debug, Clone)]
8pub struct Tilemap<Image: Debug + Clone> {
9 tiles: Vec<usize>,
11 flags: Vec<u32>,
13 size: MapSize,
14 visible_size: MapSize,
16 offset: MapPosition,
18 images: Vec<Rc<Image>>,
19 tile_size: (u32, u32),
20 subtile_offset: (i16, i16),
21 default_start: MapPosition,
22 exits: Vec<MapExit>,
23}
24
25impl<Image: Debug + Clone> Tilemap<Image> {
26 #[allow(clippy::too_many_arguments)]
27 pub fn new(
28 tiles: Vec<usize>,
29 flags: Vec<u32>,
30 size: MapSize,
31 tile_idx_image_name: Vec<String>,
32 tileset: Tileset<Image>,
33 render_size: (u32, u32),
34 default_start: MapPosition,
35 exits: Vec<MapExit>,
36 ) -> Result<Self, GameUtilError> {
37 let mut images = vec![];
38
39 let mut missing = vec![];
40 for name in tile_idx_image_name {
41 if let Some(img) = tileset.find_by_name(&name) {
42 images.push(Rc::new(img.clone()));
43 } else {
44 missing.push(name);
45 }
46 }
47 if !missing.is_empty() {
48 return Err(GameUtilError::InvalidTileset(
49 String::from("from code"),
50 missing,
51 ));
52 }
53
54 let mut visible_size = MapSize::new(
55 render_size.0 / tileset.tilesize().0,
56 render_size.1 / tileset.tilesize().1,
57 );
58 visible_size.w = visible_size.w.min(size.w);
59 visible_size.h = visible_size.h.min(size.h);
60
61 Ok(Self {
62 tiles,
63 flags,
64 size,
65 tile_size: tileset.tilesize(),
66 visible_size,
67 offset: MapPosition::new(0, 0),
68 images,
69 subtile_offset: (0, 0),
70 default_start,
71 exits,
72 })
73 }
74}
75
76impl<Image: Debug + Clone> Tilemap<Image> {
77 pub fn px_for_tile<P: Into<MapPosition>>(&self, tile: P) -> (isize, isize) {
80 let tile = tile.into();
81 (
82 (self.tile_size.0 * tile.x) as isize - (self.tile_size.0 * self.offset.x) as isize
83 + self.subtile_offset.0 as isize,
84 (self.tile_size.1 * tile.y) as isize - (self.tile_size.1 * self.offset.y) as isize
85 + self.subtile_offset.1 as isize,
86 )
87 }
88
89 pub fn orig_px_for_tile<P: Into<MapPosition>>(&self, tile: P) -> (isize, isize) {
92 let tile = tile.into();
93 (
94 (self.tile_size.0 * tile.x) as isize - (self.tile_size.0 * self.offset.x) as isize,
95 (self.tile_size.1 * tile.y) as isize - (self.tile_size.1 * self.offset.y) as isize,
96 )
97 }
98
99 pub fn first_visible_tile(&self) -> MapPosition {
103 if self.subtile_offset == (0, 0) {
104 self.offset
105 } else {
106 let (x_offset, y_offset) = self.tiles_visible_from_subtile_offset();
107 let mut offset = self.offset;
108 offset.x = offset.x.saturating_add_signed(x_offset);
109 offset.y = offset.y.saturating_add_signed(y_offset);
110 offset
111 }
112 }
113
114 fn tiles_visible_from_subtile_offset(&self) -> (i32, i32) {
115 let x_offset = self.subtile_offset.0 as f64 / self.tile_size.0 as f64;
116 let x_offset = if x_offset.is_sign_positive() {
117 x_offset.ceil()
118 } else {
119 x_offset.floor()
120 } as i32;
121 let y_offset = self.subtile_offset.1 as f64 / self.tile_size.1 as f64;
122 let y_offset = if y_offset.is_sign_positive() {
123 y_offset.ceil()
124 } else {
125 y_offset.floor()
126 } as i32;
127 (x_offset, y_offset)
128 }
129
130 pub fn first_fully_visible_tile(&self) -> MapPosition {
134 self.offset
135 }
136
137 pub fn last_visible_tile(&self) -> MapPosition {
141 let mut offset = self.offset;
142 offset.x += self.visible_size.w;
143 offset.y += self.visible_size.h;
144 if self.subtile_offset == (0, 0) {
145 offset
146 } else {
147 let (x_offset, y_offset) = self.tiles_visible_from_subtile_offset();
148 offset.x = offset.x.saturating_add_signed(x_offset);
149 offset.y = offset.y.saturating_add_signed(y_offset);
150 offset
151 }
152 }
153
154 pub fn last_fully_visible_tile(&self) -> MapPosition {
158 let mut offset = self.offset;
159 offset.x += self.visible_size.w;
160 offset.y += self.visible_size.h;
161 offset
162 }
163
164 pub fn is_inside<P: Into<MapPosition>>(&self, tile: P) -> bool {
166 let tile = tile.into();
167 tile.x < self.size.w && tile.y < self.size.h
168 }
169
170 fn tile_idx<P: Into<MapPosition>>(&self, tile: P) -> Option<usize> {
171 let tile = tile.into();
172 let i = tile.to_idx(self.size);
173 if i < self.tiles.len() {
174 Some(i)
175 } else {
176 None
177 }
178 }
179
180 fn tile_pos(&self, tile: usize) -> Option<MapPosition> {
181 if tile < self.tiles.len() {
182 Some(MapPosition::from_idx(tile, self.size))
183 } else {
184 None
185 }
186 }
187
188 pub fn draw<F: FnMut(&Image, (isize, isize))>(&self, mut render: F) {
191 for x in 0..self.visible_size.w {
192 for y in 0..self.visible_size.h {
193 let x = x.saturating_add(self.offset.x);
194 let y = y.saturating_add(self.offset.y);
195 let i = (x + y * self.size.w) as usize;
196 if i < self.tiles.len() {
197 render(&self.images[self.tiles[i]], self.px_for_tile((x, y)))
198 }
199 }
200 }
201 }
202
203 #[inline]
204 pub fn update_pos_with_offset(&self, pos: (isize, isize)) -> (isize, isize) {
205 (
206 pos.0 - self.subtile_offset.0 as isize,
207 pos.1 - self.subtile_offset.1 as isize,
208 )
209 }
210
211 pub fn center_on<P: Into<MapPosition>>(&mut self, pos: P) {
213 let pos = pos.into();
214 self.offset.x = pos.x.saturating_sub(self.visible_size.w / 2);
215 self.offset.y = pos.y.saturating_sub(self.visible_size.h / 2);
216 self.offset.x = self.offset.x.min(self.size.w - self.visible_size.w);
217 self.offset.y = self.offset.y.min(self.size.h - self.visible_size.h);
218 }
219
220 pub fn all_tiles_with_flag(&self, flag: u32) -> Vec<MapPosition> {
222 self.flags
223 .iter()
224 .enumerate()
225 .filter_map(|(i, tile_flag)| {
226 if *tile_flag & flag == flag {
227 Some(self.tile_pos(i))
228 } else {
229 None
230 }
231 })
232 .flatten()
233 .collect()
234 }
235
236 pub fn tile_has_flag<P: Into<MapPosition>>(&self, tile: P, value: u32) -> bool {
238 let tile = tile.into();
239 if let Some(i) = self.tile_idx(tile) {
240 value & self.flags[i] == value
241 } else {
242 false
243 }
244 }
245
246 pub fn flags_for_tile<P: Into<MapPosition>>(&self, tile: P) -> u32 {
248 let tile = tile.into();
249 if let Some(i) = self.tile_idx(tile) {
250 self.flags[i]
251 } else {
252 0
253 }
254 }
255
256 pub fn set_flag<P: Into<MapPosition>>(&mut self, tile: P, value: u32) {
258 let tile = tile.into();
259 if let Some(i) = self.tile_idx(tile) {
260 self.flags[i] |= value;
261 } else {
262 error!("set_flag({tile:?}, {value}) outside of map")
263 }
264 }
265
266 pub fn clear_flag<P: Into<MapPosition>>(&mut self, tile: P, value: u32) {
268 let tile = tile.into();
269 if let Some(i) = self.tile_idx(tile) {
270 self.flags[i] -= value;
271 } else {
272 error!("clear_flag({tile:?}, {value}) outside of map")
273 }
274 }
275
276 pub fn default_start(&self) -> MapPosition {
278 self.default_start
279 }
280
281 pub fn exits(&self) -> &Vec<MapExit> {
283 &self.exits
284 }
285
286 pub fn tile_size(&self) -> (u32, u32) {
287 self.tile_size
288 }
289
290 pub fn size(&self) -> MapSize {
291 self.size
292 }
293
294 pub fn set_subtile_offset(&mut self, subtile_offset: (i16, i16)) {
297 self.subtile_offset = subtile_offset;
298 }
299
300 pub fn subtile_offset(&self) -> (i16, i16) {
302 self.subtile_offset
303 }
304}
305
306impl TilemapFile {
307 pub fn into_tilemap<Image: Debug + Clone>(
308 self,
309 tileset: &Tileset<Image>,
310 visible_area_px: (u32, u32),
311 ) -> Result<Tilemap<Image>, GameUtilError> {
312 let mut images = vec![];
313 let mut flag_map = HashMap::new();
314 let mut missing = vec![];
315 for (i, tile) in self.tiles.iter().enumerate() {
316 if let Some(img) = tileset.find_by_name(&tile.image) {
317 flag_map.insert(i, tile.flags);
318 images.push(Rc::new(img.clone()));
319 } else {
320 missing.push(tile.image.clone());
321 }
322 }
323 if !missing.is_empty() {
324 return Err(GameUtilError::InvalidTileset(self.name.clone(), missing));
325 }
326 let size: MapSize = (self.map[0].len() as u32, self.map.len() as u32).into();
327 let mut visible_size: MapSize = (
328 visible_area_px.0 / tileset.tilesize().0,
329 visible_area_px.1 / tileset.tilesize().1,
330 )
331 .into();
332 visible_size.w = visible_size.w.min(size.w);
333 visible_size.h = visible_size.h.min(size.h);
334 let mut flags = vec![];
335 let mut tiles = vec![];
336 for row in &self.map {
337 for tile_idx in row {
338 let idx = *tile_idx as usize;
339 tiles.push(idx);
340 flags.push(flag_map[&idx]);
341 }
342 }
343 Ok(Tilemap {
344 tiles,
345 flags,
346 size,
347 visible_size,
348 offset: MapPosition::new(0, 0),
349 images,
350 tile_size: tileset.tilesize(),
351 subtile_offset: (0, 0),
352 default_start: self.data.start.into(),
353 exits: self
354 .data
355 .exits
356 .into_iter()
357 .map(MapExit::from_file)
358 .collect(),
359 })
360 }
361}
362
363#[cfg(test)]
364mod test {
365 use super::*;
366
367 const SAMPLE_RON: &str = r#"(
368 name: "Desert Temple",
369 tileset: "desert",
370 flags: {
371 1: "wall",
372 2: "trap"
373 },
374 tiles: [
375 (
376 image: "sand",
377 flags: 0
378 ),
379 (
380 image: "temple_floor",
381 flags: 0
382 ),
383 (
384 image: "temple_wall",
385 flags: 1
386 ),
387 ],
388 map: [
389 [2,2,2,2],
390 [2,0,0,2],
391 [2,0,0,2],
392 [2,1,2,2],
393 ],
394 data: (
395 start: (1,2),
396 exits: [
397 (1,3,"desert",4,5),
398 ]
399 )
400 )"#;
401
402 #[test]
403 fn test_loading() {
404 let tilemap_file: TilemapFile = ron::from_str(SAMPLE_RON).unwrap();
405 assert_eq!(tilemap_file.name, String::from("Desert Temple"));
406 assert_eq!(tilemap_file.tileset, String::from("desert"));
407 assert_eq!(
408 tilemap_file.data,
409 TilemapDataDescriptor {
410 start: (1, 2),
411 exits: vec![(1, 3, "desert".to_string(), 4, 5)],
412 }
413 );
414 assert_eq!(
415 tilemap_file.flags,
416 HashMap::from([(1, "wall".to_string()), (2, "trap".to_string())])
417 );
418 assert_eq!(
419 tilemap_file.map,
420 vec![
421 vec![2, 2, 2, 2],
422 vec![2, 0, 0, 2],
423 vec![2, 0, 0, 2],
424 vec![2, 1, 2, 2]
425 ]
426 );
427 assert_eq!(
428 tilemap_file.tiles,
429 vec![
430 TileDescriptor {
431 image: "sand".to_string(),
432 flags: 0,
433 },
434 TileDescriptor {
435 image: "temple_floor".to_string(),
436 flags: 0,
437 },
438 TileDescriptor {
439 image: "temple_wall".to_string(),
440 flags: 1,
441 }
442 ]
443 );
444 }
445
446 #[test]
447 fn init() {
448 let tileset =
449 Tileset::<&'static str>::new(vec![Rc::new("img")], vec!["img".to_string()], (16, 16));
450 let idx_map = vec!["img".to_string()];
451 let tilemap = Tilemap::new(
452 vec![0; 400],
453 vec![0; 400],
454 MapSize::new(20, 20),
455 idx_map,
456 tileset,
457 (300, 200),
458 MapPosition::new(0, 0),
459 vec![],
460 )
461 .unwrap();
462
463 assert_eq!(tilemap.offset, MapPosition::new(0, 0));
464 assert_eq!(tilemap.visible_size, MapSize::new(18, 12));
465 assert_eq!(tilemap.orig_px_for_tile((0_u32, 0)), (0, 0));
466 assert_eq!(tilemap.orig_px_for_tile((4_u32, 4)), (64, 64));
467 assert_eq!(tilemap.first_visible_tile(), MapPosition::new(0, 0));
468 }
469
470 #[test]
471 fn offset() {
472 let tileset =
473 Tileset::<&'static str>::new(vec![Rc::new("img")], vec!["img".to_string()], (16, 16));
474 let idx_map = vec!["img".to_string()];
475 let mut tilemap = Tilemap::new(
476 vec![0; 400],
477 vec![0; 400],
478 MapSize::new(20, 20),
479 idx_map,
480 tileset,
481 (300, 200),
482 MapPosition::new(0, 0),
483 vec![],
484 )
485 .unwrap();
486 tilemap.center_on(MapPosition::new(10, 6));
487
488 assert_eq!(tilemap.offset, MapPosition::new(1, 0));
489 assert_eq!(tilemap.visible_size, MapSize::new(18, 12));
490 assert_eq!(tilemap.orig_px_for_tile((0_u32, 0)), (-16, 0));
491 assert_eq!(tilemap.orig_px_for_tile((4_u32, 4)), (48, 64));
492 assert_eq!(tilemap.first_visible_tile(), MapPosition::new(1, 0));
493 }
494
495 #[allow(non_snake_case)]
496 #[test]
497 fn flags() {
498 let tileset =
499 Tileset::<&'static str>::new(vec![Rc::new("img")], vec!["img".to_string()], (16, 16));
500 let idx_map = vec!["img".to_string()];
501 let FLAG_WALL = 0b0001;
502 let FLAG_TRAP = 0b0010;
503 let mut flags = vec![0; 20];
504 flags[1] = FLAG_WALL;
505 flags[2] = FLAG_WALL;
506 flags[5] = FLAG_TRAP;
507 let mut tilemap = Tilemap::new(
508 vec![0; 20],
509 flags,
510 MapSize::new(4, 5),
511 idx_map,
512 tileset,
513 (300, 200),
514 MapPosition::new(0, 0),
515 vec![],
516 )
517 .unwrap();
518 tilemap.set_flag((3_u32, 3), FLAG_WALL);
519 tilemap.clear_flag((2_u32, 0), FLAG_WALL);
520 assert!(tilemap.tile_has_flag((1_u32, 0), FLAG_WALL));
521 assert!(!tilemap.tile_has_flag((1_u32, 0), FLAG_TRAP));
522 assert_eq!(tilemap.flags_for_tile((1_u32, 0)), FLAG_WALL);
523 assert_eq!(tilemap.flags_for_tile((3_u32, 0)), 0);
524 assert_eq!(
525 tilemap.all_tiles_with_flag(FLAG_WALL),
526 vec![MapPosition::new(1, 0), MapPosition::new(3, 3)]
527 );
528 }
529}