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.)
This commit is contained in:
PatR
2020-05-09 13:07:35 -07:00
parent d564fa8ac7
commit 59818fb6ab
7 changed files with 206 additions and 102 deletions

View File

@@ -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

View File

@@ -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]])

View File

@@ -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;
}

View File

@@ -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, <x,y> 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 <x,y>; 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;

View File

@@ -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;
}

View File

@@ -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++)

View File

@@ -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();