1 /* NetHack 3.6 light.c $NHDT-Date: 1559994625 2019/06/08 11:50:25 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.30 $ */
2 /* Copyright (c) Dean Luick, 1994 */
3 /* NetHack may be freely redistributed. See license for details. */
5 /* JNetHack Copyright */
6 /* For 3.6-, Copyright (c) SHIRAKATA Kentaro, 2002-2022 */
7 /* JNetHack may be freely redistributed. See license for details. */
10 #include "lev.h" /* for checking save modes */
13 * Mobile light sources.
15 * This implementation minimizes memory at the expense of extra
18 * Light sources are "things" that have a physical position and range.
19 * They have a type, which gives us information about them. Currently
20 * they are only attached to objects and monsters. Note well: the
21 * polymorphed-player handling assumes that both youmonst.m_id and
22 * youmonst.mx will always remain 0.
24 * Light sources, like timers, either follow game play (RANGE_GLOBAL) or
25 * stay on a level (RANGE_LEVEL). Light sources are unique by their
26 * (type, id) pair. For light sources attached to objects, this id
27 * is a pointer to the object.
29 * The major working function is do_light_sources(). It is called
30 * when the vision system is recreating its "could see" array. Here
31 * we add a flag (TEMP_LIT) to the array for all locations that are lit
32 * via a light source. The bad part of this is that we have to
33 * re-calculate the LOS of each light source every time the vision
34 * system runs. Even if the light sources and any topology (vision blocking
35 * positions) have not changed. The good part is that no extra memory
36 * is used, plus we don't have to figure out how far the sources have moved,
37 * or if the topology has changed.
39 * The structure of the save/restore mechanism is amazingly similar to
40 * the timer save/restore. This is because they both have the same
41 * principals of having pointers into objects that must be recalculated
42 * across saves and restores.
46 #define LSF_SHOW 0x1 /* display the light source */
47 #define LSF_NEEDS_FIXUP 0x2 /* need oid fixup */
49 static light_source *light_base = 0;
51 STATIC_DCL void FDECL(write_ls, (int, light_source *));
52 STATIC_DCL int FDECL(maybe_write_ls, (int, int, BOOLEAN_P));
54 /* imported from vision.c, for small circles */
55 extern char circle_data[];
56 extern char circle_start[];
58 /* Create a new light source. */
60 new_light_source(x, y, range, type, id)
67 if (range > MAX_RADIUS || range < 1) {
68 impossible("new_light_source: illegal range %d", range);
72 ls = (light_source *) alloc(sizeof *ls);
74 ls->next = light_base;
83 vision_full_recalc = 1; /* make the source show up */
87 * Delete a light source. This assumes only one light source is attached
88 * to an object at a time.
91 del_light_source(type, id)
95 light_source *curr, *prev;
99 /* need to be prepared for dealing a with light source which
100 has only been partially restored during a level change
101 (in particular: chameleon vs prot. from shape changers) */
104 tmp_id.a_uint = id->a_obj->o_id;
107 tmp_id.a_uint = id->a_monst->m_id;
114 for (prev = 0, curr = light_base; curr; prev = curr, curr = curr->next) {
115 if (curr->type != type)
118 == ((curr->flags & LSF_NEEDS_FIXUP) ? tmp_id.a_obj : id->a_obj)) {
120 prev->next = curr->next;
122 light_base = curr->next;
124 free((genericptr_t) curr);
125 vision_full_recalc = 1;
129 impossible("del_light_source: not found type=%d, id=%s", type,
130 fmt_ptr((genericptr_t) id->a_obj));
133 /* Mark locations that are temporarily lit via mobile light sources. */
135 do_light_sources(cs_rows)
138 int x, y, min_x, max_x, max_y, offset;
140 short at_hero_range = 0;
144 for (ls = light_base; ls; ls = ls->next) {
145 ls->flags &= ~LSF_SHOW;
148 * Check for moved light sources. It may be possible to
149 * save some effort if an object has not moved, but not in
150 * the current setup -- we need to recalculate for every
153 if (ls->type == LS_OBJECT) {
154 if (get_obj_location(ls->id.a_obj, &ls->x, &ls->y, 0))
155 ls->flags |= LSF_SHOW;
156 } else if (ls->type == LS_MONSTER) {
157 if (get_mon_location(ls->id.a_monst, &ls->x, &ls->y, 0))
158 ls->flags |= LSF_SHOW;
161 /* minor optimization: don't bother with duplicate light sources
163 if (ls->x == u.ux && ls->y == u.uy) {
164 if (at_hero_range >= ls->range)
165 ls->flags &= ~LSF_SHOW;
167 at_hero_range = ls->range;
170 if (ls->flags & LSF_SHOW) {
172 * Walk the points in the circle and see if they are
173 * visible from the center. If so, mark'em.
175 * Kevin's tests indicated that doing this brute-force
176 * method is faster for radius <= 3 (or so).
178 limits = circle_ptr(ls->range);
179 if ((max_y = (ls->y + ls->range)) >= ROWNO)
181 if ((y = (ls->y - ls->range)) < 0)
183 for (; y <= max_y; y++) {
185 offset = limits[abs(y - ls->y)];
186 if ((min_x = (ls->x - offset)) < 0)
188 if ((max_x = (ls->x + offset)) >= COLNO)
191 if (ls->x == u.ux && ls->y == u.uy) {
193 * If the light source is located at the hero, then
194 * we can use the COULD_SEE bits already calculated
195 * by the vision system. More importantly than
196 * this optimization, is that it allows the vision
197 * system to correct problems with clear_path().
198 * The function clear_path() is a simple LOS
199 * path checker that doesn't go out of its way to
200 * make things look "correct". The vision system
203 for (x = min_x; x <= max_x; x++)
204 if (row[x] & COULD_SEE)
207 for (x = min_x; x <= max_x; x++)
208 if ((ls->x == x && ls->y == y)
209 || clear_path((int) ls->x, (int) ls->y, x, y))
217 /* lit 'obj' has been thrown or kicked and is passing through x,y on the
218 way to its destination; show its light so that hero has a chance to
219 remember terrain, objects, and monsters being revealed */
221 show_transient_light(obj, x, y)
229 /* caller has verified obj->lamplit and that hero is not Blind;
230 validate light source and obtain its radius (for monster sightings) */
231 for (ls = light_base; ls; ls = ls->next) {
232 if (ls->type != LS_OBJECT)
234 if (ls->id.a_obj == obj)
237 if (!ls || obj->where != OBJ_FREE) {
238 impossible("transient light %s %s is not %s?",
239 obj->lamplit ? "lit" : "unlit", xname(obj),
240 !ls ? "a light source" : "free");
242 /* "expensive" but rare */
243 place_object(obj, bhitpos.x, bhitpos.y); /* temporarily put on map */
247 remove_object(obj); /* take back off of map */
249 radius_squared = ls->range * ls->range;
250 for (mon = fmon; mon; mon = mon->nmon) {
251 if (DEADMONSTER(mon))
253 /* light range is the radius of a circle and we're limiting
254 canseemon() to a square exclosing that circle, but setting
255 mtemplit 'erroneously' for a seen monster is not a problem;
256 it just flags monsters for another canseemon() check when
257 'obj' has reached its destination after missile traversal */
258 if (dist2(mon->mx, mon->my, x, y) <= radius_squared
261 /* [what about worm tails?] */
266 /* draw "remembered, unseen monster" glyph at locations where a monster
267 was flagged for being visible during transient light movement but can't
270 transient_light_cleanup()
275 for (mon = fmon; mon; mon = mon->nmon) {
276 if (DEADMONSTER(mon))
282 map_invisible(mon->mx, mon->my);
291 /* (mon->mx == 0) implies migrating */
292 #define mon_is_local(mon) ((mon)->mx > 0)
295 find_mid(nid, fmflags)
303 if (fmflags & FM_FMON)
304 for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
305 if (!DEADMONSTER(mtmp) && mtmp->m_id == nid)
307 if (fmflags & FM_MIGRATE)
308 for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon)
309 if (mtmp->m_id == nid)
311 if (fmflags & FM_MYDOGS)
312 for (mtmp = mydogs; mtmp; mtmp = mtmp->nmon)
313 if (mtmp->m_id == nid)
315 return (struct monst *) 0;
318 /* Save all light sources of the given range. */
320 save_light_sources(fd, mode, range)
323 int count, actual, is_global;
324 light_source **prev, *curr;
326 if (perform_bwrite(mode)) {
327 count = maybe_write_ls(fd, range, FALSE);
328 bwrite(fd, (genericptr_t) &count, sizeof count);
329 actual = maybe_write_ls(fd, range, TRUE);
331 panic("counted %d light sources, wrote %d! [range=%d]", count,
335 if (release_data(mode)) {
336 for (prev = &light_base; (curr = *prev) != 0;) {
337 if (!curr->id.a_monst) {
338 impossible("save_light_sources: no id! [range=%d]", range);
341 switch (curr->type) {
343 is_global = !obj_is_local(curr->id.a_obj);
346 is_global = !mon_is_local(curr->id.a_monst);
350 impossible("save_light_sources: bad type (%d) [range=%d]",
354 /* if global and not doing local, or vice versa, remove it */
355 if (is_global ^ (range == RANGE_LEVEL)) {
357 free((genericptr_t) curr);
359 prev = &(*prev)->next;
366 * Pull in the structures from disk, but don't recalculate the object
370 restore_light_sources(fd)
376 /* restore elements */
377 mread(fd, (genericptr_t) &count, sizeof count);
379 while (count-- > 0) {
380 ls = (light_source *) alloc(sizeof(light_source));
381 mread(fd, (genericptr_t) ls, sizeof(light_source));
382 ls->next = light_base;
387 /* to support '#stats' wizard-mode command */
389 light_stats(hdrfmt, hdrbuf, count, size)
396 Sprintf(hdrbuf, hdrfmt, (long) sizeof (light_source));
398 for (ls = light_base; ls; ls = ls->next) {
400 *size += (long) sizeof *ls;
404 /* Relink all lights that are so marked. */
406 relink_light_sources(ghostly)
413 for (ls = light_base; ls; ls = ls->next) {
414 if (ls->flags & LSF_NEEDS_FIXUP) {
415 if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) {
417 if (!lookup_id_mapping(ls->id.a_uint, &nid))
418 impossible("relink_light_sources: no id mapping");
421 if (ls->type == LS_OBJECT) {
423 ls->id.a_obj = find_oid(nid);
426 ls->id.a_monst = find_mid(nid, FM_EVERYWHERE);
429 impossible("relink_light_sources: cant find %c_id %d",
432 impossible("relink_light_sources: bad type (%d)", ls->type);
434 ls->flags &= ~LSF_NEEDS_FIXUP;
440 * Part of the light source save routine. Count up the number of light
441 * sources that would be written. If write_it is true, actually write
442 * the light source out.
445 maybe_write_ls(fd, range, write_it)
449 int count = 0, is_global;
452 for (ls = light_base; ls; ls = ls->next) {
453 if (!ls->id.a_monst) {
454 impossible("maybe_write_ls: no id! [range=%d]", range);
459 is_global = !obj_is_local(ls->id.a_obj);
462 is_global = !mon_is_local(ls->id.a_monst);
466 impossible("maybe_write_ls: bad type (%d) [range=%d]", ls->type,
470 /* if global and not doing local, or vice versa, count it */
471 if (is_global ^ (range == RANGE_LEVEL)) {
482 light_sources_sanity_check()
489 for (ls = light_base; ls; ls = ls->next) {
491 panic("insane light source: no id!");
492 if (ls->type == LS_OBJECT) {
493 otmp = (struct obj *) ls->id.a_obj;
495 if (find_oid(auint) != otmp)
496 panic("insane light source: can't find obj #%u!", auint);
497 } else if (ls->type == LS_MONSTER) {
498 mtmp = (struct monst *) ls->id.a_monst;
500 if (find_mid(auint, FM_EVERYWHERE) != mtmp)
501 panic("insane light source: can't find mon #%u!", auint);
503 panic("insane light source: bad ls type %d", ls->type);
508 /* Write a light source structure to disk. */
518 if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) {
519 if (ls->flags & LSF_NEEDS_FIXUP) {
520 bwrite(fd, (genericptr_t) ls, sizeof(light_source));
522 /* replace object pointer with id for write, then put back */
524 if (ls->type == LS_OBJECT) {
527 ls->id.a_uint = otmp->o_id;
528 if (find_oid((unsigned) ls->id.a_uint) != otmp)
529 impossible("write_ls: can't find obj #%u!",
531 } else { /* ls->type == LS_MONSTER */
532 mtmp = (struct monst *) ls->id.a_monst;
534 ls->id.a_uint = mtmp->m_id;
535 if (find_mid((unsigned) ls->id.a_uint, FM_EVERYWHERE) != mtmp)
536 impossible("write_ls: can't find mon #%u!",
539 ls->flags |= LSF_NEEDS_FIXUP;
540 bwrite(fd, (genericptr_t) ls, sizeof(light_source));
542 ls->flags &= ~LSF_NEEDS_FIXUP;
545 impossible("write_ls: bad type (%d)", ls->type);
549 /* Change light source's ID from src to dest. */
551 obj_move_light_source(src, dest)
552 struct obj *src, *dest;
556 for (ls = light_base; ls; ls = ls->next)
557 if (ls->type == LS_OBJECT && ls->id.a_obj == src)
563 /* return true if there exist any light sources */
567 return (boolean) (light_base != (light_source *) 0);
571 * Snuff an object light source if at (x,y). This currently works
572 * only for burning light sources.
575 snuff_light_source(x, y)
581 for (ls = light_base; ls; ls = ls->next)
583 * Is this position check valid??? Can I assume that the positions
584 * will always be correct because the objects would have been
585 * updated with the last vision update? [Is that recent enough???]
587 if (ls->type == LS_OBJECT && ls->x == x && ls->y == y) {
589 if (obj_is_burning(obj)) {
590 /* The only way to snuff Sunsword is to unwield it. Darkness
591 * scrolls won't affect it. (If we got here because it was
592 * dropped or thrown inside a monster, this won't matter
593 * anyway because it will go out when dropped.)
595 if (artifact_light(obj))
597 end_burn(obj, obj->otyp != MAGIC_LAMP);
599 * The current ls element has just been removed (and
600 * ls->next is now invalid). Return assuming that there
601 * is only one light source attached to each object.
608 /* Return TRUE if object sheds any light at all. */
613 /* so far, only burning objects shed light */
614 return obj_is_burning(obj);
617 /* Return TRUE if sheds light AND will be snuffed by end_burn(). */
622 return (boolean) (obj->lamplit && (obj->otyp == MAGIC_LAMP
624 || artifact_light(obj)));
627 /* copy the light source(s) attached to src, and attach it/them to dest */
629 obj_split_light_source(src, dest)
630 struct obj *src, *dest;
632 light_source *ls, *new_ls;
634 for (ls = light_base; ls; ls = ls->next)
635 if (ls->type == LS_OBJECT && ls->id.a_obj == src) {
637 * Insert the new source at beginning of list. This will
638 * never interfere us walking down the list - we are already
639 * past the insertion point.
641 new_ls = (light_source *) alloc(sizeof(light_source));
643 if (Is_candle(src)) {
644 /* split candles may emit less light than original group */
645 ls->range = candle_light_range(src);
646 new_ls->range = candle_light_range(dest);
647 vision_full_recalc = 1; /* in case range changed */
649 new_ls->id.a_obj = dest;
650 new_ls->next = light_base;
652 dest->lamplit = 1; /* now an active light source */
656 /* light source `src' has been folded into light source `dest';
657 used for merging lit candles and adding candle(s) to lit candelabrum */
659 obj_merge_light_sources(src, dest)
660 struct obj *src, *dest;
664 /* src == dest implies adding to candelabrum */
666 end_burn(src, TRUE); /* extinguish candles */
668 for (ls = light_base; ls; ls = ls->next)
669 if (ls->type == LS_OBJECT && ls->id.a_obj == dest) {
670 ls->range = candle_light_range(dest);
671 vision_full_recalc = 1; /* in case range changed */
676 /* light source `obj' is being made brighter or dimmer */
678 obj_adjust_light_radius(obj, new_radius)
684 for (ls = light_base; ls; ls = ls->next)
685 if (ls->type == LS_OBJECT && ls->id.a_obj == obj) {
686 if (new_radius != ls->range)
687 vision_full_recalc = 1;
688 ls->range = new_radius;
691 impossible("obj_adjust_light_radius: can't find %s", xname(obj));
694 /* Candlelight is proportional to the number of candles;
695 minimum range is 2 rather than 1 for playability. */
697 candle_light_range(obj)
702 if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
704 * The special candelabrum emits more light than the
705 * corresponding number of candles would.
706 * 1..3 candles, range 2 (minimum range);
707 * 4..6 candles, range 3 (normal lamp range);
708 * 7 candles, range 4 (bright).
710 radius = (obj->spe < 4) ? 2 : (obj->spe < 7) ? 3 : 4;
711 } else if (Is_candle(obj)) {
713 * Range is incremented by powers of 7 so that it will take
714 * wizard mode quantities of candles to get more light than
715 * from a lamp, without imposing an arbitrary limit.
716 * 1..6 candles, range 2;
717 * 7..48 candles, range 3;
718 * 49..342 candles, range 4; &c.
722 radius = 1; /* always incremented at least once */
728 /* we're only called for lit candelabrum or candles */
729 /* impossible("candlelight for %d?", obj->otyp); */
730 radius = 3; /* lamp's value */
735 /* light emitting artifact's range depends upon its curse/bless state */
737 arti_light_radius(obj)
741 * Used by begin_burn() when setting up a new light source
742 * (obj->lamplit will already be set by this point) and
743 * also by bless()/unbless()/uncurse()/curse() to decide
744 * whether to call obj_adjust_light_radius().
747 /* sanity check [simplifies usage by bless()/curse()/&c] */
748 if (!obj->lamplit || !artifact_light(obj))
751 /* cursed radius of 1 is not noticeable for an item that's
752 carried by the hero but is if it's carried by a monster
753 or left lit on the floor (not applicable for Sunsword) */
754 return (obj->blessed ? 3 : !obj->cursed ? 2 : 1);
757 /* adverb describing lit artifact's light; depends on curse/bless state */
759 arti_light_description(obj)
762 switch (arti_light_radius(obj)) {
765 return "brilliantly"; /* blessed */
767 return "
\83L
\83\89\83L
\83\89\82Æ"; /* blessed */
771 return "brightly"; /* uncursed */
773 return "
\96¾
\82é
\82"; /* uncursed */
777 return "dimly"; /* cursed */
779 return "
\94\96\88Ã
\82"; /* cursed */
787 return "
\95s
\8ev
\8bc
\82É";
797 win = create_nhwindow(NHW_MENU); /* corner text window */
801 Sprintf(buf, "Mobile light sources: hero @ (%2d,%2d)", u.ux, u.uy);
806 putstr(win, 0, "location range flags type id");
807 putstr(win, 0, "-------- ----- ------ ---- -------");
808 for (ls = light_base; ls; ls = ls->next) {
809 Sprintf(buf, " %2d,%2d %2d 0x%04x %s %s", ls->x, ls->y,
810 ls->range, ls->flags,
811 (ls->type == LS_OBJECT
813 : ls->type == LS_MONSTER
814 ? (mon_is_local(ls->id.a_monst)
816 : (ls->id.a_monst == &youmonst)
818 /* migrating monster */
821 fmt_ptr(ls->id.a_void));
825 putstr(win, 0, "<none>");
827 display_nhwindow(win, FALSE);
828 destroy_nhwindow(win);