1 /* NetHack 3.7	light.c	$NHDT-Date: 1604442297 2020/11/03 22:24:57 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.46 $ */
2 /* Copyright (c) Dean Luick, 1994                                       */
3 /* NetHack may be freely redistributed.  See license for details.       */
4 
5 #include "hack.h"
6 
7 /*
8  * Mobile light sources.
9  *
10  * This implementation minimizes memory at the expense of extra
11  * recalculations.
12  *
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 both g.youmonst.m_id and
17  * g.youmonst.mx will always remain 0.
18  *
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.
23  *
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.
33  *
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.
38  */
39 
40 /* flags */
41 #define LSF_SHOW 0x1        /* display the light source */
42 #define LSF_NEEDS_FIXUP 0x2 /* need oid fixup */
43 
44 static light_source *new_light_core(xchar, xchar, int, int, anything *);
45 static void discard_flashes(void);
46 static void write_ls(NHFILE *, light_source *);
47 static int maybe_write_ls(NHFILE *, int, boolean);
48 
49 /* imported from vision.c, for small circles */
50 extern xchar circle_data[];
51 extern xchar circle_start[];
52 
53 
54 /* Create a new light source.  Caller (and extern.h) doesn't need to know
55    anything about type 'light_source'. */
56 void
new_light_source(xchar x,xchar y,int range,int type,anything * id)57 new_light_source(xchar x, xchar y, int range, int type, anything *id)
58 {
59     (void) new_light_core(x, y, range, type, id);
60 }
61 
62 /* Create a new light source and return it.  Only used within this file. */
63 static light_source *
new_light_core(xchar x,xchar y,int range,int type,anything * id)64 new_light_core(xchar x, xchar y, int range, int type, anything *id)
65 {
66     light_source *ls;
67 
68     if (range > MAX_RADIUS || range < 0
69         /* camera flash uses radius 0 and passes Null object */
70         || (range == 0 && (type != LS_OBJECT || id->a_obj != 0))) {
71         impossible("new_light_source:  illegal range %d", range);
72 	return (light_source *) 0;
73     }
74 
75     ls = (light_source *) alloc(sizeof *ls);
76 
77     ls->next = g.light_base;
78     ls->x = x;
79     ls->y = y;
80     ls->range = range;
81     ls->type = type;
82     ls->id = *id;
83     ls->flags = 0;
84     g.light_base = ls;
85 
86     g.vision_full_recalc = 1; /* make the source show up */
87     return ls;
88 }
89 
90 /*
91  * Delete a light source. This assumes only one light source is attached
92  * to an object at a time.
93  */
94 void
del_light_source(int type,anything * id)95 del_light_source(int type, anything *id)
96 {
97     light_source *curr, *prev;
98     anything tmp_id;
99 
100     tmp_id = cg.zeroany;
101     /* need to be prepared for dealing a with light source which
102        has only been partially restored during a level change
103        (in particular: chameleon vs prot. from shape changers) */
104     switch (type) {
105     case LS_OBJECT:
106         tmp_id.a_uint = id->a_obj ? id->a_obj->o_id : 0;
107         break;
108     case LS_MONSTER:
109         tmp_id.a_uint = id->a_monst->m_id;
110         break;
111     default:
112         tmp_id.a_uint = 0;
113         break;
114     }
115 
116     for (prev = 0, curr = g.light_base; curr; prev = curr, curr = curr->next) {
117         if (curr->type != type)
118             continue;
119         if (curr->id.a_obj
120             == ((curr->flags & LSF_NEEDS_FIXUP) ? tmp_id.a_obj : id->a_obj)) {
121             if (prev)
122                 prev->next = curr->next;
123             else
124                 g.light_base = curr->next;
125 
126             free((genericptr_t) curr);
127             g.vision_full_recalc = 1;
128             return;
129         }
130     }
131     impossible("del_light_source: not found type=%d, id=%s", type,
132                fmt_ptr((genericptr_t) id->a_obj));
133 }
134 
135 /* Mark locations that are temporarily lit via mobile light sources. */
136 void
do_light_sources(xchar ** cs_rows)137 do_light_sources(xchar **cs_rows)
138 {
139     int x, y, min_x, max_x, max_y, offset;
140     xchar *limits;
141     short at_hero_range = 0;
142     light_source *ls;
143     xchar *row;
144 
145     for (ls = g.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 (ls->range == 0 /* camera flash; caller has set ls->{x,y} */
156                 || get_obj_location(ls->id.a_obj, &ls->x, &ls->y, 0))
157                 ls->flags |= LSF_SHOW;
158         } else if (ls->type == LS_MONSTER) {
159             if (get_mon_location(ls->id.a_monst, &ls->x, &ls->y, 0))
160                 ls->flags |= LSF_SHOW;
161         }
162 
163         /* minor optimization: don't bother with duplicate light sources
164            at hero */
165         if (ls->x == u.ux && ls->y == u.uy) {
166             if (at_hero_range >= ls->range)
167                 ls->flags &= ~LSF_SHOW;
168             else
169                 at_hero_range = ls->range;
170         }
171 
172         if (ls->flags & LSF_SHOW) {
173             /*
174              * Walk the points in the circle and see if they are
175              * visible from the center.  If so, mark'em.
176              *
177              * Kevin's tests indicated that doing this brute-force
178              * method is faster for radius <= 3 (or so).
179              */
180             limits = circle_ptr(ls->range);
181             if ((max_y = (ls->y + ls->range)) >= ROWNO)
182                 max_y = ROWNO - 1;
183             if ((y = (ls->y - ls->range)) < 0)
184                 y = 0;
185             for (; y <= max_y; y++) {
186                 row = cs_rows[y];
187                 offset = limits[abs(y - ls->y)];
188                 if ((min_x = (ls->x - offset)) < 1)
189                     min_x = 1;
190                 if ((max_x = (ls->x + offset)) >= COLNO)
191                     max_x = COLNO - 1;
192 
193                 if (ls->x == u.ux && ls->y == u.uy) {
194                     /*
195                      * If the light source is located at the hero, then
196                      * we can use the COULD_SEE bits already calculated
197                      * by the vision system.  More importantly than
198                      * this optimization, is that it allows the vision
199                      * system to correct problems with clear_path().
200                      * The function clear_path() is a simple LOS
201                      * path checker that doesn't go out of its way to
202                      * make things look "correct".  The vision system
203                      * does this.
204                      */
205                     for (x = min_x; x <= max_x; x++)
206                         if (row[x] & COULD_SEE)
207                             row[x] |= TEMP_LIT;
208                 } else {
209                     for (x = min_x; x <= max_x; x++)
210                         if ((ls->x == x && ls->y == y)
211                             || clear_path((int) ls->x, (int) ls->y, x, y))
212                             row[x] |= TEMP_LIT;
213                 }
214             }
215         }
216     }
217 }
218 
219 /* lit 'obj' has been thrown or kicked and is passing through x,y on the
220    way to its destination; show its light so that hero has a chance to
221    remember terrain, objects, and monsters being revealed;
222    if 'obj' is Null, <x,y> is being hit by a camera's light flash */
223 void
show_transient_light(struct obj * obj,int x,int y)224 show_transient_light(struct obj *obj, int x, int y)
225 {
226     light_source *ls = 0;
227     anything cameraflash;
228     struct monst *mon;
229     int radius_squared;
230 
231     /* Null object indicates camera flash */
232     if (!obj) {
233         /* no need to temporarily light an already lit spot */
234         if (levl[x][y].lit)
235             return;
236 
237         cameraflash = cg.zeroany;
238         /* radius 0 will just light <x,y>; cameraflash.a_obj is Null */
239         ls = new_light_core(x, y, 0, LS_OBJECT, &cameraflash);
240     } else {
241         /* thrown or kicked object which is emitting light; validate its
242            light source to obtain its radius (for monster sightings) */
243         for (ls = g.light_base; ls; ls = ls->next) {
244             if (ls->type != LS_OBJECT)
245                 continue;
246             if (ls->id.a_obj == obj)
247                 break;
248         }
249     }
250     if (!ls || (obj && obj->where != OBJ_FREE)) {
251         impossible("transient light %s %s is not %s?",
252                    obj->lamplit ? "lit" : "unlit", xname(obj),
253                    !ls ? "a light source" : "free");
254         return;
255     }
256 
257     if (obj) /* put lit candle or lamp temporarily on the map */
258         place_object(obj, g.bhitpos.x, g.bhitpos.y);
259     else /* camera flash:  no object; directly set light source's location */
260         ls->x = x, ls->y = y;
261 
262     /* full recalc; runs do_light_sources() */
263     vision_recalc(0);
264     flush_screen(0);
265 
266     radius_squared = ls->range * ls->range;
267     for (mon = fmon; mon; mon = mon->nmon) {
268         if (DEADMONSTER(mon) || (mon->isgd && !mon->mx))
269             continue;
270         /* light range is the radius of a circle and we're limiting
271            canseemon() to a square exclosing that circle, but setting
272            mtemplit 'erroneously' for a seen monster is not a problem;
273            it just flags monsters for another canseemon() check when
274            'obj' has reached its destination after missile traversal */
275         if (dist2(mon->mx, mon->my, x, y) <= radius_squared) {
276             if (canseemon(mon))
277                 mon->mtemplit = 1;
278         }
279         /* [what about worm tails?] */
280     }
281 
282     if (obj) { /* take thrown/kicked candle or lamp off the map */
283         delay_output();
284         remove_object(obj);
285     }
286 }
287 
288 /* delete any camera flash light sources and draw "remembered, unseen
289    monster" glyph at locations where a monster was flagged for being
290    visible during transient light movement but can't be seen now */
291 void
transient_light_cleanup(void)292 transient_light_cleanup(void)
293 {
294     struct monst *mon;
295     int mtempcount;
296 
297     /* in case we're cleaning up a camera flash, remove all object light
298        sources which aren't associated with a specific object */
299     discard_flashes();
300     if (g.vision_full_recalc) /* set by del_light_source() */
301         vision_recalc(0);
302 
303     /* for thrown/kicked candle or lamp or for camera flash, some
304        monsters may have been mapped in light which has now gone away
305        so need to be replaced by "remembered, unseen monster" glyph */
306     mtempcount = 0;
307     for (mon = fmon; mon; mon = mon->nmon) {
308         if (DEADMONSTER(mon))
309             continue;
310         if (mon->mtemplit) {
311             mon->mtemplit = 0;
312             ++mtempcount;
313             if (!canspotmon(mon))
314                 map_invisible(mon->mx, mon->my);
315         }
316     }
317     if (mtempcount)
318         flush_screen(0);
319 }
320 
321 /* camera flashes have Null object; caller wants to get rid of them now */
322 static void
discard_flashes(void)323 discard_flashes(void)
324 {
325     light_source *ls, *nxt_ls;
326 
327     for (ls = g.light_base; ls; ls = nxt_ls) {
328         nxt_ls = ls->next;
329         if (ls->type != LS_OBJECT)
330             continue;
331         if (!ls->id.a_obj)
332             del_light_source(LS_OBJECT, &ls->id);
333     }
334 }
335 
336 /* (mon->mx == 0) implies migrating */
337 #define mon_is_local(mon) ((mon)->mx > 0)
338 
339 struct monst *
find_mid(unsigned nid,unsigned fmflags)340 find_mid(unsigned nid, unsigned fmflags)
341 {
342     struct monst *mtmp;
343 
344     if (!nid)
345         return &g.youmonst;
346     if (fmflags & FM_FMON)
347         for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
348             if (!DEADMONSTER(mtmp) && mtmp->m_id == nid)
349                 return mtmp;
350     if (fmflags & FM_MIGRATE)
351         for (mtmp = g.migrating_mons; mtmp; mtmp = mtmp->nmon)
352             if (mtmp->m_id == nid)
353                 return mtmp;
354     if (fmflags & FM_MYDOGS)
355         for (mtmp = g.mydogs; mtmp; mtmp = mtmp->nmon)
356             if (mtmp->m_id == nid)
357                 return mtmp;
358     return (struct monst *) 0;
359 }
360 
361 /* Save all light sources of the given range. */
362 void
save_light_sources(NHFILE * nhfp,int range)363 save_light_sources(NHFILE *nhfp, int range)
364 {
365     int count, actual, is_global;
366     light_source **prev, *curr;
367 
368     /* camera flash light sources have Null object and would trigger
369        impossible("no id!") below; they can only happen here if we're
370        in the midst of a panic save and they wouldn't be useful after
371        restore so just throw any that are present away */
372     discard_flashes();
373     g.vision_full_recalc = 0;
374 
375     if (perform_bwrite(nhfp)) {
376         count = maybe_write_ls(nhfp, range, FALSE);
377         if (nhfp->structlevel) {
378             bwrite(nhfp->fd, (genericptr_t) &count, sizeof count);
379         }
380         actual = maybe_write_ls(nhfp, range, TRUE);
381         if (actual != count)
382             panic("counted %d light sources, wrote %d! [range=%d]", count,
383                   actual, range);
384     }
385 
386      if (release_data(nhfp)) {
387         for (prev = &g.light_base; (curr = *prev) != 0; ) {
388             if (!curr->id.a_monst) {
389                 impossible("save_light_sources: no id! [range=%d]", range);
390                 is_global = 0;
391             } else
392                 switch (curr->type) {
393                 case LS_OBJECT:
394                     is_global = !obj_is_local(curr->id.a_obj);
395                     break;
396                 case LS_MONSTER:
397                     is_global = !mon_is_local(curr->id.a_monst);
398                     break;
399                 default:
400                     is_global = 0;
401                     impossible("save_light_sources: bad type (%d) [range=%d]",
402                                curr->type, range);
403                     break;
404                 }
405             /* if global and not doing local, or vice versa, remove it */
406             if (is_global ^ (range == RANGE_LEVEL)) {
407                 *prev = curr->next;
408                 free((genericptr_t) curr);
409             } else {
410                 prev = &(*prev)->next;
411             }
412         }
413     }
414 }
415 
416 /*
417  * Pull in the structures from disk, but don't recalculate the object
418  * pointers.
419  */
420 void
restore_light_sources(NHFILE * nhfp)421 restore_light_sources(NHFILE *nhfp)
422 {
423     int count = 0;
424     light_source *ls;
425 
426     /* restore elements */
427     if (nhfp->structlevel)
428         mread(nhfp->fd, (genericptr_t) &count, sizeof count);
429 
430     while (count-- > 0) {
431         ls = (light_source *) alloc(sizeof(light_source));
432         if (nhfp->structlevel)
433             mread(nhfp->fd, (genericptr_t) ls, sizeof(light_source));
434         ls->next = g.light_base;
435         g.light_base = ls;
436     }
437 }
438 
439 DISABLE_WARNING_FORMAT_NONLITERAL
440 
441 /* to support '#stats' wizard-mode command */
442 void
light_stats(const char * hdrfmt,char * hdrbuf,long * count,long * size)443 light_stats(const char *hdrfmt, char *hdrbuf, long *count, long *size)
444 {
445     light_source *ls;
446 
447     Sprintf(hdrbuf, hdrfmt, (long) sizeof (light_source));
448     *count = *size = 0L;
449     for (ls = g.light_base; ls; ls = ls->next) {
450         ++*count;
451         *size += (long) sizeof *ls;
452     }
453 }
454 
455 RESTORE_WARNING_FORMAT_NONLITERAL
456 
457 /* Relink all lights that are so marked. */
458 void
relink_light_sources(boolean ghostly)459 relink_light_sources(boolean ghostly)
460 {
461     char which;
462     unsigned nid;
463     light_source *ls;
464 
465     for (ls = g.light_base; ls; ls = ls->next) {
466         if (ls->flags & LSF_NEEDS_FIXUP) {
467             if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) {
468                 if (ghostly) {
469                     if (!lookup_id_mapping(ls->id.a_uint, &nid))
470                         impossible("relink_light_sources: no id mapping");
471                 } else
472                     nid = ls->id.a_uint;
473                 if (ls->type == LS_OBJECT) {
474                     which = 'o';
475                     ls->id.a_obj = find_oid(nid);
476                 } else {
477                     which = 'm';
478                     ls->id.a_monst = find_mid(nid, FM_EVERYWHERE);
479                 }
480                 if (!ls->id.a_monst)
481                     impossible("relink_light_sources: cant find %c_id %d",
482                                which, nid);
483             } else
484                 impossible("relink_light_sources: bad type (%d)", ls->type);
485 
486             ls->flags &= ~LSF_NEEDS_FIXUP;
487         }
488     }
489 }
490 
491 /*
492  * Part of the light source save routine.  Count up the number of light
493  * sources that would be written.  If write_it is true, actually write
494  * the light source out.
495  */
496 static int
maybe_write_ls(NHFILE * nhfp,int range,boolean write_it)497 maybe_write_ls(NHFILE *nhfp, int range, boolean write_it)
498 {
499     int count = 0, is_global;
500     light_source *ls;
501 
502     for (ls = g.light_base; ls; ls = ls->next) {
503         if (!ls->id.a_monst) {
504             impossible("maybe_write_ls: no id! [range=%d]", range);
505             continue;
506         }
507         switch (ls->type) {
508         case LS_OBJECT:
509             is_global = !obj_is_local(ls->id.a_obj);
510             break;
511         case LS_MONSTER:
512             is_global = !mon_is_local(ls->id.a_monst);
513             break;
514         default:
515             is_global = 0;
516             impossible("maybe_write_ls: bad type (%d) [range=%d]", ls->type,
517                        range);
518             break;
519         }
520         /* if global and not doing local, or vice versa, count it */
521         if (is_global ^ (range == RANGE_LEVEL)) {
522             count++;
523             if (write_it)
524                 write_ls(nhfp, ls);
525         }
526     }
527 
528     return count;
529 }
530 
531 void
light_sources_sanity_check(void)532 light_sources_sanity_check(void)
533 {
534     light_source *ls;
535     struct monst *mtmp;
536     struct obj *otmp;
537     unsigned int auint;
538 
539     for (ls = g.light_base; ls; ls = ls->next) {
540         if (!ls->id.a_monst)
541             panic("insane light source: no id!");
542         if (ls->type == LS_OBJECT) {
543             otmp = (struct obj *) ls->id.a_obj;
544             auint = otmp->o_id;
545             if (find_oid(auint) != otmp)
546                 panic("insane light source: can't find obj #%u!", auint);
547         } else if (ls->type == LS_MONSTER) {
548             mtmp = (struct monst *) ls->id.a_monst;
549             auint = mtmp->m_id;
550             if (find_mid(auint, FM_EVERYWHERE) != mtmp)
551                 panic("insane light source: can't find mon #%u!", auint);
552         } else {
553             panic("insane light source: bad ls type %d", ls->type);
554         }
555     }
556 }
557 
558 /* Write a light source structure to disk. */
559 static void
write_ls(NHFILE * nhfp,light_source * ls)560 write_ls(NHFILE *nhfp, light_source *ls)
561 {
562     anything arg_save;
563     struct obj *otmp;
564     struct monst *mtmp;
565 
566     if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) {
567         if (ls->flags & LSF_NEEDS_FIXUP) {
568             if (nhfp->structlevel)
569                 bwrite(nhfp->fd, (genericptr_t) ls, sizeof(light_source));
570         } else {
571             /* replace object pointer with id for write, then put back */
572             arg_save = ls->id;
573             if (ls->type == LS_OBJECT) {
574                 otmp = ls->id.a_obj;
575                 ls->id = cg.zeroany;
576                 ls->id.a_uint = otmp->o_id;
577                 if (find_oid((unsigned) ls->id.a_uint) != otmp)
578                     impossible("write_ls: can't find obj #%u!",
579                                ls->id.a_uint);
580             } else { /* ls->type == LS_MONSTER */
581                 mtmp = (struct monst *) ls->id.a_monst;
582                 ls->id = cg.zeroany;
583                 ls->id.a_uint = mtmp->m_id;
584                 if (find_mid((unsigned) ls->id.a_uint, FM_EVERYWHERE) != mtmp)
585                     impossible("write_ls: can't find mon #%u!",
586                                ls->id.a_uint);
587             }
588             ls->flags |= LSF_NEEDS_FIXUP;
589             if (nhfp->structlevel)
590                 bwrite(nhfp->fd, (genericptr_t) ls, sizeof(light_source));
591             ls->id = arg_save;
592             ls->flags &= ~LSF_NEEDS_FIXUP;
593         }
594     } else {
595         impossible("write_ls: bad type (%d)", ls->type);
596     }
597 }
598 
599 /* Change light source's ID from src to dest. */
600 void
obj_move_light_source(struct obj * src,struct obj * dest)601 obj_move_light_source(struct obj *src, struct obj *dest)
602 {
603     light_source *ls;
604 
605     for (ls = g.light_base; ls; ls = ls->next)
606         if (ls->type == LS_OBJECT && ls->id.a_obj == src)
607             ls->id.a_obj = dest;
608     src->lamplit = 0;
609     dest->lamplit = 1;
610 }
611 
612 /* return true if there exist any light sources */
613 boolean
any_light_source(void)614 any_light_source(void)
615 {
616     return (boolean) (g.light_base != (light_source *) 0);
617 }
618 
619 /*
620  * Snuff an object light source if at (x,y).  This currently works
621  * only for burning light sources.
622  */
623 void
snuff_light_source(int x,int y)624 snuff_light_source(int x, int y)
625 {
626     light_source *ls;
627     struct obj *obj;
628 
629     for (ls = g.light_base; ls; ls = ls->next)
630         /*
631          * Is this position check valid??? Can I assume that the positions
632          * will always be correct because the objects would have been
633          * updated with the last vision update?  [Is that recent enough???]
634          */
635         if (ls->type == LS_OBJECT && ls->x == x && ls->y == y) {
636             obj = ls->id.a_obj;
637             if (obj_is_burning(obj)) {
638                 /* The only way to snuff Sunsword is to unwield it.  Darkness
639                  * scrolls won't affect it.  (If we got here because it was
640                  * dropped or thrown inside a monster, this won't matter
641                  * anyway because it will go out when dropped.)
642                  */
643                 if (artifact_light(obj))
644                     continue;
645                 end_burn(obj, obj->otyp != MAGIC_LAMP);
646                 /*
647                  * The current ls element has just been removed (and
648                  * ls->next is now invalid).  Return assuming that there
649                  * is only one light source attached to each object.
650                  */
651                 return;
652             }
653         }
654 }
655 
656 /* Return TRUE if object sheds any light at all. */
657 boolean
obj_sheds_light(struct obj * obj)658 obj_sheds_light(struct obj *obj)
659 {
660     /* so far, only burning objects shed light */
661     return obj_is_burning(obj);
662 }
663 
664 /* Return TRUE if sheds light AND will be snuffed by end_burn(). */
665 boolean
obj_is_burning(struct obj * obj)666 obj_is_burning(struct obj *obj)
667 {
668     return (boolean) (obj->lamplit && (ignitable(obj)
669                                        || artifact_light(obj)));
670 }
671 
672 /* copy the light source(s) attached to src, and attach it/them to dest */
673 void
obj_split_light_source(struct obj * src,struct obj * dest)674 obj_split_light_source(struct obj *src, struct obj *dest)
675 {
676     light_source *ls, *new_ls;
677 
678     for (ls = g.light_base; ls; ls = ls->next)
679         if (ls->type == LS_OBJECT && ls->id.a_obj == src) {
680             /*
681              * Insert the new source at beginning of list.  This will
682              * never interfere us walking down the list - we are already
683              * past the insertion point.
684              */
685             new_ls = (light_source *) alloc(sizeof(light_source));
686             *new_ls = *ls;
687             if (Is_candle(src)) {
688                 /* split candles may emit less light than original group */
689                 ls->range = candle_light_range(src);
690                 new_ls->range = candle_light_range(dest);
691                 g.vision_full_recalc = 1; /* in case range changed */
692             }
693             new_ls->id.a_obj = dest;
694             new_ls->next = g.light_base;
695             g.light_base = new_ls;
696             dest->lamplit = 1; /* now an active light source */
697         }
698 }
699 
700 /* light source `src' has been folded into light source `dest';
701    used for merging lit candles and adding candle(s) to lit candelabrum */
702 void
obj_merge_light_sources(struct obj * src,struct obj * dest)703 obj_merge_light_sources(struct obj *src, struct obj *dest)
704 {
705     light_source *ls;
706 
707     /* src == dest implies adding to candelabrum */
708     if (src != dest)
709         end_burn(src, TRUE); /* extinguish candles */
710 
711     for (ls = g.light_base; ls; ls = ls->next)
712         if (ls->type == LS_OBJECT && ls->id.a_obj == dest) {
713             ls->range = candle_light_range(dest);
714             g.vision_full_recalc = 1; /* in case range changed */
715             break;
716         }
717 }
718 
719 /* light source `obj' is being made brighter or dimmer */
720 void
obj_adjust_light_radius(struct obj * obj,int new_radius)721 obj_adjust_light_radius(struct obj *obj, int new_radius)
722 {
723     light_source *ls;
724 
725     for (ls = g.light_base; ls; ls = ls->next)
726         if (ls->type == LS_OBJECT && ls->id.a_obj == obj) {
727             if (new_radius != ls->range)
728                 g.vision_full_recalc = 1;
729             ls->range = new_radius;
730             return;
731         }
732     impossible("obj_adjust_light_radius: can't find %s", xname(obj));
733 }
734 
735 /* Candlelight is proportional to the number of candles;
736    minimum range is 2 rather than 1 for playability. */
737 int
candle_light_range(struct obj * obj)738 candle_light_range(struct obj *obj)
739 {
740     int radius;
741 
742     if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
743         /*
744          *      The special candelabrum emits more light than the
745          *      corresponding number of candles would.
746          *       1..3 candles, range 2 (minimum range);
747          *       4..6 candles, range 3 (normal lamp range);
748          *          7 candles, range 4 (bright).
749          */
750         radius = (obj->spe < 4) ? 2 : (obj->spe < 7) ? 3 : 4;
751     } else if (Is_candle(obj)) {
752         /*
753          *      Range is incremented quadratically. You can get the same
754          *      amount of light as from a lamp with 4 candles, and
755          *      even better light with 9 candles, and so on.
756          *       1..3  candles, range 2;
757          *       4..8  candles, range 3;
758          *       9..15 candles, range 4; &c.
759          */
760         long n = obj->quan;
761 
762         radius = 1; /* always incremented at least once */
763         while(radius*radius <= n) {
764             radius++;
765         }
766     } else {
767         /* we're only called for lit candelabrum or candles */
768         /* impossible("candlelight for %d?", obj->otyp); */
769         radius = 3; /* lamp's value */
770     }
771     return radius;
772 }
773 
774 /* light emitting artifact's range depends upon its curse/bless state */
775 int
arti_light_radius(struct obj * obj)776 arti_light_radius(struct obj *obj)
777 {
778     /*
779      * Used by begin_burn() when setting up a new light source
780      * (obj->lamplit will already be set by this point) and
781      * also by bless()/unbless()/uncurse()/curse() to decide
782      * whether to call obj_adjust_light_radius().
783      */
784 
785     /* sanity check [simplifies usage by bless()/curse()/&c] */
786     if (!obj->lamplit || !artifact_light(obj))
787         return 0;
788 
789     /* cursed radius of 1 is not noticeable for an item that's
790        carried by the hero but is if it's carried by a monster
791        or left lit on the floor (not applicable for Sunsword) */
792     return (obj->blessed ? 3 : !obj->cursed ? 2 : 1);
793 }
794 
795 /* adverb describing lit artifact's light; depends on curse/bless state */
796 const char *
arti_light_description(struct obj * obj)797 arti_light_description(struct obj *obj)
798 {
799     switch (arti_light_radius(obj)) {
800     case 3:
801         return "brilliantly"; /* blessed */
802     case 2:
803         return "brightly"; /* uncursed */
804     case 1:
805         return "dimly"; /* cursed */
806     default:
807         break;
808     }
809     return "strangely";
810 }
811 
812 int
wiz_light_sources(void)813 wiz_light_sources(void)
814 {
815     winid win;
816     char buf[BUFSZ];
817     light_source *ls;
818 
819     win = create_nhwindow(NHW_MENU); /* corner text window */
820     if (win == WIN_ERR)
821         return 0;
822 
823     Sprintf(buf, "Mobile light sources: hero @ (%2d,%2d)", u.ux, u.uy);
824     putstr(win, 0, buf);
825     putstr(win, 0, "");
826 
827     if (g.light_base) {
828         putstr(win, 0, "location range flags  type    id");
829         putstr(win, 0, "-------- ----- ------ ----  -------");
830         for (ls = g.light_base; ls; ls = ls->next) {
831             Sprintf(buf, "  %2d,%2d   %2d   0x%04x  %s  %s", ls->x, ls->y,
832                     ls->range, ls->flags,
833                     (ls->type == LS_OBJECT
834                        ? "obj"
835                        : ls->type == LS_MONSTER
836                           ? (mon_is_local(ls->id.a_monst)
837                              ? "mon"
838                              : (ls->id.a_monst == &g.youmonst)
839                                 ? "you"
840                                 /* migrating monster */
841                                 : "<m>")
842                           : "???"),
843                     fmt_ptr(ls->id.a_void));
844             putstr(win, 0, buf);
845         }
846     } else
847         putstr(win, 0, "<none>");
848 
849     display_nhwindow(win, FALSE);
850     destroy_nhwindow(win);
851 
852     return 0;
853 }
854 
855 /*light.c*/
856