… | |
… | |
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); |
… | |
… | |
268 | return 0; |
266 | return 0; |
269 | } |
267 | } |
270 | |
268 | |
271 | /* radius, distance => lightness adjust */ |
269 | /* radius, distance => lightness adjust */ |
272 | static sint8 light_atten[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 los_init |
273 | static struct los_init |
275 | { |
274 | { |
276 | los_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); |
… | |
… | |
286 | |
286 | |
287 | light_atten [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 | } los_init; |
299 | } los_init; |
293 | |
300 | |
294 | sint8 |
301 | sint8 |
295 | los_brighten (sint8 b, sint8 l) |
302 | los_brighten (sint8 b, sint8 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 *atten_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); |
… | |
… | |
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, light_atten [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 | { |
396 | { |
404 | int light = clamp (MAX_DARKNESS - darklevel, 0, MAX_LIGHT_RADIUS); |
397 | int light = clamp (MAX_DARKNESS - darklevel, 0, MAX_DARKNESS); |
405 | |
398 | |
406 | apply_light<los_brighten> (op, 0, 0, light, light_atten [light + MAX_LIGHT_RADIUS]); |
399 | apply_light<los_brighten> (pl, 0, 0, light, vision_atten [light]); |
407 | } |
400 | } |
408 | } |
401 | } |
409 | |
402 | |
410 | // possibly do 2nd pass for rare negative glow radii |
403 | // possibly do 2nd pass for rare negative glow radii |
411 | // 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 |
412 | // but they can't darken a place completely |
405 | // but they can't darken a place completely |
413 | if (pass2) |
406 | if (pass2) |
414 | for (int x = min_x; x <= max_x; x++) |
407 | for (int x = min_x; x <= max_x; x++) |
415 | for (int y = min_y; y <= max_y; y++) |
408 | for (int y = min_y; y <= max_y; y++) |
416 | { |
409 | { |
417 | maptile *m = op->map; |
410 | maptile *m = pl->observe->map; |
418 | sint16 nx = x; |
411 | sint16 nx = x; |
419 | sint16 ny = y; |
412 | sint16 ny = y; |
420 | |
413 | |
421 | if (!xy_normalise (m, nx, ny)) |
414 | if (!xy_normalise (m, nx, ny)) |
422 | continue; |
415 | continue; |
… | |
… | |
424 | mapspace &ms = m->at (nx, ny); |
417 | mapspace &ms = m->at (nx, ny); |
425 | ms.update (); |
418 | ms.update (); |
426 | sint8 light = ms.light; |
419 | sint8 light = ms.light; |
427 | |
420 | |
428 | if (expect_false (light < 0)) |
421 | if (expect_false (light < 0)) |
429 | apply_light<los_darken> (op, x - op->x, y - op->y, -light, light_atten [light + MAX_LIGHT_RADIUS]); |
422 | apply_light<los_darken> (pl, x - op->x, y - op->y, -light, light_atten [light + MAX_LIGHT_RADIUS]); |
430 | } |
423 | } |
431 | } |
424 | } |
432 | |
425 | |
433 | /* blinded_sight() - sets all viewable squares to blocked except |
426 | /* blinded_sight() - sets all viewable squares to blocked except |
434 | * 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 |
435 | * 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 |
436 | * really need for any reasonable game play. |
429 | * really need for any reasonable game play. |
437 | */ |
430 | */ |
438 | static void |
431 | static void |
439 | blinded_sight (object *op) |
432 | blinded_sight (player *pl) |
440 | { |
433 | { |
441 | op->contr->los[LOS_X0][LOS_Y0] = 3; |
434 | pl->los[LOS_X0][LOS_Y0] = 1; |
442 | } |
435 | } |
443 | |
436 | |
444 | /* |
437 | /* |
445 | * update_los() recalculates the array which specifies what is |
438 | * update_los() recalculates the array which specifies what is |
446 | * visible for the given player-object. |
439 | * visible for the given player-object. |
447 | */ |
440 | */ |
448 | void |
441 | void |
449 | update_los (object *op) |
442 | player::update_los () |
450 | { |
443 | { |
451 | if (QUERY_FLAG (op, FLAG_REMOVED)) |
444 | if (ob->flag [FLAG_REMOVED])//D really needed? |
452 | return; |
445 | return; |
453 | |
446 | |
454 | op->contr->clear_los (); |
447 | clear_los (); |
455 | |
448 | |
456 | if (QUERY_FLAG (op, FLAG_WIZ) /* ||XRAYS(op) */ ) |
449 | if (ob->flag [FLAG_WIZLOOK]) |
457 | memset (op->contr->los, 0, sizeof (op->contr->los)); |
450 | memset (los, 0, sizeof (los)); |
458 | else if (QUERY_FLAG (op, FLAG_BLIND)) /* player is blind */ |
451 | else if (observe->flag [FLAG_BLIND]) /* player is blind */ |
459 | blinded_sight (op); |
452 | blinded_sight (this); |
460 | else |
453 | else |
461 | { |
454 | { |
462 | do_los (op); |
455 | calculate_los (this); |
463 | apply_lights (op); |
456 | apply_lights (this); |
464 | } |
457 | } |
465 | |
458 | |
466 | if (QUERY_FLAG (op, FLAG_XRAYS)) |
459 | if (observe->flag [FLAG_XRAYS]) |
467 | for (int dx = -2; dx <= 2; dx++) |
460 | for (int dx = -2; dx <= 2; dx++) |
468 | for (int dy = -2; dy <= 2; dy++) |
461 | for (int dy = -2; dy <= 2; dy++) |
469 | op->contr->los[dx + LOS_X0][dy + LOS_X0] = 0; |
462 | min_it (los[dx + LOS_X0][dy + LOS_X0], 1); |
470 | } |
463 | } |
471 | |
464 | |
472 | /* update all_map_los is like update_all_los below, |
465 | /* update all_map_los is like update_all_los below, |
473 | * but updates everyone on the map, no matter where they |
466 | * but updates everyone on the map, no matter where they |
474 | * are. This generally should not be used, as a per |
467 | * are. This generally should not be used, as a per |
… | |
… | |
481 | * change_map_light function |
474 | * change_map_light function |
482 | */ |
475 | */ |
483 | void |
476 | void |
484 | update_all_map_los (maptile *map) |
477 | update_all_map_los (maptile *map) |
485 | { |
478 | { |
486 | for_all_players (pl) |
479 | for_all_players_on_map (pl, map) |
487 | if (pl->ob && pl->ob->map == map) |
|
|
488 | pl->do_los = 1; |
480 | pl->do_los = 1; |
489 | } |
481 | } |
490 | |
482 | |
491 | /* |
483 | /* |
492 | * 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 |
493 | * players on the given map within the next frame. |
485 | * players on the given map within the next frame. |
… | |
… | |
501 | * 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. |
502 | */ |
494 | */ |
503 | void |
495 | void |
504 | update_all_los (const maptile *map, int x, int y) |
496 | update_all_los (const maptile *map, int x, int y) |
505 | { |
497 | { |
|
|
498 | map->at (x, y).invalidate (); |
|
|
499 | |
506 | for_all_players (pl) |
500 | for_all_players (pl) |
507 | { |
501 | { |
508 | /* Player should not have a null map, but do this |
502 | /* Player should not have a null map, but do this |
509 | * check as a safety |
503 | * check as a safety |
510 | */ |
504 | */ |
… | |
… | |
562 | pl->do_los = 1; |
556 | pl->do_los = 1; |
563 | } |
557 | } |
564 | } |
558 | } |
565 | } |
559 | } |
566 | |
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 | |
567 | /* |
612 | /* |
568 | * 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 |
569 | * check if any players are nearby, and edit their LOS array. |
614 | * check if any players are nearby, and edit their LOS array. |
570 | */ |
615 | */ |
571 | void |
616 | void |