extend #timeout to cover all properties

Extend the wizard mode #timeout command:  show timeouts for all 67
intrinsics rather than just a handful.  Most won't appear because
they don't have any way to receive a timed value.  Except for...

Extend the wizard mode #wizintrinsic command:  allow setting a
brief (30 turn) timeout for any/every intrinsic, not just for
deafness.  It ought to prompt for duration, but that's more effort
than I'm willing to expend.  This might turn up lots of quirks that
the code isn't prepared to handle (like setting life-saving to
non-zero will break the assumption that it comes from worn amulet).
Perhaps some will warrant fixing, others just a shrug.

There are still some timed events that aren't listed by #timeout:
remaining duration to stay polymorphed in current form, number of
turns until it's safe to pray, luck decay, number of turns until
next attribute exercise/abuse check, probably others that I'm
overlooking.

Bug fix:  while testing, I observed
  Your limbs have turned to stone.
  You have turned to stone.
  You can hear again.
  You are a statue.
when deafness and petrification were timing out at the same time.
This modifies the stoning and sliming countdowns to extend deafness
duration a little if it's about to time out at the tail end of the
stoning or sliming sequence, so that "you can hear again" won't
happen until after life-saving.  There are probably other variations
of simultaneous or near simultaneous timeout that interact oddly.
This commit is contained in:
PatR
2017-06-26 01:04:58 -07:00
parent 8e213472c2
commit 8e8ea0566b
3 changed files with 171 additions and 66 deletions

View File

@@ -5,7 +5,11 @@
#ifndef PROP_H
#define PROP_H
/*** What the properties are ***/
/*** What the properties are ***
*
* note: propertynames[] array in timeout.c must be kept in synch with these.
* Property #0 is not used.
*/
/* Resistances to troubles */
#define FIRE_RES 1
#define COLD_RES 2

111
src/cmd.c
View File

@@ -1141,41 +1141,94 @@ STATIC_PTR int
wiz_intrinsic(VOID_ARGS)
{
if (wizard) {
extern const char *const propertynames[]; /* timeout.c */
static const char wizintrinsic[] = "#wizintrinsic";
static const char fmt[] = "You are%s %s.";
winid win;
anything any;
int i, n, accelerator;
char buf[BUFSZ];
int i, n, p, amt, typ;
long oldtimeout, newtimeout;
const char *propname;
menu_item *pick_list = (menu_item *) 0;
static const char *const intrinsics[] = {
"deafness",
};
win = create_nhwindow(NHW_MENU);
start_menu(win);
accelerator = 0;
for (i = 0; i < SIZE(intrinsics); ++i) {
accelerator = intrinsics[i][0];
any.a_int = i + 1;
add_menu(win, NO_GLYPH, &any, accelerator, 0,
ATR_NONE, intrinsics[i], FALSE);
for (i = 1; (propname = propertynames[i]) != 0; ++i) {
if (i == 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;
}
any.a_int = i;
add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, propname, FALSE);
}
end_menu(win, "Which intrinsic?");
n = select_menu(win, PICK_ONE, &pick_list);
end_menu(win, "Which intrinsics?");
n = select_menu(win, PICK_ANY, &pick_list);
destroy_nhwindow(win);
if (n >= 1) {
i = pick_list[0].item.a_int-1;
free((genericptr_t) pick_list);
} else {
return 0;
}
amt = 30; /* TODO: prompt for duration */
for (i = 0; i < n; ++i) {
p = pick_list[i].item.a_int;
oldtimeout = u.uprops[p].intrinsic & TIMEOUT;
newtimeout = oldtimeout + (long) amt;
switch (p) {
case SICK:
case SLIMED:
case STONED:
if (oldtimeout > 0L && newtimeout > oldtimeout)
newtimeout = oldtimeout;
break;
}
if (!strcmp(intrinsics[i], "deafness")) {
You("go deaf.");
incr_itimeout(&HDeaf, 30);
context.botl = TRUE;
switch (p) {
case BLINDED:
make_blinded(newtimeout, TRUE);
break;
case CONFUSION:
make_confused(newtimeout, TRUE);
break;
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;
default:
pline("Timeout for %s %s %d.", propertynames[p],
oldtimeout ? "increased by" : "set to", amt);
incr_itimeout(&u.uprops[p].intrinsic, amt);
break;
}
context.botl = 1; /* probably not necessary... */
}
if (n >= 1)
free((genericptr_t) pick_list);
} else
pline("Unavailable command '%s'.",
visctrl((int) cmd_from_func(wiz_intrinsic)));
@@ -2814,7 +2867,7 @@ struct ext_func_tab extcmdlist[] = {
{ '\\', "known", "show what object types have been discovered",
dodiscovered, IFBURIED | GENERALCMD },
{ '`', "knownclass", "show discovered types for one class of objects",
doclassdisco, IFBURIED|GENERALCMD },
doclassdisco, IFBURIED | GENERALCMD },
{ '\0', "levelchange", "change experience level",
wiz_level_change, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
{ '\0', "lightsources", "show mobile light sources",
@@ -2835,11 +2888,11 @@ struct ext_func_tab extcmdlist[] = {
dosacrifice, AUTOCOMPLETE },
{ 'o', "open", "open a door", doopen },
{ 'O', "options", "show option settings, possibly change them",
doset, IFBURIED|GENERALCMD },
doset, IFBURIED | GENERALCMD },
{ C('o'), "overview", "show a summary of the explored dungeon",
dooverview, IFBURIED|AUTOCOMPLETE },
dooverview, IFBURIED | AUTOCOMPLETE },
{ '\0', "panic", "test panic routine (fatal to game)",
wiz_panic, IFBURIED|AUTOCOMPLETE|WIZMODECMD },
wiz_panic, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
{ 'p', "pay", "pay your shopping bill", dopay },
{ ',', "pickup", "pick up things at the current location", dopickup },
{ '\0', "polyself", "polymorph self",
@@ -2851,7 +2904,7 @@ struct ext_func_tab extcmdlist[] = {
{ M('p'), "pray", "pray to the gods for help",
dopray, IFBURIED | AUTOCOMPLETE },
{ C('p'), "prevmsg", "view recent game messages",
doprev_message, IFBURIED|GENERALCMD },
doprev_message, IFBURIED | GENERALCMD },
{ 'P', "puton", "put on an accessory (ring, amulet, etc)", doputon },
{ 'q', "quaff", "quaff (drink) something", dodrink },
{ M('q'), "quit", "exit without saving current game",
@@ -2900,7 +2953,7 @@ struct ext_func_tab extcmdlist[] = {
{ '\0', "terrain", "show map without obstructions",
doterrain, IFBURIED | AUTOCOMPLETE },
{ 't', "throw", "throw something", dothrow },
{ '\0', "timeout", "look at timeout queue",
{ '\0', "timeout", "look at timeout queue and hero's timed intrinsics",
wiz_timeout_queue, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
{ M('T'), "tip", "empty a container", dotip, AUTOCOMPLETE },
{ '_', "travel", "travel to a specific location on the map", dotravel },

View File

@@ -15,6 +15,45 @@ STATIC_DCL void FDECL(see_lamp_flicker, (struct obj *, const char *));
STATIC_DCL void FDECL(lantern_message, (struct obj *));
STATIC_DCL void FDECL(cleanup_burn, (ANY_P *, long));
/* the order of these must match their numerical sequence in prop.h
because the property number is inferred from the array index;
used by wizard mode #timeout and #wizintrinsic */
const char *const propertynames[] = {
"0: not used",
/* Resistances */
/* 1..2 */ "fire resistance", "cold resistance",
/* 3..4 */ "sleep resistance", "disintegration resistance",
/* 5..6 */ "shock resistance", "poison resistance",
/* 7..8 */ "acid resistance", "stoning resistance",
/* 9..10 */ "drain resistance", "sickness resistance",
/* 11..12 */ "invulnerable", "magic resistance",
/* Troubles */
/* 13..16 */ "stunned", "confused", "blinded", "deafness",
/* 17..20 */ "fatally sick", "petrifying", "strangling", "vomiting",
/* 21..22 */ "slippery fingers", "becoming slime",
/* 23..24 */ "hallucinating", "halluicination resistance",
/* 25..28 */ "fumbling", "wounded legs", "sleepy", "voracious hunger",
/* Vision and senses */
/* 29..30 */ "see invisible", "telepathic",
/* 31..33 */ "warning", "warn:monster", "warn:undead",
/* 34..37 */ "searching", "clairvoyant", "infravision", "monster detection",
/* Appearance and behavior */
/* 38..41 */ "adorned (+/-Cha)", "invisible", "displaced", "stealthy",
/* 42..43 */ "monster aggrevation", "conflict",
/* Transportation */
/* 44..46 */ "jumping", "teleporting", "teleport control",
/* 47..49 */ "levitating", "flying", "water walking",
/* 50..52 */ "swimming", "magical breathing", "pass thru walls",
/* Physical attributes */
/* 53..55 */ "slow digestion", "half spell damage", "half physical damage",
/* 56..58 */ "HP regeneration", "energy regeneration", "extra protection",
/* 59 */ "protection from shape changers",
/* 60..62 */ "polymorphing", "polymorph control", "unchanging",
/* 63..66 */ "fast", "reflecting", "free action", "fixed abilites",
/* 67 */ "life will be saved",
0 /* sentinel */
};
/* He is being petrified - dialogue by inmet!tower */
static NEARDATA const char *const stoned_texts[] = {
"You are slowing down.", /* 5 */
@@ -57,6 +96,9 @@ stoned_dialogue()
multi_reason = "getting stoned";
nomovemsg = You_can_move_again; /* not unconscious */
break;
case 2:
if ((HDeaf & TIMEOUT) > 0L && (HDeaf & TIMEOUT) < 5L)
set_itimeout(&HDeaf, 5L); /* avoid Hear_again at tail end */
default:
break;
}
@@ -179,8 +221,9 @@ levitation_dialogue()
if (((HLevitation & TIMEOUT) % 2L) && i > 0L && i <= SIZE(levi_texts)) {
const char *s = levi_texts[SIZE(levi_texts) - i];
if (index(s, '%')) {
boolean danger = is_pool_or_lava(u.ux, u.uy)
&& !Is_waterlevel(&u.uz);
boolean danger = (is_pool_or_lava(u.ux, u.uy)
&& !Is_waterlevel(&u.uz));
pline(s, danger ? "over" : "in",
danger ? surface(u.ux, u.uy) : "air");
} else
@@ -225,6 +268,8 @@ slime_dialogue()
if (multi > 0)
nomul(0);
}
if (i == 2L && (HDeaf & TIMEOUT) > 0L && (HDeaf & TIMEOUT) < 5L)
set_itimeout(&HDeaf, 5L); /* avoid Hear_again at tail end */
exercise(A_DEX, FALSE);
}
@@ -1473,7 +1518,7 @@ timer_element *base;
char buf[BUFSZ];
if (!base) {
putstr(win, 0, "<empty>");
putstr(win, 0, " <empty>");
} else {
putstr(win, 0, "timeout id kind call");
for (curr = base; curr; curr = curr->next) {
@@ -1492,31 +1537,14 @@ timer_element *base;
}
}
static boolean print_prop_header = TRUE;
void
print_prop(win, text, prop)
winid win;
const char *text;
long prop;
{
char buf[BUFSZ];
if (prop & TIMEOUT) {
if (print_prop_header) {
putstr(win, 0, "");
putstr(win, 0, "Properties:");
putstr(win, 0, "");
print_prop_header = FALSE;
}
Sprintf(buf, " %10s: %ld", text, (prop & TIMEOUT));
putstr(win, 0, buf);
}
}
int
wiz_timeout_queue()
{
winid win;
char buf[BUFSZ];
const char *propname;
long intrinsic;
int i, count, longestlen, ln;
win = create_nhwindow(NHW_MENU); /* corner text window */
if (win == WIN_ERR)
@@ -1529,19 +1557,38 @@ wiz_timeout_queue()
putstr(win, 0, "");
print_queue(win, timer_base);
print_prop_header = TRUE;
print_prop(win, "Confused", Confusion);
print_prop(win, "Deaf", HDeaf);
print_prop(win, "Levitation", HLevitation);
print_prop(win, "Monster detection", HDetect_monsters);
print_prop(win, "Slimed", Slimed);
print_prop(win, "Slippery hands", Glib);
print_prop(win, "Stoned", Stoned);
print_prop(win, "Strangled", Strangled);
print_prop(win, "Stunned", Stunned);
print_prop(win, "Vomiting", Vomiting);
print_prop(win, "Wounded legs", HWounded_legs);
/* Timed properies:
* check every one; the majority can't obtain temporary timeouts in
* normal play but those can be forced via the #wizintrinsic command.
*/
count = longestlen = 0;
for (i = 1; (propname = propertynames[i]) != 0; ++i) { /* [0] not used */
intrinsic = u.uprops[i].intrinsic;
if (intrinsic & TIMEOUT) {
++count;
if ((ln = (int) strlen(propname)) > longestlen)
longestlen = ln;
}
}
putstr(win, 0, "");
if (!count) {
putstr(win, 0, "No timed properties.");
} else {
putstr(win, 0, "Timed properties:");
putstr(win, 0, "");
for (i = 1; (propname = propertynames[i]) != 0; ++i) {
intrinsic = u.uprops[i].intrinsic;
if (intrinsic & TIMEOUT) {
/* timeout value can be up to 16777215 (0x00ffffff) but
width of 4 digits should result in values lining up
almost all the time (if/when they don't, it won't
look nice but the information will still be accurate) */
Sprintf(buf, " %*s %4ld", -longestlen, propname,
(intrinsic & TIMEOUT));
putstr(win, 0, buf);
}
}
}
display_nhwindow(win, FALSE);
destroy_nhwindow(win);
@@ -1557,6 +1604,7 @@ timer_sanity_check()
for (curr = timer_base; curr; curr = curr->next)
if (curr->kind == TIMER_OBJECT) {
struct obj *obj = curr->arg.a_obj;
if (obj->timed == 0) {
pline("timer sanity: untimed obj %s, timer %ld",
fmt_ptr((genericptr_t) obj), curr->tid);