Mercurial > molko
comparison libmlk-rpg/rpg/map.c @ 243:71b3b7036de7
misc: lot of cleanups,
- prefix libraries with libmlk,
- move assets from source directories closes #2520,
- prefix header guards closes #2519
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sat, 28 Nov 2020 22:37:30 +0100 |
parents | librpg/rpg/map.c@71f989ae8de9 |
children | 8ef7fb7f14ad |
comparison
equal
deleted
inserted
replaced
242:4c24604efcab | 243:71b3b7036de7 |
---|---|
1 /* | |
2 * map.c -- game map | |
3 * | |
4 * Copyright (c) 2020 David Demelier <markand@malikania.fr> | |
5 * | |
6 * Permission to use, copy, modify, and/or distribute this software for any | |
7 * purpose with or without fee is hereby granted, provided that the above | |
8 * copyright notice and this permission notice appear in all copies. | |
9 * | |
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
17 */ | |
18 | |
19 #include <assert.h> | |
20 #include <stdio.h> | |
21 #include <stdlib.h> | |
22 #include <string.h> | |
23 | |
24 #include <core/error.h> | |
25 #include <core/event.h> | |
26 #include <core/image.h> | |
27 #include <core/maths.h> | |
28 #include <core/painter.h> | |
29 #include <core/sprite.h> | |
30 #include <core/sys.h> | |
31 #include <core/texture.h> | |
32 #include <core/window.h> | |
33 | |
34 #include <ui/debug.h> | |
35 | |
36 #include "map.h" | |
37 #include "tileset.h" | |
38 | |
39 /* | |
40 * This is the speed the player moves on the map. | |
41 * | |
42 * SPEED represents the number of pixels it must move per SEC. | |
43 * SEC simply represends the number of milliseconds in one second. | |
44 */ | |
45 #define SPEED 220 | |
46 #define SEC 1000 | |
47 | |
48 /* | |
49 * Those are margins within the edge of the screen. The camera always try to | |
50 * keep those padding between the player and the screen. | |
51 */ | |
52 #define MARGIN_WIDTH 80 | |
53 #define MARGIN_HEIGHT 80 | |
54 | |
55 #define WIDTH(map) ((map)->columns * (map)->tileset->sprite->cellw) | |
56 #define HEIGHT(map) ((map)->rows * (map)->tileset->sprite->cellh) | |
57 | |
58 /* | |
59 * This structure defines the possible movement of the player as flags since | |
60 * it's possible to make diagonal movements. | |
61 */ | |
62 enum movement { | |
63 MOVING_UP = 1 << 0, | |
64 MOVING_RIGHT = 1 << 1, | |
65 MOVING_DOWN = 1 << 2, | |
66 MOVING_LEFT = 1 << 3 | |
67 }; | |
68 | |
69 /* | |
70 * A bit of explanation within this array. The structure walksprite requires | |
71 * an orientation between 0-7 depending on the user direction. | |
72 * | |
73 * Since keys for moving the character may be pressed at the same time, we need | |
74 * a conversion table from "key pressed" to "orientation". | |
75 * | |
76 * When an orientation is impossible, it is set to -1. Example, when both left | |
77 * and right are pressed. | |
78 * | |
79 * MOVING_UP = 0001 = 0x1 | |
80 * MOVING_RIGHT = 0010 = 0x2 | |
81 * MOVING_DOWN = 0100 = 0x3 | |
82 * MOVING_LEFT = 1000 = 0x4 | |
83 */ | |
84 static unsigned int orientations[16] = { | |
85 [0x1] = 0, | |
86 [0x2] = 2, | |
87 [0x3] = 1, | |
88 [0x4] = 4, | |
89 [0x6] = 3, | |
90 [0x8] = 6, | |
91 [0x9] = 7, | |
92 [0xC] = 5 | |
93 }; | |
94 | |
95 struct collision { | |
96 int x; | |
97 int y; | |
98 unsigned int w; | |
99 unsigned int h; | |
100 }; | |
101 | |
102 static bool | |
103 is_collision_out(const struct map *map, struct collision *block, int drow, int dcol) | |
104 { | |
105 if (drow) { | |
106 /* Object outside of left-right bounds. */ | |
107 if (block->x + (int)block->w <= map->player_x || | |
108 block->x >= map->player_x + (int)map->player_sprite->cellw) | |
109 return false; | |
110 | |
111 if ((drow < 0 && block->y >= map->player_y + (int)map->player_sprite->cellh) || | |
112 (drow > 0 && block->y + block->h <= map->player_y + map->player_sprite->cellh)) | |
113 return false; | |
114 } else if (dcol) { | |
115 /* Object outside of up-down bounds. */ | |
116 if (block->y + (int)block->h <= map->player_y || | |
117 block->y >= map->player_y + (int)map->player_sprite->cellh) | |
118 return false; | |
119 | |
120 if ((dcol < 0 && block->x >= map->player_x + (int)map->player_sprite->cellw) || | |
121 (dcol > 0 && block->x + block->w <= map->player_x + map->player_sprite->cellw)) | |
122 return false; | |
123 } | |
124 | |
125 return true; | |
126 } | |
127 | |
128 static void | |
129 center(struct map *map) | |
130 { | |
131 map->view_x = map->player_x - (int)(map->view_w / 2); | |
132 map->view_y = map->player_y - (int)(map->view_h / 2); | |
133 | |
134 if (map->view_x < 0) | |
135 map->view_x = 0; | |
136 else if ((unsigned int)map->view_x > WIDTH(map) - map->view_w) | |
137 map->view_x = WIDTH(map) - map->view_w; | |
138 | |
139 if (map->view_y < 0) | |
140 map->view_y = 0; | |
141 else if ((unsigned int)map->view_y > HEIGHT(map) - map->view_h) | |
142 map->view_y = HEIGHT(map) - map->view_h; | |
143 } | |
144 | |
145 static void | |
146 init(struct map *map) | |
147 { | |
148 /* Adjust view. */ | |
149 map->view_w = window.w; | |
150 map->view_h = window.h; | |
151 | |
152 /* Adjust margin. */ | |
153 map->margin_w = map->view_w - (MARGIN_WIDTH * 2) - map->player_sprite->cellw; | |
154 map->margin_h = map->view_h - (MARGIN_HEIGHT * 2) - map->player_sprite->cellh; | |
155 | |
156 /* Center the view by default. */ | |
157 center(map); | |
158 | |
159 /* Final bits. */ | |
160 walksprite_init(&map->player_ws, map->player_sprite, 150); | |
161 } | |
162 | |
163 static void | |
164 handle_keydown(struct map *map, const union event *event) | |
165 { | |
166 switch (event->key.key) { | |
167 case KEY_UP: | |
168 map->player_movement |= MOVING_UP; | |
169 break; | |
170 case KEY_RIGHT: | |
171 map->player_movement |= MOVING_RIGHT; | |
172 break; | |
173 case KEY_DOWN: | |
174 map->player_movement |= MOVING_DOWN; | |
175 break; | |
176 case KEY_LEFT: | |
177 map->player_movement |= MOVING_LEFT; | |
178 break; | |
179 default: | |
180 break; | |
181 } | |
182 | |
183 map->player_angle = orientations[map->player_movement]; | |
184 } | |
185 | |
186 static void | |
187 handle_keyup(struct map *map, const union event *event) | |
188 { | |
189 switch (event->key.key) { | |
190 case KEY_TAB: | |
191 map->flags ^= MAP_FLAGS_SHOW_GRID | MAP_FLAGS_SHOW_COLLIDE; | |
192 break; | |
193 case KEY_UP: | |
194 map->player_movement &= ~(MOVING_UP); | |
195 break; | |
196 case KEY_RIGHT: | |
197 map->player_movement &= ~(MOVING_RIGHT); | |
198 break; | |
199 case KEY_DOWN: | |
200 map->player_movement &= ~(MOVING_DOWN); | |
201 break; | |
202 case KEY_LEFT: | |
203 map->player_movement &= ~(MOVING_LEFT); | |
204 break; | |
205 default: | |
206 break; | |
207 } | |
208 } | |
209 | |
210 static int | |
211 cmp_tile(const struct tileset_tiledef *td1, const struct tileset_tiledef *td2) | |
212 { | |
213 if (td1->id < td2->id) | |
214 return -1; | |
215 if (td1->id > td2->id) | |
216 return 1; | |
217 | |
218 return 0; | |
219 } | |
220 | |
221 static struct tileset_tiledef * | |
222 find_tiledef_by_id(const struct map *map, unsigned short id) | |
223 { | |
224 typedef int (*cmp)(const void *, const void *); | |
225 | |
226 const struct tileset_tiledef key = { | |
227 .id = id | |
228 }; | |
229 | |
230 return bsearch(&key, map->tileset->tiledefs, map->tileset->tiledefsz, | |
231 sizeof (key), (cmp)cmp_tile); | |
232 } | |
233 | |
234 static struct tileset_tiledef * | |
235 find_tiledef_by_row_column_in_layer(const struct map *map, | |
236 const struct map_layer *layer, | |
237 int row, | |
238 int col) | |
239 { | |
240 unsigned short id; | |
241 | |
242 if (row < 0 || (unsigned int)row >= map->rows || | |
243 col < 0 || (unsigned int)col >= map->columns) | |
244 return false; | |
245 | |
246 if ((id = layer->tiles[col + row * map->columns]) == 0) | |
247 return NULL; | |
248 | |
249 return find_tiledef_by_id(map, id - 1); | |
250 } | |
251 | |
252 static struct tileset_tiledef * | |
253 find_tiledef_by_row_column(const struct map *map, int row, int col) | |
254 { | |
255 struct tileset_tiledef *tile; | |
256 | |
257 /* TODO: probably a for loop when we have indefinite layers. */ | |
258 if (!(tile = find_tiledef_by_row_column_in_layer(map, &map->layers[1], row, col))) | |
259 tile = find_tiledef_by_row_column_in_layer(map, &map->layers[0], row, col); | |
260 | |
261 return tile; | |
262 } | |
263 | |
264 static void | |
265 find_block_iterate(const struct map *map, | |
266 struct collision *block, | |
267 int rowstart, | |
268 int rowend, | |
269 int colstart, | |
270 int colend, | |
271 int drow, | |
272 int dcol) | |
273 { | |
274 assert(map); | |
275 assert(block); | |
276 | |
277 for (int r = rowstart; r <= rowend; ++r) { | |
278 for (int c = colstart; c <= colend; ++c) { | |
279 struct tileset_tiledef *td; | |
280 struct collision tmp; | |
281 | |
282 if (!(td = find_tiledef_by_row_column(map, r, c))) | |
283 continue; | |
284 | |
285 /* Convert to absolute values. */ | |
286 tmp.x = td->x + c * map->tileset->sprite->cellw; | |
287 tmp.y = td->y + r * map->tileset->sprite->cellh; | |
288 tmp.w = td->w; | |
289 tmp.h = td->h; | |
290 | |
291 /* This tiledef is out of context. */ | |
292 if (!is_collision_out(map, &tmp, drow, dcol)) | |
293 continue; | |
294 | |
295 if ((drow < 0 && tmp.y + tmp.h > block->y + block->h) || | |
296 (drow > 0 && tmp.y < block->y) || | |
297 (dcol < 0 && tmp.x + tmp.w > block->x + block->w) || | |
298 (dcol > 0 && tmp.x < block->x)) { | |
299 block->x = tmp.x; | |
300 block->y = tmp.y; | |
301 block->w = tmp.w; | |
302 block->h = tmp.h; | |
303 } | |
304 } | |
305 } | |
306 } | |
307 | |
308 static void | |
309 find_collision(const struct map *map, struct collision *block, int drow, int dcolumn) | |
310 { | |
311 assert((drow && !dcolumn) || (dcolumn && !drow)); | |
312 | |
313 const int playercol = map->player_x / map->tileset->sprite->cellw; | |
314 const int playerrow = map->player_y / map->tileset->sprite->cellh; | |
315 const int ncols = (map->player_sprite->cellw / map->tileset->sprite->cellw) + 1; | |
316 const int nrows = (map->player_sprite->cellh / map->tileset->sprite->cellh) + 1; | |
317 int rowstart, rowend, colstart, colend; | |
318 | |
319 if (drow) { | |
320 colstart = playercol; | |
321 colend = playercol + ncols; | |
322 | |
323 if (drow < 0) { | |
324 /* Moving UP. */ | |
325 rowstart = 0; | |
326 rowend = playerrow; | |
327 block->x = block->y = block->h = 0; | |
328 block->w = WIDTH(map); | |
329 } else { | |
330 /* Moving DOWN. */ | |
331 rowstart = playerrow; | |
332 rowend = HEIGHT(map); | |
333 block->x = block->h = 0; | |
334 block->y = HEIGHT(map); | |
335 block->w = WIDTH(map); | |
336 } | |
337 } else { | |
338 rowstart = playerrow; | |
339 rowend = playerrow + nrows; | |
340 | |
341 if (dcolumn < 0) { | |
342 /* Moving LEFT. */ | |
343 colstart = 0; | |
344 colend = playercol; | |
345 block->x = block->y = block->w = 0; | |
346 block->h = HEIGHT(map); | |
347 } else { | |
348 /* Moving RIGHT. */ | |
349 colstart = playercol; | |
350 colend = WIDTH(map); | |
351 block->x = WIDTH(map); | |
352 block->y = block->w = 0; | |
353 block->h = block->h; | |
354 } | |
355 } | |
356 | |
357 find_block_iterate(map, block, rowstart, rowend, colstart, colend, drow, dcolumn); | |
358 } | |
359 | |
360 static void | |
361 move_x(struct map *map, int delta) | |
362 { | |
363 struct collision block; | |
364 | |
365 find_collision(map, &block, 0, delta < 0 ? -1 : +1); | |
366 | |
367 if (delta < 0 && map->player_x + delta < (int)(block.x + block.w)) | |
368 delta = map->player_x - block.x - block.w; | |
369 else if (delta > 0 && (int)(map->player_x + map->player_sprite->cellw + delta) >= block.x) | |
370 delta = block.x - map->player_x - (int)(map->player_sprite->cellw); | |
371 | |
372 map->player_x += delta; | |
373 | |
374 if ((delta < 0 && map->player_x < map->margin_x) || | |
375 (delta > 0 && map->player_x >= (int)(map->margin_x + map->margin_w))) | |
376 map->view_x += delta; | |
377 | |
378 if (map->view_x < 0) | |
379 map->view_x = 0; | |
380 else if (map->view_x >= (int)(WIDTH(map) - map->view_w)) | |
381 map->view_x = WIDTH(map) - map->view_w; | |
382 } | |
383 | |
384 static void | |
385 move_y(struct map *map, int delta) | |
386 { | |
387 struct collision block; | |
388 | |
389 find_collision(map, &block, delta < 0 ? -1 : +1, 0); | |
390 | |
391 if (delta < 0 && map->player_y + delta < (int)(block.y + block.h)) | |
392 delta = map->player_y - block.y - block.h; | |
393 else if (delta > 0 && (int)(map->player_y + map->player_sprite->cellh + delta) >= block.y) | |
394 delta = block.y - map->player_y - (int)(map->player_sprite->cellh); | |
395 | |
396 map->player_y += delta; | |
397 | |
398 if ((delta < 0 && map->player_y < map->margin_y) || | |
399 (delta > 0 && map->player_y >= (int)(map->margin_y + map->margin_h))) | |
400 map->view_y += delta; | |
401 | |
402 if (map->view_y < 0) | |
403 map->view_y = 0; | |
404 else if (map->view_y >= (int)(HEIGHT(map) - map->view_h)) | |
405 map->view_y = HEIGHT(map) - map->view_h; | |
406 } | |
407 | |
408 static void | |
409 move(struct map *map, unsigned int ticks) | |
410 { | |
411 /* This is the amount of pixels the player must move. */ | |
412 const int delta = SPEED * ticks / SEC; | |
413 | |
414 /* This is the rectangle within the view where users must be. */ | |
415 map->margin_x = map->view_x + MARGIN_WIDTH; | |
416 map->margin_y = map->view_y + MARGIN_HEIGHT; | |
417 | |
418 int dx = 0; | |
419 int dy = 0; | |
420 | |
421 if (map->player_movement == 0) | |
422 return; | |
423 | |
424 if (map->player_movement & MOVING_UP) | |
425 dy = -1; | |
426 if (map->player_movement & MOVING_DOWN) | |
427 dy = 1; | |
428 if (map->player_movement & MOVING_LEFT) | |
429 dx = -1; | |
430 if (map->player_movement & MOVING_RIGHT) | |
431 dx = 1; | |
432 | |
433 /* Move the player and adjust view if needed. */ | |
434 if (dx) | |
435 move_x(map, dx * delta); | |
436 if (dy) | |
437 move_y(map, dy * delta); | |
438 | |
439 walksprite_update(&map->player_ws, ticks); | |
440 } | |
441 | |
442 static inline void | |
443 draw_layer_tile(const struct map *map, | |
444 const struct map_layer *layer, | |
445 struct texture *colbox, | |
446 int start_col, | |
447 int start_row, | |
448 int start_x, | |
449 int start_y, | |
450 unsigned int r, | |
451 unsigned int c) | |
452 { | |
453 const struct tileset_tiledef *td; | |
454 int index, id, sc, sr, mx, my; | |
455 | |
456 index = (start_col + c) + ((start_row + r) * map->columns); | |
457 | |
458 if ((id = layer->tiles[index]) == 0) | |
459 return; | |
460 | |
461 id -= 1; | |
462 | |
463 /* Sprite row/column. */ | |
464 sc = (id) % map->tileset->sprite->ncols; | |
465 sr = (id) / map->tileset->sprite->ncols; | |
466 | |
467 /* On screen coordinates. */ | |
468 mx = start_x + (int)c * (int)map->tileset->sprite->cellw; | |
469 my = start_y + (int)r * (int)map->tileset->sprite->cellh; | |
470 | |
471 tileset_draw(map->tileset, sr, sc, mx, my); | |
472 | |
473 if ((td = find_tiledef_by_id(map, id)) && texture_ok(colbox)) | |
474 texture_scale(colbox, 0, 0, 5, 5, mx + td->x, my + td->y, td->w, td->h, 0); | |
475 | |
476 if (map->flags & MAP_FLAGS_SHOW_GRID) { | |
477 painter_set_color(0x202e37ff); | |
478 painter_draw_line(mx, my, mx + (int)map->tileset->sprite->cellw, my); | |
479 painter_draw_line( | |
480 mx + (int)map->tileset->sprite->cellw - 1, my, | |
481 mx + (int)map->tileset->sprite->cellw - 1, my + (int)map->tileset->sprite->cellh); | |
482 } | |
483 } | |
484 | |
485 static void | |
486 draw_layer(const struct map *map, const struct map_layer *layer) | |
487 { | |
488 assert(map); | |
489 assert(layer); | |
490 | |
491 /* Beginning of view in row/column. */ | |
492 const unsigned int start_col = map->view_x / map->tileset->sprite->cellw; | |
493 const unsigned int start_row = map->view_y / map->tileset->sprite->cellh; | |
494 | |
495 /* Convert into x/y coordinate. */ | |
496 const int start_x = 0 - (map->view_x % (int)map->tileset->sprite->cellw); | |
497 const int start_y = 0 - (map->view_y % (int)map->tileset->sprite->cellh); | |
498 | |
499 /* Number of row/columns to draw starting from there. */ | |
500 const unsigned int ncols = (map->view_w / map->tileset->sprite->cellw) + 2; | |
501 const unsigned int nrows = (map->view_h / map->tileset->sprite->cellh) + 2; | |
502 | |
503 struct texture colbox = {0}; | |
504 | |
505 if (!layer->tiles) | |
506 return; | |
507 | |
508 /* Show collision box if requested. */ | |
509 if (map->flags & MAP_FLAGS_SHOW_COLLIDE && texture_new(&colbox, 16, 16)) { | |
510 texture_set_blend_mode(&colbox, TEXTURE_BLEND_BLEND); | |
511 texture_set_alpha_mod(&colbox, 100); | |
512 PAINTER_BEGIN(&colbox); | |
513 painter_set_color(0xa53030ff); | |
514 painter_clear(); | |
515 PAINTER_END(); | |
516 } | |
517 | |
518 for (unsigned int r = 0; r < nrows; ++r) { | |
519 for (unsigned int c = 0; c < ncols; ++c) { | |
520 if (start_col + c >= map->columns || | |
521 start_row + r >= map->rows) | |
522 continue; | |
523 | |
524 draw_layer_tile(map, layer, &colbox, start_col, start_row, start_x, start_y, r, c); | |
525 } | |
526 } | |
527 | |
528 texture_finish(&colbox); | |
529 } | |
530 | |
531 bool | |
532 map_init(struct map *map) | |
533 { | |
534 assert(map); | |
535 | |
536 init(map); | |
537 tileset_start(map->tileset); | |
538 | |
539 return true; | |
540 } | |
541 | |
542 void | |
543 map_handle(struct map *map, const union event *ev) | |
544 { | |
545 assert(map); | |
546 assert(ev); | |
547 | |
548 switch (ev->type) { | |
549 case EVENT_KEYDOWN: | |
550 handle_keydown(map, ev); | |
551 break; | |
552 case EVENT_KEYUP: | |
553 handle_keyup(map, ev); | |
554 break; | |
555 default: | |
556 break; | |
557 } | |
558 } | |
559 | |
560 void | |
561 map_update(struct map *map, unsigned int ticks) | |
562 { | |
563 assert(map); | |
564 | |
565 action_stack_update(&map->actions, ticks); | |
566 | |
567 tileset_update(map->tileset, ticks); | |
568 move(map, ticks); | |
569 } | |
570 | |
571 void | |
572 map_draw(const struct map *map) | |
573 { | |
574 assert(map); | |
575 | |
576 struct texture box = {0}; | |
577 | |
578 /* Draw the texture about background/foreground. */ | |
579 draw_layer(map, &map->layers[MAP_LAYER_TYPE_BACKGROUND]); | |
580 draw_layer(map, &map->layers[MAP_LAYER_TYPE_FOREGROUND]); | |
581 | |
582 walksprite_draw( | |
583 &map->player_ws, | |
584 map->player_angle, | |
585 map->player_x - map->view_x, | |
586 map->player_y - map->view_y); | |
587 | |
588 draw_layer(map, &map->layers[MAP_LAYER_TYPE_ABOVE]); | |
589 | |
590 action_stack_draw(&map->actions); | |
591 | |
592 /* Draw collide box around player if requested. */ | |
593 if (map->flags & MAP_FLAGS_SHOW_COLLIDE && | |
594 texture_new(&box, map->player_sprite->cellw, map->player_sprite->cellh)) { | |
595 texture_set_alpha_mod(&box, 100); | |
596 texture_set_blend_mode(&box, TEXTURE_BLEND_BLEND); | |
597 PAINTER_BEGIN(&box); | |
598 painter_set_color(0x4f8fbaff); | |
599 painter_clear(); | |
600 PAINTER_END(); | |
601 texture_draw(&box, map->player_x - map->view_x, map->player_y - map->view_y); | |
602 texture_finish(&box); | |
603 } | |
604 } | |
605 | |
606 void | |
607 map_finish(struct map *map) | |
608 { | |
609 assert(map); | |
610 | |
611 action_stack_finish(&map->actions); | |
612 | |
613 memset(map, 0, sizeof (*map)); | |
614 } |