Index: src/fov/fov.c =================================================================== --- src/fov/fov.c (revision 3038) +++ src/fov/fov.c (working copy) @@ -13,6 +13,9 @@ #include #include "fov.h" +#undef RADIANS_PER_DEGREE +#define RADIANS_PER_DEGREE 1.74532925199432957692e-02 + /* +---++---++---++---+ | || || || | @@ -123,7 +126,7 @@ unsigned *result = (unsigned *)malloc((maxdist+2)*sizeof(unsigned)); if (result) { for (i = 0; i <= maxdist; ++i) { - result[i] = (unsigned)sqrtf((float)(maxdist*maxdist - i*i)); + result[i] = (unsigned)sqrtf((float)(maxdist*maxdist + maxdist - i*i)); } result[maxdist+1] = 0; } @@ -203,9 +206,12 @@ int x, y, dy, dy0, dy1; \ unsigned h; \ int prev_blocked = -1; \ - float end_slope_next; \ + float end_slope_next, start_slope_next; \ fov_settings_type *settings = data->settings; \ \ + if (end_slope - start_slope < FLT_EPSILON && !(1.0f - end_slope < FLT_EPSILON)) { \ + return; \ + } \ if (dx == 0) { \ fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope, apply_edge, apply_diag); \ return; \ @@ -213,11 +219,10 @@ return; \ } \ \ - dy0 = (int)(0.5f + ((float)dx)*start_slope); \ - dy1 = (int)(0.5f + ((float)dx)*end_slope); \ + dy0 = (int)(0.5f + ((float)dx)*start_slope + 10.0f*FLT_EPSILON); \ + dy1 = (int)(0.5f + ((float)dx)*end_slope - 10.0f*FLT_EPSILON); \ \ rx = data->source_##rx signx dx; \ - ry = data->source_##ry signy dy0; \ \ if (!apply_diag && dy1 == dx) { \ /* We do diagonal lines on every second octant, so they don't get done twice. */ \ @@ -225,6 +230,7 @@ \ /* But, we still need to check if we can see past it if the slopes are similar */ \ if (dy1 < dy0) { \ + ry = data->source_##ry signy dy0; \ if (settings->opaque(data->map, x, y)) { \ return; \ } \ @@ -234,11 +240,12 @@ \ /* we also need to check if the previous spot is blocked */ \ if (dy0 > 0) { \ - ry -= 1; \ + ry = data->source_##ry signy (dy0-1); \ if (settings->opaque(data->map, x, y)) { \ prev_blocked = 1; \ + } else { \ + prev_blocked = 0; \ } \ - ry += 1; \ } \ \ switch (settings->shape) { \ @@ -271,25 +278,41 @@ \ if (settings->opaque(data->map, x, y)) { \ if (settings->opaque_apply == FOV_OPAQUE_APPLY && (apply_edge || dy > 0)) { \ - settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ + settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ } \ if (prev_blocked == 0) { \ end_slope_next = fov_slope((float)dx + 0.5f, (float)dy - 0.5f); \ - fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope_next, apply_edge, apply_diag); \ + if(end_slope_next > end_slope) { \ + end_slope_next = end_slope; \ + } \ + fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope_next, apply_edge, apply_diag); \ } \ prev_blocked = 1; \ } else { \ if (apply_edge || dy > 0) { \ - settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ + settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ } \ if (prev_blocked == 1) { \ - start_slope = fov_slope((float)dx - 0.5f, (float)dy - 0.5f); \ + start_slope_next = fov_slope((float)dx - 0.5f, (float)dy - 0.5f); \ + if(start_slope_next > start_slope) { \ + start_slope = start_slope_next; \ + } \ } \ prev_blocked = 0; \ } \ } \ \ if (prev_blocked == 0) { \ + /* We need to check if the next spot is blocked and change end_slope accordingly */ \ + if (dx != dy1) { \ + ry = data->source_##ry signy (dy1+1); \ + if (settings->opaque(data->map, x, y)) { \ + end_slope_next = fov_slope((float)dx + 0.5f, (float)dy1 + 0.5f); \ + if (end_slope_next < end_slope) { \ + end_slope = end_slope_next; \ + } \ + } \ + } \ fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope, apply_edge, apply_diag); \ } \ } @@ -364,56 +387,55 @@ } } -#define BEAM_DIRECTION(d, p1, p2, p3, p4, p5, p6, p7, p8) \ - if (direction == d) { \ - end_slope = betweenf(a, 0.0f, 1.0f); \ - fov_octant_##p1(&data, 1, 0.0f, end_slope, true, true); \ - fov_octant_##p2(&data, 1, 0.0f, end_slope, false, true); \ - if (a - 1.0f > FLT_EPSILON) { /* a > 1.0f */ \ - start_slope = betweenf(2.0f - a, 0.0f, 1.0f); \ - fov_octant_##p3(&data, 1, start_slope, 1.0f, true, false); \ - fov_octant_##p4(&data, 1, start_slope, 1.0f, true, false); \ - } \ - if (a - 2.0f > FLT_EPSILON) { /* a > 2.0f */ \ - end_slope = betweenf(a - 2.0f, 0.0f, 1.0f); \ - fov_octant_##p5(&data, 1, 0.0f, end_slope, false, true); \ - fov_octant_##p6(&data, 1, 0.0f, end_slope, false, true); \ - } \ - if (a - 3.0f > FLT_EPSILON) { /* a > 3.0f */ \ - start_slope = betweenf(4.0f - a, 0.0f, 1.0f); \ - fov_octant_##p7(&data, 1, start_slope, 1.0f, true, false); \ - fov_octant_##p8(&data, 1, start_slope, 1.0f, false, false); \ - } \ - } +#define BEAM_DIRECTION(d, p1, p2, p3, p4, p5, p6, p7, p8) \ + if (direction == d) { \ + end_slope = betweenf((float)(region_end) + beam_slope, 0.0f, 1.0f); \ + fov_octant_##p1(&data, 1, 0.0f, end_slope, true, true); \ + fov_octant_##p2(&data, 1, 0.0f, end_slope, false, true); \ + if (region_end > 0) { \ + start_slope = betweenf((float)(1 - region_end) + beam_slope, 0.0f, 1.0f); \ + fov_octant_##p3(&data, 1, start_slope, 1.0f, true, false); \ + fov_octant_##p4(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (region_end > 1) { \ + end_slope = betweenf((float)(region_end - 2) + beam_slope, 0.0f, 1.0f); \ + fov_octant_##p5(&data, 1, 0.0f, end_slope, false, true); \ + fov_octant_##p6(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (region_end > 2) { \ + start_slope = betweenf((float)(3 - region_end) + beam_slope, 0.0f, 1.0f); \ + fov_octant_##p7(&data, 1, start_slope, 1.0f, true, false); \ + fov_octant_##p8(&data, 1, start_slope, 1.0f, false, false); \ +}}}} -#define BEAM_DIRECTION_DIAG(d, p1, p2, p3, p4, p5, p6, p7, p8) \ - if (direction == d) { \ - start_slope = betweenf(1.0f - a, 0.0f, 1.0f); \ - fov_octant_##p1(&data, 1, start_slope, 1.0f, true, true); \ - fov_octant_##p2(&data, 1, start_slope, 1.0f, true, false); \ - if (a - 1.0f > FLT_EPSILON) { /* a > 1.0f */ \ - end_slope = betweenf(a - 1.0f, 0.0f, 1.0f); \ - fov_octant_##p3(&data, 1, 0.0f, end_slope, false, true); \ - fov_octant_##p4(&data, 1, 0.0f, end_slope, false, true); \ - } \ - if (a - 2.0f > FLT_EPSILON) { /* a > 2.0f */ \ - start_slope = betweenf(3.0f - a, 0.0f, 1.0f); \ - fov_octant_##p5(&data, 1, start_slope, 1.0f, true, false); \ - fov_octant_##p6(&data, 1, start_slope, 1.0f, true, false); \ - } \ - if (a - 3.0f > FLT_EPSILON) { /* a > 3.0f */ \ - end_slope = betweenf(a - 3.0f, 0.0f, 1.0f); \ - fov_octant_##p7(&data, 1, 0.0f, end_slope, false, true); \ - fov_octant_##p8(&data, 1, 0.0f, end_slope, false, false); \ - } \ - } +#define BEAM_DIRECTION_DIAG(d, p1, p2, p3, p4, p5, p6, p7, p8) \ + if (direction == d) { \ + start_slope = betweenf(beam_slope - (float)(region_end), 0.0f, 1.0f); \ + fov_octant_##p1(&data, 1, start_slope, 1.0f, true, true); \ + fov_octant_##p2(&data, 1, start_slope, 1.0f, true, false); \ + if (region_end > 0) { \ + end_slope = betweenf((float)(region_end - 1) + beam_slope, 0.0f, 1.0f); \ + fov_octant_##p3(&data, 1, 0.0f, end_slope, false, true); \ + fov_octant_##p4(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (region_end > 1) { \ + start_slope = betweenf((float)(2 - region_end) + beam_slope, 0.0f, 1.0f); \ + fov_octant_##p5(&data, 1, start_slope, 1.0f, true, false); \ + fov_octant_##p6(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (region_end > 2) { \ + end_slope = betweenf((float)(region_end - 3) + beam_slope, 0.0f, 1.0f); \ + fov_octant_##p7(&data, 1, 0.0f, end_slope, false, true); \ + fov_octant_##p8(&data, 1, 0.0f, end_slope, false, false); \ +}}}} void fov_beam(fov_settings_type *settings, void *map, void *source, int source_x, int source_y, unsigned radius, fov_direction_type direction, float angle) { fov_private_data_type data; - float start_slope, end_slope, a; + int region_end; + float start_slope, end_slope, beam_slope; data.settings = settings; data.map = map; @@ -433,8 +455,20 @@ * each side of the centre of the beam). e.g. angle = 180.0f means * half the beam is 90.0 which is 2x45, so the result is 2.0. */ - a = angle/90.0f; + region_end = (int)(angle / 90.0f); + /* Only doing the above approach uses a 'tan(x) ~ x' approximation. + * Let's call a tangent to get the correct slope + */ + beam_slope = angle / 2.0f; + while (beam_slope > 45.0f) { + beam_slope -= 90.0f; + if(beam_slope < 0.0f) { + beam_slope = -beam_slope; + } + } + beam_slope = betweenf(tan(RADIANS_PER_DEGREE*beam_slope), 0.0f, 1.0f); + BEAM_DIRECTION(FOV_EAST, ppn, pmn, ppy, mpy, pmy, mmy, mpn, mmn); BEAM_DIRECTION(FOV_WEST, mpn, mmn, pmy, mmy, ppy, mpy, ppn, pmn); BEAM_DIRECTION(FOV_NORTH, mpy, mmy, pmn, mmn, ppn, mpn, ppy, pmy); @@ -445,130 +479,150 @@ BEAM_DIRECTION_DIAG(FOV_SOUTHWEST, pmy, mpn, ppy, mmn, ppn, mmy, pmn, mpy); } -#define BEAM_ANY_DIRECTION(offset, p1, p2, p3, p4, p5, p6, p7, p8) \ -angle_begin -= offset; \ -angle_end -= offset; \ -start_slope = angle_begin; \ -end_slope = betweenf(angle_end, 0.0f, 1.0f); \ -fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ -\ -if (angle_end - 1.0 > FLT_EPSILON) { \ -start_slope = betweenf(2.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p2(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 2.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 2.0f, 0.0f, 1.0f); \ -fov_octant_##p3(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 3.0 > FLT_EPSILON) { \ -start_slope = betweenf(4.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p4(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 4.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 4.0f, 0.0f, 1.0f); \ -fov_octant_##p5(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 5.0 > FLT_EPSILON) { \ -start_slope = betweenf(6.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p6(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 6.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 6.0f, 0.0f, 1.0f); \ -fov_octant_##p7(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 7.0 > FLT_EPSILON) { \ -start_slope = betweenf(8.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p8(&data, 1, start_slope, 1.0f, false, false); \ +#define BEAM_ANY_DIRECTION(p1, p2, p3, p4, p5, p6, p7, p8) \ +start_slope = angle_begin; \ +end_slope = betweenf((float)(region_diff) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ + \ +if (region_diff > 0) { \ +start_slope = betweenf((float)(1 - region_diff) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p2(&data, 1, start_slope, 1.0f, true, false); \ + \ +if (region_diff > 1) { \ +end_slope = betweenf((float)(region_diff - 2) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p3(&data, 1, 0.0f, end_slope, false, true); \ + \ +if (region_diff > 2) { \ +start_slope = betweenf((float)(3 - region_diff) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p4(&data, 1, start_slope, 1.0f, true, false); \ + \ +if (region_diff > 3) { \ +end_slope = betweenf((float)(region_diff - 4) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p5(&data, 1, 0.0f, end_slope, false, true); \ + \ +if (region_diff > 4) { \ +start_slope = betweenf((float)(5 - region_diff) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p6(&data, 1, start_slope, 1.0f, true, false); \ + \ +if (region_diff > 5) { \ +end_slope = betweenf((float)(region_diff - 6) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p7(&data, 1, 0.0f, end_slope, false, true); \ + \ +if (region_diff > 6) { \ +start_slope = betweenf((float)(7 - region_diff) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p8(&data, 1, start_slope, 1.0f, false, false); \ }}}}}}} -#define BEAM_ANY_DIRECTION_DIAG(offset, p1, p2, p3, p4, p5, p6, p7, p8) \ -angle_begin -= offset; \ -angle_end -= offset; \ -start_slope = betweenf(1.0 - angle_end, 0.0f, 1.0f); \ -end_slope = 1.0 - angle_begin; \ -fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ -\ -if (angle_end - 1.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 1.0f, 0.0f, 1.0f); \ -fov_octant_##p2(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 2.0 > FLT_EPSILON) { \ -start_slope = betweenf(3.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p3(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 3.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 3.0f, 0.0f, 1.0f); \ -fov_octant_##p4(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 4.0 > FLT_EPSILON) { \ -start_slope = betweenf(5.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p5(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 5.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 5.0f, 0.0f, 1.0f); \ -fov_octant_##p6(&data, 1, 0.0f, end_slope, false, true); \ -\ -if (angle_end - 6.0 > FLT_EPSILON) { \ -start_slope = betweenf(7.0f - angle_end, 0.0f, 1.0f); \ -fov_octant_##p7(&data, 1, start_slope, 1.0f, true, false); \ -\ -if (angle_end - 7.0 > FLT_EPSILON) { \ -end_slope = betweenf(angle_end - 7.0f, 0.0f, 1.0f); \ -fov_octant_##p8(&data, 1, 0.0f, end_slope, false, false); \ +#define BEAM_ANY_DIRECTION_MIRROR(p1, p2, p3, p4, p5, p6, p7, p8) \ +start_slope = betweenf(angle_end - (float)(region_diff), 0.0f, 1.0f); \ +end_slope = angle_begin; \ +fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ + \ +if (region_diff > 0) { \ +end_slope = betweenf((float)(region_diff - 1) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p2(&data, 1, 0.0f, end_slope, false, true); \ + \ +if (region_diff > 1) { \ +start_slope = betweenf((float)(2 - region_diff) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p3(&data, 1, start_slope, 1.0f, true, false); \ + \ +if (region_diff > 2) { \ +end_slope = betweenf((float)(region_diff - 3) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p4(&data, 1, 0.0f, end_slope, false, true); \ + \ +if (region_diff > 3) { \ +start_slope = betweenf((float)(4 - region_diff) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p5(&data, 1, start_slope, 1.0f, true, false); \ + \ +if (region_diff > 4) { \ +end_slope = betweenf((float)(region_diff - 5) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p6(&data, 1, 0.0f, end_slope, false, true); \ + \ +if (region_diff > 5) { \ +start_slope = betweenf((float)(6 - region_diff) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p7(&data, 1, start_slope, 1.0f, true, false); \ + \ +if (region_diff > 6) { \ +end_slope = betweenf((float)(region_diff - 7) + angle_end, 0.0f, 1.0f); \ +fov_octant_##p8(&data, 1, 0.0f, end_slope, false, false); \ }}}}}}} void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source, - int source_x, int source_y, unsigned radius, - float dir_angle, float beam_angle) { - + int source_x, int source_y, unsigned radius, + float dir_angle, float beam_angle) { + fov_private_data_type data; + int region_begin, region_end, region_diff; float start_slope, end_slope, angle_begin, angle_end; - + data.settings = settings; data.map = map; data.source = source; data.source_x = source_x; data.source_y = source_y; data.radius = radius; - + if (beam_angle <= 0.0f) { return; } else if (beam_angle >= 360.0f) { _fov_circle(&data); return; } - + while (dir_angle >= 360.0f) { - dir_angle -= 360.0f; + dir_angle -= 360.0f; } - while (dir_angle < 0.0f) { - dir_angle += 360.0f; + dir_angle += 360.0f; } - - /* Calculate the angles as a percentage of 45 degrees */ - angle_begin = (dir_angle - 0.5*beam_angle) / 45.0f; - angle_end = (dir_angle + 0.5*beam_angle) / 45.0f; + + /* We will calculate the *correct* slopes. 'tan(x) ~ x' is no longer a sufficient approximation */ + angle_begin = dir_angle - 0.5*beam_angle; + angle_end = dir_angle + 0.5*beam_angle; if (angle_begin < 0.0f) { - angle_begin += 8.0f; - angle_end += 8.0f; + angle_begin += 360.0f; + angle_end += 360.0f; } - - if (1.0f - angle_begin > FLT_EPSILON) { - BEAM_ANY_DIRECTION(0.0f, ppn, ppy, pmy, mpn, mmn, mmy, mpy, pmn); - } else if (2.0f - angle_begin > FLT_EPSILON) { - BEAM_ANY_DIRECTION_DIAG(1.0f, ppy, pmy, mpn, mmn, mmy, mpy, pmn, ppn); - } else if (3.0f - angle_begin > FLT_EPSILON) { - BEAM_ANY_DIRECTION(2.0f, pmy, mpn, mmn, mmy, mpy, pmn, ppn, ppy); - } else if (4.0f - angle_begin > FLT_EPSILON) { - BEAM_ANY_DIRECTION_DIAG(3.0f, mpn, mmn, mmy, mpy, pmn, ppn, ppy, pmy); - } else if (5.0f - angle_begin > FLT_EPSILON) { - BEAM_ANY_DIRECTION(4.0f, mmn, mmy, mpy, pmn, ppn, ppy, pmy, mpn); - } else if (6.0f - angle_begin > FLT_EPSILON) { - BEAM_ANY_DIRECTION_DIAG(5.0f, mmy, mpy, pmn, ppn, ppy, pmy, mpn, mmn); - } else if (7.0f - angle_begin > FLT_EPSILON) { - BEAM_ANY_DIRECTION(6.0f, mpy, pmn, ppn, ppy, pmy, mpn, mmn, mmy); - } else if (8.0f - angle_begin > FLT_EPSILON) { - BEAM_ANY_DIRECTION_DIAG(7.0f, pmn, ppn, ppy, pmy, mpn, mmn, mmy, mpy); + + /* Calculate the regions as a percentage of 45 degrees */ + region_begin = (int)(angle_begin / 45.0f); + region_end = (int)(angle_end / 45.0f); + region_diff = region_end - region_begin; + + /* Prepare to calculate the correct slopes, which will be between 0 and 1 */ + while (angle_begin > 45.0f) { + angle_begin -= 90.0f; + if(angle_begin < 0.0f) { + angle_begin = -angle_begin; + } } + while (angle_end > 45.0f) { + angle_end -= 90.0f; + if(angle_end < 0.0f) { + angle_end = -angle_end; + } + } + + /* Now calculate the correct slopes. I don't trust "tan" to be a clean function, hence the bounds */ + angle_begin = betweenf(tan(RADIANS_PER_DEGREE*angle_begin), 0.0f, 1.0f); + angle_end = betweenf(tan(RADIANS_PER_DEGREE*angle_end), 0.0f, 1.0f); + + if (region_begin == 0) { + BEAM_ANY_DIRECTION (ppn, ppy, pmy, mpn, mmn, mmy, mpy, pmn); + } else if (region_begin == 1) { + BEAM_ANY_DIRECTION_MIRROR (ppy, pmy, mpn, mmn, mmy, mpy, pmn, ppn); + } else if (region_begin == 2) { + BEAM_ANY_DIRECTION (pmy, mpn, mmn, mmy, mpy, pmn, ppn, ppy); + } else if (region_begin == 3) { + BEAM_ANY_DIRECTION_MIRROR (mpn, mmn, mmy, mpy, pmn, ppn, ppy, pmy); + } else if (region_begin == 4) { + BEAM_ANY_DIRECTION (mmn, mmy, mpy, pmn, ppn, ppy, pmy, mpn); + } else if (region_begin == 5) { + BEAM_ANY_DIRECTION_MIRROR (mmy, mpy, pmn, ppn, ppy, pmy, mpn, mmn); + } else if (region_begin == 6) { + BEAM_ANY_DIRECTION (mpy, pmn, ppn, ppy, pmy, mpn, mmn, mmy); + } else if (region_begin == 7) { + BEAM_ANY_DIRECTION_MIRROR (pmn, ppn, ppy, pmy, mpn, mmn, mmy, mpy); + } } + Index: game/engines/default/engine/utils.lua =================================================================== --- game/engines/default/engine/utils.lua (revision 3038) +++ game/engines/default/engine/utils.lua (working copy) @@ -854,13 +854,16 @@ function core.fov.circle_grids(x, y, radius, block) if radius == 0 then return {[x]={[y]=true}} end local grids = {} - core.fov.calc_circle(x, y, game.level.map.w, game.level.map.h, radius, function(_, lx, ly) - if not grids[lx] then grids[lx] = {} end - grids[lx][ly] = true + core.fov.calc_circle(x, y, game.level.map.w, game.level.map.h, radius, + function(_, lx, ly) + if block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end + end, + function(_, lx, ly) + if not grids[lx] then grids[lx] = {} end + grids[lx][ly] = true + end, + nil) - if block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end - end, function()end, nil) - -- point of origin if not grids[x] then grids[x] = {} end grids[x][y] = true @@ -871,13 +874,16 @@ function core.fov.beam_grids(x, y, radius, dir, angle, block) if radius == 0 then return {[x]={[y]=true}} end local grids = {} - core.fov.calc_beam(x, y, game.level.map.w, game.level.map.h, radius, dir, angle, function(_, lx, ly) - if not grids[lx] then grids[lx] = {} end - grids[lx][ly] = true + core.fov.calc_beam(x, y, game.level.map.w, game.level.map.h, radius, dir, angle, + function(_, lx, ly) + if block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end + end, + function(_, lx, ly) + if not grids[lx] then grids[lx] = {} end + grids[lx][ly] = true + end, + nil) - if block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end - end, function()end, nil) - -- point of origin if not grids[x] then grids[x] = {} end grids[x][y] = true Index: game/engines/default/engine/Target.lua =================================================================== --- game/engines/default/engine/Target.lua (revision 3038) +++ game/engines/default/engine/Target.lua (working copy) @@ -77,21 +77,25 @@ local stop_x, stop_y = self.source_actor.x, self.source_actor.y local stop_radius_x, stop_radius_y = self.source_actor.x, self.source_actor.y local stopped = false + local prev_x, prev_y = lx, ly while lx and ly do - local block, hit, hit_radius = false, true, true + local block, hit, hit_radius, is_los_blocked = false, true, true, false if self.target_type.block_path then - block, hit, hit_radius = self.target_type:block_path(lx, ly) + block, hit, hit_radius, is_los_blocked = self.target_type:block_path(lx, ly, self.source_actor.x, self.source_actor.y, self.target.x, self.target.y) end -- Update coordinates and set color - if hit and not stopped then - stop_x, stop_y = lx, ly - else - s = self.sr + if not stopped then + if hit then + stop_x, stop_y = lx, ly + end + if hit_radius then + stop_radius_x, stop_radius_y = lx, ly + else + stop_radius_x, stop_radius_y = prev_x, prev_y + end end - if hit_radius and not stopped then - stop_radius_x, stop_radius_y = lx, ly - end + if self.target_type.min_range and not stopped then -- Check if we should be "red" if core.fov.distance(self.source_actor.x, self.source_actor.y, lx, ly) < self.target_type.min_range then @@ -101,12 +105,14 @@ s = self.sb end end + + if is_los_blocked then s = self.sr end s:toScreen(self.display_x + (lx - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (ly - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom) if block then s = self.sr stopped = true end - + prev_x, prev_y = lx, ly lx, ly = l() end self.cursor:toScreen(self.display_x + (self.target.x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (self.target.y - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom) @@ -131,8 +137,8 @@ elseif self.target_type.cone and self.target_type.cone > 0 then local dir_angle = math.deg(math.atan2(self.target.y - self.source_actor.y, self.target.x - self.source_actor.x)) core.fov.calc_beam_any_angle( - stop_radius_x, - stop_radius_y, + self.source_actor.x, + self.source_actor.y, game.level.map.w, game.level.map.h, self.target_type.cone, @@ -178,24 +184,94 @@ range=20, selffire=true, friendlyfire=true, - block_path = function(typ, lx, ly) + block_path = function(typ, lx, ly, srcx, srcy, tgtx, tgty) + -- If 'srcx' and 'srcy' can be guaranteed to be the same as 'typ.source_actor.x' and 'typ.source_actor.y' for all situations, then feel free change this code accordingly. + if not typ.no_restrict then + -- If we want LoS to be restricted by FoV, then what better way to ensure this than by using the FoV function 'core.fov.calc_beam_any_angle'? + -- Plus, doing it this way will make what I'm *going* to do with FoV much easier :) -- tiger_eye + -- LoS is stricly less than or equal to FoV. LoS still uses bresenham line drawing (i.e., 'line.new'), which attempts to use a path from + -- the center of the source tile to the center of the target tile. Fancier stuff will come later! + -- Please note that there will be uncommon situations in which a bresenham line to a visible tile will pass through a blocked tile, + -- and the projectile will stop at the blocked tile. This just means there was not a clear path to the center of the target tile. + -- There still exists a bresenham line that passes through that tile, but you'll need to target a different tile. + + if (typ.stop_block or not typ.pass_terrain) and (not typ.passable_terrain_cache or typ.prev_srcx ~= srcx or typ.prev_srcy ~= srcy or typ.prev_tgtx ~= tgtx or typ.prev_tgty ~= tgty) then + typ.blocked_terrain_cache = {} + typ.passable_terrain_cache = {} + typ.prev_srcx, typ.prev_srcy = srcx, srcy + typ.prev_tgtx, typ.prev_tgty = tgtx, tgty + local function decrease(x, d) + if x < 0 then return x + d end + return x - d + end + local function increase(x, d) + if x < 0 then return x - d end + return x + d + end + local dx, dy = tgtx - srcx, tgty - srcy + local angle_bottom, angle_top + -- Calculate angles to the top and bottom of the target tile + if math.abs(dx) > math.abs(dy) then + angle_bottom = math.deg(math.atan2(decrease(dy, 0.5), dx)) + angle_top = math.deg(math.atan2(increase(dy, 0.5), dx)) + else + angle_bottom = math.deg(math.atan2(dy, decrease(dx, 0.5))) + angle_top = math.deg(math.atan2(dy, increase(dx, 0.5))) + end + + -- Let's use a larger than necessary 'cone_angle' to reduce odd behavior like the the following: + -- x = blocked tile, T = target, ~ = projectile path, and * is where the projectile stops + -- ...xx ...xx ...xx + -- ...xx ...xT ...xT + -- ..#.. ..#.. should be ..#*. for smoother gameplay + -- ..... --> .~*.. .~~.. + -- @.... @.... @.... + local cone_angle = 2 * math.abs(angle_top - angle_bottom) + local dir_angle = (angle_bottom + angle_top) / 2 + local radius = math.floor(math.sqrt(dx*dx + dy*dy) + 1.0) + core.fov.calc_beam_any_angle( + srcx, + srcy, + game.level.map.w, + game.level.map.h, + radius, + dir_angle, + cone_angle, + function(_, px, py) + if game.level.map:checkEntity(px, py, engine.Map.TERRAIN, "block_move") then + if not typ.blocked_terrain_cache[px] then typ.blocked_terrain_cache[px] = {} end + typ.blocked_terrain_cache[px][py] = true + return true + end + end, + function(_, px, py) + if not typ.passable_terrain_cache[px] then typ.passable_terrain_cache[px] = {} end + typ.passable_terrain_cache[px][py] = true + end, + nil) + end + + local is_terrain_blocked = typ.blocked_terrain_cache[lx] and typ.blocked_terrain_cache[lx][ly] + local is_los_blocked = not (typ.passable_terrain_cache[lx] and typ.passable_terrain_cache[lx][ly]) + if typ.range and typ.source_actor and typ.source_actor.x then local dist = core.fov.distance(typ.source_actor.x, typ.source_actor.y, lx, ly) - if math.floor(dist - typ.range + 0.5) > 0 then return true, false, false end + if math.floor(dist - typ.range + 0.5) > 0 then return true, false, false, is_los_blocked end end if typ.requires_knowledge and not game.level.map.remembers(lx, ly) and not game.level.map.seens(lx, ly) then - return true, false, false + return true, false, false, is_los_blocked end - if not typ.pass_terrain and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then - return true, true, false + + if (typ.stop_block or not typ.pass_terrain) and (is_terrain_blocked or is_los_blocked) then + return true, true, false, is_los_blocked -- If we explode due to something other than terrain, then we should explode ON the tile, not before it elseif typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then - return true, true, true + return true, true, true, is_los_blocked end end -- If we don't block the path, then the explode point should be here - return false, true, true + return false, true, true, false end, block_radius=function(typ, lx, ly) return not typ.no_restrict and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") Index: game/engines/default/engine/Projectile.lua =================================================================== --- game/engines/default/engine/Projectile.lua (revision 3038) +++ game/engines/default/engine/Projectile.lua (working copy) @@ -195,12 +195,12 @@ if x and y then self:move(x, y) end if act then self.src:projectDoAct(self.project.def.typ, self.project.def.tg, self.project.def.damtype, self.project.def.dam, self.project.def.particles, self.x, self.y, self.tmp_proj) end if stop then - local block, hit, hit_radius = false, true, true + local block, hit, hit_radius, is_los_blocked = false, true, true, false if self.project.def.typ.block_path then - block, hit, hit_radius = self.project.def.typ:block_path(self.x, self.y) + block, hit, hit_radius, is_los_blocked = self.project.def.typ:block_path(self.x, self.y, self.project.def.start_x, self.project.def.start_y, self.project.def.x, self.project.def.y) end local radius_x, radius_y - if hit_radius then + if hit_radius and not is_los_blocked then radius_x, radius_y = self.x, self.y else radius_x, radius_y = self.old_x, self.old_y Index: game/engines/default/engine/interface/ActorProject.lua =================================================================== --- game/engines/default/engine/interface/ActorProject.lua (revision 3038) +++ game/engines/default/engine/interface/ActorProject.lua (working copy) @@ -58,27 +58,38 @@ local stop_radius_x, stop_radius_y = srcx, srcy local l = line.new(srcx, srcy, x, y) lx, ly = l() + local prev_x, prev_y = lx, ly local initial_dir = lx and coord_to_dir[lx - srcx][ly - srcy] or 5 while lx and ly do - local block, hit, hit_radius = false, true, true + local block, hit, hit_radius, is_los_blocked = false, true, true, false if typ.block_path then - block, hit, hit_radius = typ:block_path(lx, ly) + block, hit, hit_radius, is_los_blocked = typ:block_path(lx, ly, srcx, srcy, x, y) end if hit then - stop_x, stop_y = lx, ly - -- Deal damage: beam - if typ.line then addGrid(lx, ly) end - -- WHAT DOES THIS DO AGAIN? - -- Call the on project of the target grid if possible - if not t.bypass and game.level.map:checkAllEntities(lx, ly, "on_project", self, t, lx, ly, damtype, dam, particles) then - return + if not is_los_blocked then + stop_x, stop_y = lx, ly + -- Deal damage: beam + if typ.line then addGrid(lx, ly) end + -- WHAT DOES THIS DO AGAIN? + -- Call the on project of the target grid if possible + if not t.bypass and game.level.map:checkAllEntities(lx, ly, "on_project", self, t, lx, ly, damtype, dam, particles) then + return + end end end if hit_radius then stop_radius_x, stop_radius_y = lx, ly + else + stop_radius_x, stop_radius_y = prev_x, prev_y end + if is_los_blocked then + stop_x, stop_y = prev_x, prev_y + stop_radius_x, stop_radius_y = prev_x, prev_y + end + if block then break end + prev_x, prev_y = lx, ly lx, ly = l() end @@ -97,12 +108,11 @@ addGrid(px, py) end, nil) - addGrid(stop_x, stop_y) elseif typ.cone and typ.cone > 0 then local dir_angle = math.deg(math.atan2(y - self.y, x - self.x)) core.fov.calc_beam_any_angle( - stop_radius_x, - stop_radius_y, + self.x, + self.y, game.level.map.w, game.level.map.h, typ.cone, @@ -115,7 +125,6 @@ addGrid(px, py) end, nil) - addGrid(stop_x, stop_y) else -- Deal damage: single addGrid(stop_x, stop_y) @@ -172,19 +181,28 @@ local stop_radius_x, stop_radius_y = self.x, self.y local l = line.new(self.x, self.y, x, y) lx, ly = l() + local prev_x, prev_y = lx, ly while lx and ly do - local block, hit, hit_radius = false, true, true + local block, hit, hit_radius, is_los_blocked = false, true, true, false if typ.block_path then - block, hit, hit_radius = typ:block_path(lx, ly) + block, hit, hit_radius, is_los_blocked = typ:block_path(lx, ly, self.x, self.y, x, y) end if hit then stop_x, stop_y = lx, ly end if hit_radius then stop_radius_x, stop_radius_y = lx, ly + else + stop_radius_x, stop_radius_y = prev_x, prev_y end + if is_los_blocked then + stop_x, stop_y = prev_x, prev_y + stop_radius_x, stop_radius_y = prev_x, prev_y + end + if block then break end + prev_x, prev_y = lx, ly lx, ly = l() end @@ -193,7 +211,7 @@ return end - if stop_x == x and stop_y == y then return true, stop_x, stop_y, stop_x, stop_y end + if stop_x == x and stop_y == y then return true, stop_x, stop_y, stop_radius_x, stop_radius_y end return false, stop_x, stop_y, stop_radius_x, stop_radius_y end @@ -236,11 +254,16 @@ if lx and ly then lx, ly = l() end if lx and ly then - local block, hit, hit_radius = false, true, true + local block, hit, hit_radius, is_los_blocked = false, true, true, false if typ.block_path then - block, hit, hit_radius = typ:block_path(lx, ly) + block, hit, hit_radius, is_los_blocked = typ:block_path(lx, ly, srcx, srcy, tgtx, tgty) end + -- If there is no path to target, then don't move any more + if is_los_blocked then + return nil, nil, false, true + end + if block then if hit then return lx, ly, false, true @@ -315,7 +338,7 @@ game.level.map.w, game.level.map.h, typ.cone, - initial_dir, + dir_angle, typ.cone_angle, function(_, px, py) if typ.block_radius and typ:block_radius(px, py) then return true end Index: game/modules/tome/dialogs/UseTalents.lua =================================================================== --- game/modules/tome/dialogs/UseTalents.lua (revision 3038) +++ game/modules/tome/dialogs/UseTalents.lua (working copy) @@ -147,10 +147,10 @@ self:simplePopup("Hotkey "..b.what.." assigned", self.actor:getTalentFromId(item.talent).name:capitalize().." assigned to hotkey "..b.what) elseif b.what == "middle" then self.actor.auto_shoot_midclick_talent = item.talent - self:simplePopup("Middle mouse click assigned", self.actor:getTalentFromId(item.talent).name:capitalize().." assigned to middle mouse click on an hostile target.") + self:simplePopup("Middle mouse click assigned", self.actor:getTalentFromId(item.talent).name:capitalize().." assigned to middle mouse click on a hostile target.") elseif b.what == "left" then self.actor.auto_shoot_talent = item.talent - self:simplePopup("Left mouse click assigned", self.actor:getTalentFromId(item.talent).name:capitalize().." assigned to left mouse click on an hostile target.") + self:simplePopup("Left mouse click assigned", self.actor:getTalentFromId(item.talent).name:capitalize().." assigned to left mouse click on a hostile target.") elseif b.what == "unbind" then if self.actor.auto_shoot_talent == item.talent then self.actor.auto_shoot_talent = nil end if self.actor.auto_shoot_midclick_talent == item.talent then self.actor.auto_shoot_midclick_talent = nil end Index: game/modules/tome/data/talents/spells/staff-combat.lua =================================================================== --- game/modules/tome/data/talents/spells/staff-combat.lua (revision 3038) +++ game/modules/tome/data/talents/spells/staff-combat.lua (working copy) @@ -31,12 +31,23 @@ target = function(self, t) return {type="bolt", range=self:getTalentRange(t), talent=t, display = {particle=particle, trail=trail}, -- Like a normal block_path, but goes over friendlies - block_path = function(typ, lx, ly) + -- Note: it ~may~ make sense to use "friendlyfire" to add this functionality + block_path = function(typ, lx, ly, srcx, srcy, tgtx, tgty) + -- use default 'block_path' + local dummy_typ = engine.Target:getType({type="hit"}) + local block, hit, hit_radius, is_los_blocked = dummy_typ.block_path(typ, lx, ly, srcx, srcy, tgtx, tgty) + + -- look for friendly actors and check if the terrain is passable local a = game.level.map(lx, ly, engine.Map.ACTOR) - if a and self:reactionToward(a) >= 0 then return false, lx, ly - elseif game.level.map:checkAllEntities(lx, ly, "block_move") then return true, lx, ly end - if typ.range and typ.source_actor and typ.source_actor.x and math.sqrt((typ.source_actor.x-lx)^2 + (typ.source_actor.y-ly)^2) > typ.range then return true end - return false, lx, ly + local is_terrain_blocked = typ.blocked_terrain_cache[lx] and typ.blocked_terrain_cache[lx][ly] + if a and self:reactionToward(a) >= 0 then + if is_los_blocked or is_terrain_blocked then + return true, false, false, is_los_blocked + else + return false, false, true, is_los_blocked + end + end + return block, hit, hit_radius, is_los_blocked end, } end,