mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
do_restart() rebuilds its exec argv from mudconf.pid_file and
mudconf.log_dir. Both were read in three places and written in NONE:
predicates.cpp the execl <- read
engine_com.cpp GetConfig -> DRIVER_CONFIG.pid_file <- read
engine_com.cpp GetConfig -> DRIVER_CONFIG.log_dir <- read
There is no `pid_file` or `log_dir` config directive either, so nothing in
netmux.conf could populate them. Both are driver-owned CLI values --
driver.cpp says so outright ("pid_file is driver-owned -- set from CLI or
default") -- and the engine had no way to learn them. So every @restart on
every site exec'd with an empty -p and -e.
Impact is quiet, which is why it lasted: a site running
netmux -p /var/run/mux/netmux.pid
has its pidfile revert to the default netmux.pid in the working directory
after the first restart, and stops maintaining the configured path. The
pid inside the stale file stays correct, because execve preserves it, so
nothing breaks until something else moves. Same for -e.
The fingerprint is visible in ps on any restarted 2.14: `-p` with nothing
after it.
Fix follows #817's precedent exactly -- driver-owned strings the engine
needs, handed over at bridge init:
- g_driver_pid_file / g_driver_log_dir replace two file-static CLI
variables in driver.cpp (pErrorBasename was also a poor name for
something that is a log directory). Globals for the same reason
g_version is one: the CDriverControl bridge is a different TU.
- mux_IDriverControl gains GetInvocationPaths. IID bumped D4D6 -> D4D7;
the comment now records both bumps and why a vtable change needs one.
- conn_bridge_init caches them into mudconf. Safe there because
Startup() runs after LoadGame(), so cf_init() cannot wipe them
afterwards. Pointers are stored, not copied: they are driver-owned
storage that lives for the process, and nothing in the engine frees or
reassigns either field.
- CScriptDriverControl implements it too, returning empty strings.
muxscript writes no pidfile and has no @restart; an invented default
would only be wrong somewhere later.
The argv is now built dynamically and omits an option it has no value for.
-c, -p and -e are all CLI_REQUIRED, so a bare flag makes the successor log
"Option 'x' requires an argument, but none was found." Populating the
fields alone would have left that warning for any site that supplies only
one of the two -- which is what test-scenario showed, since run.sh passes
-p and not -e.
Verified on a live restart, reading the successor's argv (execve replaces
argv and preserves the pid, so this is direct evidence):
boot: ./bin/netmux -c netmux.conf -p custom-name.pid -e logs
restart: netmux -c netmux.conf -p custom-name.pid -e logs
ok - successor kept -p custom-name.pid
ok - successor kept -e logs
ok - no missing-argument warning in the log
Catch-verified by removing only the bridge caching:
restart: netmux -c netmux.conf -p
not ok - successor lost the -p value
not ok - successor lost the -e value
not ok - successor logged a missing-argument warning:
15:Warning: Option '-p' requires an argument, but none was found.
tests/scenario/restart_helpers.py gains the assertion; it already pays for
a restart, so this costs no extra cycle. It skips if the server under test
was not started with -p.
Clean rebuild, since the IDriverControl vtable changed.
make test: 36 targets, 34 passed, 2 skipped (NLS), 0 failed
config: jit=yes stubslave=yes nls=no realitylvls=yes wodrealms=yes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3242 lines
83 KiB
C++
3242 lines
83 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"
|
||
#include "mux_table.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;
|
||
LBuf key = LBuf_Adopt(atr_get("could_doit.134", thing, locknum, &aowner, &aflags));
|
||
bool doit = eval_boolexp_atr(player, thing, thing, 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)));
|
||
}
|
||
|
||
// Read A_RQUOTA as the 32-bit quantity the rest of the quota system treats
|
||
// it as.
|
||
//
|
||
// The quota domain is 32-bit by construction everywhere else: pay_quota and
|
||
// mung_quotas hold it in int, do_quota parses into int, destroy_obj's refund
|
||
// is int, and both writers format into a UTF8[I32BUF_SIZE]. But the stored
|
||
// attribute is just text, and mux_atoi64 will parse whatever is in it -- so a
|
||
// corrupted or hand-crafted A_RQUOTA delivers a value no caller can represent.
|
||
//
|
||
// Widening the callers instead was the alternative, and is wrong: it would
|
||
// make one writer able to store a value every other reader still truncates,
|
||
// which converts a visible error into silent drift. Clamping keeps the
|
||
// domain honest and identical on LP64 and LLP64 (#1408).
|
||
//
|
||
static int quota_from_attr(const UTF8 *pQuota)
|
||
{
|
||
int64_t q = mux_atoi64(pQuota);
|
||
if (q > INT32_MAX)
|
||
{
|
||
return INT32_MAX;
|
||
}
|
||
if (q < INT32_MIN)
|
||
{
|
||
return INT32_MIN;
|
||
}
|
||
return static_cast<int>(q);
|
||
}
|
||
|
||
// Add two quota quantities without overflowing int. Both operands are
|
||
// already in range; their sum need not be.
|
||
//
|
||
static int quota_add(int a, int b)
|
||
{
|
||
int64_t sum = static_cast<int64_t>(a) + static_cast<int64_t>(b);
|
||
if (sum > INT32_MAX)
|
||
{
|
||
return INT32_MAX;
|
||
}
|
||
if (sum < INT32_MIN)
|
||
{
|
||
return INT32_MIN;
|
||
}
|
||
return static_cast<int>(sum);
|
||
}
|
||
|
||
static bool pay_quota(dbref who, int cost)
|
||
{
|
||
// If no cost, succeed
|
||
//
|
||
if (cost <= 0)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// determine quota
|
||
//
|
||
dbref aowner;
|
||
int aflags;
|
||
LBuf quota_str = LBuf_Adopt(atr_get("pay_quota.200", Owner(who), A_RQUOTA, &aowner, &aflags));
|
||
int quota = quota_from_attr(quota_str);
|
||
|
||
// enough to build? Wizards always have enough.
|
||
//
|
||
quota = quota_add(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(M_("Sorry, you don’t have enough %s."),
|
||
mudconf.many_coins));
|
||
}
|
||
else
|
||
{
|
||
notify(player, tprintf(M_("Sorry, that player doesn’t have enough %s."),
|
||
mudconf.many_coins));
|
||
}
|
||
return false;
|
||
}
|
||
if (mudconf.quotas)
|
||
{
|
||
if (!pay_quota(who, quota))
|
||
{
|
||
if (player == who)
|
||
{
|
||
notify(player, M_("Sorry, your building contract has run out."));
|
||
}
|
||
else
|
||
{
|
||
notify(player,
|
||
M_("Sorry, that player’s 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];
|
||
|
||
LBuf quota = LBuf_Adopt(atr_get("add_quota.288", who, A_RQUOTA, &aowner, &aflags));
|
||
|
||
// mux_ltoa takes long, so passing the int64_t from mux_atoi64 directly
|
||
// narrowed it -- a no-op on LP64, but on LLP64 the high word was dropped
|
||
// and a stored 8589934592 came back as 1 (#1408).
|
||
//
|
||
mux_ltoa(quota_add(quota_from_attr(quota), payment), buf);
|
||
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)
|
||
{
|
||
thread_local 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)
|
||
{
|
||
thread_local 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)
|
||
{
|
||
LBuf buf = LBuf_Src("handle_ears");
|
||
UTF8 *bp = buf.get();
|
||
|
||
// 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);
|
||
}
|
||
}
|
||
|
||
// 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;
|
||
LBuf buff = LBuf_Src("do_switch");
|
||
UTF8 *bp = buff.get();
|
||
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;
|
||
}
|
||
}
|
||
|
||
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)
|
||
{
|
||
LBuf tbuf = LBuf_Src("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);
|
||
}
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
CLinearTimeAbsolute lta;
|
||
LBuf buff = LBuf_Src("do_if");
|
||
UTF8 *bp = buff.get();
|
||
|
||
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);
|
||
|
||
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.
|
||
//
|
||
thread_local 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));
|
||
LBuf tmp = LBuf_Src("ok_name");
|
||
nPlain = co_tolower(tmp, pName, nPlain);
|
||
memcpy(pName, tmp, nPlain + 1);
|
||
}
|
||
}
|
||
if ( 0 == nargs
|
||
|| '\0' == pName[0]
|
||
|| ( pName[0] == '_'
|
||
&& pName[1] == '_'))
|
||
{
|
||
notify(player, M_("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, M_("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(M_("%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.
|
||
}
|
||
if (nullptr == cmd)
|
||
{
|
||
notify(player, OUT_OF_MEMORY);
|
||
return;
|
||
}
|
||
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(M_("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(M_("%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, M_("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, M_("Sorry."));
|
||
return;
|
||
}
|
||
|
||
dbref thing = NOTHING;
|
||
int atr = NOTHING;
|
||
ATTR *pattr;
|
||
if (*command)
|
||
{
|
||
if ( !parse_attrib(player, command, &thing, &pattr)
|
||
|| !pattr)
|
||
{
|
||
notify(player, M_("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
|
||
{
|
||
// No backup command to restore. Remove all hash
|
||
// entries that still reference 'old'.
|
||
//
|
||
mudstate.command_htab.erase(std::vector<UTF8>(p__Name, p__Name + n__Name));
|
||
for (auto it2 = mudstate.command_htab.begin(); it2 != mudstate.command_htab.end(); )
|
||
{
|
||
if (it2->second == old)
|
||
{
|
||
it2 = mudstate.command_htab.erase(it2);
|
||
}
|
||
else
|
||
{
|
||
++it2;
|
||
}
|
||
}
|
||
}
|
||
MEMFREE(old->cmdname);
|
||
old->cmdname = nullptr;
|
||
MEMFREE(old);
|
||
old = nullptr;
|
||
cache_prefix_cmds();
|
||
notify(player, M_("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, M_("Done."));
|
||
return;
|
||
}
|
||
}
|
||
notify(player, M_("Command not found in command table."));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
notify(player, M_("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, M_("That is not a player."));
|
||
return;
|
||
}
|
||
if (!Connected(doer))
|
||
{
|
||
notify(player, M_("That player is not connected."));
|
||
return;
|
||
}
|
||
if (!player_has_program(doer))
|
||
{
|
||
notify(player, M_("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, M_("@program cleared."));
|
||
notify(doer, M_("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, M_("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, M_("That is not a player."));
|
||
return;
|
||
}
|
||
if (!Connected(doer))
|
||
{
|
||
notify(player, M_("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, M_("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, M_("Attribute not present on object."));
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
notify(player, M_("No such attribute."));
|
||
return;
|
||
}
|
||
|
||
const auto program = static_cast<program_data*>(MEMALLOC(sizeof(program_data)));
|
||
if (nullptr == program)
|
||
{
|
||
notify(player, OUT_OF_MEMORY);
|
||
return;
|
||
}
|
||
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, M_("Dumping. Please try again later."));
|
||
bDenied = true;
|
||
}
|
||
#endif // HAVE_WORKING_FORK
|
||
|
||
|
||
if (!mudstate.bCanRestart)
|
||
{
|
||
notify(executor, M_("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, M_("GAME: Restart by %s, please wait. (All SSL connections will be dropped.)"), Moniker(Owner(executor)));
|
||
#else
|
||
raw_broadcast(0, M_("GAME: Restart by %s, please wait."), Moniker(Owner(executor)));
|
||
#endif
|
||
STARTLOG(LOG_ALWAYS, "WIZ", "RSTRT");
|
||
log_text(T("Restart by "));
|
||
log_name(executor);
|
||
ENDLOG;
|
||
|
||
// Write restart.db BEFORE the teardown, and decline the restart if it
|
||
// cannot be written (#2043).
|
||
//
|
||
// This used to happen after prepare_for_restart(), final_modules() and
|
||
// CLOSE, i.e. after the point of no return -- so a failed write was
|
||
// discovered when there was nothing left to return to, and the successor
|
||
// exec'd into a game it could not serve: prepare_for_restart() detaches
|
||
// the listener fds but leaves them open for the successor to adopt, so
|
||
// without restart.db the successor collides with its own inheritance and
|
||
// dies on "bind() failed: Address already in use".
|
||
//
|
||
// DumpRestartDb() records the listeners and closes the TLS/WebSocket
|
||
// sessions itself, because the dump needs both done first. Those are the
|
||
// only destructive steps that precede the decision, which is why the
|
||
// decline below is not free and says so.
|
||
//
|
||
#if defined(HAVE_WORKING_FORK)
|
||
if (!dump_restart_db())
|
||
{
|
||
raw_broadcast(0, M_("GAME: Restart could not be prepared and has been cancelled. The game is still running."));
|
||
STARTLOG(LOG_ALWAYS, "WIZ", "RSTRT");
|
||
log_text(T("Restart CANCELLED: restart.db could not be written. Requested by "));
|
||
log_name(executor);
|
||
ENDLOG;
|
||
return;
|
||
}
|
||
#endif // HAVE_WORKING_FORK
|
||
|
||
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)
|
||
// restart.db was written above, before the teardown, and the restart was
|
||
// declined if it could not be (#2043). Dumping here as well would
|
||
// re-serialise descriptors the teardown has since closed.
|
||
|
||
Log.StopLogging();
|
||
|
||
// #2199: build argv, omitting any option whose value is empty.
|
||
//
|
||
// -c, -p and -e are all CLI_REQUIRED, so passing a bare flag makes the
|
||
// successor log "Option 'x' requires an argument, but none was found."
|
||
// That is what a restarted server used to do for BOTH -p and -e, every
|
||
// time, because mudconf.pid_file and mudconf.log_dir were read here and
|
||
// written nowhere. They are populated now, but a site that supplies
|
||
// only one of the two would still collect a warning for the other on
|
||
// every restart -- so skip what we do not have rather than pass "".
|
||
//
|
||
const char *argvRestart[9];
|
||
int argcRestart = 0;
|
||
argvRestart[argcRestart++] = "netmux";
|
||
argvRestart[argcRestart++] = "-c";
|
||
argvRestart[argcRestart++] =
|
||
reinterpret_cast<const char *>(mudconf.config_file);
|
||
if ( nullptr != mudconf.pid_file
|
||
&& '\0' != mudconf.pid_file[0])
|
||
{
|
||
argvRestart[argcRestart++] = "-p";
|
||
argvRestart[argcRestart++] =
|
||
reinterpret_cast<const char *>(mudconf.pid_file);
|
||
}
|
||
if ( nullptr != mudconf.log_dir
|
||
&& '\0' != mudconf.log_dir[0])
|
||
{
|
||
argvRestart[argcRestart++] = "-e";
|
||
argvRestart[argcRestart++] =
|
||
reinterpret_cast<const char *>(mudconf.log_dir);
|
||
}
|
||
argvRestart[argcRestart] = nullptr;
|
||
|
||
execv("bin/netmux", const_cast<char *const *>(argvRestart));
|
||
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, const 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 *d1;
|
||
const UTF8 *place, *s1, *temp;
|
||
const UTF8 *start = target;
|
||
while (*target)
|
||
{
|
||
// Fail if no ' characters.
|
||
//
|
||
place = target;
|
||
target = reinterpret_cast<const UTF8 *>(strchr(reinterpret_cast<const 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.
|
||
//
|
||
{
|
||
LBuf buff = LBuf_Src("is_posess");
|
||
d1 = buff.get();
|
||
for (s1 = start; *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();
|
||
}
|
||
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_atoi64(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_atoi64(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(S_("#-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(S_("#-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;
|
||
int64_t num;
|
||
int 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)
|
||
{
|
||
// int64_t, not int (#1402).
|
||
//
|
||
num = mux_atoi64(charges);
|
||
if (num > 0)
|
||
{
|
||
buff = alloc_sbuf("did_it.charges");
|
||
mux_i64toa(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, M_("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, M_("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, M_("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;
|
||
}
|
||
|
||
// @list ref schema (#1667 Phase 4 C5): name 12 / target 20 / owner 20.
|
||
//
|
||
static const size_t kRefNameCols = 12;
|
||
static const size_t kRefTargetCols = 20;
|
||
static const size_t kRefOwnerCols = 20;
|
||
|
||
if (!match_found)
|
||
{
|
||
match_found = true;
|
||
UTF8 header[LBUF_SIZE];
|
||
size_t pos = 0;
|
||
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
||
M_("Reference"), kRefNameCols);
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
||
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
||
M_("Target"), kRefTargetCols);
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
||
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
||
M_("Owner"), kRefOwnerCols);
|
||
raw_notify(executor, header);
|
||
raw_notify(executor,
|
||
M_("-------------------------------------------------------"));
|
||
}
|
||
|
||
UTF8 *object_buf =
|
||
unparse_object(executor, htab_entry->target, false);
|
||
|
||
UTF8 line[LBUF_SIZE];
|
||
size_t pos = 0;
|
||
pos = mux_table_append_ljust(line, sizeof(line), pos,
|
||
htab_entry->name, kRefNameCols);
|
||
pos = mux_table_append_bytes(line, sizeof(line), pos, " ");
|
||
pos = mux_table_append_ljust(line, sizeof(line), pos,
|
||
object_buf, kRefTargetCols);
|
||
pos = mux_table_append_bytes(line, sizeof(line), pos, " ");
|
||
pos = mux_table_append_ljust(line, sizeof(line), pos,
|
||
Moniker(htab_entry->owner), kRefOwnerCols);
|
||
raw_notify(executor, line);
|
||
|
||
free_lbuf(object_buf);
|
||
}
|
||
}
|
||
|
||
if (!match_found)
|
||
{
|
||
raw_notify(executor, M_("GAME: No references found."));
|
||
}
|
||
else
|
||
{
|
||
raw_notify(executor,
|
||
M_("---------------- 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;
|
||
}
|
||
}
|
||
|
||
LBuf tbuf = LBuf_Src("do_name");
|
||
// mux_snprintf returns length written (0..count-1), not "would have".
|
||
//
|
||
size_t tbuf_len;
|
||
if ('_' == reference_name[0])
|
||
{
|
||
tbuf_len = mux_snprintf(tbuf.get(), LBUF_SIZE, T("%s"),
|
||
reference_name);
|
||
}
|
||
else
|
||
{
|
||
tbuf_len = mux_snprintf(tbuf.get(), LBUF_SIZE, T("%s.%lld"),
|
||
reference_name, static_cast<long long>(executor));
|
||
}
|
||
auto it_ref = mudstate.reference_htab.find(std::vector<UTF8>(tbuf.get(), tbuf.get() + 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, M_("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.get(), tbuf.get() + 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.get(), tbuf.get() + tbuf_len), result);
|
||
}
|
||
else
|
||
{
|
||
eOperation = OutOfMemory;
|
||
}
|
||
}
|
||
|
||
if (Delete == eOperation)
|
||
{
|
||
raw_notify(executor, M_("Reference cleared."));
|
||
}
|
||
else if (Update == eOperation)
|
||
{
|
||
raw_notify(executor, M_("Reference updated."));
|
||
}
|
||
else if (Redundant == eOperation)
|
||
{
|
||
raw_notify(executor, M_("That reference already exists."));
|
||
}
|
||
else if (NotFound == eOperation)
|
||
{
|
||
raw_notify(executor, M_("No such reference to clear."));
|
||
}
|
||
else if (Add == eOperation)
|
||
{
|
||
raw_notify(executor, M_("Reference added."));
|
||
}
|
||
else if (OutOfMemory == eOperation)
|
||
{
|
||
raw_notify(executor, OUT_OF_MEMORY);
|
||
}
|
||
}
|