380 lines
12 KiB
C
380 lines
12 KiB
C
/* NetHack 3.7 mplayer.c $NHDT-Date: 1596498188 2020/08/03 23:43:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.30 $ */
|
|
/* Copyright (c) Izchak Miller, 1992. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
staticfn const char *dev_name(void);
|
|
staticfn void get_mplname(struct monst *, char *);
|
|
staticfn void mk_mplayer_armor(struct monst *, short);
|
|
|
|
/* These are the names of those who
|
|
* contributed to the development of NetHack 3.2/3.3/3.4/3.6.
|
|
*
|
|
* Keep in alphabetical order within teams.
|
|
* Same first name is entered once within each team.
|
|
*/
|
|
static const char *const developers[] = {
|
|
/* devteam */
|
|
"Alex", "Dave", "Dean", "Derek", "Eric", "Izchak",
|
|
"Janet", "Jessie", "Ken", "Kevin", "Michael", "Mike",
|
|
"Pasi", "Pat", "Patric", "Paul", "Sean", "Steve",
|
|
"Timo", "Warwick",
|
|
/* PC team */
|
|
"Bill", "Eric", "Keizo", "Ken", "Kevin", "Michael",
|
|
"Mike", "Paul", "Stephen", "Steve", "Timo", "Yitzhak",
|
|
/* Amiga team */
|
|
"Andy", "Gregg", "Janne", "Keni", "Mike", "Olaf",
|
|
"Richard",
|
|
/* Mac team */
|
|
"Andy", "Chris", "Dean", "Jon", "Jonathan", "Kevin",
|
|
"Wang",
|
|
/* Atari team */
|
|
"Eric", "Marvin", "Warwick",
|
|
/* NT team */
|
|
"Alex", "Dion", "Michael",
|
|
/* OS/2 team */
|
|
"Helge", "Ron", "Timo",
|
|
/* VMS team */
|
|
"Joshua", "Pat", ""
|
|
};
|
|
|
|
/* return a randomly chosen developer name */
|
|
staticfn const char *
|
|
dev_name(void)
|
|
{
|
|
int i, m = 0, n = SIZE(developers);
|
|
struct monst *mtmp;
|
|
boolean match;
|
|
|
|
do {
|
|
match = FALSE;
|
|
i = rn2(n);
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (!is_mplayer(mtmp->data))
|
|
continue;
|
|
if (!strncmp(developers[i],
|
|
(has_mgivenname(mtmp)) ? MGIVENNAME(mtmp) : "",
|
|
strlen(developers[i]))) {
|
|
match = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
m++;
|
|
} while (match && m < 100); /* m for insurance */
|
|
|
|
if (match)
|
|
return (const char *) 0;
|
|
return (developers[i]);
|
|
}
|
|
|
|
staticfn void
|
|
get_mplname(struct monst *mtmp, char *nam)
|
|
{
|
|
boolean fmlkind = is_female(mtmp->data);
|
|
const char *devnam;
|
|
|
|
devnam = dev_name();
|
|
if (!devnam)
|
|
Strcpy(nam, fmlkind ? "Eve" : "Adam");
|
|
else if (fmlkind && !!strcmp(devnam, "Janet"))
|
|
Strcpy(nam, rn2(2) ? "Maud" : "Eve");
|
|
else
|
|
Strcpy(nam, devnam);
|
|
|
|
if (fmlkind || !strcmp(nam, "Janet"))
|
|
mtmp->female = 1;
|
|
else
|
|
mtmp->female = 0;
|
|
Strcat(nam, " the ");
|
|
Strcat(nam, rank_of((int) mtmp->m_lev, monsndx(mtmp->data),
|
|
(boolean) mtmp->female));
|
|
}
|
|
|
|
staticfn void
|
|
mk_mplayer_armor(struct monst *mon, short typ)
|
|
{
|
|
struct obj *obj;
|
|
|
|
if (typ == STRANGE_OBJECT)
|
|
return;
|
|
obj = mksobj(typ, FALSE, FALSE);
|
|
obj->oeroded = obj->oeroded2 = 0;
|
|
if (!rn2(3))
|
|
obj->oerodeproof = 1;
|
|
if (!rn2(3))
|
|
curse(obj);
|
|
if (!rn2(3))
|
|
bless(obj);
|
|
/* Most players who get to the endgame who have cursed equipment
|
|
* have it because the wizard or other monsters cursed it, so its
|
|
* chances of having plusses is the same as usual....
|
|
*/
|
|
obj->spe = rn2(10) ? (rn2(3) ? rn2(5) : rn1(4, 4)) : -rnd(3);
|
|
(void) mpickobj(mon, obj);
|
|
}
|
|
|
|
struct monst *
|
|
mk_mplayer(struct permonst *ptr, coordxy x, coordxy y, boolean special)
|
|
{
|
|
struct monst *mtmp;
|
|
char nam[PL_NSIZ];
|
|
|
|
if (!is_mplayer(ptr))
|
|
return ((struct monst *) 0);
|
|
|
|
if (MON_AT(x, y))
|
|
(void) rloc(m_at(x, y), RLOC_ERR|RLOC_NOMSG); /* insurance */
|
|
|
|
if (!In_endgame(&u.uz))
|
|
special = FALSE;
|
|
|
|
if ((mtmp = makemon(ptr, x, y, special ? MM_NOMSG : NO_MM_FLAGS)) != 0) {
|
|
short weapon, armor, cloak, helm, shield;
|
|
int quan;
|
|
struct obj *otmp;
|
|
|
|
mtmp->m_lev = (special ? rn1(16, 15) : rnd(16));
|
|
mtmp->mhp = mtmp->mhpmax = d((int) mtmp->m_lev, 10)
|
|
+ (special ? (30 + rnd(30)) : 30);
|
|
if (special) {
|
|
get_mplname(mtmp, nam);
|
|
mtmp = christen_monst(mtmp, nam);
|
|
/* that's why they are "stuck" in the endgame :-) */
|
|
(void) mongets(mtmp, FAKE_AMULET_OF_YENDOR);
|
|
}
|
|
mtmp->mpeaceful = 0;
|
|
set_malign(mtmp); /* peaceful may have changed again */
|
|
|
|
/* default equipment; much of it will be overridden below */
|
|
weapon = !rn2(2) ? LONG_SWORD : rnd_class(SPEAR, BULLWHIP);
|
|
armor = rnd_class(GRAY_DRAGON_SCALE_MAIL, YELLOW_DRAGON_SCALE_MAIL);
|
|
cloak = !rn2(8) ? STRANGE_OBJECT
|
|
: rnd_class(OILSKIN_CLOAK, CLOAK_OF_DISPLACEMENT);
|
|
helm = !rn2(8) ? STRANGE_OBJECT
|
|
: rnd_class(ELVEN_LEATHER_HELM, HELM_OF_TELEPATHY);
|
|
shield = !rn2(8) ? STRANGE_OBJECT
|
|
: rnd_class(ELVEN_SHIELD, SHIELD_OF_REFLECTION);
|
|
|
|
switch (monsndx(ptr)) {
|
|
case PM_ARCHEOLOGIST:
|
|
if (rn2(2))
|
|
weapon = BULLWHIP;
|
|
break;
|
|
case PM_BARBARIAN:
|
|
if (rn2(2)) {
|
|
weapon = rn2(2) ? TWO_HANDED_SWORD : BATTLE_AXE;
|
|
shield = STRANGE_OBJECT;
|
|
}
|
|
if (rn2(2))
|
|
armor = rnd_class(PLATE_MAIL, CHAIN_MAIL);
|
|
if (helm == HELM_OF_BRILLIANCE)
|
|
helm = STRANGE_OBJECT;
|
|
break;
|
|
case PM_CAVE_DWELLER:
|
|
if (rn2(4))
|
|
weapon = MACE;
|
|
else if (rn2(2))
|
|
weapon = CLUB;
|
|
if (helm == HELM_OF_BRILLIANCE)
|
|
helm = STRANGE_OBJECT;
|
|
break;
|
|
case PM_HEALER:
|
|
if (rn2(4))
|
|
weapon = QUARTERSTAFF;
|
|
else if (rn2(2))
|
|
weapon = rn2(2) ? UNICORN_HORN : SCALPEL;
|
|
if (rn2(4))
|
|
helm = rn2(2) ? HELM_OF_BRILLIANCE : HELM_OF_TELEPATHY;
|
|
if (rn2(2))
|
|
shield = STRANGE_OBJECT;
|
|
break;
|
|
case PM_KNIGHT:
|
|
if (rn2(4))
|
|
weapon = LONG_SWORD;
|
|
if (rn2(2))
|
|
armor = rnd_class(PLATE_MAIL, CHAIN_MAIL);
|
|
break;
|
|
case PM_MONK:
|
|
weapon = !rn2(3) ? SHURIKEN : STRANGE_OBJECT;
|
|
armor = STRANGE_OBJECT;
|
|
cloak = ROBE;
|
|
if (rn2(2))
|
|
shield = STRANGE_OBJECT;
|
|
break;
|
|
case PM_CLERIC:
|
|
if (rn2(2))
|
|
weapon = MACE;
|
|
if (rn2(2))
|
|
armor = rnd_class(PLATE_MAIL, CHAIN_MAIL);
|
|
if (rn2(4))
|
|
cloak = ROBE;
|
|
if (rn2(4))
|
|
helm = rn2(2) ? HELM_OF_BRILLIANCE : HELM_OF_TELEPATHY;
|
|
if (rn2(2))
|
|
shield = STRANGE_OBJECT;
|
|
break;
|
|
case PM_RANGER:
|
|
if (rn2(2))
|
|
weapon = ELVEN_DAGGER;
|
|
break;
|
|
case PM_ROGUE:
|
|
if (rn2(2))
|
|
weapon = rn2(2) ? SHORT_SWORD : ORCISH_DAGGER;
|
|
break;
|
|
case PM_SAMURAI:
|
|
if (rn2(2))
|
|
weapon = KATANA;
|
|
break;
|
|
case PM_TOURIST:
|
|
/* Defaults are just fine */
|
|
break;
|
|
case PM_VALKYRIE:
|
|
if (rn2(2))
|
|
weapon = WAR_HAMMER;
|
|
if (rn2(2))
|
|
armor = rnd_class(PLATE_MAIL, CHAIN_MAIL);
|
|
break;
|
|
case PM_WIZARD:
|
|
if (rn2(4))
|
|
weapon = rn2(2) ? QUARTERSTAFF : ATHAME;
|
|
if (rn2(2)) {
|
|
armor = rn2(2) ? BLACK_DRAGON_SCALE_MAIL
|
|
: SILVER_DRAGON_SCALE_MAIL;
|
|
cloak = CLOAK_OF_MAGIC_RESISTANCE;
|
|
}
|
|
if (rn2(4))
|
|
helm = HELM_OF_BRILLIANCE;
|
|
shield = STRANGE_OBJECT;
|
|
break;
|
|
default:
|
|
impossible("bad mplayer monster");
|
|
weapon = 0;
|
|
break;
|
|
}
|
|
|
|
if (weapon != STRANGE_OBJECT) {
|
|
otmp = mksobj(weapon, TRUE, FALSE);
|
|
otmp->oeroded = otmp->oeroded2 = 0;
|
|
otmp->spe = (special ? rn1(5, 4) : rn2(4));
|
|
if (!rn2(3))
|
|
otmp->oerodeproof = 1;
|
|
else if (!rn2(2))
|
|
otmp->greased = 1;
|
|
/* mk_artifact() with otmp and A_NONE will never return NULL */
|
|
if (special && rn2(2))
|
|
otmp = mk_artifact(otmp, A_NONE, 99, FALSE);
|
|
/* usually increase stack size if stackable weapon */
|
|
if (objects[otmp->otyp].oc_merge && !otmp->oartifact
|
|
&& monmightthrowwep(otmp))
|
|
otmp->quan += (long) rn2(is_spear(otmp) ? 4 : 8);
|
|
otmp->owt = weight(otmp);
|
|
/* mplayers knew better than to overenchant Magicbane */
|
|
if (is_art(otmp, ART_MAGICBANE))
|
|
otmp->spe = rnd(4);
|
|
(void) mpickobj(mtmp, otmp);
|
|
}
|
|
|
|
if (special) {
|
|
if (!rn2(10))
|
|
(void) mongets(mtmp, rn2(3) ? LUCKSTONE : LOADSTONE);
|
|
mk_mplayer_armor(mtmp, armor);
|
|
mk_mplayer_armor(mtmp, cloak);
|
|
mk_mplayer_armor(mtmp, helm);
|
|
mk_mplayer_armor(mtmp, shield);
|
|
if (weapon == WAR_HAMMER) /* valkyrie: wimpy weapon or Mjollnir */
|
|
mk_mplayer_armor(mtmp, GAUNTLETS_OF_POWER);
|
|
else if (rn2(8))
|
|
mk_mplayer_armor(mtmp, rnd_class(LEATHER_GLOVES,
|
|
GAUNTLETS_OF_DEXTERITY));
|
|
if (rn2(8))
|
|
mk_mplayer_armor(mtmp, rnd_class(LOW_BOOTS,
|
|
LEVITATION_BOOTS));
|
|
m_dowear(mtmp, TRUE);
|
|
|
|
quan = rn2(3) ? rn2(3) : rn2(16);
|
|
while (quan--)
|
|
(void) mongets(mtmp, rnd_class(DILITHIUM_CRYSTAL, JADE));
|
|
/* To get the gold "right" would mean a player can double his
|
|
gold supply by killing one mplayer. Not good. */
|
|
mkmonmoney(mtmp, rn2(1000));
|
|
quan = rn2(10);
|
|
while (quan--)
|
|
(void) mpickobj(mtmp, mkobj(RANDOM_CLASS, FALSE));
|
|
}
|
|
quan = rnd(3);
|
|
while (quan--)
|
|
(void) mongets(mtmp, rnd_offensive_item(mtmp));
|
|
quan = rnd(3);
|
|
while (quan--)
|
|
(void) mongets(mtmp, rnd_defensive_item(mtmp));
|
|
quan = rnd(3);
|
|
while (quan--)
|
|
(void) mongets(mtmp, rnd_misc_item(mtmp));
|
|
}
|
|
|
|
return (mtmp);
|
|
}
|
|
|
|
/* create the indicated number (num) of monster-players,
|
|
* randomly chosen, and in randomly chosen (free) locations
|
|
* on the level. If "special", the size of num should not
|
|
* be bigger than the number of _non-repeated_ names in the
|
|
* developers array, otherwise a bunch of Adams and Eves will
|
|
* fill up the overflow.
|
|
*/
|
|
void
|
|
create_mplayers(int num, boolean special)
|
|
{
|
|
int pm, x, y;
|
|
struct monst fakemon;
|
|
|
|
fakemon = cg.zeromonst;
|
|
while (num) {
|
|
int tryct = 0;
|
|
|
|
/* roll for character class */
|
|
pm = rn1(PM_WIZARD - PM_ARCHEOLOGIST + 1, PM_ARCHEOLOGIST);
|
|
set_mon_data(&fakemon, &mons[pm]);
|
|
|
|
/* roll for an available location */
|
|
do {
|
|
x = rn1(COLNO - 4, 2);
|
|
y = rnd(ROWNO - 2);
|
|
} while (!goodpos(x, y, &fakemon, 0) && tryct++ <= 50);
|
|
|
|
/* if pos not found in 50 tries, don't bother to continue */
|
|
if (tryct > 50)
|
|
return;
|
|
|
|
(void) mk_mplayer(&mons[pm], (coordxy) x, (coordxy) y, special);
|
|
num--;
|
|
}
|
|
}
|
|
|
|
void
|
|
mplayer_talk(struct monst *mtmp)
|
|
{
|
|
static const char
|
|
*same_class_msg[3] = {
|
|
"I can't win, and neither will you!",
|
|
"You don't deserve to win!",
|
|
"Mine should be the honor, not yours!",
|
|
},
|
|
*other_class_msg[3] = {
|
|
"The low-life wants to talk, eh?",
|
|
"Fight, scum!",
|
|
"Here is what I have to say!",
|
|
};
|
|
|
|
if (mtmp->mpeaceful)
|
|
return; /* will drop to humanoid talk */
|
|
|
|
SetVoice(mtmp, 0, 80, 0);
|
|
verbalize("Talk? -- %s", mtmp->data == &mons[gu.urole.mnum]
|
|
? same_class_msg[rn2(3)]
|
|
: other_class_msg[rn2(3)]);
|
|
}
|
|
|
|
/*mplayer.c*/
|