]> granicus.if.org Git - nethack/commitdiff
vampshifting by poly'd hero
authorPatR <rankin@nethack.org>
Thu, 6 Jun 2019 23:51:43 +0000 (16:51 -0700)
committerPatR <rankin@nethack.org>
Thu, 6 Jun 2019 23:51:43 +0000 (16:51 -0700)
Hero polymorphed into a vampire or v.lord can use #monster to switch
to vampire bat or fog cloud [or wolf for lord] but it was a one shot
polymorph.  Remember when current form is a shape-shifted vampire and
allow #monster in shifted form to pick another shifted form or the
vampire form.

Genocide of the alternate shape forces back to base vampire.  Genocide
of base vampire does too, then reverts to human (or dwarf, &c) as
vampires go away.  Being killed while shafe-shifted reverts all the
way to human rather than to vampire.  [Just realized:  interaction
with Unchanging wasn't taken into consideration so hasn't been tested.]

Since 'youmonst' isn't saved and restored, I had to add a field to 'u'
to hold youmonst.cham during save/restore.

Tested with 3.6.2+ and seemed to be working (except saving while
shape-shifted restored as ordinary bat/cloud/wolf because new u.mcham
wasn't there to hold youmonst.cham yet).  Builds with 3.7.0- but not
execution tested yet (I didn't want to clobber my current playground).

12 files changed:
include/extern.h
include/monst.h
include/you.h
src/apply.c
src/cmd.c
src/engrave.c
src/mon.c
src/objnam.c
src/polyself.c
src/pray.c
src/read.c
src/restore.c

index 508627de98369f997f1b51ac74eab432073ce244..08a2618614c8e04a53ca3ca22a96422d8798fa1f 100644 (file)
@@ -1439,6 +1439,7 @@ E void FDECL(restore_cham, (struct monst *));
 E boolean FDECL(hideunder, (struct monst *));
 E void FDECL(hide_monst, (struct monst *));
 E void FDECL(mon_animal_list, (BOOLEAN_P));
+E boolean FDECL(valid_vampshiftform, (int, int));
 E boolean FDECL(validvamp, (struct monst *, int *, int));
 E int FDECL(select_newcham_form, (struct monst *));
 E void FDECL(mgender_from_permonst, (struct monst *, struct permonst *));
@@ -1708,6 +1709,7 @@ E char *FDECL(killer_xname, (struct obj *));
 E char *FDECL(short_oname,
               (struct obj *, char *(*)(OBJ_P), char *(*)(OBJ_P), unsigned));
 E const char *FDECL(singular, (struct obj *, char *(*)(OBJ_P)));
+E char *FDECL(just_an, (char *, const char *));
 E char *FDECL(an, (const char *));
 E char *FDECL(An, (const char *));
 E char *FDECL(The, (const char *));
index 81e7d0b4581574a6f41d384d19b77579be4a979d..b15f89bd7b13b7f6debf88a73aa4c00ab6784e48 100644 (file)
@@ -184,6 +184,7 @@ struct monst {
 #define is_vampshifter(mon)                                      \
     ((mon)->cham == PM_VAMPIRE || (mon)->cham == PM_VAMPIRE_LORD \
      || (mon)->cham == PM_VLAD_THE_IMPALER)
+#define vampshifted(mon) (is_vampshifter((mon)) && !is_vampire((mon)->data))
 
 /* mimic appearances that block vision/light */
 #define is_lightblocker_mappear(mon)                       \
index 1e0a320e25b3305ef52674522e4fb3bb6ed4a8f2..25b60f73b46b60d7f323a2f43e17e0c48320e17c 100644 (file)
@@ -392,6 +392,7 @@ struct you {
     xchar skill_record[P_SKILL_LIMIT]; /* skill advancements */
     struct skills weapon_skills[P_NUM_SKILLS];
     boolean twoweap;         /* KMH -- Using two-weapon combat */
+    short mcham;             /* vampire mndx if shapeshifted to bat/cloud */
 
 }; /* end of `struct you' */
 
index f7eae32e6e80b858b07e1aebe204a946564243f8..1205a74998e80377013fddb1ff398023223eb04d 100644 (file)
@@ -821,6 +821,8 @@ struct obj *obj;
     if (obj->cursed && !rn2(2)) {
         if (!Blind)
             pline_The("%s fogs up and doesn't reflect!", mirror);
+        else
+            pline("Nothing seems to happen.");
         return 1;
     }
     if (!u.dx && !u.dy && !u.dz) {
@@ -841,19 +843,23 @@ struct obj *obj;
                     }
                     g.nomovemsg = 0; /* default, "you can move again" */
                 }
-            } else if (g.youmonst.data->mlet == S_VAMPIRE)
+            } else if (is_vampire(g.youmonst.data)
+                       || is_vampshifter(&g.youmonst)) {
                 You("don't have a reflection.");
-            else if (u.umonnum == PM_UMBER_HULK) {
+            else if (u.umonnum == PM_UMBER_HULK) {
                 pline("Huh?  That doesn't look like you!");
                 make_confused(HConfusion + d(3, 4), FALSE);
-            } else if (Hallucination)
+            } else if (Hallucination) {
                 You(look_str, hcolor((char *) 0));
-            else if (Sick)
+            } else if (Sick) {
                 You(look_str, "peaked");
-            else if (u.uhs >= WEAK)
+            } else if (u.uhs >= WEAK) {
                 You(look_str, "undernourished");
-            else
+            } else if (Upolyd) {
+                You("look like %s.", an(mons[u.umonnum].mname));
+            } else {
                 You("look as %s as ever.", uvisage);
+            }
         }
         return 1;
     }
@@ -884,8 +890,8 @@ struct obj *obj;
     /* couldsee(mtmp->mx, mtmp->my) is implied by the fact that bhit()
        targetted it, so we can ignore possibility of X-ray vision */
     vis = canseemon(mtmp);
-/* ways to directly see monster (excludes X-ray vision, telepathy,
-   extended detection, type-specific warning) */
+    /* ways to directly see monster (excludes X-ray vision, telepathy,
+       extended detection, type-specific warning) */
 #define SEENMON (MONSEEN_NORMAL | MONSEEN_SEEINVIS | MONSEEN_INFRAVIS)
     how_seen = vis ? howmonseen(mtmp) : 0;
     /* whether monster is able to use its vision-based capabilities */
@@ -953,7 +959,8 @@ struct obj *obj;
             if (vis)
                 You("discern no obvious reaction from %s.", mon_nam(mtmp));
             else
-                You_feel("a bit silly gesturing the mirror in that direction.");
+                You_feel(
+                       "a bit silly gesturing the mirror in that direction.");
             do_react = FALSE;
         }
         if (do_react) {
index 839284407522a9556a517b779a6137ac22cdcd05..5620064a16989d4a63128cb409c49e91afd28176 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -707,12 +707,13 @@ domonability(VOID_ARGS)
             pline("Unfortunately sound does not carry well through rock.");
         else
             aggravate();
-    } else if (g.youmonst.data->mlet == S_VAMPIRE)
+    } else if (is_vampire(g.youmonst.data) || is_vampshifter(&g.youmonst)) {
         return dopoly();
-    else if (Upolyd)
+    } else if (Upolyd) {
         pline("Any special ability you may have is purely reflexive.");
-    else
+    } else {
         You("don't have a special ability in your normal form!");
+    }
     return 0;
 }
 
@@ -1830,14 +1831,19 @@ int final;
        (with countdown timer appended for wizard mode); we really want
        the player to know he's not a samurai at the moment... */
     if (Upolyd) {
+        char anbuf[20]; /* includes trailing space; [4] suffices */
         struct permonst *uasmon = g.youmonst.data;
+        boolean altphrasing = vampshifted(&g.youmonst);
 
         tmpbuf[0] = '\0';
         /* here we always use current gender, not saved role gender */
         if (!is_male(uasmon) && !is_female(uasmon) && !is_neuter(uasmon))
             Sprintf(tmpbuf, "%s ", genders[flags.female ? 1 : 0].adj);
-        Sprintf(buf, "%sin %s%s form", !final ? "currently " : "", tmpbuf,
-                uasmon->mname);
+        if (altphrasing)
+            Sprintf(eos(tmpbuf), "%s in ", mons[g.youmonst.cham].mname);
+        Sprintf(buf, "%s%s%s%s form", !final ? "currently " : "",
+                altphrasing ? just_an(anbuf, tmpbuf) : "in ",
+                tmpbuf, uasmon->mname);
         you_are(buf, "");
     }
 
@@ -2804,7 +2810,11 @@ int final;
         && !(final == ENL_GAMEOVERDEAD
              && u.umonnum == PM_GREEN_SLIME && !Unchanging)) {
         /* foreign shape (except were-form which is handled below) */
-        Sprintf(buf, "polymorphed into %s", an(g.youmonst.data->mname));
+        if (!vampshifted(&g.youmonst))
+            Sprintf(buf, "polymorphed into %s", an(g.youmonst.data->mname));
+        else
+            Sprintf(buf, "polymorphed into %s in %s form",
+                    an(mons[g.youmonst.cham].mname), g.youmonst.data->mname);
         if (wizard)
             Sprintf(eos(buf), " (%d)", u.mtimedone);
         you_are(buf, "");
index 808501bd3ba5c9286a0531e70904ba8e1f37ce78..460414a7d8076cbc5f1c89409785759b8167b668 100644 (file)
@@ -498,7 +498,7 @@ doengrave()
     maxelen = BUFSZ - 1;
     if (oep)
         oetype = oep->engr_type;
-    if (is_demon(g.youmonst.data) || g.youmonst.data->mlet == S_VAMPIRE)
+    if (is_demon(g.youmonst.data) || is_vampire(g.youmonst.data))
         type = ENGR_BLOOD;
 
     /* Can the adventurer engrave at all? */
index 670a8945ab13b5cdcad3566991c39550aa8408c8..508546e4e4f7600d071b522ee2a9e8f692dddc87 100644 (file)
--- a/src/mon.c
+++ b/src/mon.c
@@ -3414,7 +3414,21 @@ int mndx;
     return TRUE; /* potential new form is ok */
 }
 
-/* prevent wizard mode user from specifying invalid vampshifter shape */
+/* used for hero polyself handling */
+boolean
+valid_vampshiftform(base, form)
+int base, form;
+{
+    if (base >= LOW_PM && is_vampire(&mons[base])) {
+        if (form == PM_VAMPIRE_BAT || form == PM_FOG_CLOUD
+            || (form == PM_WOLF && base != PM_VAMPIRE))
+            return TRUE;
+    }
+    return FALSE;
+}
+
+/* prevent wizard mode user from specifying invalid vampshifter shape
+   when using monpolycontrol to assign a new form to a vampshifter */
 boolean
 validvamp(mon, mndx_p, monclass)
 struct monst *mon;
index 73f1dc7d4f8dba06b574307c97e08032798872f8..853b70a0743df59a68b99e271b12368325b295e5 100644 (file)
@@ -19,7 +19,6 @@ STATIC_DCL void FDECL(releaseobuf, (char *));
 STATIC_DCL char *FDECL(minimal_xname, (struct obj *));
 STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *));
 STATIC_DCL char *FDECL(doname_base, (struct obj *obj, unsigned));
-STATIC_DCL char *FDECL(just_an, (char *str, const char *));
 STATIC_DCL boolean FDECL(singplur_lookup, (char *, char *, BOOLEAN_P,
                                            const char *const *));
 STATIC_DCL char *FDECL(singplur_compound, (char *));
@@ -1628,7 +1627,7 @@ char *FDECL((*func), (OBJ_P));
 }
 
 /* pick "", "a ", or "an " as article for 'str'; used by an() and doname() */
-STATIC_OVL char *
+char *
 just_an(outbuf, str)
 char *outbuf;
 const char *str;
index 87143cb6122664129a0a17f5004ecebf08568f0e..9753e77aeb60eb77827a430fd3af1767bb6eb98c 100644 (file)
@@ -37,9 +37,19 @@ void
 set_uasmon()
 {
     struct permonst *mdat = &mons[u.umonnum];
+    boolean was_vampshifter = valid_vampshiftform(g.youmonst.cham, u.umonnum);
 
     set_mon_data(&g.youmonst, mdat);
 
+    if (Protection_from_shape_changers)
+        g.youmonst.cham = NON_PM;
+    else if (is_vampire(g.youmonst.data))
+        g.youmonst.cham = g.youmonst.mnum;
+    /* assume hero-as-chameleon/doppelganger/sandestin doesn't change shape */
+    else if (!was_vampshifter)
+        g.youmonst.cham = NON_PM;
+    u.mcham = g.youmonst.cham; /* for save/restore since youmonst isn't */
+
 #define PROPSET(PropIndx, ON)                          \
     do {                                               \
         if (ON)                                        \
@@ -348,8 +358,7 @@ newman()
             if (u.uhp <= 0)
                 u.uhp = 1;
         } else {
-        dead: /* we come directly here if their experience level went to 0 or
-                 less */
+ dead:      /* we come directly here if experience level went to 0 or less */
             Your("new form doesn't seem healthy enough to survive.");
             g.killer.format = KILLED_BY_AN;
             Strcpy(g.killer.name, "unsuccessful polymorph");
@@ -386,9 +395,13 @@ int psflags;
 {
     char buf[BUFSZ] = DUMMY;
     int old_light, new_light, mntmp, class, tryct;
-    boolean forcecontrol = (psflags == 1), monsterpoly = (psflags == 2),
+    boolean forcecontrol = (psflags == 1),
+            monsterpoly = (psflags == 2),
+            formrevert = (psflags == 3),
             draconian = (uarm && Is_dragon_armor(uarm)),
-            iswere = (u.ulycn >= LOW_PM), isvamp = is_vampire(g.youmonst.data),
+            iswere = (u.ulycn >= LOW_PM),
+            isvamp = (is_vampire(g.youmonst.data)
+                      || is_vampshifter(&g.youmonst)),
             controllable_poly = Polymorph_control && !(Stunned || Unaware);
 
     if (Unchanging) {
@@ -408,6 +421,11 @@ int psflags;
     old_light = emits_light(g.youmonst.data);
     mntmp = NON_PM;
 
+    if (formrevert){
+        mntmp = g.youmonst.cham;
+        monsterpoly = TRUE;
+        controllable_poly = FALSE;
+    }
     if (monsterpoly && isvamp)
         goto do_vampyr;
 
@@ -433,7 +451,7 @@ int psflags;
             class = 0;
             mntmp = name_to_mon(buf);
             if (mntmp < LOW_PM) {
           by_class:
+ by_class:
                 class = name_to_monclass(buf, &mntmp);
                 if (class && mntmp == NON_PM)
                     mntmp = mkclass_poly(class);
@@ -486,7 +504,7 @@ int psflags;
     } else if (draconian || iswere || isvamp) {
         /* special changes that don't require polyok() */
         if (draconian) {
       do_merge:
+ do_merge:
             mntmp = armor_to_dragon(uarm->otyp);
             if (!(g.mvitals[mntmp].mvflags & G_GENOD)) {
                 /* allow G_EXTINCT */
@@ -521,17 +539,21 @@ int psflags;
                 update_inventory();
             }
         } else if (iswere) {
       do_shift:
+ do_shift:
             if (Upolyd && were_beastie(mntmp) != u.ulycn)
                 mntmp = PM_HUMAN; /* Illegal; force newman() */
             else
                 mntmp = u.ulycn;
         } else if (isvamp) {
       do_vampyr:
-            if (mntmp < LOW_PM || (mons[mntmp].geno & G_UNIQ))
-                mntmp = (g.youmonst.data != &mons[PM_VAMPIRE] && !rn2(10))
+ do_vampyr:
+            if (mntmp < LOW_PM || (mons[mntmp].geno & G_UNIQ)) {
+                mntmp = (g.youmonst.data == &mons[PM_VAMPIRE_LORD] && !rn2(10))
                             ? PM_WOLF
                             : !rn2(4) ? PM_FOG_CLOUD : PM_VAMPIRE_BAT;
+                if (g.youmonst.cham >= LOW_PM
+                    && !is_vampire(g.youmonst.data) && !rn2(2))
+                    mntmp = g.youmonst.cham;
+            }
             if (controllable_poly) {
                 Sprintf(buf, "Become %s?", an(mons[mntmp].mname));
                 if (yn(buf) != 'y')
@@ -570,7 +592,7 @@ int psflags;
     }
     g.sex_change_ok--; /* reset */
 
-made_change:
+ made_change:
     new_light = emits_light(g.youmonst.data);
     if (old_light != new_light) {
         if (old_light)
@@ -780,7 +802,7 @@ int mntmp;
             pline(use_thec, monsterc, "emit a mental blast");
         if (g.youmonst.data->msound == MS_SHRIEK) /* worthless, actually */
             pline(use_thec, monsterc, "shriek");
-        if (is_vampire(g.youmonst.data))
+        if (is_vampire(g.youmonst.data) || is_vampshifter(&g.youmonst))
             pline(use_thec, monsterc, "change shape");
 
         if (lays_eggs(g.youmonst.data) && flags.female &&
@@ -1024,6 +1046,8 @@ int alone;
     }
 }
 
+/* return to original form, usually either due to polymorph timing out
+   or dying from loss of hit points while being polymorphed */
 void
 rehumanize()
 {
@@ -1042,6 +1066,11 @@ rehumanize()
         }
     }
 
+    /*
+     * Right now, dying while being a shifted vampire (bat, cloud, wolf)
+     * reverts to human rather than to vampire.
+     */
+
     if (emits_light(g.youmonst.data))
         del_light_source(LS_MONSTER, monst_to_any(&g.youmonst));
     polyman("return to %s form!", g.urace.adj);
@@ -1490,7 +1519,7 @@ dopoly()
 {
     struct permonst *savedat = g.youmonst.data;
 
-    if (is_vampire(g.youmonst.data)) {
+    if (is_vampire(g.youmonst.data) || is_vampshifter(&g.youmonst)) {
         polyself(2);
         if (savedat != g.youmonst.data) {
             You("transform into %s.", an(g.youmonst.data->mname));
index a405cdc5e69890d3f77f03bdb3f9a3deeb744167..3fba7f573b8df5cb66aa9a2e18ab8f5b5734446f 100644 (file)
@@ -1962,7 +1962,8 @@ doturn()
         return (u.uconduct.gnostic == 1);
     }
     if ((u.ualign.type != A_CHAOTIC
-         && (is_demon(g.youmonst.data) || is_undead(g.youmonst.data)))
+         && (is_demon(g.youmonst.data)
+             || is_undead(g.youmonst.data) || is_vampshifter(&g.youmonst)))
         || u.ugangr > 6) { /* "Die, mortal!" */
         pline("For some reason, %s seems to ignore you.", Gname);
         aggravate();
index 70452abcc5827f898f218d3dabcc5e356849dbbb..a6aca2706e233b60b4e60c2fb440106c9809ec27 100644 (file)
@@ -2108,6 +2108,10 @@ do_class_genocide()
                     kill_genocided_monsters();
                     update_inventory(); /* eggs & tins */
                     pline("Wiped out all %s.", nam);
+                    if (Upolyd && vampshifted(&g.youmonst)
+                        /* current shifted form or base vampire form */
+                        && (i == u.umonnum || i == g.youmonst.cham))
+                        polyself(3); /* vampshifter back to vampire */
                     if (Upolyd && i == u.umonnum) {
                         u.mh = -1;
                         if (Unchanging) {
@@ -2226,6 +2230,10 @@ int how;
                 continue;
             }
             ptr = &mons[mndx];
+            /* first revert if current shifted form or base vampire form */
+            if (Upolyd && vampshifted(&g.youmonst)
+                && (mndx == u.umonnum || mndx == g.youmonst.cham))
+                polyself(3); /* vampshifter (bat, &c) back to vampire */
             /* Although "genus" is Latin for race, the hero benefits
              * from both race and role; thus genocide affects either.
              */
index 7f0e739e1dd8adbc9ff4c4b536e5f1011295089d..948e720bd5f4f52dd7f395abb1f4e098b3b18dbb 100644 (file)
@@ -593,6 +593,7 @@ unsigned int *stuckid, *steedid;
     amii_setpens(amii_numcolors); /* use colors from save file */
 #endif
     mread(fd, (genericptr_t) &u, sizeof(struct you));
+    g.youmonst.cham = u.mcham;
 
 #define ReadTimebuf(foo)                   \
     mread(fd, (genericptr_t) timebuf, 14); \
@@ -626,6 +627,7 @@ unsigned int *stuckid, *steedid;
         sysflags = newgamesysflags;
 #endif
         g.context = newgamecontext;
+        g.youmonst = cg.zeromonst;
         return FALSE;
     }
     /* in case hangup save occurred in midst of level change */