vampshifting by poly'd hero

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).
This commit is contained in:
PatR
2019-06-06 16:51:43 -07:00
parent eebbac1c65
commit a27ca52b03
12 changed files with 107 additions and 33 deletions
+2
View File
@@ -1439,6 +1439,7 @@ E void FDECL(restore_cham, (struct monst *));
E boolean FDECL(hideunder, (struct monst *)); E boolean FDECL(hideunder, (struct monst *));
E void FDECL(hide_monst, (struct monst *)); E void FDECL(hide_monst, (struct monst *));
E void FDECL(mon_animal_list, (BOOLEAN_P)); E void FDECL(mon_animal_list, (BOOLEAN_P));
E boolean FDECL(valid_vampshiftform, (int, int));
E boolean FDECL(validvamp, (struct monst *, int *, int)); E boolean FDECL(validvamp, (struct monst *, int *, int));
E int FDECL(select_newcham_form, (struct monst *)); E int FDECL(select_newcham_form, (struct monst *));
E void FDECL(mgender_from_permonst, (struct monst *, struct permonst *)); 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, E char *FDECL(short_oname,
(struct obj *, char *(*)(OBJ_P), char *(*)(OBJ_P), unsigned)); (struct obj *, char *(*)(OBJ_P), char *(*)(OBJ_P), unsigned));
E const char *FDECL(singular, (struct obj *, char *(*)(OBJ_P))); 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(An, (const char *)); E char *FDECL(An, (const char *));
E char *FDECL(The, (const char *)); E char *FDECL(The, (const char *));
+1
View File
@@ -184,6 +184,7 @@ struct monst {
#define is_vampshifter(mon) \ #define is_vampshifter(mon) \
((mon)->cham == PM_VAMPIRE || (mon)->cham == PM_VAMPIRE_LORD \ ((mon)->cham == PM_VAMPIRE || (mon)->cham == PM_VAMPIRE_LORD \
|| (mon)->cham == PM_VLAD_THE_IMPALER) || (mon)->cham == PM_VLAD_THE_IMPALER)
#define vampshifted(mon) (is_vampshifter((mon)) && !is_vampire((mon)->data))
/* mimic appearances that block vision/light */ /* mimic appearances that block vision/light */
#define is_lightblocker_mappear(mon) \ #define is_lightblocker_mappear(mon) \
+1
View File
@@ -392,6 +392,7 @@ struct you {
xchar skill_record[P_SKILL_LIMIT]; /* skill advancements */ xchar skill_record[P_SKILL_LIMIT]; /* skill advancements */
struct skills weapon_skills[P_NUM_SKILLS]; struct skills weapon_skills[P_NUM_SKILLS];
boolean twoweap; /* KMH -- Using two-weapon combat */ boolean twoweap; /* KMH -- Using two-weapon combat */
short mcham; /* vampire mndx if shapeshifted to bat/cloud */
}; /* end of `struct you' */ }; /* end of `struct you' */
+16 -9
View File
@@ -821,6 +821,8 @@ struct obj *obj;
if (obj->cursed && !rn2(2)) { if (obj->cursed && !rn2(2)) {
if (!Blind) if (!Blind)
pline_The("%s fogs up and doesn't reflect!", mirror); pline_The("%s fogs up and doesn't reflect!", mirror);
else
pline("Nothing seems to happen.");
return 1; return 1;
} }
if (!u.dx && !u.dy && !u.dz) { if (!u.dx && !u.dy && !u.dz) {
@@ -841,19 +843,23 @@ struct obj *obj;
} }
g.nomovemsg = 0; /* default, "you can move again" */ 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."); 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!"); pline("Huh? That doesn't look like you!");
make_confused(HConfusion + d(3, 4), FALSE); make_confused(HConfusion + d(3, 4), FALSE);
} else if (Hallucination) } else if (Hallucination) {
You(look_str, hcolor((char *) 0)); You(look_str, hcolor((char *) 0));
else if (Sick) } else if (Sick) {
You(look_str, "peaked"); You(look_str, "peaked");
else if (u.uhs >= WEAK) } else if (u.uhs >= WEAK) {
You(look_str, "undernourished"); 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); You("look as %s as ever.", uvisage);
}
} }
return 1; return 1;
} }
@@ -884,8 +890,8 @@ struct obj *obj;
/* couldsee(mtmp->mx, mtmp->my) is implied by the fact that bhit() /* couldsee(mtmp->mx, mtmp->my) is implied by the fact that bhit()
targetted it, so we can ignore possibility of X-ray vision */ targetted it, so we can ignore possibility of X-ray vision */
vis = canseemon(mtmp); vis = canseemon(mtmp);
/* ways to directly see monster (excludes X-ray vision, telepathy, /* ways to directly see monster (excludes X-ray vision, telepathy,
extended detection, type-specific warning) */ extended detection, type-specific warning) */
#define SEENMON (MONSEEN_NORMAL | MONSEEN_SEEINVIS | MONSEEN_INFRAVIS) #define SEENMON (MONSEEN_NORMAL | MONSEEN_SEEINVIS | MONSEEN_INFRAVIS)
how_seen = vis ? howmonseen(mtmp) : 0; how_seen = vis ? howmonseen(mtmp) : 0;
/* whether monster is able to use its vision-based capabilities */ /* whether monster is able to use its vision-based capabilities */
@@ -953,7 +959,8 @@ struct obj *obj;
if (vis) if (vis)
You("discern no obvious reaction from %s.", mon_nam(mtmp)); You("discern no obvious reaction from %s.", mon_nam(mtmp));
else 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; do_react = FALSE;
} }
if (do_react) { if (do_react) {
+16 -6
View File
@@ -707,12 +707,13 @@ domonability(VOID_ARGS)
pline("Unfortunately sound does not carry well through rock."); pline("Unfortunately sound does not carry well through rock.");
else else
aggravate(); aggravate();
} else if (g.youmonst.data->mlet == S_VAMPIRE) } else if (is_vampire(g.youmonst.data) || is_vampshifter(&g.youmonst)) {
return dopoly(); return dopoly();
else if (Upolyd) } else if (Upolyd) {
pline("Any special ability you may have is purely reflexive."); pline("Any special ability you may have is purely reflexive.");
else } else {
You("don't have a special ability in your normal form!"); You("don't have a special ability in your normal form!");
}
return 0; return 0;
} }
@@ -1830,14 +1831,19 @@ int final;
(with countdown timer appended for wizard mode); we really want (with countdown timer appended for wizard mode); we really want
the player to know he's not a samurai at the moment... */ the player to know he's not a samurai at the moment... */
if (Upolyd) { if (Upolyd) {
char anbuf[20]; /* includes trailing space; [4] suffices */
struct permonst *uasmon = g.youmonst.data; struct permonst *uasmon = g.youmonst.data;
boolean altphrasing = vampshifted(&g.youmonst);
tmpbuf[0] = '\0'; tmpbuf[0] = '\0';
/* here we always use current gender, not saved role gender */ /* here we always use current gender, not saved role gender */
if (!is_male(uasmon) && !is_female(uasmon) && !is_neuter(uasmon)) if (!is_male(uasmon) && !is_female(uasmon) && !is_neuter(uasmon))
Sprintf(tmpbuf, "%s ", genders[flags.female ? 1 : 0].adj); Sprintf(tmpbuf, "%s ", genders[flags.female ? 1 : 0].adj);
Sprintf(buf, "%sin %s%s form", !final ? "currently " : "", tmpbuf, if (altphrasing)
uasmon->mname); 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, ""); you_are(buf, "");
} }
@@ -2804,7 +2810,11 @@ int final;
&& !(final == ENL_GAMEOVERDEAD && !(final == ENL_GAMEOVERDEAD
&& u.umonnum == PM_GREEN_SLIME && !Unchanging)) { && u.umonnum == PM_GREEN_SLIME && !Unchanging)) {
/* foreign shape (except were-form which is handled below) */ /* 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) if (wizard)
Sprintf(eos(buf), " (%d)", u.mtimedone); Sprintf(eos(buf), " (%d)", u.mtimedone);
you_are(buf, ""); you_are(buf, "");
+1 -1
View File
@@ -498,7 +498,7 @@ doengrave()
maxelen = BUFSZ - 1; maxelen = BUFSZ - 1;
if (oep) if (oep)
oetype = oep->engr_type; 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; type = ENGR_BLOOD;
/* Can the adventurer engrave at all? */ /* Can the adventurer engrave at all? */
+15 -1
View File
@@ -3414,7 +3414,21 @@ int mndx;
return TRUE; /* potential new form is ok */ 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 boolean
validvamp(mon, mndx_p, monclass) validvamp(mon, mndx_p, monclass)
struct monst *mon; struct monst *mon;
+1 -2
View File
@@ -19,7 +19,6 @@ STATIC_DCL void FDECL(releaseobuf, (char *));
STATIC_DCL char *FDECL(minimal_xname, (struct obj *)); STATIC_DCL char *FDECL(minimal_xname, (struct obj *));
STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *)); STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *));
STATIC_DCL char *FDECL(doname_base, (struct obj *obj, unsigned)); 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, STATIC_DCL boolean FDECL(singplur_lookup, (char *, char *, BOOLEAN_P,
const char *const *)); const char *const *));
STATIC_DCL char *FDECL(singplur_compound, (char *)); 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() */ /* pick "", "a ", or "an " as article for 'str'; used by an() and doname() */
STATIC_OVL char * char *
just_an(outbuf, str) just_an(outbuf, str)
char *outbuf; char *outbuf;
const char *str; const char *str;
+42 -13
View File
@@ -37,9 +37,19 @@ void
set_uasmon() set_uasmon()
{ {
struct permonst *mdat = &mons[u.umonnum]; struct permonst *mdat = &mons[u.umonnum];
boolean was_vampshifter = valid_vampshiftform(g.youmonst.cham, u.umonnum);
set_mon_data(&g.youmonst, mdat); 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) \ #define PROPSET(PropIndx, ON) \
do { \ do { \
if (ON) \ if (ON) \
@@ -348,8 +358,7 @@ newman()
if (u.uhp <= 0) if (u.uhp <= 0)
u.uhp = 1; u.uhp = 1;
} else { } else {
dead: /* we come directly here if their experience level went to 0 or dead: /* we come directly here if experience level went to 0 or less */
less */
Your("new form doesn't seem healthy enough to survive."); Your("new form doesn't seem healthy enough to survive.");
g.killer.format = KILLED_BY_AN; g.killer.format = KILLED_BY_AN;
Strcpy(g.killer.name, "unsuccessful polymorph"); Strcpy(g.killer.name, "unsuccessful polymorph");
@@ -386,9 +395,13 @@ int psflags;
{ {
char buf[BUFSZ] = DUMMY; char buf[BUFSZ] = DUMMY;
int old_light, new_light, mntmp, class, tryct; 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)), 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); controllable_poly = Polymorph_control && !(Stunned || Unaware);
if (Unchanging) { if (Unchanging) {
@@ -408,6 +421,11 @@ int psflags;
old_light = emits_light(g.youmonst.data); old_light = emits_light(g.youmonst.data);
mntmp = NON_PM; mntmp = NON_PM;
if (formrevert){
mntmp = g.youmonst.cham;
monsterpoly = TRUE;
controllable_poly = FALSE;
}
if (monsterpoly && isvamp) if (monsterpoly && isvamp)
goto do_vampyr; goto do_vampyr;
@@ -433,7 +451,7 @@ int psflags;
class = 0; class = 0;
mntmp = name_to_mon(buf); mntmp = name_to_mon(buf);
if (mntmp < LOW_PM) { if (mntmp < LOW_PM) {
by_class: by_class:
class = name_to_monclass(buf, &mntmp); class = name_to_monclass(buf, &mntmp);
if (class && mntmp == NON_PM) if (class && mntmp == NON_PM)
mntmp = mkclass_poly(class); mntmp = mkclass_poly(class);
@@ -486,7 +504,7 @@ int psflags;
} else if (draconian || iswere || isvamp) { } else if (draconian || iswere || isvamp) {
/* special changes that don't require polyok() */ /* special changes that don't require polyok() */
if (draconian) { if (draconian) {
do_merge: do_merge:
mntmp = armor_to_dragon(uarm->otyp); mntmp = armor_to_dragon(uarm->otyp);
if (!(g.mvitals[mntmp].mvflags & G_GENOD)) { if (!(g.mvitals[mntmp].mvflags & G_GENOD)) {
/* allow G_EXTINCT */ /* allow G_EXTINCT */
@@ -521,17 +539,21 @@ int psflags;
update_inventory(); update_inventory();
} }
} else if (iswere) { } else if (iswere) {
do_shift: do_shift:
if (Upolyd && were_beastie(mntmp) != u.ulycn) if (Upolyd && were_beastie(mntmp) != u.ulycn)
mntmp = PM_HUMAN; /* Illegal; force newman() */ mntmp = PM_HUMAN; /* Illegal; force newman() */
else else
mntmp = u.ulycn; mntmp = u.ulycn;
} else if (isvamp) { } else if (isvamp) {
do_vampyr: do_vampyr:
if (mntmp < LOW_PM || (mons[mntmp].geno & G_UNIQ)) if (mntmp < LOW_PM || (mons[mntmp].geno & G_UNIQ)) {
mntmp = (g.youmonst.data != &mons[PM_VAMPIRE] && !rn2(10)) mntmp = (g.youmonst.data == &mons[PM_VAMPIRE_LORD] && !rn2(10))
? PM_WOLF ? PM_WOLF
: !rn2(4) ? PM_FOG_CLOUD : PM_VAMPIRE_BAT; : !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) { if (controllable_poly) {
Sprintf(buf, "Become %s?", an(mons[mntmp].mname)); Sprintf(buf, "Become %s?", an(mons[mntmp].mname));
if (yn(buf) != 'y') if (yn(buf) != 'y')
@@ -570,7 +592,7 @@ int psflags;
} }
g.sex_change_ok--; /* reset */ g.sex_change_ok--; /* reset */
made_change: made_change:
new_light = emits_light(g.youmonst.data); new_light = emits_light(g.youmonst.data);
if (old_light != new_light) { if (old_light != new_light) {
if (old_light) if (old_light)
@@ -780,7 +802,7 @@ int mntmp;
pline(use_thec, monsterc, "emit a mental blast"); pline(use_thec, monsterc, "emit a mental blast");
if (g.youmonst.data->msound == MS_SHRIEK) /* worthless, actually */ if (g.youmonst.data->msound == MS_SHRIEK) /* worthless, actually */
pline(use_thec, monsterc, "shriek"); 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"); pline(use_thec, monsterc, "change shape");
if (lays_eggs(g.youmonst.data) && flags.female && 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 void
rehumanize() 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)) if (emits_light(g.youmonst.data))
del_light_source(LS_MONSTER, monst_to_any(&g.youmonst)); del_light_source(LS_MONSTER, monst_to_any(&g.youmonst));
polyman("return to %s form!", g.urace.adj); polyman("return to %s form!", g.urace.adj);
@@ -1490,7 +1519,7 @@ dopoly()
{ {
struct permonst *savedat = g.youmonst.data; struct permonst *savedat = g.youmonst.data;
if (is_vampire(g.youmonst.data)) { if (is_vampire(g.youmonst.data) || is_vampshifter(&g.youmonst)) {
polyself(2); polyself(2);
if (savedat != g.youmonst.data) { if (savedat != g.youmonst.data) {
You("transform into %s.", an(g.youmonst.data->mname)); You("transform into %s.", an(g.youmonst.data->mname));
+2 -1
View File
@@ -1962,7 +1962,8 @@ doturn()
return (u.uconduct.gnostic == 1); return (u.uconduct.gnostic == 1);
} }
if ((u.ualign.type != A_CHAOTIC 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!" */ || u.ugangr > 6) { /* "Die, mortal!" */
pline("For some reason, %s seems to ignore you.", Gname); pline("For some reason, %s seems to ignore you.", Gname);
aggravate(); aggravate();
+8
View File
@@ -2108,6 +2108,10 @@ do_class_genocide()
kill_genocided_monsters(); kill_genocided_monsters();
update_inventory(); /* eggs & tins */ update_inventory(); /* eggs & tins */
pline("Wiped out all %s.", nam); 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) { if (Upolyd && i == u.umonnum) {
u.mh = -1; u.mh = -1;
if (Unchanging) { if (Unchanging) {
@@ -2226,6 +2230,10 @@ int how;
continue; continue;
} }
ptr = &mons[mndx]; 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 /* Although "genus" is Latin for race, the hero benefits
* from both race and role; thus genocide affects either. * from both race and role; thus genocide affects either.
*/ */
+2
View File
@@ -593,6 +593,7 @@ unsigned int *stuckid, *steedid;
amii_setpens(amii_numcolors); /* use colors from save file */ amii_setpens(amii_numcolors); /* use colors from save file */
#endif #endif
mread(fd, (genericptr_t) &u, sizeof(struct you)); mread(fd, (genericptr_t) &u, sizeof(struct you));
g.youmonst.cham = u.mcham;
#define ReadTimebuf(foo) \ #define ReadTimebuf(foo) \
mread(fd, (genericptr_t) timebuf, 14); \ mread(fd, (genericptr_t) timebuf, 14); \
@@ -626,6 +627,7 @@ unsigned int *stuckid, *steedid;
sysflags = newgamesysflags; sysflags = newgamesysflags;
#endif #endif
g.context = newgamecontext; g.context = newgamecontext;
g.youmonst = cg.zeromonst;
return FALSE; return FALSE;
} }
/* in case hangup save occurred in midst of level change */ /* in case hangup save occurred in midst of level change */