]> git.lizzy.rs Git - dungeon_game.git/blob - plugins/game/game.c
Only call on_death callback if entity was alive before
[dungeon_game.git] / plugins / game / game.c
1 #include <stdio.h>
2 #include <stdbool.h>
3 #include <stdlib.h>
4 #include <stddef.h>
5 #include <unistd.h>
6 #include <assert.h>
7 #include <ctype.h>
8 #include <time.h>
9 #include <signal.h>
10 #include <termios.h>
11 #include <sys/ioctl.h>
12 #include <math.h>
13 #include <pthread.h>
14 #include "game.h"
15
16 bool running = true;
17
18 int score = 0;
19
20 struct color black = {0, 0, 0};
21
22 struct material wall;
23 struct material air;
24 struct material outside;
25
26 struct node map[MAP_WIDTH][MAP_HEIGHT];
27
28 struct entity player;
29 struct list *entities = & (struct list) {
30         .element = &player,
31         .next = NULL,
32 };
33
34 struct entity *entity_collision_map[MAP_WIDTH][MAP_HEIGHT] = {{NULL}};
35
36 struct list *air_functions = NULL;
37
38 void quit()
39 {
40         running = false;
41 }
42
43 struct color get_color(const char *str)
44 {
45         unsigned int r, g, b;
46         sscanf(str, "#%2x%2x%2x", &r, &g, &b);
47         return (struct color) {r, g, b};
48 }
49
50 bool is_outside(int x, int y)
51 {
52         return x >= MAP_WIDTH || x < 0 || y >= MAP_HEIGHT || y < 0;
53 }
54
55 struct node get_node(int x, int y)
56 {
57         return is_outside(x, y) ? (struct node) {&outside} : map[x][y];
58 }
59
60 bool is_solid(int x, int y)
61 {
62         return get_node(x, y).material->solid;
63 }
64
65 bool move(struct entity *entity, int xoff, int yoff)
66 {
67         int x, y;
68
69         x = entity->x + xoff;
70         y = entity->y + yoff;
71
72         if (is_solid(x, y)) {
73                 if (entity->on_collide)
74                         entity->on_collide(entity, x, y);
75                 return false;
76         } else if (entity->collide_with_entities && entity_collision_map[x][y]) {
77                 if (entity->on_collide_with_entity)
78                         entity->on_collide_with_entity(entity, entity_collision_map[x][y]);
79                 return false;
80         } else {
81                 entity_collision_map[entity->x][entity->y] = NULL;
82                 entity->x = x;
83                 entity->y = y;
84                 entity_collision_map[entity->x][entity->y] = entity;
85                 return true;
86         }
87 }
88
89 void spawn(struct entity def, int x, int y)
90 {
91         if (is_outside(x, y))
92                 return;
93
94         if (def.collide_with_entities && entity_collision_map[x][y])
95                 return;
96
97         def.x = x;
98         def.y = y;
99
100         struct entity *entity = malloc(sizeof(struct entity));
101         *entity = def;
102
103         add_element(entities, entity);
104
105         if (entity->collide_with_entities)
106                 entity_collision_map[x][y] = entity;
107
108         if (entity->on_spawn)
109                 entity->on_spawn(entity);
110 }
111
112 void add_health(struct entity *entity, int health)
113 {
114         bool was_alive = entity->health > 0;
115
116         entity->health += health;
117
118         if (entity->health > entity->max_health)
119                 entity->health = entity->max_health;
120         else if (entity->health <= 0 && was_alive && entity->on_death)
121                 entity->on_death(entity);
122 }
123
124 void add_score(int s)
125 {
126         score += s;
127 }
128
129 struct list *add_element(struct list *list, void *element)
130 {
131         struct list **ptr;
132
133         for (ptr = &list; *ptr != NULL; ptr = &(*ptr)->next)
134                 ;
135
136         *ptr = malloc(sizeof(struct list));
137         (*ptr)->element = element;
138         (*ptr)->next = NULL;
139
140         return list;
141 }
142
143 void register_air_function(struct generator_function func)
144 {
145         struct generator_function *buf = malloc(sizeof(struct generator_function));
146         *buf = func;
147
148         air_functions = add_element(air_functions, buf);
149 }
150
151 /* Player */
152
153 static void player_death(struct entity *self)
154 {
155         (void) self;
156         quit();
157 }
158
159 /* Mapgen */
160
161 static bool check_direction(int x, int y, int dir)
162 {
163         if (dir % 2 == 0)
164                 return is_solid(x, y + 1) && is_solid(x, y - 1) && (is_solid(x + 1, y) || rand() % 3 > 1) && (is_solid(x - 1, y) || rand() % 3 > 1);
165         else
166                 return is_solid(x + 1, y) && is_solid(x - 1, y) && (is_solid(x, y + 1) || rand() % 3 > 1) && (is_solid(x, y - 1) || rand() % 3 > 1);
167 }
168
169 static void generate_corridor(int lx, int ly, int ldir, bool off)
170 {
171         if (is_outside(lx, ly))
172                 return;
173
174         /*
175         if (off && rand() % 100 == 0)
176                 return;
177         */
178
179         map[lx][ly] = (struct node) {&air};
180
181         for (struct list *ptr = air_functions; ptr != NULL; ptr = ptr->next) {
182                 struct generator_function *func = ptr->element;
183
184                 if (! func->chance || rand() % func->chance == 0) {
185                         func->callback(lx, ly);
186                 }
187         }
188
189         int x, y, dir;
190         int ret = (ldir + 2) % 4;
191         int limit = 50;
192
193         do {
194                 x = lx;
195                 y = ly;
196
197                 if (rand() % 3 > 1)
198                         dir = ldir;
199                 else
200                         dir = rand() % 4;
201
202                 switch (dir) {
203                         case 0:
204                                 x++;
205                                 break;
206                         case 1:
207                                 y++;
208                                 break;
209                         case 2:
210                                 x--;
211                                 break;
212                         case 3:
213                                 y--;
214                                 break;
215                 }
216
217         } while (dir == ret || (! check_direction(x, y, dir) && --limit));
218
219         if (limit)
220                 generate_corridor(x, y, dir, off);
221
222         if (rand() % 20 == 0)
223                 generate_corridor(lx, ly, ldir, true);
224 }
225
226 static void generate_corridor_random(int x, int y)
227 {
228         int dir = rand() % 4;
229
230         generate_corridor(x, y, dir, false);
231         generate_corridor(x, y, (dir + 2) % 4, false);
232 }
233
234 /* Rendering */
235
236 void set_color(struct color color, bool bg)
237 {
238         printf("\e[%u;2;%u;%u;%um", bg ? 48 : 38, color.r, color.g, color.b);
239 }
240
241 struct color light_color(struct color color, double light)
242 {
243         return (struct color) {
244                 color.r * light,
245                 color.g * light,
246                 color.b * light,
247         };
248 }
249
250 static void render(render_entity_list entity_list)
251 {
252         printf("\e[2J\e[0;0H");
253
254         struct winsize ws;
255         ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
256
257         int cols = ws.ws_col / 2 - LIGHT * 2;
258         int rows = ws.ws_row / 2 - LIGHT;
259
260         int cols_left = ws.ws_col - cols - (LIGHT * 2 + 1) * 2;
261         int rows_left = ws.ws_row - rows - (LIGHT * 2 + 1);
262
263         set_color(black, true);
264
265         for (int i = 0; i < rows; i++)
266                 for (int i = 0; i < ws.ws_col; i++)
267                         printf(" ");
268
269         for (int y = -LIGHT; y <= LIGHT; y++) {
270                 for (int i = 0; i < cols; i++)
271                         printf(" ");
272
273                 for (int x = -LIGHT; x <= LIGHT; x++) {
274                         int map_x, map_y;
275
276                         map_x = x + player.x;
277                         map_y = y + player.y;
278
279                         struct node node = get_node(map_x, map_y);
280
281                         double dist = sqrt(x * x + y * y);
282                         double light = 1.0 - (double) dist / (double) LIGHT;
283
284                         if (light <= 0)
285                                 goto empty;
286
287                         set_color(light_color(node.material->color, light), true);
288
289                         struct entity *entity = entity_list[x + LIGHT][y + LIGHT];
290
291                         if (entity) {
292                                 set_color(entity->color, false);
293                                 printf("%s", entity->texture);
294                         } else {
295                                 empty:
296                                 printf("  ");
297                         }
298                 }
299
300                 set_color(black, true);
301
302                 for (int i = 0; i < cols_left; i++)
303                         printf(" ");
304         }
305
306         for (int i = 0; i < rows_left + 1; i++)
307                 for (int i = 0; i < ws.ws_col; i++)
308                         printf(" ");
309
310         printf("\e[0;0H\e[39m");
311
312         printf("\e[32m\e[3mScore:\e[23m %d\e[39m", score);
313
314         printf("\e[0;0");
315
316         for (int i = 0; i < rows; i++)
317                 printf("\n");
318
319         printf("\t\e[1mInventory\e[22m\n\n");
320         printf("\t0x\t\e[3mNothing\e[23m\n");
321
322         printf("\e[0;0H");
323
324         for (int i = 0; i < ws.ws_row - 2; i++)
325                 printf("\n");
326
327         int hearts_cols = ws.ws_col / 2 - player.max_health;
328
329         for (int i = 0; i < hearts_cols; i++)
330                 printf(" ");
331
332         set_color((struct color) {255, 0, 0}, false);
333
334         for (int i = 0; i < player.max_health; i++) {
335                 if (i == player.health)
336                         set_color(get_color("#5A5A5A"), false);
337                 printf("\u2665 ");
338         }
339
340         printf("\e[39m\n");
341 }
342
343 /* Input */
344
345 static void handle_input(char c)
346 {
347         switch (c) {
348                 case 'q':
349                         quit();
350                         break;
351                 case 'w':
352                         move(&player, 0, -1);
353                         break;
354                 case 'a':
355                         move(&player, -1, 0);
356                         break;
357                 case 's':
358                         move(&player, 0, 1);
359                         break;
360                 case 'd':
361                         move(&player, 1, 0);
362                         break;
363         }
364 }
365
366 /* Multithreading */
367
368 static void handle_interrupt(int signal)
369 {
370         (void) signal;
371
372         running = false;
373 }
374
375 static void *input_thread(void *unused)
376 {
377         (void) unused;
378
379         while (running)
380                 handle_input(tolower(fgetc(stdin)));
381
382         return NULL;
383 }
384
385 /* Main Game */
386
387 __attribute__ ((constructor)) static void init()
388 {
389         wall = (struct material) {
390                 .solid = true,
391                 .color = get_color("#5B2F00"),
392         };
393
394         air = (struct material) {
395                 .solid = false,
396                 .color = get_color("#FFE027"),
397         };
398
399         outside = (struct material) {
400                 .solid = true,
401                 .color = black,
402         };
403
404         player = (struct entity) {
405                 .name = "player",
406                 .x = MAP_WIDTH / 2,
407                 .y = MAP_HEIGHT / 2,
408                 .color = get_color("#00FFFF"),
409                 .texture = "🙂",
410                 .remove = false,
411                 .meta = NULL,
412                 .health = 10,
413                 .max_health = 10,
414                 .collide_with_entities = true,
415
416                 .on_step = NULL,
417                 .on_collide = NULL,
418                 .on_collide_with_entity = NULL,
419                 .on_spawn = NULL,
420                 .on_remove = NULL,
421                 .on_death = &player_death,
422         };
423
424         entity_collision_map[player.x][player.y] = &player;
425
426         for (int x = 0; x < MAP_WIDTH; x++)
427                 for (int y = 0; y < MAP_HEIGHT; y++)
428                         map[x][y] = (struct node) {&wall};
429 }
430
431 void game()
432 {
433         srand(time(0));
434
435         struct sigaction sa;
436         sa.sa_handler = &handle_interrupt;
437         sigaction(SIGINT, &sa, NULL);
438
439         generate_corridor_random(player.x, player.y);
440
441         for (int i = 0; i < 50; i++)
442                 generate_corridor_random(rand() % MAP_WIDTH, rand() % MAP_HEIGHT);
443
444         struct termios oldtio, newtio;
445         tcgetattr(STDIN_FILENO, &oldtio);
446         newtio = oldtio;
447         newtio.c_lflag &= ~(ICANON | ECHO);
448         tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
449
450         printf("\e[?1049h\e[?25l");
451
452         pthread_t input_thread_id;
453         pthread_create(&input_thread_id, NULL, &input_thread, NULL);
454
455         struct timespec ts, ts_old;
456         clock_gettime(CLOCK_REALTIME, &ts_old);
457
458         while (running) {
459                 clock_gettime(CLOCK_REALTIME, &ts);
460                 double dtime = (double) (ts.tv_sec - ts_old.tv_sec) + (double) (ts.tv_nsec - ts_old.tv_nsec) / 1000000000.0;
461                 ts_old = ts;
462
463                 render_entity_list render_list = {{NULL}};
464
465                 for (struct list **ptr = &entities; *ptr != NULL; ) {
466                         struct entity *entity = (*ptr)->element;
467
468                         if (entity->remove) {
469                                 assert(entity != &player);
470                                 struct list *next = (*ptr)->next;
471
472                                 if (entity->on_remove)
473                                         entity->on_remove(entity);
474
475                                 if (entity->meta)
476                                         free(entity->meta);
477
478                                 free(entity);
479                                 free(*ptr);
480
481                                 *ptr = next;
482                                 continue;
483                         }
484
485                         int dx, dy;
486
487                         dx = entity->x - player.x;
488                         dy = entity->y - player.y;
489
490                         bool visible = abs(dx) <= LIGHT && abs(dy) <= LIGHT;
491
492                         if (visible)
493                                 render_list[dx + LIGHT][dy + LIGHT] = entity;
494
495                         if (entity->on_step)
496                                 entity->on_step(entity, (struct entity_step_data) {
497                                         .dtime = dtime,
498                                         .visible = visible,
499                                         .dx = dx,
500                                         .dy = dy,
501                                 });
502
503                         ptr = &(*ptr)->next;
504                 }
505
506                 render(render_list);
507
508                 // there is no such thing as glfwSwapBuffers, so we just wait 1 / 60 seconds to prevent artifacts
509                 usleep(1000000 / 60);
510         }
511
512         printf("\e[?1049l\e[?25h");
513         tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
514 }
515
516 /* Use later */
517
518 /*
519 get_box_char(is_solid(x, y - 1), is_solid(x, y + 1), is_solid(x - 1, y), is_solid(x + 1, y));
520
521 const char *get_box_char(bool up, bool down, bool left, bool right)
522 {
523         if (left && right && ! up && ! down)
524                 return "\u2501\u2501";
525         else if (up && down && ! right && ! left)
526                 return "\u2503 ";
527         else if (down && right && ! up && ! left)
528                 return "\u250F\u2501";
529         else if (down && left && ! up && ! right)
530                 return "\u2513 ";
531         else if (up && right && ! down && ! left)
532                 return "\u2517\u2501";
533         else if (up && left && ! down && ! right)
534                 return "\u251B ";
535         else if (up && down && right && ! left)
536                 return "\u2523\u2501";
537         else if (up && down && left && ! right)
538                 return "\u252B ";
539         else if (down && left && right && ! up)
540                 return "\u2533\u2501";
541         else if (up && left && right && ! down)
542                 return "\u253B\u2501";
543         else if (up && down && left && right)
544                 return "\u254b\u2501";
545         else if (left && ! up && ! down && ! right)
546                 return "\u2578 ";
547         else if (up && ! down && ! left && ! right)
548                 return "\u2579 ";
549         else if (right && ! up && ! down && ! left)
550                 return "\u257A\u2501";
551         else if (down && ! up && ! left && ! right)
552                 return "\u257B ";
553         else if (! up && ! down && ! left && ! right)
554                 return "\u25AA ";
555         else
556                 return "??";
557 }
558 */