… | |
… | |
99 | // this is a variant of a spiral los algorithm taken from |
99 | // this is a variant of a spiral los algorithm taken from |
100 | // http://www.geocities.com/temerra/los_rays.html |
100 | // http://www.geocities.com/temerra/los_rays.html |
101 | // which has been simplified and changed considerably, but |
101 | // which has been simplified and changed considerably, but |
102 | // still is basically the same algorithm. |
102 | // still is basically the same algorithm. |
103 | static void |
103 | static void |
104 | do_los (object *op) |
104 | calculate_los (player *pl) |
105 | { |
105 | { |
106 | player *pl = op->contr; |
|
|
107 | |
|
|
108 | int max_radius = max (pl->ns->mapx, pl->ns->mapy) / 2; |
106 | int max_radius = max (pl->ns->mapx, pl->ns->mapy) / 2; |
109 | |
107 | |
110 | memset (los, 0, sizeof (los)); |
108 | memset (los, 0, sizeof (los)); |
111 | |
109 | |
112 | q1 = 0; q2 = 0; // initialise queue, not strictly required |
110 | q1 = 0; q2 = 0; // initialise queue, not strictly required |
… | |
… | |
208 | } |
206 | } |
209 | } |
207 | } |
210 | } |
208 | } |
211 | |
209 | |
212 | // check whether this space blocks the view |
210 | // check whether this space blocks the view |
213 | maptile *m = op->map; |
211 | maptile *m = pl->observe->map; |
214 | sint16 nx = op->x + dx; |
212 | sint16 nx = pl->observe->x + dx; |
215 | sint16 ny = op->y + dy; |
213 | sint16 ny = pl->observe->y + dy; |
216 | |
214 | |
217 | if (expect_true (!xy_normalise (m, nx, ny)) |
215 | if (expect_true (!xy_normalise (m, nx, ny)) |
218 | || expect_false (m->at (nx, ny).flags () & P_BLOCKSVIEW)) |
216 | || expect_false (m->at (nx, ny).flags () & P_BLOCKSVIEW)) |
219 | { |
217 | { |
220 | l.xo = l.xe = abs (dx); |
218 | l.xo = l.xe = abs (dx); |
… | |
… | |
267 | |
265 | |
268 | return 0; |
266 | return 0; |
269 | } |
267 | } |
270 | |
268 | |
271 | /* radius, distance => lightness adjust */ |
269 | /* radius, distance => lightness adjust */ |
272 | static sint8 darkness[MAX_LIGHT_RADIUS * 2 + 1][MAX_LIGHT_RADIUS * 3 / 2 + 1]; |
270 | static sint8 light_atten[MAX_LIGHT_RADIUS * 2 + 1][MAX_LIGHT_RADIUS * 3 / 2 + 1]; |
|
|
271 | static sint8 vision_atten[MAX_DARKNESS + 1][MAX_DARKNESS * 3 / 2 + 1]; |
273 | |
272 | |
274 | static struct darkness_init |
273 | static struct los_init |
275 | { |
274 | { |
276 | darkness_init () |
275 | los_init () |
277 | { |
276 | { |
|
|
277 | /* for lights */ |
278 | for (int radius = -MAX_LIGHT_RADIUS; radius <= MAX_LIGHT_RADIUS; ++radius) |
278 | for (int radius = -MAX_LIGHT_RADIUS; radius <= MAX_LIGHT_RADIUS; ++radius) |
279 | for (int distance = 0; distance <= MAX_LIGHT_RADIUS * 3 / 2; ++distance) |
279 | for (int distance = 0; distance <= MAX_LIGHT_RADIUS * 3 / 2; ++distance) |
280 | { |
280 | { |
281 | // max intensity |
281 | // max intensity |
282 | int intensity = min (LOS_MAX, abs (radius) + 1); |
282 | int intensity = min (LOS_MAX, abs (radius) + 1); |
283 | |
283 | |
284 | // actual intensity |
284 | // actual intensity |
285 | intensity = max (0, lerp_rd (distance, 0, abs (radius) + 1, intensity, 0)); |
285 | intensity = max (0, lerp_rd (distance, 0, abs (radius) + 1, intensity, 0)); |
286 | |
286 | |
287 | darkness [radius + MAX_LIGHT_RADIUS][distance] = radius < 0 |
287 | light_atten [radius + MAX_LIGHT_RADIUS][distance] = radius < 0 |
288 | ? min (3, intensity) |
288 | ? min (3, intensity) |
289 | : LOS_MAX - intensity; |
289 | : LOS_MAX - intensity; |
290 | } |
290 | } |
|
|
291 | |
|
|
292 | /* for general vision */ |
|
|
293 | for (int radius = 0; radius <= MAX_DARKNESS; ++radius) |
|
|
294 | for (int distance = 0; distance <= MAX_DARKNESS * 3 / 2; ++distance) |
|
|
295 | { |
|
|
296 | vision_atten [radius][distance] = distance <= radius ? 3 : 4; |
|
|
297 | } |
291 | } |
298 | } |
292 | } darkness_init; |
299 | } los_init; |
293 | |
300 | |
294 | sint8 |
301 | sint8 |
295 | los_brighten (sint8 b, sint8 l) |
302 | los_brighten (sint8 b, sint8 l) |
296 | { |
303 | { |
297 | return b == LOS_BLOCKED ? b : min (b, l); |
304 | return b == LOS_BLOCKED ? b : min (b, l); |
… | |
… | |
303 | return max (b, l); |
310 | return max (b, l); |
304 | } |
311 | } |
305 | |
312 | |
306 | template<sint8 change_it (sint8, sint8)> |
313 | template<sint8 change_it (sint8, sint8)> |
307 | static void |
314 | static void |
308 | apply_light (object *op, int dx, int dy, int light, const sint8 *darkness_table) |
315 | apply_light (player *pl, int dx, int dy, int light, const sint8 *atten_table) |
309 | { |
316 | { |
310 | // min or max the circular area around basex, basey |
317 | // min or max the circular area around basex, basey |
311 | player *pl = op->contr; |
|
|
312 | |
|
|
313 | dx += LOS_X0; |
318 | dx += LOS_X0; |
314 | dy += LOS_Y0; |
319 | dy += LOS_Y0; |
315 | |
320 | |
316 | int hx = op->contr->ns->mapx / 2; |
321 | int hx = pl->ns->mapx / 2; |
317 | int hy = op->contr->ns->mapy / 2; |
322 | int hy = pl->ns->mapy / 2; |
318 | |
323 | |
319 | int ax0 = max (LOS_X0 - hx, dx - light); |
324 | int ax0 = max (LOS_X0 - hx, dx - light); |
320 | int ay0 = max (LOS_Y0 - hy, dy - light); |
325 | int ay0 = max (LOS_Y0 - hy, dy - light); |
321 | int ax1 = min (dx + light, LOS_X0 + hx); |
326 | int ax1 = min (dx + light, LOS_X0 + hx); |
322 | int ay1 = min (dy + light, LOS_Y0 + hy); |
327 | int ay1 = min (dy + light, LOS_Y0 + hy); |
323 | |
328 | |
324 | for (int ax = ax0; ax <= ax1; ax++) |
329 | for (int ax = ax0; ax <= ax1; ax++) |
325 | for (int ay = ay0; ay <= ay1; ay++) |
330 | for (int ay = ay0; ay <= ay1; ay++) |
326 | pl->los[ax][ay] = |
331 | pl->los[ax][ay] = |
327 | change_it (pl->los[ax][ay], darkness_table [idistance (ax - dx, ay - dy)]); |
332 | change_it (pl->los[ax][ay], atten_table [idistance (ax - dx, ay - dy)]); |
328 | } |
333 | } |
329 | |
334 | |
330 | /* add light, by finding all (non-null) nearby light sources, then |
335 | /* add light, by finding all (non-null) nearby light sources, then |
331 | * mark those squares specially. |
336 | * mark those squares specially. |
332 | */ |
337 | */ |
333 | static void |
338 | static void |
334 | apply_lights (object *op) |
339 | apply_lights (player *pl) |
335 | { |
340 | { |
336 | int darklevel, mflags, light, x1, y1; |
341 | object *op = pl->observe; |
337 | maptile *m = op->map; |
342 | int darklevel = op->map->darklevel (); |
338 | sint16 nx, ny; |
|
|
339 | |
|
|
340 | darklevel = m->darkness; |
|
|
341 | |
343 | |
342 | /* If the player can see in the dark, lower the darklevel for him */ |
344 | /* If the player can see in the dark, lower the darklevel for him */ |
343 | if (QUERY_FLAG (op, FLAG_SEE_IN_DARK)) |
345 | if (op->flag [FLAG_SEE_IN_DARK]) |
344 | darklevel -= LOS_MAX / 2; |
346 | darklevel = max (0, darklevel - 2); |
345 | |
347 | |
346 | /* Do a sanity check. If not valid, some code below may do odd |
|
|
347 | * things. |
|
|
348 | */ |
|
|
349 | if (darklevel > MAX_DARKNESS) |
|
|
350 | { |
|
|
351 | LOG (llevError, "Map darkness for %s on %s is too high (%d)\n", &op->name, &op->map->path, darklevel); |
|
|
352 | darklevel = MAX_DARKNESS; |
|
|
353 | } |
|
|
354 | |
|
|
355 | int half_x = op->contr->ns->mapx / 2; |
348 | int half_x = pl->ns->mapx / 2; |
356 | int half_y = op->contr->ns->mapy / 2; |
349 | int half_y = pl->ns->mapy / 2; |
357 | |
350 | |
358 | int min_x = op->x - half_x - MAX_LIGHT_RADIUS; |
351 | int min_x = op->x - half_x - MAX_LIGHT_RADIUS; |
359 | int min_y = op->y - half_y - MAX_LIGHT_RADIUS; |
352 | int min_y = op->y - half_y - MAX_LIGHT_RADIUS; |
360 | int max_x = op->x + half_x + MAX_LIGHT_RADIUS; |
353 | int max_x = op->x + half_x + MAX_LIGHT_RADIUS; |
361 | int max_y = op->y + half_y + MAX_LIGHT_RADIUS; |
354 | int max_y = op->y + half_y + MAX_LIGHT_RADIUS; |
362 | |
355 | |
363 | int pass2 = 0; // negative lights have an extra pass |
356 | int pass2 = 0; // negative lights have an extra pass |
364 | |
357 | |
365 | if (darklevel < 1) |
358 | if (!darklevel) |
366 | pass2 = 1; |
359 | pass2 = 1; |
367 | else |
360 | else |
368 | { |
361 | { |
369 | /* first, make everything totally dark */ |
362 | /* first, make everything totally dark */ |
370 | for (int dx = -half_x; dx <= half_x; dx++) |
363 | for (int dx = -half_x; dx <= half_x; dx++) |
371 | for (int dy = -half_x; dy <= half_y; dy++) |
364 | for (int dy = -half_x; dy <= half_y; dy++) |
372 | if (op->contr->los[dx + LOS_X0][dy + LOS_Y0] != LOS_BLOCKED) |
365 | if (pl->los[dx + LOS_X0][dy + LOS_Y0] != LOS_BLOCKED) |
373 | op->contr->los[dx + LOS_X0][dy + LOS_Y0] = LOS_MAX; |
366 | pl->los[dx + LOS_X0][dy + LOS_Y0] = LOS_MAX; |
374 | |
367 | |
375 | /* |
368 | /* |
376 | * Only process the area of interest. |
369 | * Only process the area of interest. |
377 | * the basex, basey values represent the position in the op->contr->los |
370 | * the basex, basey values represent the position in the op->contr->los |
378 | * array. Its easier to just increment them here (and start with the right |
371 | * array. Its easier to just increment them here (and start with the right |
379 | * value) than to recalculate them down below. |
372 | * value) than to recalculate them down below. |
380 | */ |
373 | */ |
381 | for (int x = min_x; x <= max_x; x++) |
374 | for (int x = min_x; x <= max_x; x++) |
382 | for (int y = min_y; y <= max_y; y++) |
375 | for (int y = min_y; y <= max_y; y++) |
383 | { |
376 | { |
384 | maptile *m = op->map; |
377 | maptile *m = pl->observe->map; |
385 | sint16 nx = x; |
378 | sint16 nx = x; |
386 | sint16 ny = y; |
379 | sint16 ny = y; |
387 | |
380 | |
388 | if (!xy_normalise (m, nx, ny)) |
381 | if (!xy_normalise (m, nx, ny)) |
389 | continue; |
382 | continue; |
… | |
… | |
394 | |
387 | |
395 | if (expect_false (light)) |
388 | if (expect_false (light)) |
396 | if (light < 0) |
389 | if (light < 0) |
397 | pass2 = 1; |
390 | pass2 = 1; |
398 | else |
391 | else |
399 | apply_light<los_brighten> (op, x - op->x, y - op->y, light, darkness [light + MAX_LIGHT_RADIUS]); |
392 | apply_light<los_brighten> (pl, x - op->x, y - op->y, light, light_atten [light + MAX_LIGHT_RADIUS]); |
400 | } |
393 | } |
401 | |
394 | |
402 | /* grant some vision to the player, based on the darklevel */ |
395 | /* grant some vision to the player, based on the darklevel */ |
403 | /* for outdoor maps, ensure some mininum visibility radius */ |
|
|
404 | { |
396 | { |
405 | int light = clamp (MAX_DARKNESS - darklevel, op->map->outdoor ? 2 : 0, MAX_LIGHT_RADIUS); |
397 | int light = clamp (MAX_DARKNESS - darklevel, 0, MAX_DARKNESS); |
406 | |
398 | |
407 | apply_light<los_brighten> (op, 0, 0, light, darkness [light + MAX_LIGHT_RADIUS]); |
399 | apply_light<los_brighten> (pl, 0, 0, light, vision_atten [light]); |
408 | } |
400 | } |
409 | } |
401 | } |
410 | |
402 | |
411 | // possibly do 2nd pass for rare negative glow radii |
403 | // possibly do 2nd pass for rare negative glow radii |
412 | // for effect, those are always considered to be stronger than anything else |
404 | // for effect, those are always considered to be stronger than anything else |
413 | // but they can't darken a place completely |
405 | // but they can't darken a place completely |
414 | if (pass2) |
406 | if (pass2) |
415 | for (int x = min_x; x <= max_x; x++) |
407 | for (int x = min_x; x <= max_x; x++) |
416 | for (int y = min_y; y <= max_y; y++) |
408 | for (int y = min_y; y <= max_y; y++) |
417 | { |
409 | { |
418 | maptile *m = op->map; |
410 | maptile *m = pl->observe->map; |
419 | sint16 nx = x; |
411 | sint16 nx = x; |
420 | sint16 ny = y; |
412 | sint16 ny = y; |
421 | |
413 | |
422 | if (!xy_normalise (m, nx, ny)) |
414 | if (!xy_normalise (m, nx, ny)) |
423 | continue; |
415 | continue; |
… | |
… | |
425 | mapspace &ms = m->at (nx, ny); |
417 | mapspace &ms = m->at (nx, ny); |
426 | ms.update (); |
418 | ms.update (); |
427 | sint8 light = ms.light; |
419 | sint8 light = ms.light; |
428 | |
420 | |
429 | if (expect_false (light < 0)) |
421 | if (expect_false (light < 0)) |
430 | apply_light<los_darken> (op, x - op->x, y - op->y, -light, darkness [light + MAX_LIGHT_RADIUS]); |
422 | apply_light<los_darken> (pl, x - op->x, y - op->y, -light, light_atten [light + MAX_LIGHT_RADIUS]); |
431 | } |
423 | } |
432 | } |
424 | } |
433 | |
425 | |
434 | /* blinded_sight() - sets all viewable squares to blocked except |
426 | /* blinded_sight() - sets all viewable squares to blocked except |
435 | * for the one the central one that the player occupies. A little |
427 | * for the one the central one that the player occupies. A little |
436 | * odd that you can see yourself (and what your standing on), but |
428 | * odd that you can see yourself (and what your standing on), but |
437 | * really need for any reasonable game play. |
429 | * really need for any reasonable game play. |
438 | */ |
430 | */ |
439 | static void |
431 | static void |
440 | blinded_sight (object *op) |
432 | blinded_sight (player *pl) |
441 | { |
433 | { |
442 | op->contr->los[LOS_X0][LOS_Y0] = 3; |
434 | pl->los[LOS_X0][LOS_Y0] = 1; |
443 | } |
435 | } |
444 | |
436 | |
445 | /* |
437 | /* |
446 | * update_los() recalculates the array which specifies what is |
438 | * update_los() recalculates the array which specifies what is |
447 | * visible for the given player-object. |
439 | * visible for the given player-object. |
448 | */ |
440 | */ |
449 | void |
441 | void |
450 | update_los (object *op) |
442 | player::update_los () |
451 | { |
443 | { |
452 | if (QUERY_FLAG (op, FLAG_REMOVED)) |
444 | if (ob->flag [FLAG_REMOVED])//D really needed? |
453 | return; |
445 | return; |
454 | |
446 | |
455 | op->contr->clear_los (); |
447 | clear_los (); |
456 | |
448 | |
457 | if (QUERY_FLAG (op, FLAG_WIZ) /* ||XRAYS(op) */ ) |
449 | if (ob->flag [FLAG_WIZLOOK]) |
458 | memset (op->contr->los, 0, sizeof (op->contr->los)); |
450 | memset (los, 0, sizeof (los)); |
459 | else if (QUERY_FLAG (op, FLAG_BLIND)) /* player is blind */ |
451 | else if (observe->flag [FLAG_BLIND]) /* player is blind */ |
460 | blinded_sight (op); |
452 | blinded_sight (this); |
461 | else |
453 | else |
462 | { |
454 | { |
463 | do_los (op); |
455 | calculate_los (this); |
464 | apply_lights (op); |
456 | apply_lights (this); |
465 | } |
457 | } |
466 | |
458 | |
467 | if (QUERY_FLAG (op, FLAG_XRAYS)) |
459 | if (observe->flag [FLAG_XRAYS]) |
468 | for (int dx = -2; dx <= 2; dx++) |
460 | for (int dx = -2; dx <= 2; dx++) |
469 | for (int dy = -2; dy <= 2; dy++) |
461 | for (int dy = -2; dy <= 2; dy++) |
470 | op->contr->los[dx + LOS_X0][dy + LOS_X0] = 0; |
462 | min_it (los[dx + LOS_X0][dy + LOS_X0], 1); |
471 | } |
463 | } |
472 | |
464 | |
473 | /* update all_map_los is like update_all_los below, |
465 | /* update all_map_los is like update_all_los below, |
474 | * but updates everyone on the map, no matter where they |
466 | * but updates everyone on the map, no matter where they |
475 | * are. This generally should not be used, as a per |
467 | * are. This generally should not be used, as a per |
… | |
… | |
482 | * change_map_light function |
474 | * change_map_light function |
483 | */ |
475 | */ |
484 | void |
476 | void |
485 | update_all_map_los (maptile *map) |
477 | update_all_map_los (maptile *map) |
486 | { |
478 | { |
487 | for_all_players (pl) |
479 | for_all_players_on_map (pl, map) |
488 | if (pl->ob && pl->ob->map == map) |
|
|
489 | pl->do_los = 1; |
480 | pl->do_los = 1; |
490 | } |
481 | } |
491 | |
482 | |
492 | /* |
483 | /* |
493 | * This function makes sure that update_los() will be called for all |
484 | * This function makes sure that update_los() will be called for all |
494 | * players on the given map within the next frame. |
485 | * players on the given map within the next frame. |
… | |
… | |
502 | * map is the map that changed, x and y are the coordinates. |
493 | * map is the map that changed, x and y are the coordinates. |
503 | */ |
494 | */ |
504 | void |
495 | void |
505 | update_all_los (const maptile *map, int x, int y) |
496 | update_all_los (const maptile *map, int x, int y) |
506 | { |
497 | { |
|
|
498 | map->at (x, y).invalidate (); |
|
|
499 | |
507 | for_all_players (pl) |
500 | for_all_players (pl) |
508 | { |
501 | { |
509 | /* Player should not have a null map, but do this |
502 | /* Player should not have a null map, but do this |
510 | * check as a safety |
503 | * check as a safety |
511 | */ |
504 | */ |
… | |
… | |
563 | pl->do_los = 1; |
556 | pl->do_los = 1; |
564 | } |
557 | } |
565 | } |
558 | } |
566 | } |
559 | } |
567 | |
560 | |
|
|
561 | static const int season_darkness[5][HOURS_PER_DAY] = { |
|
|
562 | /*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 */ |
|
|
563 | { 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 }, |
|
|
564 | { 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 }, |
|
|
565 | { 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 }, |
|
|
566 | { 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 }, |
|
|
567 | { 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 } |
|
|
568 | }; |
|
|
569 | |
|
|
570 | /* |
|
|
571 | * Tell players the time and compute the darkness level for all maps in the game. |
|
|
572 | * MUST be called exactly once per hour. |
|
|
573 | */ |
|
|
574 | void |
|
|
575 | maptile::adjust_daylight () |
|
|
576 | { |
|
|
577 | timeofday_t tod; |
|
|
578 | |
|
|
579 | get_tod (&tod); |
|
|
580 | |
|
|
581 | // log the time to log-1 every hour, and to chat every day |
|
|
582 | { |
|
|
583 | char todbuf[512]; |
|
|
584 | |
|
|
585 | format_tod (todbuf, sizeof (todbuf), &tod); |
|
|
586 | |
|
|
587 | for_all_players (pl) |
|
|
588 | pl->ns->send_msg (NDI_GREY, tod.hour == 15 ? CHAT_CHANNEL : LOG_CHANNEL, todbuf); |
|
|
589 | } |
|
|
590 | |
|
|
591 | /* If the light level isn't changing, no reason to do all |
|
|
592 | * the work below. |
|
|
593 | */ |
|
|
594 | sint8 new_darkness = season_darkness[tod.season][tod.hour]; |
|
|
595 | |
|
|
596 | if (new_darkness == maptile::outdoor_darkness) |
|
|
597 | return; |
|
|
598 | |
|
|
599 | new_draw_info (NDI_GREY | NDI_UNIQUE | NDI_ALL, 1, 0, |
|
|
600 | new_darkness > maptile::outdoor_darkness |
|
|
601 | ? "It becomes darker." |
|
|
602 | : "It becomes brighter."); |
|
|
603 | |
|
|
604 | maptile::outdoor_darkness = new_darkness; |
|
|
605 | |
|
|
606 | // we simply update the los for all players, which is unnecessarily |
|
|
607 | // costly, but should do for the moment. |
|
|
608 | for_all_players (pl) |
|
|
609 | pl->do_los = 1; |
|
|
610 | } |
|
|
611 | |
568 | /* |
612 | /* |
569 | * make_sure_seen: The object is supposed to be visible through walls, thus |
613 | * make_sure_seen: The object is supposed to be visible through walls, thus |
570 | * check if any players are nearby, and edit their LOS array. |
614 | * check if any players are nearby, and edit their LOS array. |
571 | */ |
615 | */ |
572 | void |
616 | void |