/* * Handle level display. */ #include #include #include #include "mon.h" #include "vars.h" #include "util.h" #include "pline.h" #include "debug.h" #include "scrsyms.h" #include "signals.h" #include "obj-map.h" #include "digdungeon.h" #include "stdio-util.h" #include "display.h" /* * dc[][] holds displayed characters; df[][] holds displayed flags. */ static char dc[LEV_X][LEV_Y]; static unsigned char df[LEV_X][LEV_Y]; /* * These hold the min and max coordinates seen, to support scrolling * the display. */ static int min_x; static int max_x; static int min_y; static int max_y; /* * dispo[xy] and mapo[xy] are the offsets between display coordimates * and level coordinates (dispo[xy]) or between level coordinates and * map coordinates (mapo[xy]). The mappings are * * level coord = screen coord - dispo * level coord = map coord - mapo * * which of course can be transformed into * * screen coord = level coord + dispo * dispo = screen coord - level coord * * map coord = level coord + mapo * mapo = map coord - level coord */ int dispox; int dispoy; int mapox; int mapoy; /* * Initialize the display code on startup. */ void initdisplay(void) { cleardisp(); dispox = 0; dispoy = 0; mapox = 0; mapoy = 0; } /* * Return whether a cell is `empty' for purposes of wall symbol * determination. */ static int empty(LOC *l) { return(l && ((l->type == LOC_CAVE) || (l->type == LOC_GATE))); } /* * Figure out (and return) the appropriate display character for a WALL * cell, ignoring visibility (ie, suitable for recording on a map). * We compute a syndrome based on which cells adjacent to the wall are * empty, then switch based on that. But note that walls ajdacent to * doors always display as TWALL, and this is actually handled during * syndrome computation. * * Note also that the synbits[] array is indexed [x][y], so the array * is transposed compared to what it looks like. (For example, the * 0x40 bit is to the left of, not above, the cell in question.) */ static char loc_map_wall(LOC *lc) { LOC *l; int dx; int dy; int x; int y; int syndrome; int b; static const int synbits[3][3] = { { 0x80, 0x40, 0x20 }, { 0x01, 0, 0x10 }, { 0x02, 0x04, 0x08 } }; syndrome = 0; for (dx=0;dx<=2;dx++) { x = lc->x + dx - 1; for (dy=0;dy<=2;dy++) { y = lc->y + dy - 1; if ((x >= 0) && (y >= 0) && (x < LEV_X) && (y < LEV_Y)) { l = &lc->on->cells[x][y]; b = synbits[dx][dy]; if ((b & 0x55) && (l->type == LOC_DOOR)) return(SYM_TWALL); if (empty(l)) syndrome += b; } } } switch (syndrome & 0x55) { case 0x44: return(SYM_VWALL); break; case 0x04: return((syndrome&0xa0)?SYM_TWALL:SYM_VWALL); break; case 0x40: return((syndrome&0x0a)?SYM_TWALL:SYM_VWALL); break; case 0x11: return(SYM_HWALL); break; case 0x01: return((syndrome&0x28)?SYM_TWALL:SYM_HWALL); break; case 0x10: return((syndrome&0x82)?SYM_TWALL:SYM_HWALL); break; } return(SYM_TWALL); } /* * Return the display character for a given location (l), for a given * display method (how). */ char loc_char(LOC *l, LCHOW how) { switch (l->type) { case LOC_ROCK: return(SYM_ROCK); break; case LOC_SDOOR: if (how == LC_DEBUG) { switch (l->walldoor) { case LOC_UWALL: return(SYM_USDOOR); break; case LOC_HWALL: return(SYM_HSDOOR); break; case LOC_VWALL: return(SYM_VSDOOR); break; case LOC_TWALL: return(SYM_TSDOOR); break; default: return(SYM_UNKN_SDOOR); break; } } /* fall through */ case LOC_WALL: if (how == LC_MAP) return(loc_map_wall(l)); switch (l->walldoor) { case LOC_UWALL: if (how == LC_ORDINARY) return(SYM_OOS); return( (l->flags & LF_VAULT) ? SYM_UVWALL : (l->flags & LF_SHOP) ? SYM_USWALL : SYM_UWALL ); break; case LOC_HWALL: return(SYM_HWALL); break; case LOC_VWALL: return(SYM_VWALL); break; case LOC_TWALL: return(SYM_TWALL); break; default: return(SYM_UNKN_WALL); break; } break; case LOC_CAVE: switch (l->cavetype) { case LOC_STAIRS_U: return(SYM_STAIRS_U); break; case LOC_STAIRS_D: return(SYM_STAIRS_D); break; case LOC_JUSTCAVE: return((l->flags&LF_WATER)?SYM_WETCAVE:SYM_JUSTCAVE); break; default: return(SYM_UNKN_CAVE); break; } break; case LOC_GATE: return(SYM_GATE); break; case LOC_DOOR: switch (l->walldoor) { case LOC_CHDOOR: return(SYM_CHDOOR); break; case LOC_CVDOOR: return(SYM_CVDOOR); break; case LOC_OHDOOR: return(SYM_OHDOOR); break; case LOC_OVDOOR: return(SYM_OVDOOR); break; default: return(SYM_UNKN_DOOR); break; } break; } return(SYM_UNKN_TYPE); } /* * Update the display of a location. If specialp is true, this is for * debugging display and thus all monsters and objects should be * shown. This actually mvaddch()es the characters onto the screen. * It does not, however, refresh(). * * This updates the dc[][] and df[][] arrays. * * This depends on SYM_OOS being distinct from any other character the * player can see (it tests c against SYM_OOS). */ static void upddisp2(LOC *l, int specialp) { char c; if (l->monst && (specialp || monst_cansee_monst(you,l->monst))) { c = l->monst->symbol; } else if (l->objs.inv && (specialp || (l->flags & LF_VISIBLE))) { c = objtypes[l->objs.inv->v[0]->type].sym; } else if (!specialp && !(l->flags & LF_VISIBLE)) { c = SYM_OOS; if (l->type == LOC_WALL) l->walldoor = LOC_UWALL; } else { c = loc_char(l,specialp?LC_DEBUG:LC_ORDINARY); } if (specialp) { if (l->flags & LF_VISIBLE) standout(); else standend(); if (c == SYM_OOS) c = ' '; mvaddch(l->y,l->x,c); standend(); } else { if (map && (c == SYM_OOS)) c = map_charat(map,l->x+mapox,l->y+mapoy); df[l->x][l->y] = (l->flags & LF_VISIBLE) ? DF_LIT : 0; dc[l->x][l->y] = c; if (c != SYM_OOS) { if (l->x < min_x) min_x = l->x; if (l->x > max_x) max_x = l->x; if (l->y < min_y) min_y = l->y; if (l->y > max_y) max_y = l->y; } } } /* * Display a level for debugging. */ void lvdisp(LEVEL *lv) { int x; int y; LOC *l; clear(); for (x=0;xcells[x][0]; for (y=0;yon; x1 = (l->x < 1) ? 0 : l->x-1; x2 = (l->x >= LEV_X-1) ? LEV_X-1 : l->x+1; y1 = (l->y < 1) ? 0 : l->y-1; y2 = (l->y >= LEV_Y-1) ? LEV_Y-1 : l->y+1; for (x=x1;x<=x2;x++) { for (y=y1;y<=y2;y++) { upddisp1(&lv->cells[x][y]); } } } #endif /* * Update the display for a cell. This does nothing if the cell isn't * visible; otherwise, it just calls upddisp1(). */ void upddisp(LOC *l) { if (l->flags & LF_VISIBLE) upddisp1(l); } /* * Update the display for a cell. This ignores LF_VISIBLE, but does * nothing for cells not on the same level as the player. Otherwise, * it just calls upddisp2 indicating `not special'. */ void upddisp1(LOC *l) { if (l->on != you->loc->on) return; upddisp2(l,0); } /* * Returns the cell `at' a given location, taking LVF_WRAPAROUND into * account. On non-wraparound levels, this returns nil if called with * a cell off the edge of the level. */ static LOC *lv_cell_at(LEVEL *lv, int x, int y) { if (x > LEV_X-1) { if (! (lv->flags & LVF_WRAPAROUND)) return(0); x -= LEV_X; } else if (x < 0) { if (! (lv->flags & LVF_WRAPAROUND)) return(0); x += LEV_X; } if (y > LEV_Y-1) { if (! (lv->flags & LVF_WRAPAROUND)) return(0); y -= LEV_Y; } else if (y < 0) { if (! (lv->flags & LVF_WRAPAROUND)) return(0); y += LEV_Y; } return(&lv->cells[x][y]); } /* * Set the wall subtype for a location (wall), as seen from another * location (from). This panics if wall and from are not on the same * level; if from isn't CAVE or DOOR/DOOR_OPEN, it just does nothing * (except for the case where wall==from). */ void checkwall_from(LOC *wall, LOC *from) { LEVEL *lv; XY d; unsigned int syndrome; int i; LOC *c; lv = wall->on; if (lv != from->on) { panic("cells on different levels to checkwall_from"); } if (wall == from) { d.x = 1; d.y = 0; } else if ( (from->type != LOC_CAVE) && ( (from->type != LOC_DOOR) || !(from->walldoor & LOC_DOOR_OPEN) ) ) { return; } else { d.x = from->x - wall->x; d.y = from->y - wall->y; d.x = (d.x < 0) ? -1 : (d.x > 0) ? 1 : 0; d.y = (d.y < 0) ? -1 : (d.y > 0) ? 1 : 0; } syndrome = 0; for (i=8;i>0;i--) { c = lv_cell_at(lv,wall->x+d.x,wall->y+d.y); d = turn_r45(d); syndrome <<= 1; if (!c || ((c->type != LOC_CAVE) && (c->type != LOC_GATE))) syndrome |= 1; } /* * syndrome now has 1s where there is inaccessible space (ROCK, WALL, * etc). (For these purposes, DOOR counts as inaccessible.) This is * relative to the direction it's being seen from; the bits look like * * (orthogonal view) * 4 2 1 * 8 * 128 -- view direction * 16 32 64 * * (diagonal view) * 8 4 2 * 16 * 1 * 32 64 128 * \ * \ * view direction * * The wall==from case computes a syndrome as if for orthogonal view * from the right, but uses a different table. */ { static const char key_o[256] = "++++++++++++++++++++++++++++++++" // 000xxxxx "++||++||++||++||++||++||++||++||" // 001xxxxx "++++++++++++++++++++++++++++++++" // 010xxxxx "++||++||++||++||++||++||++||++||" // 011xxxxx "++++++++--++--+-++++++++--++--+-" // 100xxxxx "++++++++++++++++++++++++++++++++" // 101xxxxx "++++++++--++--+-++++++++--++--+-" // 110xxxxx "+++|+++|--+++++++++|+++|--++--+p"; // 111xxxxx static const char key_d[256] = "+++++++++++++++++-+-+-+-+-+-+-+-" // 000xxxxx "+++++++++++++++++-+-+-+-+-+-+-+-" // 001xxxxx "++++|+|+++++|+|+++++|+|+++++|+|+" // 010xxxxx "++++|+|+++++|+|+++++|+|+++++|+|+" // 011xxxxx "+++++++++++++++++-+-+-+-+-+-+-+-" // 100xxxxx "+++++++++++++++++-+-+-+-+-+-+-+-" // 101xxxxx "++++|+||++++|+||++++|+|+++++|+|+" // 110xxxxx "++++|+||++++|+||+-+-|+|++-+-|+|p"; // 111xxxxx static const char key_at[256] = "++++++++++++++++++++++++++++++++" // 000xxxxx "++||++||++++++++++||++||++++++||" // 001xxxxx "++++++++++++++++++++++++++++++++" // 010xxxxx "++||++||++++++++++||++||++++++||" // 011xxxxx "++++++++--++--+-++++++++--++--+-" // 100xxxxx "++++++++++++++++++++++++++++++++" // 101xxxxx "++++++++--++--+-++++++++--++--+-" // 110xxxxx "+++|+++|+++++++++++|+++|--++--+p"; // 111xxxxx if (wall == from) { switch (key_at[syndrome]) { default: panic("impossible checkwall_from"); break; case '+': wall->walldoor |= LOC_TWALL; break; case '-': wall->walldoor |= LOC_HWALL; break; case '|': wall->walldoor |= LOC_VWALL; break; } } else if (d.x && d.y) { switch (key_d[syndrome]) { default: panic("impossible checkwall_from"); break; case '+': wall->walldoor |= LOC_TWALL; break; case '-': wall->walldoor |= ((d.x == d.y) ? LOC_HWALL : LOC_VWALL); break; case '|': wall->walldoor |= ((d.x == d.y) ? LOC_VWALL : LOC_HWALL); break; } } else { switch (key_o[syndrome]) { default: panic("impossible checkwall_from"); break; case '+': wall->walldoor |= LOC_TWALL; break; case '-': wall->walldoor |= (d.x ? LOC_HWALL : LOC_VWALL); break; case '|': wall->walldoor |= (d.x ? LOC_VWALL : LOC_HWALL); break; } } } } /* * Draw the mapped (or visible) portions of a level. */ void drawmapped(LEVEL *lv) { int x; int y; LOC *l; move(0,MXO); if (map && map_label(map)) printw("%s",map_label(map)); clrtoeol(); for (x=0;xcells[x][0]; for (y=0;y= LEV_X) || (ly >= LEV_Y)) { c = SYM_OOS; f = 0; } else { c = cv[lx][ly]; f = fv[lx][ly]; } if (c == SYM_OOS) c = ' '; if (f & DF_LIT) standout(); mvaddch(sy,sx,c); standend(); } } } /* * View a map. This is an interactive interface to exploring maps. * When displaying a map, the player can type direction keys to * scroll, < or > to move up or down a level, ^L to redraw, and ESC, * q, return, or newline to exit. */ void view_map(OBJ *m) { int ox; int oy; int x; int y; int dx; int dy; int f; char c; int fulldisp; OBJ *m2; unsigned char fa[LEV_X][LEV_Y]; char ca[LEV_X][LEV_Y]; JMP j; if (setjmp(j.b)) { pop_sigint_throw(); clear(); return; } for (x=0;x': m2 = map_auto_link_dn(m,&ox,&oy); if (m2) { m = m2; fulldisp = 1; } break; case '\f': fulldisp = 1; break; case '\33': case 'q': case '\r': case '\n': clear(); return; break; } } } } /* * Interactive interface to align a map to reality. Displays the map, * with cells where the map disagrees with the surroundings marked * with highlighted *s. Upon user approval, sets the offset values * appropriately. */ int place_map(OBJ *m, int *oxp, int *oyp, const char *prompt) { int ox; int oy; int x; int y; int mx; int my; int dx; int dy; int f; char dch; char c; int fulldisp; unsigned char fa[LEV_X][LEV_Y]; char ca[LEV_X][LEV_Y]; JMP j; if (setjmp(j.b)) { pop_sigint_throw(); clear(); return(0); } fulldisp = 1; ox = ((map_maxx(m)+map_minx(m))/2) - you->loc->x; oy = ((map_maxy(m)+map_miny(m))/2) - you->loc->y; while (1) { if (fulldisp) { fulldisp = 0; clear(); if (map_label(m)) mvprintw(0,MXO,"%s",map_label(m)); mvprintw(1,MXO,"X [%d..%d]",map_minx(m),map_maxx(m)); mvprintw(2,MXO,"Y [%d..%d]",map_miny(m),map_maxy(m)); mvaddstr(LINES-1,0,prompt); } mvprintw(4,MXO,"ox=%d oy=%d",ox,oy); clrtoeol(); for (x=0;xloc->x) && (dy == you->loc->y)) { ca[x][y] = you->symbol; fa[x][y] = 1; } else { ca[x][y] = map_charat(m,mx,my); dch = ((dx >= 0) && (dy >= 0) && (dx < LEV_X) && (dy < LEV_X)) ? dc[dx][dy] : SYM_OOS; fa[x][y] = (dch != SYM_OOS) && (ca[x][y] != dch); if ((ca[x][y] == SYM_OOS) && fa[x][y]) ca[x][y] = '*'; } } } showdisp2(&fa[0],&ca[0],0,0); move(LINES-1,COLS-2); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); if (dirkey(c,&dx,&dy,0,&f,0)) { if (f & DK_CAPS) { dx *= 8; dy *= 4; } ox += dx; oy += dy; } else { switch (c) { case '\f': fulldisp = 1; break; case '\33': case 'q': clear(); return(0); break; case '\r': case '\n': *oxp = ox; *oyp = oy; clear(); return(1); break; } } } } /* * Update the display according to the data stored in the dc[][] and * df[][] globals. This is responsible for scrolling the display as * necessary. */ void showdisp(void) { if (min_x <= max_x) { if (min_x+dispox < 0) dispox = - min_x; if (min_y+dispoy < 0) dispoy = - min_y; if (max_x+dispox >= LEV_X) dispox = LEV_X-1 - max_x; if (max_y+dispoy >= LEV_Y) dispoy = LEV_Y-1 - max_y; } showdisp2(&df[0],&dc[0],dispox,dispoy); } /* * Clear the display. This is suitable for use, for example, when the * player switches levels. */ void cleardisp(void) { int x; int y; for (x=0;x= LINES-1) { clr = 1; clear(); for (i=0;i maxl) maxl = i; } for (n=0;n (1) { f = fopenmalloc(&s,&l); linestat = (*line)(f); fclose(f); switch (linestat) { case DL_L_MORE: break; case DL_L_DONE: free(s); break <"lines">; } if (l > COLS-6) { char *s2; sp = s; while (l > COLS-2) { s2 = malloc(1+COLS-2); bcopy(sp,s2,COLS-2); s2[COLS-2] = '\0'; nexttxt(s2); sp += COLS-2; l -= COLS-2; } nexttxt(strdup(sp)); free(s); s = 0; } else { sp = s; s = 0; nexttxt(sp); } } s = 0; if (n) { if (clr) { int i; for (i=0;i LEV_X) { int i; for (i=0;i 1) { int i; for (i=0;i LEV_Y) clr = 1; } else if (n == 1) { mvprintw(0,0,"%s ",txt[0]); } getch_loop(""); if (clr) clear(); } retnow:; for (n=0;n