tinymux/mux/modules/engine/predicates.cpp
Stephen Dennis 85872f3f0f Fix stubslave POLLOUT deadlock and respect safer_iter in @switch
stubslave: The child side of the socketpair was blocking, so read()
would hang when poll() woke for POLLOUT without POLLIN. Set fd 0 to
O_NONBLOCK in main() so read() returns EAGAIN in that case.

@switch: The iter context propagation now checks mudconf.safer_iter
before capturing ##, matching @dolist's existing guard. Games with
safer_iter enabled will not leak ## into queued @switch bodies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:27:09 -06:00

3095 lines
77 KiB
C++

/*! \file predicates.cpp
* \brief Miscellaneous commands and functions.
*
* In theory, most of these functions could plausibly be called
* "predicates", either because they determine some boolean property
* of the input, or because they perform some action that makes them
* verb-like. In practice, this is a miscellany.
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
extern "C" {
#include "color_ops.h"
}
static inline const UTF8 *utf8_advance_predicate(const UTF8 *p)
{
if (nullptr == p || '\0' == *p)
{
return p;
}
size_t n = utf8_FirstByte[static_cast<unsigned char>(*p)];
if (n < 1 || n >= UTF8_CONTINUE)
{
return p + 1;
}
for (size_t i = 1; i < n; i++)
{
if ( '\0' == p[i]
|| UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(p[i])])
{
return p + 1;
}
}
return p + n;
}
#include "ganl_stub.h"
/* ---------------------------------------------------------------------------
* insert_first, remove_first: Insert or remove objects from lists.
*/
dbref insert_first(dbref head, dbref thing)
{
s_Next(thing, head);
return thing;
}
dbref remove_first(dbref head, dbref thing)
{
if (head == thing)
{
return Next(thing);
}
dbref prev;
DOLIST(prev, head)
{
if (Next(prev) == thing)
{
s_Next(prev, Next(thing));
return head;
}
}
return head;
}
/* ---------------------------------------------------------------------------
* reverse_list: Reverse the order of members in a list.
*/
dbref reverse_list(dbref list)
{
dbref newlist, rest;
newlist = NOTHING;
while (list != NOTHING)
{
rest = Next(list);
s_Next(list, newlist);
newlist = list;
list = rest;
}
return newlist;
}
/* ---------------------------------------------------------------------------
* member - indicate if thing is in list
*/
bool member(dbref thing, dbref list)
{
DOLIST(list, list)
{
if (list == thing)
{
return true;
}
}
return false;
}
bool could_doit(dbref player, dbref thing, int locknum)
{
if (thing == HOME)
{
return true;
}
// If nonplayer tries to get key, then no.
//
if ( !isPlayer(player)
&& Key(thing))
{
return false;
}
if (Pass_Locks(player))
{
return true;
}
dbref aowner;
int aflags;
UTF8 *key = atr_get("could_doit.134", thing, locknum, &aowner, &aflags);
bool doit = eval_boolexp_atr(player, thing, thing, key);
free_lbuf(key);
return doit;
}
bool can_see(dbref player, dbref thing, bool can_see_loc)
{
// Don't show if all the following apply: Sleeping players should not be
// seen. The thing is a disconnected player. The player is not a
// puppet.
//
if ( mudconf.dark_sleepers
&& isPlayer(thing)
&& !Connected(thing)
&& !Puppet(thing))
{
return false;
}
// You don't see yourself or exits.
//
if ( player == thing
|| isExit(thing))
{
return false;
}
// To be visible, light must come from either the location (can_see_loc)
// or the object itself (Light(thing)). This light is then blocked
// by the object itself being dark (it blocked its own light), by not
// passing the visibility lock, or by being in a different reality.
//
// The exception to the above is mudconf.see_own_dark which allows a
// myopic self-examination.
//
return ( ( ( can_see_loc
|| Light(thing))
&& !Dark(thing)
#ifdef REALITY_LVLS
&& IsReal(player, thing)
#endif // REALITY_LVLS
&& could_doit(player, thing, A_LVISIBLE))
|| ( mudconf.see_own_dark
&& MyopicExam(player, thing)));
}
static bool pay_quota(dbref who, int cost)
{
// If no cost, succeed
//
if (cost <= 0)
{
return true;
}
// determine quota
//
dbref aowner;
int aflags;
UTF8 *quota_str = atr_get("pay_quota.200", Owner(who), A_RQUOTA, &aowner, &aflags);
int quota = mux_atol(quota_str);
free_lbuf(quota_str);
// enough to build? Wizards always have enough.
//
quota -= cost;
if ( quota < 0
&& !Free_Quota(who)
&& !Free_Quota(Owner(who)))
{
return false;
}
// Dock the quota.
//
UTF8 buf[I32BUF_SIZE];
mux_ltoa(quota, buf);
atr_add_raw(Owner(who), A_RQUOTA, buf);
return true;
}
bool canpayfees(dbref player, dbref who, int pennies, int quota)
{
if ( !Wizard(who)
&& !Wizard(Owner(who))
&& !Free_Money(who)
&& !Free_Money(Owner(who))
&& (Pennies(Owner(who)) < pennies))
{
if (player == who)
{
notify(player, tprintf(T("Sorry, you don\xE2\x80\x99t have enough %s."),
mudconf.many_coins));
}
else
{
notify(player, tprintf(T("Sorry, that player doesn\xE2\x80\x99t have enough %s."),
mudconf.many_coins));
}
return false;
}
if (mudconf.quotas)
{
if (!pay_quota(who, quota))
{
if (player == who)
{
notify(player, T("Sorry, your building contract has run out."));
}
else
{
notify(player,
T("Sorry, that player\xE2\x80\x99s building contract has run out."));
}
return false;
}
}
payfor(who, pennies);
return true;
}
bool payfor(dbref who, int cost)
{
if ( Wizard(who)
|| Wizard(Owner(who))
|| Free_Money(who)
|| Free_Money(Owner(who)))
{
return true;
}
who = Owner(who);
int tmp;
if ((tmp = Pennies(who)) >= cost)
{
s_Pennies(who, tmp - cost);
return true;
}
return false;
}
void add_quota(dbref who, int payment)
{
dbref aowner;
int aflags;
UTF8 buf[I32BUF_SIZE];
UTF8 *quota = atr_get("add_quota.288", who, A_RQUOTA, &aowner, &aflags);
mux_ltoa(mux_atol(quota) + payment, buf);
free_lbuf(quota);
atr_add_raw(who, A_RQUOTA, buf);
}
void giveto(dbref who, int pennies)
{
if ( Wizard(who)
|| Wizard(Owner(who))
|| Free_Money(who)
|| Free_Money(Owner(who)))
{
return;
}
who = Owner(who);
s_Pennies(who, Pennies(who) + pennies);
}
// Every character in the name must be allowed by one of the character sets mentioned.
// If no character sets are mentions, everything is allowed.
//
bool IsRestricted(const UTF8 *pName, int charset)
{
if (0 == charset)
{
return false;
}
while ('\0' != pName[0])
{
bool bAllowed = false;
if ( (ALLOW_CHARSET_ASCII & charset)
&& (0x80 & pName[0]) == 0)
{
bAllowed = true;
}
else if ( (ALLOW_CHARSET_8859_1 & charset)
&& mux_is8859_1(pName))
{
bAllowed = true;
}
else if ( (ALLOW_CHARSET_8859_2 & charset)
&& mux_is8859_2(pName))
{
bAllowed = true;
}
else if ( (ALLOW_CHARSET_HANGUL & charset)
&& mux_ishangul(pName))
{
bAllowed = true;
}
else if ( (ALLOW_CHARSET_HIRAGANA & charset)
&& mux_ishiragana(pName))
{
bAllowed = true;
}
else if ( (ALLOW_CHARSET_KANJI & charset)
&& mux_iskanji(pName))
{
bAllowed = true;
}
else if ( (ALLOW_CHARSET_KATAKANA & charset)
&& mux_iskatakana(pName))
{
bAllowed = true;
}
if (!bAllowed)
{
return true;
}
pName = utf8_advance_predicate(pName);
}
return false;
}
// The following function validates that the object names (which will be
// used for things and rooms, but not for players or exits) and generates
// a canonical form of that name (with optimized ANSI).
//
UTF8 *MakeCanonicalObjectName(const UTF8 *pName, size_t *pnName, bool *pbValid, int charset)
{
static UTF8 Buf[MBUF_SIZE];
*pnName = 0;
*pbValid = false;
if (!pName)
{
return nullptr;
}
// Build up what the real name would be. If we pass all the
// checks, this is what we will return as a result.
//
mux_field fldLen = StripTabsAndTruncate(pName, Buf, MBUF_SIZE-1, MBUF_SIZE-1);
// Disallow pure ANSI names. There must be at least -something-
// visible.
//
if (0 == fldLen.m_column)
{
return nullptr;
}
// Get the stripped version (Visible parts without color info).
//
size_t nStripped;
const UTF8 *pStripped = strip_color(Buf, &nStripped);
// Do not allow LOOKUP_TOKEN, NUMBER_TOKEN, NOT_TOKEN, or SPACE
// as the first character, or SPACE as the last character
//
if ( reinterpret_cast<const UTF8 *>(strchr(reinterpret_cast<const char *>("*!#"), pStripped[0]))
|| mux_isspace(pStripped[0])
|| mux_isspace(pStripped[nStripped-1]))
{
return nullptr;
}
// Only printable characters besides ARG_DELIMITER, AND_TOKEN,
// and OR_TOKEN are allowed.
//
const UTF8 *p = pStripped;
while ('\0' != *p)
{
if (!mux_isobjectname(p))
{
return nullptr;
}
p = utf8_advance_predicate(p);
}
// Special names are specifically dis-allowed.
//
if ( (nStripped == 2 && memcmp("me", pStripped, 2) == 0)
|| (nStripped == 4 && ( memcmp("home", pStripped, 4) == 0
|| memcmp("here", pStripped, 4) == 0)))
{
return nullptr;
}
if (IsRestricted(pStripped, charset))
{
return nullptr;
}
// Reject names that would be silently truncated.
//
size_t nOrigStripped;
strip_color(pName, &nOrigStripped);
if (nOrigStripped > nStripped)
{
return nullptr;
}
*pnName = fldLen.m_byte;
*pbValid = true;
return Buf;
}
// The following function validates exit names.
//
UTF8 *MakeCanonicalExitName(const UTF8 *pName, size_t *pnName, bool *pbValid)
{
static UTF8 Buf[MBUF_SIZE];
*pnName = 0;
*pbValid = false;
if (!pName)
{
return nullptr;
}
mux_strncpy(Buf, pName, MBUF_SIZE - 1);
// Sanitize the input before processing.
//
string_token st(Buf, T(";"));
// Break the exitname down into semi-colon-separated segments. The first
// segment can contain color as it is used for showing the exit, but the
// remaining segments are stripped of color. A valid exitname requires
// at least one (display) segment.
//
UTF8 *ptr;
UTF8 clean_buf[MBUF_SIZE];
UTF8 *bp_clean = clean_buf;
bool bHaveDisplay = false;
for (ptr = st.parse(); ptr; ptr = st.parse())
{
UTF8 *pTrimmedSegment = nullptr;
if (bHaveDisplay)
{
// No color allowed in segments after the first one.
//
UTF8 *pNoColor = strip_color(ptr);
pTrimmedSegment = trim_spaces(pNoColor);
}
else
{
// Color allowed in first segment.
//
pTrimmedSegment = trim_spaces(ptr);
}
// Ignore segments which contained nothing but spaces.
//
if ('\0' != pTrimmedSegment[0])
{
bool valid = false;
size_t len = 0;
UTF8 *pValidSegment = MakeCanonicalObjectName(pTrimmedSegment, &len, &valid, mudconf.exit_name_charset);
if (valid)
{
if (bHaveDisplay)
{
safe_mb_chr_ascii(';', clean_buf, &bp_clean);
safe_mb_str(pValidSegment, clean_buf, &bp_clean);
}
else
{
safe_mb_str(pValidSegment, clean_buf, &bp_clean);
bHaveDisplay = true;
}
}
}
free_lbuf(pTrimmedSegment);
pTrimmedSegment = nullptr;
}
*bp_clean = '\0';
*pbValid = bHaveDisplay;
if (!bHaveDisplay)
{
*pnName = 0;
return Buf;
}
mux_strncpy(Buf, clean_buf, MBUF_SIZE - 1);
*pnName = mux_strlen(Buf);
return Buf;
}
// The following function validates the player name. ANSI is not
// allowed in player names. However, a player name must satisfy
// the requirements of a regular name as well.
//
bool ValidatePlayerName(const UTF8 *pName)
{
if (!pName)
{
return false;
}
size_t nName = strlen(reinterpret_cast<const char *>(pName));
// Verify that name is not empty, but not too long, either.
//
if ( nName <= 0
|| PLAYER_NAME_LIMIT <= nName)
{
return false;
}
// Do not allow LOOKUP_TOKEN, NUMBER_TOKEN, NOT_TOKEN, or SPACE
// as the first character, or SPACE as the last character
//
if ( reinterpret_cast<const UTF8 *>(strchr(reinterpret_cast<const char *>("*!#"), pName[0]))
|| mux_isspace(pName[0])
|| mux_isspace(pName[nName-1]))
{
return false;
}
// Only printable characters besides ARG_DELIMITER, AND_TOKEN,
// and OR_TOKEN are allowed.
//
if ( mudstate.bStandAlone
|| mudconf.name_spaces)
{
const UTF8 *p = pName;
while ('\0' != *p)
{
if ( !mux_isplayername(p)
&& ' ' != *p)
{
return false;
}
p = utf8_advance_predicate(p);
}
}
else
{
const UTF8 *p = pName;
while ('\0' != *p)
{
if (!mux_isplayername(p))
{
return false;
}
p = utf8_advance_predicate(p);
}
}
// Special names are specifically dis-allowed.
//
if ( (nName == 2 && memcmp("me", pName, 2) == 0)
|| (nName == 4 && ( memcmp("home", pName, 4) == 0
|| memcmp("here", pName, 4) == 0)))
{
return false;
}
if (IsRestricted(pName, mudconf.player_name_charset))
{
return false;
}
return true;
}
bool ok_password(const UTF8 *password, const UTF8 **pmsg)
{
*pmsg = nullptr;
if (*password == '\0')
{
*pmsg = T("Null passwords are not allowed.");
return false;
}
int num_upper = 0;
int num_special = 0;
int num_lower = 0;
const UTF8 *scan = password;
for ( ; *scan; scan = utf8_advance_predicate(scan))
{
if ( !mux_isprint(scan)
|| mux_isspace(*scan))
{
*pmsg = T("Illegal character in password.");
return false;
}
if (mux_isupper_ascii(*scan))
{
num_upper++;
}
else if (mux_islower_ascii(*scan))
{
num_lower++;
}
else if ( *scan != '\''
&& *scan != '-')
{
num_special++;
}
}
if ( !mudstate.bStandAlone
&& mudconf.safer_passwords)
{
if (num_upper < 1)
{
*pmsg = T("The password must contain at least one capital letter.");
return false;
}
if (num_lower < 1)
{
*pmsg = T("The password must contain at least one lowercase letter.");
return false;
}
if (num_special < 1)
{
*pmsg = T("The password must contain at least one number or a symbol other than the apostrophe or dash.");
return false;
}
}
return true;
}
/* ---------------------------------------------------------------------------
* handle_ears: Generate the 'grows ears' and 'loses ears' messages.
*/
void handle_ears(dbref thing, bool could_hear, bool can_hear)
{
if (could_hear != can_hear)
{
UTF8 *buf = alloc_lbuf("handle_ears");
UTF8 *bp = buf;
// Moniker returns PUA-encoded name.
//
const UTF8 *name = Moniker(thing);
if (isExit(thing))
{
// Truncate at first semicolon.
//
const UTF8 *semi = (const UTF8 *)strchr((const char *)name, ';');
if (semi)
{
safe_copy_buf(name, semi - name, buf, &bp);
}
else
{
safe_str(name, buf, &bp);
}
}
else
{
safe_str(name, buf, &bp);
}
const PRONOUN_SET *pg = get_pronoun_set(thing);
if (can_hear)
{
safe_tprintf_str(buf, &bp, T(" grow%s ears and can now hear."),
pg->plural ? "" : "s");
}
else
{
safe_tprintf_str(buf, &bp, T(" lose%s %s ears and become%s deaf."),
pg->plural ? "" : "s", pg->possessive,
pg->plural ? "" : "s");
}
*bp = '\0';
notify_check(thing, thing, buf, MSG_ME | MSG_NBR | MSG_LOC | MSG_INV);
free_lbuf(buf);
}
}
// For lack of better place the @switch code is here.
//
void do_switch
(
dbref executor, dbref caller, dbref enactor,
int eval, int key,
UTF8 *expr,
UTF8 *args[], int nargs,
const UTF8 *cargs[], int ncargs
)
{
if ( !expr
|| nargs <= 0)
{
return;
}
bool bMatchOne;
switch (key & SWITCH_MASK)
{
case SWITCH_DEFAULT:
if (mudconf.switch_df_all)
{
bMatchOne = false;
}
else
{
bMatchOne = true;
}
break;
case SWITCH_ANY:
bMatchOne = false;
break;
case SWITCH_ONE:
default:
bMatchOne = true;
break;
}
// Now try a wild card match of buff with stuff in coms.
//
bool bAny = false;
int a;
UTF8 *buff, *bp;
buff = bp = alloc_lbuf("do_switch");
CLinearTimeAbsolute lta;
for ( a = 0;
( !bMatchOne
|| !bAny)
&& a < nargs - 1
&& args[a]
&& args[a + 1];
a += 2)
{
bp = buff;
mux_exec(args[a], LBUF_SIZE-1, buff, &bp, executor, caller, enactor, eval|EV_FCHECK|EV_EVAL|EV_TOP,
cargs, ncargs);
*bp = '\0';
if (wild_match(buff, expr))
{
const UTF8 *save_switch = mudstate.switch_token;
mudstate.switch_token = expr;
if (key & SWITCH_NOW)
{
process_command(executor, caller, enactor, eval, false, args[a+1], cargs, ncargs);
}
else
{
{
// Preserve enclosing iter context (## from @dolist)
// so it survives into the queued @switch body.
// Respect safer_iter — same guard as @dolist uses.
//
const UTF8 *iter_tok = nullptr;
int iter_num = 0;
if (!mudconf.safer_iter)
{
int iLoop = mudstate.in_loop - 1;
if ( 0 <= iLoop
&& iLoop < MAX_ITEXT
&& mudstate.itext[iLoop])
{
iter_tok = mudstate.itext[iLoop];
iter_num = mudstate.inum[iLoop];
}
}
wait_que(executor, caller, enactor, eval, false, lta, NOTHING, 0,
args[a+1],
ncargs, cargs,
mudstate.global_regs,
nullptr, // named_sargs
iter_tok, iter_num, // iter context
expr); // switch_token
}
}
mudstate.switch_token = save_switch;
bAny = true;
}
}
free_lbuf(buff);
if ( a < nargs
&& !bAny
&& args[a])
{
const UTF8 *save_switch = mudstate.switch_token;
mudstate.switch_token = expr;
if (key & SWITCH_NOW)
{
process_command(executor, caller, enactor, eval, false, args[a], cargs, ncargs);
}
else
{
{
const UTF8 *iter_tok = nullptr;
int iter_num = 0;
if (!mudconf.safer_iter)
{
int iLoop = mudstate.in_loop - 1;
if ( 0 <= iLoop
&& iLoop < MAX_ITEXT
&& mudstate.itext[iLoop])
{
iter_tok = mudstate.itext[iLoop];
iter_num = mudstate.inum[iLoop];
}
}
wait_que(executor, caller, enactor, eval, false, lta, NOTHING, 0,
args[a],
ncargs, cargs,
mudstate.global_regs,
nullptr, // named_sargs
iter_tok, iter_num, // iter context
expr); // switch_token
}
}
mudstate.switch_token = save_switch;
}
if (key & SWITCH_NOTIFY)
{
UTF8 *tbuf = alloc_lbuf("switch.notify_cmd");
mux_strncpy(tbuf, T("@notify/quiet me"), LBUF_SIZE-1);
wait_que(executor, caller, enactor, eval, false, lta, NOTHING, A_SEMAPHORE,
tbuf,
ncargs, cargs,
mudstate.global_regs);
free_lbuf(tbuf);
}
}
// Also for lack of better place the @ifelse code is here.
// Idea for @ifelse from ChaoticMUX.
//
void do_if
(
dbref player, dbref caller, dbref enactor,
int eval, int key,
UTF8 *expr,
UTF8 *args[], int nargs,
const UTF8 *cargs[], int ncargs
)
{
UNUSED_PARAMETER(key);
if ( !expr
|| nargs <= 0)
{
return;
}
UTF8 *buff, *bp;
CLinearTimeAbsolute lta;
buff = bp = alloc_lbuf("do_if");
mux_exec(expr, LBUF_SIZE-1, buff, &bp, player, caller, enactor, eval|EV_FCHECK|EV_EVAL|EV_TOP,
cargs, ncargs);
*bp = '\0';
int a = !xlate(buff);
free_lbuf(buff);
if (a < nargs)
{
wait_que(player, caller, enactor, eval, false, lta, NOTHING, 0,
args[a],
ncargs, cargs,
mudstate.global_regs);
}
}
void do_addcommand
(
dbref player,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *command,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
bool bNoEval = (key & ADDCMD_NOEVAL) != 0;
// Validate command name.
//
static UTF8 pName[LBUF_SIZE];
if (1 <= nargs)
{
// Strip color, then strip \r\n\t and space.
//
{
size_t nRaw = strlen(reinterpret_cast<const char *>(name));
unsigned char plain[LBUF_SIZE];
size_t nPlain = co_strip_color(plain,
reinterpret_cast<const unsigned char *>(name), nRaw);
size_t j = 0;
for (size_t i = 0; i < nPlain; i++)
{
if (plain[i] != '\r' && plain[i] != '\n'
&& plain[i] != '\t' && plain[i] != ' ')
{
pName[j++] = plain[i];
}
}
pName[j] = '\0';
}
{
size_t nPlain = strlen(reinterpret_cast<const char *>(pName));
UTF8 tmp[LBUF_SIZE];
nPlain = co_tolower(tmp, pName, nPlain);
memcpy(pName, tmp, nPlain + 1);
}
}
if ( 0 == nargs
|| '\0' == pName[0]
|| ( pName[0] == '_'
&& pName[1] == '_'))
{
notify(player, T("That is not a valid command name."));
return;
}
// Validate object/attribute.
//
dbref thing;
ATTR *pattr;
if ( !parse_attrib(player, command, &thing, &pattr)
|| !pattr)
{
notify(player, T("No such attribute."));
return;
}
if (!See_attr(player, thing, pattr))
{
notify(player, NOPERM_MESSAGE);
return;
}
size_t nName = strlen(reinterpret_cast<char *>(pName));
auto it_old = mudstate.command_htab.find(std::vector<UTF8>(pName, pName + nName));
CMDENT *old = (it_old != mudstate.command_htab.end()) ? static_cast<CMDENT*>(it_old->second) : nullptr;
CMDENT *cmd;
if ( old
&& (old->callseq & CS_ADDED))
{
// Don't allow the same (thing,atr) in the list.
//
for (auto &entry : *old->addent)
{
if ( entry.thing == thing
&& entry.atr == pattr->number)
{
notify(player, tprintf(T("%s already added."), pName));
return;
}
}
// Otherwise, add another (thing,atr) to the list.
//
old->addent->push_back(ADDENT{});
ADDENT &add = old->addent->back();
add.thing = thing;
add.atr = pattr->number;
add.name = reinterpret_cast<const char *>(pName);
if (bNoEval)
{
old->callseq |= CS_NOINTERP;
}
}
else
{
if (old)
{
// Delete the old built-in (which will later be added back as
// __name).
//
mudstate.command_htab.erase(std::vector<UTF8>(pName, pName + nName));
}
cmd = nullptr;
try
{
cmd = new CMDENT;
}
catch (...)
{
; // Nothing.
}
ISOUTOFMEMORY(cmd);
cmd->cmdname = StringClone(pName);
cmd->switches = nullptr;
cmd->perms = 0;
cmd->extra = 0;
if ( old
&& (old->callseq & CS_LEADIN))
{
cmd->callseq = CS_ADDED|CS_ONE_ARG|CS_LEADIN;
}
else
{
cmd->callseq = CS_ADDED|CS_ONE_ARG;
}
if (bNoEval)
{
cmd->callseq |= CS_NOINTERP;
}
cmd->flags = CEF_ALLOC;
cmd->addent = new std::vector<ADDENT>();
cmd->addent->push_back(ADDENT{});
ADDENT &add = cmd->addent->back();
add.thing = thing;
add.atr = pattr->number;
add.name = reinterpret_cast<const char *>(pName);
mudstate.command_htab.emplace(std::vector<UTF8>(pName, pName + nName), cmd);
if ( old
&& strcmp(reinterpret_cast<char *>(pName), reinterpret_cast<char *>(old->cmdname)) == 0)
{
// We are @addcommand'ing over a built-in command by its
// unaliased name, therefore, we want to re-target all the
// aliases.
//
UTF8 *p = tprintf(T("__%s"), pName);
size_t nP = strlen(reinterpret_cast<char *>(p));
mudstate.command_htab.erase(std::vector<UTF8>(p, p + nP));
for (auto &[k, v] : mudstate.command_htab) { if (v == old) v = cmd; }
mudstate.command_htab.emplace(std::vector<UTF8>(p, p + nP), old);
}
}
// We reset the one letter commands here so you can overload them.
//
cache_prefix_cmds();
notify(player, tprintf(T("Command %s added."), pName));
}
void do_listcommands(dbref player, dbref caller, dbref enactor, int eval,
int key, UTF8 *name, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
CMDENT *old;
bool didit = false;
// Let's make this case insensitive...
//
size_t nCased;
UTF8 *pCased = mux_strlwr(name, nCased);
if (*pCased)
{
{
auto it = mudstate.command_htab.find(std::vector<UTF8>(pCased, pCased + nCased));
old = (it != mudstate.command_htab.end()) ? static_cast<CMDENT*>(it->second) : nullptr;
}
if ( old
&& (old->callseq & CS_ADDED))
{
// If it's already found in the hash table, and it's being added
// using the same object and attribute...
//
for (auto &entry : *old->addent)
{
ATTR *ap = reinterpret_cast<ATTR *>(atr_num(entry.atr));
const UTF8 *pName = T("(WARNING: Bad Attribute Number)");
if (ap)
{
pName = ap->name;
}
notify(player, tprintf(T("%s: #%d/%s"), reinterpret_cast<const UTF8 *>(entry.name.c_str()), entry.thing, pName));
}
}
else
{
notify(player, tprintf(T("%s not found in command table."), pCased));
}
return;
}
else
{
for (auto &[key, val] : mudstate.command_htab)
{
old = static_cast<CMDENT*>(val);
if (old->callseq & CS_ADDED)
{
const UTF8 *pKeyName = key.data();
int nKeyName = static_cast<int>(key.size());
for (auto &entry : *old->addent)
{
if ( static_cast<size_t>(nKeyName) != entry.name.size()
|| memcmp(pKeyName, entry.name.c_str(), nKeyName) != 0)
{
continue;
}
ATTR *ap = reinterpret_cast<ATTR *>(atr_num(entry.atr));
const UTF8 *pName = T("(WARNING: Bad Attribute Number)");
if (ap)
{
pName = ap->name;
}
notify(player, tprintf(T("%s: #%d/%s"), reinterpret_cast<const UTF8 *>(entry.name.c_str()),
entry.thing, pName));
didit = true;
}
}
}
}
if (!didit)
{
notify(player, T("No added commands found in command table."));
}
}
void do_delcommand
(
dbref player,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *command,
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 (!*name)
{
notify(player, T("Sorry."));
return;
}
dbref thing = NOTHING;
int atr = NOTHING;
ATTR *pattr;
if (*command)
{
if ( !parse_attrib(player, command, &thing, &pattr)
|| !pattr)
{
notify(player, T("No such attribute."));
return;
}
if (!See_attr(player, thing, pattr))
{
notify(player, NOPERM_MESSAGE);
return;
}
atr = pattr->number;
}
// Let's make this case insensitive...
//
size_t nCased;
UTF8 *pCased = mux_strlwr(name, nCased);
CMDENT *old, *cmd;
{
auto it = mudstate.command_htab.find(std::vector<UTF8>(pCased, pCased + nCased));
old = (it != mudstate.command_htab.end()) ? static_cast<CMDENT*>(it->second) : nullptr;
}
if ( old
&& (old->callseq & CS_ADDED))
{
UTF8 *p__Name = tprintf(T("__%s"), pCased);
size_t n__Name = strlen(reinterpret_cast<char *>(p__Name));
if (command[0] == '\0')
{
// Delete all @addcommand'ed associations with the given name.
//
delete old->addent;
old->addent = nullptr;
mudstate.command_htab.erase(std::vector<UTF8>(pCased, pCased + nCased));
{
auto it_cmd = mudstate.command_htab.find(std::vector<UTF8>(p__Name, p__Name + n__Name));
cmd = (it_cmd != mudstate.command_htab.end()) ? static_cast<CMDENT*>(it_cmd->second) : nullptr;
}
if (cmd)
{
size_t nCmdName = strlen(reinterpret_cast<char *>(cmd->cmdname));
mudstate.command_htab.emplace(std::vector<UTF8>(cmd->cmdname, cmd->cmdname + nCmdName), cmd);
if (strcmp(reinterpret_cast<char *>(pCased), reinterpret_cast<char *>(cmd->cmdname)) != 0)
{
mudstate.command_htab.emplace(std::vector<UTF8>(pCased, pCased + nCased), cmd);
}
mudstate.command_htab.erase(std::vector<UTF8>(p__Name, p__Name + n__Name));
mudstate.command_htab.emplace(std::vector<UTF8>(p__Name, p__Name + n__Name), cmd);
for (auto &[k, v] : mudstate.command_htab) { if (v == old) v = cmd; }
}
else
{
// TODO: Delete everything related to 'old'.
//
}
MEMFREE(old->cmdname);
old->cmdname = nullptr;
MEMFREE(old);
old = nullptr;
cache_prefix_cmds();
notify(player, T("Done."));
}
else
{
// Remove only the (name,thing,atr) association.
//
for (auto it = old->addent->begin(); it != old->addent->end(); ++it)
{
if ( it->thing == thing
&& it->atr == atr)
{
old->addent->erase(it);
if (old->addent->empty())
{
delete old->addent;
old->addent = nullptr;
mudstate.command_htab.erase(std::vector<UTF8>(pCased, pCased + nCased));
{
auto it_cmd2 = mudstate.command_htab.find(std::vector<UTF8>(p__Name, p__Name + n__Name));
cmd = (it_cmd2 != mudstate.command_htab.end()) ? static_cast<CMDENT*>(it_cmd2->second) : nullptr;
}
if (cmd)
{
size_t nCmdName = strlen(reinterpret_cast<char *>(cmd->cmdname));
mudstate.command_htab.emplace(std::vector<UTF8>(cmd->cmdname, cmd->cmdname + nCmdName),
cmd);
if (strcmp(reinterpret_cast<char *>(pCased), reinterpret_cast<char *>(cmd->cmdname)) != 0)
{
mudstate.command_htab.emplace(std::vector<UTF8>(pCased, pCased + nCased),
cmd);
}
mudstate.command_htab.erase(std::vector<UTF8>(p__Name, p__Name + n__Name));
mudstate.command_htab.emplace(std::vector<UTF8>(p__Name, p__Name + n__Name),
cmd);
for (auto &[k, v] : mudstate.command_htab) { if (v == old) v = cmd; }
}
MEMFREE(old->cmdname);
old->cmdname = nullptr;
MEMFREE(old);
old = nullptr;
}
cache_prefix_cmds();
notify(player, T("Done."));
return;
}
}
notify(player, T("Command not found in command table."));
}
}
else
{
notify(player, T("Command not found in command table."));
}
}
void do_quitprog(dbref player, dbref caller, dbref enactor, int eval, int key, UTF8 *name, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref doer;
if (*name)
{
doer = match_thing(player, name);
}
else
{
doer = player;
}
if ( !( Prog(player)
|| Prog(Owner(player)))
&& player != doer)
{
notify(player, NOPERM_MESSAGE);
return;
}
if ( !Good_obj(doer)
|| !isPlayer(doer))
{
notify(player, T("That is not a player."));
return;
}
if (!Connected(doer))
{
notify(player, T("That player is not connected."));
return;
}
if (!player_has_program(doer))
{
notify(player, T("Player is not in an @program."));
return;
}
program_data* program = detach_player_program(doer);
if (program)
{
for (auto& wait_reg : program->wait_regs)
{
if (wait_reg)
{
RegRelease(wait_reg);
wait_reg = nullptr;
}
}
NamedRegsClear(program->named_wait_regs);
MEMFREE(program);
program = nullptr;
}
atr_clr(doer, A_PROGCMD);
notify(player, T("@program cleared."));
notify(doer, T("Your @program has been terminated."));
}
void do_prog
(
dbref player,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *command,
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 ( !name
|| !*name)
{
notify(player, T("No players specified."));
return;
}
const dbref doer = match_thing(player, name);
if ( !( Prog(player)
|| Prog(Owner(player)))
&& player != doer)
{
notify(player, NOPERM_MESSAGE);
return;
}
if ( !Good_obj(doer)
|| !isPlayer(doer))
{
notify(player, T("That is not a player."));
return;
}
if (!Connected(doer))
{
notify(player, T("That player is not connected."));
return;
}
// Check to see if the enactor already has an @prog input pending.
//
if (player_has_program(doer))
{
notify(player, T("Input already pending."));
return;
}
UTF8 *msg = command;
const UTF8 *attrib = parse_to(&msg, ':', 1);
if (msg && *msg)
{
notify(doer, msg);
}
dbref thing;
ATTR *ap;
if (!parse_attrib(player, attrib, &thing, &ap))
{
notify(player, NOMATCH_MESSAGE);
return;
}
if (ap)
{
dbref aowner;
int aflags;
int lev;
dbref parent;
UTF8 *pBuffer = nullptr;
bool bFound = false;
ITER_PARENTS(thing, parent, lev)
{
pBuffer = atr_get("do_prog.1405", parent, ap->number, &aowner, &aflags);
if (pBuffer[0])
{
bFound = true;
break;
}
free_lbuf(pBuffer);
}
if (bFound)
{
if ( ( God(player)
|| !God(thing))
&& See_attr(player, thing, ap))
{
atr_add_raw(doer, A_PROGCMD, pBuffer);
}
else
{
notify(player, NOPERM_MESSAGE);
free_lbuf(pBuffer);
return;
}
free_lbuf(pBuffer);
}
else
{
notify(player, T("Attribute not present on object."));
return;
}
}
else
{
notify(player, T("No such attribute."));
return;
}
const auto program = static_cast<program_data*>(MEMALLOC(sizeof(program_data)));
ISOUTOFMEMORY(program);
program->wait_enactor = player;
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
{
program->wait_regs[i] = mudstate.global_regs[i];
if (mudstate.global_regs[i])
{
RegAddRef(mudstate.global_regs[i]);
}
}
program->named_wait_regs = NamedRegsCopy(mudstate.named_regs);
// Now, start waiting.
//
set_player_program(doer, program);
send_prog_prompt(doer);
}
/* ---------------------------------------------------------------------------
* do_restart: Restarts the game.
*/
void do_restart(dbref executor, dbref caller, dbref enactor, int eval, int key)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
if (!Can_SiteAdmin(executor))
{
notify(executor, NOPERM_MESSAGE);
return;
}
bool bDenied = false;
#if defined(HAVE_WORKING_FORK)
if (mudstate.dumping)
{
notify(executor, T("Dumping. Please try again later."));
bDenied = true;
}
#endif // HAVE_WORKING_FORK
if (!mudstate.bCanRestart)
{
notify(executor, T("Server just started. Please try again in a few seconds."));
bDenied = true;
}
if (bDenied)
{
STARTLOG(LOG_ALWAYS, "WIZ", "RSTRT");
log_text(T("Restart requested but not executed by "));
log_name(executor);
ENDLOG;
return;
}
#ifdef UNIX_SSL
raw_broadcast(0, T("GAME: Restart by %s, please wait. (All SSL connections will be dropped.)"), Moniker(Owner(executor)));
#else
raw_broadcast(0, T("GAME: Restart by %s, please wait."), Moniker(Owner(executor)));
#endif
STARTLOG(LOG_ALWAYS, "WIZ", "RSTRT");
log_text(T("Restart by "));
log_name(executor);
ENDLOG;
g_GanlAdapter.prepare_for_restart();
local_presync_database();
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
while (nullptr != p)
{
p->pSink->presync_database();
p = p->pNext;
}
#if defined(STUB_SLAVE)
final_stubslave();
#endif // STUB_SLAVE
final_modules();
pcache_sync();
dump_database_internal(DUMP_I_RESTART);
SYNC;
CLOSE;
#if defined(WINDOWS_PROCESSES)
exit(12345678);
#elif defined(UNIX_PROCESSES)
#if defined(HAVE_WORKING_FORK)
dump_restart_db();
#endif // HAVE_WORKING_FORK
Log.StopLogging();
#ifdef GAME_DOOFERMUX
execl("bin/netmux", mudconf.mud_name, "-c", mudconf.config_file, "-p",
mudconf.pid_file, "-e", mudconf.log_dir, static_cast<char *>(nullptr));
#else
execl("bin/netmux", "netmux", "-c", mudconf.config_file, "-p",
mudconf.pid_file, "-e", mudconf.log_dir, static_cast<char *>(nullptr));
#endif // GAME_DOOFERMUX
mux_assert(false);
#endif // UNIX_PROCESSES
}
/* ---------------------------------------------------------------------------
* do_comment: Implement the @@ (comment) command. Very cpu-intensive :-)
*/
void do_comment(dbref executor, dbref caller, dbref enactor, int eval, int key)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
}
void do_eval(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *arg1, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(executor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(arg1);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
}
static dbref promote_dflt(dbref old, dbref new0)
{
if ( old == NOPERM
|| new0 == NOPERM)
{
return NOPERM;
}
if ( old == AMBIGUOUS
|| new0 == AMBIGUOUS)
{
return AMBIGUOUS;
}
return NOTHING;
}
dbref match_possessed(dbref player, dbref thing, UTF8 *target, dbref dflt, bool check_enter)
{
// First, check normally.
//
if (Good_obj(dflt))
{
return dflt;
}
// Didn't find it directly. Recursively do a contents check.
//
dbref result, result1;
UTF8 *buff, *place, *s1, *d1, *temp;
UTF8 *start = target;
while (*target)
{
// Fail if no ' characters.
//
place = target;
target = reinterpret_cast<UTF8 *>(strchr(reinterpret_cast<char *>(place), '\''));
if ( target == nullptr
|| !*target)
{
return dflt;
}
// If string started with a ', skip past it
//
if (place == target)
{
target++;
continue;
}
// If next character is not an s or a space, skip past
//
temp = target++;
if (!*target)
{
return dflt;
}
if ( *target != 's'
&& *target != 'S'
&& *target != ' ')
{
continue;
}
// If character was not a space make sure the following character is
// a space.
//
if (*target != ' ')
{
target++;
if (!*target)
{
return dflt;
}
if (*target != ' ')
{
continue;
}
}
// Copy the container name to a new buffer so we can terminate it.
//
buff = alloc_lbuf("is_posess");
for (s1 = start, d1 = buff; *s1 && (s1 < temp); *d1++ = (*s1++))
{
; // Nothing.
}
*d1 = '\0';
// Look for the container here and in our inventory. Skip past if we
// can't find it.
//
init_match(thing, buff, NOTYPE);
if (player == thing)
{
match_neighbor();
match_possession();
}
else
{
match_possession();
}
result1 = match_result();
free_lbuf(buff);
if (!Good_obj(result1))
{
dflt = promote_dflt(dflt, result1);
continue;
}
// If we don't control it and it is either dark or opaque, skip past.
//
bool control = Controls(player, result1);
if ( ( Dark(result1)
|| Opaque(result1))
&& !control)
{
dflt = promote_dflt(dflt, NOTHING);
continue;
}
// Validate object has the ENTER bit set, if requested.
//
if ( check_enter
&& !Enter_ok(result1)
&& !control)
{
dflt = promote_dflt(dflt, NOPERM);
continue;
}
// Look for the object in the container.
//
init_match(result1, target, NOTYPE);
match_possession();
result = match_result();
result = match_possessed(player, result1, target, result, check_enter);
if (Good_obj(result))
{
return result;
}
dflt = promote_dflt(dflt, result);
}
return dflt;
}
/* ---------------------------------------------------------------------------
* parse_range: break up <what>,<low>,<high> syntax
*/
void parse_range(UTF8 **name, dbref *low_bound, dbref *high_bound)
{
UTF8 *buff1 = *name;
if (buff1 && *buff1)
{
*name = parse_to(&buff1, ',', EV_STRIP_TS);
}
if (buff1 && *buff1)
{
UTF8 *buff2 = parse_to(&buff1, ',', EV_STRIP_TS);
if (buff1 && *buff1)
{
while (mux_isspace(*buff1))
{
buff1++;
}
if (*buff1 == NUMBER_TOKEN)
{
buff1++;
}
*high_bound = mux_atol(buff1);
if (*high_bound >= mudstate.db_top)
{
*high_bound = mudstate.db_top - 1;
}
}
else
{
*high_bound = mudstate.db_top - 1;
}
while (mux_isspace(*buff2))
{
buff2++;
}
if (*buff2 == NUMBER_TOKEN)
{
buff2++;
}
*low_bound = mux_atol(buff2);
if (*low_bound < 0)
{
*low_bound = 0;
}
}
else
{
*low_bound = 0;
*high_bound = mudstate.db_top - 1;
}
}
bool parse_thing_slash(dbref player, const UTF8 *thing, const UTF8 **after, dbref *it)
{
// Get name up to '/'.
//
size_t i = 0;
while ( thing[i] != '\0'
&& thing[i] != '/')
{
i++;
}
// If no '/' in string, return failure.
//
if (thing[i] == '\0')
{
*after = nullptr;
*it = NOTHING;
return false;
}
*after = thing + i + 1;
// Look for the object.
//
init_match(player, thing, i, NOTYPE);
match_everything(MAT_EXIT_PARENTS);
*it = match_result();
// Return status of search.
//
return Good_obj(*it);
}
bool get_obj_and_lock(dbref player, const UTF8 *what, dbref *it, ATTR **attr, UTF8 *errmsg, UTF8 **bufc)
{
// Get name up to '/'.
//
size_t i = 0;
while ( what[i] != '\0'
&& what[i] != '/')
{
i++;
}
*it = match_thing_quiet(player, what, i);
if (!Good_obj(*it))
{
safe_match_result(*it, errmsg, bufc);
return false;
}
int anum;
if (what[i] == '/')
{
// <obj>/<lock> syntax, use the named lock.
//
if (!search_nametab(player, lock_sw, what + i + 1, &anum))
{
safe_str(T("#-1 LOCK NOT FOUND"), errmsg, bufc);
return false;
}
}
else
{
// Not <obj>/<lock>, do a normal get of the default lock.
//
anum = A_LOCK;
}
// Get the attribute definition, fail if not found.
//
*attr = atr_num(anum);
if (nullptr == *attr)
{
safe_str(T("#-1 LOCK NOT FOUND"), errmsg, bufc);
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// bCanReadAttr, bCanSetAttr: Verify permission to affect attributes.
// ---------------------------------------------------------------------------
bool bCanReadAttr(dbref executor, dbref target, ATTR *tattr, bool bCheckParent)
{
if (!tattr)
{
return false;
}
dbref aowner;
int aflags;
if ( !mudstate.bStandAlone
&& bCheckParent)
{
atr_pget_info(target, tattr->number, &aowner, &aflags);
}
else
{
atr_get_info(target, tattr->number, &aowner, &aflags);
}
int test_flags = tattr->flags;
if (mudstate.attrperm_list)
{
ATTRPERM *perm_walk = mudstate.attrperm_list;
while (nullptr != perm_walk)
{
if (quick_wild(perm_walk->wildcard, tattr->name))
{
test_flags |= perm_walk->flags;
}
perm_walk = perm_walk->next;
}
}
int mAllow = AF_VISUAL;
if ( (test_flags & mAllow)
|| (aflags & mAllow))
{
if ( mudstate.bStandAlone
|| tattr->number != A_DESC
|| mudconf.read_rem_desc
|| nearby(executor, target))
{
return true;
}
}
int mDeny = 0;
if (WizRoy(executor))
{
if (God(executor))
{
mDeny = AF_INTERNAL;
}
else
{
mDeny = AF_INTERNAL|AF_DARK;
}
}
else if ( Owner(executor) == aowner
|| Examinable(executor, target))
{
mDeny = AF_INTERNAL|AF_DARK|AF_MDARK;
}
if (mDeny)
{
if ( (test_flags & mDeny)
|| (aflags & mDeny))
{
return false;
}
else
{
return true;
}
}
return false;
}
bool bCanSetAttr(dbref executor, dbref target, ATTR *tattr)
{
if (!tattr)
{
return false;
}
if (NoModify(target) && !WizRoy(executor))
{
return false;
}
int mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST;
if (!God(executor))
{
if (God(target))
{
return false;
}
if (Wizard(executor))
{
mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST|AF_LOCK|AF_GOD;
}
else if (Controls(executor, target))
{
mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST|AF_LOCK|AF_WIZARD|AF_GOD;
}
else
{
return false;
}
}
dbref aowner;
int aflags;
bool info = atr_get_info(target, tattr->number, &aowner, &aflags);
int test_flags = tattr->flags;
if (mudstate.attrperm_list)
{
ATTRPERM *perm_walk = mudstate.attrperm_list;
while (nullptr != perm_walk)
{
if (quick_wild(perm_walk->wildcard,tattr->name))
{
test_flags |= perm_walk->flags;
}
perm_walk = perm_walk->next;
}
}
if ( (test_flags & mDeny)
|| (info && (aflags & mDeny)))
{
return false;
}
else
{
return true;
}
}
bool bCanLockAttr(dbref executor, dbref target, ATTR *tattr)
{
if (!tattr)
{
return false;
}
if (NoModify(target) && !WizRoy(executor))
{
return false;
}
int mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST;
if (!God(executor))
{
if (God(target))
{
return false;
}
if (Wizard(executor))
{
mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST|AF_GOD;
}
else
{
mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST|AF_WIZARD|AF_GOD;
}
}
dbref aowner;
int aflags;
bool info = atr_get_info(target, tattr->number, &aowner, &aflags);
int test_flags = tattr->flags;
if (mudstate.attrperm_list)
{
ATTRPERM *perm_walk = mudstate.attrperm_list;
while (nullptr != perm_walk)
{
if (quick_wild(perm_walk->wildcard,tattr->name))
{
test_flags |= perm_walk->flags;
}
perm_walk = perm_walk->next;
}
}
if ( (test_flags & mDeny)
|| !info
|| (aflags & mDeny))
{
return false;
}
else if ( Wizard(executor)
|| Owner(executor) == aowner)
{
return true;
}
else
{
return false;
}
}
/* ---------------------------------------------------------------------------
* where_is: Returns place where obj is linked into a list.
* ie. location for players/things, source for exits, NOTHING for rooms.
*/
dbref where_is(dbref what)
{
if (!Good_obj(what))
{
return NOTHING;
}
dbref loc;
switch (Typeof(what))
{
case TYPE_PLAYER:
case TYPE_THING:
loc = Location(what);
break;
case TYPE_EXIT:
loc = Exits(what);
break;
default:
loc = NOTHING;
break;
}
return loc;
}
/* ---------------------------------------------------------------------------
* where_room: Return room containing player, or NOTHING if no room or
* recursion exceeded. If player is a room, returns itself.
*/
dbref where_room(dbref what)
{
for (int count = mudconf.ntfy_nest_lim; count > 0; count--)
{
if (!Good_obj(what))
{
break;
}
if (isRoom(what))
{
return what;
}
if (!Has_location(what))
{
break;
}
what = Location(what);
}
return NOTHING;
}
bool locatable(dbref player, dbref it, dbref enactor)
{
// No sense in trying to locate a bad object.
//
if (!Good_obj(it))
{
return false;
}
dbref loc_it = where_is(it);
// Succeed if we can examine the target, if we are the target, if we can
// examine the location, if the player is a wizard, or if the target
// caused the lookup.
//
if ( Examinable(player, it)
|| Find_Unfindable(player)
|| loc_it == player
|| ( loc_it != NOTHING
&& ( Examinable(player, loc_it)
|| loc_it == where_is(player))
&& ( !Hidden(it)
|| See_Hidden(player)))
|| Wizard(player)
|| it == enactor)
{
return true;
}
dbref room_it = where_room(it);
bool findable_room;
if (Good_obj(room_it))
{
findable_room = Findable(room_it);
}
else
{
findable_room = true;
}
// Succeed if we control the containing room or if the target is findable
// and the containing room is not unfindable.
//
if ( ( room_it != NOTHING
&& Examinable(player, room_it))
&& ( !Hidden(it)
|| See_Hidden(player))
|| Find_Unfindable(player)
|| ( Findable(it)
&& findable_room)
&& ( !Hidden(it)
|| See_Hidden(player)))
{
return true;
}
// We can't do it.
//
return false;
}
/* ---------------------------------------------------------------------------
* nearby: Check if thing is nearby player (in inventory, in same room, or
* IS the room.
*/
bool nearby(dbref player, dbref thing)
{
if ( !Good_obj(player)
|| !Good_obj(thing))
{
return false;
}
if ( Can_Hide(thing)
&& Hidden(thing)
&& !See_Hidden(player))
{
return false;
}
dbref thing_loc = where_is(thing);
if (thing_loc == player)
{
return true;
}
dbref player_loc = where_is(player);
if ( thing_loc == player_loc
|| thing == player_loc)
{
return true;
}
return false;
}
/*
* ---------------------------------------------------------------------------
* * exit_visible, exit_displayable: Is exit visible?
*/
bool exit_visible(dbref exit, dbref player, int key)
{
#ifdef WOD_REALMS
if (!mudstate.bStandAlone)
{
int iRealmDirective = DoThingToThingVisibility(player, exit,
ACTION_IS_STATIONARY);
if (REALM_DO_HIDDEN_FROM_YOU == iRealmDirective)
{
return false;
}
}
#endif // WOD_REALMS
#ifdef REALITY_LVLS
if (!mudstate.bStandAlone)
{
if (!IsReal(player, exit))
{
return false;
}
}
#endif // REALITY_LVLS
// Exam exit's location
//
if ( (key & VE_LOC_XAM)
|| Examinable(player, exit)
|| Light(exit))
{
return true;
}
// Dark location or base
//
if ( (key & (VE_LOC_DARK | VE_BASE_DARK))
|| Dark(exit))
{
return false;
}
// VisibleLock
//
if (!could_doit(player, exit, A_LVISIBLE))
{
return false;
}
// Default
//
return true;
}
// Exit visible to look
//
bool exit_displayable(dbref exit, dbref player, int key)
{
// Dark exit
//
if (Dark(exit))
{
return false;
}
#ifdef WOD_REALMS
if (!mudstate.bStandAlone)
{
int iRealmDirective = DoThingToThingVisibility(player, exit,
ACTION_IS_STATIONARY);
if (REALM_DO_HIDDEN_FROM_YOU == iRealmDirective)
{
return false;
}
}
#endif // WOD_REALMS
#ifdef REALITY_LVLS
if (!mudstate.bStandAlone)
{
if (!IsReal(player, exit))
{
return false;
}
}
#endif // REALITY_LVLS
// Light exit
//
if (Light(exit))
{
return true;
}
// Dark location or base.
//
if (key & (VE_LOC_DARK | VE_BASE_DARK))
{
return false;
}
// VisibleLock
//
if (!could_doit(player, exit, A_LVISIBLE))
{
return false;
}
// Default
//
return true;
}
/* ---------------------------------------------------------------------------
* did_it: Have player do something to/with thing
*/
void did_it(dbref player, dbref thing, int what, const UTF8 *def, int owhat,
const UTF8 *odef, int awhat, int ctrl_flags,
const UTF8 *args[], int nargs)
{
if (alarm_clock.alarmed)
{
return;
}
UTF8 *d, *buff, *act, *charges, *bp;
dbref loc, aowner;
int num, aflags;
// If we need to call exec() from within this function, we first save
// the state of the global registers, in order to avoid munging them
// inappropriately. Do note that the restoration to their original
// values occurs BEFORE the execution of the @a-attribute. Therefore,
// any changing of setq() values done in the @-attribute and @o-attribute
// will NOT be passed on. This prevents odd behaviors that result from
// odd @verbs and so forth (the idea is to preserve the caller's control
// of the global register values).
//
bool need_pres = false;
reg_ref **preserve = nullptr;
// message to player.
//
if (what > 0)
{
d = atr_pget(thing, what, &aowner, &aflags);
if (*d)
{
if ((aflags & AF_NOEVAL) || NoEval(thing))
{
// Output raw text with no substitutions.
//
notify(player, d);
}
else
{
need_pres = true;
preserve = PushRegisters(MAX_GLOBAL_REGS);
save_global_regs(preserve);
buff = bp = alloc_lbuf("did_it.1");
mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, player, player,
AttrTrace(aflags, EV_EVAL|EV_FIGNORE|EV_FCHECK|EV_TOP),
args, nargs);
*bp = '\0';
if ( (aflags & AF_HTML)
&& Html(player))
{
safe_str(T("\r\n"), buff, &bp);
*bp = '\0';
notify_html(player, buff);
}
else
{
notify(player, buff);
}
free_lbuf(buff);
}
}
else if (def)
{
notify(player, def);
}
free_lbuf(d);
}
else if (what < 0 && def)
{
notify(player, def);
}
// message to neighbors.
//
if ( 0 < owhat
&& Has_location(player)
&& Good_obj(loc = Location(player)))
{
d = atr_pget(thing, owhat, &aowner, &aflags);
if (*d)
{
if ((aflags & AF_NOEVAL) || NoEval(thing))
{
// Output raw text with no substitutions.
//
#ifdef REALITY_LVLS
if (aflags & AF_NONAME)
{
notify_except2_rlevel(loc, player, player, thing, d);
}
else
{
notify_except2_rlevel(loc, player, player, thing,
tprintf(T("%s %s"), Moniker(player), d));
}
#else
if (aflags & AF_NONAME)
{
notify_except2(loc, player, player, thing, d);
}
else
{
notify_except2(loc, player, player, thing,
tprintf(T("%s %s"), Moniker(player), d));
}
#endif // REALITY_LVLS
}
else
{
if (!need_pres)
{
need_pres = true;
preserve = PushRegisters(MAX_GLOBAL_REGS);
save_global_regs(preserve);
}
buff = bp = alloc_lbuf("did_it.2");
mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, player, player,
AttrTrace(aflags, EV_EVAL|EV_FIGNORE|EV_FCHECK|EV_TOP),
args, nargs);
*bp = '\0';
if (*buff)
{
#ifdef REALITY_LVLS
if (aflags & AF_NONAME)
{
notify_except2_rlevel(loc, player, player, thing, buff);
}
else
{
notify_except2_rlevel(loc, player, player, thing,
tprintf(T("%s %s"), Moniker(player), buff));
}
#else
if (aflags & AF_NONAME)
{
notify_except2(loc, player, player, thing, buff);
}
else
{
notify_except2(loc, player, player, thing,
tprintf(T("%s %s"), Moniker(player), buff));
}
#endif // REALITY_LVLS
}
free_lbuf(buff);
} // else (not NOEVAL)
}
else if (odef)
{
#ifdef REALITY_LVLS
if (ctrl_flags & VERB_NONAME)
{
notify_except2_rlevel(loc, player, player, thing, odef);
}
else
{
notify_except2_rlevel(loc, player, player, thing,
tprintf(T("%s %s"), Moniker(player), odef));
}
#else
if (ctrl_flags & VERB_NONAME)
{
notify_except2(loc, player, player, thing, odef);
}
else
{
notify_except2(loc, player, player, thing,
tprintf(T("%s %s"), Moniker(player), odef));
}
#endif // REALITY_LVLS
}
free_lbuf(d);
} else if ( owhat < 0
&& odef
&& Has_location(player)
&& Good_obj(loc = Location(player)))
{
#ifdef REALITY_LVLS
if (ctrl_flags & VERB_NONAME)
{
notify_except2_rlevel(loc, player, player, thing, odef);
}
else
{
notify_except2_rlevel(loc, player, player, thing, tprintf(T("%s %s"), Name(player), odef));
}
#else
if (ctrl_flags & VERB_NONAME)
{
notify_except2(loc, player, player, thing, odef);
}
else
{
notify_except2(loc, player, player, thing, tprintf(T("%s %s"), Name(player), odef));
}
#endif // REALITY_LVLS
}
// If we preserved the state of the global registers, restore them.
//
if (need_pres)
{
restore_global_regs(preserve);
PopRegisters(preserve, MAX_GLOBAL_REGS);
}
// Do the action attribute.
//
#ifdef REALITY_LVLS
if ( 0 < awhat
&& IsReal(thing, player))
#else
if (0 < awhat)
#endif // REALITY_LVLS
{
if (*(act = atr_pget(thing, awhat, &aowner, &aflags)))
{
dbref aowner2;
int aflags2;
charges = atr_pget(thing, A_CHARGES, &aowner2, &aflags2);
if (*charges)
{
num = mux_atol(charges);
if (num > 0)
{
buff = alloc_sbuf("did_it.charges");
mux_ltoa(num-1, buff);
atr_add_raw(thing, A_CHARGES, buff);
free_sbuf(buff);
}
else if (*(buff = atr_pget(thing, A_RUNOUT, &aowner2, &aflags2)))
{
free_lbuf(act);
act = buff;
}
else
{
free_lbuf(act);
free_lbuf(buff);
free_lbuf(charges);
return;
}
}
free_lbuf(charges);
if (!((aflags & AF_NOEVAL) || NoEval(thing)))
{
CLinearTimeAbsolute lta;
wait_que(thing, player, player, AttrTrace(aflags, 0), false, lta,
NOTHING, 0,
act,
nargs, args,
mudstate.global_regs);
}
}
free_lbuf(act);
}
}
/* ---------------------------------------------------------------------------
* do_verb: Command interface to did_it.
*/
void do_verb(dbref executor, dbref caller, dbref enactor, int eval, int key,
UTF8 *victim_str, UTF8 *args[], int nargs, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
// Look for the victim.
//
if ( !victim_str
|| !*victim_str)
{
notify(executor, T("Nothing to do."));
return;
}
// Get the victim.
//
init_match(executor, victim_str, NOTYPE);
match_everything(MAT_EXIT_PARENTS);
dbref victim = noisy_match_result();
if (!Good_obj(victim))
{
return;
}
// Get the actor. Default is my cause.
//
dbref actor;
if ( nargs >= 1
&& args[0] && *args[0])
{
init_match(executor, args[0], NOTYPE);
match_everything(MAT_EXIT_PARENTS);
actor = noisy_match_result();
if (!Good_obj(actor))
{
return;
}
}
else
{
actor = enactor;
}
// Check permissions. There are two possibilities:
//
// 1. Executor controls both victim and actor. In this case,
// victim runs his action list.
//
// 2. Executor controls actor. In this case victim does not run
// his action list and any attributes that executor cannot read
// from victim are defaulted.
//
if (!Controls(executor, actor))
{
notify_quiet(executor, T("Permission denied,"));
return;
}
ATTR *ap;
int what = -1;
int owhat = -1;
int awhat = -1;
const UTF8 *whatd = nullptr;
const UTF8 *owhatd = nullptr;
int nxargs = 0;
dbref aowner = NOTHING;
int aflags = NOTHING;
UTF8 *xargs[10];
switch (nargs) // Yes, this IS supposed to fall through.
{
case 7:
// Get arguments.
//
parse_arglist(victim, actor, actor, args[6],
EV_STRIP_LS | EV_STRIP_TS, xargs, 10, nullptr, 0, &nxargs);
case 6:
// Get action attribute.
//
ap = atr_str(args[5]);
if (ap)
{
awhat = ap->number;
}
case 5:
// Get others message default.
//
if (args[4] && *args[4])
{
owhatd = args[4];
}
case 4:
// Get others message attribute.
//
ap = atr_str(args[3]);
if (ap && (ap->number > 0))
{
owhat = ap->number;
}
case 3:
// Get enactor message default.
//
if (args[2] && *args[2])
{
whatd = args[2];
}
case 2:
// Get enactor message attribute.
//
ap = atr_str(args[1]);
if (ap && (ap->number > 0))
{
what = ap->number;
}
}
// If executor doesn't control both, enforce visibility restrictions.
//
if (!Controls(executor, victim))
{
ap = nullptr;
if (what != -1)
{
atr_get_info(victim, what, &aowner, &aflags);
ap = atr_num(what);
}
if ( !ap
|| !bCanReadAttr(executor, victim, ap, false)
|| ( ap->number == A_DESC
&& !mudconf.read_rem_desc
&& !Examinable(executor, victim)
&& !nearby(executor, victim)))
{
what = -1;
}
ap = nullptr;
if (owhat != -1)
{
atr_get_info(victim, owhat, &aowner, &aflags);
ap = atr_num(owhat);
}
if ( !ap
|| !bCanReadAttr(executor, victim, ap, false)
|| ( ap->number == A_DESC
&& !mudconf.read_rem_desc
&& !Examinable(executor, victim)
&& !nearby(executor, victim)))
{
owhat = -1;
}
awhat = 0;
}
// Go do it.
//
did_it(actor, victim, what, whatd, owhat, owhatd, awhat,
key & VERB_NONAME, (const UTF8 **)xargs, nxargs);
// Free user args.
//
for (int i = 0; i < nxargs; i++)
{
free_lbuf(xargs[i]);
}
}
// AssertionFailed and OutOfMemory moved to libmux.cpp.
static void ListReferences(dbref executor, UTF8 *reference_name)
{
dbref target = NOTHING;
bool global_only = false;
if ( nullptr == reference_name
|| '\0' == reference_name[0])
{
global_only = true;
}
else
{
global_only = false;
target = lookup_player(executor, reference_name, 1);
if (!Good_obj(target))
{
raw_notify(executor, T("No such player."));
return;
}
if (!Controls(executor, target))
{
raw_notify(executor, NOPERM_MESSAGE);
return;
}
}
// Listing:
// - if global_only is true, list all references that begin with _
// - Otherwise, list all references whose owner is target
//
reference_entry *htab_entry;
bool match_found = false;
for (auto &[ref_key, ref_val] : mudstate.reference_htab)
{
htab_entry = static_cast<reference_entry*>(ref_val);
if ( ( global_only
&& '_' == htab_entry->name[0])
|| ( !global_only
&& target == htab_entry->owner))
{
if (!Good_obj(htab_entry->target))
{
continue;
}
if (!match_found)
{
match_found = true;
raw_notify(executor, tprintf(T("%-12s %-20s %-20s"),
T("Reference"), T("Target"), T("Owner")));
raw_notify(executor,
T("-------------------------------------------------------"));
}
UTF8 *object_buf =
unparse_object(executor, htab_entry->target, false);
raw_notify(executor, tprintf(T("%-12s %-20s %-20s"), htab_entry->name,
object_buf, Moniker(htab_entry->owner)));
free_lbuf(object_buf);
}
}
if (!match_found)
{
raw_notify(executor, T("GAME: No references found."));
}
else
{
raw_notify(executor,
T("---------------- End of Reference List ----------------"));
}
}
void do_reference
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *reference_name,
UTF8 *object_name,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(ncargs);
UNUSED_PARAMETER(cargs);
if (key & REFERENCE_LIST)
{
ListReferences(executor, reference_name);
return;
}
// References can only be set on objects the executor can examine.
//
dbref target = NOTHING;
if ( nullptr != object_name
&& '\0' != object_name[0])
{
target = match_thing_quiet(executor, object_name);
if (!Good_obj(target))
{
notify(executor, NOMATCH_MESSAGE);
return;
}
else if (!Examinable(executor, target))
{
notify(executor, NOPERM_MESSAGE);
return;
}
}
if ('_' == reference_name[0])
{
if (!Wizard(executor))
{
notify(executor, NOPERM_MESSAGE);
return;
}
}
UTF8 tbuf[LBUF_SIZE];
size_t tbuf_len;
if ('_' == reference_name[0])
{
tbuf_len = static_cast<size_t>(
snprintf(reinterpret_cast<char *>(tbuf), sizeof(tbuf),
"%s", reinterpret_cast<const char *>(reference_name)));
}
else
{
tbuf_len = static_cast<size_t>(
snprintf(reinterpret_cast<char *>(tbuf), sizeof(tbuf),
"%s.%ld", reinterpret_cast<const char *>(reference_name),
static_cast<long>(executor)));
}
auto it_ref = mudstate.reference_htab.find(std::vector<UTF8>(tbuf, tbuf + tbuf_len));
struct reference_entry *result = (it_ref != mudstate.reference_htab.end())
? static_cast<reference_entry*>(it_ref->second) : nullptr;
enum { Delete, Add, Update, NotFound, Redundant, OutOfMemory } eOperation;
if (nullptr != result)
{
if (NOTHING == target)
{
eOperation = Delete;
}
else if (result->target == target)
{
eOperation = Redundant;
}
else // if (result->target != target)
{
eOperation = Update;
}
}
else
{
if (NOTHING == target)
{
eOperation = NotFound;
}
else
{
eOperation = Add;
if ( !Wizard(executor)
&& ThrottleReferences(executor))
{
raw_notify(executor, T("References requested too quickly."));
return;
}
}
}
if ( Delete == eOperation
|| Update == eOperation)
{
// Release the existing reference.
//
MEMFREE(result->name);
result->name = nullptr;
MEMFREE(result);
result = nullptr;
mudstate.reference_htab.erase(std::vector<UTF8>(tbuf, tbuf + tbuf_len));
}
if ( Update == eOperation
|| Add == eOperation)
{
try
{
result = static_cast<reference_entry *>(MEMALLOC(sizeof(reference_entry)));
}
catch(...)
{
; // Nothing;
}
if (nullptr != result)
{
result->target = target;
result->owner = executor;
result->name = StringCloneLen(tbuf, tbuf_len);
mudstate.reference_htab.emplace(std::vector<UTF8>(tbuf, tbuf + tbuf_len), result);
}
else
{
eOperation = OutOfMemory;
}
}
if (Delete == eOperation)
{
raw_notify(executor, T("Reference cleared."));
}
else if (Update == eOperation)
{
raw_notify(executor, T("Reference updated."));
}
else if (Redundant == eOperation)
{
raw_notify(executor, T("That reference already exists."));
}
else if (NotFound == eOperation)
{
raw_notify(executor, T("No such reference to clear."));
}
else if (Add == eOperation)
{
raw_notify(executor, T("Reference added."));
}
else if (OutOfMemory == eOperation)
{
raw_notify(executor, OUT_OF_MEMORY);
}
}