home · contact · privacy
Server: Add ENEMY_FOV option (default: off) to force FOV on enemies.
[plomrogue] / src / server / field_of_view.c
1 /* src/server/field_of_view.c */
2
3 #define _POSIX_C_SOURCE 200809L /* strdup() */
4 #include "field_of_view.h"
5 #include <stdlib.h> /* free() */
6 #include <stdint.h> /* uint8_t, uint16_t, uint32_t */
7 #include <string.h> /* memset(), strchr(), strdup() */
8 #include "../common/rexit.h" /* exit_trouble() */
9 #include "../common/try_malloc.h" /* try_malloc() */
10 #include "map.h" /* yx_to_map_pos() */
11 #include "map_objects.h" /* MapObj */
12 #include "yx_uint8.h" /* yx_uint8 */
13 #include "world.h" /* global world  */
14
15
16
17 /* Values for mv_yx_in_dir_wrap()'s wrapping directory memory. */
18 enum wraps
19 {
20     WRAP_N = 0x01,
21     WRAP_S = 0x02,
22     WRAP_E = 0x04,
23     WRAP_W = 0x08
24 };
25
26 /* Transform "yx" to an index position in the world map. */
27 //static uint16_t yx_to_pos(struct yx_uint8 * yx);
28
29 /* Move "yx" into hex direction "d". If this moves "yx" beyond the minimal (0)
30  * or maximal (UINT8_MAX) column or row, it wraps to the opposite side. Such
31  * wrapping is returned as a wraps enum value and stored, so that further calls
32  * to move "yx" back into the opposite direction may unwrap it again. Pass an
33  * "unwrap" of UNWRAP to re-set the internal wrap memory to 0.
34  */
35 static uint8_t mv_yx_in_dir_wrap(char d, struct yx_uint8 * yx, uint8_t unwrap);
36
37 /* Wrapper to "mv_yx_in_dir_wrap()", returns 1 if the wrapped function moved
38  * "yx" within the wrap borders and the map size, else 0.
39  */
40 extern uint8_t mv_yx_in_dir_legal(char dir, struct yx_uint8 * yx);
41
42 /* Return one by one hex dir characters of walking through a circle of "radius".
43  * The circle is initialized by passing a "new_circle" of 1 and the "radius"
44  * and only returns non-null hex direction characters if "new_circle" is 0.
45  */
46 static char next_circle_dir(uint8_t new_circle, uint8_t radius_new);
47
48 /* Draw circle of hexes flagged LIMIT "radius" away from "yx" to "fov_map". */
49 extern void draw_border_circle(struct yx_uint8 yx, uint8_t radius,
50                                uint8_t * fov_map);
51
52 /* eye_to_cell_dir_ratio() helper. */
53 static void geometry_to_char_ratio(uint8_t * n1, uint8_t * n2, uint8_t indent,
54                                    int16_t diff_y, int16_t diff_x,
55                                    uint8_t vertical, uint8_t variant);
56
57 /* From the chars in "available_dirs" and the geometry described by the other
58  * parameters return a string of hex direction characters representing the
59  * approximation of a straight line. "variant" marks the direction as either in
60  * the northern, north-eastern or south-western hex neighborhood if 1, or the
61  * others if 0.
62  */
63 static char * eye_to_cell_dir_ratio(char * available_dirs, uint8_t indent,
64                                     int16_t diff_y, int16_t diff_x,
65                                     uint8_t vertical, uint8_t variant,
66                                     uint8_t shift_right);
67
68 /* Return string approximating in one or two hex direction chars the direction
69  * that a "diff_y" and "diff_x" lead to in the internal half-indented 2D
70  * encoding of hexagonal maps, with "indent" the movement's start indentation.
71  */
72 static char * dir_from_delta(uint8_t indent, int16_t diff_y, int16_t diff_x);
73
74 /* Return string of hex movement direction characters describing the best
75  * possible hex approximation to a straight line from "yx_eye" to "yx_cell". If
76  * "right" is set and the string is of length two, return it with the direction
77  * strings scarcer character appearing first.
78  */
79 static char * eye_to_cell(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_cell,
80                           uint8_t right);
81
82 /* Return string of hex movement direction characters describing the best
83  * possible hex approximation to a straight line from "yx_eye" to "yx_cell". If
84  * "right" is set and the string is of length two, return it with the direction
85  * strings scarcer character appearing first.
86  */
87 static char * eye_to_cell(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_cell,
88                           uint8_t right);
89
90 /* fill_shadow() helper, determining if map's top left cell starts a shadow. */
91 static uint8_t is_top_left_shaded(uint16_t pos_a, uint16_t pos_b,
92                                   int16_t a_y_on_left);
93
94 /* Flag as HIDDEN all cells in "fov_map" that are enclosed by 1) the map's
95  * borders or cells flagged LIMIT and 2) the shadow arms of cells flagged
96  * SHADOW_LEFT and SHADOW_RIGHT extending from "yx_cell", as seen as left and
97  * right as seen from "yx_eye". "pos_a" and "pos_b" store the terminal positions
98  * of these arms in "fov_map" ("pos_a" for the left, "pos_b" for the right one).
99  */
100 static void fill_shadow(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_cell,
101                         uint8_t * fov_map, uint16_t pos_a, uint16_t pos_b);
102
103 /* Flag with "flag" cells of a path from "yx_start" to the end of the map or (if
104  * closer) the view border circle of the cells flagged as LIMIT, in a direction
105  * parallel to the one determined by walking a path from "yx_eye" to the cell
106  * reachable by moving one step into "dir" from "yx_start". If "shift_right" is
107  * set, choose among the possible paths the one whose starting cell is set most
108  * to the right, else do the opposite.
109  */
110 static uint16_t shadow_arm(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_start,
111                            uint8_t * fov_map, char dir, uint8_t flag,
112                            uint8_t shift_right);
113
114 /* From "yx_start", draw shadow of what is invisible as seen from "yx_eye" into
115  * "fov_map" by extending shadow arms from "yx_start" as shadow borders until
116  * the edges of the map or, if smaller, the maximum viewing distance, flag these
117  * shadow arms' cells as HIDE_LATER and the area enclosed by them as HIDDEN.
118  * "dir_left" and "dir_right" are hex directions to move to from "yx_start" for
119  * cells whose shortest straight path to "yx_eye" serve as the lines of sight
120  * enclosing the shadow left and right (left and right as seen from "yx_eye").
121  */
122 static void shadow(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_start,
123                    uint8_t * fov_map, char dir_left, char dir_right);
124
125 /* In "fov_map", if cell of position "yx_cell" is not HIDDEN, set it as VISIBLE,
126  * and if an obstacle to view is positioned there in the game map, flag cells
127  *behind it, unseen from "yx_eye", as HIDDEN on the interior and HIDE_LATER on
128  * their borders.
129  *
130  * The shape and width of shadows is determined by 1) calculating an approximate
131  * direction of "yx_cell" as seen from "yx_eye" as one hex movement direction,
132  * or two directly neighboring each other (i.e. "east", "east and north-east"),
133  * 2) deriving the two hex movement directions clockwise immediately preceding
134  * the first (or only) direction and immediately succeeding the second (or only)
135  * one and 3) passing the two directions thus gained as shadow arm direction
136  * calibration values to shadow() (after this function's other arguments).
137  */
138 static void set_view_of_cell_and_shadows(struct yx_uint8 * yx_cell,
139                                          struct yx_uint8 * yx_eye,
140                                          uint8_t * fov_map);
141
142
143
144 static uint8_t mv_yx_in_dir_wrap(char d, struct yx_uint8 * yx, uint8_t unwrap)
145 {
146     static uint8_t wrap = 0;
147     if (unwrap)
148     {
149         wrap = 0;
150         return 0;
151     }
152     struct yx_uint8 original;
153     original.y = yx->y;
154     original.x = yx->x;
155     if     (d == 'e')
156     {
157         yx->x = yx->x + (yx->y % 2);
158         yx->y--;
159     }
160     else if (d == 'd')
161     {
162         yx->x++;
163     }
164     else if (d == 'c')
165     {
166         yx->x = yx->x + (yx->y % 2);
167         yx->y++;
168     }
169     else if (d == 'x')
170     {
171         yx->x = yx->x - !(yx->y % 2);
172         yx->y++;
173     }
174     else if (d == 's')
175     {
176         yx->x--;
177     }
178     else if (d == 'w')
179     {
180         yx->x = yx->x - !(yx->y % 2);
181         yx->y--;
182     }
183     else
184     {
185         exit_trouble(1, "mv_yx_in_dir_wrap()", "illegal direction");
186     }
187     if      (strchr("edc", d) && yx->x < original.x)
188     {
189         wrap = wrap & WRAP_W ? wrap ^ WRAP_W : wrap | WRAP_E;
190     }
191     else if (strchr("xsw", d) && yx->x > original.x)
192     {
193         wrap = wrap & WRAP_E ? wrap ^ WRAP_E : wrap | WRAP_W;
194     }
195     if      (strchr("we", d) && yx->y > original.y)
196     {
197         wrap = wrap & WRAP_S ? wrap ^ WRAP_S : wrap | WRAP_N;
198     }
199     else if (strchr("xc", d) && yx->y < original.y)
200     {
201         wrap = wrap & WRAP_N ? wrap ^ WRAP_N : wrap | WRAP_S;
202     }
203     return wrap;
204 }
205
206
207
208 extern uint8_t mv_yx_in_dir_legal(char dir, struct yx_uint8 * yx)
209 {
210     uint8_t wraptest = mv_yx_in_dir_wrap(dir, yx, 0);
211     if (!wraptest && yx->x < world.map.size.x && yx->y < world.map.size.y)
212     {
213         return 1;
214     }
215     return 0;
216 }
217
218
219
220 static char next_circle_dir(uint8_t new_circle, uint8_t radius_new)
221 {
222     static uint8_t i_dirs = 0;
223     static uint8_t i_dist = 0;
224     static uint8_t radius = 0;
225     char * dirs = "dcxswe";
226     if (new_circle)
227     {
228         i_dirs = 0;
229         i_dist = 0;
230         radius = radius_new;
231         return '\0';
232     }
233     char ret_dir = dirs[i_dirs];
234     i_dist++;
235     if (i_dist == radius)
236     {
237         i_dist = 0;
238         i_dirs++;
239     }
240     return ret_dir;
241 }
242
243
244
245 extern void draw_border_circle(struct yx_uint8 yx, uint8_t radius,
246                                uint8_t * fov_map)
247 {
248     uint8_t dist;
249     for (dist = 1; dist <= radius; dist++)
250     {
251         mv_yx_in_dir_wrap('w', &yx, 0);
252     }
253     next_circle_dir(1, radius);
254     char dir;
255     while ('\0' != (dir = next_circle_dir(0, 0)))
256     {
257          if (mv_yx_in_dir_legal(dir, &yx))
258          {
259             uint16_t pos = yx_to_map_pos(&yx);
260             fov_map[pos] = LIMIT;
261         }
262     }
263     mv_yx_in_dir_wrap(0, NULL, 1);
264 }
265
266
267
268 static void geometry_to_char_ratio(uint8_t * n1, uint8_t * n2, uint8_t indent,
269                                    int16_t diff_y, int16_t diff_x,
270                                    uint8_t vertical, uint8_t variant)
271 {
272     if      (vertical)
273     {
274         *n1 = (diff_y / 2) - diff_x + ( indent * (diff_y % 2));
275         *n2 = (diff_y / 2) + diff_x + (!indent * (diff_y % 2));
276     }
277     else if (!vertical)
278     {
279         *n1 = diff_y;
280         *n2 = diff_x - (diff_y / 2) - (indent * (diff_y % 2));
281     }
282     if (!variant)
283     {
284         uint8_t tmp = *n1;
285         *n1 = *n2;
286         *n2 = tmp;
287     }
288 }
289
290
291
292 static char * eye_to_cell_dir_ratio(char * available_dirs, uint8_t indent,
293                                     int16_t diff_y, int16_t diff_x,
294                                     uint8_t vertical, uint8_t variant,
295                                     uint8_t shift_right)
296 {
297     char * f_name = "eye_to_cell_dir_ratio()";
298     uint8_t n1, n2;
299     geometry_to_char_ratio(&n1, &n2, indent, diff_y, diff_x, vertical, variant);
300     uint8_t size_chars = n1 + n2;
301     char * dirs = try_malloc(size_chars + 1, f_name);
302     uint8_t n_strong_char = n1 / n2;
303     uint8_t more_char1 = 0 < n_strong_char;
304     n_strong_char = !more_char1 ? (n2 / n1) : n_strong_char;
305     uint16_t i, i_alter;
306     uint8_t i_of_char = shift_right;
307     for (i = 0, i_alter = 0; i < size_chars; i++)
308     {
309         char dirchar = available_dirs[i_of_char];
310         if (more_char1 != i_of_char)
311         {
312             i_alter++;
313             if (i_alter == n_strong_char)
314             {
315                 i_alter = 0;
316                 i_of_char = !i_of_char;
317             }
318         }
319         else
320         {
321             i_of_char = !i_of_char;
322         }
323
324         dirs[i] = dirchar;
325     }
326     dirs[i] = '\0';
327     return dirs;
328 }
329
330
331
332 static char * dir_from_delta(uint8_t indent, int16_t diff_y, int16_t diff_x)
333 {
334     int16_t double_x = 2 * diff_x;
335     int16_t indent_corrected_double_x_pos =  double_x - indent  + !indent;
336     int16_t indent_corrected_double_x_neg = -double_x - !indent +  indent;
337     if (diff_y > 0)
338     {
339         if (diff_y ==  double_x || diff_y == indent_corrected_double_x_pos)
340         {
341             return "c";
342         }
343         if (diff_y == -double_x || diff_y == indent_corrected_double_x_neg)
344         {
345             return "x";
346         }
347         if (diff_y  <  double_x || diff_y  < indent_corrected_double_x_pos)
348         {
349             return "dc";
350         }
351         if (diff_y  < -double_x || diff_y  < indent_corrected_double_x_neg)
352         {
353             return "xs";
354         }
355         return "cx";
356     }
357     if (diff_y < 0)
358     {
359         if (diff_y ==  double_x || diff_y == indent_corrected_double_x_pos)
360         {
361             return "w";
362         }
363         if (diff_y == -double_x || diff_y == indent_corrected_double_x_neg)
364         {
365             return "e";
366         }
367         if (diff_y  >  double_x || diff_y  > indent_corrected_double_x_pos)
368         {
369             return "sw";
370         }
371         if (diff_y  > -double_x || diff_y  > indent_corrected_double_x_neg)
372         {
373             return "ed";
374         }
375         return "we";
376     }
377     return 0 > diff_x ? "s" : "d";
378 }
379
380
381
382 static char * eye_to_cell(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_cell,
383                           uint8_t right)
384 {
385     int16_t diff_y = yx_cell->y - yx_eye->y;
386     int16_t diff_x = yx_cell->x - yx_eye->x;
387     uint8_t indent = yx_eye->y % 2;
388     char * dir = dir_from_delta(indent, diff_y, diff_x);
389     char * dirs;
390     if (1 == strlen(dir))
391     {
392         return strdup(dir);
393     }
394     else if (!strcmp(dir, "dc"))
395     {
396         dirs = eye_to_cell_dir_ratio(dir, indent,  diff_y,  diff_x,  0,0,right);
397     }
398     else if (!strcmp(dir, "xs"))
399     {
400         dirs = eye_to_cell_dir_ratio(dir, !indent,  diff_y, -diff_x, 0,1,right);
401     }
402     else if (!strcmp(dir, "cx"))
403     {
404         dirs = eye_to_cell_dir_ratio(dir, indent,  diff_y,  diff_x,  1,0,right);
405     }
406     else if (!strcmp(dir, "sw"))
407     {
408         dirs = eye_to_cell_dir_ratio(dir, !indent, -diff_y, -diff_x, 0,0,right);
409     }
410     else if (!strcmp(dir, "ed"))
411     {
412         dirs = eye_to_cell_dir_ratio(dir, indent, -diff_y,  diff_x, 0,1,right);
413     }
414     else if (!strcmp(dir, "we"))
415     {
416         dirs = eye_to_cell_dir_ratio(dir, indent, -diff_y,  diff_x, 1,1,right);
417     }
418     return dirs;
419 }
420
421
422
423 static uint8_t is_top_left_shaded(uint16_t pos_a, uint16_t pos_b,
424                                   int16_t a_y_on_left)
425 {
426     uint16_t start_last_row = world.map.size.x * (world.map.size.y - 1);
427     uint8_t a_on_left_or_bottom =    0 <= a_y_on_left
428                                   || (pos_a >= start_last_row);
429     uint8_t b_on_top_or_right =    pos_b < world.map.size.x
430                                 || pos_b % world.map.size.x==world.map.size.x-1;
431     return pos_a != pos_b && b_on_top_or_right && a_on_left_or_bottom;
432 }
433
434
435
436 static void fill_shadow(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_cell,
437                         uint8_t * fov_map, uint16_t pos_a, uint16_t pos_b)
438 {
439     int16_t a_y_on_left = !(pos_a%world.map.size.x)? pos_a/world.map.size.x :-1;
440     int16_t b_y_on_left = !(pos_b%world.map.size.x)? pos_b/world.map.size.x :-1;
441     uint8_t top_left_shaded = is_top_left_shaded(pos_a, pos_b, a_y_on_left);
442     uint16_t pos;
443     uint8_t y, x, in_shade;
444     for (y = 0; y < world.map.size.y; y++)
445     {
446         in_shade =    (top_left_shaded || (b_y_on_left >= 0 && y > b_y_on_left))
447                    && (a_y_on_left < 0 || y < a_y_on_left);
448         for (x = 0; x < world.map.size.x; x++)
449         {
450             pos = (y * world.map.size.x) + x;
451             if (yx_eye->y == yx_cell->y && yx_eye->x < yx_cell->x)
452             {
453                 uint8_t val = fov_map[pos] & (SHADOW_LEFT | SHADOW_RIGHT);
454                 in_shade = 0 < val ? 1 : in_shade;
455             }
456             else if (yx_eye->y == yx_cell->y && yx_eye->x > yx_cell->x)
457             {
458                 uint8_t val = fov_map[pos] & (SHADOW_LEFT | SHADOW_RIGHT);
459                 in_shade = 0 < val ? 0 : in_shade;
460             }
461             else if (yx_eye->y > yx_cell->y && y <= yx_cell->y)
462             {
463                 in_shade = 0 < (fov_map[pos] & SHADOW_LEFT) ? 1 : in_shade;
464                 in_shade = (fov_map[pos] & SHADOW_RIGHT) ? 0 : in_shade;
465             }
466             else if (yx_eye->y < yx_cell->y && y >= yx_cell->y)
467             {
468                 in_shade = 0 < (fov_map[pos] & SHADOW_RIGHT) ? 1 : in_shade;
469                 in_shade = (fov_map[pos] & SHADOW_LEFT) ? 0 : in_shade;
470             }
471             if (!(fov_map[pos] & (SHADOW_LEFT | SHADOW_RIGHT))
472                 && in_shade)
473             {
474                 fov_map[pos] = fov_map[pos] | HIDDEN;
475             }
476         }
477     }
478 }
479
480
481
482 static uint16_t shadow_arm(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_start,
483                            uint8_t * fov_map, char dir, uint8_t flag,
484                            uint8_t shift_right)
485 {
486     struct yx_uint8 yx_border = *yx_start;
487     uint16_t pos = yx_to_map_pos(&yx_border);
488     if (mv_yx_in_dir_legal(dir, &yx_border))
489     {
490         uint8_t met_limit = 0;
491         uint8_t i_dirs = 0;
492         char * dirs = eye_to_cell(yx_eye, &yx_border, shift_right);
493         yx_border = *yx_start;
494         while (!met_limit && mv_yx_in_dir_legal(dirs[i_dirs], &yx_border))
495         {
496             pos = yx_to_map_pos(&yx_border);
497             met_limit = fov_map[pos] & LIMIT;
498             fov_map[pos] = fov_map[pos] | flag;
499             i_dirs = dirs[i_dirs + 1] ? i_dirs + 1 : 0;
500         }
501         free(dirs);
502     }
503     mv_yx_in_dir_wrap(0, NULL, 1);
504     return pos;
505 }
506
507
508
509 static void shadow(struct yx_uint8 * yx_eye, struct yx_uint8 * yx_start,
510                    uint8_t * fov_map, char dir_left, char dir_right)
511 {
512     uint16_t pos_a, pos_b, pos_start, i;
513     pos_a = shadow_arm(yx_eye, yx_start, fov_map, dir_left, SHADOW_LEFT, 0);
514     pos_b = shadow_arm(yx_eye, yx_start, fov_map, dir_right, SHADOW_RIGHT, 1);
515     pos_start = yx_to_map_pos(yx_start);
516     fov_map[pos_start] = fov_map[pos_start] | SHADOW_LEFT | SHADOW_RIGHT;
517     fill_shadow(yx_eye, yx_start, fov_map, pos_a, pos_b);
518     for (i = 0; i < world.map.size.y * world.map.size.x; i++)
519     {
520         if (fov_map[i] & (SHADOW_LEFT | SHADOW_RIGHT) && i != pos_start)
521         {
522             fov_map[i] = fov_map[i] | HIDE_LATER;
523         }
524         fov_map[i] = fov_map[i] ^ (fov_map[i] & SHADOW_LEFT);
525         fov_map[i] = fov_map[i] ^ (fov_map[i] & SHADOW_RIGHT);
526     }
527     return;
528 }
529
530
531
532 static void set_view_of_cell_and_shadows(struct yx_uint8 * yx_cell,
533                                          struct yx_uint8 * yx_eye,
534                                          uint8_t * fov_map)
535 {
536     char * dirs = "dcxswe";
537     uint16_t pos = yx_to_map_pos(yx_cell);
538     if (!(fov_map[pos] & HIDDEN))
539     {
540         fov_map[pos] = fov_map[pos] | VISIBLE;
541         if ('X' == world.map.cells[pos])
542         {
543             uint8_t last_pos = strlen(dirs) - 1;
544             int16_t diff_y = yx_cell->y - yx_eye->y;
545             int16_t diff_x = yx_cell->x - yx_eye->x;
546             uint8_t indent = yx_eye->y % 2;
547             char * dir = dir_from_delta(indent, diff_y, diff_x);
548             uint8_t start_pos = strchr(dirs, dir[0]) - dirs;
549             char prev = start_pos > 0 ? dirs[start_pos - 1] : dirs[last_pos];
550             char next = start_pos < last_pos ? dirs[start_pos + 1] : dirs[0];
551             if (dir[1])
552             {
553                 uint8_t end_pos = strchr(dirs, dir[1]) - dirs;
554                 next = end_pos < last_pos ? dirs[end_pos + 1] : dirs[0];
555             }
556             shadow(yx_eye, yx_cell, fov_map, prev, next);
557         }
558     }
559 }
560
561
562
563 extern uint8_t * build_fov_map(struct MapObj * eye)
564 {
565     char * f_name = "build_fov_map()";
566     uint8_t radius = 2 * world.map.size.y;
567     uint32_t map_size = world.map.size.y * world.map.size.x;
568     struct yx_uint8 yx = eye->pos;
569     uint8_t * fov_map = try_malloc(map_size, f_name);
570     memset(fov_map, 0, map_size);
571     draw_border_circle(yx, radius, fov_map);
572     fov_map[yx_to_map_pos(&yx)] = VISIBLE;
573     uint8_t dist;
574     for (dist = 1; dist <= radius; dist++)
575     {
576         uint8_t first_round = 1;
577         char dir;
578         next_circle_dir(1, dist);
579         while ('\0' != (dir = next_circle_dir(0, 0)))
580         {
581             char i_dir = first_round ? 'e' : dir;
582             first_round = 0;
583             if (mv_yx_in_dir_legal(i_dir, &yx))
584             {
585                 set_view_of_cell_and_shadows(&yx, &eye->pos, fov_map);
586             }
587         }
588     }
589     uint16_t i;
590     for (i = 0; i < world.map.size.y * world.map.size.x; i++)
591     {
592         if (fov_map[i] & HIDE_LATER)
593         {
594               fov_map[i] = fov_map[i] ^ (fov_map[i] & VISIBLE);
595         }
596     }
597     return fov_map;
598 }