From 59818fb6abd5beb00dbc534cb847eee02bed9879 Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 9 May 2020 13:07:35 -0700 Subject: [PATCH] implement #986 - camera flash 'tweak' Implement the suggested feature that a camera's flash actually update hero's memory of the map as it traverses across the level. Turned out to be more work than anticipated despite having the code for a thrown or kicked lit candle or lamp to build upon. Among other things it needed to update the circle code to handle previously unused radius 0 to operate on the center point only. I've never touched that before and hope this hasn't introduced any bugs. Also removes several instances of vision code operating on column #0. (At least one is still present.) --- doc/fixes37.0 | 2 + include/vision.h | 2 +- src/apply.c | 15 +++-- src/light.c | 158 ++++++++++++++++++++++++++++++++++------------- src/uhitm.c | 22 ++++++- src/vision.c | 99 +++++++++++++++-------------- src/zap.c | 10 ++- 7 files changed, 206 insertions(+), 102 deletions(-) diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 7a5d84997..72c20003f 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -324,6 +324,8 @@ extended achievement and conduct fields for xlogfile record amount of gold in hero's possession in xlogfile new objects: amulets of flying and guarding new monsters: displacer beast ('f') and genetic engineer ('Q') +make camera flash which reveals previously unseen map features or objects or + monsters record those on the hero's map; monsters revert to 'unseen' Platform- and/or Interface-Specific New Features diff --git a/include/vision.h b/include/vision.h index f0ccd6f05..8b2b0090c 100644 --- a/include/vision.h +++ b/include/vision.h @@ -52,7 +52,7 @@ /* * Circle information */ -#define MAX_RADIUS 15 /* this is in points from the source */ +#define MAX_RADIUS 16 /* this is in points from the source */ /* Use this macro to get a list of distances of the edges (see vision.c). */ #define circle_ptr(z) (&circle_data[(int) circle_start[z]]) diff --git a/src/apply.c b/src/apply.c index 45c2c7b54..856b54ee5 100644 --- a/src/apply.c +++ b/src/apply.c @@ -75,11 +75,16 @@ struct obj *obj; (u.dz > 0) ? surface(u.ux, u.uy) : ceiling(u.ux, u.uy)); } else if (!u.dx && !u.dy) { (void) zapyourself(obj, TRUE); - } else if ((mtmp = bhit(u.dx, u.dy, COLNO, FLASHED_LIGHT, - (int FDECL((*), (MONST_P, OBJ_P))) 0, - (int FDECL((*), (OBJ_P, OBJ_P))) 0, &obj)) != 0) { - obj->ox = u.ux, obj->oy = u.uy; - (void) flash_hits_mon(mtmp, obj); + } else { + mtmp = bhit(u.dx, u.dy, COLNO, FLASHED_LIGHT, + (int FDECL((*), (MONST_P, OBJ_P))) 0, + (int FDECL((*), (OBJ_P, OBJ_P))) 0, &obj); + obj->ox = u.ux, obj->oy = u.uy; /* flash_hits_mon() wants this */ + if (mtmp) + (void) flash_hits_mon(mtmp, obj); + /* normally bhit() would do this but for FLASHED_LIGHT we want it + to be deferred until after flash_hits_mon() */ + transient_light_cleanup(); } return 1; } diff --git a/src/light.c b/src/light.c index c34acfcdc..26932b40b 100644 --- a/src/light.c +++ b/src/light.c @@ -41,6 +41,9 @@ #define LSF_SHOW 0x1 /* display the light source */ #define LSF_NEEDS_FIXUP 0x2 /* need oid fixup */ +static light_source *FDECL(new_light_core, (XCHAR_P, XCHAR_P, int, int, + anything *)); +static void NDECL(discard_flashes); static void FDECL(write_ls, (NHFILE *, light_source *)); static int FDECL(maybe_write_ls, (NHFILE *, int, BOOLEAN_P)); @@ -49,18 +52,31 @@ extern char circle_data[]; extern char circle_start[]; -/* Create a new light source. */ +/* Create a new light source. Caller (and extern.h) doesn't need to know + anything about type 'light_source'. */ void new_light_source(x, y, range, type, id) xchar x, y; int range, type; anything *id; +{ + (void) new_light_core(x, y, range, type, id); +} + +/* Create a new light source and return it. Only used within this file. */ +static light_source * +new_light_core(x, y, range, type, id) + xchar x, y; + int range, type; + anything *id; { light_source *ls; - if (range > MAX_RADIUS || range < 1) { - impossible("new_light_source: illegal range %d", range); - return; + if (range > MAX_RADIUS || range < 0 + /* camera flash uses radius 0 and passes Null object */ + || (range == 0 && (type != LS_OBJECT || id->a_obj != 0))) { + impossible("new_light_source: illegal range %d", range); + return (light_source *) 0; } ls = (light_source *) alloc(sizeof *ls); @@ -75,6 +91,7 @@ new_light_source(x, y, range, type, id) g.light_base = ls; g.vision_full_recalc = 1; /* make the source show up */ + return ls; } /* @@ -95,7 +112,7 @@ anything *id; (in particular: chameleon vs prot. from shape changers) */ switch (type) { case LS_OBJECT: - tmp_id.a_uint = id->a_obj->o_id; + tmp_id.a_uint = id->a_obj ? id->a_obj->o_id : 0; break; case LS_MONSTER: tmp_id.a_uint = id->a_monst->m_id; @@ -145,7 +162,8 @@ char **cs_rows; * vision recalc. */ if (ls->type == LS_OBJECT) { - if (get_obj_location(ls->id.a_obj, &ls->x, &ls->y, 0)) + if (ls->range == 0 /* camera flash; caller has set ls->{x,y} */ + || get_obj_location(ls->id.a_obj, &ls->x, &ls->y, 0)) ls->flags |= LSF_SHOW; } else if (ls->type == LS_MONSTER) { if (get_mon_location(ls->id.a_monst, &ls->x, &ls->y, 0)) @@ -177,8 +195,8 @@ char **cs_rows; for (; y <= max_y; y++) { row = cs_rows[y]; offset = limits[abs(y - ls->y)]; - if ((min_x = (ls->x - offset)) < 0) - min_x = 0; + if ((min_x = (ls->x - offset)) < 1) + min_x = 1; if ((max_x = (ls->x + offset)) >= COLNO) max_x = COLNO - 1; @@ -210,62 +228,94 @@ char **cs_rows; /* lit 'obj' has been thrown or kicked and is passing through x,y on the way to its destination; show its light so that hero has a chance to - remember terrain, objects, and monsters being revealed */ + remember terrain, objects, and monsters being revealed; + if 'obj' is Null, is being hit by a camera's light flash */ void show_transient_light(obj, x, y) struct obj *obj; int x, y; { - light_source *ls; + light_source *ls = 0; + anything cameraflash; struct monst *mon; int radius_squared; - /* caller has verified obj->lamplit and that hero is not Blind; - validate light source and obtain its radius (for monster sightings) */ - for (ls = g.light_base; ls; ls = ls->next) { - if (ls->type != LS_OBJECT) - continue; - if (ls->id.a_obj == obj) - break; + /* Null object indicates camera flash */ + if (!obj) { + /* no need to temporarily light an already lit spot */ + if (levl[x][y].lit) + return; + + cameraflash = cg.zeroany; + /* radius 0 will just light ; cameraflash.a_obj is Null */ + ls = new_light_core(x, y, 0, LS_OBJECT, &cameraflash); + } else { + /* thrown or kicked object which is emitting light; validate its + light source to obtain its radius (for monster sightings) */ + for (ls = g.light_base; ls; ls = ls->next) { + if (ls->type != LS_OBJECT) + continue; + if (ls->id.a_obj == obj) + break; + } } - if (!ls || obj->where != OBJ_FREE) { + if (!ls || (obj && obj->where != OBJ_FREE)) { impossible("transient light %s %s is not %s?", obj->lamplit ? "lit" : "unlit", xname(obj), !ls ? "a light source" : "free"); - } else { - /* "expensive" but rare */ - place_object(obj, g.bhitpos.x, g.bhitpos.y); /* temporarily put on map */ - vision_recalc(0); - flush_screen(0); - delay_output(); - remove_object(obj); /* take back off of map */ + return; + } - radius_squared = ls->range * ls->range; - for (mon = fmon; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) - continue; - /* light range is the radius of a circle and we're limiting - canseemon() to a square exclosing that circle, but setting - mtemplit 'erroneously' for a seen monster is not a problem; - it just flags monsters for another canseemon() check when - 'obj' has reached its destination after missile traversal */ - if (dist2(mon->mx, mon->my, x, y) <= radius_squared - && canseemon(mon)) + if (obj) /* put lit candle or lamp temporarily on the map */ + place_object(obj, g.bhitpos.x, g.bhitpos.y); + else /* camera flash: no object; directly set light source's location */ + ls->x = x, ls->y = y; + + /* full recalc; runs do_light_sources() */ + vision_recalc(0); + flush_screen(0); + + radius_squared = ls->range * ls->range; + for (mon = fmon; mon; mon = mon->nmon) { + if (DEADMONSTER(mon) || (mon->isgd && !mon->mx)) + continue; + /* light range is the radius of a circle and we're limiting + canseemon() to a square exclosing that circle, but setting + mtemplit 'erroneously' for a seen monster is not a problem; + it just flags monsters for another canseemon() check when + 'obj' has reached its destination after missile traversal */ + if (dist2(mon->mx, mon->my, x, y) <= radius_squared) { + if (canseemon(mon)) mon->mtemplit = 1; - /* [what about worm tails?] */ } + /* [what about worm tails?] */ + } + + if (obj) { /* take thrown/kicked candle or lamp off the map */ + delay_output(); + remove_object(obj); } } -/* draw "remembered, unseen monster" glyph at locations where a monster - was flagged for being visible during transient light movement but can't - be seen now */ +/* delete any camera flash light sources and draw "remembered, unseen + monster" glyph at locations where a monster was flagged for being + visible during transient light movement but can't be seen now */ void transient_light_cleanup() { struct monst *mon; - int mtempcount = 0; + int mtempcount; + + /* in case we're cleaning up a camera flash, remove all object light + sources which aren't associated with a specific object */ + discard_flashes(); + if (g.vision_full_recalc) /* set by del_light_source() */ + vision_recalc(0); + /* for thrown/kicked candle or lamp or for camera flash, some + monsters may have been mapped in light which has now gone away + so need to be replaced by "remembered, unseen monster" glyph */ + mtempcount = 0; for (mon = fmon; mon; mon = mon->nmon) { if (DEADMONSTER(mon)) continue; @@ -276,9 +326,22 @@ transient_light_cleanup() map_invisible(mon->mx, mon->my); } } - if (mtempcount) { - vision_recalc(0); + if (mtempcount) flush_screen(0); +} + +/* camera flashes have Null object; caller wants to get rid of them now */ +static void +discard_flashes() +{ + light_source *ls, *nxt_ls; + + for (ls = g.light_base; ls; ls = nxt_ls) { + nxt_ls = ls->next; + if (ls->type != LS_OBJECT) + continue; + if (!ls->id.a_obj) + del_light_source(LS_OBJECT, &ls->id); } } @@ -318,6 +381,13 @@ int range; int count, actual, is_global; light_source **prev, *curr; + /* camera flash light sources have Null object and would trigger + impossible("no id!") below; they can only happen here if we're + in the midst of a panic save and they wouldn't be useful after + restore so just throw any that are present away */ + discard_flashes(); + g.vision_full_recalc = 0; + if (perform_bwrite(nhfp)) { count = maybe_write_ls(nhfp, range, FALSE); if (nhfp->structlevel) { @@ -330,7 +400,7 @@ int range; } if (release_data(nhfp)) { - for (prev = &g.light_base; (curr = *prev) != 0;) { + for (prev = &g.light_base; (curr = *prev) != 0; ) { if (!curr->id.a_monst) { impossible("save_light_sources: no id! [range=%d]", range); is_global = 0; diff --git a/src/uhitm.c b/src/uhitm.c index 0b9b22c47..74a48641b 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -3110,14 +3110,21 @@ struct monst *mon; u.umconf--; } +/* returns 1 if light flash has noticeable effect on 'mtmp', 0 otherwise */ int flash_hits_mon(mtmp, otmp) struct monst *mtmp; struct obj *otmp; /* source of flash */ { - int tmp, amt, res = 0, useeit = canseemon(mtmp); + struct rm *lev; + int tmp, amt, useeit, res = 0; - if (mtmp->msleeping) { + if (g.notonhead) + return 0; + lev = &levl[mtmp->mx][mtmp->my]; + useeit = canseemon(mtmp); + + if (mtmp->msleeping && haseyes(mtmp->data)) { mtmp->msleeping = 0; if (useeit) { pline_The("flash awakens %s.", mon_nam(mtmp)); @@ -3144,8 +3151,19 @@ struct obj *otmp; /* source of flash */ mtmp->mcansee = 0; mtmp->mblinded = (tmp < 3) ? 0 : rnd(1 + 50 / tmp); } + } else if (flags.verbose && useeit) { + if (lev->lit) + pline("The flash of light shines on %s.", mon_nam(mtmp)); + else + pline("%s is illuminated.", Monnam(mtmp)); + res = 2; /* 'message has been given' temporary value */ } } + if (res) { + if (!lev->lit) + display_nhwindow(WIN_MESSAGE, TRUE); + res &= 1; /* change temporary 2 back to 0 */ + } return res; } diff --git a/src/vision.c b/src/vision.c index fb3c589be..2d9f8bd24 100644 --- a/src/vision.c +++ b/src/vision.c @@ -24,45 +24,48 @@ * */ const char circle_data[] = { - /* 0*/ 1, 1, - /* 2*/ 2, 2, 1, - /* 5*/ 3, 3, 2, 1, - /* 9*/ 4, 4, 4, 3, 2, - /* 14*/ 5, 5, 5, 4, 3, 2, - /* 20*/ 6, 6, 6, 5, 5, 4, 2, - /* 27*/ 7, 7, 7, 6, 6, 5, 4, 2, - /* 35*/ 8, 8, 8, 7, 7, 6, 6, 4, 2, - /* 44*/ 9, 9, 9, 9, 8, 8, 7, 6, 5, 3, - /* 54*/ 10, 10, 10, 10, 9, 9, 8, 7, 6, 5, 3, - /* 65*/ 11, 11, 11, 11, 10, 10, 9, 9, 8, 7, 5, 3, - /* 77*/ 12, 12, 12, 12, 11, 11, 10, 10, 9, 8, 7, 5, 3, - /* 90*/ 13, 13, 13, 13, 12, 12, 12, 11, 10, 10, 9, 7, 6, 3, - /*104*/ 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 6, 3, - /*119*/ 15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3, - /*135*/ 16 /* MAX_RADIUS+1; used to terminate range loops -dlc */ + /* 0*/ 0, + /* 1*/ 1, 1, + /* 3*/ 2, 2, 1, + /* 6*/ 3, 3, 2, 1, + /* 10*/ 4, 4, 4, 3, 2, + /* 15*/ 5, 5, 5, 4, 3, 2, + /* 21*/ 6, 6, 6, 5, 5, 4, 2, + /* 28*/ 7, 7, 7, 6, 6, 5, 4, 2, + /* 36*/ 8, 8, 8, 7, 7, 6, 6, 4, 2, + /* 45*/ 9, 9, 9, 9, 8, 8, 7, 6, 5, 3, + /* 55*/ 10, 10, 10, 10, 9, 9, 8, 7, 6, 5, 3, + /* 66*/ 11, 11, 11, 11, 10, 10, 9, 9, 8, 7, 5, 3, + /* 78*/ 12, 12, 12, 12, 11, 11, 10, 10, 9, 8, 7, 5, 3, + /* 91*/ 13, 13, 13, 13, 12, 12, 12, 11, 10, 10, 9, 7, 6, 3, + /*105*/ 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 6, 3, + /*120*/ 15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3, + /*136*/ 16 /* MAX_RADIUS+1; used to terminate range loops -dlc */ }; /* * These are the starting indexes into the circle_data[] array for a - * circle of a given radius. + * circle of a given radius. Radius 0 used to be unused, but is now + * used for a single point: temporary light source of a camera flash + * as it traverses its path. */ const char circle_start[] = { - /* */ 0, /* circles of radius zero are not used */ - /* 1*/ 0, - /* 2*/ 2, - /* 3*/ 5, - /* 4*/ 9, - /* 5*/ 14, - /* 6*/ 20, - /* 7*/ 27, - /* 8*/ 35, - /* 9*/ 44, - /*10*/ 54, - /*11*/ 65, - /*12*/ 77, - /*13*/ 90, - /*14*/ 104, - /*15*/ 119, + /* 0*/ 0, + /* 1*/ 1, + /* 2*/ 3, + /* 3*/ 6, + /* 4*/ 10, + /* 5*/ 15, + /* 6*/ 21, + /* 7*/ 38, + /* 8*/ 36, + /* 9*/ 45, + /*10*/ 55, + /*11*/ 66, + /*12*/ 78, + /*13*/ 91, + /*14*/ 105, + /*15*/ 120, }; /*==========================================================================*/ @@ -263,11 +266,10 @@ char **rmin, **rmax; nrmin = *rmin; nrmax = *rmax; - (void) memset((genericptr_t) * *rows, 0, - ROWNO * COLNO); /* we see nothing */ + (void) memset((genericptr_t) **rows, 0, ROWNO * COLNO); /* see nothing */ for (row = 0; row < ROWNO; row++) { /* set row min & max */ *nrmin++ = COLNO - 1; - *nrmax++ = 0; + *nrmax++ = 1; } } @@ -490,23 +492,23 @@ void vision_recalc(control) int control; { + extern unsigned char seenv_matrix[3][3]; /* from display.c */ + static unsigned char colbump[COLNO + 1]; /* cols to bump sv */ char **temp_array; /* points to the old vision array */ char **next_array; /* points to the new vision array */ char *next_row; /* row pointer for the new array */ char *old_row; /* row pointer for the old array */ char *next_rmin; /* min pointer for the new array */ char *next_rmax; /* max pointer for the new array */ - const char *ranges; /* circle ranges -- used for xray & night vision */ + const char *ranges; /* circle ranges -- used for xray & night vision */ int row = 0; /* row counter (outer loop) */ int start, stop; /* inner loop starting/stopping index */ int dx, dy; /* one step from a lit door or lit wall (see below) */ register int col; /* inner loop counter */ register struct rm *lev; /* pointer to current pos */ - struct rm *flev; /* pointer to position in "front" of current pos */ - extern unsigned char seenv_matrix[3][3]; /* from display.c */ - static unsigned char colbump[COLNO + 1]; /* cols to bump sv */ - unsigned char *sv; /* ptr to seen angle bits */ - int oldseenv; /* previous seenv value */ + struct rm *flev; /* pointer to position in "front" of current pos */ + unsigned char *sv; /* ptr to seen angle bits */ + int oldseenv; /* previous seenv value */ g.vision_full_recalc = 0; /* reset flag */ if (g.in_mklev || !iflags.vision_inited) @@ -532,7 +534,6 @@ int control; * * + Monsters to see with the "new" vision, even on the rogue * level. - * * + Monsters can see you even when you're in a pit. */ view_from(u.uy, u.ux, next_array, next_rmin, next_rmax, 0, @@ -564,7 +565,7 @@ int control; } else if (Is_rogue_level(&u.uz)) { rogue_vision(next_array, next_rmin, next_rmax); } else { - int has_night_vision = 1; /* hero has night vision */ + int lo_col, has_night_vision = 1; /* hero has night vision */ if (Underwater && !Is_waterlevel(&u.uz)) { /* @@ -574,8 +575,9 @@ int control; */ has_night_vision = 0; + lo_col = max(u.ux - 1, 1); for (row = u.uy - 1; row <= u.uy + 1; row++) - for (col = u.ux - 1; col <= u.ux + 1; col++) { + for (col = lo_col; col <= u.ux + 1; col++) { if (!isok(col, row) || !is_pool(col, row)) continue; @@ -592,7 +594,7 @@ int control; if (row >= ROWNO) break; - next_rmin[row] = max(0, u.ux - 1); + next_rmin[row] = max(1, u.ux - 1); next_rmax[row] = min(COLNO - 1, u.ux + 1); next_row = next_array[row]; @@ -620,11 +622,12 @@ int control; dy = v_abs(u.uy - row); next_row = next_array[row]; - start = max(0, u.ux - ranges[dy]); + start = max(1, u.ux - ranges[dy]); stop = min(COLNO - 1, u.ux + ranges[dy]); for (col = start; col <= stop; col++) { char old_row_val = next_row[col]; + next_row[col] |= IN_SIGHT; oldseenv = levl[col][row].seenv; levl[col][row].seenv = SVALL; /* see all! */ @@ -663,7 +666,7 @@ int control; dy = v_abs(u.uy - row); next_row = next_array[row]; - start = max(0, u.ux - ranges[dy]); + start = max(1, u.ux - ranges[dy]); stop = min(COLNO - 1, u.ux + ranges[dy]); for (col = start; col <= stop; col++) diff --git a/src/zap.c b/src/zap.c index 64e642e16..158f542c2 100644 --- a/src/zap.c +++ b/src/zap.c @@ -3320,6 +3320,8 @@ struct obj **pobj; /* object tossed/used, set to NULL /* iron bars will block anything big enough and break some things */ if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) { + if (obj->lamplit && !Blind) + show_transient_light(obj, g.bhitpos.x, g.bhitpos.y); if (typ == IRONBARS && hits_bars(pobj, x - ddx, y - ddy, g.bhitpos.x, g.bhitpos.y, point_blank ? 0 : !rn2(5), 1)) { @@ -3328,9 +3330,11 @@ struct obj **pobj; /* object tossed/used, set to NULL g.bhitpos.x -= ddx; g.bhitpos.y -= ddy; break; - } else if (obj->lamplit && !Blind) { - show_transient_light(obj, g.bhitpos.x, g.bhitpos.y); } + } else if (weapon == FLASHED_LIGHT) { + if (!Blind) + show_transient_light((struct obj *) 0, + g.bhitpos.x, g.bhitpos.y); } if (weapon == ZAPPED_WAND && find_drawbridge(&x, &y)) { @@ -3546,6 +3550,8 @@ struct obj **pobj; /* object tossed/used, set to NULL pay_for_damage("destroy", FALSE); bhit_done: + /* note: for FLASHED_LIGHT, _caller_ must call transient_light_cleanup() + after possibly calling flash_hits_mon() */ if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) transient_light_cleanup(); -- 2.50.1