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