comparison src/libmlk-rpg/rpg/map.c @ 320:8f9937403749

misc: improve loading of data
author David Demelier <markand@malikania.fr>
date Fri, 01 Oct 2021 20:30:00 +0200
parents libmlk-rpg/rpg/map.c@d01e83210ca2
children 460c78706989
comparison
equal deleted inserted replaced
319:b843eef4cc35 320:8f9937403749
1 /*
2 * map.c -- game map
3 *
4 * Copyright (c) 2020-2021 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 100
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 160
53 #define MARGIN_HEIGHT 90
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 /*
96 * Check if this block is usable for collision detection. For example if the
97 * player is moving upwards but the collision shape is below it is unnecessary
98 * to check.
99 */
100 static int
101 is_block_relevant(const struct map *map,
102 const struct map_block *block,
103 int drow,
104 int dcol)
105 {
106 if (drow) {
107 /* Object outside of left-right bounds. */
108 if (block->x + (int)block->w <= map->player_x ||
109 block->x >= map->player_x + (int)map->player_sprite->cellw)
110 return 0;
111
112 if ((drow < 0 && block->y >= map->player_y + (int)map->player_sprite->cellh) ||
113 (drow > 0 && block->y + block->h <= map->player_y + map->player_sprite->cellh))
114 return 0;
115 } else if (dcol) {
116 /* Object outside of up-down bounds. */
117 if (block->y + (int)block->h <= map->player_y ||
118 block->y >= map->player_y + (int)map->player_sprite->cellh)
119 return 0;
120
121 if ((dcol < 0 && block->x >= map->player_x + (int)map->player_sprite->cellw) ||
122 (dcol > 0 && block->x + block->w <= map->player_x + map->player_sprite->cellw))
123 return 0;
124 }
125
126 return 1;
127 }
128
129 /*
130 * Determine if this collision shape is "closer" to the player by checking the
131 * new block coordinates with the previous one.
132 */
133 static int
134 is_block_better(const struct map_block *now,
135 const struct map_block *new,
136 int drow,
137 int dcol)
138 {
139 return ((drow < 0 && new->y + new->h > now->y + now->h) ||
140 (drow > 0 && new->y < now->y) ||
141 (dcol < 0 && new->x + new->w > now->x + now->w) ||
142 (dcol > 0 && new->x < now->x));
143
144 }
145
146 static void
147 center(struct map *map)
148 {
149 map->view_x = map->player_x - (int)(map->view_w / 2);
150 map->view_y = map->player_y - (int)(map->view_h / 2);
151
152 if (map->view_x < 0)
153 map->view_x = 0;
154 else if ((unsigned int)map->view_x > WIDTH(map) - map->view_w)
155 map->view_x = WIDTH(map) - map->view_w;
156
157 if (map->view_y < 0)
158 map->view_y = 0;
159 else if ((unsigned int)map->view_y > HEIGHT(map) - map->view_h)
160 map->view_y = HEIGHT(map) - map->view_h;
161 }
162
163 static void
164 init(struct map *map)
165 {
166 /* Adjust view. */
167 map->view_w = window.w;
168 map->view_h = window.h;
169
170 /* Adjust margin. */
171 map->margin_w = map->view_w - (MARGIN_WIDTH * 2) - map->player_sprite->cellw;
172 map->margin_h = map->view_h - (MARGIN_HEIGHT * 2) - map->player_sprite->cellh;
173
174 /* Center the view by default. */
175 center(map);
176
177 /* Final bits. */
178 walksprite_init(&map->player_ws, map->player_sprite, 150);
179 }
180
181 static void
182 handle_keydown(struct map *map, const union event *event)
183 {
184 switch (event->key.key) {
185 case KEY_UP:
186 map->player_movement |= MOVING_UP;
187 break;
188 case KEY_RIGHT:
189 map->player_movement |= MOVING_RIGHT;
190 break;
191 case KEY_DOWN:
192 map->player_movement |= MOVING_DOWN;
193 break;
194 case KEY_LEFT:
195 map->player_movement |= MOVING_LEFT;
196 break;
197 default:
198 break;
199 }
200
201 map->player_angle = orientations[map->player_movement];
202 }
203
204 static void
205 handle_keyup(struct map *map, const union event *event)
206 {
207 switch (event->key.key) {
208 case KEY_TAB:
209 map->flags ^= MAP_FLAGS_SHOW_GRID | MAP_FLAGS_SHOW_COLLIDE;
210 break;
211 case KEY_UP:
212 map->player_movement &= ~(MOVING_UP);
213 break;
214 case KEY_RIGHT:
215 map->player_movement &= ~(MOVING_RIGHT);
216 break;
217 case KEY_DOWN:
218 map->player_movement &= ~(MOVING_DOWN);
219 break;
220 case KEY_LEFT:
221 map->player_movement &= ~(MOVING_LEFT);
222 break;
223 default:
224 break;
225 }
226 }
227
228 static int
229 cmp_tile(const struct tileset_tiledef *td1, const struct tileset_tiledef *td2)
230 {
231 if (td1->id < td2->id)
232 return -1;
233 if (td1->id > td2->id)
234 return 1;
235
236 return 0;
237 }
238
239 static struct tileset_tiledef *
240 find_tiledef_by_id(const struct map *map, unsigned short id)
241 {
242 typedef int (*cmp)(const void *, const void *);
243
244 const struct tileset_tiledef key = {
245 .id = id
246 };
247
248 return bsearch(&key, map->tileset->tiledefs, map->tileset->tiledefsz,
249 sizeof (key), (cmp)cmp_tile);
250 }
251
252 static struct tileset_tiledef *
253 find_tiledef_by_row_column_in_layer(const struct map *map,
254 const struct map_layer *layer,
255 int row,
256 int col)
257 {
258 unsigned short id;
259
260 if (row < 0 || (unsigned int)row >= map->rows ||
261 col < 0 || (unsigned int)col >= map->columns)
262 return 0;
263
264 if ((id = layer->tiles[col + row * map->columns]) == 0)
265 return NULL;
266
267 return find_tiledef_by_id(map, id - 1);
268 }
269
270 static struct tileset_tiledef *
271 find_tiledef_by_row_column(const struct map *map, int row, int col)
272 {
273 struct tileset_tiledef *tile;
274
275 /* TODO: probably a for loop when we have indefinite layers. */
276 if (!(tile = find_tiledef_by_row_column_in_layer(map, &map->layers[1], row, col)))
277 tile = find_tiledef_by_row_column_in_layer(map, &map->layers[0], row, col);
278
279 return tile;
280 }
281
282 static void
283 find_block_iterate(const struct map *map,
284 struct map_block *block,
285 int rowstart,
286 int rowend,
287 int colstart,
288 int colend,
289 int drow,
290 int dcol)
291 {
292 assert(map);
293 assert(block);
294
295 /* First, check with tiledefs. */
296 for (int r = rowstart; r <= rowend; ++r) {
297 for (int c = colstart; c <= colend; ++c) {
298 struct tileset_tiledef *td;
299 struct map_block tmp;
300
301 if (!(td = find_tiledef_by_row_column(map, r, c)))
302 continue;
303
304 /* Convert to absolute values. */
305 tmp.x = td->x + c * map->tileset->sprite->cellw;
306 tmp.y = td->y + r * map->tileset->sprite->cellh;
307 tmp.w = td->w;
308 tmp.h = td->h;
309
310 /* This tiledef is out of context. */
311 if (!is_block_relevant(map, &tmp, drow, dcol))
312 continue;
313
314 if (is_block_better(block, &tmp, drow, dcol)) {
315 block->x = tmp.x;
316 block->y = tmp.y;
317 block->w = tmp.w;
318 block->h = tmp.h;
319 }
320 }
321 }
322
323 /* Now check if there are objects closer than tiledefs. */
324 for (size_t i = 0; i < map->blocksz; ++i) {
325 const struct map_block *new = &map->blocks[i];
326
327 if (is_block_relevant(map, new, drow, dcol) &&
328 is_block_better(block, new, drow, dcol)) {
329 block->x = new->x;
330 block->y = new->y;
331 block->w = new->w;
332 block->h = new->h;
333 }
334 }
335 }
336
337 static void
338 find_collision(const struct map *map, struct map_block *block, int drow, int dcolumn)
339 {
340 assert((drow && !dcolumn) || (dcolumn && !drow));
341
342 const int playercol = map->player_x / map->tileset->sprite->cellw;
343 const int playerrow = map->player_y / map->tileset->sprite->cellh;
344 const int ncols = (map->player_sprite->cellw / map->tileset->sprite->cellw) + 1;
345 const int nrows = (map->player_sprite->cellh / map->tileset->sprite->cellh) + 1;
346 int rowstart, rowend, colstart, colend;
347
348 if (drow) {
349 colstart = playercol;
350 colend = playercol + ncols;
351
352 if (drow < 0) {
353 /* Moving UP. */
354 rowstart = 0;
355 rowend = playerrow;
356 block->x = block->y = block->h = 0;
357 block->w = WIDTH(map);
358 } else {
359 /* Moving DOWN. */
360 rowstart = playerrow;
361 rowend = HEIGHT(map);
362 block->x = block->h = 0;
363 block->y = HEIGHT(map);
364 block->w = WIDTH(map);
365 }
366 } else {
367 rowstart = playerrow;
368 rowend = playerrow + nrows;
369
370 if (dcolumn < 0) {
371 /* Moving LEFT. */
372 colstart = 0;
373 colend = playercol;
374 block->x = block->y = block->w = 0;
375 block->h = HEIGHT(map);
376 } else {
377 /* Moving RIGHT. */
378 colstart = playercol;
379 colend = WIDTH(map);
380 block->x = WIDTH(map);
381 block->y = block->w = 0;
382 block->h = block->h;
383 }
384 }
385
386 find_block_iterate(map, block, rowstart, rowend, colstart, colend, drow, dcolumn);
387 }
388
389 static void
390 move_x(struct map *map, int delta)
391 {
392 struct map_block block;
393
394 find_collision(map, &block, 0, delta < 0 ? -1 : +1);
395
396 if (delta < 0 && map->player_x + delta < (int)(block.x + block.w))
397 delta = map->player_x - block.x - block.w;
398 else if (delta > 0 && (int)(map->player_x + map->player_sprite->cellw + delta) >= block.x)
399 delta = block.x - map->player_x - (int)(map->player_sprite->cellw);
400
401 map->player_x += delta;
402
403 if ((delta < 0 && map->player_x < map->margin_x) ||
404 (delta > 0 && map->player_x >= (int)(map->margin_x + map->margin_w)))
405 map->view_x += delta;
406
407 if (map->view_x < 0)
408 map->view_x = 0;
409 else if (map->view_x >= (int)(WIDTH(map) - map->view_w))
410 map->view_x = WIDTH(map) - map->view_w;
411 }
412
413 static void
414 move_y(struct map *map, int delta)
415 {
416 struct map_block block;
417
418 find_collision(map, &block, delta < 0 ? -1 : +1, 0);
419
420 if (delta < 0 && map->player_y + delta < (int)(block.y + block.h))
421 delta = map->player_y - block.y - block.h;
422 else if (delta > 0 && (int)(map->player_y + map->player_sprite->cellh + delta) >= block.y)
423 delta = block.y - map->player_y - (int)(map->player_sprite->cellh);
424
425 map->player_y += delta;
426
427 if ((delta < 0 && map->player_y < map->margin_y) ||
428 (delta > 0 && map->player_y >= (int)(map->margin_y + map->margin_h)))
429 map->view_y += delta;
430
431 if (map->view_y < 0)
432 map->view_y = 0;
433 else if (map->view_y >= (int)(HEIGHT(map) - map->view_h))
434 map->view_y = HEIGHT(map) - map->view_h;
435 }
436
437 static void
438 move(struct map *map, unsigned int ticks)
439 {
440 /* This is the amount of pixels the player must move. */
441 const int delta = SPEED * ticks / SEC;
442
443 /* This is the rectangle within the view where users must be. */
444 map->margin_x = map->view_x + MARGIN_WIDTH;
445 map->margin_y = map->view_y + MARGIN_HEIGHT;
446
447 int dx = 0;
448 int dy = 0;
449
450 if (map->player_movement == 0)
451 return;
452
453 if (map->player_movement & MOVING_UP)
454 dy = -1;
455 if (map->player_movement & MOVING_DOWN)
456 dy = 1;
457 if (map->player_movement & MOVING_LEFT)
458 dx = -1;
459 if (map->player_movement & MOVING_RIGHT)
460 dx = 1;
461
462 /* Move the player and adjust view if needed. */
463 if (dx)
464 move_x(map, dx * delta);
465 if (dy)
466 move_y(map, dy * delta);
467
468 walksprite_update(&map->player_ws, ticks);
469 }
470
471 static inline void
472 draw_layer_tile(const struct map *map,
473 const struct map_layer *layer,
474 struct texture *colbox,
475 int start_col,
476 int start_row,
477 int start_x,
478 int start_y,
479 unsigned int r,
480 unsigned int c)
481 {
482 const struct tileset_tiledef *td;
483 int index, id, sc, sr, mx, my;
484
485 index = (start_col + c) + ((start_row + r) * map->columns);
486
487 if ((id = layer->tiles[index]) == 0)
488 return;
489
490 id -= 1;
491
492 /* Sprite row/column. */
493 sc = (id) % map->tileset->sprite->ncols;
494 sr = (id) / map->tileset->sprite->ncols;
495
496 /* On screen coordinates. */
497 mx = start_x + (int)c * (int)map->tileset->sprite->cellw;
498 my = start_y + (int)r * (int)map->tileset->sprite->cellh;
499
500 tileset_draw(map->tileset, sr, sc, mx, my);
501
502 if ((td = find_tiledef_by_id(map, id)) && texture_ok(colbox))
503 texture_scale(colbox, 0, 0, 5, 5, mx + td->x, my + td->y, td->w, td->h, 0);
504
505 if (map->flags & MAP_FLAGS_SHOW_GRID) {
506 painter_set_color(0x202e37ff);
507 painter_draw_line(mx, my, mx + (int)map->tileset->sprite->cellw, my);
508 painter_draw_line(
509 mx + (int)map->tileset->sprite->cellw - 1, my,
510 mx + (int)map->tileset->sprite->cellw - 1, my + (int)map->tileset->sprite->cellh);
511 }
512 }
513
514 static void
515 draw_layer(const struct map *map, const struct map_layer *layer)
516 {
517 assert(map);
518 assert(layer);
519
520 /* Beginning of view in row/column. */
521 const unsigned int start_col = map->view_x / map->tileset->sprite->cellw;
522 const unsigned int start_row = map->view_y / map->tileset->sprite->cellh;
523
524 /* Convert into x/y coordinate. */
525 const int start_x = 0 - (map->view_x % (int)map->tileset->sprite->cellw);
526 const int start_y = 0 - (map->view_y % (int)map->tileset->sprite->cellh);
527
528 /* Number of row/columns to draw starting from there. */
529 const unsigned int ncols = (map->view_w / map->tileset->sprite->cellw) + 2;
530 const unsigned int nrows = (map->view_h / map->tileset->sprite->cellh) + 2;
531
532 struct texture colbox = {0};
533
534 if (!layer->tiles)
535 return;
536
537 /* Show collision box if requested. */
538 if (map->flags & MAP_FLAGS_SHOW_COLLIDE && texture_new(&colbox, 16, 16) == 0) {
539 texture_set_blend_mode(&colbox, TEXTURE_BLEND_BLEND);
540 texture_set_alpha_mod(&colbox, 100);
541 PAINTER_BEGIN(&colbox);
542 painter_set_color(0xa53030ff);
543 painter_clear();
544 PAINTER_END();
545 }
546
547 for (unsigned int r = 0; r < nrows; ++r) {
548 for (unsigned int c = 0; c < ncols; ++c) {
549 if (start_col + c >= map->columns ||
550 start_row + r >= map->rows)
551 continue;
552
553 draw_layer_tile(map, layer, &colbox, start_col, start_row, start_x, start_y, r, c);
554 }
555 }
556
557 texture_finish(&colbox);
558 }
559
560 static void
561 draw_collide(const struct map *map)
562 {
563 struct texture box = {0};
564
565 if (map->flags & MAP_FLAGS_SHOW_COLLIDE && texture_new(&box, 64, 64) == 0) {
566 /* Draw collide box around player if requested. */
567 texture_set_alpha_mod(&box, 100);
568 texture_set_blend_mode(&box, TEXTURE_BLEND_BLEND);
569 PAINTER_BEGIN(&box);
570 painter_set_color(0x4f8fbaff);
571 painter_clear();
572 PAINTER_END();
573 texture_scale(&box, 0, 0, 64, 64,
574 map->player_x - map->view_x, map->player_y - map->view_y,
575 map->player_sprite->cellw, map->player_sprite->cellh, 0.f);
576
577 /* Do the same for every objects. */
578 PAINTER_BEGIN(&box);
579 painter_set_color(0xa8ca58ff);
580 painter_clear();
581 PAINTER_END();
582
583 for (size_t i = 0; i < map->blocksz; ++i) {
584 texture_scale(&box, 0, 0, 64, 64,
585 map->blocks[i].x - map->view_x, map->blocks[i].y - map->view_y,
586 map->blocks[i].w, map->blocks[i].h,
587 0.f);
588 }
589
590 texture_finish(&box);
591 }
592 }
593
594 int
595 map_init(struct map *map)
596 {
597 assert(map);
598
599 init(map);
600 tileset_start(map->tileset);
601
602 return 0;
603 }
604
605 void
606 map_handle(struct map *map, const union event *ev)
607 {
608 assert(map);
609 assert(ev);
610
611 switch (ev->type) {
612 case EVENT_KEYDOWN:
613 handle_keydown(map, ev);
614 break;
615 case EVENT_KEYUP:
616 handle_keyup(map, ev);
617 break;
618 default:
619 break;
620 }
621
622 action_stack_handle(&map->astack_par, ev);
623 action_stack_handle(&map->astack_seq, ev);
624 }
625
626 void
627 map_update(struct map *map, unsigned int ticks)
628 {
629 assert(map);
630
631 action_stack_update(&map->astack_par, ticks);
632 action_stack_update(&map->astack_seq, ticks);
633
634 tileset_update(map->tileset, ticks);
635
636 /* No movements if the sequential actions are running. */
637 if (action_stack_completed(&map->astack_seq))
638 move(map, ticks);
639 }
640
641 void
642 map_draw(const struct map *map)
643 {
644 assert(map);
645
646 /* Draw the texture about background/foreground. */
647 draw_layer(map, &map->layers[MAP_LAYER_TYPE_BACKGROUND]);
648 draw_layer(map, &map->layers[MAP_LAYER_TYPE_FOREGROUND]);
649
650 walksprite_draw(
651 &map->player_ws,
652 map->player_angle,
653 map->player_x - map->view_x,
654 map->player_y - map->view_y);
655
656 draw_layer(map, &map->layers[MAP_LAYER_TYPE_ABOVE]);
657 draw_collide(map);
658
659 action_stack_draw(&map->astack_par);
660 action_stack_draw(&map->astack_seq);
661 }
662
663 void
664 map_finish(struct map *map)
665 {
666 assert(map);
667
668 action_stack_finish(&map->astack_par);
669 action_stack_finish(&map->astack_seq);
670
671 memset(map, 0, sizeof (*map));
672 }