--- deliantra/server/common/los.C 2008/12/20 02:32:31 1.42 +++ deliantra/server/common/los.C 2009/01/09 15:10:21 1.59 @@ -21,26 +21,29 @@ * The authors can be reached via e-mail to */ -/* Nov 95 - inserted USE_LIGHTING code stuff in here - b.t. */ - #include #include -static void expand_lighted_sight (object *op); +#define SEE_IN_DARK_RADIUS 2 +#define MAX_VISION 10 // maximum visible radius +// los flags enum { - LOS_XI = 0x01, - LOS_YI = 0x02, + FLG_XI = 0x01, // we have an x-parent + FLG_YI = 0x02, // we have an y-parent + FLG_BLOCKED = 0x04, // this space blocks the view + FLG_QUEUED = 0x80 // already queued in queue, or border }; struct los_info { - sint8 xo, yo; // obscure angle - sint8 xe, ye; // angle deviation - uint8 culled; - uint8 queued; + uint8 flags; // FLG_xxx + uint8 culled; // culled from "tree" uint8 visible; - uint8 flags; + uint8 pad0; + + sint8 xo, yo; // obscure angle + sint8 xe, ye; // angle deviation }; // temporary storage for the los algorithm, @@ -77,17 +80,14 @@ sint8 x = LOS_X0 + dx; sint8 y = LOS_Y0 + dy; - if (x < 0 || x >= MAP_CLIENT_X) return; - if (y < 0 || y >= MAP_CLIENT_Y) return; - los_info &l = los[x][y]; l.flags |= flags; - if (l.queued) + if (l.flags & FLG_QUEUED) return; - l.queued = 1; + l.flags |= FLG_QUEUED; queue[q1].x = dx; queue[q1].y = dy; @@ -101,13 +101,38 @@ // which has been simplified and changed considerably, but // still is basically the same algorithm. static void -do_los (object *op) +calculate_los (player *pl) { - player *pl = op->contr; - - int max_radius = max (pl->ns->mapx, pl->ns->mapy) / 2; + { + memset (los, 0, sizeof (los)); - memset (los, 0, sizeof (los)); + // we keep one line for ourselves, for the border flag + // so the client area is actually MAP_CLIENT_(X|Y) - 2 + int half_x = min (LOS_X0 - 1, pl->ns->mapx / 2); + int half_y = min (LOS_Y0 - 1, pl->ns->mapy / 2); + + // create borders, the corners are not touched + for (int dx = -half_x; dx <= half_x; ++dx) + los [dx + LOS_X0][LOS_Y0 - (half_y + 1)].flags = + los [dx + LOS_X0][LOS_Y0 + (half_y + 1)].flags = FLG_QUEUED; + + for (int dy = -half_y; dy <= half_y; ++dy) + los [LOS_X0 - (half_x + 1)][dy + LOS_Y0].flags = + los [LOS_X0 + (half_x + 1)][dy + LOS_Y0].flags = FLG_QUEUED; + + // now reset the los area and also add blocked flags + // which supposedly is faster than doing it inside the + // spiral path algorithm below, except when very little + // area is visible, in which case it is slower. which evens + // out los calculation times between large and small los maps. + // apply_lights also iterates over this area, maybe these + // two passes could be combined somehow. + unordered_mapwalk (pl->observe, -half_x, -half_y, half_x, half_y) + { + los_info &l = los [LOS_X0 + dx][LOS_Y0 + dy]; + l.flags = m->at (nx, ny).flags () & P_BLOCKSVIEW ? FLG_BLOCKED : 0; + } + } q1 = 0; q2 = 0; // initialise queue, not strictly required enqueue (0, 0); // enqueue center @@ -130,17 +155,15 @@ sint8 x = LOS_X0 + dx; sint8 y = LOS_Y0 + dy; - //int distance = idistance (dx, dy); if (distance > max_radius) continue;//D - int distance = 0;//D - los_info &l = los[x][y]; - if (expect_true (l.flags & (LOS_XI | LOS_YI))) + if (expect_true (l.flags & (FLG_XI | FLG_YI))) { l.culled = 1; + l.xo = l.yo = l.xe = l.ye = 0; // check contributing spaces, first horizontal - if (expect_true (l.flags & LOS_XI)) + if (expect_true (l.flags & FLG_XI)) { los_info *xi = &los[x - sign (dx)][y]; @@ -175,7 +198,7 @@ } // check contributing spaces, last vertical, identical structure - if (expect_true (l.flags & LOS_YI)) + if (expect_true (l.flags & FLG_YI)) { los_info *yi = &los[x][y - sign (dy)]; @@ -209,13 +232,7 @@ } } - // check whether this space blocks the view - maptile *m = op->map; - sint16 nx = op->x + dx; - sint16 ny = op->y + dy; - - if (expect_true (!xy_normalise (m, nx, ny)) - || expect_false (m->at (nx, ny).flags () & P_BLOCKSVIEW)) + if (l.flags & FLG_BLOCKED) { l.xo = l.xe = abs (dx); l.yo = l.ye = abs (dy); @@ -224,6 +241,7 @@ // copy the los from the square towards the player, // so outward diagonal corners are lit. pl->los[x][y] = los[x - sign0 (dx)][y - sign0 (dy)].visible ? 0 : LOS_BLOCKED; + l.visible = false; } else @@ -234,7 +252,7 @@ && (l.ye <= 0 || l.ye > l.yo); pl->los[x][y] = l.culled ? LOS_BLOCKED - : l.visible ? max (0, 2 - max_radius + distance) + : l.visible ? 0 : 3; } @@ -245,36 +263,26 @@ // positive and negative directions. if (!l.culled) { - if (dx >= 0) enqueue (dx + 1, dy, LOS_XI); - if (dx <= 0) enqueue (dx - 1, dy, LOS_XI); - if (dy >= 0) enqueue (dx, dy + 1, LOS_YI); - if (dy <= 0) enqueue (dx, dy - 1, LOS_YI); + if (dx >= 0) enqueue (dx + 1, dy, FLG_XI); + if (dx <= 0) enqueue (dx - 1, dy, FLG_XI); + if (dy >= 0) enqueue (dx, dy + 1, FLG_YI); + if (dy <= 0) enqueue (dx, dy - 1, FLG_YI); } } } -/* returns true if op carries one or more lights - * This is a trivial function now days, but it used to - * be a bit longer. Probably better for callers to just - * check the op->glow_radius instead of calling this. - */ -int -has_carried_lights (const object *op) -{ - /* op may glow! */ - if (op->glow_radius > 0) - return 1; - - return 0; -} - /* radius, distance => lightness adjust */ -static sint8 darkness[MAX_LIGHT_RADIUS * 2 + 1][MAX_LIGHT_RADIUS * 3 / 2 + 1]; +static sint8 light_atten[MAX_LIGHT_RADIUS * 2 + 1][MAX_LIGHT_RADIUS * 3 / 2 + 1]; +static sint8 vision_atten[MAX_VISION + 1][MAX_VISION * 3 / 2 + 1]; -static struct darkness_init +static struct los_init { - darkness_init () + los_init () { + assert (("QUEUE_LENGTH, MAP_CLIENT_X and MAP_CLIENT_Y *must* be powers of two", + !(QUEUE_LENGTH & (QUEUE_LENGTH - 1)))); + + /* for lights */ for (int radius = -MAX_LIGHT_RADIUS; radius <= MAX_LIGHT_RADIUS; ++radius) for (int distance = 0; distance <= MAX_LIGHT_RADIUS * 3 / 2; ++distance) { @@ -284,12 +292,17 @@ // actual intensity intensity = max (0, lerp_rd (distance, 0, abs (radius) + 1, intensity, 0)); - darkness [radius + MAX_LIGHT_RADIUS][distance] = radius < 0 + light_atten [radius + MAX_LIGHT_RADIUS][distance] = radius < 0 ? min (3, intensity) : LOS_MAX - intensity; } + + /* for general vision */ + for (int radius = 0; radius <= MAX_VISION; ++radius) + for (int distance = 0; distance <= MAX_VISION * 3 / 2; ++distance) + vision_atten [radius][distance] = distance <= radius ? clamp (lerp (radius, 0, MAX_DARKNESS, 3, 0), 0, 3) : 4; } -} darkness_init; +} los_init; sint8 los_brighten (sint8 b, sint8 l) @@ -305,16 +318,14 @@ template static void -apply_light (object *op, int dx, int dy, int light, const sint8 *darkness_table) +apply_light (player *pl, int dx, int dy, int light, const sint8 *atten_table) { // min or max the circular area around basex, basey - player *pl = op->contr; - dx += LOS_X0; dy += LOS_Y0; - int hx = op->contr->ns->mapx / 2; - int hy = op->contr->ns->mapy / 2; + int hx = pl->ns->mapx / 2; + int hy = pl->ns->mapy / 2; int ax0 = max (LOS_X0 - hx, dx - light); int ay0 = max (LOS_Y0 - hy, dy - light); @@ -324,53 +335,41 @@ for (int ax = ax0; ax <= ax1; ax++) for (int ay = ay0; ay <= ay1; ay++) pl->los[ax][ay] = - change_it (pl->los[ax][ay], darkness_table [idistance (ax - dx, ay - dy)]); + change_it (pl->los[ax][ay], atten_table [idistance (ax - dx, ay - dy)]); } /* add light, by finding all (non-null) nearby light sources, then * mark those squares specially. */ static void -apply_lights (object *op) +apply_lights (player *pl) { - int darklevel, mflags, light, x1, y1; - maptile *m = op->map; - sint16 nx, ny; - - darklevel = m->darkness; + object *op = pl->observe; + int darklevel = op->map->darklevel (); - /* If the player can see in the dark, lower the darklevel for him */ - if (QUERY_FLAG (op, FLAG_SEE_IN_DARK)) - darklevel -= LOS_MAX / 2; + int half_x = pl->ns->mapx / 2; + int half_y = pl->ns->mapy / 2; - /* Do a sanity check. If not valid, some code below may do odd - * things. - */ - if (darklevel > MAX_DARKNESS) - { - LOG (llevError, "Map darkness for %s on %s is too high (%d)\n", &op->name, &op->map->path, darklevel); - darklevel = MAX_DARKNESS; - } - - int half_x = op->contr->ns->mapx / 2; - int half_y = op->contr->ns->mapy / 2; + int pass2 = 0; // negative lights have an extra pass - int min_x = op->x - half_x - MAX_LIGHT_RADIUS; - int min_y = op->y - half_y - MAX_LIGHT_RADIUS; - int max_x = op->x + half_x + MAX_LIGHT_RADIUS; - int max_y = op->y + half_y + MAX_LIGHT_RADIUS; + maprect *rects = pl->observe->map->split_to_tiles ( + pl->observe->x - half_x - MAX_LIGHT_RADIUS, + pl->observe->y - half_y - MAX_LIGHT_RADIUS, + pl->observe->x + half_x + MAX_LIGHT_RADIUS + 1, + pl->observe->y + half_y + MAX_LIGHT_RADIUS + 1 + ); - int pass2 = 0; // negative lights have an extra pass + /* If the player can see in the dark, increase light/vision radius */ + int bonus = op->flag [FLAG_SEE_IN_DARK] ? SEE_IN_DARK_RADIUS : 0; - if (darklevel < 1) + if (!darklevel) pass2 = 1; else { /* first, make everything totally dark */ for (int dx = -half_x; dx <= half_x; dx++) for (int dy = -half_x; dy <= half_y; dy++) - if (op->contr->los[dx + LOS_X0][dy + LOS_Y0] != LOS_BLOCKED) - op->contr->los[dx + LOS_X0][dy + LOS_Y0] = LOS_MAX; + max_it (pl->los[dx + LOS_X0][dy + LOS_Y0], LOS_MAX); /* * Only process the area of interest. @@ -378,16 +377,9 @@ * array. Its easier to just increment them here (and start with the right * value) than to recalculate them down below. */ - for (int x = min_x; x <= max_x; x++) - for (int y = min_y; y <= max_y; y++) + for (maprect *r = rects; r->m; ++r) + rect_mapwalk (r, 0, 0) { - maptile *m = op->map; - sint16 nx = x; - sint16 ny = y; - - if (!xy_normalise (m, nx, ny)) - continue; - mapspace &ms = m->at (nx, ny); ms.update (); sint8 light = ms.light; @@ -396,15 +388,26 @@ if (light < 0) pass2 = 1; else - apply_light (op, x - op->x, y - op->y, light, darkness [light + MAX_LIGHT_RADIUS]); + { + light = clamp (light + bonus, 0, MAX_LIGHT_RADIUS); + apply_light (pl, dx - pl->observe->x, dy - pl->observe->y, light, light_atten [light + MAX_LIGHT_RADIUS]); + } } - /* grant some vision to the player, based on the darklevel */ - /* for outdoor maps, ensure some mininum visibility radius */ + /* grant some vision to the player, based on outside, outdoor, and darklevel */ { - int light = clamp (MAX_DARKNESS - darklevel, op->map->outdoor ? 2 : 0, MAX_LIGHT_RADIUS); + int light; + + if (!op->map->outdoor) // not outdoor, darkness becomes light radius + light = MAX_DARKNESS - op->map->darkness; + else if (op->map->darkness > 0) // outdoor and darkness > 0 => use darkness as max radius + light = lerp_rd (maptile::outdoor_darkness + 0, 0, MAX_DARKNESS, MAX_DARKNESS - op->map->darkness, 0); + else // outdoor and darkness <= 0 => start wide and decrease quickly + light = lerp (maptile::outdoor_darkness + op->map->darkness, 0, MAX_DARKNESS, MAX_VISION, 2); - apply_light (op, 0, 0, light, darkness [light + MAX_LIGHT_RADIUS]); + light = clamp (light + bonus, 0, MAX_VISION); + + apply_light (pl, 0, 0, light, vision_atten [light]); } } @@ -412,22 +415,18 @@ // for effect, those are always considered to be stronger than anything else // but they can't darken a place completely if (pass2) - for (int x = min_x; x <= max_x; x++) - for (int y = min_y; y <= max_y; y++) + for (maprect *r = rects; r->m; ++r) + rect_mapwalk (r, 0, 0) { - maptile *m = op->map; - sint16 nx = x; - sint16 ny = y; - - if (!xy_normalise (m, nx, ny)) - continue; - mapspace &ms = m->at (nx, ny); ms.update (); sint8 light = ms.light; if (expect_false (light < 0)) - apply_light (op, x - op->x, y - op->y, -light, darkness [light + MAX_LIGHT_RADIUS]); + { + light = clamp (light - bonus, 0, MAX_DARKNESS); + apply_light (pl, dx - pl->observe->x, dy - pl->observe->y, -light, light_atten [light + MAX_LIGHT_RADIUS]); + } } } @@ -437,9 +436,9 @@ * really need for any reasonable game play. */ static void -blinded_sight (object *op) +blinded_sight (player *pl) { - op->contr->los[LOS_X0][LOS_Y0] = 3; + pl->los[LOS_X0][LOS_Y0] = 1; } /* @@ -447,27 +446,29 @@ * visible for the given player-object. */ void -update_los (object *op) +player::update_los () { - if (QUERY_FLAG (op, FLAG_REMOVED)) + if (ob->flag [FLAG_REMOVED])//D really needed? return; - op->contr->clear_los (); - - if (QUERY_FLAG (op, FLAG_WIZ) /* ||XRAYS(op) */ ) - memset (op->contr->los, 0, sizeof (op->contr->los)); - else if (QUERY_FLAG (op, FLAG_BLIND)) /* player is blind */ - blinded_sight (op); + if (ob->flag [FLAG_WIZLOOK]) + clear_los (0); + else if (observe->flag [FLAG_BLIND]) /* player is blind */ + { + clear_los (); + blinded_sight (this); + } else { - do_los (op); - apply_lights (op); + clear_los (); + calculate_los (this); + apply_lights (this); } - if (QUERY_FLAG (op, FLAG_XRAYS)) + if (observe->flag [FLAG_XRAYS]) for (int dx = -2; dx <= 2; dx++) for (int dy = -2; dy <= 2; dy++) - op->contr->los[dx + LOS_X0][dy + LOS_X0] = 0; + min_it (los[dx + LOS_X0][dy + LOS_Y0], 1); } /* update all_map_los is like update_all_los below, @@ -484,9 +485,8 @@ void update_all_map_los (maptile *map) { - for_all_players (pl) - if (pl->ob && pl->ob->map == map) - pl->do_los = 1; + for_all_players_on_map (pl, map) + pl->do_los = 1; } /* @@ -504,6 +504,12 @@ void update_all_los (const maptile *map, int x, int y) { + // no need to do anything if we don't have darkness + if (map->darklevel () <= 0) + return; + + map->at (x, y).invalidate (); + for_all_players (pl) { /* Player should not have a null map, but do this @@ -522,10 +528,8 @@ * some. */ if (pl->ob->map == map) - { - if ((abs (pl->ob->x - x) <= pl->ns->mapx / 2) && (abs (pl->ob->y - y) <= pl->ns->mapy / 2)) - pl->do_los = 1; - } + if ((abs (pl->ob->x - x) <= pl->ns->mapx / 2) && (abs (pl->ob->y - y) <= pl->ns->mapy / 2)) + pl->do_los = 1; /* Now we check to see if player is on adjacent * maps to the one that changed and also within @@ -565,6 +569,57 @@ } } +static const int season_darkness[5][HOURS_PER_DAY] = { + /*0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 2 3 4 5 6 7 8 9 10 11 12 13 */ + { 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 1, 2, 2, 2, 3, 3, 4, 4, 5 }, + { 5, 5, 4, 4, 4, 4, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 }, + { 5, 4, 4, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 4 }, + { 4, 4, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 4 }, + { 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4 } +}; + +/* + * Tell players the time and compute the darkness level for all maps in the game. + * MUST be called exactly once per hour. + */ +void +maptile::adjust_daylight () +{ + timeofday_t tod; + + get_tod (&tod); + + // log the time to log-1 every hour, and to chat every day + { + char todbuf[512]; + + format_tod (todbuf, sizeof (todbuf), &tod); + + for_all_players (pl) + pl->ns->send_msg (NDI_GREY, tod.hour == 15 ? CHAT_CHANNEL : LOG_CHANNEL, todbuf); + } + + /* If the light level isn't changing, no reason to do all + * the work below. + */ + sint8 new_darkness = season_darkness[tod.season][tod.hour]; + + if (new_darkness == maptile::outdoor_darkness) + return; + + new_draw_info (NDI_GREY | NDI_UNIQUE | NDI_ALL, 1, 0, + new_darkness > maptile::outdoor_darkness + ? "It becomes darker." + : "It becomes brighter."); + + maptile::outdoor_darkness = new_darkness; + + // we simply update the los for all players, which is unnecessarily + // costly, but should do for the moment. + for_all_players (pl) + pl->do_los = 1; +} + /* * make_sure_seen: The object is supposed to be visible through walls, thus * check if any players are nearby, and edit their LOS array. @@ -576,7 +631,7 @@ if (pl->ob->map == op->map && pl->ob->y - pl->ns->mapy / 2 <= op->y && pl->ob->y + pl->ns->mapy / 2 >= op->y && pl->ob->x - pl->ns->mapx / 2 <= op->x && pl->ob->x + pl->ns->mapx / 2 >= op->x) - pl->los[op->x - pl->ob->x + LOS_X0][op->y - pl->ob->y + LOS_X0] = 0; + pl->los[op->x - pl->ob->x + LOS_X0][op->y - pl->ob->y + LOS_Y0] = 0; } /* @@ -593,3 +648,4 @@ pl->ob->y + pl->ns->mapy / 2 >= op->y && pl->ob->x - pl->ns->mapx / 2 <= op->x && pl->ob->x + pl->ns->mapx / 2 >= op->x) pl->do_los = 1; } +