]> granicus.if.org Git - nethack/commitdiff
Add #wizcast command to cast any spell
authorPasi Kallinen <paxed@alt.org>
Tue, 6 Sep 2022 19:49:43 +0000 (22:49 +0300)
committerPasi Kallinen <paxed@alt.org>
Tue, 6 Sep 2022 19:58:28 +0000 (22:58 +0300)
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.

dat/wizhelp
doc/Guidebook.mn
doc/Guidebook.tex
include/extern.h
src/apply.c
src/cmd.c
src/pray.c
src/spell.c
src/teleport.c

index f55cee0d113b8c6e399829735518aa7ea5a9f44c..c3638a366915a928ff38ddb4a9ad9ee7bc4c0c74 100644 (file)
@@ -22,6 +22,7 @@ Debug-Mode Quick Reference:
 #vanquished     == disclose counts of dead monsters sorted in various ways
 #vision         == show vision array
 #wizborn        == show monster birth/death/geno/extinct stats
+#wizcast        == cast any spell
 #wizfliplevel   == transpose the current dungeon level
 #wizintrinsic   == set selected intrinsic timeouts
 #wizkill        == remove monster(s) from play
index c26796aad52567f5378960efcf5de0f2443dcb68..6104c81c5fdebf2d6009e4c637a7e2de9b33eb7f 100644 (file)
@@ -1720,6 +1720,9 @@ Debug mode only.
 Bury objects under and around you.
 Autocompletes.
 Debug mode only.
+.lp #wizcast
+Cast any spell.
+Debug mode only.
 .lp #wizdetect
 Reveal hidden things (secret doors or traps or unseen monsters)
 within a modest radius.
index 086b247126b770b8686b80de092f3229e0fa4f60..75eebf7ff5547e7ab6d7c1d115540f76cbcc8de4 100644 (file)
@@ -1849,6 +1849,10 @@ Bury objects under and around you.
 Autocompletes.
 Debug mode only.
 %.lp
+\item[\tb{\#wizcast}]
+Cast any spell.
+Debug mode only.
+%.lp
 \item[\tb{\#wizdetect}]
 Reveal hidden things (secret doors or traps or unseen monsters)
 within a modest radius.
index 467c561312539ca8272571385e2594f61a5769c7..457d8ac5a0fad87979fba85200dec944a6a43e2d 100644 (file)
@@ -2624,9 +2624,10 @@ extern int study_book(struct obj *);
 extern void book_disappears(struct obj *);
 extern void book_substitution(struct obj *, struct obj *);
 extern void age_spells(void);
+extern int dowizcast(void);
 extern int docast(void);
 extern int spell_skilltype(int);
-extern int spelleffects(int, boolean);
+extern int spelleffects(int, boolean, boolean);
 extern int tport_spell(int);
 extern void losespells(void);
 extern int dovspell(void);
index 10dd26ac6e4d5fdd054f3a35193ebf9e82bafd42..de6562755980efd70e79e7d54fea9b3bade79898 100644 (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,
index e2d9d1687685abb29e2848b312948e2c5d8b9918..cb36eadb9c44ebf095ca366dff4bcfdce955a363 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -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)
index 3f5847f94471ad1ab319c3b7c4a23ec724714cce..3b18dc5abe2c6d32c35b95a5bddc6e472bd81469 100644 (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;
     }
index 8e2bc0e485d4d3fa1ea1706416c4d5f6aeddab22..f10c86a4c10f8d6b5edd7da4d1c7729e99363f22 100644 (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 */
     /*
index 49794e37dd2fb989b02a5b44dd166c04b12cdfc7..af1499f42641b69187404c08adcc4db8126b5b92 100644 (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;