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. */
6 #include "lev.h" /* for checking save modes */
9 * Mobile light sources.
11 * This implementation minimizes memory at the expense of extra
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.
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.
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.
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.
44 #define LSF_SHOW 0x1 /* display the light source */
45 #define LSF_NEEDS_FIXUP 0x2 /* need oid fixup */
47 static light_source *light_base = 0;
49 STATIC_DCL void FDECL(write_ls, (int, light_source *));
50 STATIC_DCL int FDECL(maybe_write_ls, (int, int, BOOLEAN_P));
52 /* imported from vision.c, for small circles */
53 extern char circle_data[];
54 extern char circle_start[];
57 /* Create a new light source. */
59 new_light_source(x, y, range, type, id)
66 if (range > MAX_RADIUS || range < 1) {
67 impossible("new_light_source: illegal range %d", range);
71 ls = (light_source *) alloc(sizeof(light_source));
73 ls->next = light_base;
82 vision_full_recalc = 1; /* make the source show up */
86 * Delete a light source. This assumes only one light source is attached
87 * to an object at a time.
90 del_light_source(type, id)
94 light_source *curr, *prev;
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) */
101 case LS_OBJECT: tmp_id = (genericptr_t)(((struct obj *)id)->o_id);
103 case LS_MONSTER: tmp_id = (genericptr_t)(((struct monst *)id)->m_id);
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)) {
113 prev->next = curr->next;
115 light_base = curr->next;
117 free((genericptr_t)curr);
118 vision_full_recalc = 1;
122 impossible("del_light_source: not found type=%d, id=0x%lx", type, (long)id);
125 /* Mark locations that are temporarily lit via mobile light sources. */
127 do_light_sources(cs_rows)
130 int x, y, min_x, max_x, max_y, offset;
132 short at_hero_range = 0;
136 for (ls = light_base; ls; ls = ls->next) {
137 ls->flags &= ~LSF_SHOW;
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
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;
153 /* minor optimization: don't bother with duplicate light sources */
155 if (ls->x == u.ux && ls->y == u.uy) {
156 if (at_hero_range >= ls->range)
157 ls->flags &= ~LSF_SHOW;
159 at_hero_range = ls->range;
162 if (ls->flags & LSF_SHOW) {
164 * Walk the points in the circle and see if they are
165 * visible from the center. If so, mark'em.
167 * Kevin's tests indicated that doing this brute-force
168 * method is faster for radius <= 3 (or so).
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++) {
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;
179 if (ls->x == u.ux && ls->y == u.uy) {
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
191 for (x = min_x; x <= max_x; x++)
192 if (row[x] & COULD_SEE)
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))
205 /* (mon->mx == 0) implies migrating */
206 #define mon_is_local(mon) ((mon)->mx > 0)
209 find_mid(nid, fmflags)
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;
229 /* Save all light sources of the given range. */
231 save_light_sources(fd, mode, range)
234 int count, actual, is_global;
235 light_source **prev, *curr;
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);
242 panic("counted %d light sources, wrote %d! [range=%d]",
243 count, actual, range);
246 if (release_data(mode)) {
247 for (prev = &light_base; (curr = *prev) != 0; ) {
249 impossible("save_light_sources: no id! [range=%d]", range);
252 switch (curr->type) {
254 is_global = !obj_is_local((struct obj *)curr->id);
257 is_global = !mon_is_local((struct monst *)curr->id);
261 impossible("save_light_sources: bad type (%d) [range=%d]",
265 /* if global and not doing local, or vice versa, remove it */
266 if (is_global ^ (range == RANGE_LEVEL)) {
268 free((genericptr_t)curr);
270 prev = &(*prev)->next;
277 * Pull in the structures from disk, but don't recalculate the object
281 restore_light_sources(fd)
287 /* restore elements */
288 mread(fd, (genericptr_t) &count, sizeof count);
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;
298 /* Relink all lights that are so marked. */
300 relink_light_sources(ghostly)
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) {
311 if (!lookup_id_mapping((unsigned)ls->id, &nid))
312 impossible("relink_light_sources: no id mapping");
314 nid = (unsigned) ls->id;
315 if (ls->type == LS_OBJECT) {
317 ls->id = (genericptr_t) find_oid(nid);
320 ls->id = (genericptr_t) find_mid(nid, FM_EVERYWHERE);
323 impossible("relink_light_sources: cant find %c_id %d",
326 impossible("relink_light_sources: bad type (%d)", ls->type);
328 ls->flags &= ~LSF_NEEDS_FIXUP;
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.
339 maybe_write_ls(fd, range, write_it)
343 int count = 0, is_global;
346 for (ls = light_base; ls; ls = ls->next) {
348 impossible("maybe_write_ls: no id! [range=%d]", range);
353 is_global = !obj_is_local((struct obj *)ls->id);
356 is_global = !mon_is_local((struct monst *)ls->id);
360 impossible("maybe_write_ls: bad type (%d) [range=%d]",
364 /* if global and not doing local, or vice versa, count it */
365 if (is_global ^ (range == RANGE_LEVEL)) {
367 if (write_it) write_ls(fd, ls);
374 /* Write a light source structure to disk. */
380 genericptr_t arg_save;
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));
388 /* replace object pointer with id for write, then put back */
390 if (ls->type == LS_OBJECT) {
391 otmp = (struct obj *)ls->id;
392 ls->id = (genericptr_t)otmp->o_id;
394 if (find_oid((unsigned)ls->id) != otmp)
395 panic("write_ls: can't find obj #%u!", (unsigned)ls->id);
397 } else { /* ls->type == LS_MONSTER */
398 mtmp = (struct monst *)ls->id;
399 ls->id = (genericptr_t)mtmp->m_id;
401 if (find_mid((unsigned)ls->id, FM_EVERYWHERE) != mtmp)
402 panic("write_ls: can't find mon #%u!", (unsigned)ls->id);
405 ls->flags |= LSF_NEEDS_FIXUP;
406 bwrite(fd, (genericptr_t)ls, sizeof(light_source));
408 ls->flags &= ~LSF_NEEDS_FIXUP;
411 impossible("write_ls: bad type (%d)", ls->type);
415 /* Change light source's ID from src to dest. */
417 obj_move_light_source(src, dest)
418 struct obj *src, *dest;
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;
429 /* return true if there exist any light sources */
433 return light_base != (light_source *) 0;
437 * Snuff an object light source if at (x,y). This currently works
438 * only for burning light sources.
441 snuff_light_source(x, y)
447 for (ls = light_base; ls; ls = ls->next)
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???]
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.)
461 if (artifact_light(obj)) continue;
462 end_burn(obj, obj->otyp != MAGIC_LAMP);
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.
473 /* Return TRUE if object sheds any light at all. */
478 /* so far, only burning objects shed light */
479 return obj_is_burning(obj);
482 /* Return TRUE if sheds light AND will be snuffed by end_burn(). */
487 return (obj->lamplit &&
488 (obj->otyp == MAGIC_LAMP || ignitable(obj) || artifact_light(obj)));
491 /* copy the light source(s) attachted to src, and attach it/them to dest */
493 obj_split_light_source(src, dest)
494 struct obj *src, *dest;
496 light_source *ls, *new_ls;
498 for (ls = light_base; ls; ls = ls->next)
499 if (ls->type == LS_OBJECT && ls->id == (genericptr_t) src) {
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.
505 new_ls = (light_source *) alloc(sizeof(light_source));
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 */
513 new_ls->id = (genericptr_t) dest;
514 new_ls->next = light_base;
516 dest->lamplit = 1; /* now an active light source */
520 /* light source `src' has been folded into light source `dest';
521 used for merging lit candles and adding candle(s) to lit candelabrum */
523 obj_merge_light_sources(src, dest)
524 struct obj *src, *dest;
528 /* src == dest implies adding to candelabrum */
529 if (src != dest) end_burn(src, TRUE); /* extinguish candles */
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 */
539 /* Candlelight is proportional to the number of candles;
540 minimum range is 2 rather than 1 for playability. */
542 candle_light_range(obj)
547 if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
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).
555 radius = (obj->spe < 4) ? 2 : (obj->spe < 7) ? 3 : 4;
556 } else if (Is_candle(obj)) {
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.
567 radius = 1; /* always incremented at least once */
573 /* we're only called for lit candelabrum or candles */
574 /* impossible("candlelight for %d?", obj->otyp); */
575 radius = 3; /* lamp's value */
581 extern char *FDECL(fmt_ptr, (const genericptr, char *)); /* from alloc.c */
587 char buf[BUFSZ], arg_address[20];
590 win = create_nhwindow(NHW_MENU); /* corner text window */
591 if (win == WIN_ERR) return 0;
593 Sprintf(buf, "Mobile light sources: hero @ (%2d,%2d)", u.ux, u.uy);
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 */
609 fmt_ptr(ls->id, arg_address));
613 putstr(win, 0, "<none>");
616 display_nhwindow(win, FALSE);
617 destroy_nhwindow(win);