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