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