make rank() static again
[NetHack.git] / src / light.c
blobe89c316cad8143226dc54fb52713aede156a519b
1 /* NetHack 3.7 light.c $NHDT-Date: 1726609514 2024/09/17 21:45:14 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.75 $ */
2 /* Copyright (c) Dean Luick, 1994 */
3 /* NetHack may be freely redistributed. See license for details. */
5 #include "hack.h"
7 /*
8 * Mobile light sources.
10 * This implementation minimizes memory at the expense of extra
11 * recalculations.
13 * Light sources are "things" that have a physical position and range.
14 * They have a type, which gives us information about them. Currently
15 * they are only attached to objects and monsters. Note well: the
16 * polymorphed-player handling assumes that gy.youmonst.m_id will
17 * always remain 1 and gy.youmonst.mx will always remain 0.
19 * Light sources, like timers, either follow game play (RANGE_GLOBAL) or
20 * stay on a level (RANGE_LEVEL). Light sources are unique by their
21 * (type, id) pair. For light sources attached to objects, this id
22 * is a pointer to the object.
24 * The major working function is do_light_sources(). It is called
25 * when the vision system is recreating its "could see" array. Here
26 * we add a flag (TEMP_LIT) to the array for all locations that are lit
27 * via a light source. The bad part of this is that we have to
28 * re-calculate the LOS of each light source every time the vision
29 * system runs. Even if the light sources and any topology (vision blocking
30 * positions) have not changed. The good part is that no extra memory
31 * is used, plus we don't have to figure out how far the sources have moved,
32 * or if the topology has changed.
34 * The structure of the save/restore mechanism is amazingly similar to
35 * the timer save/restore. This is because they both have the same
36 * principals of having pointers into objects that must be recalculated
37 * across saves and restores.
40 /* flags */
41 #define LSF_SHOW 0x1 /* display the light source */
42 #define LSF_NEEDS_FIXUP 0x2 /* need oid fixup */
43 #define LSF_IS_PROBLEMATIC 0x4 /* impossible situation encountered */
45 staticfn light_source *new_light_core(coordxy, coordxy,
46 int, int, anything *) NONNULLPTRS;
47 staticfn void delete_ls(light_source *);
48 staticfn void discard_flashes(void);
49 staticfn void write_ls(NHFILE *, light_source *);
50 staticfn int maybe_write_ls(NHFILE *, int, boolean);
51 staticfn unsigned whereis_mon(struct monst *, unsigned);
53 /* imported from vision.c, for small circles */
54 extern const coordxy circle_data[];
55 extern const coordxy circle_start[];
58 /* Create a new light source. Caller (and extern.h) doesn't need to know
59 anything about type 'light_source'. */
60 void
61 new_light_source(coordxy x, coordxy y, int range, int type, anything *id)
63 (void) new_light_core(x, y, range, type, id);
66 /* Create a new light source and return it. Only used within this file. */
67 staticfn light_source *
68 new_light_core(coordxy x, coordxy y, int range, int type, anything *id)
70 light_source *ls;
72 if (range > MAX_RADIUS || range < 0
73 /* camera flash uses radius 0 and passes Null object */
74 || (range == 0 && (type != LS_OBJECT || id->a_obj != 0))) {
75 impossible("new_light_source: illegal range %d", range);
76 return (light_source *) 0;
79 ls = (light_source *) alloc(sizeof *ls);
81 (void) memset((genericptr_t) ls, 0, sizeof (light_source));
82 ls->next = gl.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 gl.light_base = ls;
91 gv.vision_full_recalc = 1; /* make the source show up */
92 return ls;
95 /* Find and delete a light source.
96 Assumes at most one light source is attached to an object at a time. */
97 void
98 del_light_source(int type, anything *id)
100 light_source *curr;
101 anything tmp_id;
103 tmp_id = cg.zeroany;
104 /* need to be prepared for dealing a with light source which
105 has only been partially restored during a level change
106 (in particular: chameleon vs prot. from shape changers) */
107 switch (type) {
108 case LS_NONE:
109 impossible("del_light_source:type=none");
110 tmp_id.a_uint = 0;
111 break;
112 case LS_OBJECT:
113 tmp_id.a_uint = id->a_obj ? id->a_obj->o_id : 0;
114 break;
115 case LS_MONSTER:
116 tmp_id.a_uint = id->a_monst->m_id;
117 break;
118 default:
119 tmp_id.a_uint = 0;
120 break;
123 /* find the light source from its id */
124 for (curr = gl.light_base; curr; curr = curr->next) {
125 if (curr->type != type)
126 continue;
127 if (curr->id.a_obj
128 == ((curr->flags & LSF_NEEDS_FIXUP) ? tmp_id.a_obj : id->a_obj))
129 break;
131 if (curr) {
132 delete_ls(curr);
133 } else {
134 impossible("del_light_source: not found type=%d, id=%s", type,
135 fmt_ptr((genericptr_t) id->a_obj));
139 /* remove a light source from the light_base list and free it */
140 staticfn void
141 delete_ls(light_source *ls)
143 light_source *curr, *prev;
145 for (prev = 0, curr = gl.light_base; curr;
146 prev = curr, curr = curr->next) {
147 if (curr == ls) {
148 if (prev)
149 prev->next = curr->next;
150 else
151 gl.light_base = curr->next;
152 break;
155 if (curr) {
156 assert(curr == ls);
157 (void) memset((genericptr_t) ls, 0, sizeof(light_source));
158 free((genericptr_t) ls);
159 gv.vision_full_recalc = 1;
160 } else {
161 impossible("delete_ls not found, ls=%s", fmt_ptr((genericptr_t) ls));
163 return;
166 /* Mark locations that are temporarily lit via mobile light sources. */
167 void
168 do_light_sources(seenV **cs_rows)
170 coordxy x, y, min_x, max_x, max_y;
171 int offset;
172 const coordxy *limits;
173 short at_hero_range = 0;
174 light_source *ls;
175 seenV *row;
177 for (ls = gl.light_base; ls; ls = ls->next) {
178 ls->flags &= ~LSF_SHOW;
181 * Check for moved light sources. It may be possible to
182 * save some effort if an object has not moved, but not in
183 * the current setup -- we need to recalculate for every
184 * vision recalc.
186 if (ls->type == LS_OBJECT) {
187 if (ls->range == 0 /* camera flash; caller has set ls->{x,y} */
188 || get_obj_location(ls->id.a_obj, &ls->x, &ls->y, 0))
189 ls->flags |= LSF_SHOW;
190 } else if (ls->type == LS_MONSTER) {
191 if (get_mon_location(ls->id.a_monst, &ls->x, &ls->y, 0))
192 ls->flags |= LSF_SHOW;
195 /* minor optimization: don't bother with duplicate light sources
196 at hero */
197 if (u_at(ls->x, ls->y)) {
198 if (at_hero_range >= ls->range)
199 ls->flags &= ~LSF_SHOW;
200 else
201 at_hero_range = ls->range;
204 if (ls->flags & LSF_SHOW) {
206 * Walk the points in the circle and see if they are
207 * visible from the center. If so, mark'em.
209 * Kevin's tests indicated that doing this brute-force
210 * method is faster for radius <= 3 (or so).
212 limits = circle_ptr(ls->range);
213 if ((max_y = (ls->y + ls->range)) >= ROWNO)
214 max_y = ROWNO - 1;
215 if ((y = (ls->y - ls->range)) < 0)
216 y = 0;
217 for (; y <= max_y; y++) {
218 row = cs_rows[y];
219 offset = limits[abs(y - ls->y)];
220 if ((min_x = (ls->x - offset)) < 1)
221 min_x = 1;
222 if ((max_x = (ls->x + offset)) >= COLNO)
223 max_x = COLNO - 1;
225 if (u_at(ls->x, ls->y)) {
227 * If the light source is located at the hero, then
228 * we can use the COULD_SEE bits already calculated
229 * by the vision system. More importantly than
230 * this optimization, is that it allows the vision
231 * system to correct problems with clear_path().
232 * The function clear_path() is a simple LOS
233 * path checker that doesn't go out of its way to
234 * make things look "correct". The vision system
235 * does this.
237 for (x = min_x; x <= max_x; x++)
238 if (row[x] & COULD_SEE)
239 row[x] |= TEMP_LIT;
240 } else {
241 for (x = min_x; x <= max_x; x++)
242 if ((ls->x == x && ls->y == y)
243 || clear_path((int) ls->x, (int) ls->y, x, y))
244 row[x] |= TEMP_LIT;
251 /* lit 'obj' has been thrown or kicked and is passing through x,y on the
252 way to its destination; show its light so that hero has a chance to
253 remember terrain, objects, and monsters being revealed;
254 if 'obj' is Null, <x,y> is being hit by a camera's light flash */
255 void
256 show_transient_light(struct obj *obj, coordxy x, coordxy y)
258 light_source *ls = 0;
259 anything cameraflash;
260 struct monst *mon;
261 int radius_squared;
263 /* Null object indicates camera flash */
264 if (!obj) {
265 /* no need to temporarily light an already lit spot */
266 if (levl[x][y].lit)
267 return;
269 cameraflash = cg.zeroany;
270 /* radius 0 will just light <x,y>; cameraflash.a_obj is Null */
271 ls = new_light_core(x, y, 0, LS_OBJECT, &cameraflash);
272 /* pacify static analysis; 'ls' is never Null for
273 new_light_core(,,0,LS_OBJECT,&zeroany) */
274 assert(ls != NULL);
275 } else {
276 /* thrown or kicked object which is emitting light; validate its
277 light source to obtain its radius (for monster sightings) */
278 for (ls = gl.light_base; ls; ls = ls->next) {
279 if (ls->type != LS_OBJECT)
280 continue;
281 if (ls->id.a_obj == obj)
282 break;
284 assert(obj != NULL); /* necessary condition to get into this 'else' */
285 if (!ls || obj->where != OBJ_FREE) {
286 impossible("transient light %s %s %s not %s?",
287 obj->lamplit ? "lit" : "unlit",
288 simpleonames(obj), otense(obj, "are"),
289 !ls ? "a light source" : "free");
290 return;
294 if (obj) /* put lit candle or lamp temporarily on the map */
295 place_object(obj, gb.bhitpos.x, gb.bhitpos.y);
296 else /* camera flash: no object; directly set light source's location */
297 ls->x = x, ls->y = y;
299 /* full recalc; runs do_light_sources() */
300 vision_recalc(0);
301 flush_screen(0);
303 radius_squared = ls->range * ls->range;
304 for (mon = fmon; mon; mon = mon->nmon) {
305 if (DEADMONSTER(mon) || (mon->isgd && !mon->mx))
306 continue;
307 /* light range is the radius of a circle and we're limiting
308 canseemon() to a square enclosing that circle, but setting
309 mtemplit 'erroneously' for a seen monster is not a problem;
310 it just flags monsters for another canseemon() check when
311 'obj' has reached its destination after missile traversal */
312 if (dist2(mon->mx, mon->my, x, y) <= radius_squared) {
313 if (canseemon(mon))
314 mon->mtemplit = 1;
316 /* [what about worm tails?] */
319 if (obj) { /* take thrown/kicked candle or lamp off the map */
320 nh_delay_output();
321 remove_object(obj);
325 /* delete any camera flash light sources and draw "remembered, unseen
326 monster" glyph at locations where a monster was flagged for being
327 visible during transient light movement but can't be seen now */
328 void
329 transient_light_cleanup(void)
331 struct monst *mon;
332 int mtempcount;
334 /* in case we're cleaning up a camera flash, remove all object light
335 sources which aren't associated with a specific object */
336 discard_flashes();
337 if (gv.vision_full_recalc) /* set by del_light_source() */
338 vision_recalc(0);
340 /* for thrown/kicked candle or lamp or for camera flash, some
341 monsters may have been mapped in light which has now gone away
342 so need to be replaced by "remembered, unseen monster" glyph */
343 mtempcount = 0;
344 for (mon = fmon; mon; mon = mon->nmon) {
345 if (DEADMONSTER(mon))
346 continue;
347 if (mon->mtemplit) {
348 mon->mtemplit = 0;
349 ++mtempcount;
350 if (!canspotmon(mon))
351 map_invisible(mon->mx, mon->my);
354 if (mtempcount)
355 flush_screen(0);
358 /* camera flashes have Null object; caller wants to get rid of them now */
359 staticfn void
360 discard_flashes(void)
362 light_source *ls, *nxt_ls;
364 for (ls = gl.light_base; ls; ls = nxt_ls) {
365 nxt_ls = ls->next;
366 if (ls->type == LS_OBJECT && !ls->id.a_obj)
367 delete_ls(ls);
371 /* (mon->mx == 0) implies migrating */
372 #define mon_is_local(mon) ((mon)->mx > 0)
374 struct monst *
375 find_mid(unsigned nid, unsigned fmflags)
377 struct monst *mtmp;
379 if ((fmflags & FM_YOU) && nid == 1)
380 return &gy.youmonst;
381 if (fmflags & FM_FMON)
382 for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
383 if (!DEADMONSTER(mtmp) && mtmp->m_id == nid)
384 return mtmp;
385 if (fmflags & FM_MIGRATE)
386 for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon)
387 if (mtmp->m_id == nid)
388 return mtmp;
389 if (fmflags & FM_MYDOGS)
390 for (mtmp = gm.mydogs; mtmp; mtmp = mtmp->nmon)
391 if (mtmp->m_id == nid)
392 return mtmp;
393 return (struct monst *) 0;
396 staticfn unsigned
397 whereis_mon(struct monst *mon, unsigned fmflags)
399 struct monst *mtmp;
401 if ((fmflags & FM_YOU) && mon == &gy.youmonst)
402 return FM_YOU;
403 if (fmflags & FM_FMON)
404 for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
405 if (mtmp == mon)
406 return FM_FMON;
407 if (fmflags & FM_MIGRATE)
408 for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon)
409 if (mtmp == mon)
410 return FM_MIGRATE;
411 if (fmflags & FM_MYDOGS)
412 for (mtmp = gm.mydogs; mtmp; mtmp = mtmp->nmon)
413 if (mtmp == mon)
414 return FM_MYDOGS;
415 return 0;
418 /* Save all light sources of the given range. */
419 void
420 save_light_sources(NHFILE *nhfp, int range)
422 int count, actual, is_global;
423 light_source **prev, *curr;
425 /* camera flash light sources have Null object and would trigger
426 impossible("no id!") below; they can only happen here if we're
427 in the midst of a panic save and they wouldn't be useful after
428 restore so just throw any that are present away */
429 discard_flashes();
430 gv.vision_full_recalc = 0;
432 if (perform_bwrite(nhfp)) {
433 count = maybe_write_ls(nhfp, range, FALSE);
434 if (nhfp->structlevel) {
435 bwrite(nhfp->fd, (genericptr_t) &count, sizeof count);
437 actual = maybe_write_ls(nhfp, range, TRUE);
438 if (actual != count)
439 panic("counted %d light sources, wrote %d! [range=%d]", count,
440 actual, range);
443 if (release_data(nhfp)) {
444 for (prev = &gl.light_base; (curr = *prev) != 0; ) {
445 if (!curr->id.a_monst) {
446 impossible("save_light_sources: no id! [range=%d]", range);
447 is_global = 0;
448 } else
449 switch (curr->type) {
450 case LS_OBJECT:
451 is_global = !obj_is_local(curr->id.a_obj);
452 break;
453 case LS_MONSTER:
454 is_global = !mon_is_local(curr->id.a_monst);
455 break;
456 default:
457 is_global = 0;
458 impossible("save_light_sources: bad type (%d) [range=%d]",
459 curr->type, range);
460 break;
462 /* if global and not doing local, or vice versa, remove it */
463 if (is_global ^ (range == RANGE_LEVEL)) {
464 *prev = curr->next;
465 (void) memset((genericptr_t) curr, 0, sizeof(light_source));
466 free((genericptr_t) curr);
467 } else {
468 prev = &(*prev)->next;
475 * Pull in the structures from disk, but don't recalculate the object
476 * pointers.
478 void
479 restore_light_sources(NHFILE *nhfp)
481 int count = 0;
482 light_source *ls;
484 /* restore elements */
485 if (nhfp->structlevel)
486 mread(nhfp->fd, (genericptr_t) &count, sizeof count);
488 while (count-- > 0) {
489 ls = (light_source *) alloc(sizeof(light_source));
490 if (nhfp->structlevel)
491 mread(nhfp->fd, (genericptr_t) ls, sizeof(light_source));
492 ls->next = gl.light_base;
493 gl.light_base = ls;
497 DISABLE_WARNING_FORMAT_NONLITERAL
499 /* to support '#stats' wizard-mode command */
500 void
501 light_stats(const char *hdrfmt, char *hdrbuf, long *count, long *size)
503 light_source *ls;
505 Sprintf(hdrbuf, hdrfmt, (long) sizeof (light_source));
506 *count = *size = 0L;
507 for (ls = gl.light_base; ls; ls = ls->next) {
508 ++*count;
509 *size += (long) sizeof *ls;
513 RESTORE_WARNING_FORMAT_NONLITERAL
515 /* Relink all lights that are so marked. */
516 void
517 relink_light_sources(boolean ghostly)
519 char which;
520 unsigned nid;
521 light_source *ls;
524 * Caveat:
525 * There has been at least one instance during to-be-3.7 development
526 * where the light_base linked list ended up with a circular link.
527 * If that happens, then once all the traversed elements have their
528 * LSF_NEEDS_FIXUP flag cleared, the traversal attempt will run wild.
530 * The circular list instance was blamed on attempting to restore
531 * a save file which should have been invalidated by version/patch/
532 * editlevel verification, but wasn't rejected because EDITLEVEL
533 * didn't get incremented when it should have been. Valid data should
534 * never produce the problem and it isn't possible in general to guard
535 * against code updates that neglect to set the verification info up
536 * to date.
539 for (ls = gl.light_base; ls; ls = ls->next) {
540 if (ls->flags & LSF_NEEDS_FIXUP) {
541 if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) {
542 nid = ls->id.a_uint;
543 if (ghostly && !lookup_id_mapping(nid, &nid))
544 panic("relink_light_sources: no id mapping");
546 which = '\0';
547 if (ls->type == LS_OBJECT) {
548 if ((ls->id.a_obj = find_oid(nid)) == 0)
549 which = 'o';
550 } else {
551 if ((ls->id.a_monst = find_mid(nid, FM_EVERYWHERE)) == 0)
552 which = 'm';
554 if (which != '\0')
555 panic("relink_light_sources: can't find %c_id %u",
556 which, nid);
557 } else {
558 panic("relink_light_sources: bad type (%d)", ls->type);
560 ls->flags &= ~LSF_NEEDS_FIXUP;
566 * Part of the light source save routine. Count up the number of light
567 * sources that would be written. If write_it is true, actually write
568 * the light source out.
570 staticfn int
571 maybe_write_ls(NHFILE *nhfp, int range, boolean write_it)
573 int count = 0, is_global;
574 light_source *ls;
576 for (ls = gl.light_base; ls; ls = ls->next) {
577 if (!ls->id.a_monst) {
578 impossible("maybe_write_ls: no id! [range=%d]", range);
579 continue;
581 switch (ls->type) {
582 case LS_OBJECT:
583 is_global = !obj_is_local(ls->id.a_obj);
584 break;
585 case LS_MONSTER:
586 is_global = !mon_is_local(ls->id.a_monst);
587 break;
588 default:
589 is_global = 0;
590 impossible("maybe_write_ls: bad type (%d) [range=%d]", ls->type,
591 range);
592 break;
594 /* if global and not doing local, or vice versa, count it */
595 if (is_global ^ (range == RANGE_LEVEL)) {
596 count++;
597 if (write_it)
598 write_ls(nhfp, ls);
602 return count;
605 void
606 light_sources_sanity_check(void)
608 light_source *ls;
609 struct monst *mtmp;
610 struct obj *otmp;
611 unsigned int auint;
613 for (ls = gl.light_base; ls; ls = ls->next) {
614 if (!ls->id.a_monst)
615 panic("insane light source: no id!");
616 if (ls->type == LS_OBJECT) {
617 otmp = ls->id.a_obj;
618 auint = otmp->o_id;
619 if (find_oid(auint) != otmp)
620 panic("insane light source: can't find obj #%u!", auint);
621 } else if (ls->type == LS_MONSTER) {
622 mtmp = (struct monst *) ls->id.a_monst;
623 auint = mtmp->m_id;
624 if (find_mid(auint, FM_EVERYWHERE) != mtmp)
625 panic("insane light source: can't find mon #%u!", auint);
626 } else {
627 panic("insane light source: bad ls type %d", ls->type);
632 /* Write a light source structure to disk. */
633 staticfn void
634 write_ls(NHFILE *nhfp, light_source *ls)
636 anything arg_save;
637 struct obj *otmp;
638 struct monst *mtmp;
640 if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) {
641 if (ls->flags & LSF_NEEDS_FIXUP) {
642 if (nhfp->structlevel)
643 bwrite(nhfp->fd, (genericptr_t) ls, sizeof(light_source));
644 } else {
645 /* replace object pointer with id for write, then put back */
646 arg_save = ls->id;
647 if (ls->type == LS_OBJECT) {
648 otmp = ls->id.a_obj;
649 ls->id = cg.zeroany;
650 ls->id.a_uint = otmp->o_id;
651 if (find_oid((unsigned) ls->id.a_uint) != otmp) {
652 impossible("write_ls: can't find obj #%u!",
653 ls->id.a_uint);
654 ls->flags |= LSF_IS_PROBLEMATIC;
656 } else { /* ls->type == LS_MONSTER */
657 unsigned monloc = 0;
659 mtmp = (struct monst *) ls->id.a_monst;
661 /* The monster pointer has been stashed in the light source
662 * for a while and while there is code meant to clean-up the
663 * light source aspects if a monster goes away, there have
664 * been some reports of light source issues, such as when
665 * going to the planes.
667 * Verify that the stashed monst pointer is still present
668 * in one of the monster chains before pulling subfield
669 * values such as m_id from it, to avoid any attempt to
670 * pull random m_id value from (now) freed memory.
672 * find_mid() disregards a DEADMONSTER, but whereis_mon()
673 * does not. */
675 if ((monloc = whereis_mon(mtmp, FM_EVERYWHERE)) != 0) {
676 ls->id = cg.zeroany;
677 ls->id.a_uint = mtmp->m_id;
678 if (find_mid((unsigned) ls->id.a_uint, monloc) != mtmp) {
679 impossible("write_ls: can't find mon%s #%u!",
680 DEADMONSTER(mtmp) ? " because it's dead"
681 : "",
682 ls->id.a_uint);
683 ls->flags |= LSF_IS_PROBLEMATIC;
685 } else {
686 impossible(
687 "write_ls: stashed monst ptr not in any chain");
688 ls->flags |= LSF_IS_PROBLEMATIC;
691 if (ls->flags & LSF_IS_PROBLEMATIC) {
692 /* TODO: cleanup this ls, or skip writing it */
694 ls->flags |= LSF_NEEDS_FIXUP;
695 if (nhfp->structlevel)
696 bwrite(nhfp->fd, (genericptr_t) ls, sizeof(light_source));
697 ls->id = arg_save;
698 ls->flags &= ~LSF_NEEDS_FIXUP;
699 ls->flags &= ~LSF_IS_PROBLEMATIC;
701 } else {
702 impossible("write_ls: bad type (%d)", ls->type);
706 /* Change light source's ID from src to dest. */
707 void
708 obj_move_light_source(struct obj *src, struct obj *dest)
710 light_source *ls;
712 for (ls = gl.light_base; ls; ls = ls->next)
713 if (ls->type == LS_OBJECT && ls->id.a_obj == src)
714 ls->id.a_obj = dest;
715 src->lamplit = 0;
716 dest->lamplit = 1;
719 /* return true if there exist any light sources */
720 boolean
721 any_light_source(void)
723 return (boolean) (gl.light_base != (light_source *) 0);
727 * Snuff an object light source if at (x,y). This currently works
728 * only for burning light sources.
730 void
731 snuff_light_source(coordxy x, coordxy y)
733 light_source *ls;
734 struct obj *obj;
736 for (ls = gl.light_base; ls; ls = ls->next)
738 * Is this position check valid??? Can I assume that the positions
739 * will always be correct because the objects would have been
740 * updated with the last vision update? [Is that recent enough???]
742 if (ls->type == LS_OBJECT && ls->x == x && ls->y == y) {
743 obj = ls->id.a_obj;
744 if (obj_is_burning(obj)) {
745 /* The only way to snuff Sunsword is to unwield it. Darkness
746 * scrolls won't affect it. (If we got here because it was
747 * dropped or thrown inside a monster, this won't matter
748 * anyway because it will go out when dropped.)
750 if (artifact_light(obj))
751 continue;
752 end_burn(obj, obj->otyp != MAGIC_LAMP);
754 * The current ls element has just been removed (and
755 * ls->next is now invalid). Return assuming that there
756 * is only one light source attached to each object.
758 return;
763 /* Return TRUE if object sheds any light at all. */
764 boolean
765 obj_sheds_light(struct obj *obj)
767 /* so far, only burning objects shed light */
768 return obj_is_burning(obj);
771 /* Return TRUE if sheds light AND will be snuffed by end_burn(). */
772 boolean
773 obj_is_burning(struct obj *obj)
775 return (boolean) (obj->lamplit && (ignitable(obj)
776 || artifact_light(obj)));
779 /* copy the light source(s) attached to src, and attach it/them to dest */
780 void
781 obj_split_light_source(struct obj *src, struct obj *dest)
783 light_source *ls, *new_ls;
785 for (ls = gl.light_base; ls; ls = ls->next)
786 if (ls->type == LS_OBJECT && ls->id.a_obj == src) {
788 * Insert the new source at beginning of list. This will
789 * never interfere us walking down the list - we are already
790 * past the insertion point.
792 new_ls = (light_source *) alloc(sizeof(light_source));
793 *new_ls = *ls;
794 if (Is_candle(src)) {
795 /* split candles may emit less light than original group */
796 ls->range = candle_light_range(src);
797 new_ls->range = candle_light_range(dest);
798 gv.vision_full_recalc = 1; /* in case range changed */
800 new_ls->id.a_obj = dest;
801 new_ls->next = gl.light_base;
802 gl.light_base = new_ls;
803 dest->lamplit = 1; /* now an active light source */
807 /* light source `src' has been folded into light source `dest';
808 used for merging lit candles and adding candle(s) to lit candelabrum */
809 void
810 obj_merge_light_sources(struct obj *src, struct obj *dest)
812 light_source *ls;
814 /* src == dest implies adding to candelabrum */
815 if (src != dest)
816 end_burn(src, TRUE); /* extinguish candles */
818 for (ls = gl.light_base; ls; ls = ls->next)
819 if (ls->type == LS_OBJECT && ls->id.a_obj == dest) {
820 ls->range = candle_light_range(dest);
821 gv.vision_full_recalc = 1; /* in case range changed */
822 break;
826 /* light source `obj' is being made brighter or dimmer */
827 void
828 obj_adjust_light_radius(struct obj *obj, int new_radius)
830 light_source *ls;
832 for (ls = gl.light_base; ls; ls = ls->next)
833 if (ls->type == LS_OBJECT && ls->id.a_obj == obj) {
834 if (new_radius != ls->range)
835 gv.vision_full_recalc = 1;
836 ls->range = new_radius;
837 return;
839 impossible("obj_adjust_light_radius: can't find %s", xname(obj));
842 /* Candlelight is proportional to the number of candles;
843 minimum range is 2 rather than 1 for playability. */
845 candle_light_range(struct obj *obj)
847 int radius;
849 if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
851 * The special candelabrum emits more light than the
852 * corresponding number of candles would.
853 * 1..3 candles, range 2 (minimum range);
854 * 4..6 candles, range 3 (normal lamp range);
855 * 7 candles, range 4 (bright).
857 radius = (obj->spe < 4) ? 2 : (obj->spe < 7) ? 3 : 4;
858 } else if (Is_candle(obj)) {
860 * Range is incremented quadratically. You can get the same
861 * amount of light as from a lamp with 4 candles, and
862 * even better light with 9 candles, and so on.
863 * 1..3 candles, range 2;
864 * 4..8 candles, range 3;
865 * 9..15 candles, range 4; &c.
867 long n = obj->quan;
869 radius = 1; /* always incremented at least once */
870 while(radius*radius <= n && radius < MAX_RADIUS) {
871 radius++;
873 } else {
874 /* we're only called for lit candelabrum or candles */
875 /* impossible("candlelight for %d?", obj->otyp); */
876 radius = 3; /* lamp's value */
878 return radius;
881 /* light emitting artifact's range depends upon its curse/bless state */
883 arti_light_radius(struct obj *obj)
885 int res;
888 * Used by begin_burn() when setting up a new light source
889 * (obj->lamplit will already be set by this point) and
890 * also by bless()/unbless()/uncurse()/curse() to decide
891 * whether to call obj_adjust_light_radius().
894 /* sanity check [simplifies usage by bless()/curse()/&c] */
895 if (!obj->lamplit || !artifact_light(obj))
896 return 0;
898 /* cursed radius of 1 is not noticeable for an item that's
899 carried by the hero but is if it's carried by a monster
900 or left lit on the floor (not applicable for Sunsword) */
901 res = (obj->blessed ? 3 : !obj->cursed ? 2 : 1);
903 /* if poly'd into gold dragon with embedded scales, make the scales
904 have minimum radiance (hero as light source will use light radius
905 based on monster form); otherwise, worn gold DSM gives off more
906 light than other light sources */
907 if (obj == uskin)
908 res = 1;
909 else if (obj->otyp == GOLD_DRAGON_SCALE_MAIL) /* DSM but not scales */
910 ++res;
912 return res;
915 /* adverb describing lit artifact's light; radius varies depending upon
916 curse/bless state; also used for gold dragon scales/scale mail */
917 const char *
918 arti_light_description(struct obj *obj)
920 switch (arti_light_radius(obj)) {
921 case 4:
922 return "radiantly"; /* blessed gold dragon scale mail */
923 case 3:
924 return "brilliantly"; /* blessed artifact, uncursed gold DSM */
925 case 2:
926 return "brightly"; /* uncursed artifact, cursed gold DSM */
927 case 1:
928 return "dimly"; /* cursed artifact, embedded scales */
929 default:
930 break;
932 return "strangely";
935 /* the #lightsources command */
937 wiz_light_sources(void)
939 winid win;
940 char buf[BUFSZ];
941 light_source *ls;
943 win = create_nhwindow(NHW_MENU); /* corner text window */
944 if (win == WIN_ERR)
945 return ECMD_OK;
947 Sprintf(buf, "Mobile light sources: hero @ (%2d,%2d)", u.ux, u.uy);
948 putstr(win, 0, buf);
949 putstr(win, 0, "");
951 if (gl.light_base) {
952 putstr(win, 0, "location range flags type id");
953 putstr(win, 0, "-------- ----- ------ ---- -------");
954 for (ls = gl.light_base; ls; ls = ls->next) {
955 Sprintf(buf, " %2d,%2d %2d 0x%04x %s %s", ls->x, ls->y,
956 ls->range, ls->flags,
957 (ls->type == LS_OBJECT
958 ? "obj"
959 : ls->type == LS_MONSTER
960 ? (mon_is_local(ls->id.a_monst)
961 ? "mon"
962 : (ls->id.a_monst == &gy.youmonst)
963 ? "you"
964 /* migrating monster */
965 : "<m>")
966 : "???"),
967 fmt_ptr(ls->id.a_void));
968 putstr(win, 0, buf);
970 } else
971 putstr(win, 0, "<none>");
973 display_nhwindow(win, FALSE);
974 destroy_nhwindow(win);
976 return ECMD_OK;
979 /* for 'onefile' processing where end of this file isn't necessarily the
980 end of the source code seen by the compiler */
981 #undef LSF_SHOW
982 #undef LSF_NEEDS_FIXUP
983 #undef mon_is_local
985 /*light.c*/