diff --git a/include/extern.h b/include/extern.h index 508627de9..08a261861 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 *)); diff --git a/include/monst.h b/include/monst.h index 81e7d0b45..b15f89bd7 100644 --- a/include/monst.h +++ b/include/monst.h @@ -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) \ diff --git a/include/you.h b/include/you.h index 1e0a320e2..25b60f73b 100644 --- a/include/you.h +++ b/include/you.h @@ -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' */ diff --git a/src/apply.c b/src/apply.c index f7eae32e6..1205a7499 100644 --- a/src/apply.c +++ b/src/apply.c @@ -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) { diff --git a/src/cmd.c b/src/cmd.c index 839284407..5620064a1 100644 --- 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, ""); diff --git a/src/engrave.c b/src/engrave.c index 808501bd3..460414a7d 100644 --- a/src/engrave.c +++ b/src/engrave.c @@ -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? */ diff --git a/src/mon.c b/src/mon.c index 670a8945a..508546e4e 100644 --- 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; diff --git a/src/objnam.c b/src/objnam.c index 73f1dc7d4..853b70a07 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -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; diff --git a/src/polyself.c b/src/polyself.c index 87143cb61..9753e77ae 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -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)); diff --git a/src/pray.c b/src/pray.c index a405cdc5e..3fba7f573 100644 --- a/src/pray.c +++ b/src/pray.c @@ -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(); diff --git a/src/read.c b/src/read.c index 70452abcc..a6aca2706 100644 --- a/src/read.c +++ b/src/read.c @@ -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. */ diff --git a/src/restore.c b/src/restore.c index 7f0e739e1..948e720bd 100644 --- a/src/restore.c +++ b/src/restore.c @@ -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 */