/* NetHack 3.7 wizcmds.c $NHDT-Date: 1709675219 2024/03/05 21:46:59 $ $NHDT-Branch: keni-mdlib-followup $:$NHDT-Revision: 1.713 $ */ /*-Copyright (c) Robert Patrick Rankin, 2024. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" #include "func_tab.h" extern const char unavailcmd[]; /* cmd.c [27] */ extern const char *levltyp[MAX_TYPE + 2]; /* cmd.c */ static int size_monst(struct monst *, boolean); static int size_obj(struct obj *); static void count_obj(struct obj *, long *, long *, boolean, boolean); static void obj_chain(winid, const char *, struct obj *, boolean, long *, long *); static void mon_invent_chain(winid, const char *, struct monst *, long *, long *); static void mon_chain(winid, const char *, struct monst *, boolean, long *, long *); static void contained_stats(winid, const char *, long *, long *); static void misc_stats(winid, long *, long *); static void you_sanity_check(void); static void makemap_unmakemon(struct monst *, boolean); static int QSORTCALLBACK migrsort_cmp(const genericptr, const genericptr); static void list_migrating_mons(d_level *); DISABLE_WARNING_FORMAT_NONLITERAL /* #wizwish command - wish for something */ int wiz_wish(void) /* Unlimited wishes for debug mode by Paul Polderman */ { if (wizard) { boolean save_verbose = flags.verbose; flags.verbose = FALSE; makewish(); flags.verbose = save_verbose; (void) encumber_msg(); } else pline(unavailcmd, ecname_from_fn(wiz_wish)); return ECMD_OK; } DISABLE_WARNING_FORMAT_NONLITERAL /* #wizidentify command - reveal and optionally identify hero's inventory */ int wiz_identify(void) { if (wizard) { iflags.override_ID = (int) cmd_from_func(wiz_identify); /* command remapping might leave #wizidentify as the only way to invoke us, in which case cmd_from_func() will yield NUL; it won't matter to display_inventory()/display_pickinv() if ^I invokes some other command--what matters is that display_pickinv() and xname() see override_ID as nonzero */ if (!iflags.override_ID) iflags.override_ID = C('I'); (void) display_inventory((char *) 0, FALSE); iflags.override_ID = 0; } else pline(unavailcmd, ecname_from_fn(wiz_identify)); return ECMD_OK; } RESTORE_WARNING_FORMAT_NONLITERAL /* used when wiz_makemap() gets rid of monsters for the old incarnation of a level before creating a new incarnation of it */ void makemap_unmakemon(struct monst *mtmp, boolean migratory) { int ndx = monsndx(mtmp->data); /* uncreate any unique monster so that it is eligible to be remade on the new incarnation of the level; ignores DEADMONSTER() [why?] */ if (mtmp->data->geno & G_UNIQ) gm.mvitals[ndx].mvflags &= ~G_EXTINCT; if (gm.mvitals[ndx].born) gm.mvitals[ndx].born--; /* vault is going away; get rid of guard who might be in play or be parked at <0,0>; for the latter, might already be flagged as dead but is being kept around because of the 'isgd' flag */ if (mtmp->isgd) { mtmp->isgd = 0; /* after this, fall through to mongone() */ } else if (DEADMONSTER(mtmp)) { return; /* already set to be discarded */ } else if (mtmp->isshk && on_level(&u.uz, &ESHK(mtmp)->shoplevel)) { setpaid(mtmp); } if (migratory) { /* caller has removed 'mtmp' from migrating_mons; put it onto fmon so that dmonsfree() bookkeeping for number of dead or removed monsters won't get out of sync; it is not on the map but mongone() -> m_detach() -> mon_leaving_level() copes with that */ mtmp->mstate |= MON_OFFMAP; mtmp->mstate &= ~(MON_MIGRATING | MON_LIMBO | MON_ENDGAME_MIGR); mtmp->nmon = fmon; fmon = mtmp; } mongone(mtmp); } /* get rid of the all the monsters on--or intimately involved with--current level; used when #wizmakemap destroys the level before replacing it */ void makemap_remove_mons(void) { struct monst *mtmp, **mprev; /* keep steed and other adjacent pets after releasing them from traps, stopping eating, &c as if hero were ascending */ keepdogs(TRUE); /* (pets-only; normally we'd be using 'FALSE') */ /* get rid of all the monsters that didn't make it to 'mydogs' */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { /* if already dead, dmonsfree(below) will get rid of it */ if (DEADMONSTER(mtmp)) continue; makemap_unmakemon(mtmp, FALSE); } /* some monsters retain details of this level in mon->mextra; that data becomes invalid when the level is replaced by a new one; get rid of them now if migrating or already arrived elsewhere; [when on their 'home' level, the previous loop got rid of them; if they aren't actually migrating but have been placed on some 'away' level, such monsters are treated like the Wizard: kept on migrating monsters list, scheduled to migrate back to their present location instead of being saved with whatever level they happen to be on; see keepdogs() and keep_mon_accessible(dog.c)] */ for (mprev = &gm.migrating_mons; (mtmp = *mprev) != 0; ) { if (mtmp->mextra && ((mtmp->isshk && on_level(&u.uz, &ESHK(mtmp)->shoplevel)) || (mtmp->ispriest && on_level(&u.uz, &EPRI(mtmp)->shrlevel)) || (mtmp->isgd && on_level(&u.uz, &EGD(mtmp)->gdlevel)))) { *mprev = mtmp->nmon; makemap_unmakemon(mtmp, TRUE); } else { mprev = &mtmp->nmon; } } /* release dead and 'unmade' monsters */ dmonsfree(); if (fmon) { impossible("makemap_remove_mons: 'fmon' did not get emptied?"); } return; } DISABLE_WARNING_FORMAT_NONLITERAL /* #wizmakemap - discard current dungeon level and replace with a new one */ int wiz_makemap(void) { if (wizard) { boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz); makemap_prepost(TRUE, was_in_W_tower); /* create a new level; various things like bestowing a guardian angel on Astral or setting off alarm on Ft.Ludios are handled by goto_level(do.c) so won't occur for replacement levels */ mklev(); makemap_prepost(FALSE, was_in_W_tower); } else { pline(unavailcmd, ecname_from_fn(wiz_makemap)); } return ECMD_OK; } /* the #wizmap command - reveal the level map and any traps or engravings on it */ int wiz_map(void) { if (wizard) { struct trap *t; struct engr *ep; long save_Hconf = HConfusion, save_Hhallu = HHallucination; notice_mon_off(); HConfusion = HHallucination = 0L; for (t = gf.ftrap; t != 0; t = t->ntrap) { t->tseen = 1; map_trap(t, TRUE); } for (ep = head_engr; ep != 0; ep = ep->nxt_engr) { map_engraving(ep, TRUE); } do_mapping(); notice_mon_on(); HConfusion = save_Hconf; HHallucination = save_Hhallu; } else pline(unavailcmd, ecname_from_fn(wiz_map)); return ECMD_OK; } /* #wizgenesis - generate monster(s); a count prefix will be honored */ int wiz_genesis(void) { if (wizard) { boolean mongen_saved = iflags.debug_mongen; iflags.debug_mongen = FALSE; (void) create_particular(); iflags.debug_mongen = mongen_saved; } else pline(unavailcmd, ecname_from_fn(wiz_genesis)); return ECMD_OK; } /* #wizwhere command - display dungeon layout */ int wiz_where(void) { if (wizard) (void) print_dungeon(FALSE, (schar *) 0, (xint16 *) 0); else pline(unavailcmd, ecname_from_fn(wiz_where)); return ECMD_OK; } /* the #wizdetect command - detect secret doors, traps, hidden monsters */ int wiz_detect(void) { if (wizard) (void) findit(); else pline(unavailcmd, ecname_from_fn(wiz_detect)); return ECMD_OK; } RESTORE_WARNING_FORMAT_NONLITERAL /* the #wizkill command - pick targets and reduce them to 0HP; by default, the hero is credited/blamed; use 'm' prefix to avoid that */ int wiz_kill(void) { struct monst *mtmp; coord cc; int ans; char c, qbuf[QBUFSZ]; const char *prompt = "Pick first monster to slay"; boolean save_verbose = flags.verbose, save_autodescribe = iflags.autodescribe; d_level uarehere = u.uz; cc.x = u.ux, cc.y = u.uy; for (;;) { pline("%s:", prompt); prompt = "Next monster"; flags.verbose = FALSE; iflags.autodescribe = TRUE; ans = getpos(&cc, TRUE, "a monster"); flags.verbose = save_verbose; iflags.autodescribe = save_autodescribe; if (ans < 0 || cc.x < 1) break; mtmp = 0; if (u_at(cc.x, cc.y)) { if (u.usteed) { Sprintf(qbuf, "Kill %.110s?", mon_nam(u.usteed)); if ((c = ynq(qbuf)) == 'q') break; if (c == 'y') mtmp = u.usteed; } if (!mtmp) { Sprintf(qbuf, "%s?", Role_if(PM_SAMURAI) ? "Perform seppuku" : "Commit suicide"); if (paranoid_query(TRUE, qbuf)) { Sprintf(gk.killer.name, "%s own player", uhis()); gk.killer.format = KILLED_BY; done(DIED); } break; } } else if (u.uswallow) { mtmp = next2u(cc.x, cc.y) ? u.ustuck : 0; } else { mtmp = m_at(cc.x, cc.y); } /* whether there's an unseen monster here or not, player will know that there's no monster here after the kill or failed attempt; let hero know too */ (void) unmap_invisible(cc.x, cc.y); if (mtmp) { /* we don't require that the monster be seen or sensed so we issue our own message in order to name it in case it isn't; note that if it triggers other kills, those might be referred to as "it" */ int tame = !!mtmp->mtame, seen = (canspotmon(mtmp) || (u.uswallow && mtmp == u.ustuck)), flgs = (SUPPRESS_IT | SUPPRESS_HALLUCINATION | ((tame && has_mgivenname(mtmp)) ? SUPPRESS_SADDLE : 0)), articl = tame ? ARTICLE_YOUR : seen ? ARTICLE_THE : ARTICLE_A; const char *adjs = tame ? (!seen ? "poor, unseen" : "poor") : (!seen ? "unseen" : (const char *) 0); char *Mn = x_monnam(mtmp, articl, adjs, flgs, FALSE); if (!iflags.menu_requested) { /* normal case: hero is credited/blamed */ You("%s %s!", nonliving(mtmp->data) ? "destroy" : "kill", Mn); xkilled(mtmp, XKILL_NOMSG); } else { /* 'm'-prefix */ /* we know that monsters aren't moving because player has just issued this #wizkill command, but if 'mtmp' is a gas spore whose explosion kills any other monsters we need to have the mon_moving flag be True in order to avoid blaming or crediting hero for their deaths */ gc.context.mon_moving = TRUE; pline("%s is %s.", upstart(Mn), nonliving(mtmp->data) ? "destroyed" : "killed"); /* Null second arg suppresses the usual message */ monkilled(mtmp, (char *) 0, AD_PHYS); gc.context.mon_moving = FALSE; } /* end targetting loop if an engulfer dropped hero onto a level- changing trap */ if (u.utotype || !on_level(&u.uz, &uarehere)) break; } else { There("is no monster there."); break; } } /* since #wizkill takes no game time, it is possible to kill something in the main dungeon and immediately level teleport into the endgame which will delete the main dungeon's level files; avoid triggering impossible "dmonsfree: 0 removed doesn't match N pending" by forcing dead monster cleanup; we don't track whether anything was actually killed above--if nothing was, this will be benign */ dmonsfree(); /* distinction between ECMD_CANCEL and ECMD_OK is unimportant here */ return ECMD_OK; /* no time elapses */ } DISABLE_WARNING_FORMAT_NONLITERAL /* the #wizloadlua command - load an arbitrary lua file */ int wiz_load_lua(void) { if (wizard) { char buf[BUFSZ]; /* Large but not unlimited memory and CPU so random bits of * code can be tested by wizards. */ nhl_sandbox_info sbi = {NHL_SB_SAFE | NHL_SB_DEBUGGING, 16*1024*1024, 0, 16*1024*1024}; buf[0] = '\0'; getlin("Load which lua file?", buf); if (buf[0] == '\033' || buf[0] == '\0') return ECMD_CANCEL; if (!strchr(buf, '.')) strcat(buf, ".lua"); (void) load_lua(buf, &sbi); } else pline(unavailcmd, ecname_from_fn(wiz_load_lua)); return ECMD_OK; } /* the #wizloaddes command - load a special level lua file */ int wiz_load_splua(void) { if (wizard) { char buf[BUFSZ]; buf[0] = '\0'; getlin("Load which des lua file?", buf); if (buf[0] == '\033' || buf[0] == '\0') return ECMD_CANCEL; if (!strchr(buf, '.')) strcat(buf, ".lua"); lspo_reset_level(NULL); (void) load_special(buf); lspo_finalize_level(NULL); } else pline(unavailcmd, ecname_from_fn(wiz_load_splua)); return ECMD_OK; } /* the #wizlevelport command - level teleport */ int wiz_level_tele(void) { if (wizard) level_tele(); else pline(unavailcmd, ecname_from_fn(wiz_level_tele)); return ECMD_OK; } RESTORE_WARNING_FORMAT_NONLITERAL /* #wizfliplevel - transpose the current level */ int wiz_flip_level(void) { static const char choices[] = "0123", prmpt[] = "Flip 0=randomly, 1=vertically, 2=horizontally, 3=both:"; /* * Does not handle * levregions, * monster mtrack, * migrating monsters aimed at returning to specific coordinates * on this level * as flipping is normally done only during level creation. */ if (wizard) { char c = yn_function(prmpt, choices, '\0', TRUE); if (c && strchr(choices, c)) { c -= '0'; if (!c) flip_level_rnd(3, TRUE); else flip_level((int) c, TRUE); docrt(); } else { pline("%s", Never_mind); } } return ECMD_OK; } /* #levelchange command - adjust hero's experience level */ int wiz_level_change(void) { char buf[BUFSZ] = DUMMY; int newlevel = 0; int ret; getlin("To what experience level do you want to be set?", buf); (void) mungspaces(buf); if (buf[0] == '\033' || buf[0] == '\0') ret = 0; else ret = sscanf(buf, "%d", &newlevel); if (ret != 1) { pline1(Never_mind); return ECMD_OK; } if (newlevel == u.ulevel) { You("are already that experienced."); } else if (newlevel < u.ulevel) { if (u.ulevel == 1) { You("are already as inexperienced as you can get."); return ECMD_OK; } if (newlevel < 1) newlevel = 1; while (u.ulevel > newlevel) losexp("#levelchange"); } else { if (u.ulevel >= MAXULEV) { You("are already as experienced as you can get."); return ECMD_OK; } if (newlevel > MAXULEV) newlevel = MAXULEV; while (u.ulevel < newlevel) pluslvl(FALSE); } u.ulevelmax = u.ulevel; return ECMD_OK; } DISABLE_WARNING_CONDEXPR_IS_CONSTANT /* #wiztelekinesis */ int wiz_telekinesis(void) { int ans = 0; coord cc; struct monst *mtmp = (struct monst *) 0; cc.x = u.ux; cc.y = u.uy; pline("Pick a monster to hurtle."); do { ans = getpos(&cc, TRUE, "a monster"); if (ans < 0 || cc.x < 1) return ECMD_CANCEL; if ((((mtmp = m_at(cc.x, cc.y)) != 0) && canspotmon(mtmp)) || u_at(cc.x, cc.y)) { if (!getdir("which direction?")) return ECMD_CANCEL; if (mtmp) { mhurtle(mtmp, u.dx, u.dy, 6); if (!DEADMONSTER(mtmp) && canspotmon(mtmp)) { cc.x = mtmp->mx; cc.y = mtmp->my; } } else { hurtle(u.dx, u.dy, 6, FALSE); cc.x = u.ux, cc.y = u.uy; } } } while (u.utotype == UTOTYPE_NONE); return ECMD_OK; } RESTORE_WARNING_CONDEXPR_IS_CONSTANT /* #panic command - test program's panic handling */ int wiz_panic(void) { if (iflags.debug_fuzzer) { u.uhp = u.uhpmax = 1000; u.uen = u.uenmax = 1000; return ECMD_OK; } if (paranoid_query(TRUE, "Do you want to call panic() and end your game?")) panic("Crash test."); return ECMD_OK; } /* #debugfuzzer command - fuzztest the program */ int wiz_fuzzer(void) { if (flags.suppress_alert < FEATURE_NOTICE_VER(3,7,0)) { pline("The fuzz tester will make NetHack execute random keypresses."); There("is no conventional way out of this mode."); } if (paranoid_query(TRUE, "Do you want to start fuzz testing?")) iflags.debug_fuzzer = TRUE; /* Thoth, take the reins */ return ECMD_OK; } /* #polyself command - change hero's form */ int wiz_polyself(void) { polyself(POLY_CONTROLLED); return ECMD_OK; } /* #seenv command */ int wiz_show_seenv(void) { winid win; coordxy x, y, startx, stopx, curx; int v; char row[COLNO + 1]; win = create_nhwindow(NHW_TEXT); /* * Each seenv description takes up 2 characters, so center * the seenv display around the hero. */ startx = max(1, u.ux - (COLNO / 4)); stopx = min(startx + (COLNO / 2), COLNO); /* can't have a line exactly 80 chars long */ if (stopx - startx == COLNO / 2) startx++; for (y = 0; y < ROWNO; y++) { for (x = startx, curx = 0; x < stopx; x++, curx += 2) { if (u_at(x, y)) { row[curx] = row[curx + 1] = '@'; } else { v = levl[x][y].seenv & 0xff; if (v == 0) row[curx] = row[curx + 1] = ' '; else Sprintf(&row[curx], "%02x", v); } } /* remove trailing spaces */ for (x = curx - 1; x >= 0; x--) if (row[x] != ' ') break; row[x + 1] = '\0'; putstr(win, 0, row); } display_nhwindow(win, TRUE); destroy_nhwindow(win); return ECMD_OK; } /* #vision command */ int wiz_show_vision(void) { winid win; coordxy x, y; int v; char row[COLNO + 1]; win = create_nhwindow(NHW_TEXT); Sprintf(row, "Flags: 0x%x could see, 0x%x in sight, 0x%x temp lit", COULD_SEE, IN_SIGHT, TEMP_LIT); putstr(win, 0, row); putstr(win, 0, ""); for (y = 0; y < ROWNO; y++) { for (x = 1; x < COLNO; x++) { if (u_at(x, y)) { row[x] = '@'; } else { v = gv.viz_array[y][x]; /* data access should be hidden */ row[x] = (v == 0) ? ' ' : ('0' + v); } } /* remove trailing spaces */ for (x = COLNO - 1; x >= 1; x--) if (row[x] != ' ') break; row[x + 1] = '\0'; putstr(win, 0, &row[1]); } display_nhwindow(win, TRUE); destroy_nhwindow(win); return ECMD_OK; } /* #wmode command */ int wiz_show_wmodes(void) { winid win; coordxy x, y; char row[COLNO + 1]; struct rm *lev; boolean istty = WINDOWPORT(tty); win = create_nhwindow(NHW_TEXT); if (istty) putstr(win, 0, ""); /* tty only: blank top line */ for (y = 0; y < ROWNO; y++) { for (x = 0; x < COLNO; x++) { lev = &levl[x][y]; if (u_at(x, y)) row[x] = '@'; else if (IS_WALL(lev->typ) || lev->typ == SDOOR) row[x] = '0' + (lev->wall_info & WM_MASK); else if (lev->typ == CORR) row[x] = '#'; else if (IS_ROOM(lev->typ) || IS_DOOR(lev->typ)) row[x] = '.'; else row[x] = 'x'; } row[COLNO] = '\0'; /* map column 0, levl[0][], is off the left edge of the screen */ putstr(win, 0, &row[1]); } display_nhwindow(win, TRUE); destroy_nhwindow(win); return ECMD_OK; } /* wizard mode variant of #terrain; internal levl[][].typ values in base-36 */ void wiz_map_levltyp(void) { winid win; coordxy x, y; int terrain; char row[COLNO + 1]; boolean istty = !strcmp(windowprocs.name, "tty"); win = create_nhwindow(NHW_TEXT); /* map row 0, levl[][0], is drawn on the second line of tty screen */ if (istty) putstr(win, 0, ""); /* tty only: blank top line */ for (y = 0; y < ROWNO; y++) { /* map column 0, levl[0][], is off the left edge of the screen; it should always have terrain type "undiggable stone" */ for (x = 1; x < COLNO; x++) { terrain = levl[x][y].typ; /* assumes there aren't more than 10+26+26 terrain types */ row[x - 1] = (char) ((terrain == STONE && !may_dig(x, y)) ? '*' : (terrain < 10) ? '0' + terrain : (terrain < 36) ? 'a' + terrain - 10 : 'A' + terrain - 36); } x--; if (levl[0][y].typ != STONE || may_dig(0, y)) row[x++] = '!'; row[x] = '\0'; putstr(win, 0, row); } { char dsc[COLBUFSZ]; s_level *slev = Is_special(&u.uz); Sprintf(dsc, "D:%d,L:%d", u.uz.dnum, u.uz.dlevel); /* [dungeon branch features currently omitted] */ /* special level features */ if (slev) { Sprintf(eos(dsc), " \"%s\"", slev->proto); /* special level flags (note: dungeon.def doesn't set `maze' or `hell' for any specific levels so those never show up) */ if (slev->flags.maze_like) Strcat(dsc, " mazelike"); if (slev->flags.hellish) Strcat(dsc, " hellish"); if (slev->flags.town) Strcat(dsc, " town"); if (slev->flags.rogue_like) Strcat(dsc, " roguelike"); /* alignment currently omitted to save space */ } /* level features */ if (gl.level.flags.nfountains) Sprintf(eos(dsc), " %c:%d", defsyms[S_fountain].sym, (int) gl.level.flags.nfountains); if (gl.level.flags.nsinks) Sprintf(eos(dsc), " %c:%d", defsyms[S_sink].sym, (int) gl.level.flags.nsinks); if (gl.level.flags.has_vault) Strcat(dsc, " vault"); if (gl.level.flags.has_shop) Strcat(dsc, " shop"); if (gl.level.flags.has_temple) Strcat(dsc, " temple"); if (gl.level.flags.has_court) Strcat(dsc, " throne"); if (gl.level.flags.has_zoo) Strcat(dsc, " zoo"); if (gl.level.flags.has_morgue) Strcat(dsc, " morgue"); if (gl.level.flags.has_barracks) Strcat(dsc, " barracks"); if (gl.level.flags.has_beehive) Strcat(dsc, " hive"); if (gl.level.flags.has_swamp) Strcat(dsc, " swamp"); /* level flags */ if (gl.level.flags.noteleport) Strcat(dsc, " noTport"); if (gl.level.flags.hardfloor) Strcat(dsc, " noDig"); if (gl.level.flags.nommap) Strcat(dsc, " noMMap"); if (!gl.level.flags.hero_memory) Strcat(dsc, " noMem"); if (gl.level.flags.shortsighted) Strcat(dsc, " shortsight"); if (gl.level.flags.graveyard) Strcat(dsc, " graveyard"); if (gl.level.flags.is_maze_lev) Strcat(dsc, " maze"); if (gl.level.flags.is_cavernous_lev) Strcat(dsc, " cave"); if (gl.level.flags.arboreal) Strcat(dsc, " tree"); if (Sokoban) Strcat(dsc, " sokoban-rules"); /* non-flag info; probably should include dungeon branching checks (extra stairs and magic portals) here */ if (Invocation_lev(&u.uz)) Strcat(dsc, " invoke"); if (On_W_tower_level(&u.uz)) Strcat(dsc, " tower"); /* append a branch identifier for completeness' sake */ if (u.uz.dnum == 0) Strcat(dsc, " dungeon"); else if (u.uz.dnum == mines_dnum) Strcat(dsc, " mines"); else if (In_sokoban(&u.uz)) Strcat(dsc, " sokoban"); else if (u.uz.dnum == quest_dnum) Strcat(dsc, " quest"); else if (Is_knox(&u.uz)) Strcat(dsc, " ludios"); else if (u.uz.dnum == 1) Strcat(dsc, " gehennom"); else if (u.uz.dnum == tower_dnum) Strcat(dsc, " vlad"); else if (In_endgame(&u.uz)) Strcat(dsc, " endgame"); else { /* somebody's added a dungeon branch we're not expecting */ const char *brname = gd.dungeons[u.uz.dnum].dname; if (!brname || !*brname) brname = "unknown"; if (!strncmpi(brname, "the ", 4)) brname += 4; Sprintf(eos(dsc), " %s", brname); } /* limit the line length to map width */ if (strlen(dsc) >= COLNO) dsc[COLNO - 1] = '\0'; /* truncate */ putstr(win, 0, dsc); } display_nhwindow(win, TRUE); destroy_nhwindow(win); return; } DISABLE_WARNING_FORMAT_NONLITERAL /* explanation of base-36 output from wiz_map_levltyp() */ void wiz_levltyp_legend(void) { winid win; int i, j, last, c; const char *dsc, *fmt; char buf[BUFSZ]; win = create_nhwindow(NHW_TEXT); putstr(win, 0, "#terrain encodings:"); putstr(win, 0, ""); fmt = " %c - %-28s"; /* TODO: include tab-separated variant for win32 */ *buf = '\0'; /* output in pairs, left hand column holds [0],[1],...,[N/2-1] and right hand column holds [N/2],[N/2+1],...,[N-1]; N ('last') will always be even, and may or may not include the empty string entry to pad out the final pair, depending upon how many other entries are present in levltyp[] */ last = SIZE(levltyp) & ~1; for (i = 0; i < last / 2; ++i) for (j = i; j < last; j += last / 2) { dsc = levltyp[j]; c = !*dsc ? ' ' : !strncmp(dsc, "unreachable", 11) ? '*' /* same int-to-char conversion as wiz_map_levltyp() */ : (j < 10) ? '0' + j : (j < 36) ? 'a' + j - 10 : 'A' + j - 36; Sprintf(eos(buf), fmt, c, dsc); if (j > i) { putstr(win, 0, buf); *buf = '\0'; } } display_nhwindow(win, TRUE); destroy_nhwindow(win); return; } RESTORE_WARNING_FORMAT_NONLITERAL DISABLE_WARNING_CONDEXPR_IS_CONSTANT /* #wizsmell command - test usmellmon(). */ int wiz_smell(void) { struct monst *mtmp; /* monster being smelled */ struct permonst *mptr; int ans, glyph; coord cc; /* screen pos to sniff */ boolean is_you; cc.x = u.ux; cc.y = u.uy; if (!olfaction(gy.youmonst.data)) { You("are incapable of detecting odors in your present form."); return ECMD_OK; } You("can move the cursor to a monster that you want to smell."); do { pline("Pick a monster to smell."); ans = getpos(&cc, TRUE, "a monster"); if (ans < 0 || cc.x < 0) { return ECMD_CANCEL; /* done */ } is_you = FALSE; if (u_at(cc.x, cc.y)) { if (u.usteed) { mptr = u.usteed->data; } else { mptr = gy.youmonst.data; is_you = TRUE; } } else if ((mtmp = m_at(cc.x, cc.y)) != (struct monst *) 0) { mptr = mtmp->data; } else { mptr = (struct permonst *) 0; } /* Buglet: mapping or unmapping "remembered, unseen monster" should cause time to elapse; since we're in wizmode, don't bother */ glyph = glyph_at(cc.x, cc.y); /* Is it a monster? */ if (mptr) { if (is_you) You("surreptitiously sniff under your %s.", body_part(ARM)); if (!usmellmon(mptr)) pline("%s to not give off any smell.", is_you ? "You seem" : "That monster seems"); if (!glyph_is_monster(glyph)) map_invisible(cc.x, cc.y); } else { You("don't smell any monster there."); if (glyph_is_invisible(glyph)) unmap_invisible(cc.x, cc.y); } } while (TRUE); return ECMD_OK; } RESTORE_WARNING_CONDEXPR_IS_CONSTANT DISABLE_WARNING_FORMAT_NONLITERAL #define DEFAULT_TIMEOUT_INCR 30 /* #wizinstrinsic command to set some intrinsics for testing */ int wiz_intrinsic(void) { if (wizard) { static const char wizintrinsic[] = "#wizintrinsic"; static const char fmt[] = "You are%s %s."; winid win; anything any; char buf[BUFSZ]; int i, j, n, amt, typ, p = 0; long oldtimeout, newtimeout; const char *propname; menu_item *pick_list = (menu_item *) 0; int clr = NO_COLOR; any = cg.zeroany; win = create_nhwindow(NHW_MENU); start_menu(win, MENU_BEHAVE_STANDARD); if (iflags.cmdassist) { /* start menu with a subtitle */ Sprintf(buf, "[Precede any selection with a count to increment by other than %d.]", DEFAULT_TIMEOUT_INCR); add_menu_str(win, buf); } for (i = 0; (propname = property_by_index(i, &p)) != 0; ++i) { if (p == HALLUC_RES) { /* Grayswandir vs hallucination; ought to be redone to use u.uprops[HALLUC].blocked instead of being treated as a separate property; letting in be manually toggled even only in wizard mode would be asking for trouble... */ continue; } if (p == FIRE_RES) { /* FIRE_RES and properties beyond it (in the propertynames[] ordering, not their numerical PROP values), can only be set to timed values here so show a separator */ add_menu_str(win, "--"); } any.a_int = i + 1; /* +1: avoid 0 */ oldtimeout = u.uprops[p].intrinsic & TIMEOUT; if (oldtimeout) Sprintf(buf, "%-27s [%li]", propname, oldtimeout); else Sprintf(buf, "%s", propname); add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); } end_menu(win, "Which intrinsics?"); n = select_menu(win, PICK_ANY, &pick_list); destroy_nhwindow(win); for (j = 0; j < n; ++j) { i = pick_list[j].item.a_int - 1; /* -1: reverse +1 above */ propname = property_by_index(i, &p); oldtimeout = u.uprops[p].intrinsic & TIMEOUT; amt = (pick_list[j].count == -1L) ? DEFAULT_TIMEOUT_INCR : (int) pick_list[j].count; if (amt <= 0) /* paranoia */ continue; newtimeout = oldtimeout + (long) amt; switch (p) { case SICK: case SLIMED: case STONED: if (oldtimeout > 0L && newtimeout > oldtimeout) newtimeout = oldtimeout; break; } switch (p) { case BLINDED: make_blinded(newtimeout, TRUE); break; #if 0 /* make_confused() only gives feedback when confusion is * ending so use the 'default' case for it instead */ case CONFUSION: make_confused(newtimeout, TRUE); break; #endif /*0*/ case DEAF: make_deaf(newtimeout, TRUE); break; case HALLUC: make_hallucinated(newtimeout, TRUE, 0L); break; case SICK: typ = !rn2(2) ? SICK_VOMITABLE : SICK_NONVOMITABLE; make_sick(newtimeout, wizintrinsic, TRUE, typ); break; case SLIMED: Sprintf(buf, fmt, !Slimed ? "" : " still", "turning into slime"); make_slimed(newtimeout, buf); break; case STONED: Sprintf(buf, fmt, !Stoned ? "" : " still", "turning into stone"); make_stoned(newtimeout, buf, KILLED_BY, wizintrinsic); break; case STUNNED: make_stunned(newtimeout, TRUE); break; case VOMITING: Sprintf(buf, fmt, !Vomiting ? "" : " still", "vomiting"); make_vomiting(newtimeout, FALSE); pline1(buf); break; case WARN_OF_MON: if (!Warn_of_mon) { gc.context.warntype.speciesidx = PM_GRID_BUG; gc.context.warntype.species = &mons[gc.context.warntype.speciesidx]; } goto def_feedback; case GLIB: /* slippery fingers might need a persistent inventory update so needs more than simple incr_itimeout() but we want the pline() issued with that */ make_glib((int) newtimeout); /*FALLTHRU*/ default: def_feedback: if (p != GLIB) incr_itimeout(&u.uprops[p].intrinsic, amt); disp.botl = TRUE; /* have pline() do a status update */ pline("Timeout for %s %s %d.", propname, oldtimeout ? "increased by" : "set to", amt); break; } /* this has to be after incr_itimeout() */ if (p == LEVITATION || p == FLYING) float_vs_flight(); else if (p == PROT_FROM_SHAPE_CHANGERS) rescham(); } if (n >= 1) free((genericptr_t) pick_list); docrt(); } else pline(unavailcmd, ecname_from_fn(wiz_intrinsic)); return ECMD_OK; } RESTORE_WARNING_FORMAT_NONLITERAL /* #wizrumorcheck command - verify each rumor access */ int wiz_rumor_check(void) { rumor_check(); return ECMD_OK; } /* * wizard mode sanity_check code */ static const char template[] = "%-27s %4ld %6ld"; static const char stats_hdr[] = " count bytes"; static const char stats_sep[] = "--------------------------- ----- -------"; static int size_obj(struct obj *otmp) { int sz = (int) sizeof (struct obj); if (otmp->oextra) { sz += (int) sizeof (struct oextra); if (ONAME(otmp)) sz += (int) strlen(ONAME(otmp)) + 1; if (OMONST(otmp)) sz += size_monst(OMONST(otmp), FALSE); if (OMAILCMD(otmp)) sz += (int) strlen(OMAILCMD(otmp)) + 1; /* sz += (int) sizeof (unsigned); -- now part of oextra itself */ } return sz; } static void count_obj(struct obj *chain, long *total_count, long *total_size, boolean top, boolean recurse) { long count, size; struct obj *obj; for (count = size = 0, obj = chain; obj; obj = obj->nobj) { if (top) { count++; size += size_obj(obj); } if (recurse && obj->cobj) count_obj(obj->cobj, total_count, total_size, TRUE, TRUE); } *total_count += count; *total_size += size; } DISABLE_WARNING_FORMAT_NONLITERAL /* RESTORE_WARNING follows wiz_show_stats */ static void obj_chain( winid win, const char *src, struct obj *chain, boolean force, long *total_count, long *total_size) { char buf[BUFSZ]; long count = 0L, size = 0L; count_obj(chain, &count, &size, TRUE, FALSE); if (count || size || force) { *total_count += count; *total_size += size; Sprintf(buf, template, src, count, size); putstr(win, 0, buf); } } static void mon_invent_chain( winid win, const char *src, struct monst *chain, long *total_count, long *total_size) { char buf[BUFSZ]; long count = 0, size = 0; struct monst *mon; for (mon = chain; mon; mon = mon->nmon) count_obj(mon->minvent, &count, &size, TRUE, FALSE); if (count || size) { *total_count += count; *total_size += size; Sprintf(buf, template, src, count, size); putstr(win, 0, buf); } } static void contained_stats( winid win, const char *src, long *total_count, long *total_size) { char buf[BUFSZ]; long count = 0, size = 0; struct monst *mon; count_obj(gi.invent, &count, &size, FALSE, TRUE); count_obj(fobj, &count, &size, FALSE, TRUE); count_obj(gl.level.buriedobjlist, &count, &size, FALSE, TRUE); count_obj(gm.migrating_objs, &count, &size, FALSE, TRUE); /* DEADMONSTER check not required in this loop since they have no * inventory */ for (mon = fmon; mon; mon = mon->nmon) count_obj(mon->minvent, &count, &size, FALSE, TRUE); for (mon = gm.migrating_mons; mon; mon = mon->nmon) count_obj(mon->minvent, &count, &size, FALSE, TRUE); if (count || size) { *total_count += count; *total_size += size; Sprintf(buf, template, src, count, size); putstr(win, 0, buf); } } static int size_monst(struct monst *mtmp, boolean incl_wsegs) { int sz = (int) sizeof (struct monst); if (mtmp->wormno && incl_wsegs) sz += size_wseg(mtmp); if (mtmp->mextra) { sz += (int) sizeof (struct mextra); if (MGIVENNAME(mtmp)) sz += (int) strlen(MGIVENNAME(mtmp)) + 1; if (EGD(mtmp)) sz += (int) sizeof (struct egd); if (EPRI(mtmp)) sz += (int) sizeof (struct epri); if (ESHK(mtmp)) sz += (int) sizeof (struct eshk); if (EMIN(mtmp)) sz += (int) sizeof (struct emin); if (EDOG(mtmp)) sz += (int) sizeof (struct edog); /* mextra->mcorpsenm doesn't point to more memory */ } return sz; } static void mon_chain( winid win, const char *src, struct monst *chain, boolean force, long *total_count, long *total_size) { char buf[BUFSZ]; long count, size; struct monst *mon; /* mon->wormno means something different for migrating_mons and mydogs */ boolean incl_wsegs = !strcmpi(src, "fmon"); count = size = 0L; for (mon = chain; mon; mon = mon->nmon) { count++; size += size_monst(mon, incl_wsegs); } if (count || size || force) { *total_count += count; *total_size += size; Sprintf(buf, template, src, count, size); putstr(win, 0, buf); } } static void misc_stats( winid win, long *total_count, long *total_size) { char buf[BUFSZ], hdrbuf[QBUFSZ]; long count, size; int idx; struct trap *tt; struct damage *sd; /* shop damage */ struct kinfo *k; /* delayed killer */ struct cemetery *bi; /* bones info */ /* traps and engravings are output unconditionally; * others only if nonzero */ count = size = 0L; for (tt = gf.ftrap; tt; tt = tt->ntrap) { ++count; size += (long) sizeof *tt; } *total_count += count; *total_size += size; Sprintf(hdrbuf, "traps, size %ld", (long) sizeof (struct trap)); Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); count = size = 0L; engr_stats("engravings, size %ld+text", hdrbuf, &count, &size); *total_count += count; *total_size += size; Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); count = size = 0L; light_stats("light sources, size %ld", hdrbuf, &count, &size); if (count || size) { *total_count += count; *total_size += size; Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); } count = size = 0L; timer_stats("timers, size %ld", hdrbuf, &count, &size); if (count || size) { *total_count += count; *total_size += size; Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); } count = size = 0L; for (sd = gl.level.damagelist; sd; sd = sd->next) { ++count; size += (long) sizeof *sd; } if (count || size) { *total_count += count; *total_size += size; Sprintf(hdrbuf, "shop damage, size %ld", (long) sizeof (struct damage)); Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); } count = size = 0L; region_stats("regions, size %ld+%ld*rect+N", hdrbuf, &count, &size); if (count || size) { *total_count += count; *total_size += size; Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); } count = size = 0L; for (k = gk.killer.next; k; k = k->next) { ++count; size += (long) sizeof *k; } if (count || size) { *total_count += count; *total_size += size; Sprintf(hdrbuf, "delayed killer%s, size %ld", plur(count), (long) sizeof (struct kinfo)); Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); } count = size = 0L; for (bi = gl.level.bonesinfo; bi; bi = bi->next) { ++count; size += (long) sizeof *bi; } if (count || size) { *total_count += count; *total_size += size; Sprintf(hdrbuf, "bones history, size %ld", (long) sizeof (struct cemetery)); Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); } count = size = 0L; for (idx = 0; idx < NUM_OBJECTS; ++idx) if (objects[idx].oc_uname) { ++count; size += (long) (strlen(objects[idx].oc_uname) + 1); } if (count || size) { *total_count += count; *total_size += size; Strcpy(hdrbuf, "object type names, text"); Sprintf(buf, template, hdrbuf, count, size); putstr(win, 0, buf); } } static void you_sanity_check(void) { struct monst *mtmp; if (u.uswallow && !u.ustuck) { /* this probably ought to be panic() */ impossible("sanity_check: swallowed by nothing?"); display_nhwindow(WIN_MESSAGE, TRUE); /* try to recover from whatever the problem is */ u.uswallow = 0; u.uswldtim = 0; docrt(); } if ((mtmp = m_at(u.ux, u.uy)) != 0) { /* u.usteed isn't on the map */ if (u.ustuck != mtmp) impossible("sanity_check: you over monster"); } check_wornmask_slots(); (void) check_invent_gold("invent"); } void sanity_check(void) { if (iflags.sanity_no_check) { /* in case a recurring sanity_check warning occurs, we mustn't re-trigger it when ^P is used, otherwise msg_window:Single and msg_window:Combination will always repeat the most recent instance, never able to go back to any earlier messages */ iflags.sanity_no_check = FALSE; return; } you_sanity_check(); obj_sanity_check(); timer_sanity_check(); mon_sanity_check(); light_sources_sanity_check(); bc_sanity_check(); trap_sanity_check(); engraving_sanity_check(); } /* qsort() comparison routine for use in list_migrating_mons() */ static int QSORTCALLBACK migrsort_cmp(const genericptr vptr1, const genericptr vptr2) { const struct monst *m1 = *(const struct monst **) vptr1, *m2 = *(const struct monst **) vptr2; int d1 = (int) m1->mux, l1 = (int) m1->muy, d2 = (int) m2->mux, l2 = (int) m2->muy; /* if different branches, sort by dungeon number */ if (d1 != d2) return d1 - d2; /* within same branch, sort by level number */ if (l1 != l2) return l1 - l2; /* same destination level: use a tie-breaker to force stable sort; monst->m_id is unsigned so we need more than just simple subtraction */ return (m1->m_id < m2->m_id) ? -1 : (m1->m_id > m2->m_id); } /* called by #migratemons; displays count of migrating monsters, optionally displays them as well */ static void list_migrating_mons( d_level *nextlevl) /* default destination for wiz_migrate_mons() */ { winid win = WIN_ERR; boolean showit = FALSE; unsigned n; int xyloc; coordxy x, y; char c, prmpt[10], xtra[10], buf[BUFSZ]; struct monst *mtmp, **marray; int here = 0, nxtlv = 0, other = 0; for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { if (mtmp->mux == u.uz.dnum && mtmp->muy == u.uz.dlevel) ++here; else if (mtmp->mux == nextlevl->dnum && mtmp->muy == nextlevl->dlevel) ++nxtlv; else ++other; } if (here + nxtlv + other == 0) { pline("No monsters currently migrating."); } else { pline( "%d mon%s pending for current level, %d for next level, %d for others.", here, plur(here), nxtlv, other); prmpt[0] = xtra[0] = '\0'; (void) strkitten(here ? prmpt : xtra, 'c'); (void) strkitten(nxtlv ? prmpt : xtra, 'n'); (void) strkitten(other ? prmpt : xtra, 'o'); Strcat(prmpt, "a q"); if (*xtra) Sprintf(eos(prmpt), "%c%s", '\033', xtra); c = yn_function("List which?", prmpt, 'q', TRUE); n = (c == 'c') ? here : (c == 'n') ? nxtlv : (c == 'o') ? other : (c == 'a') ? here + nxtlv + other : 0; if (n > 0) { win = create_nhwindow(NHW_TEXT); switch (c) { case 'c': case 'n': case 'o': Sprintf(buf, "Monster%s migrating to %s:", plur(n), (c == 'c') ? "current level" : (c == 'n') ? "next level" : "'other' levels"); break; default: Strcpy(buf, "All migrating monsters:"); break; } putstr(win, 0, buf); putstr(win, 0, ""); /* collect the migrating monsters into an array; for 'o' and 'a' where multiple destination levels might be present, sort by the destination; 'c' and 'n' don't need to be sorted but we do that anyway to get the same tie-breaker as 'o' and 'a' */ marray = (struct monst **) alloc((n + 1) * sizeof *marray); n = 0; for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { if (c == 'a') showit = TRUE; else if (mtmp->mux == u.uz.dnum && mtmp->muy == u.uz.dlevel) showit = (c == 'c'); else if (mtmp->mux == nextlevl->dnum && mtmp->muy == nextlevl->dlevel) showit = (c == 'n'); else showit = (c == 'o'); if (showit) marray[n++] = mtmp; } marray[n] = (struct monst *) 0; /* mark end for traversal loop */ if (n > 1) qsort((genericptr_t) marray, (size_t) n, sizeof *marray, migrsort_cmp); /* sort elements [0] through [n-1] */ for (n = 0; (mtmp = marray[n]) != 0; ++n) { Sprintf(buf, " %s", minimal_monnam(mtmp, FALSE)); /* minimal_monnam() appends map coordinates; strip that */ (void) strsubst(buf, " <0,0>", ""); if (has_mgivenname(mtmp)) /* if mtmp is named, include that */ Sprintf(eos(buf), " named %s", MGIVENNAME(mtmp)); if (c == 'o' || c == 'a') Sprintf(eos(buf), " to %d:%d", mtmp->mux, mtmp->muy); xyloc = mtmp->mtrack[0].x; /* (for legibility) */ if (xyloc == MIGR_EXACT_XY) { x = mtmp->mtrack[1].x; y = mtmp->mtrack[1].y; Sprintf(eos(buf), " at <%d,%d>", (int) x, (int) y); } putstr(win, 0, buf); } free((genericptr_t) marray); display_nhwindow(win, FALSE); destroy_nhwindow(win); } else if (c != 'q') { pline("None."); } } } /* the #stats command * Display memory usage of all monsters and objects on the level. */ int wiz_show_stats(void) { char buf[BUFSZ]; winid win; long total_obj_size, total_obj_count, total_mon_size, total_mon_count, total_ovr_size, total_ovr_count, total_misc_size, total_misc_count; win = create_nhwindow(NHW_TEXT); putstr(win, 0, "Current memory statistics:"); total_obj_count = total_obj_size = 0L; putstr(win, 0, stats_hdr); Sprintf(buf, " Objects, base size %ld", (long) sizeof (struct obj)); putstr(win, 0, buf); obj_chain(win, "invent", gi.invent, TRUE, &total_obj_count, &total_obj_size); obj_chain(win, "fobj", fobj, TRUE, &total_obj_count, &total_obj_size); obj_chain(win, "buried", gl.level.buriedobjlist, FALSE, &total_obj_count, &total_obj_size); obj_chain(win, "migrating obj", gm.migrating_objs, FALSE, &total_obj_count, &total_obj_size); obj_chain(win, "billobjs", gb.billobjs, FALSE, &total_obj_count, &total_obj_size); mon_invent_chain(win, "minvent", fmon, &total_obj_count, &total_obj_size); mon_invent_chain(win, "migrating minvent", gm.migrating_mons, &total_obj_count, &total_obj_size); contained_stats(win, "contained", &total_obj_count, &total_obj_size); putstr(win, 0, stats_sep); Sprintf(buf, template, " Obj total", total_obj_count, total_obj_size); putstr(win, 0, buf); total_mon_count = total_mon_size = 0L; putstr(win, 0, ""); Sprintf(buf, " Monsters, base size %ld", (long) sizeof (struct monst)); putstr(win, 0, buf); mon_chain(win, "fmon", fmon, TRUE, &total_mon_count, &total_mon_size); mon_chain(win, "migrating", gm.migrating_mons, FALSE, &total_mon_count, &total_mon_size); /* 'gm.mydogs' is only valid during level change or end of game disclosure, but conceivably we've been called from within debugger at such time */ if (gm.mydogs) /* monsters accompanying hero */ mon_chain(win, "mydogs", gm.mydogs, FALSE, &total_mon_count, &total_mon_size); putstr(win, 0, stats_sep); Sprintf(buf, template, " Mon total", total_mon_count, total_mon_size); putstr(win, 0, buf); total_ovr_count = total_ovr_size = 0L; putstr(win, 0, ""); putstr(win, 0, " Overview"); overview_stats(win, template, &total_ovr_count, &total_ovr_size); putstr(win, 0, stats_sep); Sprintf(buf, template, " Over total", total_ovr_count, total_ovr_size); putstr(win, 0, buf); total_misc_count = total_misc_size = 0L; putstr(win, 0, ""); putstr(win, 0, " Miscellaneous"); misc_stats(win, &total_misc_count, &total_misc_size); putstr(win, 0, stats_sep); Sprintf(buf, template, " Misc total", total_misc_count, total_misc_size); putstr(win, 0, buf); putstr(win, 0, ""); putstr(win, 0, stats_sep); Sprintf(buf, template, " Grand total", (total_obj_count + total_mon_count + total_ovr_count + total_misc_count), (total_obj_size + total_mon_size + total_ovr_size + total_misc_size)); putstr(win, 0, buf); #if defined(__BORLANDC__) && !defined(_WIN32) show_borlandc_stats(win); #endif display_nhwindow(win, FALSE); destroy_nhwindow(win); return ECMD_OK; } RESTORE_WARNING_FORMAT_NONLITERAL #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) /* the #wizdispmacros command * Verify that some display macros are returning sane values */ int wiz_display_macros(void) { static const char display_issues[] = "Display macro issues:"; char buf[BUFSZ]; winid win; int glyph, test, trouble = 0, no_glyph = NO_GLYPH, max_glyph = MAX_GLYPH; win = create_nhwindow(NHW_TEXT); for (glyph = 0; glyph < MAX_GLYPH; ++glyph) { /* glyph_is_cmap / glyph_to_cmap() */ if (glyph_is_cmap(glyph)) { test = glyph_to_cmap(glyph); /* check for MAX_GLYPH return */ if (test == no_glyph) { if (!trouble++) putstr(win, 0, display_issues); Sprintf(buf, "glyph_is_cmap() / glyph_to_cmap(glyph=%d)" " sync failure, returned NO_GLYPH (%d)", glyph, test); putstr(win, 0, buf); } if (glyph_is_cmap_zap(glyph) && !(test >= S_vbeam && test <= S_rslant)) { if (!trouble++) putstr(win, 0, display_issues); Sprintf(buf, "glyph_is_cmap_zap(glyph=%d) returned non-zap cmap %d", glyph, test); putstr(win, 0, buf); } /* check against defsyms array subscripts */ if (!IndexOk(test, defsyms)) { if (!trouble++) putstr(win, 0, display_issues); Sprintf(buf, "glyph_to_cmap(glyph=%d) returns %d" " exceeds defsyms[%d] bounds (MAX_GLYPH = %d)", glyph, test, SIZE(defsyms), max_glyph); putstr(win, 0, buf); } } /* glyph_is_monster / glyph_to_mon */ if (glyph_is_monster(glyph)) { test = glyph_to_mon(glyph); /* check against mons array subscripts */ if (test < 0 || test >= NUMMONS) { if (!trouble++) putstr(win, 0, display_issues); Sprintf(buf, "glyph_to_mon(glyph=%d) returns %d" " exceeds mons[%d] bounds", glyph, test, NUMMONS); putstr(win, 0, buf); } } /* glyph_is_object / glyph_to_obj */ if (glyph_is_object(glyph)) { test = glyph_to_obj(glyph); /* check against objects array subscripts */ if (test < 0 || test > NUM_OBJECTS) { if (!trouble++) putstr(win, 0, display_issues); Sprintf(buf, "glyph_to_obj(glyph=%d) returns %d" " exceeds objects[%d] bounds", glyph, test, NUM_OBJECTS); putstr(win, 0, buf); } } } if (!trouble) putstr(win, 0, "No display macro issues detected."); display_nhwindow(win, FALSE); destroy_nhwindow(win); return ECMD_OK; } #endif /* (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) */ #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) /* the #wizmondiff command */ int wiz_mon_diff(void) { static const char window_title[] = "Review of monster difficulty ratings" " [index:level]:"; char buf[BUFSZ]; winid win; int mhardcoded = 0, mcalculated = 0, trouble = 0, cnt = 0, mdiff = 0; int mlev; struct permonst *ptr; /* * Possible extension: choose between showing discrepancies, * showing all monsters, or monsters within a particular class. */ win = create_nhwindow(NHW_TEXT); for (ptr = &mons[0]; ptr->mlet; ptr++, cnt++) { mcalculated = mstrength(ptr); mhardcoded = (int) ptr->difficulty; mdiff = mhardcoded - mcalculated; if (mdiff) { if (!trouble++) putstr(win, 0, window_title); mlev = (int) ptr->mlevel; if (mlev > 50) /* hack for named demons */ mlev = 50; Snprintf(buf, sizeof buf, "%-18s [%3d:%2d]: calculated: %2d, hardcoded: %2d (%+d)", ptr->pmnames[NEUTRAL], cnt, mlev, mcalculated, mhardcoded, mdiff); putstr(win, 0, buf); } } if (!trouble) putstr(win, 0, "No monster difficulty discrepancies were detected."); display_nhwindow(win, FALSE); destroy_nhwindow(win); return ECMD_OK; } #endif /* (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) */ /* #migratemons command */ int wiz_migrate_mons(void) { #ifdef DEBUG_MIGRATING_MONS int mcount; char inbuf[BUFSZ]; struct permonst *ptr; struct monst *mtmp; #endif d_level tolevel; if (Is_stronghold(&u.uz)) assign_level(&tolevel, &valley_level); else if (!Is_botlevel(&u.uz)) get_level(&tolevel, depth(&u.uz) + 1); else tolevel.dnum = 0, tolevel.dlevel = 0; list_migrating_mons(&tolevel); #ifdef DEBUG_MIGRATING_MONS inbuf[0] = '\033', inbuf[1] = '\0'; if (tolevel.dnum || tolevel.dlevel) getlin("How many random monsters to migrate to next level? [0]", inbuf); else pline("Can't get there from here."); if (*inbuf == '\033') return ECMD_OK; mcount = atoi(inbuf); if (mcount < 1) mcount = 0; else if (mcount > ((COLNO - 1) * ROWNO)) mcount = (COLNO - 1) * ROWNO; while (mcount > 0) { ptr = rndmonst(); mtmp = makemon(ptr, 0, 0, MM_NOMSG); if (mtmp) migrate_to_level(mtmp, ledger_no(&tolevel), MIGR_RANDOM, (coord *) 0); mcount--; } #endif /* DEBUG_MIGRATING_MONS */ return ECMD_OK; } /*wizcmds.c*/