Add #wizcast command to cast any spell

Wizard-mode command to cast any spell without checks that would
prevent casting, and with no energy use.

Mainly to allow the fuzzer to exercise the spell code paths.
This commit is contained in:
Pasi Kallinen
2022-09-06 22:49:43 +03:00
parent 4f2996758b
commit e952f67f2c
9 changed files with 95 additions and 33 deletions

View File

@@ -1922,7 +1922,7 @@ jump(int magic) /* 0=Physical, otherwise skill level */
/* attempt "jumping" spell if hero has no innate jumping ability */
if (!magic && !Jumping && known_spell(SPE_JUMPING) >= spe_Fresh)
return spelleffects(SPE_JUMPING, FALSE);
return spelleffects(SPE_JUMPING, FALSE, FALSE);
if (!magic && (nolimbs(g.youmonst.data) || slithy(g.youmonst.data))) {
/* normally (nolimbs || slithy) implies !Jumping,

View File

@@ -2749,6 +2749,8 @@ struct ext_func_tab extcmdlist[] = {
wiz_debug_cmd_bury, IFBURIED | AUTOCOMPLETE | WIZMODECMD,
NULL },
#endif
{ '\0', "wizcast", "cast any spell",
dowizcast, IFBURIED | WIZMODECMD, NULL },
{ C('e'), "wizdetect", "reveal hidden things within a small radius",
wiz_detect, IFBURIED | WIZMODECMD, NULL },
#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG)

View File

@@ -2141,7 +2141,7 @@ doturn(void)
if (!Role_if(PM_CLERIC) && !Role_if(PM_KNIGHT)) {
/* Try to use the "turn undead" spell. */
if (known_spell(SPE_TURN_UNDEAD))
return spelleffects(SPE_TURN_UNDEAD, FALSE);
return spelleffects(SPE_TURN_UNDEAD, FALSE, FALSE);
You("don't know how to turn undead!");
return ECMD_OK;
}

View File

@@ -41,6 +41,7 @@ static char *spellretention(int, char *);
static int throwspell(void);
static void cast_protection(void);
static void spell_backfire(int);
static boolean spelleffects_check(int, int *, int *);
static const char *spelltypemnemonic(int);
static boolean can_center_spell_location(coordxy, coordxy);
static boolean spell_aim_step(genericptr_t, coordxy, coordxy);
@@ -734,6 +735,38 @@ getspell(int* spell_no)
spell_no);
}
/* #wizcast - cast any spell even without knowing it */
int
dowizcast(void)
{
winid win;
menu_item *selected;
anything any;
int i, n;
win = create_nhwindow(NHW_MENU);
start_menu(win, MENU_BEHAVE_STANDARD);
any = cg.zeroany;
for (i = 0; i < MAXSPELL; i++) {
n = (SPE_DIG + i);
if (n >= SPE_BLANK_PAPER)
break;
any.a_int = n;
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, 0, OBJ_NAME(objects[n]), MENU_ITEMFLAGS_NONE);
}
end_menu(win, "Cast which spell?");
n = select_menu(win, PICK_ONE, &selected);
destroy_nhwindow(win);
if (n > 0) {
i = selected[0].item.a_int;
free((genericptr_t) selected);
return spelleffects(i, FALSE, TRUE);
}
return ECMD_OK;
}
/* the #cast command -- cast a spell */
int
docast(void)
@@ -741,7 +774,7 @@ docast(void)
int spell_no;
if (getspell(&spell_no))
return spelleffects(g.spl_book[spell_no].sp_id, FALSE);
return spelleffects(g.spl_book[spell_no].sp_id, FALSE, FALSE);
return ECMD_OK;
}
@@ -891,18 +924,13 @@ spell_backfire(int spell)
return;
}
/* hero casts a spell of type spell_otyp, eg. SPE_SLEEP.
hero must know the spell. */
int
spelleffects(int spell_otyp, boolean atme)
static boolean
spelleffects_check(int spell, int *res, int *energy)
{
int spell = spell_idx(spell_otyp);
int energy, damage, chance, n, intell;
int otyp, skill, role_skill, res = ECMD_OK;
int chance;
boolean confused = (Confusion != 0);
boolean physical_damage = FALSE;
struct obj *pseudo;
coord cc;
*energy = 0;
/*
* Reject attempting to cast while stunned or with no free hands.
@@ -914,14 +942,15 @@ spelleffects(int spell_otyp, boolean atme)
* place in getspell(), we don't get called.)
*/
if ((spell == UNKNOWN_SPELL) || rejectcasting()) {
return ECMD_OK; /* no time elapses */
*res = ECMD_OK; /* no time elapses */
return TRUE;
}
/*
* Note: dotele() also calculates energy use and checks nutrition
* and strength requirements; if any of these change, update it too.
*/
energy = SPELL_LEV_PW(spellev(spell)); /* 5 <= energy <= 35 */
*energy = SPELL_LEV_PW(spellev(spell)); /* 5 <= energy <= 35 */
/*
* Spell casting no longer affects knowledge of the spell. A
@@ -931,11 +960,12 @@ spelleffects(int spell_otyp, boolean atme)
Your("knowledge of this spell is twisted.");
pline("It invokes nightmarish images in your mind...");
spell_backfire(spell);
u.uen -= rnd(energy);
u.uen -= rnd(*energy);
if (u.uen < 0)
u.uen = 0;
g.context.botl = 1;
return ECMD_TIME;
*res = ECMD_TIME;
return TRUE;
} else if (spellknow(spell) <= KEEN / 200) { /* 100 turns left */
You("strain to recall the spell.");
} else if (spellknow(spell) <= KEEN / 40) { /* 500 turns left */
@@ -948,13 +978,16 @@ spelleffects(int spell_otyp, boolean atme)
if (u.uhunger <= 10 && spellid(spell) != SPE_DETECT_FOOD) {
You("are too hungry to cast that spell.");
return ECMD_OK;
*res = ECMD_OK;
return TRUE;
} else if (ACURR(A_STR) < 4 && spellid(spell) != SPE_RESTORE_ABILITY) {
You("lack the strength to cast spells.");
return ECMD_OK;
*res = ECMD_OK;
return TRUE;
} else if (check_capacity(
"Your concentration falters while carrying so much stuff.")) {
return ECMD_TIME;
*res = ECMD_TIME;
return TRUE;
}
/* if the cast attempt is already going to fail due to insufficient
@@ -962,7 +995,7 @@ spelleffects(int spell_otyp, boolean atme)
in and no turn will be consumed; however, when it does kick in,
the attempt may fail due to lack of energy after the draining, in
which case a turn will be used up in addition to the energy loss */
if (u.uhave.amulet && u.uen >= energy) {
if (u.uhave.amulet && u.uen >= *energy) {
You_feel("the amulet draining your energy away.");
/* this used to be 'energy += rnd(2 * energy)' (without 'res'),
so if amulet-induced cost was more than u.uen, nothing
@@ -970,14 +1003,14 @@ spelleffects(int spell_otyp, boolean atme)
and player could just try again (and again and again...);
now we drain some energy immediately, which has a
side-effect of not increasing the hunger aspect of casting */
u.uen -= rnd(2 * energy);
u.uen -= rnd(2 * *energy);
if (u.uen < 0)
u.uen = 0;
g.context.botl = 1;
res = ECMD_TIME; /* time is used even if spell doesn't get cast */
*res = ECMD_TIME; /* time is used even if spell doesn't get cast */
}
if (energy > u.uen) {
if (*energy > u.uen) {
/*
* Hero has insufficient energy/power to cast the spell.
* Augment the message when current energy is at maximum.
@@ -989,12 +1022,12 @@ spelleffects(int spell_otyp, boolean atme)
*/
You("don't have enough energy to cast that spell%s.",
(u.uen < u.uenmax) ? "" /* not at full energy => normal message */
: (energy > u.uenpeak) ? " yet" /* haven't ever had enough */
: (*energy > u.uenpeak) ? " yet" /* haven't ever had enough */
: " anymore"); /* once had enough but have lost some since */
return res;
return TRUE;
} else {
if (spellid(spell) != SPE_DETECT_FOOD) {
int hungr = energy * 2;
int hungr = *energy * 2;
/* If hero is a wizard, their current intelligence
* (bonuses + temporary + current)
@@ -1009,7 +1042,7 @@ spelleffects(int spell_otyp, boolean atme)
* b) Wizards have spent their life at magic and
* understand quite well how to cast spells.
*/
intell = acurr(A_INT);
int intell = acurr(A_INT);
if (!Role_if(PM_WIZARD))
intell = 10;
switch (intell) {
@@ -1046,16 +1079,34 @@ spelleffects(int spell_otyp, boolean atme)
chance = percent_success(spell);
if (confused || (rnd(100) > chance)) {
You("fail to cast the spell correctly.");
u.uen -= energy / 2;
u.uen -= *energy / 2;
g.context.botl = 1;
return ECMD_TIME;
*res = ECMD_TIME;
return TRUE;
}
return FALSE;
}
/* hero casts a spell of type spell_otyp, eg. SPE_SLEEP.
hero must know the spell (unless force is TRUE). */
int
spelleffects(int spell_otyp, boolean atme, boolean force)
{
int spell = force ? spell_otyp : spell_idx(spell_otyp);
int energy = 0, damage, n;
int otyp, skill, role_skill, res = ECMD_OK;
boolean physical_damage = FALSE;
struct obj *pseudo;
coord cc;
if (!force && spelleffects_check(spell, &res, &energy))
return res;
u.uen -= energy;
g.context.botl = 1;
exercise(A_WIS, TRUE);
/* pseudo is a temporary "false" object containing the spell stats */
pseudo = mksobj(spellid(spell), FALSE, FALSE);
pseudo = mksobj(force ? spell : spellid(spell), FALSE, FALSE);
pseudo->blessed = pseudo->cursed = 0;
pseudo->quan = 20L; /* do not let useup get it */
/*

View File

@@ -822,7 +822,7 @@ dotele(
if (castit) {
/* energy cost is deducted in spelleffects() */
exercise(A_WIS, TRUE);
if ((spelleffects(SPE_TELEPORT_AWAY, TRUE) & ECMD_TIME))
if ((spelleffects(SPE_TELEPORT_AWAY, TRUE, FALSE) & ECMD_TIME))
return 1;
else if (!break_the_rules)
return 0;