/**
* \file speech.c
*
* \brief Speech-related commands in PennMUSH.
*
*
*/
/* speech.c */
#include "copyrite.h"
#include "config.h"
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "conf.h"
#include "ansi.h"
#include "externs.h"
#include "mushdb.h"
#include "dbdefs.h"
#include "lock.h"
#include "flags.h"
#include "log.h"
#include "match.h"
#include "attrib.h"
#include "parse.h"
#include "game.h"
#include "pcre.h"
#include "confmagic.h"
int okay_pemit(dbref player, dbref target);
static dbref speech_loc(dbref thing);
void propagate_sound(dbref thing, const char *msg);
static void do_audible_stuff(dbref loc, dbref *excs, int numexcs,
const char *msg);
static void do_one_remit(dbref player, const char *target, const char *msg,
int flags);
dbref na_zemit(dbref current, void *data);
const char *
spname(dbref thing)
{
/* if FULL_INVIS is defined, dark wizards and dark objects will be
* Someone and Something, respectively.
*/
if (FULL_INVIS && DarkLegal(thing)) {
if (IsPlayer(thing))
return "Someone";
else
return "Something";
} else {
return accented_name(thing);
}
}
/** Can player pemit to target?
* You can pemit if you're pemit_all, if you're pemitting to yourself,
* if you're pemitting to a non-player, or if you pass target's
* pagelock and target isn't HAVEN.
* \param player dbref attempting to pemit.
* \param target target dbref to pemit to.
* \retval 1 player may pemit to target.
* \retval 0 player may not pemit to target.
*/
int
okay_pemit(dbref player, dbref target)
{
if (Pemit_All(player))
return 1;
if (IsPlayer(target) && Haven(target))
return 0;
if (!eval_lock(player, target, Page_Lock)) {
fail_lock(player, target, Page_Lock, NULL, NOTHING);
return 0;
}
return 1;
}
static dbref
speech_loc(dbref thing)
{
/* This is the place where speech, poses, and @emits by thing should be
* heard. For things and players, it's the loc; For rooms, it's the room
* itself; for exits, it's the source. */
if (!GoodObject(thing))
return NOTHING;
switch (Typeof(thing)) {
case TYPE_ROOM:
return thing;
case TYPE_EXIT:
return Home(thing);
default:
return Location(thing);
}
}
/** The teach command.
* \param player the enactor.
* \param cause the object causing the command to run.
* \param tbuf1 the command being taught.
*/
void
do_teach(dbref player, dbref cause, const char *tbuf1)
{
dbref loc;
static int recurse = 0;
char *command;
loc = speech_loc(player);
if (!GoodObject(loc))
return;
if (!eval_lock(player, loc, Speech_Lock)) {
fail_lock(player, loc, Speech_Lock, T("You may not speak here!"), NOTHING);
return;
}
if (recurse) {
/* Somebody tried to teach the teach command. Cute. Dumb. */
notify(player, T("You can't teach 'teach', sorry."));
recurse = 0;
return;
}
if (!tbuf1 || !*tbuf1) {
notify(player, T("What command do you want to teach?"));
return;
}
recurse = 1; /* Protect use from recursive teach */
notify_except(Contents(loc), NOTHING,
tprintf(T("%s types --> %s%s%s"), spname(player),
ANSI_HILITE, tbuf1, ANSI_NORMAL), NA_INTER_HEAR);
command = mush_strdup(tbuf1, "string"); /* process_command is destructive */
process_command(player, command, cause, 1);
mush_free(command, "string");
recurse = 0; /* Ok, we can be called again safely */
}
/** The say command.
* \param player the enactor.
* \param tbuf1 the message to say.
*/
void
do_say(dbref player, const char *tbuf1)
{
dbref loc;
loc = speech_loc(player);
if (!GoodObject(loc))
return;
if (!eval_lock(player, loc, Speech_Lock)) {
fail_lock(player, loc, Speech_Lock, T("You may not speak here!"), NOTHING);
return;
}
if (*tbuf1 == SAY_TOKEN && CHAT_STRIP_QUOTE)
tbuf1++;
/* notify everybody */
notify_format(player, T("You say, \"%s\""), tbuf1);
notify_except(Contents(loc), player,
tprintf(T("%s says, \"%s\""), spname(player), tbuf1),
NA_INTER_HEAR);
}
/** A comparator for raw dbrefs.
* This exists because the i_comp function in funlist.c got hairy some time ago.
* \param a the first element to compare.
* \param b the second element to compare.
*/
static int
dbref_comp(const void *a, const void *b)
{
const dbref *x = (const dbref *) a;
const dbref *y = (const dbref *) b;
return (int) *x - (int) *y;
}
/** The oemit(/list) command.
* \verbatim
* This implements @oemit and @oemit/list.
* \endverbatim
* \param player the enactor.
* \param list the list of dbrefs to oemit from the emit.
* \param message the message to emit.
* \param flags PEMIT_* flags.
*/
void
do_oemit_list(dbref player, char *list, const char *message, int flags)
{
char *temp, *p, *s;
dbref who;
dbref pass[12], locs[10];
int i, oneloc = 0;
int na_flags = NA_INTER_HEAR;
/* If no message, further processing is pointless.
* If no list, they should have used @remit. */
if (!message || !*message || !list || !*list)
return;
orator = player;
pass[0] = 0;
/* Find out what room to do this in. If they supplied a db# before
* the '/', then oemit to anyone in the room who's not on list.
* Otherwise, oemit to every location which has at least one of the
* people in the list. This is intended for actions which involve
* players who are in different rooms, e.g.:
*
* X (in #0) fires an arrow at Y (in #2).
*
* X sees: You fire an arrow at Y. (pemit to X)
* Y sees: X fires an arrow at you! (pemit to Y)
* #0 sees: X fires an arrow at Y. (oemit/list to X Y)
* #2 sees: X fires an arrow at Y. (from the same oemit)
*/
/* Find out what room to do this in. They should have supplied a db#
* before the '/'. */
if ((temp = strchr(list, '/'))) {
*temp++ = '\0';
pass[1] = noisy_match_result(player, list, NOTYPE, MAT_EVERYTHING);
if (!GoodObject(pass[1])) {
notify(player, T("I can't find that room."));
return;
}
if (!eval_lock(player, pass[1], Speech_Lock)) {
fail_lock(player, pass[1], Speech_Lock, T("You may not speak there!"),
NOTHING);
return;
}
oneloc = 1; /* we are only oemitting to one location */
} else {
temp = list;
}
s = trim_space_sep(temp, ' ');
while (s) {
p = split_token(&s, ' ');
/* If a room was given, we match relative to the room */
if (oneloc)
who = match_result(pass[1], p, NOTYPE, MAT_POSSESSION | MAT_ABSOLUTE);
else
who = noisy_match_result(player, p, NOTYPE, MAT_OBJECTS);
/* pass[0] tracks the number of valid players we've found.
* pass[1] is the given room (possibly nothing right now)
* pass[2..12] are dbrefs of players
* locs[0..10] are corresponding dbrefs of locations
*/
if (GoodObject(who) && GoodObject(Location(who))
&& eval_lock(player, Location(who), Speech_Lock)
) {
if (pass[0] < 10) {
locs[pass[0]] = Location(who);
pass[pass[0] + 2] = who;
pass[0]++;
} else {
notify(player, T("Too many people to oemit to."));
break;
}
}
}
/* Sort the list of rooms to oemit to so we don't oemit to the same
* room twice */
qsort((void *) locs, pass[0], sizeof(locs[0]), dbref_comp);
if (flags & PEMIT_SPOOF)
na_flags |= NA_SPOOF;
for (i = 0; i < pass[0]; i++) {
if (i != 0 && locs[i] == locs[i - 1])
continue;
pass[1] = locs[i];
notify_anything_loc(orator, na_exceptN, pass, ns_esnotify, na_flags,
message, locs[i]);
do_audible_stuff(pass[1], &pass[2], pass[0], message);
}
}
/** The whisper command.
* \param player the enactor.
* \param arg1 name of the object to whisper to.
* \param arg2 message to whisper.
* \param noisy if 1, others overhear that a whisper has occurred.
*/
void
do_whisper(dbref player, const char *arg1, const char *arg2, int noisy)
{
dbref who;
int key;
const char *gap;
char *tbuf, *tp;
char *p;
dbref good[100];
int gcount = 0;
const char *head;
int overheard;
char *current;
const char **start;
if (!arg1 || !*arg1) {
notify(player, T("Whisper to whom?"));
return;
}
if (!arg2 || !*arg2) {
notify(player, T("Whisper what?"));
return;
}
tp = tbuf = (char *) mush_malloc(BUFFER_LEN, "string");
if (!tbuf)
mush_panic("Unable to allocate memory in do_whisper_list");
overheard = 0;
head = arg1;
start = &head;
/* Figure out what kind of message */
gap = " ";
switch (*arg2) {
case SEMI_POSE_TOKEN:
gap = "";
case POSE_TOKEN:
key = 1;
arg2++;
break;
default:
key = 2;
break;
}
*tp = '\0';
/* Make up a list of good and bad names */
while (head && *head) {
current = next_in_list(start);
who = match_result(player, current, TYPE_PLAYER, MAT_NEAR_THINGS |
MAT_CONTAINER);
if (!GoodObject(who) || !can_interact(player, who, INTERACT_HEAR)) {
safe_chr(' ', tbuf, &tp);
safe_str_space(current, tbuf, &tp);
if (GoodObject(who))
notify_format(player, T("%s can't hear you."), Name(who));
} else {
/* A good whisper */
good[gcount++] = who;
if (gcount >= 100) {
notify(player, T("Too many people to whisper to."));
break;
}
}
}
*tp = '\0';
if (*tbuf)
notify_format(player, T("Unable to whisper to:%s"), tbuf);
if (!gcount) {
mush_free((Malloc_t) tbuf, "string");
return;
}
/* Drunk wizards... */
if (Dark(player))
noisy = 0;
/* Set up list of good names */
tp = tbuf;
safe_str(" to ", tbuf, &tp);
for (who = 0; who < gcount; who++) {
if (noisy && (get_random_long(0, 100) < WHISPER_LOUDNESS))
overheard = 1;
safe_itemizer(who + 1, (who == gcount - 1), ",", T("and"), " ", tbuf, &tp);
safe_str(Name(good[who]), tbuf, &tp);
}
*tp = '\0';
if (key == 1) {
notify_format(player, (gcount > 1) ? T("%s sense: %s%s%s") :
T("%s senses: %s%s%s"), tbuf + 4, Name(player), gap, arg2);
p = tprintf("You sense: %s%s%s", Name(player), gap, arg2);
} else {
notify_format(player, T("You whisper, \"%s\"%s."), arg2, tbuf);
p = tprintf("%s whispers%s: %s", Name(player),
gcount > 1 ? tbuf : "", arg2);
}
for (who = 0; who < gcount; who++) {
notify_must_puppet(good[who], p);
if (Location(good[who]) != Location(player))
overheard = 0;
}
if (overheard) {
dbref first = Contents(Location(player));
if (!GoodObject(first))
return;
p = tprintf("%s whispers%s.", Name(player), tbuf);
DOLIST(first, first) {
overheard = 1;
for (who = 0; who < gcount; who++) {
if ((first == player) || (first == good[who])) {
overheard = 0;
break;
}
}
if (overheard)
notify_noecho(first, p);
}
}
mush_free((Malloc_t) tbuf, "string");
}
/** Send a message to a list of dbrefs. To avoid repeated generation
* of the NOSPOOF string, we set it up the first time we encounter
* something Nospoof, and then check for it thereafter.
* The list is destructively modified.
* \param player the enactor.
* \param list the list of players to pemit to, destructively modified.
* \param message the message to pemit.
* \param flags PEMIT_* flags
*/
void
do_pemit_list(dbref player, char *list, const char *message, int flags)
{
char *bp, *p;
char *nsbuf, *nspbuf;
char *l;
dbref who;
int nospoof;
/* If no list or no message, further processing is pointless. */
if (!message || !*message || !list || !*list)
return;
nspbuf = nsbuf = NULL;
nospoof = (flags & PEMIT_SPOOF) ? 0 : 1;
list[BUFFER_LEN - 1] = '\0';
l = trim_space_sep(list, ' ');
while ((p = split_token(&l, ' '))) {
who = noisy_match_result(player, p, NOTYPE, MAT_ABSOLUTE);
if (GoodObject(who) && okay_pemit(player, who)) {
if (nospoof && Nospoof(who)) {
if (Paranoid(who)) {
if (!nspbuf) {
bp = nspbuf = mush_malloc(BUFFER_LEN, "string");
if (player == Owner(player))
safe_format(nspbuf, &bp, "[%s(#%d)->] %s", Name(player),
player, message);
else
safe_format(nspbuf, &bp, "[%s(#%d)'s %s(#%d)->] %s",
Name(Owner(player)), Owner(player),
Name(player), player, message);
*bp = '\0';
}
notify(who, nspbuf);
} else {
if (!nsbuf) {
bp = nsbuf = mush_malloc(BUFFER_LEN, "string");
safe_format(nsbuf, &bp, "[%s->] %s", Name(player), message);
*bp = '\0';
}
notify(who, nsbuf);
}
} else {
notify_must_puppet(who, message);
}
}
}
if (nsbuf)
mush_free(nsbuf, "string");
if (nspbuf)
mush_free(nspbuf, "string");
}
/** Send a message to an object.
* \param player the enactor.
* \param arg1 the name of the object to pemit to.
* \param arg2 the message to pemit.
* \param flags PEMIT_* flags.
*/
void
do_pemit(dbref player, const char *arg1, const char *arg2, int flags)
{
dbref who;
int silent, nospoof;
silent = (flags & PEMIT_SILENT) ? 1 : 0;
nospoof = (flags & PEMIT_SPOOF) ? 0 : 1;
switch (who = match_result(player, arg1, NOTYPE,
MAT_OBJECTS | MAT_HERE | MAT_CONTAINER)) {
case NOTHING:
notify(player, T("I don't see that here."));
break;
case AMBIGUOUS:
notify(player, T("I don't know who you mean!"));
break;
default:
if (!okay_pemit(player, who)) {
notify_format(player,
T("I'm sorry, but %s wishes to be left alone now."),
Name(who));
return;
}
if (!silent)
notify_format(player, T("You pemit \"%s\" to %s."), arg2, Name(who));
if (nospoof && Nospoof(who)) {
if (Paranoid(who)) {
if (player == Owner(player))
notify_format(who, "[%s(#%d)->%s] %s", Name(player), player,
Name(who), arg2);
else
notify_format(who, "[%s(#%d)'s %s(#%d)->%s] %s",
Name(Owner(player)), Owner(player),
Name(player), player, Name(who), arg2);
} else
notify_format(who, "[%s->%s] %s", Name(player), Name(who), arg2);
} else {
notify_must_puppet(who, arg2);
}
break;
}
}
/** The pose and semipose command.
* \param player the enactor.
* \param tbuf1 the message to pose.
* \param space if 1, put a space between name and pose; if 0, don't (semipose)
*/
void
do_pose(dbref player, const char *tbuf1, int space)
{
dbref loc;
loc = speech_loc(player);
if (!GoodObject(loc))
return;
if (!eval_lock(player, loc, Speech_Lock)) {
fail_lock(player, loc, Speech_Lock, T("You may not speak here!"), NOTHING);
return;
}
/* notify everybody */
if (!space)
notify_except(Contents(loc), NOTHING,
tprintf("%s %s", spname(player), tbuf1), NA_INTER_HEAR);
else
notify_except(Contents(loc), NOTHING,
tprintf("%s%s", spname(player), tbuf1), NA_INTER_HEAR);
}
/** The *wall commands.
* \param player the enactor.
* \param message message to broadcast.
* \param target type of broadcast (all, royalty, wizard)
* \param emit if 1, this is a wallemit.
*/
void
do_wall(dbref player, const char *message, enum wall_type target, int emit)
{
const char *gap = "", *prefix;
const char *mask;
int pose = 0;
/* Only @wall is available to those with the announce power.
* Only @rwall is available to royalty.
*/
if (!(Wizard(player) ||
((target == WALL_ALL) && Can_Announce(player)) ||
((target == WALL_RW) && Royalty(player)))) {
notify(player, T("Posing as a wizard could be hazardous to your health."));
return;
}
/* put together the message and figure out what type it is */
if (!emit) {
gap = " ";
switch (*message) {
case SAY_TOKEN:
if (CHAT_STRIP_QUOTE)
message++;
break;
case SEMI_POSE_TOKEN:
gap = "";
case POSE_TOKEN:
pose = 1;
message++;
break;
}
}
if (!*message) {
notify(player, T("What did you want to say?"));
return;
}
if (target == WALL_WIZ) {
/* to wizards only */
mask = "WIZARD";
prefix = WIZWALL_PREFIX;
} else if (target == WALL_RW) {
/* to wizards and royalty */
mask = "WIZARD ROYALTY";
prefix = RWALL_PREFIX;
} else {
/* to everyone */
mask = NULL;
prefix = WALL_PREFIX;
}
/* broadcast the message */
if (pose)
flag_broadcast(mask, 0, "%s %s%s%s", prefix, Name(player), gap, message);
else if (emit)
flag_broadcast(mask, 0, "%s [%s]: %s", prefix, Name(player), message);
else
flag_broadcast(mask, 0,
"%s %s %s, \"%s\"", prefix, Name(player),
target == WALL_ALL ? "shouts" : "says", message);
}
/** The page command.
* \param player the enactor.
* \param arg1 the list of players to page.
* \param arg2 the message to page.
* \param cause the object that caused the command to run.
* \param noeval if 1, page/noeval.
* \param multipage if 1, a page/list; if 0, a page/blind.
* \param override if 1, page/override.
*/
void
do_page(dbref player, const char *arg1, const char *arg2, dbref cause,
int noeval, int multipage, int override)
{
dbref target;
const char *message;
const char *gap;
int key;
char *tbuf, *tp;
char *tbuf2, *tp2;
dbref good[100];
int gcount = 0;
char *msgbuf, *mb;
const char *head;
const char *hp = NULL;
const char **start;
char *current;
int i;
int repage = 0;
int fails_lock;
int is_haven;
ATTR *a;
tp2 = tbuf2 = (char *) mush_malloc(BUFFER_LEN, "string");
if (!tbuf2)
mush_panic("Unable to allocate memory in do_page");
if (arg2 && *arg2 && *arg1) {
/* page to=msg. Always evaluate to, maybe evaluate msg */
process_expression(tbuf2, &tp2, &arg1, player, cause, cause,
PE_DEFAULT, PT_DEFAULT, NULL);
*tp2 = '\0';
head = tbuf2;
message = arg2;
} else if (arg2 && *arg2) {
/* page =msg */
message = arg2;
repage = 1;
} else {
/* page msg */
message = arg1;
repage = 1;
}
if (repage) {
a = atr_get_noparent(player, "LASTPAGED");
if (!a || !*((hp = head = safe_atr_value(a)))) {
notify(player, T("You haven't paged anyone since connecting."));
mush_free((Malloc_t) tbuf2, "string");
return;
}
if (!message || !*message) {
notify_format(player, T("You last paged %s."), head);
mush_free((Malloc_t) tbuf2, "string");
if (hp)
free((Malloc_t) hp);
return;
}
}
tp = tbuf = (char *) mush_malloc(BUFFER_LEN, "string");
if (!tbuf)
mush_panic("Unable to allocate memory in do_page");
if (override && !Pemit_All(player)) {
notify(player, "Try again after you get the pemit_all power.");
override = 0;
}
start = &head;
while (head && *head && (gcount < 99)) {
current = next_in_list(start);
target = lookup_player(current);
if (!GoodObject(target))
target = short_page(current);
if (target == NOTHING) {
notify_format(player,
T("I can't find who you're trying to page with: %s"),
current);
safe_chr(' ', tbuf, &tp);
safe_str_space(current, tbuf, &tp);
} else if (target == AMBIGUOUS) {
notify_format(player,
T("I'm not sure who you want to page with: %s"), current);
safe_chr(' ', tbuf, &tp);
safe_str_space(current, tbuf, &tp);
} else {
fails_lock = !(override || eval_lock(player, target, Page_Lock));
is_haven = !override && Haven(target);
if (!Connected(target) || (Dark(target) && (is_haven || fails_lock))) {
/* A player isn't connected if they aren't connected, or if
* they're DARK and HAVEN, or DARK and the pagelock fails. */
page_return(player, target, "Away", "AWAY",
tprintf(T("%s is not connected."), Name(target)));
if (fails_lock)
fail_lock(player, target, Page_Lock, NULL, NOTHING);
safe_chr(' ', tbuf, &tp);
safe_str_space(current, tbuf, &tp);
} else if (is_haven) {
page_return(player, target, "Haven", "HAVEN",
tprintf(T("%s is not accepting any pages."), Name(target)));
safe_chr(' ', tbuf, &tp);
safe_str_space(Name(target), tbuf, &tp);
} else if (fails_lock) {
page_return(player, target, "Haven", "HAVEN",
tprintf(T("%s is not accepting your pages."),
Name(target)));
fail_lock(player, target, Page_Lock, NULL, NOTHING);
safe_chr(' ', tbuf, &tp);
safe_str_space(Name(target), tbuf, &tp);
} else {
/* This is a good page */
good[gcount] = target;
gcount++;
}
}
}
/* Reset tbuf2 to use later */
tp2 = tbuf2;
/* We now have an array of good[] dbrefs, a gcount of the good ones,
* and a tbuf with bad ones.
*/
/* We don't know what the heck's going on here, but we're not paging
* anyone, this looks like a spam attack. */
if (gcount == 99) {
notify(player, T("You're trying to page too many people at once."));
mush_free((Malloc_t) tbuf, "string");
mush_free((Malloc_t) tbuf2, "string");
if (hp)
free((Malloc_t) hp);
return;
}
/* We used to stick 'Unable to page' on at the start, but this is
* faster for the 90% of the cases where there isn't a bad name
* That may sound high, but, remember, we (almost) never have a bad
* name if we're repaging, which is probably 75% of all pages */
*tp = '\0';
if (*tbuf)
notify_format(player, T("Unable to page:%s"), tbuf);
if (!gcount) {
/* Well, that was a total waste of time. */
mush_free((Malloc_t) tbuf, "string");
mush_free((Malloc_t) tbuf2, "string");
if (hp)
free((Malloc_t) hp);
return;
}
/* Can the player afford to pay for this thing? */
if (!payfor(player, PAGE_COST * gcount)) {
notify_format(player, T("You don't have enough %s."), MONIES);
mush_free((Malloc_t) tbuf, "string");
mush_free((Malloc_t) tbuf2, "string");
if (hp)
free((Malloc_t) hp);
return;
}
/* Okay, we have a real page, the player can pay for it, and it's
* actually going to someone. We're in this for keeps now. */
/* Evaluate the message if we need to. */
if (noeval)
msgbuf = NULL;
else {
mb = msgbuf = (char *) mush_malloc(BUFFER_LEN, "string");
if (!msgbuf)
mush_panic("Unable to allocate memory in do_page");
process_expression(msgbuf, &mb, &message, player, cause, cause,
PE_DEFAULT, PT_DEFAULT, NULL);
*mb = '\0';
message = msgbuf;
}
if (Haven(player))
notify(player, T("You are set HAVEN and cannot receive pages."));
/* Figure out what kind of message */
global_eval_context.wenv[0] = (char *) message;
gap = " ";
switch (*message) {
case SEMI_POSE_TOKEN:
gap = "";
case POSE_TOKEN:
key = 1;
message++;
break;
default:
key = 3;
break;
}
/* Reset tbuf and tbuf2 to use later */
tp = tbuf;
tp2 = tbuf2;
/* tbuf2 is used to hold a fancy formatted list of names,
* with commas and the word 'and' , if needed. */
/* tbuf holds a space-separated list of names for repaging */
/* Set up a pretty formatted list. */
for (i = 0; i < gcount; i++) {
if (i)
safe_chr(' ', tbuf, &tp);
safe_str_space(Name(good[i]), tbuf, &tp);
safe_itemizer(i + 1, (i == gcount - 1), ",", T("and"), " ", tbuf2, &tp2);
safe_str(Name(good[i]), tbuf2, &tp2);
}
*tp = '\0';
*tp2 = '\0';
(void) atr_add(player, "LASTPAGED", tbuf, GOD, NOTHING);
/* Reset tbuf to use later */
tp = tbuf;
/* Figure out the one success message, and send it */
if (key == 1)
notify_format(player, T("Long distance to %s%s: %s%s%s"),
((gcount > 1) && (!multipage)) ? "(blind) " : "", tbuf2,
Name(player), gap, message);
else
notify_format(player, T("You paged %s%s with '%s'"),
((gcount > 1) && (!multipage)) ? "(blind) " : "", tbuf2,
message);
/* Figure out the 'name' of the player */
if (PAGE_ALIASES && (a = atr_get_noparent(player, "ALIAS")))
current = tprintf("%s (%s)", Name(player), atr_value(a));
else
current = (char *) Name(player);
/* Now, build the thing we want to send to the pagees,
* and put it in tbuf */
/* Build the header */
if (key == 1) {
safe_str(T("From afar"), tbuf, &tp);
if ((gcount > 1) && (multipage)) {
safe_str(T(" (to "), tbuf, &tp);
safe_str(tbuf2, tbuf, &tp);
safe_chr(')', tbuf, &tp);
}
safe_str(", ", tbuf, &tp);
safe_str(current, tbuf, &tp);
safe_str(gap, tbuf, &tp);
} else {
safe_str(current, tbuf, &tp);
safe_str(T(" pages"), tbuf, &tp);
if ((gcount > 1) && (multipage)) {
safe_chr(' ', tbuf, &tp);
safe_str(tbuf2, tbuf, &tp);
}
safe_str(": ", tbuf, &tp);
}
/* Tack on the message */
safe_str(message, tbuf, &tp);
*tp = '\0';
/* Tell each page recipient with tbuf */
for (i = 0; i < gcount; i++) {
if (!IsPlayer(player) && Nospoof(good[i]))
notify_format(good[i], "[#%d] %s", player, tbuf);
else
notify(good[i], tbuf);
page_return(player, good[i], "Idle", "IDLE", NULL);
}
mush_free((Malloc_t) tbuf, "string");
mush_free((Malloc_t) tbuf2, "string");
if (msgbuf)
mush_free((Malloc_t) msgbuf, "string");
if (hp)
free((Malloc_t) hp);
}
/** Does a message match a filter pattern on an object?
* \param thing object with the filter.
* \param msg message to match.
* \param flag if 0, filter; if 1, infilter.
* \retval 1 message matches filter.
* \retval 0 message does not match filter.
*/
int
filter_found(dbref thing, const char *msg, int flag)
{
char *filter;
ATTR *a;
char *p, *bp;
char *temp; /* need this so we don't leak memory
* by failing to free the storage
* allocated by safe_uncompress
*/
int i;
int matched = 0;
if (!flag)
a = atr_get(thing, "FILTER");
else
a = atr_get(thing, "INFILTER");
if (!a)
return matched;
filter = safe_atr_value(a);
temp = filter;
for (i = 0; (i < MAX_ARG) && !matched; i++) {
p = bp = filter;
process_expression(p, &bp, (char const **) &filter, 0, 0, 0,
PE_NOTHING, PT_COMMA, NULL);
if (*filter == ',')
*filter++ = '\0';
if (*p == '\0' && *filter == '\0') /* No more filters */
break;
if (*p == '\0') /* Empty filter */
continue;
if (AF_Regexp(a))
matched = quick_regexp_match(p, msg, AF_Case(a));
else
matched = local_wild_match_case(p, msg, AF_Case(a));
}
free((Malloc_t) temp);
return matched;
}
/** Copy a message into a buffer, adding an object's PREFIX attribute.
* \param thing object with prefix attribute.
* \param msg message.
* \param tbuf1 destination buffer.
*/
void
make_prefixstr(dbref thing, const char *msg, char *tbuf1)
{
char *bp, *asave;
char const *ap;
char *wsave[10], *preserve[NUMQ];
ATTR *a;
int j;
a = atr_get(thing, "PREFIX");
bp = tbuf1;
if (!a) {
safe_str("From ", tbuf1, &bp);
safe_str(Name(IsExit(thing) ? Source(thing) : thing), tbuf1, &bp);
safe_str(", ", tbuf1, &bp);
} else {
for (j = 0; j < 10; j++) {
wsave[j] = global_eval_context.wenv[j];
global_eval_context.wenv[j] = NULL;
}
global_eval_context.wenv[0] = (char *) msg;
save_global_regs("prefix_save", preserve);
asave = safe_atr_value(a);
ap = asave;
process_expression(tbuf1, &bp, &ap, thing, orator, orator,
PE_DEFAULT, PT_DEFAULT, NULL);
free((Malloc_t) asave);
restore_global_regs("prefix_save", preserve);
for (j = 0; j < 10; j++)
global_eval_context.wenv[j] = wsave[j];
if (bp != tbuf1)
safe_chr(' ', tbuf1, &bp);
}
safe_str(msg, tbuf1, &bp);
*bp = '\0';
return;
}
/** pass a message on, for AUDIBLE, prepending a prefix, unless the
* message matches a filter pattern.
* \param thing object to check for filter and prefix.
* \param msg message to pass.
*/
void
propagate_sound(dbref thing, const char *msg)
{
char tbuf1[BUFFER_LEN];
dbref loc = Location(thing);
dbref pass[2];
if (!GoodObject(loc))
return;
/* check to see that filter doesn't suppress message */
if (filter_found(thing, msg, 0))
return;
/* figure out the prefix */
make_prefixstr(thing, msg, tbuf1);
/* Exits pass the message on to things in the next room.
* Objects pass the message on to the things outside.
* Don't tell yourself your own message.
*/
if (IsExit(thing)) {
notify_anything(orator, na_next, &Contents(loc), NULL, NA_INTER_HEAR,
tbuf1);
} else {
pass[0] = Contents(loc);
pass[1] = thing;
notify_anything(orator, na_nextbut, pass, NULL, NA_INTER_HEAR, tbuf1);
}
}
static void
do_audible_stuff(dbref loc, dbref *excs, int numexcs, const char *msg)
{
dbref e;
int exclude = 0;
int i;
if (!Audible(loc))
return;
if (IsRoom(loc)) {
DOLIST(e, Exits(loc)) {
if (Audible(e))
propagate_sound(e, msg);
}
} else {
for (i = 0; i < numexcs; i++)
if (*(excs + i) == loc)
exclude = 1;
if (!exclude)
propagate_sound(loc, msg);
}
}
/** notify_anthing() wrapper to notify everyone in a location except one
* object.
* \param first object in location to notify.
* \param exception dbref of object not to notify, or NOTHING.
* \param msg message to send.
* \param flags flags to pass to notify_anything().
*/
void
notify_except(dbref first, dbref exception, const char *msg, int flags)
{
dbref loc;
dbref pass[2];
if (!GoodObject(first))
return;
loc = Location(first);
if (!GoodObject(loc))
return;
if (exception == NOTHING)
exception = AMBIGUOUS;
pass[0] = loc;
pass[1] = exception;
notify_anything(orator, na_except, pass, ns_esnotify, flags, msg);
do_audible_stuff(loc, &pass[1], 1, msg);
}
/** notify_anthing() wrapper to notify everyone in a location except two
* objects.
* \param first object in location to notify.
* \param exc1 dbref of one object not to notify, or NOTHING.
* \param exc2 dbref of another object not to notify, or NOTHING.
* \param msg message to send.
* \param flags interaction flags to control type of interaction.
*/
void
notify_except2(dbref first, dbref exc1, dbref exc2, const char *msg, int flags)
{
dbref loc;
dbref pass[3];
if (!GoodObject(first))
return;
loc = Location(first);
if (!GoodObject(loc))
return;
if (exc1 == NOTHING)
exc1 = AMBIGUOUS;
if (exc2 == NOTHING)
exc2 = AMBIGUOUS;
pass[0] = loc;
pass[1] = exc1;
pass[2] = exc2;
notify_anything(orator, na_except2, pass, ns_esnotify, flags, msg);
do_audible_stuff(loc, &pass[1], 2, msg);
}
/** The think command.
* \param player the enactor.
* \param message the message to think.
*/
void
do_think(dbref player, const char *message)
{
notify(player, message);
}
/** The emit command.
* \verbatim
* This implements @emit.
* \endverbatim
* \param player the enactor.
* \param tbuf1 the message to emit.
* \param flags bitmask of notification flags.
*/
void
do_emit(dbref player, const char *tbuf1, int flags)
{
dbref loc;
int na_flags = NA_INTER_HEAR;
loc = speech_loc(player);
if (!GoodObject(loc))
return;
if (!eval_lock(player, loc, Speech_Lock)) {
fail_lock(player, loc, Speech_Lock, T("You may not speak here!"), NOTHING);
return;
}
/* notify everybody */
if (flags & PEMIT_SPOOF)
na_flags |= NA_SPOOF;
notify_anything(player, na_loc, &loc, ns_esnotify, na_flags, tbuf1);
do_audible_stuff(loc, NULL, 0, tbuf1);
}
/** Remit a message to a single room.
* \param player the enactor.
* \param target string containing dbref of room to remit in.
* \param msg message to emit.
* \param flags PEMIT_* flags
*/
static void
do_one_remit(dbref player, const char *target, const char *msg, int flags)
{
dbref room;
int na_flags = NA_INTER_HEAR;
room = match_result(player, target, NOTYPE, MAT_EVERYTHING);
if (!GoodObject(room)) {
notify(player, T("I can't find that."));
} else {
if (IsExit(room)) {
notify(player, T("There can't be anything in that!"));
} else if (!okay_pemit(player, room)) {
notify_format(player,
T("I'm sorry, but %s wishes to be left alone now."),
Name(room));
} else if (!eval_lock(player, room, Speech_Lock)) {
fail_lock(player, room, Speech_Lock, T("You may not speak there!"),
NOTHING);
} else {
if (!(flags & PEMIT_SILENT) && (Location(player) != room)) {
const char *rmno;
rmno = unparse_object(player, room);
notify_format(player, T("You remit, \"%s\" in %s"), msg, rmno);
}
if (flags & PEMIT_SPOOF)
na_flags |= NA_SPOOF;
notify_anything_loc(player, na_loc, &room, ns_esnotify, na_flags,
msg, room);
do_audible_stuff(room, NULL, 0, msg);
}
}
}
/** Remit a message
* \verbatim
* This implements @remit.
* \endverbatim
* \param player the enactor.
* \param arg1 string containing dbref(s) of rooms to remit it.
* \param arg2 message to emit.
* \param flags for remit.
*/
void
do_remit(dbref player, char *arg1, const char *arg2, int flags)
{
if (flags & PEMIT_LIST) {
/* @remit/list */
char *current;
arg1 = trim_space_sep(arg1, ' ');
while ((current = split_token(&arg1, ' ')) != NULL)
do_one_remit(player, current, arg2, flags);
} else {
do_one_remit(player, arg1, arg2, flags);
}
}
/** Emit a message to the absolute location of enactor.
* \param player the enactor.
* \param tbuf1 message to emit.
* \param flags bitmask of notification flags.
*/
void
do_lemit(dbref player, const char *tbuf1, int flags)
{
/* give a message to the "absolute" location of an object */
dbref room;
int rec = 0;
int na_flags = NA_INTER_HEAR;
int silent = (flags & PEMIT_SILENT) ? 1 : 0;
/* only players and things may use this command */
if (!Mobile(player))
return;
/* prevent infinite loop if player is inside himself */
if (((room = Location(player)) == player) || !GoodObject(room)) {
notify(player, T("Invalid container object."));
do_rawlog(LT_ERR, T("** BAD CONTAINER ** #%d is inside #%d."), player,
room);
return;
}
while (!IsRoom(room) && (rec < 15)) {
room = Location(room);
rec++;
}
if (rec > 15) {
notify(player, T("Too many containers."));
return;
} else if (!eval_lock(player, room, Speech_Lock)) {
fail_lock(player, room, Speech_Lock, T("You may not speak there!"),
NOTHING);
return;
} else {
if (!silent && (Location(player) != room))
notify_format(player, T("You lemit: \"%s\""), tbuf1);
if (flags & PEMIT_SPOOF)
na_flags |= NA_SPOOF;
notify_anything(player, na_loc, &room, ns_esnotify, na_flags, tbuf1);
}
}
/** notify_anything() function for zone emits.
* \param current unused.
* \param data array of notify data.
* \return last object in zone, or NOTHING.
*/
dbref
na_zemit(dbref current __attribute__ ((__unused__)), void *data)
{
dbref this;
dbref room;
dbref *dbrefs = data;
this = dbrefs[0];
do {
if (this == NOTHING) {
for (room = dbrefs[1]; room < db_top; room++) {
if (IsRoom(room) && (Zone(room) == dbrefs[2])
&& eval_lock(dbrefs[3], room, Speech_Lock)
)
break;
}
if (!(room < db_top))
return NOTHING;
this = room;
dbrefs[1] = room + 1;
} else if (IsRoom(this)) {
this = Contents(this);
} else {
this = Next(this);
}
} while ((this == NOTHING) || (this == dbrefs[4]));
dbrefs[0] = this;
return this;
}
/** The zemit command.
* \verbatim
* This implements @zemit and @nszemit.
* \endverbatim
* \param player the enactor.
* \param arg1 string containing dbref of ZMO.
* \param arg2 message to emit.
* \param flags bitmask of notificati flags.
*/
void
do_zemit(dbref player, const char *arg1, const char *arg2, int flags)
{
const char *where;
dbref zone;
dbref pass[5];
int na_flags = NA_INTER_HEAR;
zone = match_result(player, arg1, NOTYPE, MAT_ABSOLUTE);
if (!GoodObject(zone)) {
notify(player, T("Invalid zone."));
return;
}
if (!controls(player, zone)) {
notify(player, T("Permission denied."));
return;
}
/* this command is computationally expensive */
if (!payfor(player, FIND_COST)) {
notify(player, T("Sorry, you don't have enough money to do that."));
return;
}
where = unparse_object(player, zone);
notify_format(player, T("You zemit, \"%s\" in zone %s"), arg2, where);
pass[0] = NOTHING;
pass[1] = 0;
pass[2] = zone;
pass[3] = player;
pass[4] = player;
if (flags & PEMIT_SPOOF)
na_flags |= NA_SPOOF;
notify_anything(player, na_zemit, pass, ns_esnotify, na_flags, arg2);
}