tinymux/mux/modules/engine/speech.cpp
Stephen Dennis 2285fa54be chore(int64): quota, charges, queue counts stay full width after mux_atoi64 (#1402)
Maintainer direction: prefer int64_t over long/int for parsed softcode
and attribute integers so LLP64 and LP64 behave the same.

This first slice covers the leftovers called out on the issue:

- quota: store/show/set paths (avoids @quota/all 2^32 writing 0)
- A_CHARGES in set/levels/predicates (RUNOUT vs wrap-to-positive)
- semaphore counts and @ps/queue kick/warp parsing
- IDLETIMEOUT attribute seconds

Bounded API edges (RunTasks(int), optional int *pnum) clamp after the
full-width parse. pot: quota notify formats use %lld.
2026-07-28 19:40:41 -06:00

1883 lines
48 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*! \file speech.cpp
* \brief Commands which involve speaking.
*
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
UTF8 *modSpeech(dbref player, const UTF8 *message, bool bWhich, const UTF8 *command)
{
dbref aowner;
int aflags;
LBuf mod = LBuf_Adopt(atr_get("modSpeech.25", player, bWhich ? A_SPEECHMOD : A_SAYSTRING,
&aowner, &aflags));
if ( mod[0] == '\0'
|| alarm_clock.alarmed)
{
return nullptr;
}
UTF8 *new_message = alloc_lbuf("modspeech");
UTF8 *t_ptr = new_message;
if ((aflags & AF_NOEVAL) || NoEval(player))
{
mux_strncpy(new_message, mod, LBUF_SIZE-1);
t_ptr = new_message + strlen((const char *)new_message);
}
else
{
const UTF8 *args[2];
args[0] = message;
args[1] = command;
mux_exec(mod, LBUF_SIZE-1, new_message, &t_ptr, player, player, player,
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP), args, 2);
}
*t_ptr = '\0';
return new_message;
}
static int64_t idle_timeout_val(dbref player)
{
// If IDLETIMEOUT attribute is not present, the value
// returned will be zero.
//
// int64_t, not int (#1402): attribute-sourced seconds stay full width.
//
dbref aowner;
int aflags;
LBuf ITbuffer = LBuf_Adopt(atr_get("idle_timeout_val.53", player, A_IDLETMOUT, &aowner, &aflags));
return mux_atoi64(ITbuffer);
}
static bool sp_ok(dbref player)
{
if ( Gagged(player)
&& !Wizard(player))
{
notify(player, M_("Sorry. Gagged players cannot speak."));
return false;
}
if (!mudconf.robot_speak)
{
if (Robot(player) && !Controls(player, Location(player)))
{
notify(player, M_("Sorry, robots may not speak in public."));
return false;
}
}
if (Auditorium(Location(player)))
{
if (!could_doit(player, Location(player), A_LSPEECH))
{
notify(player, M_("Sorry, you may not speak in this place."));
return false;
}
}
return true;
}
void do_think(dbref executor, dbref caller, dbref enactor, int eval, int key,
UTF8 *message, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
UTF8 *bp;
LBuf buf = LBuf_Src("do_think");
bp = buf;
mux_exec(message, LBUF_SIZE-1, buf, &bp, executor, caller, enactor, eval|EV_FCHECK|EV_EVAL|EV_TOP,
nullptr, 0);
*bp = '\0';
notify(executor, buf);
}
void do_say(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *message, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
// Make sure speaker is somewhere if speaking in a place
//
dbref loc = where_is(executor);
if ( !( Good_obj(loc)
&& sp_ok(executor)))
{
return;
}
int say_flags, depth;
// Convert prefix-coded messages into the normal type
//
say_flags = key & (SAY_NOEVAL | SAY_HERE | SAY_ROOM | SAY_HTML);
key &= ~(SAY_NOEVAL | SAY_HERE | SAY_ROOM | SAY_HTML);
if (key == SAY_PREFIX)
{
switch (message[0])
{
case '"':
message++;
key = SAY_SAY;
break;
case ':':
message++;
if (*message == ' ')
{
message++;
key = SAY_POSE_NOSPC;
}
else
{
key = SAY_POSE;
}
break;
case ';':
message++;
key = SAY_POSE_NOSPC;
break;
case 0xE2:
if ( 0x80 == message[1]
&& 0x9C == message[2])
{
// U+201C - Unicode version of opening double quote.
//
message += 3;
key = SAY_SAY;
}
else
{
key = SAY_EMIT;
}
break;
case '\\':
message++;
// FALLTHROUGH
//
default:
key = SAY_EMIT;
break;
}
}
const UTF8 *command = T("");
if (SAY_SAY == key)
{
command = T("say");
}
else if (SAY_POSE == key || SAY_POSE_NOSPC == key)
{
command = T("pose");
}
else if (SAY_EMIT == key)
{
command = T("@emit");
}
// Parse speechmod if present.
//
UTF8 *messageOrig = message;
UTF8 *messageNew = nullptr;
if (!(say_flags & SAY_NOEVAL))
{
messageNew = modSpeech(executor, message, true, command);
if (messageNew)
{
message = messageNew;
}
}
// Send the message on its way
//
UTF8 *saystring;
switch (key)
{
case SAY_SAY:
saystring = modSpeech(executor, messageOrig, false, command);
if (saystring)
{
notify_saypose(executor, tprintf(M_("%s %s “%s”"),
Moniker(executor), saystring, message));
#ifdef REALITY_LVLS
notify_except_rlevel(loc, executor, executor, tprintf(M_("%s %s “%s”"), Moniker(executor), saystring, message), MSG_SAYPOSE);
#else
notify_except(loc, executor, executor, tprintf(M_("%s %s “%s”"), Moniker(executor), saystring, message), MSG_SAYPOSE);
#endif
free_lbuf(saystring);
}
else
{
notify_saypose(executor, tprintf(M_("You say, “%s”"), message));
#ifdef REALITY_LVLS
notify_except_rlevel(loc, executor, executor, tprintf(M_("%s says, “%s”"), Moniker(executor), message), MSG_SAYPOSE);
#else
notify_except(loc, executor, executor, tprintf(M_("%s says, “%s”"), Moniker(executor), message), MSG_SAYPOSE);
#endif
}
break;
case SAY_POSE:
#ifdef REALITY_LVLS
notify_except_rlevel(loc, executor, -1, tprintf(M_("%s %s"), Moniker(executor), message), MSG_SAYPOSE);
#else
notify_all_from_inside_saypose(loc, executor, tprintf(M_("%s %s"), Moniker(executor), message));
#endif
break;
case SAY_POSE_NOSPC:
#ifdef REALITY_LVLS
notify_except_rlevel(loc, executor, -1, tprintf(M_("%s%s"), Moniker(executor), message), MSG_SAYPOSE);
#else
notify_all_from_inside_saypose(loc, executor, tprintf(M_("%s%s"), Moniker(executor), message));
#endif
break;
case SAY_EMIT:
if ( (say_flags & SAY_HERE)
|| (say_flags & SAY_HTML)
|| !say_flags)
{
if (say_flags & SAY_HTML)
{
notify_all_from_inside_html(loc, executor, message);
}
else
{
#ifdef REALITY_LVLS
notify_except_rlevel(loc, executor, -1, message, SAY_EMIT);
#else
notify_all_from_inside(loc, executor, message);
#endif
}
}
if (say_flags & SAY_ROOM)
{
if ( isRoom(loc)
&& (say_flags & SAY_HERE))
{
if (messageNew)
{
free_lbuf(messageNew);
}
return;
}
for (depth = 0; !isRoom(loc) && (depth < 20); depth++)
{
loc = Location(loc);
if ( !Good_obj(loc)
|| (loc == Location(loc)))
{
if (messageNew)
{
free_lbuf(messageNew);
}
return;
}
}
if (isRoom(loc))
{
#ifdef REALITY_LVLS
notify_except_rlevel(loc, executor, -1, message, -1);
#else
notify_all_from_inside(loc, executor, message);
#endif
}
}
break;
}
if (messageNew)
{
free_lbuf(messageNew);
}
}
struct wall_broadcast_context
{
int target;
dbref player;
UTF8 *message;
};
static void wall_broadcast_callback(dbref connected_player, void *ctx)
{
wall_broadcast_context *wbc = static_cast<wall_broadcast_context *>(ctx);
switch (wbc->target)
{
case SHOUT_WIZARD:
if (Wizard(connected_player))
{
notify_with_cause(connected_player, wbc->player, wbc->message);
}
break;
case SHOUT_ADMIN:
if (WizRoy(connected_player))
{
notify_with_cause(connected_player, wbc->player, wbc->message);
}
break;
default:
notify_with_cause(connected_player, wbc->player, wbc->message);
break;
}
}
static void wall_broadcast(int target, dbref player, UTF8 *message)
{
wall_broadcast_context ctx = { target, player, message };
for_each_connected_player(wall_broadcast_callback, &ctx);
}
static const UTF8 *announce_msg = T("Announcement: ");
static const UTF8 *broadcast_msg = T("Broadcast: ");
static const UTF8 *admin_msg = T("Admin: ");
void do_shout(dbref executor, dbref caller, dbref enactor, int eval, int key,
UTF8 *message, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
UTF8 *p = nullptr, *messageNew = nullptr, *buf2 = nullptr, *bp = nullptr;
bool bNoTag = (key & SHOUT_NOTAG) ? true : false;
bool bEmit = (key & SHOUT_EMIT) ? true : false;
bool bPose = (key & SHOUT_POSE) ? true : bEmit;
bool bSpace = !bEmit;
key &= ~(SHOUT_NOTAG | SHOUT_POSE | SHOUT_EMIT);
static const UTF8 *prefix, *loghead, *logtext1, *logsay, *saystring;
if (key & SHOUT_ADMIN)
{
key = SHOUT_ADMIN; // @wall/wiz/admin is treated as @wall/admin
prefix = admin_msg;
loghead = T("ASHOUT");
logtext1 = T(" ADMIN");
logsay = T(" yells: ");
saystring = T("says, “");
}
else if (key & SHOUT_WIZARD)
{
prefix = broadcast_msg;
loghead = T("BCAST");
logtext1 = T(" WIZ");
logsay = T(" broadcasts: ");
saystring = T("says, “");
}
else
{
prefix = announce_msg;
loghead = T("SHOUT");
logtext1 = T(" WALL");
logsay = T(" shouts: ");
saystring = T("shouts, “");
}
if (bNoTag)
{
prefix = T("");
}
if (!bPose)
{
switch (*message)
{
case ';':
bSpace = false;
// FALL THROUGH
case ':':
bPose = true;
// FALL THROUGH
case '"':
message++;
break;
}
}
// Parse speechmod if present.
//
messageNew = modSpeech(executor, message, true, T("@wall"));
if (messageNew)
{
message = messageNew;
}
if (!bPose)
{
buf2 = alloc_lbuf("do_shout");
bp = buf2;
safe_str(saystring, buf2, &bp);
safe_str(message, buf2, &bp);
safe_str(T(""), buf2, &bp);
*bp = '\0';
}
p = tprintf(M_("%s%s%s%s"), prefix, bEmit ? T("") : Moniker(executor),
bSpace ? T(" ") : T(""), bPose ? reinterpret_cast<UTF8 *>(message) : reinterpret_cast<UTF8 *>(buf2));
wall_broadcast(key, executor, p);
if (!bPose)
{
free_lbuf(buf2);
}
STARTLOG(LOG_SHOUTS, "WIZ", loghead);
log_name(executor);
if (bEmit)
{
log_text(logtext1);
log_text(T("emits: "));
}
else if (bPose)
{
log_text(logtext1);
log_text(T("poses: "));
}
else
{
log_text(logsay);
}
log_text(message);
ENDLOG;
if (messageNew)
{
free_lbuf(messageNew);
}
}
/* ---------------------------------------------------------------------------
* do_page: Handle the page command.
* Page-pose code from shadow@prelude.cc.purdue.
*/
static void page_return(dbref player, dbref target, const UTF8 *tag,
int anum, const UTF8 *dflt)
{
if (alarm_clock.alarmed)
{
return;
}
dbref aowner;
int aflags;
UTF8 *bp;
LBuf str = LBuf_Adopt(atr_pget(target, anum, &aowner, &aflags));
if (*str.get())
{
LBuf str2 = LBuf_Src("page_return");
bp = str2;
mux_exec(str, LBUF_SIZE-1, str2, &bp, target, player, player,
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP|EV_NO_LOCATION),
nullptr, 0);
*bp = '\0';
if (*str2.get())
{
CLinearTimeAbsolute ltaNow;
ltaNow.GetLocal();
FIELDEDTIME ft;
ltaNow.ReturnFields(&ft);
UTF8 *p = tprintf(M_("%s message from %s: %s"), tag,
Moniker(target), str2.get());
notify_with_cause_ooc(player, target, p, MSG_SRC_PAGE);
p = tprintf(M_("[%d:%02d] %s message sent to %s."), ft.iHour,
ft.iMinute, tag, Moniker(player));
notify_with_cause_ooc(target, player, p, MSG_SRC_PAGE);
}
}
else if (dflt && *dflt)
{
notify_with_cause_ooc(player, target, dflt, MSG_SRC_PAGE);
}
}
static bool page_check(dbref player, dbref target)
{
// Validate first and charge last. payfor() used to run before any of
// these checks and nothing refunded it when they failed, so paging an
// offline player -- or one whose PAGE-lock rejected the sender -- still
// cost pagecost and delivered nothing. do_page() calls this once per
// recipient, so a multi-target page multiplied the loss (#1187).
//
// payfor() deducts only on success, so moving it last needs no refund
// path.
//
if (!Connected(target))
{
page_return(player, target, T("Away"), A_AWAY,
tprintf(M_("Sorry, %s is not connected."), Moniker(target)));
return false;
}
if (!could_doit(player, target, A_LPAGE))
{
if ( Can_Hide(target)
&& Hidden(target)
&& !See_Hidden(player))
{
page_return(player, target, T("Away"), A_AWAY,
tprintf(M_("Sorry, %s is not connected."), Moniker(target)));
}
else
{
page_return(player, target, T("Reject"), A_REJECT,
tprintf(M_("Sorry, %s is not accepting pages."), Moniker(target)));
}
return false;
}
// A wizard may page someone who cannot page back; everyone else may not.
// Hold the warning until the page is actually paid for, so a sender who
// cannot afford it does not get told about a page that never happens.
//
bool bCannotReturn = false;
if (!could_doit(target, player, A_LPAGE))
{
if (!Wizard(player))
{
notify(player, tprintf(M_("Sorry, %s cant return your page."),
Moniker(target)));
return false;
}
bCannotReturn = true;
}
if (!payfor(player, Guest(player) ? 0 : mudconf.pagecost))
{
notify(player, tprintf(M_("You dont have enough %s."), mudconf.many_coins));
return false;
}
if (bCannotReturn)
{
notify(player, tprintf(M_("Warning: %s cant return your page."),
Moniker(target)));
}
return true;
}
// The combinations are:
//
// nargs arg1[0] arg2[0]
// '' 1 '\0' '\0' Report LastPaged to player.
// 'a' 1 'a' '\0' Page LastPaged with A
// 'a=' 2 'a' '\0' Page A. LastPaged <- A
// '=b' 2 '\0' 'b' Page LastPaged with B
// 'a=b' 2 'a' 'b' Page A with B. LastPaged <- A
// 'a=b1=[b2=]*...' All treated the same as 'a=b'.
//
void do_page
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *arg1,
UTF8 *arg2,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bBlind = (key & PAGE_BLIND) != 0;
int nPlayers = 0;
dbref aPlayers[(LBUF_SIZE+1)/2];
// Either we have been given a recipient list, or we are relying on an
// existing A_LASTPAGE.
//
bool bModified = false;
if ( nargs == 2
&& arg1[0] != '\0')
{
bModified = true;
UTF8 *p = arg1;
while (*p != '\0')
{
UTF8 *q = reinterpret_cast<UTF8 *>(strchr(reinterpret_cast<char *>(p), '"'));
if (q)
{
*q = '\0';
}
// Decode space-delimited or comma-delimited recipients.
//
string_token st(p, T(", "));
UTF8 *r;
for (r = st.parse(); r; r = st.parse())
{
dbref target = lookup_player(executor, r, true);
if (target != NOTHING)
{
aPlayers[nPlayers++] = target;
}
else
{
notify(executor, tprintf(M_("I dont recognize “%s”."), r));
}
}
if (q)
{
p = q + 1;
// Handle quoted named.
//
q = reinterpret_cast<UTF8 *>(strchr(reinterpret_cast<char *>(p), '"'));
if (q)
{
*q = '\0';
}
dbref target = lookup_player(executor, p, true);
if (target != NOTHING)
{
aPlayers[nPlayers++] = target;
}
else
{
notify(executor, tprintf(M_("I dont recognize “%s”."), p));
}
if (q)
{
p = q + 1;
}
else
{
break;
}
}
else
{
break;
}
}
}
else
{
// Need to decode the A_LASTPAGE.
//
dbref aowner;
int aflags;
LBuf pLastPage = LBuf_Adopt(atr_get("do_page.645", executor, A_LASTPAGE, &aowner, &aflags));
string_token st(pLastPage, T(" "));
UTF8 *p;
for (p = st.parse(); p; p = st.parse())
{
dbref target = mux_atoi64(p);
if ( Good_obj(target)
&& isPlayer(target))
{
aPlayers[nPlayers++] = target;
}
else
{
notify(executor, tprintf(M_("I dont recognize #%d."), target));
bModified = true;
}
}
}
int nValid = nPlayers;
// Remove duplicate dbrefs.
//
int i;
for (i = 0; i < nPlayers-1; i++)
{
if (aPlayers[i] != NOTHING)
{
int j;
for (j = i+1; j < nPlayers; j++)
{
if (aPlayers[j] == aPlayers[i])
{
aPlayers[j] = NOTHING;
bModified = true;
nValid--;
}
}
}
}
// If we are doing more than reporting, we have some other dbref
// validation to do.
//
if ( nargs == 2
|| arg1[0] != '\0')
{
for (i = 0; i < nPlayers; i++)
{
if ( Good_obj(aPlayers[i])
&& !page_check(executor, aPlayers[i]))
{
aPlayers[i] = NOTHING;
bModified = true;
nValid--;
}
}
}
if (bModified)
{
// Our aPlayers could be different than the one encoded on A_LASTPAGE.
// Update the database.
//
ITL itl;
LBuf pBuff = LBuf_Src("do_page.lastpage");
UTF8 *pBufc = pBuff;
ItemToList_Init(&itl, pBuff, &pBufc);
for (i = 0; i < nPlayers; i++)
{
if ( Good_obj(aPlayers[i])
&& !ItemToList_AddInteger(&itl, aPlayers[i]))
{
break;
}
}
ItemToList_Final(&itl);
atr_add_raw(executor, A_LASTPAGE, pBuff);
}
// Verify that the recipient list isn't empty.
//
if (nValid == 0)
{
if ( nargs == 1
&& arg1[0] == '\0')
{
notify(executor, M_("You have not paged anyone."));
}
else
{
notify(executor, M_("No one to page."));
}
return;
}
// Build a friendly representation of the recipient list.
//
LBuf aFriendly = LBuf_Src("do_page.friendly");
UTF8 *pFriendly = aFriendly;
if (nValid > 1)
{
safe_chr('(', aFriendly, &pFriendly);
}
bool bFirst = true;
for (i = 0; i < nPlayers; i++)
{
if (aPlayers[i] != NOTHING)
{
if (bFirst)
{
bFirst = false;
}
else
{
safe_copy_buf(T(", "), 2, aFriendly, &pFriendly);
}
safe_str(Moniker(aPlayers[i]), aFriendly, &pFriendly);
}
}
if (nValid > 1)
{
safe_chr(')', aFriendly, &pFriendly);
}
*pFriendly = '\0';
// We may be able to proceed directly to the reporting case.
//
if ( nargs == 1
&& arg1[0] == '\0')
{
notify(executor, tprintf(M_("You last paged %s."), aFriendly.get()));
return;
}
// Build messages.
//
LBuf omessage = LBuf_Src("do_page.omessage");
LBuf imessage = LBuf_Src("do_page.imessage");
UTF8 *omp = omessage;
UTF8 *imp = imessage;
UTF8 *pMessage;
if (nargs == 1)
{
// 'page A' form.
//
pMessage = arg1;
}
else
{
// 'page A=', 'page =B', and 'page A=B' forms.
//
pMessage = arg2;
}
int pageMode;
switch (*pMessage)
{
case '\0':
pageMode = 1;
break;
case ':':
pMessage++;
if (' ' != *pMessage)
{
pageMode = 2;
break;
}
// FALL THROUGH
case ';':
pageMode = 3;
pMessage++;
break;
case '"':
pMessage++;
// FALL THROUGH
default:
pageMode = 0;
}
UTF8 *newMessage = modSpeech(executor, pMessage, true, T("page"));
if (newMessage)
{
pMessage = newMessage;
}
switch (pageMode)
{
case 1:
// 'page A=' form.
//
if (bBlind || nValid == 1)
{
safe_tprintf_str(omessage, &omp, M_("From afar, %s pages you."),
Moniker(executor));
}
else
{
safe_tprintf_str(omessage, &omp, M_("From afar, %s pages %s."),
Moniker(executor), aFriendly.get());
}
safe_tprintf_str(imessage, &imp, M_("You page %s."), aFriendly.get());
break;
case 2:
// Whole sentences: the recipient list is conditional, so two
// msgids rather than T/M_ fragment assembly (see #1575 / #1588).
//
if (!bBlind && nValid > 1)
{
safe_tprintf_str(omessage, &omp, M_("From afar, to %s: %s %s"),
aFriendly.get(), Moniker(executor), pMessage);
}
else
{
safe_tprintf_str(omessage, &omp, M_("From afar, %s %s"),
Moniker(executor), pMessage);
}
safe_tprintf_str(imessage, &imp, M_("Long distance to %s: %s %s"),
aFriendly.get(), Moniker(executor), pMessage);
break;
case 3:
if (!bBlind && nValid > 1)
{
safe_tprintf_str(omessage, &omp, M_("From afar, to %s: %s%s"),
aFriendly.get(), Moniker(executor), pMessage);
}
else
{
safe_tprintf_str(omessage, &omp, M_("From afar, %s%s"),
Moniker(executor), pMessage);
}
safe_tprintf_str(imessage, &imp, M_("Long distance to %s: %s%s"),
aFriendly.get(), Moniker(executor), pMessage);
break;
default:
if (!bBlind && nValid > 1)
{
safe_tprintf_str(omessage, &omp, M_("To %s, %s pages: %s"),
aFriendly.get(), Moniker(executor), pMessage);
}
else
{
safe_tprintf_str(omessage, &omp, M_("%s pages: %s"),
Moniker(executor), pMessage);
}
safe_tprintf_str(imessage, &imp, M_("You paged %s with %s"),
aFriendly.get(), pMessage);
break;
}
// Send message to recipients.
//
for (i = 0; i < nPlayers; i++)
{
dbref target = aPlayers[i];
if (target != NOTHING)
{
notify_with_cause_ooc(target, executor, omessage, MSG_SRC_PAGE);
int target_idle = fetch_idle(target);
int64_t target_idle_timeout_val = idle_timeout_val(target);
if (target_idle >= target_idle_timeout_val)
{
page_return(executor, target, T("Idle"), A_IDLE, nullptr);
}
}
}
// Send message to sender.
//
notify(executor, imessage);
if (newMessage)
{
free_lbuf(newMessage);
}
}
/* ---------------------------------------------------------------------------
* do_pemit: Messages to specific players, or to all but specific players.
*/
static void whisper_pose(dbref player, dbref target, UTF8 *message, bool bSpace)
{
UTF8 *newMessage = modSpeech(player, message, true, T("whisper"));
if (newMessage)
{
message = newMessage;
}
LBuf buff = LBuf_Src("do_pemit.whisper.pose");
mux_strncpy(buff, Moniker(player), LBUF_SIZE-1);
notify_with_cause(target, player, tprintf(M_("You sense %s%s%s"), buff.get(),
bSpace ? " " : "", message));
if (newMessage)
{
free_lbuf(newMessage);
}
}
static dbref FindPemitTarget(dbref player, int key, UTF8 *recipient)
{
dbref target = NOTHING;
switch (key)
{
case PEMIT_FSAY:
case PEMIT_FPOSE:
case PEMIT_FPOSE_NS:
case PEMIT_FEMIT:
target = match_controlled(player, recipient);
break;
default:
init_match(player, recipient, TYPE_PLAYER);
match_everything(0);
target = match_result();
}
return target;
}
void do_pemit_single
(
dbref player,
int key,
bool bDoContents,
int pemit_flags,
dbref target,
int chPoseType,
UTF8 *message
)
{
dbref loc;
UTF8 *bp;
int depth;
bool ok_to_do = false;
switch (key)
{
case PEMIT_FSAY:
case PEMIT_FPOSE:
case PEMIT_FPOSE_NS:
case PEMIT_FEMIT:
if (target == NOTHING)
{
return;
}
ok_to_do = true;
break;
}
UTF8 *newMessage = nullptr;
UTF8 *saystring = nullptr;
UTF8 *p;
switch (target)
{
case NOTHING:
switch (key)
{
case PEMIT_WHISPER:
notify(player, M_("Whisper to whom?"));
break;
case PEMIT_PEMIT:
notify(player, M_("Emit to whom?"));
break;
case PEMIT_OEMIT:
notify(player, M_("Emit except to whom?"));
break;
default:
notify(player, M_("Sorry."));
break;
}
break;
case AMBIGUOUS:
notify(player, M_("I dont know who you mean!"));
break;
default:
// Enforce locality constraints.
//
if ( !ok_to_do
&& ( nearby(player, target)
|| Long_Fingers(player)
|| Controls(player, target)))
{
ok_to_do = true;
}
if ( !ok_to_do
&& key == PEMIT_PEMIT
&& isPlayer(target)
&& mudconf.pemit_players)
{
if (!page_check(player, target))
{
return;
}
ok_to_do = true;
}
if ( !ok_to_do
&& ( !mudconf.pemit_any
|| key != PEMIT_PEMIT))
{
notify(player, M_("You are too far away to do that."));
return;
}
if ( bDoContents
&& !Controls(player, target)
&& !mudconf.pemit_any)
{
notify(player, NOPERM_MESSAGE);
return;
}
loc = where_is(target);
switch (key)
{
case PEMIT_PEMIT:
if (bDoContents)
{
if (Has_contents(target))
{
notify_all_from_inside(target, player, message);
}
}
else
{
if (pemit_flags & PEMIT_HTML)
{
notify_with_cause_html(target, player, message);
}
else
{
notify_with_cause(target, player, message);
}
}
break;
case PEMIT_OEMIT:
notify_except(Location(target), player, target, message, 0);
break;
case PEMIT_WHISPER:
if ( isPlayer(target)
&& !Connected(target))
{
page_return(player, target, T("Away"), A_AWAY,
tprintf(M_("Sorry, %s is not connected."), Moniker(target)));
return;
}
switch (chPoseType)
{
case ':':
message++;
if (' ' == *message)
{
message++;
whisper_pose(player, target, message, false);
}
else
{
whisper_pose(player, target, message, true);
}
break;
case ';':
message++;
whisper_pose(player, target, message, false);
break;
case '"':
message++;
default:
newMessage = modSpeech(player, message, true, T("whisper"));
if (newMessage)
{
message = newMessage;
}
notify_with_cause(target, player,
tprintf(M_("%s whispers “%s”"), Moniker(player), message));
if (newMessage)
{
free_lbuf(newMessage);
}
}
if ( !mudconf.quiet_whisper
&& !Wizard(player))
{
loc = where_is(player);
if (loc != NOTHING)
{
LBuf buf2 = LBuf_Src("do_pemit.whisper.buzz");
bp = buf2;
safe_str(Moniker(player), buf2, &bp);
safe_str(T(" whispers something to "), buf2, &bp);
safe_str(Moniker(target), buf2, &bp);
*bp = '\0';
notify_except2(loc, player, player, target, buf2);
}
}
break;
case PEMIT_FSAY:
newMessage = modSpeech(target, message, true, T("@fsay"));
if (newMessage)
{
message = newMessage;
}
notify(target, tprintf(M_("You say, “%s”"), message));
if (loc != NOTHING)
{
saystring = modSpeech(target, message, false, T("@fsay"));
if (saystring)
{
p = tprintf(M_("%s %s “%s”"), Moniker(target),
saystring, message);
notify_except(loc, player, target, p, 0);
}
else
{
p = tprintf(M_("%s says, “%s”"), Moniker(target),
message);
notify_except(loc, player, target, p, 0);
}
}
if (saystring)
{
free_lbuf(saystring);
}
if (newMessage)
{
free_lbuf(newMessage);
}
break;
case PEMIT_FPOSE:
newMessage = modSpeech(target, message, true, T("@fpose"));
if (newMessage)
{
message = newMessage;
}
p = tprintf(M_("%s %s"), Moniker(target), message);
notify_all_from_inside(loc, player, p);
if (newMessage)
{
free_lbuf(newMessage);
}
break;
case PEMIT_FPOSE_NS:
newMessage = modSpeech(target, message, true, T("@fpose"));
if (newMessage)
{
message = newMessage;
}
p = tprintf(M_("%s%s"), Moniker(target), message);
notify_all_from_inside(loc, player, p);
if (newMessage)
{
free_lbuf(newMessage);
}
break;
case PEMIT_FEMIT:
if ( (pemit_flags & PEMIT_HERE)
|| !pemit_flags)
{
notify_all_from_inside(loc, player, message);
}
if (pemit_flags & PEMIT_ROOM)
{
if ( isRoom(loc)
&& (pemit_flags & PEMIT_HERE))
{
return;
}
depth = 0;
while ( !isRoom(loc)
&& depth++ < 20)
{
loc = Location(loc);
if ( loc == NOTHING
|| loc == Location(loc))
{
return;
}
}
if (isRoom(loc))
{
notify_all_from_inside(loc, player, message);
}
}
break;
}
}
}
void do_pemit_single
(
dbref player,
int key,
bool bDoContents,
int pemit_flags,
UTF8 *recipient,
int chPoseType,
UTF8 *message
)
{
dbref target = FindPemitTarget(player, key, recipient);
do_pemit_single(player, key, bDoContents, pemit_flags, target, chPoseType, message);
}
void do_pemit_list
(
dbref player,
int key,
bool bDoContents,
int pemit_flags,
UTF8 *list,
int chPoseType,
UTF8 *message
)
{
if ( '\0' == message[0]
|| '\0' == list[0])
{
return;
}
UTF8 *error_message = nullptr;
UTF8 *error_ptr = nullptr;
dbref aPlayers[(LBUF_SIZE+1)/2];
int nPlayers = 0;
string_token st(list, T(", "));
for (UTF8 *p = st.parse(); p; p = st.parse())
{
dbref target = FindPemitTarget(player, key, p);
if (Good_obj(target))
{
aPlayers[nPlayers++] = target;
}
else
{
if (nullptr == error_message)
{
error_message = alloc_lbuf("do_pemit_list.error");
error_ptr = error_message;
safe_str(T("Emit error(s): "), error_message, &error_ptr);
}
else
{
safe_str(T(", "), error_message, &error_ptr);
}
safe_str(p, error_message, &error_ptr);
switch (target)
{
case NOTHING:
safe_str(T(" (unknown)"), error_message, &error_ptr);
break;
case AMBIGUOUS:
safe_str(T(" (ambiguous)"), error_message, &error_ptr);
break;
}
}
}
// Remove duplicate dbrefs.
//
int i;
for (i = 0; i < nPlayers-1; i++)
{
if (aPlayers[i] != NOTHING)
{
int j;
for (j = i+1; j < nPlayers; j++)
{
if (aPlayers[j] == aPlayers[i])
{
aPlayers[j] = NOTHING;
}
}
}
}
// For PEMIT_OEMIT, emit to the room of the first valid target,
// excluding all listed targets.
//
if (PEMIT_OEMIT == key)
{
// Find first valid target.
//
dbref first_target = NOTHING;
for (int i = 0; i < nPlayers; i++)
{
if (NOTHING != aPlayers[i])
{
first_target = aPlayers[i];
break;
}
}
if (NOTHING != first_target)
{
// Enforce locality constraints.
//
if ( nearby(player, first_target)
|| Long_Fingers(player)
|| Controls(player, first_target))
{
dbref loc = Location(first_target);
if (Good_obj(loc))
{
notify_except_N(loc, player, aPlayers, nPlayers, message, 0);
}
}
else
{
notify(player, M_("You are too far away to do that."));
}
}
}
else
{
for (int i = 0; i < nPlayers; i++)
{
dbref target = aPlayers[i];
if (NOTHING != target)
{
do_pemit_single(player, key, bDoContents, pemit_flags, target, chPoseType, message);
}
}
}
if (nullptr != error_message)
{
*error_ptr = '\0';
notify(player, error_message);
free_lbuf(error_message);
error_message = nullptr;
error_ptr = nullptr;
}
}
// Check if the target can be contacted by executor. If not, issue an
// error message to the executor and return false. If checks pass, return
// true.
//
static bool noisy_check_whisper_target(dbref executor, dbref target, int key)
{
bool ok_to_do = false;
if ( nearby(executor, target)
|| Long_Fingers(executor)
|| Controls(executor, target))
{
ok_to_do = true;
}
if ( !ok_to_do
&& ( !mudconf.pemit_any
|| PEMIT_PEMIT != key))
{
notify(executor,
tprintf(M_("Sorry, you are too far away to contact %s."),
Moniker(target)));
return false;
}
if ( isPlayer(target)
&& !Connected(target))
{
page_return(executor, target, T("Away"), A_AWAY,
tprintf(M_("Sorry, %s is not connected."),
Moniker(target)));
return false;
}
return true;
}
void do_pemit_whisper
(
dbref executor,
dbref caller,
dbref enactor,
int key,
int nargs,
UTF8 *recipient,
UTF8 *message
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
// If the argument count is not 2, pull recipients from A_LASTWHISPER.
//
if (nargs < 2)
{
if ('\0' == recipient[0])
{
notify(executor, M_("No one to whisper to."));
return;
}
message = recipient;
recipient = nullptr;
}
bool bModified = true;
int nPlayers = 0;
dbref aPlayers[(LBUF_SIZE+1)/2];
// Read the A_LASTWHISPER attribute and use that recipient list.
//
if ( PEMIT_WHISPER == key
&& ( nullptr == recipient
|| '\0' == recipient[0]))
{
dbref aowner;
int aflags;
LBuf stored_recipient = LBuf_Adopt(
atr_get("do_whisper.1316", executor, A_LASTWHISPER,
&aowner, &aflags));
bModified = false;
string_token st(stored_recipient, T(", "));
UTF8 *r;
for (r = st.parse(); r; r = st.parse())
{
dbref target = mux_atoi64(r);
if (Good_obj(target))
{
if (!noisy_check_whisper_target(executor, target, key))
{
continue;
}
else
{
aPlayers[nPlayers++] = target;
}
}
}
}
if (bModified)
{
UTF8 *p = recipient;
while ('\0' != *p)
{
UTF8 *q = reinterpret_cast<UTF8 *>(strchr(reinterpret_cast<char *>(p), '"'));
if (q)
{
*q = '\0';
}
// Decode space-delimited or comma-delimited recipients.
//
string_token st(p, T(", "));
UTF8 *r;
for (r = st.parse(); r; r = st.parse())
{
dbref target = lookup_player(executor, r, true);
if (NOTHING == target)
{
init_match(executor, r, NOTYPE);
match_neighbor();
target = match_result();
}
if (NOTHING != target)
{
if (!noisy_check_whisper_target(executor, target, key))
{
continue;
}
else
{
aPlayers[nPlayers++] = target;
}
}
}
if (q)
{
p = q + 1;
// Handle quoted named.
//
q = reinterpret_cast<UTF8 *>(strchr(reinterpret_cast<char *>(p), '"'));
if (q)
{
*q = '\0';
}
dbref target = lookup_player(executor, p, true);
if (NOTHING != target)
{
// Apply the same locality/connected gate as the
// non-quoted branch; do not 'continue' here, which would
// skip the p advance below and loop forever.
//
if (noisy_check_whisper_target(executor, target, key))
{
aPlayers[nPlayers++] = target;
}
}
else
{
init_match(executor, p, NOTYPE);
match_neighbor();
target = match_result();
if (NOTHING != target)
{
if (noisy_check_whisper_target(executor, target, key))
{
aPlayers[nPlayers++] = target;
}
}
}
if (q)
{
p = q + 1;
}
else
{
break;
}
}
else
{
break;
}
}
int nValid = nPlayers;
// Remove duplicate dbrefs.
//
int i;
for (i = 0; i < nPlayers-1; i++)
{
if (NOTHING != aPlayers[i])
{
int j;
for (j = i+1; j < nPlayers; j++)
{
if (aPlayers[j] == aPlayers[i])
{
aPlayers[j] = NOTHING;
bModified = true;
nValid--;
}
}
}
}
}
if ( bModified
&& 0 < nPlayers)
{
// Our aPlayers could be different than the one encoded on A_LASTWHISPER
// Update the database.
//
ITL itl;
LBuf pBuff = LBuf_Src("do_pemit_whisper.lastwhisper");
UTF8 *pBufc = pBuff;
ItemToList_Init(&itl, pBuff, &pBufc);
for (int i = 0; i < nPlayers; i++)
{
if ( Good_obj(aPlayers[i])
&& !ItemToList_AddInteger(&itl, aPlayers[i]))
{
break;
}
}
ItemToList_Final(&itl);
atr_add_raw(executor, A_LASTWHISPER, pBuff);
}
// Decode PEMIT_HERE, PEMIT_ROOM, PEMIT_HTML and remove from key.
//
int mask = PEMIT_HERE | PEMIT_ROOM | PEMIT_HTML;
int pemit_flags = key & mask;
key &= ~mask;
int chPoseType = *message;
bool bPoseSpace = true;
if (':' == chPoseType)
{
message[0] = ' ';
if (' ' == message[1])
{
// ": test" → skip the extra space, no space between name and text.
//
bPoseSpace = false;
}
}
if ( 1 == nPlayers
&& Good_obj(aPlayers[0]))
{
switch (chPoseType)
{
case ';':
notify(executor, tprintf(M_("%s senses “%s%s”"),
Moniker(aPlayers[0]), Moniker(executor), &message[1]));
break;
case ':':
if (bPoseSpace)
{
notify(executor, tprintf(M_("%s senses “%s %s”"),
Moniker(aPlayers[0]), Moniker(executor), &message[1]));
}
else
{
notify(executor, tprintf(M_("%s senses “%s%s”"),
Moniker(aPlayers[0]), Moniker(executor), &message[1]));
}
break;
default:
notify(executor, tprintf(M_("You whisper “%s” to %s."), message,
Moniker(aPlayers[0])));
break;
}
}
else if (1 < nPlayers)
{
LBuf aFriendly = LBuf_Src("do_pemit_whisper.friendly");
UTF8 *pFriendly = aFriendly;
bool bFirst = true;
for (int i = 0; i < nPlayers; i++)
{
if (NOTHING != aPlayers[i])
{
if (bFirst)
{
bFirst = false;
}
else if (nPlayers-1 == i)
{
if (2 == nPlayers)
{
safe_copy_buf(T(" and "), 5, aFriendly, &pFriendly);
}
else
{
safe_copy_buf(T(", and "), 6, aFriendly, &pFriendly);
}
}
else
{
safe_copy_buf(T(", "), 2, aFriendly, &pFriendly);
}
safe_str(Moniker(aPlayers[i]), aFriendly, &pFriendly);
}
}
*pFriendly = '\0';
switch (chPoseType)
{
case ';':
notify(executor, tprintf(M_("%s sense “%s%s”"),
aFriendly.get(), Moniker(executor), &message[1]));
break;
case ':':
if (bPoseSpace)
{
notify(executor, tprintf(M_("%s sense “%s %s”"),
aFriendly.get(), Moniker(executor), &message[1]));
}
else
{
notify(executor, tprintf(M_("%s sense “%s%s”"),
aFriendly.get(), Moniker(executor), &message[1]));
}
break;
default:
notify(executor, tprintf(M_("You whisper “%s” to %s."), message,
aFriendly.get()));
break;
}
}
for (int i = 0; i < nPlayers; i++)
{
if (Good_obj(aPlayers[i]))
{
do_pemit_single(executor, key, false, pemit_flags,
tprintf(T("#%d"), aPlayers[i]), chPoseType, message);
}
}
}
void do_pemit
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *recipient,
UTF8 *message,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if ( nargs < 2
&& key != PEMIT_WHISPER)
{
return;
}
if (PEMIT_WHISPER == key)
{
do_pemit_whisper(executor, caller, enactor, key, nargs, recipient, message);
return;
}
// Decode PEMIT_CONTENTS and PEMIT_LIST and remove from key.
//
bool bDoContents = false;
if (key & PEMIT_CONTENTS)
{
bDoContents = true;
}
bool bDoList = false;
if (key & (PEMIT_LIST|PEMIT_WHISPER))
{
bDoList = true;
}
key &= ~(PEMIT_CONTENTS | PEMIT_LIST);
// Decode PEMIT_HERE, PEMIT_ROOM, PEMIT_HTML and remove from key.
//
int mask = PEMIT_HERE | PEMIT_ROOM | PEMIT_HTML;
int pemit_flags = key & mask;
key &= ~mask;
int chPoseType = *message;
if ( PEMIT_WHISPER == key
&& ':' == chPoseType)
{
message[0] = ' ';
}
if (bDoList)
{
do_pemit_list(executor, key, bDoContents, pemit_flags, recipient,
chPoseType, message);
}
else
{
do_pemit_single(executor, key, bDoContents, pemit_flags, recipient,
chPoseType, message);
}
}