add MALE, FEMALE, and gender-neutral names for individual monster species
to the mons array. The gender-neutral name (NEUTRAL) is mandatory, the
MALE and FEMALE versions are not.
replace code uses of the mname field of permonst with one of the three
potentially-available gender-specific names.
consolidate some separate mons entries that differed only by species into a
single mons entry (caveman, cavewoman and priest,priestess etc.)
consolidate several "* lord" and "* queen/* king" monst entries into
their single species, and allow both genders on some where it makes some
sense (there is probably more work and cleanup to come out of this at some
point, and the chosen gender-neutral name variations are not cast in stone
if someone has better suggestions).
related function or macro additions:
pmname(pm, gender) to get the gender variation of the permonst name. It
guards against monsters that haven't got anything except NEUTRAL naming
and falls back to the NEUTRAL version if FEMALE and MALE versions are
missing.
Ugender to obtain the current hero gender.
Mgender(mtmp) to obtain the gender of a monster
While the code can safely refer directly to pmnames[NEUTRAL] safely in the
code because it always exists, the other two (pmnames[MALE] and
pmnames[FEMALE] may not exist so use:
pmname(ptr, gidx)
where -ptr is a permonst *
-gidx is an index into the pmnames array field of the
permonst struct
pmname() checks for a valid index and checks for null-pointers for
pmnames[MALE] and pmnames[FEMALE], and will fall back to pmnames[NEUTRAL] if
the pointer requested if the requested variation is unavailable, or if the
gidx is out-of-range.
Allow code to specify makemon flags to request female or male (via MM_MALE
and MM_FEMALE flags respectively)to makedefs, since the species alone doesn't
distinguish male/female anymore. Specifying MM_MALE or MM_FEMALE won't
override the pm M2_MALE and M2_FEMALE flags on a mons[] entry.
male and female tiles have been added to win/share/monsters.txt.
The majority are duplicated placeholders except for those that were
separate mons entries before. Perhaps someone will contribute artwork in the
future to make the male and female variations visually distinguishable.
tilemapping via has the MALE tile indexes in the glyph2tile[]
array produced at build time. If a window port has information that the
FEMALE tile is required, it just has to increment the index returned
from the glyph2tile[] array by 1.
statues already preserved gender of the monster through STATUE_FEMALE
and STATUE_MALE, so ensure that pmnames takes that into consideration.
I expect some refinement will be required after broad play-testing puts it to
the test.
consolidate caveman,cavewoman and priest,priestess monst.c entries etc
This commit will require a bump of editlevel in patchlevel.h because it alters
the index numbers of the monsters due to the consolidation of some. Those
index numbers are saved in some other structures, even though the mons[] array
itself is not part of the savefile.
Window Port Interface Change
Also add a parameter to print_glyph to convey additional information beyond
the glyph to the window ports. Every single window port was calling back to
mapglyph for the information anyway, so just included it in the interface and
produce the information right in the display core.
The mapglyph() function uses will be eliminated, although there are still some
in the code yet to be dealt with.
win32, tty, x11, Qt, msdos window ports have all had adjustments done to
utilize the new parameter instead of calling mapglyph, but some of those
window ports have not been thoroughly tested since the changes.
Interface change additional info:
print_glyph(window, x, y, glyph, bkglyph, *glyphmod)
-- Print the glyph at (x,y) on the given window. Glyphs are
integers at the interface, mapped to whatever the window-
port wants (symbol, font, color, attributes, ...there's
a 1-1 map between glyphs and distinct things on the map).
-- bkglyph is a background glyph for potential use by some
graphical or tiled environments to allow the depiction
to fall against a background consistent with the grid
around x,y. If bkglyph is NO_GLYPH, then the parameter
should be ignored (do nothing with it).
-- glyphmod provides extended information about the glyph
that window ports can use to enhance the display in
various ways.
unsigned int glyphmod[NUM_GLYPHMOD]
where:
glyphmod[GM_TTYCHAR] is the text characters associated
with the original NetHack display.
glyphmod[GM_FLAGS] are the special flags that denote
additional information that window
ports can use.
glyphmod[GM_COLOR] is the text character
color associated with the original
NetHack display.
Support for including the glyphmod info in the display glyph buffer
alongside the glyph itself was added and is the default operation.
That can be turned off by defining UNBUFFERED_GLYPHMOD at compile time.
With UNBUFFERED_GLYPHMOD operation, a call will be placed to map_glyphmod()
immediately prior to every print_glyph() call.
1510 lines
51 KiB
C
1510 lines
51 KiB
C
/* NetHack 3.7 sounds.c $NHDT-Date: 1600933442 2020/09/24 07:44:02 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.103 $ */
|
|
/* Copyright (c) 1989 Janet Walz, Mike Threepoint */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
static boolean FDECL(mon_is_gecko, (struct monst *));
|
|
static int FDECL(domonnoise, (struct monst *));
|
|
static int NDECL(dochat);
|
|
static struct monst *FDECL(responsive_mon_at, (int, int));
|
|
static int FDECL(mon_in_room, (struct monst *, int));
|
|
|
|
/* this easily could be a macro, but it might overtax dumb compilers */
|
|
static int
|
|
mon_in_room(mon, rmtyp)
|
|
struct monst *mon;
|
|
int rmtyp;
|
|
{
|
|
int rno = levl[mon->mx][mon->my].roomno;
|
|
if (rno >= ROOMOFFSET)
|
|
return g.rooms[rno - ROOMOFFSET].rtype == rmtyp;
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
dosounds()
|
|
{
|
|
register struct mkroom *sroom;
|
|
register int hallu, vx, vy;
|
|
#if defined(AMIGA) && defined(AZTEC_C_WORKAROUND)
|
|
int xx;
|
|
#endif
|
|
struct monst *mtmp;
|
|
|
|
if (Deaf || !flags.acoustics || u.uswallow || Underwater)
|
|
return;
|
|
|
|
hallu = Hallucination ? 1 : 0;
|
|
|
|
if (g.level.flags.nfountains && !rn2(400)) {
|
|
static const char *const fountain_msg[4] = {
|
|
"bubbling water.", "water falling on coins.",
|
|
"the splashing of a naiad.", "a soda fountain!",
|
|
};
|
|
You_hear1(fountain_msg[rn2(3) + hallu]);
|
|
}
|
|
if (g.level.flags.nsinks && !rn2(300)) {
|
|
static const char *const sink_msg[3] = {
|
|
"a slow drip.", "a gurgling noise.", "dishes being washed!",
|
|
};
|
|
You_hear1(sink_msg[rn2(2) + hallu]);
|
|
}
|
|
if (g.level.flags.has_court && !rn2(200)) {
|
|
static const char *const throne_msg[4] = {
|
|
"the tones of courtly conversation.",
|
|
"a sceptre pounded in judgment.",
|
|
"Someone shouts \"Off with %s head!\"", "Queen Beruthiel's cats!",
|
|
};
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if ((mtmp->msleeping || is_lord(mtmp->data)
|
|
|| is_prince(mtmp->data)) && !is_animal(mtmp->data)
|
|
&& mon_in_room(mtmp, COURT)) {
|
|
/* finding one is enough, at least for now */
|
|
int which = rn2(3) + hallu;
|
|
|
|
if (which != 2)
|
|
You_hear1(throne_msg[which]);
|
|
else
|
|
pline(throne_msg[2], uhis());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (g.level.flags.has_swamp && !rn2(200)) {
|
|
static const char *const swamp_msg[3] = {
|
|
"hear mosquitoes!", "smell marsh gas!", /* so it's a smell...*/
|
|
"hear Donald Duck!",
|
|
};
|
|
You1(swamp_msg[rn2(2) + hallu]);
|
|
return;
|
|
}
|
|
if (g.level.flags.has_vault && !rn2(200)) {
|
|
if (!(sroom = search_special(VAULT))) {
|
|
/* strange ... */
|
|
g.level.flags.has_vault = 0;
|
|
return;
|
|
}
|
|
if (gd_sound())
|
|
switch (rn2(2) + hallu) {
|
|
case 1: {
|
|
boolean gold_in_vault = FALSE;
|
|
|
|
for (vx = sroom->lx; vx <= sroom->hx; vx++)
|
|
for (vy = sroom->ly; vy <= sroom->hy; vy++)
|
|
if (g_at(vx, vy))
|
|
gold_in_vault = TRUE;
|
|
#if defined(AMIGA) && defined(AZTEC_C_WORKAROUND)
|
|
/* Bug in aztec assembler here. Workaround below */
|
|
xx = ROOM_INDEX(sroom) + ROOMOFFSET;
|
|
xx = (xx != vault_occupied(u.urooms));
|
|
if (xx)
|
|
#else
|
|
if (vault_occupied(u.urooms)
|
|
!= (ROOM_INDEX(sroom) + ROOMOFFSET))
|
|
#endif /* AZTEC_C_WORKAROUND */
|
|
{
|
|
if (gold_in_vault)
|
|
You_hear(!hallu
|
|
? "someone counting gold coins."
|
|
: "the quarterback calling the play.");
|
|
else
|
|
You_hear("someone searching.");
|
|
break;
|
|
}
|
|
}
|
|
/*FALLTHRU*/
|
|
case 0:
|
|
You_hear("the footsteps of a guard on patrol.");
|
|
break;
|
|
case 2:
|
|
You_hear("Ebenezer Scrooge!");
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
if (g.level.flags.has_beehive && !rn2(200)) {
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if ((mtmp->data->mlet == S_ANT && is_flyer(mtmp->data))
|
|
&& mon_in_room(mtmp, BEEHIVE)) {
|
|
switch (rn2(2) + hallu) {
|
|
case 0:
|
|
You_hear("a low buzzing.");
|
|
break;
|
|
case 1:
|
|
You_hear("an angry drone.");
|
|
break;
|
|
case 2:
|
|
You_hear("bees in your %sbonnet!",
|
|
uarmh ? "" : "(nonexistent) ");
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (g.level.flags.has_morgue && !rn2(200)) {
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if ((is_undead(mtmp->data) || is_vampshifter(mtmp))
|
|
&& mon_in_room(mtmp, MORGUE)) {
|
|
const char *hair = body_part(HAIR); /* hair/fur/scales */
|
|
|
|
switch (rn2(2) + hallu) {
|
|
case 0:
|
|
You("suddenly realize it is unnaturally quiet.");
|
|
break;
|
|
case 1:
|
|
pline_The("%s on the back of your %s %s up.", hair,
|
|
body_part(NECK), vtense(hair, "stand"));
|
|
break;
|
|
case 2:
|
|
pline_The("%s on your %s %s to stand up.", hair,
|
|
body_part(HEAD), vtense(hair, "seem"));
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (g.level.flags.has_barracks && !rn2(200)) {
|
|
static const char *const barracks_msg[4] = {
|
|
"blades being honed.", "loud snoring.", "dice being thrown.",
|
|
"General MacArthur!",
|
|
};
|
|
int count = 0;
|
|
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if (is_mercenary(mtmp->data)
|
|
#if 0 /* don't bother excluding these */
|
|
&& !strstri(mtmp->data->pmnames[NEUTRAL], "watch")
|
|
&& !strstri(mtmp->data->pmnames[NEUTRAL], "guard")
|
|
#endif
|
|
&& mon_in_room(mtmp, BARRACKS)
|
|
/* sleeping implies not-yet-disturbed (usually) */
|
|
&& (mtmp->msleeping || ++count > 5)) {
|
|
You_hear1(barracks_msg[rn2(3) + hallu]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (g.level.flags.has_zoo && !rn2(200)) {
|
|
static const char *const zoo_msg[3] = {
|
|
"a sound reminiscent of an elephant stepping on a peanut.",
|
|
"a sound reminiscent of a seal barking.", "Doctor Dolittle!",
|
|
};
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if ((mtmp->msleeping || is_animal(mtmp->data))
|
|
&& mon_in_room(mtmp, ZOO)) {
|
|
You_hear1(zoo_msg[rn2(2) + hallu]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (g.level.flags.has_shop && !rn2(200)) {
|
|
if (!(sroom = search_special(ANY_SHOP))) {
|
|
/* strange... */
|
|
g.level.flags.has_shop = 0;
|
|
return;
|
|
}
|
|
if (tended_shop(sroom)
|
|
&& !index(u.ushops, (int) (ROOM_INDEX(sroom) + ROOMOFFSET))) {
|
|
static const char *const shop_msg[3] = {
|
|
"someone cursing shoplifters.",
|
|
"the chime of a cash register.", "Neiman and Marcus arguing!",
|
|
};
|
|
You_hear1(shop_msg[rn2(2) + hallu]);
|
|
}
|
|
return;
|
|
}
|
|
if (g.level.flags.has_temple && !rn2(200)
|
|
&& !(Is_astralevel(&u.uz) || Is_sanctum(&u.uz))) {
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if (mtmp->ispriest && inhistemple(mtmp)
|
|
/* priest must be active */
|
|
&& mtmp->mcanmove && !mtmp->msleeping
|
|
/* hero must be outside this temple */
|
|
&& temple_occupied(u.urooms) != EPRI(mtmp)->shroom)
|
|
break;
|
|
}
|
|
if (mtmp) {
|
|
/* Generic temple messages; no attempt to match topic or tone
|
|
to the pantheon involved, let alone to the specific deity.
|
|
These are assumed to be coming from the attending priest;
|
|
asterisk means that the priest must be capable of speech;
|
|
pound sign (octathorpe,&c--don't go there) means that the
|
|
priest and the altar must not be directly visible (we don't
|
|
care if telepathy or extended detection reveals that the
|
|
priest is not currently standing on the altar; he's mobile). */
|
|
static const char *const temple_msg[] = {
|
|
"*someone praising %s.", "*someone beseeching %s.",
|
|
"#an animal carcass being offered in sacrifice.",
|
|
"*a strident plea for donations.",
|
|
};
|
|
const char *msg;
|
|
int trycount = 0, ax = EPRI(mtmp)->shrpos.x,
|
|
ay = EPRI(mtmp)->shrpos.y;
|
|
boolean speechless = (mtmp->data->msound <= MS_ANIMAL),
|
|
in_sight = canseemon(mtmp) || cansee(ax, ay);
|
|
|
|
do {
|
|
msg = temple_msg[rn2(SIZE(temple_msg) - 1 + hallu)];
|
|
if (index(msg, '*') && speechless)
|
|
continue;
|
|
if (index(msg, '#') && in_sight)
|
|
continue;
|
|
break; /* msg is acceptable */
|
|
} while (++trycount < 50);
|
|
while (!letter(*msg))
|
|
++msg; /* skip control flags */
|
|
if (index(msg, '%'))
|
|
You_hear(msg, halu_gname(EPRI(mtmp)->shralign));
|
|
else
|
|
You_hear1(msg);
|
|
return;
|
|
}
|
|
}
|
|
if (Is_oracle_level(&u.uz) && !rn2(400)) {
|
|
/* make sure the Oracle is still here */
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if (mtmp->data == &mons[PM_ORACLE])
|
|
break;
|
|
}
|
|
/* and don't produce silly effects when she's clearly visible */
|
|
if (mtmp && (hallu || !canseemon(mtmp))) {
|
|
static const char *const ora_msg[5] = {
|
|
"a strange wind.", /* Jupiter at Dodona */
|
|
"convulsive ravings.", /* Apollo at Delphi */
|
|
"snoring snakes.", /* AEsculapius at Epidaurus */
|
|
"someone say \"No more woodchucks!\"",
|
|
"a loud ZOT!" /* both rec.humor.oracle */
|
|
};
|
|
You_hear1(ora_msg[rn2(3) + hallu * 2]);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
static const char *const h_sounds[] = {
|
|
"beep", "boing", "sing", "belche", "creak", "cough",
|
|
"rattle", "ululate", "pop", "jingle", "sniffle", "tinkle",
|
|
"eep", "clatter", "hum", "sizzle", "twitter", "wheeze",
|
|
"rustle", "honk", "lisp", "yodel", "coo", "burp",
|
|
"moo", "boom", "murmur", "oink", "quack", "rumble",
|
|
"twang", "bellow", "toot", "gargle", "hoot", "warble"
|
|
};
|
|
|
|
const char *
|
|
growl_sound(mtmp)
|
|
register struct monst *mtmp;
|
|
{
|
|
const char *ret;
|
|
|
|
switch (mtmp->data->msound) {
|
|
case MS_MEW:
|
|
case MS_HISS:
|
|
ret = "hiss";
|
|
break;
|
|
case MS_BARK:
|
|
case MS_GROWL:
|
|
ret = "growl";
|
|
break;
|
|
case MS_ROAR:
|
|
ret = "roar";
|
|
break;
|
|
case MS_BUZZ:
|
|
ret = "buzz";
|
|
break;
|
|
case MS_SQEEK:
|
|
ret = "squeal";
|
|
break;
|
|
case MS_SQAWK:
|
|
ret = "screech";
|
|
break;
|
|
case MS_NEIGH:
|
|
ret = "neigh";
|
|
break;
|
|
case MS_WAIL:
|
|
ret = "wail";
|
|
break;
|
|
case MS_MOO:
|
|
ret = "low";
|
|
break;
|
|
case MS_SILENT:
|
|
ret = "commotion";
|
|
break;
|
|
default:
|
|
ret = "scream";
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* the sounds of a seriously abused pet, including player attacking it */
|
|
void
|
|
growl(mtmp)
|
|
register struct monst *mtmp;
|
|
{
|
|
register const char *growl_verb = 0;
|
|
|
|
if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound)
|
|
return;
|
|
|
|
/* presumably nearness and soundok checks have already been made */
|
|
if (Hallucination)
|
|
growl_verb = h_sounds[rn2(SIZE(h_sounds))];
|
|
else
|
|
growl_verb = growl_sound(mtmp);
|
|
if (growl_verb) {
|
|
pline("%s %s!", Monnam(mtmp), vtense((char *) 0, growl_verb));
|
|
iflags.last_msg = PLNMSG_GROWL;
|
|
if (g.context.run)
|
|
nomul(0);
|
|
wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 18);
|
|
}
|
|
}
|
|
|
|
/* the sounds of mistreated pets */
|
|
void
|
|
yelp(mtmp)
|
|
register struct monst *mtmp;
|
|
{
|
|
register const char *yelp_verb = 0;
|
|
|
|
if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound)
|
|
return;
|
|
|
|
/* presumably nearness and soundok checks have already been made */
|
|
if (Hallucination)
|
|
yelp_verb = h_sounds[rn2(SIZE(h_sounds))];
|
|
else
|
|
switch (mtmp->data->msound) {
|
|
case MS_MEW:
|
|
yelp_verb = (!Deaf) ? "yowl" : "arch";
|
|
break;
|
|
case MS_BARK:
|
|
case MS_GROWL:
|
|
yelp_verb = (!Deaf) ? "yelp" : "recoil";
|
|
break;
|
|
case MS_ROAR:
|
|
yelp_verb = (!Deaf) ? "snarl" : "bluff";
|
|
break;
|
|
case MS_SQEEK:
|
|
yelp_verb = (!Deaf) ? "squeal" : "quiver";
|
|
break;
|
|
case MS_SQAWK:
|
|
yelp_verb = (!Deaf) ? "screak" : "thrash";
|
|
break;
|
|
case MS_WAIL:
|
|
yelp_verb = (!Deaf) ? "wail" : "cringe";
|
|
break;
|
|
}
|
|
if (yelp_verb) {
|
|
pline("%s %s!", Monnam(mtmp), vtense((char *) 0, yelp_verb));
|
|
if (g.context.run)
|
|
nomul(0);
|
|
wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 12);
|
|
}
|
|
}
|
|
|
|
/* the sounds of distressed pets */
|
|
void
|
|
whimper(mtmp)
|
|
register struct monst *mtmp;
|
|
{
|
|
register const char *whimper_verb = 0;
|
|
|
|
if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound)
|
|
return;
|
|
|
|
/* presumably nearness and soundok checks have already been made */
|
|
if (Hallucination)
|
|
whimper_verb = h_sounds[rn2(SIZE(h_sounds))];
|
|
else
|
|
switch (mtmp->data->msound) {
|
|
case MS_MEW:
|
|
case MS_GROWL:
|
|
whimper_verb = "whimper";
|
|
break;
|
|
case MS_BARK:
|
|
whimper_verb = "whine";
|
|
break;
|
|
case MS_SQEEK:
|
|
whimper_verb = "squeal";
|
|
break;
|
|
}
|
|
if (whimper_verb) {
|
|
pline("%s %s.", Monnam(mtmp), vtense((char *) 0, whimper_verb));
|
|
if (g.context.run)
|
|
nomul(0);
|
|
wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 6);
|
|
}
|
|
}
|
|
|
|
/* pet makes "I'm hungry" noises */
|
|
void
|
|
beg(mtmp)
|
|
register struct monst *mtmp;
|
|
{
|
|
if (mtmp->msleeping || !mtmp->mcanmove
|
|
|| !(carnivorous(mtmp->data) || herbivorous(mtmp->data)))
|
|
return;
|
|
|
|
/* presumably nearness and soundok checks have already been made */
|
|
if (!is_silent(mtmp->data) && mtmp->data->msound <= MS_ANIMAL) {
|
|
(void) domonnoise(mtmp);
|
|
} else if (mtmp->data->msound >= MS_HUMANOID) {
|
|
if (!canspotmon(mtmp))
|
|
map_invisible(mtmp->mx, mtmp->my);
|
|
verbalize("I'm hungry.");
|
|
} else {
|
|
/* this is pretty lame but is better than leaving out the block
|
|
of speech types between animal and humanoid; this covers
|
|
MS_SILENT too (if caller lets that get this far) since it's
|
|
excluded by the first two cases */
|
|
if (canspotmon(mtmp))
|
|
pline("%s seems famished.", Monnam(mtmp));
|
|
/* looking famished will be a good trick for a tame skeleton... */
|
|
}
|
|
}
|
|
|
|
/* hero has attacked a peaceful monster within 'mon's view */
|
|
const char *
|
|
maybe_gasp(mon)
|
|
struct monst *mon;
|
|
{
|
|
static const char *const Exclam[] = {
|
|
"Gasp!", "Uh-oh.", "Oh my!", "What?", "Why?",
|
|
};
|
|
struct permonst *mptr = mon->data;
|
|
int msound = mptr->msound;
|
|
boolean dogasp = FALSE;
|
|
|
|
/* other roles' guardians and cross-aligned priests don't gasp */
|
|
if ((msound == MS_GUARDIAN && mptr != &mons[g.urole.guardnum])
|
|
|| (msound == MS_PRIEST && !p_coaligned(mon)))
|
|
msound = MS_SILENT;
|
|
/* co-aligned angels do gasp */
|
|
else if (msound == MS_CUSS && has_emin(mon)
|
|
&& (p_coaligned(mon) ? !EMIN(mon)->renegade : EMIN(mon)->renegade))
|
|
msound = MS_HUMANOID;
|
|
|
|
/*
|
|
* Only called for humanoids so animal noise handling is ignored.
|
|
*/
|
|
switch (msound) {
|
|
case MS_HUMANOID:
|
|
case MS_ARREST: /* Kops */
|
|
case MS_SOLDIER: /* solider, watchman */
|
|
case MS_GUARD: /* vault guard */
|
|
case MS_NURSE:
|
|
case MS_SEDUCE: /* nymph, succubus/incubus */
|
|
case MS_LEADER: /* quest leader */
|
|
case MS_GUARDIAN: /* leader's guards */
|
|
case MS_SELL: /* shopkeeper */
|
|
case MS_ORACLE:
|
|
case MS_PRIEST: /* temple priest, roaming aligned priest (not mplayer) */
|
|
case MS_BOAST: /* giants */
|
|
case MS_IMITATE: /* doppelganger, leocrotta, Aleax */
|
|
dogasp = TRUE;
|
|
break;
|
|
/* issue comprehensible word(s) if hero is similar type of creature */
|
|
case MS_ORC: /* used to be synonym for MS_GRUNT */
|
|
case MS_GRUNT: /* ogres, trolls, gargoyles, one or two others */
|
|
case MS_LAUGH: /* leprechaun, gremlin */
|
|
case MS_ROAR: /* dragon, xorn, owlbear */
|
|
/* capable of speech but only do so if hero is similar type */
|
|
case MS_DJINNI:
|
|
case MS_VAMPIRE: /* vampire in its own form */
|
|
case MS_WERE: /* lycanthrope in human form */
|
|
case MS_SPELL: /* titan, barrow wight, Nazgul, nalfeshnee */
|
|
dogasp = (mptr->mlet == g.youmonst.data->mlet);
|
|
break;
|
|
/* capable of speech but don't care if you attack peacefuls */
|
|
case MS_BRIBE:
|
|
case MS_CUSS:
|
|
case MS_RIDER:
|
|
case MS_NEMESIS:
|
|
/* can't speak */
|
|
case MS_SILENT:
|
|
default:
|
|
break;
|
|
}
|
|
if (dogasp) {
|
|
return Exclam[rn2(SIZE(Exclam))]; /* [mon->m_id % SIZE(Exclam)]; */
|
|
}
|
|
return (const char *) 0;
|
|
}
|
|
|
|
/* return True if mon is a gecko or seems to look like one (hallucination) */
|
|
static boolean
|
|
mon_is_gecko(mon)
|
|
struct monst *mon;
|
|
{
|
|
int glyph;
|
|
|
|
/* return True if it is actually a gecko */
|
|
if (mon->data == &mons[PM_GECKO])
|
|
return TRUE;
|
|
/* return False if it is a long worm; we might be chatting to its tail
|
|
(not strictly needed; long worms are MS_SILENT so won't get here) */
|
|
if (mon->data == &mons[PM_LONG_WORM])
|
|
return FALSE;
|
|
/* result depends upon whether map spot shows a gecko, which will
|
|
be due to hallucination or to mimickery since mon isn't one */
|
|
glyph = glyph_at(mon->mx, mon->my);
|
|
return (boolean) (glyph_to_mon(glyph) == PM_GECKO);
|
|
}
|
|
|
|
static int
|
|
domonnoise(mtmp)
|
|
register struct monst *mtmp;
|
|
{
|
|
char verbuf[BUFSZ];
|
|
register const char *pline_msg = 0, /* Monnam(mtmp) will be prepended */
|
|
*verbl_msg = 0, /* verbalize() */
|
|
*verbl_msg_mcan = 0; /* verbalize() if cancelled */
|
|
struct permonst *ptr = mtmp->data;
|
|
int msound = ptr->msound, gnomeplan = 0;
|
|
|
|
/* presumably nearness and sleep checks have already been made */
|
|
if (Deaf)
|
|
return 0;
|
|
if (is_silent(ptr))
|
|
return 0;
|
|
|
|
/* leader might be poly'd; if he can still speak, give leader speech */
|
|
if (mtmp->m_id == g.quest_status.leader_m_id && msound > MS_ANIMAL)
|
|
msound = MS_LEADER;
|
|
/* make sure it's your role's quest guardian; adjust if not */
|
|
else if (msound == MS_GUARDIAN && ptr != &mons[g.urole.guardnum])
|
|
msound = mons[genus(monsndx(ptr), 1)].msound;
|
|
/* some normally non-speaking types can/will speak if hero is similar */
|
|
else if (msound == MS_ORC
|
|
&& ((same_race(ptr, g.youmonst.data) /* current form, */
|
|
|| same_race(ptr, &mons[Race_switch])) /* unpoly'd form */
|
|
|| Hallucination))
|
|
msound = MS_HUMANOID;
|
|
/* silliness, with slight chance to interfere with shopping */
|
|
else if (Hallucination && mon_is_gecko(mtmp))
|
|
msound = MS_SELL;
|
|
|
|
/* be sure to do this before talking; the monster might teleport away, in
|
|
* which case we want to check its pre-teleport position
|
|
*/
|
|
if (!canspotmon(mtmp))
|
|
map_invisible(mtmp->mx, mtmp->my);
|
|
|
|
switch (msound) {
|
|
case MS_ORACLE:
|
|
return doconsult(mtmp);
|
|
case MS_PRIEST:
|
|
priest_talk(mtmp);
|
|
break;
|
|
case MS_LEADER:
|
|
case MS_NEMESIS:
|
|
case MS_GUARDIAN:
|
|
quest_chat(mtmp);
|
|
break;
|
|
case MS_SELL: /* pitch, pay, total */
|
|
if (!Hallucination || (mtmp->isshk && !rn2(2))) {
|
|
shk_chat(mtmp);
|
|
} else {
|
|
/* approximation of GEICO's advertising slogan (it actually
|
|
concludes with "save you 15% or more on car insurance.") */
|
|
Sprintf(verbuf, "15 minutes could save you 15 %s.",
|
|
currency(15L)); /* "zorkmids" */
|
|
verbl_msg = verbuf;
|
|
}
|
|
break;
|
|
case MS_VAMPIRE: {
|
|
/* vampire messages are varied by tameness, peacefulness, and time of
|
|
* night */
|
|
boolean isnight = night();
|
|
boolean kindred = (Upolyd && (u.umonnum == PM_VAMPIRE
|
|
|| u.umonnum == PM_VAMPIRE_LEADER));
|
|
boolean nightchild =
|
|
(Upolyd && (u.umonnum == PM_WOLF || u.umonnum == PM_WINTER_WOLF
|
|
|| u.umonnum == PM_WINTER_WOLF_CUB));
|
|
const char *racenoun =
|
|
(flags.female && g.urace.individual.f)
|
|
? g.urace.individual.f
|
|
: (g.urace.individual.m) ? g.urace.individual.m : g.urace.noun;
|
|
|
|
if (mtmp->mtame) {
|
|
if (kindred) {
|
|
Sprintf(verbuf, "Good %s to you Master%s",
|
|
isnight ? "evening" : "day",
|
|
isnight ? "!" : ". Why do we not rest?");
|
|
verbl_msg = verbuf;
|
|
} else {
|
|
Sprintf(verbuf, "%s%s",
|
|
nightchild ? "Child of the night, " : "",
|
|
midnight()
|
|
? "I can stand this craving no longer!"
|
|
: isnight
|
|
? "I beg you, help me satisfy this growing craving!"
|
|
: "I find myself growing a little weary.");
|
|
verbl_msg = verbuf;
|
|
}
|
|
} else if (mtmp->mpeaceful) {
|
|
if (kindred && isnight) {
|
|
Sprintf(verbuf, "Good feeding %s!",
|
|
flags.female ? "sister" : "brother");
|
|
verbl_msg = verbuf;
|
|
} else if (nightchild && isnight) {
|
|
Sprintf(verbuf, "How nice to hear you, child of the night!");
|
|
verbl_msg = verbuf;
|
|
} else
|
|
verbl_msg = "I only drink... potions.";
|
|
} else {
|
|
static const char *const vampmsg[] = {
|
|
/* These first two (0 and 1) are specially handled below */
|
|
"I vant to suck your %s!",
|
|
"I vill come after %s without regret!",
|
|
/* other famous vampire quotes can follow here if desired */
|
|
};
|
|
int vampindex;
|
|
|
|
if (kindred)
|
|
verbl_msg =
|
|
"This is my hunting ground that you dare to prowl!";
|
|
else if (g.youmonst.data == &mons[PM_SILVER_DRAGON]
|
|
|| g.youmonst.data == &mons[PM_BABY_SILVER_DRAGON]) {
|
|
/* Silver dragons are silver in color, not made of silver */
|
|
Sprintf(verbuf, "%s! Your silver sheen does not frighten me!",
|
|
g.youmonst.data == &mons[PM_SILVER_DRAGON]
|
|
? "Fool"
|
|
: "Young Fool");
|
|
verbl_msg = verbuf;
|
|
} else {
|
|
vampindex = rn2(SIZE(vampmsg));
|
|
if (vampindex == 0) {
|
|
Sprintf(verbuf, vampmsg[vampindex], body_part(BLOOD));
|
|
verbl_msg = verbuf;
|
|
} else if (vampindex == 1) {
|
|
Sprintf(verbuf, vampmsg[vampindex],
|
|
Upolyd ? an(pmname(&mons[u.umonnum],
|
|
flags.female ? FEMALE : MALE))
|
|
: an(racenoun));
|
|
verbl_msg = verbuf;
|
|
} else
|
|
verbl_msg = vampmsg[vampindex];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case MS_WERE:
|
|
if (flags.moonphase == FULL_MOON && (night() ^ !rn2(13))) {
|
|
pline("%s throws back %s head and lets out a blood curdling %s!",
|
|
Monnam(mtmp), mhis(mtmp),
|
|
(ptr == &mons[PM_HUMAN_WERERAT]) ? "shriek" : "howl");
|
|
wake_nearto(mtmp->mx, mtmp->my, 11 * 11);
|
|
} else
|
|
pline_msg =
|
|
"whispers inaudibly. All you can make out is \"moon\".";
|
|
break;
|
|
case MS_BARK:
|
|
if (flags.moonphase == FULL_MOON && night()) {
|
|
pline_msg = "howls.";
|
|
} else if (mtmp->mpeaceful) {
|
|
if (mtmp->mtame
|
|
&& (mtmp->mconf || mtmp->mflee || mtmp->mtrapped
|
|
|| g.moves > EDOG(mtmp)->hungrytime || mtmp->mtame < 5))
|
|
pline_msg = "whines.";
|
|
else if (mtmp->mtame && EDOG(mtmp)->hungrytime > g.moves + 1000)
|
|
pline_msg = "yips.";
|
|
else {
|
|
if (mtmp->data
|
|
!= &mons[PM_DINGO]) /* dingos do not actually bark */
|
|
pline_msg = "barks.";
|
|
}
|
|
} else {
|
|
pline_msg = "growls.";
|
|
}
|
|
break;
|
|
case MS_MEW:
|
|
if (mtmp->mtame) {
|
|
if (mtmp->mconf || mtmp->mflee || mtmp->mtrapped
|
|
|| mtmp->mtame < 5)
|
|
pline_msg = "yowls.";
|
|
else if (g.moves > EDOG(mtmp)->hungrytime)
|
|
pline_msg = "meows.";
|
|
else if (EDOG(mtmp)->hungrytime > g.moves + 1000)
|
|
pline_msg = "purrs.";
|
|
else
|
|
pline_msg = "mews.";
|
|
break;
|
|
}
|
|
/*FALLTHRU*/
|
|
case MS_GROWL:
|
|
pline_msg = mtmp->mpeaceful ? "snarls." : "growls!";
|
|
break;
|
|
case MS_ROAR:
|
|
pline_msg = mtmp->mpeaceful ? "snarls." : "roars!";
|
|
break;
|
|
case MS_SQEEK:
|
|
pline_msg = "squeaks.";
|
|
break;
|
|
case MS_SQAWK:
|
|
if (ptr == &mons[PM_RAVEN] && !mtmp->mpeaceful)
|
|
verbl_msg = "Nevermore!";
|
|
else
|
|
pline_msg = "squawks.";
|
|
break;
|
|
case MS_HISS:
|
|
if (!mtmp->mpeaceful)
|
|
pline_msg = "hisses!";
|
|
else
|
|
return 0; /* no sound */
|
|
break;
|
|
case MS_BUZZ:
|
|
pline_msg = mtmp->mpeaceful ? "drones." : "buzzes angrily.";
|
|
break;
|
|
case MS_GRUNT:
|
|
pline_msg = "grunts.";
|
|
break;
|
|
case MS_NEIGH:
|
|
if (mtmp->mtame < 5)
|
|
pline_msg = "neighs.";
|
|
else if (g.moves > EDOG(mtmp)->hungrytime)
|
|
pline_msg = "whinnies.";
|
|
else
|
|
pline_msg = "whickers.";
|
|
break;
|
|
case MS_MOO:
|
|
pline_msg = mtmp->mpeaceful ? "moos." : "bellows!";
|
|
break;
|
|
case MS_WAIL:
|
|
pline_msg = "wails mournfully.";
|
|
break;
|
|
case MS_GURGLE:
|
|
pline_msg = "gurgles.";
|
|
break;
|
|
case MS_BURBLE:
|
|
pline_msg = "burbles.";
|
|
break;
|
|
case MS_TRUMPET:
|
|
pline_msg = "trumpets!";
|
|
wake_nearto(mtmp->mx, mtmp->my, 11 * 11);
|
|
break;
|
|
case MS_SHRIEK:
|
|
pline_msg = "shrieks.";
|
|
aggravate();
|
|
break;
|
|
case MS_IMITATE:
|
|
pline_msg = "imitates you.";
|
|
break;
|
|
case MS_BONES:
|
|
pline("%s rattles noisily.", Monnam(mtmp));
|
|
You("freeze for a moment.");
|
|
nomul(-2);
|
|
g.multi_reason = "scared by rattling";
|
|
g.nomovemsg = 0;
|
|
break;
|
|
case MS_LAUGH: {
|
|
static const char *const laugh_msg[4] = {
|
|
"giggles.", "chuckles.", "snickers.", "laughs.",
|
|
};
|
|
pline_msg = laugh_msg[rn2(4)];
|
|
break;
|
|
}
|
|
case MS_MUMBLE:
|
|
pline_msg = "mumbles incomprehensibly.";
|
|
break;
|
|
case MS_ORC: /* this used to be an alias for grunt, now it is distinct */
|
|
pline_msg = "grunts.";
|
|
break;
|
|
case MS_DJINNI:
|
|
if (mtmp->mtame) {
|
|
verbl_msg = "Sorry, I'm all out of wishes.";
|
|
} else if (mtmp->mpeaceful) {
|
|
if (ptr == &mons[PM_WATER_DEMON])
|
|
pline_msg = "gurgles.";
|
|
else
|
|
verbl_msg = "I'm free!";
|
|
} else {
|
|
if (ptr != &mons[PM_PRISONER])
|
|
verbl_msg = "This will teach you not to disturb me!";
|
|
else /* vague because prisoner might already be out of cell */
|
|
verbl_msg = "Get me out of here.";
|
|
}
|
|
break;
|
|
case MS_BOAST: /* giants */
|
|
if (!mtmp->mpeaceful) {
|
|
switch (rn2(4)) {
|
|
case 0:
|
|
pline("%s boasts about %s gem collection.", Monnam(mtmp),
|
|
mhis(mtmp));
|
|
break;
|
|
case 1:
|
|
pline_msg = "complains about a diet of mutton.";
|
|
break;
|
|
default:
|
|
pline_msg = "shouts \"Fee Fie Foe Foo!\" and guffaws.";
|
|
wake_nearto(mtmp->mx, mtmp->my, 7 * 7);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
/*FALLTHRU*/
|
|
case MS_HUMANOID:
|
|
if (!mtmp->mpeaceful) {
|
|
if (In_endgame(&u.uz) && is_mplayer(ptr))
|
|
mplayer_talk(mtmp);
|
|
else
|
|
pline_msg = "threatens you.";
|
|
break;
|
|
}
|
|
/* Generic peaceful humanoid behaviour. */
|
|
if (mtmp->mflee)
|
|
pline_msg = "wants nothing to do with you.";
|
|
else if (mtmp->mhp < mtmp->mhpmax / 4)
|
|
pline_msg = "moans.";
|
|
else if (mtmp->mconf || mtmp->mstun)
|
|
verbl_msg = !rn2(3) ? "Huh?" : rn2(2) ? "What?" : "Eh?";
|
|
else if (!mtmp->mcansee)
|
|
verbl_msg = "I can't see!";
|
|
else if (mtmp->mtrapped) {
|
|
struct trap *t = t_at(mtmp->mx, mtmp->my);
|
|
|
|
if (t)
|
|
t->tseen = 1;
|
|
verbl_msg = "I'm trapped!";
|
|
} else if (mtmp->mhp < mtmp->mhpmax / 2)
|
|
pline_msg = "asks for a potion of healing.";
|
|
else if (mtmp->mtame && !mtmp->isminion
|
|
&& g.moves > EDOG(mtmp)->hungrytime)
|
|
verbl_msg = "I'm hungry.";
|
|
/* Specific monsters' interests */
|
|
else if (is_elf(ptr))
|
|
pline_msg = "curses orcs.";
|
|
else if (is_dwarf(ptr))
|
|
pline_msg = "talks about mining.";
|
|
else if (likes_magic(ptr))
|
|
pline_msg = "talks about spellcraft.";
|
|
else if (ptr->mlet == S_CENTAUR)
|
|
pline_msg = "discusses hunting.";
|
|
else if (is_gnome(ptr)) {
|
|
if (Hallucination && (gnomeplan = rn2(4)) % 2) {
|
|
/* skipped for rn2(4) result of 0 or 2;
|
|
gag from an early episode of South Park called "Gnomes";
|
|
initially, Tweek (introduced in that episode) is the only
|
|
one aware of the tiny gnomes after spotting them sneaking
|
|
about; they are embarked upon a three-step business plan;
|
|
a diagram of the plan shows:
|
|
Phase 1 Phase 2 Phase 3
|
|
Collect underpants ? Profit
|
|
and they never verbalize step 2 so we don't either */
|
|
verbl_msg = (gnomeplan == 1) ? "Phase one, collect underpants."
|
|
: "Phase three, profit!";
|
|
}
|
|
else {
|
|
verbl_msg =
|
|
"Many enter the dungeon, and few return to the sunlit lands.";
|
|
}
|
|
}
|
|
else
|
|
switch (monsndx(ptr)) {
|
|
case PM_HOBBIT:
|
|
pline_msg =
|
|
(mtmp->mhpmax - mtmp->mhp >= 10)
|
|
? "complains about unpleasant dungeon conditions."
|
|
: "asks you about the One Ring.";
|
|
break;
|
|
case PM_ARCHEOLOGIST:
|
|
pline_msg =
|
|
"describes a recent article in \"Spelunker Today\" magazine.";
|
|
break;
|
|
case PM_TOURIST:
|
|
verbl_msg = "Aloha.";
|
|
break;
|
|
default:
|
|
pline_msg = "discusses dungeon exploration.";
|
|
break;
|
|
}
|
|
break;
|
|
case MS_SEDUCE: {
|
|
int swval;
|
|
|
|
if (SYSOPT_SEDUCE) {
|
|
if (ptr->mlet != S_NYMPH
|
|
&& could_seduce(mtmp, &g.youmonst, (struct attack *) 0) == 1) {
|
|
(void) doseduce(mtmp);
|
|
break;
|
|
}
|
|
swval = ((poly_gender() != (int) mtmp->female) ? rn2(3) : 0);
|
|
} else
|
|
swval = ((poly_gender() == 0) ? rn2(3) : 0);
|
|
switch (swval) {
|
|
case 2:
|
|
verbl_msg = "Hello, sailor.";
|
|
break;
|
|
case 1:
|
|
pline_msg = "comes on to you.";
|
|
break;
|
|
default:
|
|
pline_msg = "cajoles you.";
|
|
}
|
|
} break;
|
|
case MS_ARREST:
|
|
if (mtmp->mpeaceful)
|
|
verbalize("Just the facts, %s.", flags.female ? "Ma'am" : "Sir");
|
|
else {
|
|
static const char *const arrest_msg[3] = {
|
|
"Anything you say can be used against you.",
|
|
"You're under arrest!", "Stop in the name of the Law!",
|
|
};
|
|
verbl_msg = arrest_msg[rn2(3)];
|
|
}
|
|
break;
|
|
case MS_BRIBE:
|
|
if (mtmp->mpeaceful && !mtmp->mtame) {
|
|
(void) demon_talk(mtmp);
|
|
break;
|
|
}
|
|
/* fall through */
|
|
case MS_CUSS:
|
|
if (!mtmp->mpeaceful)
|
|
cuss(mtmp);
|
|
else if (is_lminion(mtmp))
|
|
verbl_msg = "It's not too late.";
|
|
else
|
|
verbl_msg = "We're all doomed.";
|
|
break;
|
|
case MS_SPELL:
|
|
/* deliberately vague, since it's not actually casting any spell */
|
|
pline_msg = "seems to mutter a cantrip.";
|
|
break;
|
|
case MS_NURSE:
|
|
verbl_msg_mcan = "I hate this job!";
|
|
if (uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep)))
|
|
verbl_msg = "Put that weapon away before you hurt someone!";
|
|
else if (uarmc || uarm || uarmh || uarms || uarmg || uarmf)
|
|
verbl_msg = Role_if(PM_HEALER)
|
|
? "Doc, I can't help you unless you cooperate."
|
|
: "Please undress so I can examine you.";
|
|
else if (uarmu)
|
|
verbl_msg = "Take off your shirt, please.";
|
|
else
|
|
verbl_msg = "Relax, this won't hurt a bit.";
|
|
break;
|
|
case MS_GUARD:
|
|
if (money_cnt(g.invent))
|
|
verbl_msg = "Please drop that gold and follow me.";
|
|
else
|
|
verbl_msg = "Please follow me.";
|
|
break;
|
|
case MS_SOLDIER: {
|
|
static const char
|
|
*const soldier_foe_msg[3] = {
|
|
"Resistance is useless!", "You're dog meat!", "Surrender!",
|
|
},
|
|
*const soldier_pax_msg[3] = {
|
|
"What lousy pay we're getting here!",
|
|
"The food's not fit for Orcs!",
|
|
"My feet hurt, I've been on them all day!",
|
|
};
|
|
verbl_msg = mtmp->mpeaceful ? soldier_pax_msg[rn2(3)]
|
|
: soldier_foe_msg[rn2(3)];
|
|
break;
|
|
}
|
|
case MS_RIDER: {
|
|
const char *tribtitle;
|
|
struct obj *book = 0;
|
|
boolean ms_Death = (ptr == &mons[PM_DEATH]);
|
|
|
|
/* 3.6 tribute */
|
|
if (ms_Death && !g.context.tribute.Deathnotice
|
|
&& (book = u_have_novel()) != 0) {
|
|
if ((tribtitle = noveltitle(&book->novelidx)) != 0) {
|
|
Sprintf(verbuf, "Ah, so you have a copy of /%s/.", tribtitle);
|
|
/* no Death featured in these two, so exclude them */
|
|
if (strcmpi(tribtitle, "Snuff")
|
|
&& strcmpi(tribtitle, "The Wee Free Men"))
|
|
Strcat(verbuf, " I may have been misquoted there.");
|
|
verbl_msg = verbuf;
|
|
}
|
|
g.context.tribute.Deathnotice = 1;
|
|
} else if (ms_Death && rn2(3) && Death_quote(verbuf, sizeof verbuf)) {
|
|
verbl_msg = verbuf;
|
|
/* end of tribute addition */
|
|
|
|
} else if (ms_Death && !rn2(10)) {
|
|
pline_msg = "is busy reading a copy of Sandman #8.";
|
|
} else
|
|
verbl_msg = "Who do you think you are, War?";
|
|
break;
|
|
} /* case MS_RIDER */
|
|
} /* switch */
|
|
|
|
if (pline_msg) {
|
|
pline("%s %s", Monnam(mtmp), pline_msg);
|
|
} else if (mtmp->mcan && verbl_msg_mcan) {
|
|
verbalize1(verbl_msg_mcan);
|
|
} else if (verbl_msg) {
|
|
/* more 3.6 tribute */
|
|
if (ptr == &mons[PM_DEATH]) {
|
|
/* Death talks in CAPITAL LETTERS
|
|
and without quotation marks */
|
|
char tmpbuf[BUFSZ];
|
|
|
|
pline1(ucase(strcpy(tmpbuf, verbl_msg)));
|
|
} else {
|
|
verbalize1(verbl_msg);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* #chat command */
|
|
int
|
|
dotalk()
|
|
{
|
|
int result;
|
|
|
|
result = dochat();
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
dochat()
|
|
{
|
|
struct monst *mtmp;
|
|
int tx, ty;
|
|
struct obj *otmp;
|
|
|
|
if (is_silent(g.youmonst.data)) {
|
|
pline("As %s, you cannot speak.",
|
|
an(pmname(g.youmonst.data, flags.female ? FEMALE : MALE)));
|
|
return 0;
|
|
}
|
|
if (Strangled) {
|
|
You_cant("speak. You're choking!");
|
|
return 0;
|
|
}
|
|
if (u.uswallow) {
|
|
pline("They won't hear you out there.");
|
|
return 0;
|
|
}
|
|
if (Underwater) {
|
|
Your("speech is unintelligible underwater.");
|
|
return 0;
|
|
}
|
|
if (Deaf) {
|
|
pline("How can you hold a conversation when you cannot hear?");
|
|
return 0;
|
|
}
|
|
|
|
if (!Blind && (otmp = shop_object(u.ux, u.uy)) != (struct obj *) 0) {
|
|
/* standing on something in a shop and chatting causes the shopkeeper
|
|
to describe the price(s). This can inhibit other chatting inside
|
|
a shop, but that shouldn't matter much. shop_object() returns an
|
|
object iff inside a shop and the shopkeeper is present and willing
|
|
(not angry) and able (not asleep) to speak and the position
|
|
contains any objects other than just gold.
|
|
*/
|
|
price_quote(otmp);
|
|
return 1;
|
|
}
|
|
|
|
if (!getdir("Talk to whom? (in what direction)")) {
|
|
/* decided not to chat */
|
|
return 0;
|
|
}
|
|
|
|
if (u.usteed && u.dz > 0) {
|
|
if (!u.usteed->mcanmove || u.usteed->msleeping) {
|
|
pline("%s seems not to notice you.", Monnam(u.usteed));
|
|
return 1;
|
|
} else
|
|
return domonnoise(u.usteed);
|
|
}
|
|
|
|
if (u.dz) {
|
|
pline("They won't hear you %s there.", u.dz < 0 ? "up" : "down");
|
|
return 0;
|
|
}
|
|
|
|
if (u.dx == 0 && u.dy == 0) {
|
|
/*
|
|
* Let's not include this.
|
|
* It raises all sorts of questions: can you wear
|
|
* 2 helmets, 2 amulets, 3 pairs of gloves or 6 rings as a marilith,
|
|
* etc... --KAA
|
|
if (u.umonnum == PM_ETTIN) {
|
|
You("discover that your other head makes boring conversation.");
|
|
return 1;
|
|
}
|
|
*/
|
|
pline("Talking to yourself is a bad habit for a dungeoneer.");
|
|
return 0;
|
|
}
|
|
|
|
tx = u.ux + u.dx;
|
|
ty = u.uy + u.dy;
|
|
|
|
if (!isok(tx, ty))
|
|
return 0;
|
|
|
|
mtmp = m_at(tx, ty);
|
|
|
|
if (!mtmp || mtmp->mundetected) {
|
|
if ((otmp = vobj_at(tx, ty)) != 0 && otmp->otyp == STATUE) {
|
|
/* Talking to a statue */
|
|
if (!Blind)
|
|
pline_The("%s seems not to notice you.",
|
|
/* if hallucinating, you can't tell it's a statue */
|
|
Hallucination ? rndmonnam((char *) 0) : "statue");
|
|
return 0;
|
|
}
|
|
if (IS_WALL(levl[tx][ty].typ) || levl[tx][ty].typ == SDOOR) {
|
|
/* Talking to a wall; secret door remains hidden by behaving
|
|
like a wall; IS_WALL() test excludes solid rock even when
|
|
that serves as a wall bordering a corridor */
|
|
if (Blind && !IS_WALL(g.lastseentyp[tx][ty])) {
|
|
/* when blind, you can only talk to a wall if it has
|
|
already been mapped as a wall */
|
|
;
|
|
} else if (!Hallucination) {
|
|
pline("It's like talking to a wall.");
|
|
} else {
|
|
static const char *const walltalk[] = {
|
|
"gripes about its job.",
|
|
"tells you a funny joke!",
|
|
"insults your heritage!",
|
|
"chuckles.",
|
|
"guffaws merrily!",
|
|
"deprecates your exploration efforts.",
|
|
"suggests a stint of rehab...",
|
|
"doesn't seem to be interested.",
|
|
};
|
|
int idx = rn2(10);
|
|
|
|
if (idx >= SIZE(walltalk))
|
|
idx = SIZE(walltalk) - 1;
|
|
pline_The("wall %s", walltalk[idx]);
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!mtmp || mtmp->mundetected
|
|
|| M_AP_TYPE(mtmp) == M_AP_FURNITURE
|
|
|| M_AP_TYPE(mtmp) == M_AP_OBJECT)
|
|
return 0;
|
|
|
|
/* sleeping monsters won't talk, except priests (who wake up) */
|
|
if ((!mtmp->mcanmove || mtmp->msleeping) && !mtmp->ispriest) {
|
|
/* If it is unseen, the player can't tell the difference between
|
|
not noticing him and just not existing, so skip the message. */
|
|
if (canspotmon(mtmp))
|
|
pline("%s seems not to notice you.", Monnam(mtmp));
|
|
return 0;
|
|
}
|
|
|
|
/* if this monster is waiting for something, prod it into action */
|
|
mtmp->mstrategy &= ~STRAT_WAITMASK;
|
|
|
|
if (mtmp->mtame && mtmp->meating) {
|
|
if (!canspotmon(mtmp))
|
|
map_invisible(mtmp->mx, mtmp->my);
|
|
pline("%s is eating noisily.", Monnam(mtmp));
|
|
return 0;
|
|
}
|
|
|
|
return domonnoise(mtmp);
|
|
}
|
|
|
|
/* is there a monster at <x,y> that can see the hero and react? */
|
|
static struct monst *
|
|
responsive_mon_at(x, y)
|
|
int x, y;
|
|
{
|
|
struct monst *mtmp = isok(x, y) ? m_at(x, y) : 0;
|
|
|
|
if (mtmp && (!mtmp->mcanmove || mtmp->msleeping /* immobilized monst */
|
|
|| !mtmp->mcansee || !haseyes(mtmp->data) /* blind monst */
|
|
|| (Invis && !perceives(mtmp->data)) /* unseen hero */
|
|
|| (x != mtmp->mx || y != mtmp->my))) /* worm tail */
|
|
mtmp = (struct monst *) 0;
|
|
return mtmp;
|
|
}
|
|
|
|
/* player chose 'uarmh' for #tip (pickup.c); visual #chat, sort of... */
|
|
int
|
|
tiphat()
|
|
{
|
|
struct monst *mtmp;
|
|
struct obj *otmp;
|
|
int x, y, range, glyph, vismon, unseen, statue, res;
|
|
|
|
if (!uarmh) /* can't get here from there */
|
|
return 0;
|
|
|
|
res = uarmh->bknown ? 0 : 1;
|
|
if (cursed(uarmh)) /* "You can't. It is cursed." */
|
|
return res; /* if learned of curse, use a move */
|
|
|
|
/* might choose a position, but dealing with direct lines is simpler */
|
|
if (!getdir("At whom? (in what direction)")) /* bail on ESC */
|
|
return res; /* iffy; now know it's not cursed for sure (since we got
|
|
* past prior test) but might have already known that */
|
|
res = 1; /* physical action is going to take place */
|
|
|
|
/* most helmets have a short wear/take-off delay and we could set
|
|
'multi' to account for that, but we'll pretend that no extra time
|
|
beyond the current move is necessary */
|
|
You("briefly doff your %s.", helm_simple_name(uarmh));
|
|
|
|
if (!u.dx && !u.dy) {
|
|
if (u.usteed && u.dz > 0) {
|
|
if (!u.usteed->mcanmove || u.usteed->msleeping)
|
|
pline("%s doesn't notice.", Monnam(u.usteed));
|
|
else
|
|
(void) domonnoise(u.usteed);
|
|
} else if (u.dz) {
|
|
pline("There's no one %s there.", (u.dz < 0) ? "up" : "down");
|
|
} else {
|
|
pline_The("lout here doesn't acknowledge you...");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
mtmp = (struct monst *) 0;
|
|
vismon = unseen = statue = 0, glyph = GLYPH_MON_OFF;
|
|
x = u.ux, y = u.uy;
|
|
for (range = 1; range <= BOLT_LIM + 1; ++range) {
|
|
x += u.dx, y += u.dy;
|
|
if (!isok(x, y) || (range > 1 && !couldsee(x, y))) {
|
|
/* switch back to coordinates for previous interation's 'mtmp' */
|
|
x -= u.dx, y -= u.dy;
|
|
break;
|
|
}
|
|
mtmp = m_at(x, y);
|
|
vismon = (mtmp && canseemon(mtmp));
|
|
glyph = glyph_at(x, y);
|
|
unseen = glyph_is_invisible(glyph);
|
|
statue = (glyph_is_statue(glyph) /* mimic or hallucinatory statue */
|
|
|| (!vismon && !unseen && (otmp = vobj_at(x, y)) != 0
|
|
&& otmp->otyp == STATUE)); /* or actual statue */
|
|
if (vismon && (M_AP_TYPE(mtmp) == M_AP_FURNITURE
|
|
|| M_AP_TYPE(mtmp) == M_AP_OBJECT))
|
|
vismon = 0, mtmp = (struct monst *) 0;
|
|
if (vismon || unseen || (statue && Hallucination)
|
|
/* unseen adjacent monster will respond if able */
|
|
|| (range == 1 && mtmp && responsive_mon_at(x, y)
|
|
&& !is_silent(mtmp->data))
|
|
/* we check accessible() after m_at() in case there's a
|
|
visible monster phazing through a wall here */
|
|
|| !(accessible(x, y) || levl[x][y].typ == IRONBARS))
|
|
break;
|
|
}
|
|
|
|
if (unseen || (statue && Hallucination)) {
|
|
pline("That %screature is ignoring you!", unseen ? "unseen " : "");
|
|
} else if (!mtmp || !responsive_mon_at(x, y)) {
|
|
if (vismon) /* 'vismon' is only True when 'mtmp' is non-Null */
|
|
pline("%s seems not to notice you.", Monnam(mtmp));
|
|
else
|
|
goto nada;
|
|
} else { /* 'mtmp' is guaranteed to be non-Null if we get here */
|
|
/* if this monster is waiting for something, prod it into action */
|
|
mtmp->mstrategy &= ~STRAT_WAITMASK;
|
|
|
|
if (vismon && humanoid(mtmp->data) && mtmp->mpeaceful && !Conflict) {
|
|
if ((otmp = which_armor(mtmp, W_ARMH)) == 0) {
|
|
pline("%s waves.", Monnam(mtmp));
|
|
} else if (otmp->cursed) {
|
|
pline("%s grasps %s %s but can't remove it.", Monnam(mtmp),
|
|
mhis(mtmp), helm_simple_name(otmp));
|
|
otmp->bknown = 1;
|
|
} else {
|
|
pline("%s tips %s %s in response.", Monnam(mtmp),
|
|
mhis(mtmp), helm_simple_name(otmp));
|
|
}
|
|
} else if (vismon && humanoid(mtmp->data)) {
|
|
static const char *const reaction[3] = {
|
|
"curses", "gestures rudely", "gestures offensively",
|
|
};
|
|
int which = !Deaf ? rn2(3) : rn1(2, 1),
|
|
twice = (Deaf || which > 0 || rn2(3)) ? 0 : rn1(2, 1);
|
|
|
|
pline("%s %s%s%s at you...", Monnam(mtmp), reaction[which],
|
|
twice ? " and " : "", twice ? reaction[twice] : "");
|
|
} else if (distu(x, y) <= 2 && !Deaf && domonnoise(mtmp)) {
|
|
if (!vismon)
|
|
map_invisible(x, y);
|
|
} else if (vismon) {
|
|
pline("%s doesn't respond.", Monnam(mtmp));
|
|
} else {
|
|
nada:
|
|
pline("%s", nothing_happens);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
#ifdef USER_SOUNDS
|
|
|
|
#if defined(WIN32) || defined(QT_GRAPHICS)
|
|
extern void FDECL(play_usersound, (const char *, int));
|
|
#endif
|
|
#if defined(TTY_SOUND_ESCCODES)
|
|
extern void FDECL(play_usersound_via_idx, (int, int));
|
|
#endif
|
|
|
|
typedef struct audio_mapping_rec {
|
|
struct nhregex *regex;
|
|
char *filename;
|
|
int volume;
|
|
int idx;
|
|
struct audio_mapping_rec *next;
|
|
} audio_mapping;
|
|
|
|
static audio_mapping *soundmap = 0;
|
|
static audio_mapping *FDECL(sound_matches_message, (const char *));
|
|
|
|
char *sounddir = 0; /* set in files.c */
|
|
|
|
/* adds a sound file mapping, returns 0 on failure, 1 on success */
|
|
int
|
|
add_sound_mapping(mapping)
|
|
const char *mapping;
|
|
{
|
|
char text[256];
|
|
char filename[256];
|
|
char filespec[256];
|
|
int volume, idx = -1;
|
|
|
|
if (sscanf(mapping, "MESG \"%255[^\"]\"%*[\t ]\"%255[^\"]\" %d %d", text,
|
|
filename, &volume, &idx) == 4
|
|
|| sscanf(mapping, "MESG \"%255[^\"]\"%*[\t ]\"%255[^\"]\" %d", text,
|
|
filename, &volume) == 3) {
|
|
audio_mapping *new_map;
|
|
|
|
if (!sounddir)
|
|
sounddir = dupstr(".");
|
|
if (strlen(sounddir) + 1 + strlen(filename) >= sizeof filespec) {
|
|
raw_print("sound file name too long");
|
|
return 0;
|
|
}
|
|
Sprintf(filespec, "%s/%s", sounddir, filename);
|
|
|
|
if (idx >= 0 || can_read_file(filespec)) {
|
|
new_map = (audio_mapping *) alloc(sizeof *new_map);
|
|
new_map->regex = regex_init();
|
|
new_map->filename = dupstr(filespec);
|
|
new_map->volume = volume;
|
|
new_map->idx = idx;
|
|
new_map->next = soundmap;
|
|
|
|
if (!regex_compile(text, new_map->regex)) {
|
|
const char *re_error_desc = regex_error_desc(new_map->regex);
|
|
|
|
regex_free(new_map->regex);
|
|
free((genericptr_t) new_map->filename);
|
|
free((genericptr_t) new_map);
|
|
raw_print(re_error_desc);
|
|
return 0;
|
|
} else {
|
|
soundmap = new_map;
|
|
}
|
|
} else {
|
|
Sprintf(text, "cannot read %.243s", filespec);
|
|
raw_print(text);
|
|
return 0;
|
|
}
|
|
} else {
|
|
raw_print("syntax error in SOUND");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static audio_mapping *
|
|
sound_matches_message(msg)
|
|
const char *msg;
|
|
{
|
|
audio_mapping *snd = soundmap;
|
|
|
|
while (snd) {
|
|
if (regex_match(msg, snd->regex))
|
|
return snd;
|
|
snd = snd->next;
|
|
}
|
|
return (audio_mapping *) 0;
|
|
}
|
|
|
|
void
|
|
play_sound_for_message(msg)
|
|
const char *msg;
|
|
{
|
|
audio_mapping *snd = sound_matches_message(msg);
|
|
|
|
if (snd)
|
|
play_usersound(snd->filename, snd->volume);
|
|
}
|
|
|
|
void
|
|
maybe_play_sound(msg)
|
|
const char *msg;
|
|
{
|
|
#if defined(WIN32) || defined(QT_GRAPHICS) || defined(TTY_SOUND_ESCCODES)
|
|
audio_mapping *snd = sound_matches_message(msg);
|
|
|
|
if (snd
|
|
#if defined(WIN32) || defined(QT_GRAPHICS)
|
|
#ifdef TTY_SOUND_ESCCODES
|
|
&& !iflags.vt_sounddata
|
|
#endif
|
|
#if defined(QT_GRAPHICS)
|
|
&& WINDOWPORT("Qt")
|
|
#endif
|
|
#if defined(WIN32)
|
|
&& (WINDOWPORT("tty") || WINDOWPORT("mswin") || WINDOWPORT("curses"))
|
|
#endif
|
|
#endif /* WIN32 || QT_GRAPHICS */
|
|
)
|
|
play_usersound(snd->filename, snd->volume);
|
|
#if defined(TTY_GRAPHICS) && defined(TTY_SOUND_ESCCODES)
|
|
else if (snd && iflags.vt_sounddata && snd->idx >= 0 && WINDOWPORT("tty"))
|
|
play_usersound_via_idx(snd->idx, snd->volume);
|
|
#endif /* TTY_GRAPHICS && TTY_SOUND_ESCCODES */
|
|
#endif /* WIN32 || QT_GRAPHICS || TTY_SOUND_ESCCODES */
|
|
}
|
|
|
|
void
|
|
release_sound_mappings()
|
|
{
|
|
audio_mapping *nextsound = 0;
|
|
|
|
while (soundmap) {
|
|
nextsound = soundmap->next;
|
|
regex_free(soundmap->regex);
|
|
free((genericptr_t) soundmap->filename);
|
|
free((genericptr_t) soundmap);
|
|
soundmap = nextsound;
|
|
}
|
|
|
|
if (sounddir)
|
|
free((genericptr_t) sounddir), sounddir = 0;
|
|
}
|
|
|
|
#endif /* USER_SOUNDS */
|
|
|
|
/*sounds.c*/
|