/*! \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(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(message) : reinterpret_cast(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 can’t return your page."), Moniker(target))); return false; } bCannotReturn = true; } if (!payfor(player, Guest(player) ? 0 : mudconf.pagecost)) { notify(player, tprintf(M_("You don’t have enough %s."), mudconf.many_coins)); return false; } if (bCannotReturn) { notify(player, tprintf(M_("Warning: %s can’t 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(strchr(reinterpret_cast(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 don’t recognize “%s”."), r)); } } if (q) { p = q + 1; // Handle quoted named. // q = reinterpret_cast(strchr(reinterpret_cast(p), '"')); if (q) { *q = '\0'; } dbref target = lookup_player(executor, p, true); if (target != NOTHING) { aPlayers[nPlayers++] = target; } else { notify(executor, tprintf(M_("I don’t 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 don’t 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 don’t 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(strchr(reinterpret_cast(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(strchr(reinterpret_cast(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); } }