Files
nethack/src/wizcmds.c
2024-03-08 09:42:03 -05:00

1822 lines
58 KiB
C

/* 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*/