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*/