1 | /* SCCS Id: @(#)light.c 3.3 97/04/10 */ 2 | /* Copyright (c) Dean Luick, 1994 */ 3 | /* NetHack may be freely redistributed. See license for details. */ 4 | 5 | #include "hack.h" 6 | #include "lev.h" /* for checking save modes */ 7 | 8 | /* 9 | * Mobile light sources. 10 | * 11 | * This implementation minimizes memory at the expense of extra 12 | * recalculations. 13 | * 14 | * Light sources are "things" that have a physical position and range. 15 | * They have a type, which gives us information about them. Currently 16 | * they are only attached to objects and monsters. Note well: the 17 | * polymorphed-player handling assumes that both youmonst.m_id and 18 | * youmonst.mx will always remain 0. 19 | * 20 | * Light sources, like timers, either follow game play (RANGE_GLOBAL) or 21 | * stay on a level (RANGE_LEVEL). Light sources are unique by their 22 | * (type, id) pair. For light sources attached to objects, this id 23 | * is a pointer to the object. 24 | * 25 | * The major working function is do_light_sources(). It is called 26 | * when the vision system is recreating its "could see" array. Here 27 | * we add a flag (TEMP_LIT) to the array for all locations that are lit 28 | * via a light source. The bad part of this is that we have to 29 | * re-calculate the LOS of each light source every time the vision 30 | * system runs. Even if the light sources and any topology (vision blocking 31 | * positions) have not changed. The good part is that no extra memory 32 | * is used, plus we don't have to figure out how far the sources have moved, 33 | * or if the topology has changed. 34 | * 35 | * The structure of the save/restore mechanism is amazingly similar to 36 | * the timer save/restore. This is because they both have the same 37 | * principals of having pointers into objects that must be recalculated 38 | * across saves and restores. 39 | */ 40 | 41 | #ifdef OVL3 42 | 43 | typedef struct ls_t { 44 | struct ls_t *next; 45 | xchar x, y; /* source's position */ 46 | short range; /* source's current range */ 47 | short flags; 48 | short type; /* type of light source */ 49 | genericptr_t id; /* source's identifier */ 50 | } light_source; 51 | 52 | /* flags */ 53 | #define LSF_SHOW 0x1 /* display the light source */ 54 | #define LSF_NEEDS_FIXUP 0x2 /* need oid fixup */ 55 | 56 | static light_source *light_base = 0; 57 | 58 | STATIC_DCL void FDECL(write_ls, (int, light_source *)); 59 | STATIC_DCL int FDECL(maybe_write_ls, (int, int, BOOLEAN_P)); 60 | 61 | /* imported from vision.c, for small circles */ 62 | extern char circle_data[]; 63 | extern char circle_start[]; 64 | 65 | 66 | /* Create a new light source. */ 67 | void 68 | new_light_source(x, y, range, type, id) 69 | xchar x, y; 70 | int range, type; 71 | genericptr_t id; 72 | { 73 | light_source *ls; 74 | 75 | if (range > MAX_RADIUS || range < 1) { 76 | impossible("new_light_source: illegal range %d", range); 77 | return; 78 | } 79 | 80 | ls = (light_source *) alloc(sizeof(light_source)); 81 | 82 | ls->next = light_base; 83 | ls->x = x; 84 | ls->y = y; 85 | ls->range = range; 86 | ls->type = type; 87 | ls->id = id; 88 | ls->flags = 0; 89 | light_base = ls; 90 | 91 | vision_full_recalc = 1; /* make the source show up */ 92 | } 93 | 94 | /* 95 | * Delete a light source. This assumes only one light source is attached 96 | * to an object at a time. 97 | */ 98 | void 99 | del_light_source(type, id) 100 | int type; 101 | genericptr_t id; 102 | { 103 | light_source *curr, *prev; 104 | genericptr_t tmp_id; 105 | 106 | /* need to be prepared for dealing a with light source which 107 | has only been partially restored during a level change 108 | (in particular: chameleon vs prot. from shape changers) */ 109 | switch (type) { 110 | case LS_OBJECT: tmp_id = (genericptr_t)(((struct obj *)id)->o_id); 111 | break; 112 | case LS_MONSTER: tmp_id = (genericptr_t)(((struct monst *)id)->m_id); 113 | break; 114 | default: tmp_id = 0; 115 | break; 116 | } 117 | 118 | for (prev = 0, curr = light_base; curr; prev = curr, curr = curr->next) { 119 | if (curr->type != type) continue; 120 | if (curr->id == ((curr->flags & LSF_NEEDS_FIXUP) ? tmp_id : id)) { 121 | if (prev) 122 | prev->next = curr->next; 123 | else 124 | light_base = curr->next; 125 | 126 | free((genericptr_t)curr); 127 | vision_full_recalc = 1; 128 | return; 129 | } 130 | } 131 | impossible("del_light_source: not found type=%d, id=0x%lx", type, (long)id); 132 | } 133 | 134 | /* Mark locations that are temporarily lit via mobile light sources. */ 135 | void 136 | do_light_sources(cs_rows) 137 | char **cs_rows; 138 | { 139 | int x, y, min_x, max_x, max_y, offset; 140 | char *limits; 141 | short at_hero_range = 0; 142 | light_source *ls; 143 | char *row; 144 | 145 | for (ls = light_base; ls; ls = ls->next) { 146 | ls->flags &= ~LSF_SHOW; 147 | 148 | /* 149 | * Check for moved light sources. It may be possible to 150 | * save some effort if an object has not moved, but not in 151 | * the current setup -- we need to recalculate for every 152 | * vision recalc. 153 | */ 154 | if (ls->type == LS_OBJECT) { 155 | if (get_obj_location((struct obj *) ls->id, &ls->x, &ls->y, 0)) 156 | ls->flags |= LSF_SHOW; 157 | } else if (ls->type == LS_MONSTER) { 158 | if (get_mon_location((struct monst *) ls->id, &ls->x, &ls->y, 0)) 159 | ls->flags |= LSF_SHOW; 160 | } 161 | 162 | /* minor optimization: don't bother with duplicate light sources */ 163 | /* at hero */ 164 | if (ls->x == u.ux && ls->y == u.uy) { 165 | if (at_hero_range >= ls->range) 166 | ls->flags &= ~LSF_SHOW; 167 | else 168 | at_hero_range = ls->range; 169 | } 170 | 171 | if (ls->flags & LSF_SHOW) { 172 | /* 173 | * Walk the points in the circle and see if they are 174 | * visible from the center. If so, mark'em. 175 | * 176 | * Kevin's tests indicated that doing this brute-force 177 | * method is faster for radius <= 3 (or so). 178 | */ 179 | limits = circle_ptr(ls->range); 180 | if ((max_y = (ls->y + ls->range)) >= ROWNO) max_y = ROWNO-1; 181 | if ((y = (ls->y - ls->range)) < 0) y = 0; 182 | for (; y <= max_y; y++) { 183 | row = cs_rows[y]; 184 | offset = limits[abs(y - ls->y)]; 185 | if ((min_x = (ls->x - offset)) < 0) min_x = 0; 186 | if ((max_x = (ls->x + offset)) >= COLNO) max_x = COLNO-1; 187 | 188 | if (ls->x == u.ux && ls->y == u.uy) { 189 | /* 190 | * If the light source is located at the hero, then 191 | * we can use the COULD_SEE bits already calcualted 192 | * by the vision system. More importantly than 193 | * this optimization, is that it allows the vision 194 | * system to correct problems with clear_path(). 195 | * The function clear_path() is a simple LOS 196 | * path checker that doesn't go out of its way 197 | * make things look "correct". The vision system 198 | * does this. 199 | */ 200 | for (x = min_x; x <= max_x; x++) 201 | if (row[x] & COULD_SEE) 202 | row[x] |= TEMP_LIT; 203 | } else { 204 | for (x = min_x; x <= max_x; x++) 205 | if ((ls->x == x && ls->y == y) 206 | || clear_path((int)ls->x, (int) ls->y, x, y)) 207 | row[x] |= TEMP_LIT; 208 | } 209 | } 210 | } 211 | } 212 | } 213 | 214 | /* (mon->mx == 0) implies migrating */ 215 | #define mon_is_local(mon) ((mon)->mx > 0) 216 | 217 | struct monst * 218 | find_mid(nid, fmflags) 219 | unsigned nid; 220 | unsigned fmflags; 221 | { 222 | struct monst *mtmp; 223 | 224 | if (!nid) 225 | return &youmonst; 226 | if (fmflags & FM_FMON) 227 | for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) 228 | if (!DEADMONSTER(mtmp) && mtmp->m_id == nid) return mtmp; 229 | if (fmflags & FM_MIGRATE) 230 | for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) 231 | if (mtmp->m_id == nid) return mtmp; 232 | if (fmflags & FM_MYDOGS) 233 | for (mtmp = mydogs; mtmp; mtmp = mtmp->nmon) 234 | if (mtmp->m_id == nid) return mtmp; 235 | return (struct monst *) 0; 236 | } 237 | 238 | /* Save all light sources of the given range. */ 239 | void 240 | save_light_sources(fd, mode, range) 241 | int fd, mode, range; 242 | { 243 | int count, actual, is_global; 244 | light_source **prev, *curr; 245 | 246 | if (perform_bwrite(mode)) { 247 | count = maybe_write_ls(fd, range, FALSE); 248 | bwrite(fd, (genericptr_t) &count, sizeof count); 249 | actual = maybe_write_ls(fd, range, TRUE); 250 | if (actual != count) 251 | panic("counted %d light sources, wrote %d! [range=%d]", 252 | count, actual, range); 253 | } 254 | 255 | if (release_data(mode)) { 256 | for (prev = &light_base; (curr = *prev) != 0; ) { 257 | if (!curr->id) { 258 | impossible("save_light_sources: no id! [range=%d]", range); 259 | is_global = 0; 260 | } else 261 | switch (curr->type) { 262 | case LS_OBJECT: 263 | is_global = !obj_is_local((struct obj *)curr->id); 264 | break; 265 | case LS_MONSTER: 266 | is_global = !mon_is_local((struct monst *)curr->id); 267 | break; 268 | default: 269 | is_global = 0; 270 | impossible("save_light_sources: bad type (%d) [range=%d]", 271 | curr->type, range); 272 | break; 273 | } 274 | /* if global and not doing local, or vice versa, remove it */ 275 | if (is_global ^ (range == RANGE_LEVEL)) { 276 | *prev = curr->next; 277 | free((genericptr_t)curr); 278 | } else { 279 | prev = &(*prev)->next; 280 | } 281 | } 282 | } 283 | } 284 | 285 | /* 286 | * Pull in the structures from disk, but don't recalculate the object 287 | * pointers. 288 | */ 289 | void 290 | restore_light_sources(fd) 291 | int fd; 292 | { 293 | int count; 294 | light_source *ls; 295 | 296 | /* restore elements */ 297 | mread(fd, (genericptr_t) &count, sizeof count); 298 | 299 | while (count-- > 0) { 300 | ls = (light_source *) alloc(sizeof(light_source)); 301 | mread(fd, (genericptr_t) ls, sizeof(light_source)); 302 | ls->next = light_base; 303 | light_base = ls; 304 | } 305 | } 306 | 307 | /* Relink all lights that are so marked. */ 308 | void 309 | relink_light_sources(ghostly) 310 | boolean ghostly; 311 | { 312 | char which; 313 | unsigned nid; 314 | light_source *ls; 315 | 316 | for (ls = light_base; ls; ls = ls->next) { 317 | if (ls->flags & LSF_NEEDS_FIXUP) { 318 | if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) { 319 | if (ghostly) { 320 | if (!lookup_id_mapping((unsigned)ls->id, &nid)) 321 | impossible("relink_light_sources: no id mapping"); 322 | } else 323 | nid = (unsigned) ls->id; 324 | if (ls->type == LS_OBJECT) { 325 | which = 'o'; 326 | ls->id = (genericptr_t) find_oid(nid); 327 | } else { 328 | which = 'm'; 329 | ls->id = (genericptr_t) find_mid(nid, FM_EVERYWHERE); 330 | } 331 | if (!ls->id) 332 | impossible("relink_light_sources: cant find %c_id %d", 333 | which, nid); 334 | } else 335 | impossible("relink_light_sources: bad type (%d)", ls->type); 336 | 337 | ls->flags &= ~LSF_NEEDS_FIXUP; 338 | } 339 | } 340 | } 341 | 342 | /* 343 | * Part of the light source save routine. Count up the number of light 344 | * sources that would be written. If write_it is true, actually write 345 | * the light source out. 346 | */ 347 | STATIC_OVL int 348 | maybe_write_ls(fd, range, write_it) 349 | int fd, range; 350 | boolean write_it; 351 | { 352 | int count = 0, is_global; 353 | light_source *ls; 354 | 355 | for (ls = light_base; ls; ls = ls->next) { 356 | if (!ls->id) { 357 | impossible("maybe_write_ls: no id! [range=%d]", range); 358 | continue; 359 | } 360 | switch (ls->type) { 361 | case LS_OBJECT: 362 | is_global = !obj_is_local((struct obj *)ls->id); 363 | break; 364 | case LS_MONSTER: 365 | is_global = !mon_is_local((struct monst *)ls->id); 366 | break; 367 | default: 368 | is_global = 0; 369 | impossible("maybe_write_ls: bad type (%d) [range=%d]", 370 | ls->type, range); 371 | break; 372 | } 373 | /* if global and not doing local, or vice versa, count it */ 374 | if (is_global ^ (range == RANGE_LEVEL)) { 375 | count++; 376 | if (write_it) write_ls(fd, ls); 377 | } 378 | } 379 | 380 | return count; 381 | } 382 | 383 | /* Write a light source structure to disk. */ 384 | STATIC_OVL void 385 | write_ls(fd, ls) 386 | int fd; 387 | light_source *ls; 388 | { 389 | genericptr_t arg_save; 390 | struct obj *otmp; 391 | struct monst *mtmp; 392 | 393 | if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) { 394 | if (ls->flags & LSF_NEEDS_FIXUP) 395 | bwrite(fd, (genericptr_t)ls, sizeof(light_source)); 396 | else { 397 | /* replace object pointer with id for write, then put back */ 398 | arg_save = ls->id; 399 | if (ls->type == LS_OBJECT) { 400 | otmp = (struct obj *)ls->id; 401 | ls->id = (genericptr_t)otmp->o_id; 402 | #ifdef DEBUG 403 | if (find_oid((unsigned)ls->id) != otmp) 404 | panic("write_ls: can't find obj #%u!", (unsigned)ls->id); 405 | #endif 406 | } else { /* ls->type == LS_MONSTER */ 407 | mtmp = (struct monst *)ls->id; 408 | ls->id = (genericptr_t)mtmp->m_id; 409 | #ifdef DEBUG 410 | if (find_mid((unsigned)ls->id, FM_EVERYWHERE) != mtmp) 411 | panic("write_ls: can't find mon #%u!", (unsigned)ls->id); 412 | #endif 413 | } 414 | ls->flags |= LSF_NEEDS_FIXUP; 415 | bwrite(fd, (genericptr_t)ls, sizeof(light_source)); 416 | ls->id = arg_save; 417 | ls->flags &= ~LSF_NEEDS_FIXUP; 418 | } 419 | } else { 420 | impossible("write_ls: bad type (%d)", ls->type); 421 | } 422 | } 423 | 424 | /* Change light source's ID from src to dest. */ 425 | void 426 | obj_move_light_source(src, dest) 427 | struct obj *src, *dest; 428 | { 429 | light_source *ls; 430 | 431 | for (ls = light_base; ls; ls = ls->next) 432 | if (ls->type == LS_OBJECT && ls->id == (genericptr_t) src) 433 | ls->id = (genericptr_t) dest; 434 | src->lamplit = 0; 435 | dest->lamplit = 1; 436 | } 437 | 438 | /* return true if there exist any light sources */ 439 | boolean 440 | any_light_source() 441 | { 442 | return light_base != (light_source *) 0; 443 | } 444 | 445 | /* 446 | * Snuff an object light source if at (x,y). This currently works 447 | * only for burning light sources. 448 | */ 449 | void 450 | snuff_light_source(x, y) 451 | int x, y; 452 | { 453 | light_source *ls; 454 | struct obj *obj; 455 | 456 | for (ls = light_base; ls; ls = ls->next) 457 | /* 458 | Is this position check valid??? Can I assume that the positions 459 | will always be correct because the objects would have been 460 | updated with the last vision update? [Is that recent enough???] 461 | */ 462 | if (ls->type == LS_OBJECT && ls->x == x && ls->y == y) { 463 | obj = (struct obj *) ls->id; 464 | if (obj_is_burning(obj)) { 465 | end_burn(obj, obj->otyp != MAGIC_LAMP); 466 | /* 467 | * The current ls element has just been removed (and 468 | * ls->next is now invalid). Return assuming that there 469 | * is only one light source attached to each object. 470 | */ 471 | return; 472 | } 473 | } 474 | } 475 | 476 | /* Return TRUE if object sheds any light at all. */ 477 | boolean 478 | obj_sheds_light(obj) 479 | struct obj *obj; 480 | { 481 | /* so far, only burning objects shed light */ 482 | return obj_is_burning(obj); 483 | } 484 | 485 | /* Return TRUE if sheds light AND will be snuffed by end_burn(). */ 486 | boolean 487 | obj_is_burning(obj) 488 | struct obj *obj; 489 | { 490 | return (obj->lamplit && 491 | ( obj->otyp == MAGIC_LAMP 492 | || obj->otyp == BRASS_LANTERN 493 | || obj->otyp == OIL_LAMP 494 | || obj->otyp == CANDELABRUM_OF_INVOCATION 495 | || obj->otyp == TALLOW_CANDLE 496 | || obj->otyp == WAX_CANDLE 497 | || obj->otyp == POT_OIL)); 498 | } 499 | 500 | /* copy the light source(s) attachted to src, and attach it/them to dest */ 501 | void 502 | obj_split_light_source(src, dest) 503 | struct obj *src, *dest; 504 | { 505 | light_source *ls, *new_ls; 506 | 507 | for (ls = light_base; ls; ls = ls->next) 508 | if (ls->type == LS_OBJECT && ls->id == (genericptr_t) src) { 509 | /* 510 | * Insert the new source at beginning of list. This will 511 | * never interfere us walking down the list - we are already 512 | * past the insertion point. 513 | */ 514 | new_ls = (light_source *) alloc(sizeof(light_source)); 515 | *new_ls = *ls; 516 | if (Is_candle(src)) { 517 | /* split candles may emit less light than original group */ 518 | ls->range = candle_light_range(src); 519 | new_ls->range = candle_light_range(dest); 520 | vision_full_recalc = 1; /* in case range changed */ 521 | } 522 | new_ls->id = (genericptr_t) dest; 523 | new_ls->next = light_base; 524 | light_base = new_ls; 525 | dest->lamplit = 1; /* now an active light source */ 526 | } 527 | } 528 | 529 | /* light source `src' has been folded into light source `dest'; 530 | used for merging lit candles and adding candle(s) to lit candelabrum */ 531 | void 532 | obj_merge_light_sources(src, dest) 533 | struct obj *src, *dest; 534 | { 535 | light_source *ls; 536 | 537 | /* src == dest implies adding to candelabrum */ 538 | if (src != dest) end_burn(src, TRUE); /* extinguish candles */ 539 | 540 | for (ls = light_base; ls; ls = ls->next) 541 | if (ls->type == LS_OBJECT && ls->id == (genericptr_t) dest) { 542 | ls->range = candle_light_range(dest); 543 | vision_full_recalc = 1; /* in case range changed */ 544 | break; 545 | } 546 | } 547 | 548 | /* Candlelight is proportional to the number of candles; 549 | minimum range is 2 rather than 1 for playability. */ 550 | int 551 | candle_light_range(obj) 552 | struct obj *obj; 553 | { 554 | int radius; 555 | 556 | if (obj->otyp == CANDELABRUM_OF_INVOCATION) { 557 | /* 558 | * The special candelabrum emits more light than the 559 | * corresponding number of candles would. 560 | * 1..3 candles, range 2 (minimum range); 561 | * 4..6 candles, range 3 (normal lamp range); 562 | * 7 candles, range 4 (bright). 563 | */ 564 | radius = (obj->spe < 4) ? 2 : (obj->spe < 7) ? 3 : 4; 565 | } else if (Is_candle(obj)) { 566 | /* 567 | * Range is incremented by powers of 7 so that it will take 568 | * wizard mode quantities of candles to get more light than 569 | * from a lamp, without imposing an arbitrary limit. 570 | * 1..6 candles, range 2; 571 | * 7..48 candles, range 3; 572 | * 49..342 candles, range 4; &c. 573 | */ 574 | long n = obj->quan; 575 | 576 | radius = 1; /* always incremented at least once */ 577 | do { 578 | radius++; 579 | n /= 7L; 580 | } while (n > 0L); 581 | } else { 582 | /* we're only called for lit candelabrum or candles */ 583 | /* impossible("candlelight for %d?", obj->otyp); */ 584 | radius = 3; /* lamp's value */ 585 | } 586 | return radius; 587 | } 588 | 589 | #ifdef WIZARD 590 | extern char *FDECL(fmt_ptr, (const genericptr, char *)); /* from alloc.c */ 591 | 592 | int 593 | wiz_light_sources() 594 | { 595 | winid win; 596 | char buf[BUFSZ], arg_address[20]; 597 | light_source *ls; 598 | 599 | win = create_nhwindow(NHW_MENU); /* corner text window */ 600 | if (win == WIN_ERR) return 0; 601 | 602 | Sprintf(buf, "Mobile light sources: hero @ (%2d,%2d)", u.ux, u.uy); 603 | putstr(win, 0, buf); 604 | putstr(win, 0, ""); 605 | 606 | if (light_base) { 607 | putstr(win, 0, "location range flags type id"); 608 | putstr(win, 0, "-------- ----- ------ ---- -------"); 609 | for (ls = light_base; ls; ls = ls->next) { 610 | Sprintf(buf, " %2d,%2d %2d 0x%04x %s %s", 611 | ls->x, ls->y, ls->range, ls->flags, 612 | (ls->type == LS_OBJECT ? "obj" : 613 | ls->type == LS_MONSTER ? 614 | (mon_is_local((struct monst *)ls->id) ? "mon" : 615 | ((struct monst *)ls->id == &youmonst) ? "you" : 616 | "<m>") : /* migrating monster */ 617 | "???"), 618 | fmt_ptr(ls->id, arg_address)); 619 | putstr(win, 0, buf); 620 | } 621 | } else 622 | putstr(win, 0, "<none>"); 623 | 624 | 625 | display_nhwindow(win, FALSE); 626 | destroy_nhwindow(win); 627 | 628 | return 0; 629 | } 630 | 631 | #endif /* WIZARD */ 632 | 633 | #endif /* OVL3 */ 634 | 635 | /*light.c*/