mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Found verifying #783: the SQLite loaders are gated on metadata keys -- sqlite_load_comsys() on has_comsys, sqlite_load_mail() on mail_db_top -- but ONLY the DbConvert -C/-m import path ever set them. Channel and mail mutations write through to the tables row-by-row, so a channel created with @ccreate (or mail sent in-game) on a game that never went through a -C/-m import landed in the tables and was then ignored forever: the next warm boot found no gate, fell back to the legacy comsys.db/mail.db flatfiles, and the runtime came up without them while the orphaned rows lingered. The write-throughs now maintain the gates: sqlite_wt_channel() sets has_comsys, and sqlite_wt_mail_body() keeps mail_db_top current (it sizes the loader's body array). One cheap metadata upsert alongside mutations that are already rare. Verified: a channel @ccreate'd and mail sent in one muxscript session now warm-load in a fresh process -- db_unload -C/-m exports both, where before it exported nothing ("has no comsys/mail") despite the rows sitting in the tables. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5354 lines
145 KiB
C++
5354 lines
145 KiB
C++
/*! \file comsys.cpp
|
|
* \brief Channel Communication System.
|
|
*
|
|
* The functions here manage channels, channel membership, the comsys.db, and
|
|
* the interaction of players and other objects with channels.
|
|
*/
|
|
|
|
#include "copyright.h"
|
|
#include "autoconf.h"
|
|
#include "config.h"
|
|
#include "externs.h"
|
|
|
|
#include <algorithm>
|
|
#include <unordered_map>
|
|
|
|
using namespace std;
|
|
|
|
static unordered_map<dbref, comsys_t> comsys_table;
|
|
|
|
#define DFLT_MAX_LOG 0
|
|
#define MIN_RECALL_REQUEST 1
|
|
#define DFLT_RECALL_REQUEST 10
|
|
#define MAX_RECALL_REQUEST 200
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SQLite write-through helpers for comsys mutations.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#include "sqlite_backend.h"
|
|
|
|
#define SQLITE_COMSYS_WRITABLE() (!mudstate.bSQLiteLoading)
|
|
|
|
static void sqlite_wt_channel(struct channel *ch)
|
|
{
|
|
if (!SQLITE_COMSYS_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.SyncChannel(ch->name, ch->header,
|
|
ch->type, ch->temp1, ch->temp2,
|
|
ch->charge, ch->charge_who, ch->amount_col,
|
|
ch->num_messages, ch->chan_obj))
|
|
{
|
|
Log.tinyprintf(T("comsys sqlite_wt_channel failed for %s" ENDLINE), ch->name);
|
|
}
|
|
|
|
// Keep the loader's gate current: sqlite_load_comsys() (warm boot
|
|
// and db_unload -C) only reads the channel tables when has_comsys
|
|
// is set, but until now ONLY a DbConvert -C import set it -- a
|
|
// channel created in-game wrote through to the tables and was then
|
|
// ignored forever (the boot fell back to the legacy comsys.db
|
|
// flatfile). (#783)
|
|
//
|
|
if (!sqldb.PutMeta("has_comsys", 1))
|
|
{
|
|
Log.tinyprintf(T("comsys sqlite_wt_channel: has_comsys meta update failed" ENDLINE));
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_delete_channel(const UTF8 *name)
|
|
{
|
|
if (!SQLITE_COMSYS_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.DeleteChannel(name))
|
|
{
|
|
Log.tinyprintf(T("comsys sqlite_wt_delete_channel failed for %s" ENDLINE), name);
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_channel_user(const UTF8 *channel_name, struct comuser *user)
|
|
{
|
|
if (!SQLITE_COMSYS_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.SyncChannelUser(channel_name, user->who,
|
|
user->bUserIsOn, user->ComTitleStatus,
|
|
user->bGagJoinLeave,
|
|
reinterpret_cast<const UTF8 *>(user->title.c_str())))
|
|
{
|
|
Log.tinyprintf(T("comsys sqlite_wt_channel_user failed for %s/#%d" ENDLINE),
|
|
channel_name, user->who);
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_delete_channel_user(const UTF8 *channel_name, int who)
|
|
{
|
|
if (!SQLITE_COMSYS_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.DeleteChannelUser(channel_name, who))
|
|
{
|
|
Log.tinyprintf(T("comsys sqlite_wt_delete_channel_user failed for %s/#%d" ENDLINE),
|
|
channel_name, who);
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_player_channel(int who, const UTF8 *alias, const UTF8 *channel_name)
|
|
{
|
|
if (!SQLITE_COMSYS_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.SyncPlayerChannel(who, alias, channel_name))
|
|
{
|
|
Log.tinyprintf(T("comsys sqlite_wt_player_channel failed for #%d/%s" ENDLINE),
|
|
who, alias);
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_delete_player_channel(int who, const UTF8 *alias)
|
|
{
|
|
if (!SQLITE_COMSYS_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.DeletePlayerChannel(who, alias))
|
|
{
|
|
Log.tinyprintf(T("comsys sqlite_wt_delete_player_channel failed for #%d/%s" ENDLINE),
|
|
who, alias);
|
|
}
|
|
}
|
|
|
|
// Return value is a static buffer.
|
|
//
|
|
static UTF8* RestrictTitleValue(const UTF8* pTitleRequest)
|
|
{
|
|
// Remove all '\r\n\t' from the string.
|
|
// Terminate any ANSI in the string.
|
|
//
|
|
thread_local UTF8 NewTitle[MAX_TITLE_LEN + 1];
|
|
StripTabsAndTruncate(pTitleRequest, NewTitle, MAX_TITLE_LEN, MAX_TITLE_LEN);
|
|
return NewTitle;
|
|
}
|
|
|
|
static void do_setcomtitlestatus(const dbref player, struct channel* ch, const bool status)
|
|
{
|
|
struct comuser* user = select_user(ch, player);
|
|
if (ch && user)
|
|
{
|
|
user->ComTitleStatus = status;
|
|
sqlite_wt_channel_user(ch->name, user);
|
|
}
|
|
}
|
|
|
|
static void do_setgagjoinleavestatus(const dbref player, struct channel* ch, const bool status)
|
|
{
|
|
struct comuser* user = select_user(ch, player);
|
|
if (ch && user)
|
|
{
|
|
user->bGagJoinLeave = status;
|
|
sqlite_wt_channel_user(ch->name, user);
|
|
}
|
|
}
|
|
|
|
static void do_setnewtitle(const dbref player, struct channel* ch, const UTF8* pValidatedTitle)
|
|
{
|
|
struct comuser* user = select_user(ch, player);
|
|
|
|
if (ch && user)
|
|
{
|
|
user->title.assign(reinterpret_cast<const char *>(pValidatedTitle));
|
|
sqlite_wt_channel_user(ch->name, user);
|
|
}
|
|
}
|
|
|
|
// Save communication system data to disk.
|
|
//
|
|
void save_comsys(UTF8* filename)
|
|
{
|
|
UTF8 buffer[500];
|
|
|
|
mux_sprintf(buffer, sizeof(buffer), T("%s.#"), filename);
|
|
FILE* fp;
|
|
if (!mux_fopen(&fp, buffer, T("wb")))
|
|
{
|
|
Log.tinyprintf(T("Unable to open %s for writing." ENDLINE), buffer);
|
|
return;
|
|
}
|
|
mux_fprintf(fp, T("+V5\n"));
|
|
mux_fprintf(fp, T("*** Begin CHANNELS ***\n"));
|
|
|
|
save_channels(fp);
|
|
|
|
mux_fprintf(fp, T("*** Begin COMSYS ***\n"));
|
|
save_comsystem(fp);
|
|
|
|
mux_fclose(fp);
|
|
|
|
ReplaceFile(buffer, filename);
|
|
}
|
|
|
|
// Aliases must be between 1 and MAX_ALIAS_LEN characters. No spaces. No ANSI.
|
|
//
|
|
UTF8* MakeCanonicalComAlias
|
|
(
|
|
const UTF8* pAlias,
|
|
size_t* nValidAlias,
|
|
bool* bValidAlias
|
|
)
|
|
{
|
|
thread_local UTF8 Buffer[MAX_ALIAS_LEN + 1];
|
|
*nValidAlias = 0;
|
|
*bValidAlias = false;
|
|
|
|
if (!pAlias)
|
|
{
|
|
return nullptr;
|
|
}
|
|
size_t n = 0;
|
|
while (pAlias[n])
|
|
{
|
|
if (!mux_isprint_ascii(pAlias[n])
|
|
|| ' ' == pAlias[n])
|
|
{
|
|
return nullptr;
|
|
}
|
|
n++;
|
|
}
|
|
|
|
if (n < 1)
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (MAX_ALIAS_LEN < n)
|
|
{
|
|
n = MAX_ALIAS_LEN;
|
|
}
|
|
memcpy(Buffer, pAlias, n);
|
|
Buffer[n] = '\0';
|
|
*nValidAlias = n;
|
|
*bValidAlias = true;
|
|
return Buffer;
|
|
}
|
|
|
|
static bool ParseChannelLine(UTF8* pBuffer, UTF8* pAlias5, UTF8** ppChannelName)
|
|
{
|
|
// Fetch alias portion. We need to find the first space.
|
|
//
|
|
auto p = reinterpret_cast<UTF8*>(strchr(reinterpret_cast<char*>(pBuffer), ' '));
|
|
if (!p)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
*p = '\0';
|
|
bool bValidAlias;
|
|
size_t nValidAlias;
|
|
const UTF8* pValidAlias = MakeCanonicalComAlias(pBuffer, &nValidAlias, &bValidAlias);
|
|
if (!bValidAlias)
|
|
{
|
|
return false;
|
|
}
|
|
mux_strncpy(pAlias5, pValidAlias, MAX_ALIAS_LEN);
|
|
|
|
// Skip any leading space before the channel name.
|
|
//
|
|
p++;
|
|
while (mux_isspace(*p))
|
|
{
|
|
p++;
|
|
}
|
|
|
|
if (*p == '\0')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The rest of the line is the channel name.
|
|
//
|
|
*ppChannelName = StringClone(p);
|
|
return true;
|
|
}
|
|
|
|
static bool ReadListOfNumbers(FILE* fp, const int cnt, int anum[])
|
|
{
|
|
UTF8 buffer[200];
|
|
if (fgets(reinterpret_cast<char*>(buffer), sizeof(buffer), fp))
|
|
{
|
|
UTF8* p = buffer;
|
|
for (int i = 0; i < cnt; i++)
|
|
{
|
|
if (mux_isdigit(p[0])
|
|
|| ('-' == p[0]
|
|
&& mux_isdigit(p[1])))
|
|
{
|
|
anum[i] = mux_atol(p);
|
|
do
|
|
{
|
|
p++;
|
|
}
|
|
while (mux_isdigit(*p));
|
|
|
|
if (' ' == *p)
|
|
{
|
|
p++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ('\n' == *p)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Perform cleanup of comsystem data for players with 0 channels or player
|
|
// objects that were destroyed.
|
|
//
|
|
void purge_comsystem(void)
|
|
{
|
|
#ifdef ABORT_PURGE_COMSYS
|
|
return;
|
|
#endif // ABORT_PURGE_COMSYS
|
|
|
|
for (auto it = comsys_table.begin(); it != comsys_table.end(); )
|
|
{
|
|
const comsys_t &c = it->second;
|
|
if (c.aliases.empty())
|
|
{
|
|
it = comsys_table.erase(it);
|
|
continue;
|
|
}
|
|
if (isPlayer(c.who))
|
|
{
|
|
++it;
|
|
continue;
|
|
}
|
|
if (God(Owner(c.who))
|
|
&& Going(c.who))
|
|
{
|
|
it = comsys_table.erase(it);
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save Comsys channel data to the indicated file.
|
|
//
|
|
void save_channels(FILE* fp)
|
|
{
|
|
purge_comsystem();
|
|
|
|
int np = static_cast<int>(comsys_table.size());
|
|
|
|
mux_fprintf(fp, T("%d\n"), np);
|
|
for (auto &kv : comsys_table)
|
|
{
|
|
const comsys_t &c = kv.second;
|
|
|
|
// Write user dbref and # of channels.
|
|
//
|
|
mux_fprintf(fp, T("%d %d\n"), c.who, static_cast<int>(c.aliases.size()));
|
|
for (size_t j = 0; j < c.aliases.size(); j++)
|
|
{
|
|
// Write channel alias and channel name.
|
|
//
|
|
mux_fprintf(fp, T("%s %s\n"),
|
|
reinterpret_cast<const UTF8 *>(c.aliases[j].alias.c_str()),
|
|
reinterpret_cast<const UTF8 *>(c.aliases[j].channel.c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
static comsys_t* get_comsys(const dbref which)
|
|
{
|
|
if (which < 0)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
comsys_t &c = comsys_table[which];
|
|
c.who = which;
|
|
return &c;
|
|
}
|
|
|
|
void del_comsys(const dbref who)
|
|
{
|
|
comsys_table.erase(who);
|
|
}
|
|
|
|
// Sort aliases.
|
|
//
|
|
void sort_com_aliases(comsys_t &c)
|
|
{
|
|
std::sort(c.aliases.begin(), c.aliases.end(),
|
|
[](const com_alias &a, const com_alias &b) { return a.alias < b.alias; });
|
|
}
|
|
|
|
// Lookup player's comsys data and find the channel associated with
|
|
// the given alias.
|
|
//
|
|
static const UTF8* get_channel_from_alias(dbref player, UTF8* alias)
|
|
{
|
|
auto it = comsys_table.find(player);
|
|
if (it == comsys_table.end())
|
|
{
|
|
return T("");
|
|
}
|
|
const comsys_t &c = it->second;
|
|
for (const auto &ca : c.aliases)
|
|
{
|
|
if (ca.alias == reinterpret_cast<const char *>(alias))
|
|
{
|
|
return reinterpret_cast<const UTF8 *>(ca.channel.c_str());
|
|
}
|
|
}
|
|
return T("");
|
|
}
|
|
|
|
// Version 5 start on 2026-MAR-04
|
|
//
|
|
// -- Adds bGagJoinLeave as a 4th number on each user line.
|
|
//
|
|
void load_comsystem_V5(FILE* fp)
|
|
{
|
|
LBuf temp = LBuf_Src("load_comsystem_V5");
|
|
|
|
int nc = 0;
|
|
if (nullptr == fgets(reinterpret_cast<char*>(temp.get()), LBUF_SIZE, fp))
|
|
{
|
|
return;
|
|
}
|
|
nc = mux_atol(temp);
|
|
|
|
for (int i = 0; i < nc; i++)
|
|
{
|
|
int anum[10];
|
|
|
|
auto ch = new channel();
|
|
|
|
size_t nChannel = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
if (nChannel > MAX_CHANNEL_LEN)
|
|
{
|
|
nChannel = MAX_CHANNEL_LEN;
|
|
}
|
|
if (temp[nChannel - 1] == '\n')
|
|
{
|
|
// Get rid of trailing '\n'.
|
|
//
|
|
nChannel--;
|
|
}
|
|
memcpy(ch->name, temp, nChannel);
|
|
ch->name[nChannel] = '\0';
|
|
|
|
size_t nHeader = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
if (nHeader > MAX_HEADER_LEN)
|
|
{
|
|
nHeader = MAX_HEADER_LEN;
|
|
}
|
|
if (temp[nHeader - 1] == '\n')
|
|
{
|
|
nHeader--;
|
|
}
|
|
memcpy(ch->header, temp, nHeader);
|
|
ch->header[nHeader] = '\0';
|
|
|
|
vector<UTF8> channel_name_vector(ch->name, ch->name+nChannel);
|
|
mudstate.channel_names.insert(make_pair(channel_name_vector, ch));
|
|
|
|
ch->type = 127;
|
|
ch->temp1 = 0;
|
|
ch->temp2 = 0;
|
|
ch->charge = 0;
|
|
ch->charge_who = NOTHING;
|
|
ch->amount_col = 0;
|
|
ch->num_messages = 0;
|
|
ch->chan_obj = NOTHING;
|
|
|
|
mux_assert(ReadListOfNumbers(fp, 8, anum));
|
|
ch->type = anum[0];
|
|
ch->temp1 = anum[1];
|
|
ch->temp2 = anum[2];
|
|
ch->charge = anum[3];
|
|
ch->charge_who = anum[4];
|
|
ch->amount_col = anum[5];
|
|
ch->num_messages = anum[6];
|
|
ch->chan_obj = anum[7];
|
|
|
|
int num_users_to_read = 0;
|
|
mux_assert(ReadListOfNumbers(fp, 1, &num_users_to_read));
|
|
if (num_users_to_read > 0)
|
|
{
|
|
for (int j = 0; j < num_users_to_read; j++)
|
|
{
|
|
comuser t_user;
|
|
|
|
t_user.who = NOTHING;
|
|
t_user.bUserIsOn = false;
|
|
t_user.ComTitleStatus = false;
|
|
|
|
mux_assert(ReadListOfNumbers(fp, 4, anum));
|
|
t_user.who = anum[0];
|
|
t_user.bUserIsOn = (anum[1] ? true : false);
|
|
t_user.ComTitleStatus = (anum[2] ? true : false);
|
|
t_user.bGagJoinLeave = (anum[3] ? true : false);
|
|
|
|
// Read Comtitle.
|
|
//
|
|
size_t nTitle = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
const UTF8* pTitle = temp;
|
|
|
|
if (!Good_dbref(t_user.who))
|
|
{
|
|
Log.tinyprintf(
|
|
T("load_comsystem: dbref %d out of range [0, %d)." ENDLINE), t_user.who, mudstate.db_top);
|
|
}
|
|
else if (isGarbage(t_user.who))
|
|
{
|
|
Log.tinyprintf(T("load_comsystem: dbref is GARBAGE." ENDLINE), t_user.who);
|
|
}
|
|
else
|
|
{
|
|
// Validate comtitle.
|
|
//
|
|
if (3 < nTitle && temp[0] == 't' && temp[1] == ':')
|
|
{
|
|
pTitle = temp + 2;
|
|
nTitle -= 2;
|
|
if (pTitle[nTitle - 1] == '\n')
|
|
{
|
|
// Get rid of trailing '\n'.
|
|
//
|
|
nTitle--;
|
|
}
|
|
if (nTitle <= 0 || MAX_TITLE_LEN < nTitle)
|
|
{
|
|
nTitle = 0;
|
|
pTitle = temp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nTitle = 0;
|
|
}
|
|
|
|
t_user.title.assign(reinterpret_cast<const char *>(pTitle), nTitle);
|
|
|
|
auto result = ch->users.emplace(t_user.who, std::move(t_user));
|
|
comuser &user = result.first->second;
|
|
|
|
if (!(isPlayer(user.who))
|
|
&& !(Going(user.who)
|
|
&& (God(Owner(user.who)))))
|
|
{
|
|
do_joinchannel(user.who, ch);
|
|
}
|
|
user.bConnected = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Version 4 start on 2007-MAR-17
|
|
//
|
|
// -- Supports UTF-8 and ANSI as code-points.
|
|
// -- Relies on a version number at the top of the file instead of within
|
|
// this section.
|
|
//
|
|
void load_comsystem_V4(FILE* fp)
|
|
{
|
|
LBuf temp = LBuf_Src("load_comsystem_V4");
|
|
|
|
int nc = 0;
|
|
if (nullptr == fgets(reinterpret_cast<char*>(temp.get()), LBUF_SIZE, fp))
|
|
{
|
|
return;
|
|
}
|
|
nc = mux_atol(temp);
|
|
|
|
for (int i = 0; i < nc; i++)
|
|
{
|
|
int anum[10];
|
|
|
|
auto ch = new channel();
|
|
|
|
size_t nChannel = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
if (nChannel > MAX_CHANNEL_LEN)
|
|
{
|
|
nChannel = MAX_CHANNEL_LEN;
|
|
}
|
|
if (temp[nChannel - 1] == '\n')
|
|
{
|
|
// Get rid of trailing '\n'.
|
|
//
|
|
nChannel--;
|
|
}
|
|
memcpy(ch->name, temp, nChannel);
|
|
ch->name[nChannel] = '\0';
|
|
|
|
size_t nHeader = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
if (nHeader > MAX_HEADER_LEN)
|
|
{
|
|
nHeader = MAX_HEADER_LEN;
|
|
}
|
|
if (temp[nHeader - 1] == '\n')
|
|
{
|
|
nHeader--;
|
|
}
|
|
memcpy(ch->header, temp, nHeader);
|
|
ch->header[nHeader] = '\0';
|
|
|
|
vector<UTF8> channel_name_vector(ch->name, ch->name+nChannel);
|
|
mudstate.channel_names.insert(make_pair(channel_name_vector, ch));
|
|
|
|
ch->type = 127;
|
|
ch->temp1 = 0;
|
|
ch->temp2 = 0;
|
|
ch->charge = 0;
|
|
ch->charge_who = NOTHING;
|
|
ch->amount_col = 0;
|
|
ch->num_messages = 0;
|
|
ch->chan_obj = NOTHING;
|
|
|
|
mux_assert(ReadListOfNumbers(fp, 8, anum));
|
|
ch->type = anum[0];
|
|
ch->temp1 = anum[1];
|
|
ch->temp2 = anum[2];
|
|
ch->charge = anum[3];
|
|
ch->charge_who = anum[4];
|
|
ch->amount_col = anum[5];
|
|
ch->num_messages = anum[6];
|
|
ch->chan_obj = anum[7];
|
|
|
|
int num_users_to_read = 0;
|
|
mux_assert(ReadListOfNumbers(fp, 1, &num_users_to_read));
|
|
if (num_users_to_read > 0)
|
|
{
|
|
for (int j = 0; j < num_users_to_read; j++)
|
|
{
|
|
comuser t_user;
|
|
|
|
t_user.who = NOTHING;
|
|
t_user.bUserIsOn = false;
|
|
t_user.ComTitleStatus = false;
|
|
|
|
mux_assert(ReadListOfNumbers(fp, 3, anum));
|
|
t_user.who = anum[0];
|
|
t_user.bUserIsOn = (anum[1] ? true : false);
|
|
t_user.ComTitleStatus = (anum[2] ? true : false);
|
|
|
|
// Read Comtitle.
|
|
//
|
|
size_t nTitle = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
const UTF8* pTitle = temp;
|
|
|
|
if (!Good_dbref(t_user.who))
|
|
{
|
|
Log.tinyprintf(
|
|
T("load_comsystem: dbref %d out of range [0, %d)." ENDLINE), t_user.who, mudstate.db_top);
|
|
}
|
|
else if (isGarbage(t_user.who))
|
|
{
|
|
Log.tinyprintf(T("load_comsystem: dbref is GARBAGE." ENDLINE), t_user.who);
|
|
}
|
|
else
|
|
{
|
|
// Validate comtitle.
|
|
//
|
|
if (3 < nTitle && temp[0] == 't' && temp[1] == ':')
|
|
{
|
|
pTitle = temp + 2;
|
|
nTitle -= 2;
|
|
if (pTitle[nTitle - 1] == '\n')
|
|
{
|
|
// Get rid of trailing '\n'.
|
|
//
|
|
nTitle--;
|
|
}
|
|
if (nTitle <= 0 || MAX_TITLE_LEN < nTitle)
|
|
{
|
|
nTitle = 0;
|
|
pTitle = temp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nTitle = 0;
|
|
}
|
|
|
|
t_user.title.assign(reinterpret_cast<const char *>(pTitle), nTitle);
|
|
|
|
auto result = ch->users.emplace(t_user.who, std::move(t_user));
|
|
comuser &user = result.first->second;
|
|
|
|
if (!(isPlayer(user.who))
|
|
&& !(Going(user.who)
|
|
&& (God(Owner(user.who)))))
|
|
{
|
|
do_joinchannel(user.who, ch);
|
|
}
|
|
user.bConnected = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load comsys database types 0, 1, 2 or 3.
|
|
// Used Prior to Version 4 (2007-MAR-17)
|
|
//
|
|
void load_comsystem_V0123(FILE* fp)
|
|
{
|
|
int ver = 0;
|
|
LBuf temp = LBuf_Src("load_comsystem_V0123");
|
|
|
|
int nc = 0;
|
|
if (nullptr == fgets(reinterpret_cast<char *>(temp.get()), LBUF_SIZE, fp))
|
|
{
|
|
return;
|
|
}
|
|
if (!strncmp(reinterpret_cast<char *>(temp.get()), "+V", 2))
|
|
{
|
|
// +V2 has colored headers.
|
|
//
|
|
ver = mux_atol(temp + 2);
|
|
if (ver < 1 || 3 < ver)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mux_assert(ReadListOfNumbers(fp, 1, &nc));
|
|
}
|
|
else
|
|
{
|
|
nc = mux_atol(temp);
|
|
}
|
|
|
|
for (int i = 0; i < nc; i++)
|
|
{
|
|
int anum[10];
|
|
|
|
auto ch = new channel();
|
|
|
|
size_t nChannel = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
if (temp[nChannel - 1] == '\n')
|
|
{
|
|
// Get rid of trailing '\n'.
|
|
//
|
|
nChannel--;
|
|
temp[nChannel] = '\0';
|
|
}
|
|
|
|
// Convert entire line to UTF-8 including ANSI escapes.
|
|
//
|
|
const UTF8* pBufferUnicode = ConvertToUTF8(reinterpret_cast<char*>(temp.get()), &nChannel);
|
|
if (MAX_CHANNEL_LEN < nChannel)
|
|
{
|
|
nChannel = MAX_CHANNEL_LEN;
|
|
while (0 < nChannel
|
|
&& UTF8_CONTINUE <= utf8_FirstByte[temp[nChannel - 1]])
|
|
{
|
|
nChannel--;
|
|
}
|
|
}
|
|
|
|
memcpy(ch->name, pBufferUnicode, nChannel);
|
|
ch->name[nChannel] = '\0';
|
|
|
|
if (ver >= 2)
|
|
{
|
|
size_t nHeader = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
if (temp[nHeader - 1] == '\n')
|
|
{
|
|
nHeader--;
|
|
temp[nHeader] = '\0';
|
|
}
|
|
|
|
// Convert entire line to UTF-8 including ANSI escapes.
|
|
//
|
|
pBufferUnicode = ConvertToUTF8(reinterpret_cast<char*>(temp.get()), &nHeader);
|
|
if (MAX_HEADER_LEN < nHeader)
|
|
{
|
|
nHeader = MAX_HEADER_LEN;
|
|
while (0 < nHeader
|
|
&& UTF8_CONTINUE <= utf8_FirstByte[static_cast<unsigned char>(pBufferUnicode[nHeader - 1])])
|
|
{
|
|
nHeader--;
|
|
}
|
|
}
|
|
|
|
memcpy(ch->header, pBufferUnicode, nHeader);
|
|
ch->header[nHeader] = '\0';
|
|
}
|
|
|
|
vector<UTF8> channel_name_vector(ch->name, ch->name + nChannel);
|
|
mudstate.channel_names.insert(make_pair(channel_name_vector, ch));
|
|
|
|
ch->type = 127;
|
|
ch->temp1 = 0;
|
|
ch->temp2 = 0;
|
|
ch->charge = 0;
|
|
ch->charge_who = NOTHING;
|
|
ch->amount_col = 0;
|
|
ch->num_messages = 0;
|
|
ch->chan_obj = NOTHING;
|
|
|
|
if (ver >= 1)
|
|
{
|
|
mux_assert(ReadListOfNumbers(fp, 8, anum));
|
|
ch->type = anum[0];
|
|
ch->temp1 = anum[1];
|
|
ch->temp2 = anum[2];
|
|
ch->charge = anum[3];
|
|
ch->charge_who = anum[4];
|
|
ch->amount_col = anum[5];
|
|
ch->num_messages = anum[6];
|
|
ch->chan_obj = anum[7];
|
|
}
|
|
else
|
|
{
|
|
mux_assert(ReadListOfNumbers(fp, 10, anum));
|
|
ch->type = anum[0];
|
|
// anum[1] is not used.
|
|
ch->temp1 = anum[2];
|
|
ch->temp2 = anum[3];
|
|
// anum[4] is not used.
|
|
ch->charge = anum[5];
|
|
ch->charge_who = anum[6];
|
|
ch->amount_col = anum[7];
|
|
ch->num_messages = anum[8];
|
|
ch->chan_obj = anum[9];
|
|
}
|
|
|
|
if (ver <= 1)
|
|
{
|
|
// Build colored header if not +V2 or later db.
|
|
//
|
|
if (ch->type & CHANNEL_PUBLIC)
|
|
{
|
|
mux_sprintf(temp, LBUF_SIZE, T("%s[%s%s%s%s%s]%s"), COLOR_FG_CYAN, COLOR_INTENSE,
|
|
COLOR_FG_BLUE, ch->name, COLOR_RESET, COLOR_FG_CYAN, COLOR_RESET);
|
|
}
|
|
else
|
|
{
|
|
mux_sprintf(temp, LBUF_SIZE, T("%s[%s%s%s%s%s]%s"), COLOR_FG_MAGENTA, COLOR_INTENSE,
|
|
COLOR_FG_RED, ch->name, COLOR_RESET, COLOR_FG_MAGENTA,
|
|
COLOR_RESET);
|
|
}
|
|
StripTabsAndTruncate(temp, ch->header, MAX_HEADER_LEN, MAX_HEADER_LEN);
|
|
}
|
|
|
|
int num_users_to_read = 0;
|
|
mux_assert(ReadListOfNumbers(fp, 1, &num_users_to_read));
|
|
if (num_users_to_read > 0)
|
|
{
|
|
for (int j = 0; j < num_users_to_read; j++)
|
|
{
|
|
comuser t_user;
|
|
|
|
t_user.who = NOTHING;
|
|
t_user.bUserIsOn = false;
|
|
t_user.ComTitleStatus = false;
|
|
|
|
if (ver == 3)
|
|
{
|
|
mux_assert(ReadListOfNumbers(fp, 3, anum));
|
|
t_user.who = anum[0];
|
|
t_user.bUserIsOn = (anum[1] ? true : false);
|
|
t_user.ComTitleStatus = (anum[2] ? true : false);
|
|
}
|
|
else
|
|
{
|
|
t_user.ComTitleStatus = true;
|
|
if (ver)
|
|
{
|
|
mux_assert(ReadListOfNumbers(fp, 2, anum));
|
|
t_user.who = anum[0];
|
|
t_user.bUserIsOn = (anum[1] ? true : false);
|
|
}
|
|
else
|
|
{
|
|
mux_assert(ReadListOfNumbers(fp, 4, anum));
|
|
t_user.who = anum[0];
|
|
// anum[1] is not used.
|
|
// anum[2] is not used.
|
|
t_user.bUserIsOn = (anum[3] ? true : false);
|
|
}
|
|
}
|
|
|
|
// Read Comtitle.
|
|
//
|
|
size_t nTitle = GetLineTrunc(temp, LBUF_SIZE, fp);
|
|
|
|
// Convert entire line to UTF-8 including ANSI escapes.
|
|
//
|
|
UTF8* pTitle = ConvertToUTF8(reinterpret_cast<char*>(temp.get()), &nTitle);
|
|
|
|
if (!Good_dbref(t_user.who))
|
|
{
|
|
Log.tinyprintf(
|
|
T("load_comsystem: dbref %d out of range [0, %d)." ENDLINE), t_user.who, mudstate.db_top);
|
|
}
|
|
else if (isGarbage(t_user.who))
|
|
{
|
|
Log.tinyprintf(T("load_comsystem: dbref is GARBAGE." ENDLINE), t_user.who);
|
|
}
|
|
else
|
|
{
|
|
// Validate comtitle.
|
|
//
|
|
if (3 < nTitle && pTitle[0] == 't' && pTitle[1] == ':')
|
|
{
|
|
pTitle = pTitle + 2;
|
|
nTitle -= 2;
|
|
if (pTitle[nTitle - 1] == '\n')
|
|
{
|
|
// Get rid of trailing '\n'.
|
|
//
|
|
nTitle--;
|
|
}
|
|
|
|
if (nTitle <= 0
|
|
|| MAX_TITLE_LEN < nTitle)
|
|
{
|
|
nTitle = 0;
|
|
pTitle = temp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nTitle = 0;
|
|
}
|
|
|
|
t_user.title.assign(reinterpret_cast<const char *>(pTitle), nTitle);
|
|
|
|
auto result = ch->users.emplace(t_user.who, std::move(t_user));
|
|
comuser &user = result.first->second;
|
|
|
|
if (!(isPlayer(user.who))
|
|
&& !(Going(user.who)
|
|
&& (God(Owner(user.who)))))
|
|
{
|
|
do_joinchannel(user.who, ch);
|
|
}
|
|
user.bConnected = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void load_channels_V4(FILE* fp)
|
|
{
|
|
LBuf buffer = LBuf_Src("load_channels_V4");
|
|
|
|
int np = 0;
|
|
mux_assert(ReadListOfNumbers(fp, 1, &np));
|
|
for (int i = 0; i < np; i++)
|
|
{
|
|
int anum[2];
|
|
int who = 0;
|
|
int numchannels = 0;
|
|
mux_assert(ReadListOfNumbers(fp, 2, anum));
|
|
who = anum[0];
|
|
numchannels = anum[1];
|
|
|
|
comsys_t c;
|
|
c.who = who;
|
|
|
|
if (numchannels > 0)
|
|
{
|
|
for (int j = 0; j < numchannels; j++)
|
|
{
|
|
size_t n = GetLineTrunc(buffer, LBUF_SIZE, fp);
|
|
if (buffer[n - 1] == '\n')
|
|
{
|
|
// Get rid of trailing '\n'.
|
|
//
|
|
n--;
|
|
buffer[n] = '\0';
|
|
}
|
|
UTF8 aliasBuf[MAX_ALIAS_LEN + 1];
|
|
UTF8 *channelName = nullptr;
|
|
if (ParseChannelLine(buffer, aliasBuf, &channelName))
|
|
{
|
|
com_alias ca;
|
|
ca.alias.assign(reinterpret_cast<const char *>(aliasBuf));
|
|
ca.channel.assign(reinterpret_cast<const char *>(channelName));
|
|
c.aliases.push_back(std::move(ca));
|
|
MEMFREE(channelName);
|
|
}
|
|
}
|
|
sort_com_aliases(c);
|
|
}
|
|
if (Good_obj(c.who))
|
|
{
|
|
comsys_table[c.who] = std::move(c);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("Invalid dbref %d." ENDLINE), who);
|
|
}
|
|
purge_comsystem();
|
|
}
|
|
}
|
|
|
|
void load_channels_V0123(FILE* fp)
|
|
{
|
|
char buffer[LBUF_SIZE];
|
|
|
|
int np = 0;
|
|
mux_assert(ReadListOfNumbers(fp, 1, &np));
|
|
for (int i = 0; i < np; i++)
|
|
{
|
|
int anum[2];
|
|
int who = 0;
|
|
int numchannels = 0;
|
|
mux_assert(ReadListOfNumbers(fp, 2, anum));
|
|
who = anum[0];
|
|
numchannels = anum[1];
|
|
|
|
comsys_t c;
|
|
c.who = who;
|
|
|
|
if (numchannels > 0)
|
|
{
|
|
for (int j = 0; j < numchannels; j++)
|
|
{
|
|
size_t n = GetLineTrunc(reinterpret_cast<UTF8*>(buffer), sizeof(buffer), fp);
|
|
if (buffer[n - 1] == '\n')
|
|
{
|
|
// Get rid of trailing '\n'.
|
|
//
|
|
n--;
|
|
buffer[n] = '\0';
|
|
}
|
|
|
|
// Convert the entire line to UTF8 before parsing the line
|
|
// into fields. The first field no ANSI in it anyway.
|
|
//
|
|
size_t nBufferUnicode;
|
|
UTF8* pBufferUnicode = ConvertToUTF8(buffer, &nBufferUnicode);
|
|
UTF8 aliasBuf[MAX_ALIAS_LEN + 1];
|
|
UTF8 *channelName = nullptr;
|
|
if (ParseChannelLine(pBufferUnicode, aliasBuf, &channelName))
|
|
{
|
|
com_alias ca;
|
|
ca.alias.assign(reinterpret_cast<const char *>(aliasBuf));
|
|
ca.channel.assign(reinterpret_cast<const char *>(channelName));
|
|
c.aliases.push_back(std::move(ca));
|
|
MEMFREE(channelName);
|
|
}
|
|
}
|
|
sort_com_aliases(c);
|
|
}
|
|
if (Good_obj(c.who))
|
|
{
|
|
comsys_table[c.who] = std::move(c);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("Invalid dbref %d." ENDLINE), who);
|
|
}
|
|
purge_comsystem();
|
|
}
|
|
}
|
|
|
|
void load_comsys_V5(FILE* fp)
|
|
{
|
|
char buffer[200];
|
|
if (fgets(buffer, sizeof(buffer), fp)
|
|
&& strcmp(buffer, "*** Begin CHANNELS ***\n") == 0)
|
|
{
|
|
load_channels_V4(fp);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("Error: Couldn\xE2\x80\x99t find Begin CHANNELS." ENDLINE));
|
|
return;
|
|
}
|
|
|
|
if (fgets(buffer, sizeof(buffer), fp)
|
|
&& strcmp(buffer, "*** Begin COMSYS ***\n") == 0)
|
|
{
|
|
load_comsystem_V5(fp);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("Error: Couldn\xE2\x80\x99t find Begin COMSYS." ENDLINE));
|
|
}
|
|
}
|
|
|
|
void load_comsys_V4(FILE* fp)
|
|
{
|
|
char buffer[200];
|
|
if (fgets(buffer, sizeof(buffer), fp)
|
|
&& strcmp(buffer, "*** Begin CHANNELS ***\n") == 0)
|
|
{
|
|
load_channels_V4(fp);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("Error: Couldn\xE2\x80\x99t find Begin CHANNELS." ENDLINE));
|
|
return;
|
|
}
|
|
|
|
if (fgets(buffer, sizeof(buffer), fp)
|
|
&& strcmp(buffer, "*** Begin COMSYS ***\n") == 0)
|
|
{
|
|
load_comsystem_V4(fp);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("Error: Couldn\xE2\x80\x99t find Begin COMSYS." ENDLINE));
|
|
}
|
|
}
|
|
|
|
void load_comsys_V0123(FILE* fp)
|
|
{
|
|
char buffer[200];
|
|
if (fgets(buffer, sizeof(buffer), fp)
|
|
&& strcmp(buffer, "*** Begin CHANNELS ***\n") == 0)
|
|
{
|
|
load_channels_V0123(fp);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("Error: Couldn\xE2\x80\x99t find Begin CHANNELS." ENDLINE));
|
|
return;
|
|
}
|
|
|
|
if (fgets(buffer, sizeof(buffer), fp)
|
|
&& strcmp(buffer, "*** Begin COMSYS ***\n") == 0)
|
|
{
|
|
load_comsystem_V0123(fp);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("Error: Couldn\xE2\x80\x99t find Begin COMSYS." ENDLINE));
|
|
}
|
|
}
|
|
|
|
|
|
// Open the given filename, check version, and attempt to read comsystem
|
|
// data as indicated by the version number at the head of the file.
|
|
//
|
|
void load_comsys(UTF8* filename)
|
|
{
|
|
comsys_table.clear();
|
|
|
|
FILE* fp;
|
|
if (!mux_fopen(&fp, filename, T("rb")))
|
|
{
|
|
Log.tinyprintf(T("Error: Couldn\xE2\x80\x99t find %s." ENDLINE), filename);
|
|
}
|
|
else
|
|
{
|
|
Log.tinyprintf(T("LOADING: %s" ENDLINE), filename);
|
|
const int ch = getc(fp);
|
|
if (EOF == ch)
|
|
{
|
|
Log.tinyprintf(T("Error: Couldn\xE2\x80\x99t read first byte."));
|
|
}
|
|
else
|
|
{
|
|
ungetc(ch, fp);
|
|
if ('+' == ch)
|
|
{
|
|
// Version 4 or later.
|
|
//
|
|
UTF8 nbuf1[8];
|
|
|
|
// Read the version number.
|
|
//
|
|
if (fgets(reinterpret_cast<char*>(nbuf1), sizeof(nbuf1), fp))
|
|
{
|
|
if (strncmp(reinterpret_cast<char*>(nbuf1), "+V5", 3) == 0)
|
|
{
|
|
// Started v5 on 2026-MAR-04.
|
|
//
|
|
load_comsys_V5(fp);
|
|
}
|
|
else if (strncmp(reinterpret_cast<char*>(nbuf1), "+V4", 3) == 0)
|
|
{
|
|
// Started v4 on 2007-MAR-13.
|
|
//
|
|
load_comsys_V4(fp);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
load_comsys_V0123(fp);
|
|
}
|
|
}
|
|
|
|
mux_fclose(fp);
|
|
Log.tinyprintf(T("LOADING: %s (done)" ENDLINE), filename);
|
|
}
|
|
}
|
|
|
|
// Save channel data and some user state info on a per-channel basis.
|
|
//
|
|
void save_comsystem(FILE* fp)
|
|
{
|
|
// Number of channels.
|
|
//
|
|
mux_fprintf(fp, T("%d\n"), static_cast<int>(mudstate.channel_names.size()));
|
|
for (auto it = mudstate.channel_names.begin(); it != mudstate.channel_names.end(); ++it)
|
|
{
|
|
const auto ch = it->second;
|
|
|
|
// Channel name.
|
|
//
|
|
mux_fprintf(fp, T("%s\n"), ch->name);
|
|
|
|
// Channel header.
|
|
//
|
|
mux_fprintf(fp, T("%s\n"), ch->header);
|
|
|
|
mux_fprintf(fp, T("%d %d %d %d %d %d %d %d\n"), ch->type, ch->temp1,
|
|
ch->temp2, ch->charge, ch->charge_who, ch->amount_col,
|
|
ch->num_messages, ch->chan_obj);
|
|
|
|
// Count the number of 'valid' users to dump.
|
|
//
|
|
int number_of_valid_users = 0;
|
|
for (auto &kv : ch->users)
|
|
{
|
|
const comuser &user = kv.second;
|
|
if (user.who >= 0 && user.who < mudstate.db_top)
|
|
{
|
|
number_of_valid_users++;
|
|
}
|
|
}
|
|
|
|
// Number of users on this channel.
|
|
//
|
|
mux_fprintf(fp, T("%d\n"), number_of_valid_users);
|
|
for (auto &kv : ch->users)
|
|
{
|
|
const comuser &user = kv.second;
|
|
if (user.who >= 0 && user.who < mudstate.db_top)
|
|
{
|
|
// Write user state: dbref, on flag, comtitle status, and gag status.
|
|
//
|
|
mux_fprintf(fp, T("%d %d %d %d\n"), user.who, user.bUserIsOn, user.ComTitleStatus, user.bGagJoinLeave);
|
|
|
|
// Write user title data.
|
|
//
|
|
if (!user.title.empty())
|
|
{
|
|
mux_fprintf(fp, T("t:%s\n"), reinterpret_cast<const UTF8 *>(user.title.c_str()));
|
|
}
|
|
else
|
|
{
|
|
mux_fprintf(fp, T("t:\n"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// call_mogrifier: Evaluate a MOGRIFY`<suffix> attribute on the channel
|
|
// object. Returns an alloc_lbuf'd result (caller must free_lbuf) or
|
|
// nullptr if the attribute doesn't exist or is empty.
|
|
//
|
|
// Args passed as %0..%N-1.
|
|
//
|
|
static UTF8 *call_mogrifier
|
|
(
|
|
const dbref chan_obj,
|
|
const dbref executor,
|
|
const UTF8 *suffix,
|
|
const UTF8 *args[],
|
|
int nargs
|
|
)
|
|
{
|
|
if (!Good_obj(chan_obj))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Build attribute name: MOGRIFY`<suffix>
|
|
//
|
|
UTF8 aname[SBUF_SIZE];
|
|
mux_sprintf(aname, sizeof(aname), T("MOGRIFY`%s"), suffix);
|
|
|
|
ATTR *pattr = atr_str(aname);
|
|
if (!pattr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf atext = LBuf_Adopt(atr_pget(chan_obj, pattr->number, &aowner, &aflags));
|
|
if (!*atext.get())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
LBufPtr result = LBufPtr_Src("call_mogrifier");
|
|
UTF8 *bp = result;
|
|
mux_exec(atext, LBUF_SIZE-1, result, &bp, chan_obj, chan_obj, executor,
|
|
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP),
|
|
args, nargs);
|
|
*bp = '\0';
|
|
return result.release();
|
|
}
|
|
|
|
static void BuildChannelMessage
|
|
(
|
|
const bool bSpoof,
|
|
const UTF8* pHeader,
|
|
const struct comuser* user,
|
|
const dbref ch_obj,
|
|
const UTF8* pPose,
|
|
UTF8** messNormal,
|
|
UTF8** messNoComtitle
|
|
)
|
|
{
|
|
// Allocate necessary buffers.
|
|
//
|
|
*messNormal = alloc_lbuf("BCM.messNormal");
|
|
*messNoComtitle = nullptr;
|
|
if (!bSpoof)
|
|
{
|
|
*messNoComtitle = alloc_lbuf("BCM.messNoComtitle");
|
|
}
|
|
|
|
// Comtitle Check.
|
|
//
|
|
const bool hasComTitle = !user->title.empty();
|
|
|
|
UTF8* mnptr = *messNormal; // Message without comtitle removal.
|
|
UTF8* mncptr = *messNoComtitle; // Message with comtitle removal.
|
|
|
|
safe_str(pHeader, *messNormal, &mnptr);
|
|
safe_chr(' ', *messNormal, &mnptr);
|
|
if (!bSpoof)
|
|
{
|
|
safe_str(pHeader, *messNoComtitle, &mncptr);
|
|
safe_chr(' ', *messNoComtitle, &mncptr);
|
|
}
|
|
|
|
// Don't evaluate a title if there isn't one to parse or evaluation of
|
|
// comtitles is disabled. If they're set spoof, ComTitleStatus doesn't
|
|
// matter.
|
|
//
|
|
if (hasComTitle && (user->ComTitleStatus || bSpoof))
|
|
{
|
|
if (mudconf.eval_comtitle)
|
|
{
|
|
// Evaluate the comtitle as code.
|
|
//
|
|
LBuf TempToEval = LBuf_Src("eval_comtitle");
|
|
mux_strncpy(TempToEval, reinterpret_cast<const UTF8 *>(user->title.c_str()), LBUF_SIZE - 1);
|
|
mux_exec(TempToEval, LBUF_SIZE - 1, *messNormal, &mnptr, user->who, user->who, user->who,
|
|
EV_FCHECK | EV_EVAL | EV_TOP, nullptr, 0);
|
|
}
|
|
else
|
|
{
|
|
safe_str(reinterpret_cast<const UTF8 *>(user->title.c_str()), *messNormal, &mnptr);
|
|
}
|
|
if (!bSpoof)
|
|
{
|
|
safe_chr(' ', *messNormal, &mnptr);
|
|
safe_str(Moniker(user->who), *messNormal, &mnptr);
|
|
safe_str(Moniker(user->who), *messNoComtitle, &mncptr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
safe_str(Moniker(user->who), *messNormal, &mnptr);
|
|
if (!bSpoof)
|
|
{
|
|
safe_str(Moniker(user->who), *messNoComtitle, &mncptr);
|
|
}
|
|
}
|
|
|
|
bool bChannelSayString = false;
|
|
bool bChannelSpeechMod = false;
|
|
|
|
if (Good_obj(ch_obj))
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
{
|
|
LBuf test_attr = LBuf_Adopt(atr_get("BuildChannelMessage.1304", ch_obj,
|
|
A_SAYSTRING, &aowner, &aflags));
|
|
if ('\0' != test_attr[0])
|
|
{
|
|
bChannelSayString = true;
|
|
}
|
|
}
|
|
|
|
{
|
|
LBuf test_attr = LBuf_Adopt(atr_get("BuildChannelMessage.1312", ch_obj,
|
|
A_SPEECHMOD, &aowner, &aflags));
|
|
if ('\0' != test_attr[0])
|
|
{
|
|
bChannelSpeechMod = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
UTF8* saystring = nullptr;
|
|
UTF8* newPose = nullptr;
|
|
|
|
switch (pPose[0])
|
|
{
|
|
case ':':
|
|
pPose++;
|
|
if (' ' == *pPose)
|
|
{
|
|
pPose++;
|
|
}
|
|
else
|
|
{
|
|
safe_chr(' ', *messNormal, &mnptr);
|
|
if (!bSpoof)
|
|
{
|
|
safe_chr(' ', *messNoComtitle, &mncptr);
|
|
}
|
|
}
|
|
newPose = modSpeech(bChannelSpeechMod ? ch_obj : user->who, pPose, true, T("channel/pose"));
|
|
if (newPose)
|
|
{
|
|
pPose = newPose;
|
|
}
|
|
safe_str(pPose, *messNormal, &mnptr);
|
|
if (!bSpoof)
|
|
{
|
|
safe_str(pPose, *messNoComtitle, &mncptr);
|
|
}
|
|
break;
|
|
|
|
case ';':
|
|
pPose++;
|
|
newPose = modSpeech(bChannelSpeechMod ? ch_obj : user->who, pPose, true, T("channel/pose"));
|
|
if (newPose)
|
|
{
|
|
pPose = newPose;
|
|
}
|
|
safe_str(pPose, *messNormal, &mnptr);
|
|
if (!bSpoof)
|
|
{
|
|
safe_str(pPose, *messNoComtitle, &mncptr);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
newPose = modSpeech(bChannelSpeechMod ? ch_obj : user->who, pPose, true, T("channel"));
|
|
if (newPose)
|
|
{
|
|
pPose = newPose;
|
|
}
|
|
saystring = modSpeech(bChannelSayString ? ch_obj : user->who, pPose, false, T("channel"));
|
|
if (saystring)
|
|
{
|
|
safe_chr(' ', *messNormal, &mnptr);
|
|
safe_str(saystring, *messNormal, &mnptr);
|
|
safe_str(T(" \xE2\x80\x9C"), *messNormal, &mnptr);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T(" says, \xE2\x80\x9C"), *messNormal, &mnptr);
|
|
}
|
|
safe_str(pPose, *messNormal, &mnptr);
|
|
safe_str(T("\xE2\x80\x9D"), *messNormal, &mnptr);
|
|
if (!bSpoof)
|
|
{
|
|
if (saystring)
|
|
{
|
|
safe_chr(' ', *messNoComtitle, &mncptr);
|
|
safe_str(saystring, *messNoComtitle, &mncptr);
|
|
safe_str(T(" \xE2\x80\x9C"), *messNoComtitle, &mncptr);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T(" says, \xE2\x80\x9C"), *messNoComtitle, &mncptr);
|
|
}
|
|
safe_str(pPose, *messNoComtitle, &mncptr);
|
|
safe_str(T("\xE2\x80\x9D"), *messNoComtitle, &mncptr);
|
|
}
|
|
break;
|
|
}
|
|
*mnptr = '\0';
|
|
if (!bSpoof)
|
|
{
|
|
*mncptr = '\0';
|
|
}
|
|
if (newPose)
|
|
{
|
|
free_lbuf(newPose);
|
|
}
|
|
if (saystring)
|
|
{
|
|
free_lbuf(saystring);
|
|
}
|
|
}
|
|
|
|
static void do_processcom(dbref player, UTF8* arg1, UTF8* arg2)
|
|
{
|
|
if (!*arg2)
|
|
{
|
|
raw_notify(player, T("No message."));
|
|
return;
|
|
}
|
|
if (3500 < strlen(reinterpret_cast<const char*>(arg2)))
|
|
{
|
|
arg2[3500] = '\0';
|
|
}
|
|
struct channel* ch = select_channel(arg1);
|
|
if (!ch)
|
|
{
|
|
raw_notify(player, tprintf(T("Unknown channel %s."), arg1));
|
|
return;
|
|
}
|
|
struct comuser* user = select_user(ch, player);
|
|
if (!user)
|
|
{
|
|
raw_notify(player, T("You are not listed as on that channel. Delete this alias and readd."));
|
|
return;
|
|
}
|
|
|
|
if (Gagged(player)
|
|
&& !Wizard(player))
|
|
{
|
|
raw_notify(player, T("GAGGED players may not speak on channels."));
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(reinterpret_cast<const char*>(arg2), "on"))
|
|
{
|
|
do_joinchannel(player, ch);
|
|
}
|
|
else if (!strcmp(reinterpret_cast<const char*>(arg2), "off"))
|
|
{
|
|
do_leavechannel(player, ch);
|
|
}
|
|
else if (!user->bUserIsOn)
|
|
{
|
|
raw_notify(player, tprintf(T("You must be on %s to do that."), arg1));
|
|
}
|
|
else if (!strcmp(reinterpret_cast<const char*>(arg2), "who"))
|
|
{
|
|
do_comwho(player, ch);
|
|
}
|
|
else if (!strncmp(reinterpret_cast<const char*>(arg2), "last", 4)
|
|
&& (arg2[4] == '\0'
|
|
|| (arg2[4] == ' '
|
|
&& is_integer(arg2 + 5, nullptr))))
|
|
{
|
|
// Parse optional number after the 'last' command.
|
|
//
|
|
int nRecall = DFLT_RECALL_REQUEST;
|
|
if (arg2[4] == ' ')
|
|
{
|
|
nRecall = mux_atol(arg2 + 5);
|
|
}
|
|
do_comlast(player, ch, nRecall);
|
|
}
|
|
else if (!test_transmit_access(player, ch))
|
|
{
|
|
raw_notify(player, T("That channel type cannot be transmitted on."));
|
|
}
|
|
else
|
|
{
|
|
if (!payfor(player, Guest(player) ? 0 : ch->charge))
|
|
{
|
|
raw_notify(player, tprintf(T("You don\xE2\x80\x99t have enough %s."), mudconf.many_coins));
|
|
return;
|
|
}
|
|
ch->amount_col += ch->charge;
|
|
sqlite_wt_channel(ch);
|
|
giveto(ch->charge_who, ch->charge);
|
|
|
|
// Check MOGRIFY`BLOCK on the channel object. If it returns a
|
|
// non-empty string, send that string to the speaker and suppress
|
|
// the message entirely.
|
|
//
|
|
if (Good_obj(ch->chan_obj))
|
|
{
|
|
UTF8 chattype[2];
|
|
chattype[0] = (arg2[0] == ':' || arg2[0] == ';') ? arg2[0] : '"';
|
|
chattype[1] = '\0';
|
|
UTF8 sdrBuf[32];
|
|
mux_sprintf(sdrBuf, sizeof(sdrBuf), T("#%d"), player);
|
|
const UTF8 *block_args[5] = {
|
|
chattype, ch->name, arg2, (const UTF8 *)Name(player), sdrBuf
|
|
};
|
|
UTF8 *block_result = call_mogrifier(ch->chan_obj, player,
|
|
T("BLOCK"), block_args, 5);
|
|
if (block_result)
|
|
{
|
|
if (*block_result)
|
|
{
|
|
raw_notify(player, block_result);
|
|
free_lbuf(block_result);
|
|
return;
|
|
}
|
|
free_lbuf(block_result);
|
|
}
|
|
}
|
|
|
|
// BuildChannelMessage allocates messNormal and messNoComtitle,
|
|
// SendChannelMessage frees them.
|
|
//
|
|
UTF8* messNormal;
|
|
UTF8* messNoComtitle;
|
|
BuildChannelMessage((ch->type & CHANNEL_SPOOF) != 0, ch->header, user,
|
|
ch->chan_obj, arg2, &messNormal, &messNoComtitle);
|
|
SendChannelMessage(player, ch, messNormal, messNoComtitle);
|
|
}
|
|
}
|
|
|
|
inline void notify_comsys(const dbref target, const dbref sender, const UTF8 *msg)
|
|
{
|
|
notify_with_cause_ooc(target, sender, msg, MSG_SRC_COMSYS);
|
|
}
|
|
|
|
// Transmit the given message as appropriate to all listening parties.
|
|
// Perform channel message logging, if configured, for the channel.
|
|
//
|
|
void SendChannelMessage
|
|
(
|
|
const dbref executor,
|
|
struct channel* ch,
|
|
UTF8* msgNormal,
|
|
UTF8* msgNoComtitle,
|
|
const bool bJoinLeaveMsg
|
|
)
|
|
{
|
|
// Transmit messages.
|
|
//
|
|
const bool bSpoof = ((ch->type & CHANNEL_SPOOF) != 0);
|
|
ch->num_messages++;
|
|
sqlite_wt_channel(ch);
|
|
|
|
// Look up the CHATFORMAT attribute number once (lazy init).
|
|
//
|
|
static int s_chatformat_atr = 0;
|
|
static bool s_chatformat_init = false;
|
|
if (!s_chatformat_init)
|
|
{
|
|
ATTR *pattr = atr_str(T("CHATFORMAT"));
|
|
s_chatformat_atr = pattr ? pattr->number : 0;
|
|
s_chatformat_init = true;
|
|
}
|
|
|
|
// Evaluate mogrifier attributes on the channel object, if present.
|
|
// These run once per message, not per listener.
|
|
//
|
|
// MOGRIFY`MESSAGE — transform the composed message text.
|
|
// MOGRIFY`FORMAT — channel-wide format (like per-player CHATFORMAT).
|
|
// MOGRIFY`OVERRIDE — if true, skip per-player CHATFORMAT.
|
|
// MOGRIFY`NOBUFFER — if true, skip recall buffer logging.
|
|
//
|
|
UTF8 *mogMsg = nullptr;
|
|
UTF8 *mogFmt = nullptr;
|
|
bool bMogOverride = false;
|
|
bool bMogNobuffer = false;
|
|
|
|
if ( Good_obj(ch->chan_obj)
|
|
&& !bJoinLeaveMsg)
|
|
{
|
|
UTF8 sdrBuf[32];
|
|
mux_sprintf(sdrBuf, sizeof(sdrBuf), T("#%d"), executor);
|
|
const UTF8 *mog_args[3] = { ch->name, msgNormal, sdrBuf };
|
|
|
|
// MOGRIFY`MESSAGE: if non-empty, replaces the message text.
|
|
//
|
|
mogMsg = call_mogrifier(ch->chan_obj, executor,
|
|
T("MESSAGE"), mog_args, 3);
|
|
|
|
// MOGRIFY`OVERRIDE: if true (non-zero), skip per-player CHATFORMAT.
|
|
//
|
|
UTF8 *mogOvr = call_mogrifier(ch->chan_obj, executor,
|
|
T("OVERRIDE"), mog_args, 3);
|
|
if (mogOvr)
|
|
{
|
|
bMogOverride = xlate(mogOvr);
|
|
free_lbuf(mogOvr);
|
|
}
|
|
|
|
// MOGRIFY`NOBUFFER: if true (non-zero), skip recall buffer.
|
|
//
|
|
UTF8 *mogNB = call_mogrifier(ch->chan_obj, executor,
|
|
T("NOBUFFER"), mog_args, 3);
|
|
if (mogNB)
|
|
{
|
|
bMogNobuffer = xlate(mogNB);
|
|
free_lbuf(mogNB);
|
|
}
|
|
|
|
// Update message args if MOGRIFY`MESSAGE replaced the text.
|
|
//
|
|
const UTF8 *fmt_msg = (mogMsg && *mogMsg) ? mogMsg : msgNormal;
|
|
const UTF8 *fmt_args[3] = { ch->name, fmt_msg, sdrBuf };
|
|
|
|
// MOGRIFY`FORMAT: channel-wide format applied to the
|
|
// (possibly mogrified) message.
|
|
//
|
|
mogFmt = call_mogrifier(ch->chan_obj, executor,
|
|
T("FORMAT"), fmt_args, 3);
|
|
}
|
|
|
|
// Determine effective message after mogrification.
|
|
//
|
|
const UTF8 *effMsgNormal = (mogMsg && *mogMsg) ? mogMsg : msgNormal;
|
|
const UTF8 *effMsgNoComtitle = (mogMsg && *mogMsg) ? mogMsg : msgNoComtitle;
|
|
|
|
for (auto &kv : ch->users)
|
|
{
|
|
comuser &user = kv.second;
|
|
if (bJoinLeaveMsg && user.bGagJoinLeave)
|
|
{
|
|
continue;
|
|
}
|
|
if (user.bConnected
|
|
&& user.bUserIsOn
|
|
&& test_receive_access(user.who, ch))
|
|
{
|
|
// If MOGRIFY`FORMAT produced output, use it (channel-wide
|
|
// formatting takes priority over per-player CHATFORMAT,
|
|
// unless the player has their own CHATFORMAT and OVERRIDE
|
|
// is not set).
|
|
//
|
|
if (mogFmt && *mogFmt && bMogOverride)
|
|
{
|
|
notify_comsys(user.who, executor, mogFmt);
|
|
continue;
|
|
}
|
|
|
|
const UTF8 *pMsg;
|
|
if (user.ComTitleStatus
|
|
|| bSpoof
|
|
|| effMsgNoComtitle == nullptr)
|
|
{
|
|
pMsg = effMsgNormal;
|
|
}
|
|
else
|
|
{
|
|
pMsg = effMsgNoComtitle;
|
|
}
|
|
|
|
// Check if the receiver has a CHATFORMAT attribute
|
|
// (unless MOGRIFY`OVERRIDE suppresses it).
|
|
//
|
|
if ( 0 < s_chatformat_atr
|
|
&& !bMogOverride)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf chatfmt = LBuf_Adopt(atr_pget(user.who, s_chatformat_atr,
|
|
&aowner, &aflags));
|
|
if ('\0' != chatfmt[0])
|
|
{
|
|
LBuf fmtbuf = LBuf_Src("chatformat");
|
|
UTF8 *bp = fmtbuf;
|
|
UTF8 sdrBuf[32];
|
|
mux_sprintf(sdrBuf, sizeof(sdrBuf), T("#%d"), executor);
|
|
const UTF8 *cfa[3] = { ch->name, pMsg, sdrBuf };
|
|
mux_exec(chatfmt, LBUF_SIZE-1, fmtbuf, &bp,
|
|
user.who, user.who, executor,
|
|
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP),
|
|
cfa, 3);
|
|
*bp = '\0';
|
|
notify_comsys(user.who, executor, fmtbuf);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Use MOGRIFY`FORMAT if available, otherwise the raw message.
|
|
//
|
|
if (mogFmt && *mogFmt)
|
|
{
|
|
notify_comsys(user.who, executor, mogFmt);
|
|
}
|
|
else
|
|
{
|
|
notify_comsys(user.who, executor, pMsg);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle logging (skip if MOGRIFY`NOBUFFER).
|
|
//
|
|
const dbref obj = ch->chan_obj;
|
|
if ( Good_obj(obj)
|
|
&& !bMogNobuffer)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
int logmax = DFLT_MAX_LOG;
|
|
ATTR* pattr = atr_str(T("MAX_LOG"));
|
|
if (pattr
|
|
&& pattr->number)
|
|
{
|
|
LBuf maxbuf = LBuf_Adopt(atr_get("SendChannelMessage.1141", obj, pattr->number, &aowner, &aflags));
|
|
logmax = mux_atol(maxbuf);
|
|
}
|
|
|
|
if (0 < logmax)
|
|
{
|
|
if (logmax > MAX_RECALL_REQUEST)
|
|
{
|
|
logmax = MAX_RECALL_REQUEST;
|
|
atr_add(ch->chan_obj, pattr->number, mux_ltoa_t(logmax), GOD,
|
|
AF_CONST | AF_NOPROG | AF_NOPARSE);
|
|
}
|
|
const UTF8* p = tprintf(T("HISTORY_%d"), iMod(ch->num_messages, logmax));
|
|
const int atr = mkattr(GOD, p);
|
|
if (0 < atr)
|
|
{
|
|
pattr = atr_str(T("LOG_TIMESTAMPS"));
|
|
if (pattr
|
|
&& atr_get_info(obj, pattr->number, &aowner, &aflags))
|
|
{
|
|
CLinearTimeAbsolute ltaNow;
|
|
ltaNow.GetLocal();
|
|
|
|
// Save message in history with timestamp.
|
|
//
|
|
LBuf temp = LBuf_Src("chan_history");
|
|
mux_sprintf(temp, LBUF_SIZE, T("[%s] %s"), ltaNow.ReturnDateString(0), msgNormal);
|
|
atr_add(ch->chan_obj, atr, temp, GOD, AF_CONST | AF_NOPROG | AF_NOPARSE);
|
|
}
|
|
else
|
|
{
|
|
// Save message in history without timestamp.
|
|
//
|
|
atr_add(ch->chan_obj, atr, msgNormal, GOD,
|
|
AF_CONST | AF_NOPROG | AF_NOPARSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (ch->chan_obj != NOTHING)
|
|
{
|
|
ch->chan_obj = NOTHING;
|
|
}
|
|
|
|
// Free mogrifier results.
|
|
//
|
|
if (mogMsg)
|
|
{
|
|
free_lbuf(mogMsg);
|
|
}
|
|
if (mogFmt)
|
|
{
|
|
free_lbuf(mogFmt);
|
|
}
|
|
|
|
// Since msgNormal and msgNoComTitle are no longer needed, free them here.
|
|
//
|
|
if (msgNormal)
|
|
{
|
|
free_lbuf(msgNormal);
|
|
}
|
|
if (msgNoComtitle
|
|
&& msgNoComtitle != msgNormal)
|
|
{
|
|
free_lbuf(msgNoComtitle);
|
|
}
|
|
}
|
|
|
|
static void ChannelMOTD(dbref executor, dbref enactor, int attr)
|
|
{
|
|
if (Good_obj(executor))
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf q = LBuf_Adopt(atr_get("ChannelMOTD.1186", executor, attr, &aowner, &aflags));
|
|
if ('\0' != q[0])
|
|
{
|
|
LBuf buf = LBuf_Src("chanmotd");
|
|
UTF8* bp = buf;
|
|
|
|
mux_exec(q, LBUF_SIZE - 1, buf, &bp, executor, executor, enactor,
|
|
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP), nullptr, 0);
|
|
*bp = '\0';
|
|
|
|
notify_comsys(enactor, executor, buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Add player to the given channel. Transmit join messages as appropriate.
|
|
//
|
|
void do_joinchannel(const dbref player, struct channel* ch)
|
|
{
|
|
int attr;
|
|
|
|
struct comuser* user = select_user(ch, player);
|
|
|
|
if (!user)
|
|
{
|
|
if (static_cast<int>(ch->users.size()) >= MAX_USERS_PER_CHANNEL)
|
|
{
|
|
raw_notify(player, tprintf(T("Too many people on channel %s already."),
|
|
ch->name));
|
|
return;
|
|
}
|
|
|
|
comuser cu;
|
|
cu.who = player;
|
|
cu.bUserIsOn = true;
|
|
cu.ComTitleStatus = true;
|
|
|
|
// if (Connected(player))&&(isPlayer(player))
|
|
//
|
|
if (UNDEAD(player))
|
|
{
|
|
cu.bConnected = true;
|
|
}
|
|
|
|
auto result = ch->users.emplace(player, std::move(cu));
|
|
user = &result.first->second;
|
|
|
|
sqlite_wt_channel_user(ch->name, user);
|
|
attr = A_COMJOIN;
|
|
}
|
|
else if (!user->bUserIsOn)
|
|
{
|
|
user->bUserIsOn = true;
|
|
sqlite_wt_channel_user(ch->name, user);
|
|
attr = A_COMON;
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("You are already on channel %s."), ch->name));
|
|
return;
|
|
}
|
|
|
|
if (!Hidden(player))
|
|
{
|
|
UTF8 *messNormal, *messNoComtitle;
|
|
BuildChannelMessage((ch->type & CHANNEL_SPOOF) != 0, ch->header, user,
|
|
ch->chan_obj, T(":has joined this channel."), &messNormal,
|
|
&messNoComtitle);
|
|
SendChannelMessage(player, ch, messNormal, messNoComtitle, true);
|
|
}
|
|
ChannelMOTD(ch->chan_obj, user->who, attr);
|
|
}
|
|
|
|
// Process leave channnel request.
|
|
//
|
|
void do_leavechannel(dbref player, struct channel* ch)
|
|
{
|
|
struct comuser* user = select_user(ch, player);
|
|
raw_notify(player, tprintf(T("You have left channel %s."), ch->name));
|
|
|
|
if (user->bUserIsOn)
|
|
{
|
|
if (!Hidden(player))
|
|
{
|
|
UTF8 *messNormal, *messNoComtitle;
|
|
BuildChannelMessage((ch->type & CHANNEL_SPOOF) != 0, ch->header, user,
|
|
ch->chan_obj, T(":has left this channel."), &messNormal,
|
|
&messNoComtitle);
|
|
SendChannelMessage(player, ch, messNormal, messNoComtitle, true);
|
|
}
|
|
ChannelMOTD(ch->chan_obj, user->who, A_COMOFF);
|
|
user->bUserIsOn = false;
|
|
sqlite_wt_channel_user(ch->name, user);
|
|
}
|
|
}
|
|
|
|
static void do_comwho_line
|
|
(
|
|
const dbref player,
|
|
struct channel* ch,
|
|
const struct comuser* user
|
|
)
|
|
{
|
|
UTF8* msg;
|
|
UTF8* buff = nullptr;
|
|
|
|
if (!user->title.empty())
|
|
{
|
|
// There is a comtitle.
|
|
//
|
|
if (Staff(player))
|
|
{
|
|
buff = unparse_object(player, user->who, false);
|
|
if (ch->type & CHANNEL_SPOOF)
|
|
{
|
|
msg = tprintf(T("%s as %s"), buff, reinterpret_cast<const UTF8 *>(user->title.c_str()));
|
|
}
|
|
else
|
|
{
|
|
msg = tprintf(T("%s as %s %s"), buff, reinterpret_cast<const UTF8 *>(user->title.c_str()), buff);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ch->type & CHANNEL_SPOOF)
|
|
{
|
|
msg = const_cast<UTF8 *>(reinterpret_cast<const UTF8 *>(user->title.c_str()));
|
|
}
|
|
else
|
|
{
|
|
buff = unparse_object(player, user->who, false);
|
|
msg = tprintf(T("%s %s"), reinterpret_cast<const UTF8 *>(user->title.c_str()), buff);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buff = unparse_object(player, user->who, false);
|
|
msg = buff;
|
|
}
|
|
|
|
raw_notify(player, msg);
|
|
if (buff)
|
|
{
|
|
free_lbuf(buff);
|
|
}
|
|
}
|
|
|
|
void do_comwho(dbref player, struct channel* ch)
|
|
{
|
|
raw_notify(player, T("-- Players --"));
|
|
for (auto &kv : ch->users)
|
|
{
|
|
comuser &user = kv.second;
|
|
if (isPlayer(user.who))
|
|
{
|
|
if (Connected(user.who)
|
|
&& (!Hidden(user.who)
|
|
|| Wizard_Who(player)
|
|
|| See_Hidden(player)))
|
|
{
|
|
if (user.bUserIsOn)
|
|
{
|
|
do_comwho_line(player, ch, &user);
|
|
}
|
|
}
|
|
else if (!Hidden(user.who))
|
|
{
|
|
do_comdisconnectchannel(user.who, ch->name);
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player, T("-- Objects --"));
|
|
for (auto &kv : ch->users)
|
|
{
|
|
comuser &user = kv.second;
|
|
if (!isPlayer(user.who))
|
|
{
|
|
if (Going(user.who)
|
|
&& God(Owner(user.who)))
|
|
{
|
|
do_comdisconnectchannel(user.who, ch->name);
|
|
}
|
|
else if (user.bUserIsOn)
|
|
{
|
|
do_comwho_line(player, ch, &user);
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player, tprintf(T("-- %s --"), ch->name));
|
|
}
|
|
|
|
void do_comlast(dbref player, struct channel* ch, int arg)
|
|
{
|
|
// Validate the channel object.
|
|
//
|
|
if (!Good_obj(ch->chan_obj))
|
|
{
|
|
raw_notify(player, T("Channel does not have an object."));
|
|
return;
|
|
}
|
|
|
|
dbref aowner;
|
|
int aflags;
|
|
const dbref obj = ch->chan_obj;
|
|
int logmax = MAX_RECALL_REQUEST;
|
|
|
|
// Lookup depth of logging.
|
|
//
|
|
ATTR* pattr = atr_str(T("MAX_LOG"));
|
|
if (pattr
|
|
&& (atr_get_info(obj, pattr->number, &aowner, &aflags)))
|
|
{
|
|
LBuf maxbuf = LBuf_Adopt(atr_get("do_comlast.1408", obj, pattr->number, &aowner, &aflags));
|
|
logmax = mux_atol(maxbuf);
|
|
}
|
|
|
|
if (logmax < 1)
|
|
{
|
|
raw_notify(player, T("Channel does not log."));
|
|
return;
|
|
}
|
|
|
|
if (arg < MIN_RECALL_REQUEST)
|
|
{
|
|
arg = MIN_RECALL_REQUEST;
|
|
}
|
|
|
|
if (arg > logmax)
|
|
{
|
|
arg = logmax;
|
|
}
|
|
|
|
int histnum = ch->num_messages - arg;
|
|
|
|
raw_notify(player, tprintf(T("%s -- Begin Comsys Recall --"), ch->header));
|
|
|
|
for (int count = 0; count < arg; count++)
|
|
{
|
|
histnum++;
|
|
pattr = atr_str(tprintf(T("HISTORY_%d"), iMod(histnum, logmax)));
|
|
if (pattr)
|
|
{
|
|
LBuf message = LBuf_Adopt(atr_get("do_comlast.1436", obj, pattr->number,
|
|
&aowner, &aflags));
|
|
raw_notify(player, message);
|
|
}
|
|
}
|
|
|
|
raw_notify(player, tprintf(T("%s -- End Comsys Recall --"), ch->header));
|
|
}
|
|
|
|
// Turn channel history timestamping on or off for the given channel.
|
|
//
|
|
static bool do_chanlog_timestamps(dbref player, UTF8* channel, UTF8* arg)
|
|
{
|
|
UNUSED_PARAMETER(player);
|
|
|
|
// Validate arg.
|
|
//
|
|
int value = 0;
|
|
if (nullptr == arg
|
|
|| !is_integer(arg, nullptr)
|
|
|| ((value = mux_atol(arg)) != 0
|
|
&& value != 1))
|
|
{
|
|
// arg is not "0" and not "1".
|
|
//
|
|
return false;
|
|
}
|
|
|
|
struct channel* ch = select_channel(channel);
|
|
if (!Good_obj(ch->chan_obj))
|
|
{
|
|
// No channel object has been set.
|
|
//
|
|
return false;
|
|
}
|
|
|
|
dbref aowner;
|
|
int aflags;
|
|
ATTR* pattr = atr_str(T("MAX_LOG"));
|
|
if (nullptr == pattr
|
|
|| !atr_get_info(ch->chan_obj, pattr->number, &aowner, &aflags))
|
|
{
|
|
// Logging isn't enabled.
|
|
//
|
|
return false;
|
|
}
|
|
|
|
const int atr = mkattr(GOD, T("LOG_TIMESTAMPS"));
|
|
if (atr <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (value)
|
|
{
|
|
atr_add(ch->chan_obj, atr, mux_ltoa_t(value), GOD,
|
|
AF_CONST | AF_NOPROG | AF_NOPARSE);
|
|
}
|
|
else
|
|
{
|
|
atr_clr(ch->chan_obj, atr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Set number of entries for channel logging.
|
|
//
|
|
static bool do_chanlog(dbref player, UTF8* channel, UTF8* arg)
|
|
{
|
|
UNUSED_PARAMETER(player);
|
|
|
|
int value;
|
|
if (!*arg
|
|
|| !is_integer(arg, nullptr)
|
|
|| (value = mux_atol(arg)) > MAX_RECALL_REQUEST)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (value < 0)
|
|
{
|
|
value = 0;
|
|
}
|
|
|
|
const struct channel* ch = select_channel(channel);
|
|
if (!Good_obj(ch->chan_obj))
|
|
{
|
|
// No channel object has been set.
|
|
//
|
|
return false;
|
|
}
|
|
|
|
const int atr = mkattr(GOD, T("MAX_LOG"));
|
|
if (atr <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf oldvalue = LBuf_Adopt(atr_get("do_chanlog.1477", ch->chan_obj, atr, &aowner, &aflags));
|
|
const int oldnum = mux_atol(oldvalue);
|
|
if (value < oldnum)
|
|
{
|
|
for (int count = 0; count <= oldnum; count++)
|
|
{
|
|
ATTR* hist = atr_str(tprintf(T("HISTORY_%d"), count));
|
|
if (hist)
|
|
{
|
|
atr_clr(ch->chan_obj, hist->number);
|
|
}
|
|
}
|
|
}
|
|
atr_add(ch->chan_obj, atr, mux_ltoa_t(value), GOD,
|
|
AF_CONST | AF_NOPROG | AF_NOPARSE);
|
|
return true;
|
|
}
|
|
|
|
// Find struct channel entry by name with the channel_name hash table.
|
|
//
|
|
struct channel* select_channel(UTF8* channel_name)
|
|
{
|
|
// Try exact match first.
|
|
//
|
|
const auto channel_name_length = strlen(reinterpret_cast<char*>(channel_name));
|
|
const vector<UTF8> channel_vector(channel_name, channel_name + channel_name_length);
|
|
const auto it = mudstate.channel_names.find(channel_vector);
|
|
if (it != mudstate.channel_names.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
|
|
// Fall back to case-insensitive search.
|
|
//
|
|
for (const auto& entry : mudstate.channel_names)
|
|
{
|
|
if (0 == mux_stricmp(channel_name, entry.second->name))
|
|
{
|
|
return entry.second;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Locate player in the user's list for the given channel.
|
|
//
|
|
struct comuser* select_user(struct channel* ch, const dbref player)
|
|
{
|
|
if (!ch)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
auto it = ch->users.find(player);
|
|
if (it != ch->users.end())
|
|
{
|
|
return &it->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#define MAX_ALIASES_PER_PLAYER 100
|
|
|
|
void do_addcom
|
|
(
|
|
const dbref executor,
|
|
const dbref caller,
|
|
dbref enactor,
|
|
const int eval,
|
|
const int key,
|
|
const int nargs,
|
|
UTF8* arg1,
|
|
UTF8* channel,
|
|
const UTF8* cargs[],
|
|
int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(nargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
bool bValidAlias;
|
|
size_t nValidAlias;
|
|
UTF8* pValid = MakeCanonicalComAlias(arg1, &nValidAlias, &bValidAlias);
|
|
if (bValidAlias)
|
|
{
|
|
mudstate.pIComsysControl->AddAlias(executor, pValid, channel);
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("You need to specify a valid alias."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool bValidAlias;
|
|
size_t nValidAlias;
|
|
UTF8* pValidAlias = MakeCanonicalComAlias(arg1, &nValidAlias, &bValidAlias);
|
|
if (!bValidAlias)
|
|
{
|
|
raw_notify(executor, T("You need to specify a valid alias."));
|
|
return;
|
|
}
|
|
if ('\0' == channel[0])
|
|
{
|
|
raw_notify(executor, T("You need to specify a channel."));
|
|
return;
|
|
}
|
|
|
|
struct channel* ch = select_channel(channel);
|
|
if (!ch)
|
|
{
|
|
UTF8 Buffer[MAX_CHANNEL_LEN + 1];
|
|
StripTabsAndTruncate(channel, Buffer, MAX_CHANNEL_LEN, MAX_CHANNEL_LEN);
|
|
raw_notify(executor, tprintf(T("Channel %s does not exist yet."), Buffer));
|
|
return;
|
|
}
|
|
if (!test_join_access(executor, ch))
|
|
{
|
|
raw_notify(executor, T("Sorry, this channel type does not allow you to join."));
|
|
return;
|
|
}
|
|
comsys_t* c = get_comsys(executor);
|
|
if (static_cast<int>(c->aliases.size()) >= MAX_ALIASES_PER_PLAYER)
|
|
{
|
|
raw_notify(executor, tprintf(T("Sorry, but you have reached the maximum number of aliases allowed.")));
|
|
return;
|
|
}
|
|
|
|
// Check if alias already exists.
|
|
//
|
|
string sAlias(reinterpret_cast<const char *>(pValidAlias));
|
|
for (const auto &ca : c->aliases)
|
|
{
|
|
if (ca.alias == sAlias)
|
|
{
|
|
const UTF8* p = tprintf(T("That alias is already in use for channel %s."),
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()));
|
|
raw_notify(executor, p);
|
|
return;
|
|
}
|
|
}
|
|
|
|
com_alias newAlias;
|
|
newAlias.alias = sAlias;
|
|
newAlias.channel.assign(reinterpret_cast<const char *>(channel));
|
|
c->aliases.push_back(std::move(newAlias));
|
|
sort_com_aliases(*c);
|
|
|
|
sqlite_wt_player_channel(executor, pValidAlias, channel);
|
|
|
|
if (!select_user(ch, executor))
|
|
{
|
|
do_joinchannel(executor, ch);
|
|
}
|
|
|
|
raw_notify(executor, tprintf(T("Channel %s added with alias %s."), channel, pValidAlias));
|
|
}
|
|
|
|
void do_delcom(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8* arg1, const UTF8* cargs[],
|
|
int ncargs)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->DelAlias(executor, arg1);
|
|
return;
|
|
}
|
|
|
|
if (!arg1)
|
|
{
|
|
raw_notify(executor, T("Need an alias to delete."));
|
|
return;
|
|
}
|
|
comsys_t* c = get_comsys(executor);
|
|
|
|
string sArg(reinterpret_cast<const char *>(arg1));
|
|
for (auto it = c->aliases.begin(); it != c->aliases.end(); ++it)
|
|
{
|
|
if (it->alias == sArg)
|
|
{
|
|
// Count how many aliases map to the same channel.
|
|
//
|
|
int found = 0;
|
|
for (const auto &ca : c->aliases)
|
|
{
|
|
if (ca.channel == it->channel)
|
|
{
|
|
found++;
|
|
}
|
|
}
|
|
|
|
// If we found no other channels, delete it.
|
|
//
|
|
if (found <= 1)
|
|
{
|
|
do_delcomchannel(executor,
|
|
const_cast<UTF8 *>(reinterpret_cast<const UTF8 *>(it->channel.c_str())),
|
|
false);
|
|
raw_notify(executor, tprintf(T("Alias %s for channel %s deleted."),
|
|
arg1,
|
|
reinterpret_cast<const UTF8 *>(it->channel.c_str())));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, tprintf(T("Alias %s for channel %s deleted."),
|
|
arg1,
|
|
reinterpret_cast<const UTF8 *>(it->channel.c_str())));
|
|
}
|
|
|
|
sqlite_wt_delete_player_channel(executor, arg1);
|
|
c->aliases.erase(it);
|
|
return;
|
|
}
|
|
}
|
|
raw_notify(executor, T("Unable to find that alias."));
|
|
}
|
|
|
|
// Process a complete unsubscribe for a player from a particular channel.
|
|
//
|
|
void do_delcomchannel(dbref player, UTF8* channel, bool bQuiet)
|
|
{
|
|
struct channel* ch = select_channel(channel);
|
|
if (!ch)
|
|
{
|
|
raw_notify(player, tprintf(T("Unknown channel %s."), channel));
|
|
}
|
|
else
|
|
{
|
|
auto it = ch->users.find(player);
|
|
if (it != ch->users.end())
|
|
{
|
|
comuser &user = it->second;
|
|
do_comdisconnectchannel(player, channel);
|
|
if (!bQuiet)
|
|
{
|
|
if (user.bUserIsOn
|
|
&& !Hidden(player))
|
|
{
|
|
UTF8 *messNormal, *messNoComtitle;
|
|
BuildChannelMessage((ch->type & CHANNEL_SPOOF) != 0,
|
|
ch->header, &user, ch->chan_obj,
|
|
T(":has left this channel."), &messNormal,
|
|
&messNoComtitle);
|
|
SendChannelMessage(player, ch, messNormal, messNoComtitle, true);
|
|
}
|
|
raw_notify(player, tprintf(T("You have left channel %s."),
|
|
channel));
|
|
}
|
|
|
|
ChannelMOTD(ch->chan_obj, user.who, A_COMLEAVE);
|
|
sqlite_wt_delete_channel_user(ch->name, player);
|
|
|
|
ch->users.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void do_createchannel(const dbref executor, const dbref caller, dbref enactor, const int eval, const int key, UTF8* channel,
|
|
const UTF8* cargs[], const int ncargs)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->CreateChannel(executor, channel);
|
|
return;
|
|
}
|
|
|
|
if ('\0' == channel[0])
|
|
{
|
|
raw_notify(executor, T("You must specify a channel to create."));
|
|
return;
|
|
}
|
|
|
|
if (!Comm_All(executor))
|
|
{
|
|
raw_notify(executor, NOPERM_MESSAGE);
|
|
return;
|
|
}
|
|
|
|
auto newchannel = new ::channel();
|
|
|
|
size_t nNameNoANSI;
|
|
UTF8* pNameNoANSI;
|
|
UTF8 Buffer[MAX_HEADER_LEN + 1];
|
|
mux_field fldChannel = StripTabsAndTruncate(channel, Buffer, MAX_HEADER_LEN,
|
|
MAX_HEADER_LEN);
|
|
if (fldChannel.m_byte == fldChannel.m_column)
|
|
{
|
|
// The channel name does not contain ANSI, so first, we add some to
|
|
// get the header.
|
|
//
|
|
const size_t nMax = MAX_HEADER_LEN - (sizeof(COLOR_INTENSE) - 1)
|
|
- (sizeof(COLOR_RESET) - 1) - 2;
|
|
size_t nChannel = fldChannel.m_byte;
|
|
if (nMax < nChannel)
|
|
{
|
|
nChannel = nMax;
|
|
}
|
|
Buffer[nChannel] = '\0';
|
|
mux_sprintf(newchannel->header, sizeof(newchannel->header),
|
|
T("%s[%s]%s"), COLOR_INTENSE, Buffer, COLOR_RESET);
|
|
|
|
// Then, we use the non-ANSI part for the name.
|
|
//
|
|
nNameNoANSI = nChannel;
|
|
pNameNoANSI = Buffer;
|
|
}
|
|
else
|
|
{
|
|
// The given channel name does contain color.
|
|
//
|
|
memcpy(newchannel->header, Buffer, fldChannel.m_byte + 1);
|
|
pNameNoANSI = strip_color(Buffer, &nNameNoANSI);
|
|
}
|
|
|
|
if (nNameNoANSI > MAX_CHANNEL_LEN)
|
|
{
|
|
raw_notify(executor, T("Channel name is too long."));
|
|
delete newchannel;
|
|
return;
|
|
}
|
|
|
|
memcpy(newchannel->name, pNameNoANSI, nNameNoANSI);
|
|
newchannel->name[nNameNoANSI] = '\0';
|
|
|
|
if (select_channel(newchannel->name))
|
|
{
|
|
raw_notify(executor, tprintf(T("Channel %s already exists."), newchannel->name));
|
|
delete newchannel;
|
|
return;
|
|
}
|
|
|
|
newchannel->type = 127;
|
|
newchannel->temp1 = 0;
|
|
newchannel->temp2 = 0;
|
|
newchannel->charge = 0;
|
|
newchannel->charge_who = executor;
|
|
newchannel->amount_col = 0;
|
|
newchannel->chan_obj = NOTHING;
|
|
newchannel->num_messages = 0;
|
|
|
|
const vector<UTF8> channel_name(newchannel->name, newchannel->name + nNameNoANSI);
|
|
mudstate.channel_names.insert(make_pair(channel_name, newchannel));
|
|
sqlite_wt_channel(newchannel);
|
|
|
|
// Report the channel creation using non-ANSI name.
|
|
//
|
|
raw_notify(executor, tprintf(T("Channel %s created."), newchannel->name));
|
|
}
|
|
|
|
void do_destroychannel
|
|
(
|
|
const dbref executor,
|
|
const dbref caller,
|
|
const dbref enactor,
|
|
const int eval,
|
|
const int key,
|
|
UTF8* channel_name,
|
|
const UTF8* cargs[],
|
|
const int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->DestroyChannel(executor, channel_name);
|
|
return;
|
|
}
|
|
|
|
const auto channel_name_length = strlen(reinterpret_cast<char*>(channel_name));
|
|
const vector<UTF8> channel_name_vector(channel_name, channel_name + channel_name_length);
|
|
const auto it = mudstate.channel_names.find(channel_name_vector);
|
|
|
|
if (it != mudstate.channel_names.end())
|
|
{
|
|
raw_notify(executor, tprintf(T("Could not find channel_name %s."), channel_name));
|
|
return;
|
|
}
|
|
|
|
auto ch = it->second;
|
|
|
|
if ( !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
raw_notify(executor, NOPERM_MESSAGE);
|
|
return;
|
|
}
|
|
sqlite_wt_delete_channel(ch->name);
|
|
|
|
delete ch;
|
|
ch = nullptr;
|
|
mudstate.channel_names.erase(it);
|
|
raw_notify(executor, tprintf(T("Channel %s destroyed."), channel_name));
|
|
}
|
|
|
|
|
|
static void do_listchannels(dbref player, UTF8* pattern)
|
|
{
|
|
const bool perm = Comm_All(player);
|
|
if (!perm)
|
|
{
|
|
raw_notify(player, T("Warning: Only public channels and your channels will be shown."));
|
|
}
|
|
|
|
bool bWild;
|
|
if (nullptr != pattern
|
|
&& '\0' != *pattern)
|
|
{
|
|
bWild = true;
|
|
}
|
|
else
|
|
{
|
|
bWild = false;
|
|
}
|
|
|
|
raw_notify(player, T("*** Channel Header Owner Access Users Msgs"));
|
|
|
|
for (auto it = mudstate.channel_names.begin(); it != mudstate.channel_names.end(); ++it)
|
|
{
|
|
const auto ch = it->second;
|
|
|
|
if ( perm
|
|
|| (ch->type & CHANNEL_PUBLIC)
|
|
|| Controls(player, ch->charge_who)
|
|
|| nullptr != select_user(ch, player))
|
|
{
|
|
if ( !bWild
|
|
|| quick_wild(pattern, ch->name))
|
|
{
|
|
// Determine effective access for the querying player
|
|
// based on both flags and locks.
|
|
//
|
|
bool bCanJoin = test_join_access(player, ch);
|
|
bool bCanXmit = test_transmit_access(player, ch);
|
|
bool bCanRecv = test_receive_access(player, ch);
|
|
|
|
LBuf temp = LBuf_Src("do_listchannels");
|
|
UTF8* bp = temp;
|
|
|
|
// PLS flags.
|
|
//
|
|
safe_chr((ch->type & CHANNEL_PUBLIC) ? 'P' : '-', temp, &bp);
|
|
safe_chr((ch->type & CHANNEL_LOUD) ? 'L' : '-', temp, &bp);
|
|
safe_chr((ch->type & CHANNEL_SPOOF) ? 'S' : '-', temp, &bp);
|
|
safe_chr(' ', temp, &bp);
|
|
|
|
// Channel name (13 cols).
|
|
//
|
|
mux_field iPos(4, 4);
|
|
iPos += StripTabsAndTruncate(ch->name,
|
|
temp.get() + iPos.m_byte,
|
|
(LBUF_SIZE - 1) - iPos.m_byte,
|
|
13);
|
|
bp = temp.get() + iPos.m_byte;
|
|
iPos = PadField(temp, LBUF_SIZE - 1, 18, iPos);
|
|
bp = temp.get() + iPos.m_byte;
|
|
|
|
// Header (15 cols).
|
|
//
|
|
const UTF8 *pHeader = ch->header;
|
|
if ('\0' == pHeader[0])
|
|
{
|
|
pHeader = T("-");
|
|
}
|
|
iPos += StripTabsAndTruncate(pHeader,
|
|
temp.get() + iPos.m_byte,
|
|
(LBUF_SIZE - 1) - iPos.m_byte,
|
|
15);
|
|
bp = temp.get() + iPos.m_byte;
|
|
iPos = PadField(temp, LBUF_SIZE - 1, 34, iPos);
|
|
bp = temp.get() + iPos.m_byte;
|
|
|
|
// Owner name (15 cols).
|
|
//
|
|
iPos += StripTabsAndTruncate(Moniker(ch->charge_who),
|
|
temp.get() + iPos.m_byte,
|
|
(LBUF_SIZE - 1) - iPos.m_byte,
|
|
15);
|
|
bp = temp.get() + iPos.m_byte;
|
|
iPos = PadField(temp, LBUF_SIZE - 1, 50, iPos);
|
|
bp = temp.get() + iPos.m_byte;
|
|
|
|
// Effective access JXR (3 cols + 2 spaces).
|
|
//
|
|
safe_chr(bCanJoin ? 'J' : '-', temp, &bp);
|
|
safe_chr(bCanXmit ? 'X' : '-', temp, &bp);
|
|
safe_chr(bCanRecv ? 'R' : '-', temp, &bp);
|
|
iPos = mux_field(
|
|
static_cast<unsigned int>(bp - temp.get()),
|
|
static_cast<unsigned int>(bp - temp.get()));
|
|
iPos = PadField(temp, LBUF_SIZE - 1, 56, iPos);
|
|
bp = temp.get() + iPos.m_byte;
|
|
|
|
// Users and Messages.
|
|
//
|
|
mux_sprintf(bp, (LBUF_SIZE - 1) - (bp - temp.get()),
|
|
T("%5d %4d"),
|
|
static_cast<int>(ch->users.size()),
|
|
ch->num_messages);
|
|
raw_notify(player, temp);
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player, T("-- End of list of Channels --"));
|
|
}
|
|
|
|
void do_comtitle
|
|
(
|
|
const dbref executor,
|
|
const dbref caller,
|
|
const dbref enactor,
|
|
int eval,
|
|
const int key,
|
|
const int nargs,
|
|
UTF8* arg1,
|
|
UTF8* arg2,
|
|
const UTF8* cargs[],
|
|
const int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->ComTitle(executor, arg1, arg2, key);
|
|
return;
|
|
}
|
|
|
|
if (!*arg1)
|
|
{
|
|
raw_notify(executor, T("Need an alias to do comtitle."));
|
|
return;
|
|
}
|
|
|
|
UTF8 channel[MAX_CHANNEL_LEN + 1];
|
|
mux_strncpy(channel, get_channel_from_alias(executor, arg1), MAX_CHANNEL_LEN);
|
|
|
|
if (channel[0] == '\0')
|
|
{
|
|
raw_notify(executor, T("Unknown alias."));
|
|
return;
|
|
}
|
|
struct channel* ch = select_channel(channel);
|
|
if (ch)
|
|
{
|
|
if (select_user(ch, executor))
|
|
{
|
|
if (key == COMTITLE_OFF)
|
|
{
|
|
if ((ch->type & CHANNEL_SPOOF) == 0)
|
|
{
|
|
raw_notify(executor, tprintf(T("Comtitles are now off for channel %s"), channel));
|
|
do_setcomtitlestatus(executor, ch, false);
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("You can not turn off comtitles on that channel."));
|
|
}
|
|
}
|
|
else if (key == COMTITLE_ON)
|
|
{
|
|
raw_notify(executor, tprintf(T("Comtitles are now on for channel %s"), channel));
|
|
do_setcomtitlestatus(executor, ch, true);
|
|
}
|
|
else if (key == COMTITLE_GAG)
|
|
{
|
|
raw_notify(executor, tprintf(T("Join/leave messages are now gagged for channel %s"), channel));
|
|
do_setgagjoinleavestatus(executor, ch, true);
|
|
}
|
|
else if (key == COMTITLE_UNGAG)
|
|
{
|
|
raw_notify(executor, tprintf(T("Join/leave messages are now ungagged for channel %s"), channel));
|
|
do_setgagjoinleavestatus(executor, ch, false);
|
|
}
|
|
else
|
|
{
|
|
UTF8* pValidatedTitleValue = RestrictTitleValue(arg2);
|
|
do_setnewtitle(executor, ch, pValidatedTitleValue);
|
|
raw_notify(executor, tprintf(T("Title set to \xE2\x80\x98%s\xE2\x80\x99 on channel %s."),
|
|
pValidatedTitleValue, channel));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("Illegal comsys alias, please delete."));
|
|
}
|
|
}
|
|
|
|
void do_comlist
|
|
(
|
|
const dbref executor,
|
|
dbref caller,
|
|
const dbref enactor,
|
|
int eval,
|
|
const int key,
|
|
UTF8* pattern,
|
|
const UTF8* cargs[],
|
|
const int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->ComList(executor, pattern);
|
|
return;
|
|
}
|
|
|
|
bool bWild;
|
|
if (nullptr != pattern
|
|
&& '\0' != *pattern)
|
|
{
|
|
bWild = true;
|
|
}
|
|
else
|
|
{
|
|
bWild = false;
|
|
}
|
|
|
|
raw_notify(executor, T("Alias Channel Status Title"));
|
|
|
|
const comsys_t* c = get_comsys(executor);
|
|
for (size_t i = 0; i < c->aliases.size(); i++)
|
|
{
|
|
const UTF8 *chanName = reinterpret_cast<const UTF8 *>(c->aliases[i].channel.c_str());
|
|
struct comuser* user = select_user(select_channel(const_cast<UTF8 *>(chanName)), executor);
|
|
if (user)
|
|
{
|
|
if (!bWild
|
|
|| quick_wild(pattern, chanName))
|
|
{
|
|
UTF8* p =
|
|
tprintf(T("%-15.15s %-18.18s %s %s%s %s"),
|
|
reinterpret_cast<const UTF8 *>(c->aliases[i].alias.c_str()),
|
|
chanName,
|
|
(user->bUserIsOn ? "on " : "off"),
|
|
(user->ComTitleStatus ? "con " : "coff"),
|
|
(user->bGagJoinLeave ? " gag" : ""),
|
|
reinterpret_cast<const UTF8 *>(user->title.c_str()));
|
|
raw_notify(executor, p);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, tprintf(
|
|
T("Bad Comsys Alias: %s for Channel: %s"),
|
|
reinterpret_cast<const UTF8 *>(c->aliases[i].alias.c_str()),
|
|
chanName));
|
|
}
|
|
}
|
|
raw_notify(executor, T("-- End of comlist --"));
|
|
}
|
|
|
|
// Cleanup channels owned by the player.
|
|
//
|
|
void do_channelnuke(const dbref player)
|
|
{
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
return; // Module handles via IServerEventsSink::data_free.
|
|
}
|
|
|
|
bool found = true;
|
|
while (found)
|
|
{
|
|
found = false;
|
|
for (auto it = mudstate.channel_names.begin(); it != mudstate.channel_names.end(); ++it)
|
|
{
|
|
auto ch = it->second;
|
|
|
|
if (player == ch->charge_who)
|
|
{
|
|
sqlite_wt_delete_channel(ch->name);
|
|
|
|
delete ch;
|
|
ch = nullptr;
|
|
|
|
// Removing an element invalidates the iterator, so the search much be restarted.
|
|
//
|
|
mudstate.channel_names.erase(it);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void do_clearcom(const dbref executor, const dbref caller, const dbref enactor, int eval, const int key)
|
|
{
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(key);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->ClearAliases(executor);
|
|
return;
|
|
}
|
|
|
|
const comsys_t* c = get_comsys(executor);
|
|
|
|
for (int i = static_cast<int>(c->aliases.size()) - 1; i > -1; --i)
|
|
{
|
|
do_delcom(executor, caller, enactor, 0, 0,
|
|
const_cast<UTF8 *>(reinterpret_cast<const UTF8 *>(c->aliases[i].alias.c_str())),
|
|
nullptr, 0);
|
|
}
|
|
}
|
|
|
|
void do_allcom(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8* arg1, const UTF8* cargs[],
|
|
int ncargs)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->AllCom(executor, arg1);
|
|
return;
|
|
}
|
|
|
|
if (strcmp(reinterpret_cast<char*>(arg1), "who") != 0
|
|
&& strcmp(reinterpret_cast<char*>(arg1), "on") != 0
|
|
&& strcmp(reinterpret_cast<char*>(arg1), "off") != 0)
|
|
{
|
|
raw_notify(executor, T("Only options available are: on, off and who."));
|
|
return;
|
|
}
|
|
|
|
const comsys_t* c = get_comsys(executor);
|
|
for (size_t i = 0; i < c->aliases.size(); i++)
|
|
{
|
|
do_processcom(executor,
|
|
const_cast<UTF8 *>(reinterpret_cast<const UTF8 *>(c->aliases[i].channel.c_str())),
|
|
arg1);
|
|
if (strcmp(reinterpret_cast<char*>(arg1), "who") == 0)
|
|
{
|
|
raw_notify(executor, T(""));
|
|
}
|
|
}
|
|
}
|
|
|
|
void do_channelwho(const dbref executor, const dbref caller, dbref enactor, const int eval, const int key, UTF8* arg1, const UTF8* cargs[],
|
|
const int ncargs)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->ChanWho(executor, arg1);
|
|
return;
|
|
}
|
|
|
|
UTF8 channel[MAX_CHANNEL_LEN + 1];
|
|
size_t i = 0;
|
|
while ('\0' != arg1[i]
|
|
&& '/' != arg1[i]
|
|
&& i < MAX_CHANNEL_LEN)
|
|
{
|
|
channel[i] = arg1[i];
|
|
i++;
|
|
}
|
|
channel[i] = '\0';
|
|
|
|
bool bAll = false;
|
|
if ('/' == arg1[i]
|
|
&& 'a' == arg1[i + 1])
|
|
{
|
|
bAll = true;
|
|
}
|
|
|
|
struct channel* ch = nullptr;
|
|
if (i <= MAX_CHANNEL_LEN)
|
|
{
|
|
ch = select_channel(channel);
|
|
}
|
|
if (nullptr == ch)
|
|
{
|
|
raw_notify(executor, tprintf(T("Unknown channel %s."), channel));
|
|
return;
|
|
}
|
|
if (!(Comm_All(executor)
|
|
|| Controls(executor, ch->charge_who)))
|
|
{
|
|
raw_notify(executor, NOPERM_MESSAGE);
|
|
return;
|
|
}
|
|
|
|
raw_notify(executor, tprintf(T("-- %s --"), ch->name));
|
|
raw_notify(executor, tprintf(T("%-29.29s %-6.6s %-6.6s"), "Name", "Status", "Player"));
|
|
for (auto &kv : ch->users)
|
|
{
|
|
const comuser &user = kv.second;
|
|
if ((bAll
|
|
|| UNDEAD(user.who))
|
|
&& (!Hidden(user.who)
|
|
|| Wizard_Who(executor)
|
|
|| See_Hidden(executor)))
|
|
{
|
|
thread_local UTF8 temp[SBUF_SIZE];
|
|
UTF8* buff = unparse_object(executor, user.who, false);
|
|
mux_sprintf(temp, sizeof(temp), T("%-29.29s %-6.6s %-6.6s"), strip_color(buff),
|
|
user.bUserIsOn ? "on " : "off",
|
|
isPlayer(user.who) ? "yes" : "no ");
|
|
raw_notify(executor, temp);
|
|
free_lbuf(buff);
|
|
}
|
|
}
|
|
raw_notify(executor, tprintf(T("-- %s --"), ch->name));
|
|
}
|
|
|
|
// Assemble and transmit player disconnection messages to the player's active
|
|
// set of channels.
|
|
//
|
|
static void do_comdisconnectraw_notify(const dbref player, UTF8* chan)
|
|
{
|
|
struct channel* ch = select_channel(chan);
|
|
if (!ch) return;
|
|
|
|
struct comuser* cu = select_user(ch, player);
|
|
if (!cu) return;
|
|
|
|
if ((ch->type & CHANNEL_LOUD)
|
|
&& cu->bUserIsOn
|
|
&& !Hidden(player))
|
|
{
|
|
UTF8 *messNormal, *messNoComtitle;
|
|
BuildChannelMessage((ch->type & CHANNEL_SPOOF) != 0, ch->header, cu,
|
|
ch->chan_obj, T(":has disconnected."), &messNormal,
|
|
&messNoComtitle);
|
|
SendChannelMessage(player, ch, messNormal, messNoComtitle, true);
|
|
}
|
|
}
|
|
|
|
// Assemble and transmit player connection messages to the player's active
|
|
// set of channels.
|
|
//
|
|
static void do_comconnectraw_notify(const dbref player, UTF8* chan)
|
|
{
|
|
struct channel* ch = select_channel(chan);
|
|
if (!ch) return;
|
|
struct comuser* cu = select_user(ch, player);
|
|
if (!cu) return;
|
|
|
|
if ((ch->type & CHANNEL_LOUD)
|
|
&& cu->bUserIsOn
|
|
&& !Hidden(player))
|
|
{
|
|
UTF8 *messNormal, *messNoComtitle;
|
|
BuildChannelMessage((ch->type & CHANNEL_SPOOF) != 0, ch->header, cu,
|
|
ch->chan_obj, T(":has connected."), &messNormal,
|
|
&messNoComtitle);
|
|
SendChannelMessage(player, ch, messNormal, messNoComtitle, true);
|
|
}
|
|
}
|
|
|
|
// Set player as connected on the channel.
|
|
//
|
|
static void do_comconnectchannel(dbref player, UTF8* channel, const string &alias)
|
|
{
|
|
struct channel* ch = select_channel(channel);
|
|
if (ch)
|
|
{
|
|
struct comuser* user = select_user(ch, player);
|
|
if (user)
|
|
{
|
|
user->bConnected = true;
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player,
|
|
tprintf(T("Bad Comsys Alias: %s for Channel: %s"),
|
|
reinterpret_cast<const UTF8 *>(alias.c_str()), channel));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("Bad Comsys Alias: %s for Channel: %s"),
|
|
reinterpret_cast<const UTF8 *>(alias.c_str()), channel));
|
|
}
|
|
}
|
|
|
|
// Check player for any active channels. If found, mark the player as
|
|
// disconnected. Transmit disconnection messages as needed.
|
|
//
|
|
void do_comdisconnect(const dbref player)
|
|
{
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
return; // Module handles via IServerEventsSink::disconnect.
|
|
}
|
|
|
|
auto it = comsys_table.find(player);
|
|
if (it == comsys_table.end())
|
|
{
|
|
return;
|
|
}
|
|
const comsys_t &c = it->second;
|
|
|
|
vector<string> seen;
|
|
for (const auto &ca : c.aliases)
|
|
{
|
|
bool bFound = false;
|
|
for (const auto &s : seen)
|
|
{
|
|
if (s == ca.channel)
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
seen.push_back(ca.channel);
|
|
UTF8 *chanName = const_cast<UTF8 *>(reinterpret_cast<const UTF8 *>(ca.channel.c_str()));
|
|
|
|
// Process channel removals.
|
|
//
|
|
do_comdisconnectchannel(player, chanName);
|
|
|
|
// Send disconnection messages if necessary.
|
|
//
|
|
do_comdisconnectraw_notify(player, chanName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Locate all active channels for the given player; mark the player as
|
|
// connected and send connect notifications as appropriate.
|
|
//
|
|
void do_comconnect(const dbref player)
|
|
{
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
return; // Module handles via IServerEventsSink::connect.
|
|
}
|
|
|
|
auto it = comsys_table.find(player);
|
|
if (it == comsys_table.end())
|
|
{
|
|
return;
|
|
}
|
|
const comsys_t &c = it->second;
|
|
|
|
vector<string> seen;
|
|
for (const auto &ca : c.aliases)
|
|
{
|
|
bool bFound = false;
|
|
for (const auto &s : seen)
|
|
{
|
|
if (s == ca.channel)
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
seen.push_back(ca.channel);
|
|
UTF8 *chanName = const_cast<UTF8 *>(reinterpret_cast<const UTF8 *>(ca.channel.c_str()));
|
|
do_comconnectchannel(player, chanName, ca.alias);
|
|
do_comconnectraw_notify(player, chanName);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Mark the given player as disconnected on the channel.
|
|
//
|
|
void do_comdisconnectchannel(const dbref player, UTF8* channel)
|
|
{
|
|
struct channel* ch = select_channel(channel);
|
|
if (!ch)
|
|
{
|
|
return;
|
|
}
|
|
|
|
struct comuser* user = select_user(ch, player);
|
|
if (user)
|
|
{
|
|
user->bConnected = false;
|
|
}
|
|
}
|
|
|
|
void do_editchannel
|
|
(
|
|
dbref executor,
|
|
dbref caller,
|
|
dbref enactor,
|
|
int eval,
|
|
int flag,
|
|
int nargs,
|
|
UTF8* arg1,
|
|
UTF8* arg2,
|
|
const UTF8* cargs[],
|
|
int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->EditChannel(executor, arg1, arg2, flag);
|
|
return;
|
|
}
|
|
|
|
struct channel* ch = select_channel(arg1);
|
|
if (!ch)
|
|
{
|
|
raw_notify(executor, tprintf(T("Unknown channel %s."), arg1));
|
|
return;
|
|
}
|
|
|
|
if (!(Comm_All(executor)
|
|
|| Controls(executor, ch->charge_who)))
|
|
{
|
|
raw_notify(executor, NOPERM_MESSAGE);
|
|
return;
|
|
}
|
|
|
|
bool add_remove = true;
|
|
UTF8* s = arg2;
|
|
if (*s == '!')
|
|
{
|
|
add_remove = false;
|
|
s++;
|
|
}
|
|
|
|
switch (flag)
|
|
{
|
|
case EDIT_CHANNEL_CCHOWN:
|
|
{
|
|
init_match(executor, arg2, NOTYPE);
|
|
match_everything(0);
|
|
const dbref who = match_result();
|
|
if (Good_obj(who))
|
|
{
|
|
ch->charge_who = who;
|
|
raw_notify(executor, T("Set."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("Invalid player."));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EDIT_CHANNEL_CCHARGE:
|
|
{
|
|
const int c_charge = mux_atol(arg2);
|
|
if (0 <= c_charge
|
|
&& c_charge <= MAX_COST)
|
|
{
|
|
ch->charge = c_charge;
|
|
raw_notify(executor, T("Set."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("That is not a reasonable cost."));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EDIT_CHANNEL_CPFLAGS:
|
|
{
|
|
int access = 0;
|
|
if (strcmp(reinterpret_cast<char*>(s), "join") == 0)
|
|
{
|
|
access = CHANNEL_PLAYER_JOIN;
|
|
}
|
|
else if (strcmp(reinterpret_cast<char*>(s), "receive") == 0)
|
|
{
|
|
access = CHANNEL_PLAYER_RECEIVE;
|
|
}
|
|
else if (strcmp(reinterpret_cast<char*>(s), "transmit") == 0)
|
|
{
|
|
access = CHANNEL_PLAYER_TRANSMIT;
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("@cpflags: Unknown Flag."));
|
|
}
|
|
|
|
if (access)
|
|
{
|
|
if (add_remove)
|
|
{
|
|
ch->type |= access;
|
|
raw_notify(executor, T("@cpflags: Set."));
|
|
}
|
|
else
|
|
{
|
|
ch->type &= ~access;
|
|
raw_notify(executor, T("@cpflags: Cleared."));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EDIT_CHANNEL_COFLAGS:
|
|
{
|
|
int access = 0;
|
|
if (strcmp(reinterpret_cast<char*>(s), "join") == 0)
|
|
{
|
|
access = CHANNEL_OBJECT_JOIN;
|
|
}
|
|
else if (strcmp(reinterpret_cast<char*>(s), "receive") == 0)
|
|
{
|
|
access = CHANNEL_OBJECT_RECEIVE;
|
|
}
|
|
else if (strcmp(reinterpret_cast<char*>(s), "transmit") == 0)
|
|
{
|
|
access = CHANNEL_OBJECT_TRANSMIT;
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("@coflags: Unknown Flag."));
|
|
}
|
|
|
|
if (access)
|
|
{
|
|
if (add_remove)
|
|
{
|
|
ch->type |= access;
|
|
raw_notify(executor, T("@coflags: Set."));
|
|
}
|
|
else
|
|
{
|
|
ch->type &= ~access;
|
|
raw_notify(executor, T("@coflags: Cleared."));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
sqlite_wt_channel(ch);
|
|
}
|
|
|
|
bool test_join_access(const dbref player, struct channel* chan)
|
|
{
|
|
if (Comm_All(player))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int access;
|
|
if (isPlayer(player))
|
|
{
|
|
access = CHANNEL_PLAYER_JOIN;
|
|
}
|
|
else
|
|
{
|
|
access = CHANNEL_OBJECT_JOIN;
|
|
}
|
|
return ((chan->type & access) != 0
|
|
|| could_doit(player, chan->chan_obj, A_LOCK));
|
|
}
|
|
|
|
bool test_transmit_access(const dbref player, struct channel* chan)
|
|
{
|
|
if (Comm_All(player))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int access;
|
|
if (isPlayer(player))
|
|
{
|
|
access = CHANNEL_PLAYER_TRANSMIT;
|
|
}
|
|
else
|
|
{
|
|
access = CHANNEL_OBJECT_TRANSMIT;
|
|
}
|
|
return ((chan->type & access) != 0
|
|
|| could_doit(player, chan->chan_obj, A_LUSE));
|
|
}
|
|
|
|
bool test_receive_access(const dbref player, struct channel* chan)
|
|
{
|
|
if (Comm_All(player))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int access;
|
|
if (isPlayer(player))
|
|
{
|
|
access = CHANNEL_PLAYER_RECEIVE;
|
|
}
|
|
else
|
|
{
|
|
access = CHANNEL_OBJECT_RECEIVE;
|
|
}
|
|
return ((chan->type & access) != 0
|
|
|| could_doit(player, chan->chan_obj, A_LENTER));
|
|
}
|
|
|
|
// true means continue, and false means stop.
|
|
//
|
|
bool do_comsystem(const dbref who, UTF8* cmd)
|
|
{
|
|
auto t = reinterpret_cast<UTF8*>(strchr(reinterpret_cast<char*>(cmd), ' '));
|
|
if (!t
|
|
|| t - cmd > MAX_ALIAS_LEN
|
|
|| t[1] == '\0')
|
|
{
|
|
// Doesn't fit the pattern of "alias message".
|
|
//
|
|
return true;
|
|
}
|
|
|
|
UTF8 alias[MAX_ALIAS_LEN + 1];
|
|
memcpy(alias, cmd, t - cmd);
|
|
alias[t - cmd] = '\0';
|
|
|
|
const UTF8* ch = get_channel_from_alias(who, alias);
|
|
if (ch[0] == '\0')
|
|
{
|
|
// Not really an alias after all.
|
|
//
|
|
return true;
|
|
}
|
|
|
|
t++;
|
|
do_processcom(who, const_cast<UTF8 *>(ch), t);
|
|
return false;
|
|
}
|
|
|
|
void do_cemit
|
|
(
|
|
const dbref executor,
|
|
const dbref caller,
|
|
const dbref enactor,
|
|
const int eval,
|
|
const int key,
|
|
const int nargs,
|
|
UTF8* chan,
|
|
UTF8* text,
|
|
const UTF8* cargs[],
|
|
const int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->CEmit(executor, chan, text, key);
|
|
return;
|
|
}
|
|
|
|
struct channel* ch = select_channel(chan);
|
|
if (!ch)
|
|
{
|
|
raw_notify(executor, tprintf(T("Channel %s does not exist."), chan));
|
|
return;
|
|
}
|
|
if (!Controls(executor, ch->charge_who)
|
|
&& !Comm_All(executor))
|
|
{
|
|
raw_notify(executor, NOPERM_MESSAGE);
|
|
return;
|
|
}
|
|
UTF8* text2 = alloc_lbuf("do_cemit");
|
|
if (key == CEMIT_NOHEADER)
|
|
{
|
|
mux_strncpy(text2, text, LBUF_SIZE - 1);
|
|
}
|
|
else
|
|
{
|
|
mux_strncpy(text2, tprintf(T("%s %s"), ch->header, text), LBUF_SIZE - 1);
|
|
}
|
|
SendChannelMessage(executor, ch, text2, text2);
|
|
}
|
|
|
|
void do_chopen
|
|
(
|
|
const dbref executor,
|
|
const dbref caller,
|
|
const dbref enactor,
|
|
const int eval,
|
|
const int key,
|
|
const int nargs,
|
|
UTF8* chan,
|
|
UTF8* value,
|
|
const UTF8* cargs[],
|
|
int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->CSet(executor, chan, value, key);
|
|
return;
|
|
}
|
|
|
|
if (key == CSET_LIST)
|
|
{
|
|
do_chanlist(executor, caller, enactor, 0, 1, nullptr, nullptr, 0);
|
|
return;
|
|
}
|
|
|
|
const UTF8* msg = nullptr;
|
|
struct channel* ch = select_channel(chan);
|
|
if (!ch)
|
|
{
|
|
msg = tprintf(T("@cset: Channel %s does not exist."), chan);
|
|
raw_notify(executor, msg);
|
|
return;
|
|
}
|
|
if (!Controls(executor, ch->charge_who)
|
|
&& !Comm_All(executor))
|
|
{
|
|
raw_notify(executor, NOPERM_MESSAGE);
|
|
return;
|
|
}
|
|
dbref thing;
|
|
|
|
switch (key)
|
|
{
|
|
case CSET_PUBLIC:
|
|
ch->type |= CHANNEL_PUBLIC;
|
|
msg = tprintf(T("@cset: Channel %s placed on the public listings."), chan);
|
|
break;
|
|
|
|
case CSET_PRIVATE:
|
|
ch->type &= ~CHANNEL_PUBLIC;
|
|
msg = tprintf(T("@cset: Channel %s taken off the public listings."), chan);
|
|
break;
|
|
|
|
case CSET_LOUD:
|
|
ch->type |= CHANNEL_LOUD;
|
|
msg = tprintf(T("@cset: Channel %s now sends connect/disconnect msgs."), chan);
|
|
break;
|
|
|
|
case CSET_QUIET:
|
|
ch->type &= ~CHANNEL_LOUD;
|
|
msg = tprintf(T("@cset: Channel %s connect/disconnect msgs muted."), chan);
|
|
break;
|
|
|
|
case CSET_SPOOF:
|
|
ch->type |= CHANNEL_SPOOF;
|
|
msg = tprintf(T("@cset: Channel %s set spoofable."), chan);
|
|
break;
|
|
|
|
case CSET_NOSPOOF:
|
|
ch->type &= ~CHANNEL_SPOOF;
|
|
msg = tprintf(T("@cset: Channel %s set unspoofable."), chan);
|
|
break;
|
|
|
|
case CSET_OBJECT:
|
|
init_match(executor, value, NOTYPE);
|
|
match_everything(0);
|
|
thing = match_result();
|
|
|
|
if (thing == NOTHING)
|
|
{
|
|
ch->chan_obj = thing;
|
|
msg = tprintf(T("Channel %s is now disassociated from any channel object."), ch->name);
|
|
}
|
|
else if (Good_obj(thing))
|
|
{
|
|
ch->chan_obj = thing;
|
|
UTF8* buff = unparse_object(executor, thing, false);
|
|
msg = tprintf(T("Channel %s is now using %s as channel object."), ch->name, buff);
|
|
free_lbuf(buff);
|
|
}
|
|
else
|
|
{
|
|
msg = tprintf(T("%d is not a valid channel object."), thing);
|
|
}
|
|
break;
|
|
|
|
case CSET_HEADER:
|
|
do_cheader(executor, chan, value);
|
|
msg = T("Set.");
|
|
break;
|
|
|
|
case CSET_LOG:
|
|
if (do_chanlog(executor, chan, value))
|
|
{
|
|
msg = tprintf(T("@cset: Channel %s maximum history set."), chan);
|
|
}
|
|
else
|
|
{
|
|
msg = tprintf(T("@cset: Maximum history must be a number less than or equal to %d."), MAX_RECALL_REQUEST);
|
|
}
|
|
break;
|
|
|
|
case CSET_LOG_TIME:
|
|
if (do_chanlog_timestamps(executor, chan, value))
|
|
{
|
|
msg = tprintf(T("@cset: Channel %s timestamp logging set."), chan);
|
|
}
|
|
else
|
|
{
|
|
msg = tprintf(T("@cset: Failed. Is logging enabled for %s?"), chan);
|
|
}
|
|
}
|
|
sqlite_wt_channel(ch);
|
|
raw_notify(executor, msg);
|
|
}
|
|
|
|
void do_chboot
|
|
(
|
|
const dbref executor,
|
|
const dbref caller,
|
|
const dbref enactor,
|
|
const int eval,
|
|
int key,
|
|
const int nargs,
|
|
UTF8* channel,
|
|
UTF8* victim,
|
|
const UTF8* cargs[],
|
|
int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->CBoot(executor, channel, victim, key);
|
|
return;
|
|
}
|
|
|
|
// I sure hope it's not going to be that long.
|
|
//
|
|
struct channel* ch = select_channel(channel);
|
|
if (!ch)
|
|
{
|
|
raw_notify(executor, T("@cboot: Unknown channel."));
|
|
return;
|
|
}
|
|
struct comuser* user = select_user(ch, executor);
|
|
if (!user)
|
|
{
|
|
raw_notify(executor, T("@cboot: You are not on that channel."));
|
|
return;
|
|
}
|
|
if (!Controls(executor, ch->charge_who)
|
|
&& !Comm_All(executor))
|
|
{
|
|
raw_notify(executor, T("@cboot: You can\xE2\x80\x99t do that!"));
|
|
return;
|
|
}
|
|
const dbref thing = match_thing(executor, victim);
|
|
|
|
if (!Good_obj(thing))
|
|
{
|
|
return;
|
|
}
|
|
struct comuser* vu = select_user(ch, thing);
|
|
if (!vu)
|
|
{
|
|
raw_notify(executor, tprintf(T("@cboot: %s is not on the channel."),
|
|
Moniker(thing)));
|
|
return;
|
|
}
|
|
|
|
raw_notify(executor, tprintf(T("You boot %s off channel %s."),
|
|
Moniker(thing), ch->name));
|
|
raw_notify(thing, tprintf(T("%s boots you off channel %s."),
|
|
Moniker(thing), ch->name));
|
|
|
|
if (!(key & CBOOT_QUIET))
|
|
{
|
|
UTF8 *mess1, *mess1nct;
|
|
UTF8 *mess2, *mess2nct;
|
|
BuildChannelMessage((ch->type & CHANNEL_SPOOF) != 0, ch->header, user,
|
|
ch->chan_obj, T(":boots"), &mess1, &mess1nct);
|
|
BuildChannelMessage((ch->type & CHANNEL_SPOOF) != 0, nullptr, vu,
|
|
ch->chan_obj, T(":off the channel."), &mess2,
|
|
&mess2nct);
|
|
UTF8* messNormal = alloc_lbuf("do_chboot.messnormal");
|
|
UTF8* messNoComtitle = alloc_lbuf("do_chboot.messnocomtitle");
|
|
UTF8* mnp = messNormal;
|
|
UTF8* mnctp = messNoComtitle;
|
|
if (mess1)
|
|
{
|
|
safe_str(mess1, messNormal, &mnp);
|
|
free_lbuf(mess1);
|
|
}
|
|
if (mess2)
|
|
{
|
|
safe_str(mess2, messNormal, &mnp);
|
|
free_lbuf(mess2);
|
|
}
|
|
*mnp = '\0';
|
|
if (mess1nct)
|
|
{
|
|
safe_str(mess1nct, messNoComtitle, &mnctp);
|
|
free_lbuf(mess1nct);
|
|
}
|
|
if (mess2nct)
|
|
{
|
|
safe_str(mess2nct, messNoComtitle, &mnctp);
|
|
free_lbuf(mess2nct);
|
|
}
|
|
*mnctp = '\0';
|
|
SendChannelMessage(executor, ch, messNormal, messNoComtitle);
|
|
do_delcomchannel(thing, channel, false);
|
|
}
|
|
else
|
|
{
|
|
do_delcomchannel(thing, channel, true);
|
|
}
|
|
}
|
|
|
|
// Process a channel header set request.
|
|
//
|
|
void do_cheader(const dbref player, UTF8* channel, const UTF8* header)
|
|
{
|
|
struct channel* ch = select_channel(channel);
|
|
if (!ch)
|
|
{
|
|
raw_notify(player, T("That channel does not exist."));
|
|
return;
|
|
}
|
|
if (!Controls(player, ch->charge_who)
|
|
&& !Comm_All(player))
|
|
{
|
|
raw_notify(player, NOPERM_MESSAGE);
|
|
return;
|
|
}
|
|
|
|
if ('\0' == header[0])
|
|
{
|
|
// Empty value resets to the default bold [ChannelName] header.
|
|
//
|
|
mux_sprintf(ch->header, sizeof(ch->header),
|
|
T("%s[%s]%s"), COLOR_INTENSE, ch->name, COLOR_RESET);
|
|
}
|
|
else
|
|
{
|
|
// Optimize/terminate any ANSI in the string.
|
|
//
|
|
UTF8 NewHeader_ANSI[MAX_HEADER_LEN + 1];
|
|
const mux_field nLen = StripTabsAndTruncate(header, NewHeader_ANSI,
|
|
MAX_HEADER_LEN, MAX_HEADER_LEN);
|
|
memcpy(ch->header, NewHeader_ANSI, nLen.m_byte + 1);
|
|
}
|
|
sqlite_wt_channel(ch);
|
|
}
|
|
|
|
struct chanlist_node
|
|
{
|
|
UTF8* name;
|
|
struct channel* ptr;
|
|
};
|
|
|
|
static int DCL_CDECL chanlist_comp(const void* a, const void* b)
|
|
{
|
|
const auto ca = reinterpret_cast<const chanlist_node *>(a);
|
|
const auto cb = reinterpret_cast<const chanlist_node *>(b);
|
|
return mux_stricmp(ca->name, cb->name);
|
|
}
|
|
|
|
void do_chanlist
|
|
(
|
|
const dbref executor,
|
|
const dbref caller,
|
|
const dbref enactor,
|
|
const int eval,
|
|
int key,
|
|
UTF8* pattern,
|
|
const UTF8* cargs[],
|
|
const int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIComsysControl)
|
|
{
|
|
mudstate.pIComsysControl->ChanList(executor, pattern, key);
|
|
return;
|
|
}
|
|
|
|
if (key & CLIST_FULL)
|
|
{
|
|
do_listchannels(executor, pattern);
|
|
return;
|
|
}
|
|
|
|
dbref owner;
|
|
int flags = 0;
|
|
|
|
if (key & CLIST_HEADERS)
|
|
{
|
|
raw_notify(executor, T("*** Channel Owner Header"));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("*** Channel Owner Description"));
|
|
}
|
|
|
|
bool bWild;
|
|
if (nullptr != pattern
|
|
&& '\0' != *pattern)
|
|
{
|
|
bWild = true;
|
|
}
|
|
else
|
|
{
|
|
bWild = false;
|
|
}
|
|
|
|
#define MAX_SUPPORTED_NUM_ENTRIES 10000
|
|
|
|
auto entries = mudstate.channel_names.size();
|
|
if (MAX_SUPPORTED_NUM_ENTRIES < entries)
|
|
{
|
|
// Nobody should have so many channels.
|
|
//
|
|
entries = MAX_SUPPORTED_NUM_ENTRIES;
|
|
}
|
|
|
|
if (0 < entries)
|
|
{
|
|
for (auto it = mudstate.channel_names.begin(); it != mudstate.channel_names.end(); ++it)
|
|
{
|
|
const auto ch = it->second;
|
|
|
|
if (!bWild != quick_wild(pattern, ch->name))
|
|
{
|
|
if (Comm_All(executor)
|
|
|| (ch->type & CHANNEL_PUBLIC)
|
|
|| Controls(executor, ch->charge_who)
|
|
|| nullptr != select_user(ch, executor))
|
|
{
|
|
const UTF8* pBuffer = nullptr;
|
|
UTF8* atrstr = nullptr;
|
|
|
|
if (key & CLIST_HEADERS)
|
|
{
|
|
pBuffer = ch->header;
|
|
}
|
|
else
|
|
{
|
|
if (NOTHING != ch->chan_obj)
|
|
{
|
|
atrstr = atr_pget(ch->chan_obj, A_DESC, &owner, &flags);
|
|
}
|
|
|
|
if (nullptr != atrstr && '\0' != atrstr[0])
|
|
{
|
|
pBuffer = atrstr;
|
|
}
|
|
else
|
|
{
|
|
pBuffer = T("No description.");
|
|
}
|
|
}
|
|
|
|
UTF8* temp = alloc_mbuf("do_chanlist_temp");
|
|
mux_sprintf(temp, MBUF_SIZE, T("%c%c%c "),
|
|
(ch->type & (CHANNEL_PUBLIC)) ? 'P' : '-',
|
|
(ch->type & (CHANNEL_LOUD)) ? 'L' : '-',
|
|
(ch->type & (CHANNEL_SPOOF)) ? 'S' : '-');
|
|
mux_field iPos(4, 4);
|
|
|
|
iPos += StripTabsAndTruncate(ch->name,
|
|
temp + iPos.m_byte,
|
|
(MBUF_SIZE - 1) - iPos.m_byte,
|
|
13);
|
|
iPos = PadField(temp, MBUF_SIZE - 1, 18, iPos);
|
|
iPos += StripTabsAndTruncate(Moniker(ch->charge_who),
|
|
temp + iPos.m_byte,
|
|
(MBUF_SIZE - 1) - iPos.m_byte,
|
|
15);
|
|
iPos = PadField(temp, MBUF_SIZE - 1, 34, iPos);
|
|
iPos += StripTabsAndTruncate(pBuffer,
|
|
temp + iPos.m_byte,
|
|
(MBUF_SIZE - 1) - iPos.m_byte,
|
|
45);
|
|
iPos = PadField(temp, MBUF_SIZE - 1, 79, iPos);
|
|
|
|
raw_notify(executor, temp);
|
|
free_mbuf(temp);
|
|
|
|
if (nullptr != atrstr)
|
|
{
|
|
free_lbuf(atrstr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
raw_notify(executor, T("-- End of list of Channels --"));
|
|
}
|
|
|
|
// Returns a player's comtitle for a named channel.
|
|
//
|
|
FUNCTION(fun_comtitle)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
dbref victim = lookup_player(executor, fargs[0], true);
|
|
if (!Good_obj(victim))
|
|
{
|
|
init_match(executor, fargs[0], TYPE_THING);
|
|
match_everything(0);
|
|
victim = match_result();
|
|
if (!Good_obj(victim))
|
|
{
|
|
safe_str(T("#-1 OBJECT DOES NOT EXIST"), buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct channel* chn = select_channel(fargs[1]);
|
|
if (!chn)
|
|
{
|
|
safe_str(T("#-1 CHANNEL DOES NOT EXIST"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
comsys_t* c = get_comsys(executor);
|
|
struct comuser* user;
|
|
|
|
bool onchannel = false;
|
|
if (Wizard(executor))
|
|
{
|
|
onchannel = true;
|
|
}
|
|
else
|
|
{
|
|
user = select_user(chn, executor);
|
|
if (user)
|
|
{
|
|
onchannel = true;
|
|
}
|
|
}
|
|
|
|
if (!onchannel)
|
|
{
|
|
safe_noperm(buff, bufc);
|
|
return;
|
|
}
|
|
|
|
user = select_user(chn, victim);
|
|
if (user)
|
|
{
|
|
safe_str(reinterpret_cast<const UTF8 *>(user->title.c_str()), buff, bufc);
|
|
return;
|
|
}
|
|
safe_str(T("#-1 OBJECT NOT ON THAT CHANNEL"), buff, bufc);
|
|
}
|
|
|
|
// Returns a player's comsys alias for a named channel.
|
|
//
|
|
FUNCTION(fun_comalias)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
dbref victim = lookup_player(executor, fargs[0], true);
|
|
if (!Good_obj(victim))
|
|
{
|
|
init_match(executor, fargs[0], TYPE_THING);
|
|
match_everything(0);
|
|
victim = match_result();
|
|
if (!Good_obj(victim))
|
|
{
|
|
safe_str(T("#-1 OBJECT DOES NOT EXIST"), buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct channel* chn = select_channel(fargs[1]);
|
|
if (!chn)
|
|
{
|
|
safe_str(T("#-1 CHANNEL DOES NOT EXIST"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Wizards can get the comalias for anyone. Players and objects can check
|
|
// for themselves. Objects that Inherit can check for their owners.
|
|
//
|
|
if (!Wizard(executor)
|
|
&& executor != victim
|
|
&& (Owner(executor) != victim
|
|
|| !Inherits(executor)))
|
|
{
|
|
safe_noperm(buff, bufc);
|
|
return;
|
|
}
|
|
|
|
auto it = comsys_table.find(victim);
|
|
if (it != comsys_table.end())
|
|
{
|
|
const comsys_t &cc = it->second;
|
|
for (const auto &ca : cc.aliases)
|
|
{
|
|
if (ca.channel == reinterpret_cast<const char *>(fargs[1]))
|
|
{
|
|
safe_str(reinterpret_cast<const UTF8 *>(ca.alias.c_str()), buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
safe_str(T("#-1 OBJECT NOT ON THAT CHANNEL"), buff, bufc);
|
|
}
|
|
|
|
// Returns a list of channels.
|
|
//
|
|
FUNCTION(fun_channels)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
|
|
SEP sep;
|
|
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
|
|
{
|
|
return;
|
|
}
|
|
|
|
dbref who = NOTHING;
|
|
if (nfargs >= 1 && fargs[0][0] != '\0')
|
|
{
|
|
who = lookup_player(executor, fargs[0], true);
|
|
if (who == NOTHING
|
|
&& mux_stricmp(fargs[0], T("all")) != 0)
|
|
{
|
|
safe_str(T("#-1 PLAYER NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int pg_offset = 0;
|
|
int pg_limit = 0;
|
|
if (nfargs >= 3) pg_offset = mux_atol(fargs[2]);
|
|
if (nfargs >= 4) pg_limit = mux_atol(fargs[3]);
|
|
if (pg_offset < 0) pg_offset = 0;
|
|
if (pg_limit < 0) pg_limit = 0;
|
|
|
|
int pos = 0;
|
|
int count = 0;
|
|
bool bFirst = true;
|
|
for (auto it = mudstate.channel_names.begin(); it != mudstate.channel_names.end(); ++it)
|
|
{
|
|
const auto ch = it->second;
|
|
|
|
if ( (Comm_All(executor)
|
|
|| (ch->type & CHANNEL_PUBLIC)
|
|
|| Controls(executor, ch->charge_who)
|
|
|| nullptr != select_user(ch, executor))
|
|
&& (who == NOTHING
|
|
|| Controls(who, ch->charge_who)))
|
|
{
|
|
if (pos < pg_offset)
|
|
{
|
|
pos++;
|
|
continue;
|
|
}
|
|
if (pg_limit > 0 && count >= pg_limit)
|
|
break;
|
|
|
|
if (!bFirst)
|
|
{
|
|
print_sep(sep, buff, bufc);
|
|
}
|
|
safe_str(ch->name, buff, bufc);
|
|
bFirst = false;
|
|
count++;
|
|
pos++;
|
|
}
|
|
}
|
|
}
|
|
|
|
FUNCTION(fun_chanobj)
|
|
{
|
|
UNUSED_PARAMETER(executor);
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel* ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
const dbref obj = ch->chan_obj;
|
|
if (Good_obj(obj))
|
|
{
|
|
safe_str(tprintf(T("#%d"), obj), buff, bufc);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T("#-1"), buff, bufc);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Channel query functions — PennMUSH-compatible.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
FUNCTION(fun_cowner)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Visibility check.
|
|
//
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
safe_tprintf_str(buff, bufc, T("#%d"), ch->charge_who);
|
|
}
|
|
|
|
// cmogrifier(<channel>) — Returns the dbref of the channel object (which
|
|
// is also the mogrifier object in TinyMUX). Returns #-1 if no channel
|
|
// object is set. PennMUSH compatibility.
|
|
//
|
|
FUNCTION(fun_cmogrifier)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
if (Good_obj(ch->chan_obj))
|
|
{
|
|
safe_tprintf_str(buff, bufc, T("#%d"), ch->chan_obj);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T("#-1"), buff, bufc);
|
|
}
|
|
}
|
|
|
|
FUNCTION(fun_cusers)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
safe_str(mux_ltoa_t(static_cast<int>(ch->users.size())), buff, bufc);
|
|
}
|
|
|
|
FUNCTION(fun_cmsgs)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
safe_str(mux_ltoa_t(ch->num_messages), buff, bufc);
|
|
}
|
|
|
|
FUNCTION(fun_cbuffer)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Buffer size is the MAX_LOG attribute on the channel object.
|
|
//
|
|
int logmax = 0;
|
|
if (Good_obj(ch->chan_obj))
|
|
{
|
|
ATTR *pattr = atr_str(T("MAX_LOG"));
|
|
if (pattr && pattr->number)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf maxbuf = LBuf_Adopt(atr_get("fun_cbuffer", ch->chan_obj,
|
|
pattr->number, &aowner, &aflags));
|
|
logmax = mux_atol(maxbuf);
|
|
}
|
|
}
|
|
safe_str(mux_ltoa_t(logmax), buff, bufc);
|
|
}
|
|
|
|
FUNCTION(fun_cdesc)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Description is the DESC attribute on the channel object.
|
|
//
|
|
if (Good_obj(ch->chan_obj))
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf desc = LBuf_Adopt(atr_pget(ch->chan_obj, A_DESC, &aowner, &aflags));
|
|
if ('\0' != desc[0])
|
|
{
|
|
safe_str(desc, buff, bufc);
|
|
}
|
|
}
|
|
}
|
|
|
|
FUNCTION(fun_cflags)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
if (nfargs >= 2)
|
|
{
|
|
// cflags(channel, player) — per-user flags.
|
|
//
|
|
dbref target = match_thing(executor, fargs[1]);
|
|
if (!Good_obj(target))
|
|
{
|
|
safe_str(T("#-1 NO MATCH"), buff, bufc);
|
|
return;
|
|
}
|
|
struct comuser *user = select_user(ch, target);
|
|
if (nullptr == user)
|
|
{
|
|
safe_str(T("#-1 NOT ON CHANNEL"), buff, bufc);
|
|
return;
|
|
}
|
|
// Return user flags: on/off, gag, comtitle status.
|
|
//
|
|
if (!user->bUserIsOn)
|
|
{
|
|
safe_chr('O', buff, bufc);
|
|
}
|
|
if (user->bGagJoinLeave)
|
|
{
|
|
safe_chr('G', buff, bufc);
|
|
}
|
|
if (!user->ComTitleStatus)
|
|
{
|
|
safe_chr('Q', buff, bufc);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// cflags(channel) — channel flags.
|
|
//
|
|
if (ch->type & CHANNEL_PUBLIC) safe_chr('P', buff, bufc);
|
|
if (ch->type & CHANNEL_LOUD) safe_chr('L', buff, bufc);
|
|
if (ch->type & CHANNEL_SPOOF) safe_chr('S', buff, bufc);
|
|
}
|
|
}
|
|
|
|
FUNCTION(fun_cstatus)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
dbref target = match_thing(executor, fargs[1]);
|
|
if (!Good_obj(target))
|
|
{
|
|
safe_str(T("#-1 NO MATCH"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
struct comuser *user = select_user(ch, target);
|
|
if (nullptr == user)
|
|
{
|
|
safe_str(T("Off"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
if (!user->bUserIsOn)
|
|
{
|
|
safe_str(T("Off"), buff, bufc);
|
|
}
|
|
else if (user->bGagJoinLeave)
|
|
{
|
|
safe_str(T("Gag"), buff, bufc);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T("On"), buff, bufc);
|
|
}
|
|
}
|
|
|
|
FUNCTION(fun_crecall)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Must be on the channel.
|
|
//
|
|
struct comuser *user = select_user(ch, executor);
|
|
if (nullptr == user || !user->bUserIsOn)
|
|
{
|
|
safe_str(T("#-1 NOT ON CHANNEL"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Get buffer depth from channel object.
|
|
//
|
|
if (!Good_obj(ch->chan_obj))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ATTR *pattr = atr_str(T("MAX_LOG"));
|
|
int logmax = 0;
|
|
if (pattr && pattr->number)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf maxbuf = LBuf_Adopt(atr_get("fun_crecall", ch->chan_obj,
|
|
pattr->number, &aowner, &aflags));
|
|
logmax = mux_atol(maxbuf);
|
|
}
|
|
if (logmax < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Number of lines to recall (default 1, max logmax).
|
|
//
|
|
int nLines = 1;
|
|
if (nfargs >= 2 && fargs[1][0] != '\0')
|
|
{
|
|
nLines = mux_atol(fargs[1]);
|
|
if (nLines < 1) nLines = 1;
|
|
if (nLines > logmax) nLines = logmax;
|
|
}
|
|
|
|
// Output separator (default newline).
|
|
//
|
|
SEP sep;
|
|
sep.n = 1;
|
|
sep.str[0] = '\r';
|
|
sep.str[1] = '\0';
|
|
if (nfargs >= 3)
|
|
{
|
|
memcpy(sep.str, fargs[2], strlen(reinterpret_cast<char *>(fargs[2])) + 1);
|
|
sep.n = strlen(reinterpret_cast<char *>(fargs[2]));
|
|
}
|
|
|
|
int histnum = ch->num_messages - nLines;
|
|
bool bFirst = true;
|
|
for (int count = 0; count < nLines; count++)
|
|
{
|
|
histnum++;
|
|
const UTF8 *attrname = tprintf(T("HISTORY_%d"),
|
|
((histnum % logmax) + logmax) % logmax);
|
|
int atr = mkattr(GOD, attrname);
|
|
if (0 < atr)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf msg = LBuf_Adopt(atr_get("fun_crecall", ch->chan_obj,
|
|
atr, &aowner, &aflags));
|
|
if ('\0' != msg[0])
|
|
{
|
|
if (!bFirst)
|
|
{
|
|
safe_copy_buf(sep.str, sep.n, buff, bufc);
|
|
}
|
|
safe_str(msg, buff, bufc);
|
|
bFirst = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// chaninfo(channel, field) — generic channel metadata accessor.
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// Visibility: PUBLIC, or subscriber, or Comm_All, or Controls(charge_who).
|
|
// The "object" field additionally requires Wizard.
|
|
//
|
|
FUNCTION(fun_chaninfo)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who)
|
|
&& nullptr == select_user(ch, executor))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
const UTF8 *field = fargs[1];
|
|
|
|
if (0 == mux_stricmp(field, T("name")))
|
|
{
|
|
safe_str(ch->name, buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("header")))
|
|
{
|
|
safe_str(ch->header, buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("owner")))
|
|
{
|
|
safe_tprintf_str(buff, bufc, T("#%d"), ch->charge_who);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("object")))
|
|
{
|
|
if (!Wizard(executor))
|
|
{
|
|
safe_noperm(buff, bufc);
|
|
return;
|
|
}
|
|
if (Good_obj(ch->chan_obj))
|
|
{
|
|
safe_tprintf_str(buff, bufc, T("#%d"), ch->chan_obj);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T("#-1"), buff, bufc);
|
|
}
|
|
}
|
|
else if (0 == mux_stricmp(field, T("type")))
|
|
{
|
|
safe_str(mux_ltoa_t(ch->type), buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("flags")))
|
|
{
|
|
if (ch->type & CHANNEL_PUBLIC) safe_chr('P', buff, bufc);
|
|
if (ch->type & CHANNEL_LOUD) safe_chr('L', buff, bufc);
|
|
if (ch->type & CHANNEL_SPOOF) safe_chr('S', buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("charge")))
|
|
{
|
|
safe_str(mux_ltoa_t(ch->charge), buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("users")))
|
|
{
|
|
safe_str(mux_ltoa_t(static_cast<int>(ch->users.size())), buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("msgs")))
|
|
{
|
|
safe_str(mux_ltoa_t(ch->num_messages), buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("desc")))
|
|
{
|
|
if (Good_obj(ch->chan_obj))
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf desc = LBuf_Adopt(atr_pget(ch->chan_obj, A_DESC, &aowner, &aflags));
|
|
if ('\0' != desc[0])
|
|
{
|
|
safe_str(desc, buff, bufc);
|
|
}
|
|
}
|
|
}
|
|
else if (0 == mux_stricmp(field, T("buffer")))
|
|
{
|
|
int logmax = 0;
|
|
if (Good_obj(ch->chan_obj))
|
|
{
|
|
ATTR *pattr = atr_str(T("MAX_LOG"));
|
|
if (pattr && pattr->number)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf maxbuf = LBuf_Adopt(atr_get("fun_chaninfo", ch->chan_obj,
|
|
pattr->number, &aowner, &aflags));
|
|
logmax = mux_atol(maxbuf);
|
|
}
|
|
}
|
|
safe_str(mux_ltoa_t(logmax), buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("canjoin")))
|
|
{
|
|
safe_chr(test_join_access(executor, ch) ? '1' : '0', buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("cantransmit")))
|
|
{
|
|
safe_chr(test_transmit_access(executor, ch) ? '1' : '0', buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("canreceive")))
|
|
{
|
|
safe_chr(test_receive_access(executor, ch) ? '1' : '0', buff, bufc);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T("#-1 INVALID FIELD"), buff, bufc);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// chanfind(header) — reverse-lookup channel name from header string.
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// Given a channel header (display name), returns the canonical channel name.
|
|
// Useful when players see e.g. "<PublicServices>" in chat and need to find
|
|
// the internal name "PubServ". Case-insensitive match.
|
|
//
|
|
// Visibility: same subscriber-aware model as chaninfo.
|
|
//
|
|
FUNCTION(fun_chanfind)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nfargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
const UTF8 *target = fargs[0];
|
|
|
|
for (auto it = mudstate.channel_names.begin();
|
|
it != mudstate.channel_names.end(); ++it)
|
|
{
|
|
const auto ch = it->second;
|
|
|
|
if (0 == mux_stricmp(target, ch->header))
|
|
{
|
|
// Visibility check.
|
|
//
|
|
if ( (ch->type & CHANNEL_PUBLIC)
|
|
|| Comm_All(executor)
|
|
|| Controls(executor, ch->charge_who)
|
|
|| nullptr != select_user(ch, executor))
|
|
{
|
|
safe_str(ch->name, buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// chanusers(channel[, separator[, field]]) — list subscriber data.
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// With no field arg (or empty), returns delimited dbrefs.
|
|
// With a field arg, returns that field for each subscriber:
|
|
// title, status, flags, gagjoin, comtitles, alias, name.
|
|
//
|
|
// "alias" requires Wizard (exposes other players' aliases).
|
|
// Other fields follow the co-member rule (executor must be visible).
|
|
//
|
|
// Visibility: PUBLIC, or subscriber, or Comm_All, or Controls(charge_who).
|
|
//
|
|
FUNCTION(fun_chanusers)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
|
|
SEP sep;
|
|
if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING))
|
|
{
|
|
return;
|
|
}
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who)
|
|
&& nullptr == select_user(ch, executor))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Determine output field (default: dbref).
|
|
//
|
|
enum FieldType { FT_DBREF, FT_TITLE, FT_STATUS, FT_FLAGS,
|
|
FT_GAGJOIN, FT_COMTITLES, FT_ALIAS, FT_NAME };
|
|
FieldType ft = FT_DBREF;
|
|
|
|
if (nfargs >= 3 && fargs[2][0] != '\0')
|
|
{
|
|
const UTF8 *field = fargs[2];
|
|
if (0 == mux_stricmp(field, T("title")))
|
|
{
|
|
ft = FT_TITLE;
|
|
}
|
|
else if (0 == mux_stricmp(field, T("status")))
|
|
{
|
|
ft = FT_STATUS;
|
|
}
|
|
else if (0 == mux_stricmp(field, T("flags")))
|
|
{
|
|
ft = FT_FLAGS;
|
|
}
|
|
else if (0 == mux_stricmp(field, T("gagjoin")))
|
|
{
|
|
ft = FT_GAGJOIN;
|
|
}
|
|
else if (0 == mux_stricmp(field, T("comtitles")))
|
|
{
|
|
ft = FT_COMTITLES;
|
|
}
|
|
else if (0 == mux_stricmp(field, T("alias")))
|
|
{
|
|
if (!Wizard(executor))
|
|
{
|
|
safe_noperm(buff, bufc);
|
|
return;
|
|
}
|
|
ft = FT_ALIAS;
|
|
}
|
|
else if (0 == mux_stricmp(field, T("name")))
|
|
{
|
|
ft = FT_NAME;
|
|
}
|
|
else
|
|
{
|
|
safe_str(T("#-1 INVALID FIELD"), buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int pg_offset = 0;
|
|
int pg_limit = 0;
|
|
if (nfargs >= 4) pg_offset = mux_atol(fargs[3]);
|
|
if (nfargs >= 5) pg_limit = mux_atol(fargs[4]);
|
|
if (pg_offset < 0) pg_offset = 0;
|
|
if (pg_limit < 0) pg_limit = 0;
|
|
|
|
int pos = 0;
|
|
int count = 0;
|
|
bool bFirst = true;
|
|
for (const auto &kv : ch->users)
|
|
{
|
|
if (pos < pg_offset)
|
|
{
|
|
pos++;
|
|
continue;
|
|
}
|
|
if (pg_limit > 0 && count >= pg_limit)
|
|
break;
|
|
|
|
if (!bFirst)
|
|
{
|
|
print_sep(sep, buff, bufc);
|
|
}
|
|
bFirst = false;
|
|
|
|
const comuser &user = kv.second;
|
|
|
|
switch (ft)
|
|
{
|
|
case FT_DBREF:
|
|
safe_tprintf_str(buff, bufc, T("#%d"), kv.first);
|
|
break;
|
|
|
|
case FT_NAME:
|
|
safe_str(Moniker(kv.first), buff, bufc);
|
|
break;
|
|
|
|
case FT_TITLE:
|
|
safe_str(reinterpret_cast<const UTF8 *>(user.title.c_str()),
|
|
buff, bufc);
|
|
break;
|
|
|
|
case FT_STATUS:
|
|
safe_str(user.bUserIsOn ? T("On") : T("Off"), buff, bufc);
|
|
break;
|
|
|
|
case FT_FLAGS:
|
|
if (!user.bUserIsOn) safe_chr('O', buff, bufc);
|
|
if (user.bGagJoinLeave) safe_chr('G', buff, bufc);
|
|
if (!user.ComTitleStatus) safe_chr('Q', buff, bufc);
|
|
break;
|
|
|
|
case FT_GAGJOIN:
|
|
safe_chr(user.bGagJoinLeave ? '1' : '0', buff, bufc);
|
|
break;
|
|
|
|
case FT_COMTITLES:
|
|
safe_chr(user.ComTitleStatus ? '1' : '0', buff, bufc);
|
|
break;
|
|
|
|
case FT_ALIAS:
|
|
{
|
|
auto it = comsys_table.find(kv.first);
|
|
if (it != comsys_table.end())
|
|
{
|
|
const comsys_t &cc = it->second;
|
|
for (const auto &ca : cc.aliases)
|
|
{
|
|
if (0 == mux_stricmp(
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()),
|
|
ch->name))
|
|
{
|
|
safe_str(reinterpret_cast<const UTF8 *>(
|
|
ca.alias.c_str()), buff, bufc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
count++;
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// chanuser(channel, player, field) — per-user channel data.
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// Per-field permissions:
|
|
// alias: self, or Owner(executor)==victim && Inherits(executor), or Wizard
|
|
// title, status, flags, gagjoin, comtitles:
|
|
// self, or both executor and victim on channel, or Wizard
|
|
//
|
|
FUNCTION(fun_chanuser)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
struct channel *ch = select_channel(fargs[0]);
|
|
if (nullptr == ch)
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Channel visibility: subscriber-aware.
|
|
//
|
|
if ( !(ch->type & CHANNEL_PUBLIC)
|
|
&& !Comm_All(executor)
|
|
&& !Controls(executor, ch->charge_who)
|
|
&& nullptr == select_user(ch, executor))
|
|
{
|
|
safe_str(T("#-1 CHANNEL NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// Resolve target player/object.
|
|
//
|
|
dbref victim = lookup_player(executor, fargs[1], true);
|
|
if (!Good_obj(victim))
|
|
{
|
|
init_match(executor, fargs[1], TYPE_THING);
|
|
match_everything(0);
|
|
victim = match_result();
|
|
if (!Good_obj(victim))
|
|
{
|
|
safe_str(T("#-1 PLAYER NOT FOUND"), buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const UTF8 *field = fargs[2];
|
|
|
|
// The "alias" field has its own permission rule.
|
|
//
|
|
if (0 == mux_stricmp(field, T("alias")))
|
|
{
|
|
if ( !Wizard(executor)
|
|
&& executor != victim
|
|
&& (Owner(executor) != victim
|
|
|| !Inherits(executor)))
|
|
{
|
|
safe_noperm(buff, bufc);
|
|
return;
|
|
}
|
|
|
|
auto it = comsys_table.find(victim);
|
|
if (it != comsys_table.end())
|
|
{
|
|
const comsys_t &cc = it->second;
|
|
for (const auto &ca : cc.aliases)
|
|
{
|
|
if (0 == mux_stricmp(
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()),
|
|
fargs[0]))
|
|
{
|
|
safe_str(reinterpret_cast<const UTF8 *>(ca.alias.c_str()),
|
|
buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
safe_str(T("#-1 NOT ON CHANNEL"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
// All other fields: self, or co-member, or Wizard.
|
|
//
|
|
if ( !Wizard(executor)
|
|
&& executor != victim)
|
|
{
|
|
struct comuser *executor_user = select_user(ch, executor);
|
|
if (nullptr == executor_user)
|
|
{
|
|
safe_noperm(buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Look up the target's user record on this channel.
|
|
//
|
|
struct comuser *user = select_user(ch, victim);
|
|
if (nullptr == user)
|
|
{
|
|
safe_str(T("#-1 NOT ON CHANNEL"), buff, bufc);
|
|
return;
|
|
}
|
|
|
|
if (0 == mux_stricmp(field, T("title")))
|
|
{
|
|
safe_str(reinterpret_cast<const UTF8 *>(user->title.c_str()),
|
|
buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("status")))
|
|
{
|
|
if (user->bUserIsOn)
|
|
{
|
|
safe_str(T("On"), buff, bufc);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T("Off"), buff, bufc);
|
|
}
|
|
}
|
|
else if (0 == mux_stricmp(field, T("flags")))
|
|
{
|
|
if (!user->bUserIsOn)
|
|
{
|
|
safe_chr('O', buff, bufc);
|
|
}
|
|
if (user->bGagJoinLeave)
|
|
{
|
|
safe_chr('G', buff, bufc);
|
|
}
|
|
if (!user->ComTitleStatus)
|
|
{
|
|
safe_chr('Q', buff, bufc);
|
|
}
|
|
}
|
|
else if (0 == mux_stricmp(field, T("gagjoin")))
|
|
{
|
|
safe_chr(user->bGagJoinLeave ? '1' : '0', buff, bufc);
|
|
}
|
|
else if (0 == mux_stricmp(field, T("comtitles")))
|
|
{
|
|
safe_chr(user->ComTitleStatus ? '1' : '0', buff, bufc);
|
|
}
|
|
else
|
|
{
|
|
safe_str(T("#-1 INVALID FIELD"), buff, bufc);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SQLite comsys bulk sync and load.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#include "sqlite_backend.h"
|
|
|
|
static void clear_runtime_comsys_data(void)
|
|
{
|
|
for (auto it = mudstate.channel_names.begin(); it != mudstate.channel_names.end(); ++it)
|
|
{
|
|
channel *ch = it->second;
|
|
delete ch;
|
|
}
|
|
mudstate.channel_names.clear();
|
|
|
|
comsys_table.clear();
|
|
}
|
|
|
|
bool sqlite_sync_comsys(void)
|
|
{
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.Begin())
|
|
{
|
|
return false;
|
|
}
|
|
if (!sqldb.ClearComsysTables())
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
|
|
// Sync channels and their users.
|
|
//
|
|
for (auto it = mudstate.channel_names.begin(); it != mudstate.channel_names.end(); ++it)
|
|
{
|
|
struct channel *ch = it->second;
|
|
if (!sqldb.SyncChannel(ch->name, ch->header,
|
|
ch->type, ch->temp1, ch->temp2,
|
|
ch->charge, ch->charge_who, ch->amount_col,
|
|
ch->num_messages, ch->chan_obj))
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
|
|
for (auto &kv : ch->users)
|
|
{
|
|
comuser &user = kv.second;
|
|
if (user.who >= 0 && user.who < mudstate.db_top)
|
|
{
|
|
if (!sqldb.SyncChannelUser(ch->name, user.who,
|
|
user.bUserIsOn, user.ComTitleStatus,
|
|
user.bGagJoinLeave,
|
|
reinterpret_cast<const UTF8 *>(user.title.c_str())))
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sync player channel aliases.
|
|
//
|
|
for (auto &kv : comsys_table)
|
|
{
|
|
const comsys_t &c = kv.second;
|
|
for (const auto &ca : c.aliases)
|
|
{
|
|
// Skip orphaned aliases for channels that no longer exist.
|
|
//
|
|
if (nullptr == select_channel(
|
|
const_cast<UTF8 *>(reinterpret_cast<const UTF8 *>(ca.channel.c_str()))))
|
|
{
|
|
continue;
|
|
}
|
|
if (!sqldb.SyncPlayerChannel(c.who,
|
|
reinterpret_cast<const UTF8 *>(ca.alias.c_str()),
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str())))
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!sqldb.PutMeta("has_comsys", 1))
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
if (!sqldb.Commit())
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int sqlite_load_comsys(void)
|
|
{
|
|
mudstate.bSQLiteLoading = true;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
|
|
int has_comsys = 0;
|
|
CSQLiteDB::MetaGetResult has_comsys_meta = sqldb.GetMetaEx("has_comsys", &has_comsys);
|
|
if (CSQLiteDB::MetaGetResult::Error == has_comsys_meta)
|
|
{
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
if (CSQLiteDB::MetaGetResult::Found != has_comsys_meta || 0 == has_comsys)
|
|
{
|
|
mudstate.bSQLiteLoading = false;
|
|
return 0;
|
|
}
|
|
|
|
// Reset in-memory structures before repopulating.
|
|
//
|
|
clear_runtime_comsys_data();
|
|
|
|
// Load channels.
|
|
//
|
|
if (!sqldb.LoadAllChannels([](const UTF8 *name, const UTF8 *header,
|
|
int type, int temp1, int temp2, int charge, int charge_who,
|
|
int amount_col, int num_messages, int chan_obj)
|
|
{
|
|
auto ch = new channel();
|
|
|
|
mux_strncpy(ch->name, name, MAX_CHANNEL_LEN);
|
|
mux_strncpy(ch->header, header, MAX_HEADER_LEN);
|
|
ch->type = type;
|
|
ch->temp1 = temp1;
|
|
ch->temp2 = temp2;
|
|
ch->charge = charge;
|
|
ch->charge_who = charge_who;
|
|
ch->amount_col = amount_col;
|
|
ch->num_messages = num_messages;
|
|
ch->chan_obj = chan_obj;
|
|
|
|
size_t nName = strlen(reinterpret_cast<const char *>(ch->name));
|
|
vector<UTF8> channel_name_vector(ch->name, ch->name + nName);
|
|
mudstate.channel_names.insert(make_pair(channel_name_vector, ch));
|
|
}))
|
|
{
|
|
clear_runtime_comsys_data();
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
|
|
// Load channel users.
|
|
//
|
|
if (!sqldb.LoadAllChannelUsers([](const UTF8 *channel_name, int who,
|
|
bool is_on, bool comtitle_status, bool gag_join_leave,
|
|
const UTF8 *title)
|
|
{
|
|
auto ch_name = const_cast<UTF8 *>(channel_name);
|
|
struct channel *ch = select_channel(ch_name);
|
|
if (!ch)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!Good_dbref(who) || isGarbage(who))
|
|
{
|
|
return;
|
|
}
|
|
|
|
comuser cu;
|
|
cu.who = who;
|
|
cu.bUserIsOn = is_on;
|
|
cu.ComTitleStatus = comtitle_status;
|
|
cu.bGagJoinLeave = gag_join_leave;
|
|
cu.title.assign(reinterpret_cast<const char *>(title));
|
|
|
|
auto result = ch->users.emplace(who, std::move(cu));
|
|
comuser &user = result.first->second;
|
|
|
|
if (!(isPlayer(user.who))
|
|
&& !(Going(user.who)
|
|
&& (God(Owner(user.who)))))
|
|
{
|
|
do_joinchannel(user.who, ch);
|
|
}
|
|
user.bConnected = true;
|
|
}))
|
|
{
|
|
clear_runtime_comsys_data();
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
|
|
// Load player channel aliases.
|
|
//
|
|
// We need to group by player. Use a map to collect aliases per player,
|
|
// then create comsys_t entries.
|
|
//
|
|
struct PlayerAliasEntry
|
|
{
|
|
string alias;
|
|
string channel_name;
|
|
};
|
|
std::map<int, std::vector<PlayerAliasEntry>> player_aliases;
|
|
|
|
if (!sqldb.LoadAllPlayerChannels([&player_aliases](int who, const UTF8 *alias,
|
|
const UTF8 *channel_name)
|
|
{
|
|
PlayerAliasEntry entry;
|
|
entry.alias.assign(reinterpret_cast<const char *>(alias));
|
|
entry.channel_name.assign(reinterpret_cast<const char *>(channel_name));
|
|
player_aliases[who].push_back(std::move(entry));
|
|
}))
|
|
{
|
|
player_aliases.clear();
|
|
clear_runtime_comsys_data();
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
|
|
for (auto &pa : player_aliases)
|
|
{
|
|
int who = pa.first;
|
|
auto &entries = pa.second;
|
|
|
|
if (!Good_obj(who))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
comsys_t c;
|
|
c.who = who;
|
|
|
|
for (auto &e : entries)
|
|
{
|
|
com_alias ca;
|
|
ca.alias = std::move(e.alias);
|
|
ca.channel = std::move(e.channel_name);
|
|
c.aliases.push_back(std::move(ca));
|
|
}
|
|
sort_com_aliases(c);
|
|
comsys_table[who] = std::move(c);
|
|
}
|
|
|
|
mudstate.bSQLiteLoading = false;
|
|
return 1;
|
|
}
|