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