/* NetHack 3.7 polyself.c $NHDT-Date: 1681429658 2023/04/13 23:47:38 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.197 $ */ /* Copyright (C) 1987, 1988, 1989 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ /* * Polymorph self routine. * * Note: the light source handling code assumes that both gy.youmonst.m_id * and gy.youmonst.mx will always remain 0 when it handles the case of the * player polymorphed into a light-emitting monster. * * Transformation sequences: * /-> polymon poly into monster form * polyself = * \-> newman -> polyman fail to poly, get human form * * rehumanize -> polyman return to original form * * polymon (called directly) usually golem petrification */ /*see_monsters():此函数用于更新玩家视野内的怪物信息,以便玩家可以看到附近的怪物。 encumber_msg():此函数用于检查玩家的负重情况,如果负重过重,会给出相应的提示信息。 retouch_equipment(2):此函数用于重新调整玩家的装备,例如在玩家变形时调整装备以适应新的形态。 selftouch():此函数用于处理玩家自身触摸的情况,例如玩家触摸到具有石化效果的物体,可能导致玩家石化。 hides_under():此函数用于检查玩家是否可以在当前位置下隐藏,例如在地上的物品堆下隐藏。 canletgo():此函数用于检查玩家是否可以放下或释放手中的物品,例如在需要脱下手套时放下武器。*/ #include "hack.h" static void check_strangling(boolean); static void polyman(const char *, const char *); static void dropp(struct obj *); static void break_armor(void); static void drop_weapon(int); static int armor_to_dragon(int); static void newman(void); static void polysense(void); static const char no_longer_petrify_resistant[] = "No longer petrify-resistant, you"; /* update the gy.youmonst.data structure pointer and intrinsics */ void set_uasmon(void) { struct permonst *mdat = &mons[u.umonnum]; boolean was_vampshifter = valid_vampshiftform(gy.youmonst.cham, u.umonnum); set_mon_data(&gy.youmonst, mdat); if (Protection_from_shape_changers) gy.youmonst.cham = NON_PM; else if (is_vampire(gy.youmonst.data)) gy.youmonst.cham = gy.youmonst.mnum; /* assume hero-as-chameleon/doppelganger/sandestin doesn't change shape */ else if (!was_vampshifter) gy.youmonst.cham = NON_PM; u.mcham = gy.youmonst.cham; /* for save/restore since youmonst isn't */ #define PROPSET(PropIndx, ON) \ do { \ if (ON) \ u.uprops[PropIndx].intrinsic |= FROMFORM; \ else \ u.uprops[PropIndx].intrinsic &= ~FROMFORM; \ } while (0) PROPSET(FIRE_RES, resists_fire(&gy.youmonst)); PROPSET(COLD_RES, resists_cold(&gy.youmonst)); PROPSET(SLEEP_RES, resists_sleep(&gy.youmonst)); PROPSET(DISINT_RES, resists_disint(&gy.youmonst)); PROPSET(SHOCK_RES, resists_elec(&gy.youmonst)); PROPSET(POISON_RES, resists_poison(&gy.youmonst)); PROPSET(ACID_RES, resists_acid(&gy.youmonst)); PROPSET(STONE_RES, resists_ston(&gy.youmonst)); { /* resists_drli() takes wielded weapon into account; suppress it */ struct obj *save_uwep = uwep; uwep = 0; PROPSET(DRAIN_RES, resists_drli(&gy.youmonst)); uwep = save_uwep; } /* resists_magm() takes wielded, worn, and carried equipment into into account; cheat and duplicate its monster-specific part */ PROPSET(ANTIMAGIC, (dmgtype(mdat, AD_MAGM) || mdat == &mons[PM_BABY_GRAY_DRAGON] || dmgtype(mdat, AD_RBRE))); PROPSET(SICK_RES, (mdat->mlet == S_FUNGUS || mdat == &mons[PM_GHOUL])); PROPSET(STUNNED, (mdat == &mons[PM_STALKER] || is_bat(mdat))); PROPSET(HALLUC_RES, dmgtype(mdat, AD_HALU)); PROPSET(SEE_INVIS, perceives(mdat)); PROPSET(TELEPAT, telepathic(mdat)); /* note that Infravision uses mons[race] rather than usual mons[role] */ PROPSET(INFRAVISION, infravision(Upolyd ? mdat : &mons[gu.urace.mnum])); PROPSET(INVIS, pm_invisible(mdat)); PROPSET(TELEPORT, can_teleport(mdat)); PROPSET(TELEPORT_CONTROL, control_teleport(mdat)); PROPSET(LEVITATION, is_floater(mdat)); /* floating eye is the only 'floater'; it is also flagged as a 'flyer'; suppress flying for it so that enlightenment doesn't confusingly show latent flight capability always blocked by levitation */ PROPSET(FLYING, (is_flyer(mdat) && !is_floater(mdat))); PROPSET(SWIMMING, is_swimmer(mdat)); /* [don't touch MAGICAL_BREATHING here; both Amphibious and Breathless key off of it but include different monster forms...] */ PROPSET(PASSES_WALLS, passes_walls(mdat)); PROPSET(REGENERATION, regenerates(mdat)); PROPSET(REFLECTING, (mdat == &mons[PM_SILVER_DRAGON])); PROPSET(BLINDED, !haseyes(mdat)); #undef PROPSET float_vs_flight(); /* maybe toggle (BFlying & I_SPECIAL) */ polysense(); #ifdef STATUS_HILITES if (VIA_WINDOWPORT()) status_initialize(REASSESS_ONLY); #endif } /* Levitation overrides Flying; set or clear BFlying|I_SPECIAL */ void float_vs_flight(void) { boolean stuck_in_floor = (u.utrap && u.utraptype != TT_PIT); /* floating overrides flight; so does being trapped in the floor */ if ((HLevitation || ELevitation) || ((HFlying || EFlying) && stuck_in_floor)) BFlying |= I_SPECIAL; else BFlying &= ~I_SPECIAL; /* being trapped on the ground (bear trap, web, molten lava survived with fire resistance, former lava solidified via cold, tethered to a buried iron ball) overrides floating--the floor is reachable */ if ((HLevitation || ELevitation) && stuck_in_floor) BLevitation |= I_SPECIAL; else BLevitation &= ~I_SPECIAL; gc.context.botl = TRUE; } /* for changing into form that's immune to strangulation */ static void check_strangling(boolean on) { /* on -- maybe resume strangling */ if (on) { boolean was_strangled = (Strangled != 0L); /* when Strangled is already set, polymorphing from one vulnerable form into another causes the counter to be reset */ if (uamul && uamul->otyp == AMULET_OF_STRANGULATION && can_be_strangled(&gy.youmonst)) { Strangled = 6L; gc.context.botl = TRUE; Your("%s %s your %s!", simpleonames(uamul), was_strangled ? "still constricts" : "begins constricting", body_part(NECK)); /* "throat" */ makeknown(AMULET_OF_STRANGULATION); } /* off -- maybe block strangling */ } else { if (Strangled && !can_be_strangled(&gy.youmonst)) { Strangled = 0L; gc.context.botl = TRUE; You("are no longer being strangled."); } } } DISABLE_WARNING_FORMAT_NONLITERAL /* make a (new) human out of the player */ static void polyman(const char *fmt, const char *arg) { boolean sticking = (sticks(gy.youmonst.data) && u.ustuck && !u.uswallow), was_mimicking = (U_AP_TYPE != M_AP_NOTHING); boolean was_blind = !!Blind; if (Upolyd) { u.acurr = u.macurr; /* restore old attribs */ u.amax = u.mamax; u.umonnum = u.umonster; flags.female = u.mfemale; } set_uasmon(); u.mh = u.mhmax = 0; u.mtimedone = 0; skinback(FALSE); u.uundetected = 0; if (sticking) uunstick(); find_ac(); if (was_mimicking) { if (gm.multi < 0) unmul(""); gy.youmonst.m_ap_type = M_AP_NOTHING; gy.youmonst.mappearance = 0; } newsym(u.ux, u.uy); urgent_pline(fmt, arg); /* check whether player foolishly genocided self while poly'd */ if (ugenocided()) { /* intervening activity might have clobbered genocide info */ struct kinfo *kptr = find_delayed_killer(POLYMORPH); if (kptr != (struct kinfo *) 0 && kptr->name[0]) { gk.killer.format = kptr->format; Strcpy(gk.killer.name, kptr->name); } else { gk.killer.format = KILLED_BY; Strcpy(gk.killer.name, "self-genocide"); } dealloc_killer(kptr); done(GENOCIDED); } if (u.twoweap && !could_twoweap(gy.youmonst.data)) untwoweapon(); if (u.utrap && u.utraptype == TT_PIT) { set_utrap(rn1(6, 2), TT_PIT); /* time to escape resets */ } if (was_blind && !Blind) { /* reverting from eyeless */ set_itimeout(&HBlinded, 1L); make_blinded(0L, TRUE); /* remove blindness */ } check_strangling(TRUE); if (!Levitation && !u.ustuck && is_pool_or_lava(u.ux, u.uy)) spoteffects(TRUE); see_monsters(); } RESTORE_WARNING_FORMAT_NONLITERAL void change_sex(void) { /* Some monsters are always of one sex and their sex can't be changed; * Succubi/incubi can change, but are handled below. * * !Upolyd check necessary because is_male() and is_female() * may be true for certain roles */ if (!Upolyd || (!is_male(gy.youmonst.data) && !is_female(gy.youmonst.data) && !is_neuter(gy.youmonst.data))) flags.female = !flags.female; if (Upolyd) /* poly'd: also change saved sex */ u.mfemale = !u.mfemale; max_rank_sz(); /* [this appears to be superfluous] */ if ((Upolyd ? u.mfemale : flags.female) && gu.urole.name.f) Strcpy(gp.pl_character, gu.urole.name.f); else Strcpy(gp.pl_character, gu.urole.name.m); if (!Upolyd) { u.umonnum = u.umonster; } else if (u.umonnum == PM_AMOROUS_DEMON) { flags.female = !flags.female; #if 0 /* change monster type to match new sex; disabled with PM_AMOROUS_DEMON */ u.umonnum = (u.umonnum == PM_SUCCUBUS) ? PM_INCUBUS : PM_SUCCUBUS; #endif set_uasmon(); } } /* log a message if non-poly'd hero's gender has changed */ void livelog_newform(boolean viapoly, int oldgend, int newgend) { char buf[BUFSZ]; const char *oldrole, *oldrank, *newrole, *newrank; /* * TODO? * Give other logging feedback here instead of in newman(). */ if (!Upolyd) { if (newgend != oldgend) { oldrole = (oldgend && gu.urole.name.f) ? gu.urole.name.f : gu.urole.name.m; newrole = (newgend && gu.urole.name.f) ? gu.urole.name.f : gu.urole.name.m; oldrank = rank_of(u.ulevel, Role_switch, oldgend); newrank = rank_of(u.ulevel, Role_switch, newgend); Sprintf(buf, "%.10s %.30s", genders[flags.female].adj, newrank); livelog_printf(LL_MINORAC, "%s into %s", viapoly ? "polymorphed" : "transformed", an(strcmp(newrole, oldrole) ? newrole : strcmp(newrank, oldrank) ? newrank : buf)); } } } static void newman(void) { const char *newform; int i, oldlvl, newlvl, oldgend, newgend, hpmax, enmax; oldlvl = u.ulevel; newlvl = oldlvl + rn1(5, -2); /* new = old + {-2,-1,0,+1,+2} */ if (newlvl > 127 || newlvl < 1) { /* level went below 0? */ goto dead; /* old level is still intact (in case of lifesaving) */ } if (newlvl > MAXULEV) newlvl = MAXULEV; /* If your level goes down, your peak level goes down by the same amount so that you can't simply use blessed full healing to undo the decrease. But if your level goes up, your peak level does *not* undergo the same adjustment; you might end up losing out on the chance to regain some levels previously lost to other causes. */ if (newlvl < oldlvl) u.ulevelmax -= (oldlvl - newlvl); if (u.ulevelmax < newlvl) u.ulevelmax = newlvl; u.ulevel = newlvl; oldgend = poly_gender(); if (gs.sex_change_ok && !rn2(10)) change_sex(); adjabil(oldlvl, (int) u.ulevel); /* random experience points for the new experience level */ u.uexp = rndexp(FALSE); /* set up new attribute points (particularly Con) */ redist_attr(); /* * New hit points: * remove "level gain"-based HP from any extra HP accumulated * (the "extra" might actually be negative); * modify the extra, retaining {80%, 90%, 100%, or 110%}; * add in newly generated set of level-gain HP. * * (This used to calculate new HP in direct proportion to old HP, * but that was subject to abuse: accumulate a large amount of * extra HP, drain level down to 1, then polyself to level 2 or 3 * [lifesaving capability needed to handle level 0 and -1 cases] * and the extra got multiplied by 2 or 3. Repeat the level * drain and polyself steps until out of lifesaving capability.) */ hpmax = u.uhpmax; for (i = 0; i < oldlvl; i++) hpmax -= (int) u.uhpinc[i]; /* hpmax * rn1(4,8) / 10; 0.95*hpmax on average */ hpmax = rounddiv((long) hpmax * (long) rn1(4, 8), 10); for (i = 0; (u.ulevel = i) < newlvl; i++) hpmax += newhp(); if (hpmax < u.ulevel) hpmax = u.ulevel; /* min of 1 HP per level */ /* retain same proportion for current HP; u.uhp * hpmax / u.uhpmax */ u.uhp = rounddiv((long) u.uhp * (long) hpmax, u.uhpmax); u.uhpmax = hpmax; /* * Do the same for spell power. */ enmax = u.uenmax; for (i = 0; i < oldlvl; i++) enmax -= (int) u.ueninc[i]; enmax = rounddiv((long) enmax * (long) rn1(4, 8), 10); for (i = 0; (u.ulevel = i) < newlvl; i++) enmax += newpw(); if (enmax < u.ulevel) enmax = u.ulevel; u.uen = rounddiv((long) u.uen * (long) enmax, ((u.uenmax < 1) ? 1 : u.uenmax)); u.uenmax = enmax; /* [should alignment record be tweaked too?] */ u.uhunger = rn1(500, 500); if (Sick) make_sick(0L, (char *) 0, FALSE, SICK_ALL); if (Stoned) make_stoned(0L, (char *) 0, 0, (char *) 0); if (u.uhp <= 0) { if (Polymorph_control) { /* even when Stunned || Unaware */ if (u.uhp <= 0) u.uhp = 1; } else { dead: /* we come directly here if experience level went to 0 or less */ urgent_pline( "Your new form doesn't seem healthy enough to survive."); gk.killer.format = KILLED_BY_AN; Strcpy(gk.killer.name, "unsuccessful polymorph"); done(DIED); /* must have been life-saved to get here */ newuhs(FALSE); (void) encumber_msg(); /* used to be done by redist_attr() */ return; /* lifesaved */ } } newuhs(FALSE); /* use saved gender we're about to revert to, not current */ newform = ((Upolyd ? u.mfemale : flags.female) && gu.urace.individual.f) ? gu.urace.individual.f : (gu.urace.individual.m) ? gu.urace.individual.m : gu.urace.noun; polyman("You feel like a new %s!", newform); newgend = poly_gender(); /* note: newman() bypasses achievements for new ranks attained and doesn't log "new