mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Restart (#1039–#1043): zero process-local DESC fields after load, restore peer via getpeername, strip non-survivable TLS/WS flags, drop TLS and WebSocket before exec, tear down adopt failures with shutdownsock, abort partial restart loads cleanly. DB (#1044–#1047): cache_flush_writes returns success only after Commit; leave dirty queue on failure; atr_head union SQLite with pending cache/queue; set mudstate.bStandAlone in DbConvert; flush attr queue before import Commit. Queue (#1048–#1052): setup_que refunds on OOM; sql_que frees BQUE on Query fail and inits waittime; do_wait rolls back semaphore if wait_que fails; include nest depth limit 50; CPU halt_que uses Owner+object for machines. JIT (#1053–#1057): full 64-bit rv_load_i64; setq/r single-char RegisterSet; decline long CARGS; accumulate inline u() watermarks; bounded guest_strnlen on ECALL paths. Conf/convert (#1058–#1061): @enable/@disable sync g_dc; live driver knobs for output/retry/max_players/timeouts/quota; safe heap encode for t5x/t6h/r7h attributes.
2253 lines
61 KiB
C++
2253 lines
61 KiB
C++
/*! \file engine.cpp
|
|
* \brief Game engine logic: notification, matching, dumps, and loading.
|
|
*
|
|
* Functions in this file implement game-level semantics including
|
|
* notification dispatch, attribute matching, database dumps and loads,
|
|
* and other engine-side operations. Driver-level startup, CLI parsing,
|
|
* and process management are in driver.cpp.
|
|
*/
|
|
|
|
#include "copyright.h"
|
|
#include "autoconf.h"
|
|
#include "config.h"
|
|
#include "externs.h"
|
|
#include "color_ops.h"
|
|
#include "sqlite_backend.h"
|
|
|
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
|
#include <pcre2.h>
|
|
|
|
void do_dump(dbref executor, dbref caller, dbref enactor, int eval, int key)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
|
|
#if defined(HAVE_WORKING_FORK)
|
|
if (mudstate.dumping)
|
|
{
|
|
notify(executor, T("Dumping in progress. Try again later."));
|
|
return;
|
|
}
|
|
#endif
|
|
notify(executor, T("Dumping..."));
|
|
fork_and_dump(key);
|
|
}
|
|
|
|
// print out stuff into error file
|
|
//
|
|
void report(void)
|
|
{
|
|
STARTLOG(LOG_BUGS, "BUG", "INFO");
|
|
log_text(T("Command: \xE2\x80\x98"));
|
|
log_text(g_debug_cmd);
|
|
log_text(T("\xE2\x80\x99"));
|
|
ENDLOG;
|
|
if (Good_obj(mudstate.curr_executor))
|
|
{
|
|
STARTLOG(LOG_BUGS, "BUG", "INFO");
|
|
log_text(T("Player: "));
|
|
log_name_and_loc(mudstate.curr_executor);
|
|
if ( mudstate.curr_enactor != mudstate.curr_executor
|
|
&& Good_obj(mudstate.curr_enactor))
|
|
{
|
|
log_text(T(" Enactor: "));
|
|
log_name_and_loc(mudstate.curr_enactor);
|
|
}
|
|
ENDLOG;
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* regexp_match: Load a regular expression match and insert it into
|
|
* registers.
|
|
*/
|
|
|
|
bool regexp_match
|
|
(
|
|
UTF8 *pattern,
|
|
UTF8 *str,
|
|
int case_opt,
|
|
UTF8 *args[],
|
|
int nargs
|
|
)
|
|
{
|
|
int matches;
|
|
int i;
|
|
PCRE2_SIZE erroffset;
|
|
int errcode;
|
|
|
|
/*
|
|
* Load the regexp pattern. This allocates memory which must be
|
|
* later freed. PCRE2 code objects must be explicitly freed with pcre2_code_free().
|
|
*/
|
|
pcre2_code *re;
|
|
if (alarm_clock.alarmed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
re = pcre2_compile_8(pattern, PCRE2_ZERO_TERMINATED, PCRE2_UTF|case_opt,
|
|
&errcode, &erroffset, nullptr);
|
|
if (re == nullptr)
|
|
{
|
|
/*
|
|
* This is a matching error. We have an error message in
|
|
* regexp_errbuf that we can ignore, since we're doing
|
|
* command-matching.
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
// Create match data block for storing results
|
|
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr);
|
|
if (match_data == nullptr)
|
|
{
|
|
pcre2_code_free(re);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Now we try to match the pattern. The relevant fields will
|
|
* automatically be filled in by this.
|
|
*/
|
|
matches = pcre2_match(re, str, PCRE2_ZERO_TERMINATED, 0, 0, match_data, nullptr);
|
|
|
|
if (matches < 0)
|
|
{
|
|
pcre2_match_data_free(match_data);
|
|
pcre2_code_free(re);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Now we fill in our args vector. Note that in regexp matching,
|
|
* 0 is the entire string matched, and the parenthesized strings
|
|
* go from 1 to 9. We DO PRESERVE THIS PARADIGM, for consistency
|
|
* with other languages.
|
|
*/
|
|
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
|
|
|
|
for (i = 0; i < nargs && i < matches; ++i)
|
|
{
|
|
args[i] = alloc_lbuf("regexp_match");
|
|
PCRE2_SIZE substring_length = 0;
|
|
|
|
// Calculate substring length (equivalent to what pcre_copy_substring would do)
|
|
int rc = pcre2_substring_length_bynumber(match_data, i, &substring_length);
|
|
if (rc < 0 || substring_length >= LBUF_SIZE)
|
|
{
|
|
free_lbuf(args[i]);
|
|
args[i] = nullptr;
|
|
continue;
|
|
}
|
|
|
|
// Get the substring
|
|
PCRE2_SIZE outlen = 0;
|
|
rc = pcre2_substring_copy_bynumber(match_data, i, args[i], &outlen);
|
|
if (rc < 0)
|
|
{
|
|
free_lbuf(args[i]);
|
|
args[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
// Fill any remaining args with nullptr
|
|
for (; i < nargs; ++i)
|
|
{
|
|
args[i] = nullptr;
|
|
}
|
|
|
|
pcre2_match_data_free(match_data);
|
|
pcre2_code_free(re);
|
|
return true;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* atr_match: Check attribute list for wild card matches and queue them.
|
|
*/
|
|
|
|
static int atr_match1
|
|
(
|
|
dbref thing,
|
|
dbref parent,
|
|
dbref player,
|
|
UTF8 type,
|
|
UTF8 *str,
|
|
UTF8 *raw_str,
|
|
int check_exclude,
|
|
int hash_insert
|
|
)
|
|
{
|
|
// See if we can do it. Silently fail if we can't.
|
|
//
|
|
if (!could_doit(player, parent, A_LUSE))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int match = 0;
|
|
if ( ( AMATCH_CMD == type
|
|
&& mudstate.bfNoCommands.IsSet(parent))
|
|
|| ( AMATCH_LISTEN == type
|
|
&& mudstate.bfNoListens.IsSet(parent)))
|
|
{
|
|
// We may need to process the hash_insert to support exclusion of
|
|
// this parent's $-commands before we return no matches.
|
|
//
|
|
if (hash_insert)
|
|
{
|
|
// Because we know this object contains no commands, there is no
|
|
// need to look at the attribute values.
|
|
//
|
|
atr_push();
|
|
unsigned char *as;
|
|
for (int atr = atr_head(parent, &as); atr; atr = atr_next(&as))
|
|
{
|
|
ATTR *ap = atr_num(atr);
|
|
|
|
if ( ap
|
|
&& 0 == (ap->flags & AF_NOPROG)
|
|
&& ( !check_exclude
|
|
|| ( 0 == (ap->flags & AF_PRIVATE)
|
|
&& mudstate.parent_htab.find(ap->number) == mudstate.parent_htab.end())))
|
|
{
|
|
mudstate.parent_htab.emplace(ap->number, &atr);
|
|
}
|
|
}
|
|
atr_pop();
|
|
}
|
|
return match;
|
|
}
|
|
|
|
bool bFoundCommands = false;
|
|
bool bFoundListens = false;
|
|
|
|
atr_push();
|
|
unsigned char *as;
|
|
for (int atr = atr_head(parent, &as); atr; atr = atr_next(&as))
|
|
{
|
|
ATTR *ap = atr_num(atr);
|
|
|
|
// Never check NOPROG attributes.
|
|
//
|
|
if ( !ap
|
|
|| (ap->flags & AF_NOPROG))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We need to grab the attribute even before we know whether we'll use
|
|
// it or not in order to maintain cached knowledge about ^-Commands
|
|
// and $-Commands.
|
|
//
|
|
dbref aowner;
|
|
int aflags;
|
|
LBuf buff = LBuf_Src("atr_match_scan");
|
|
atr_get_str(buff, parent, atr, &aowner, &aflags);
|
|
|
|
UTF8 *s = nullptr;
|
|
if ( 0 == (aflags & AF_NOPROG)
|
|
&& ( AMATCH_CMD == buff[0]
|
|
|| AMATCH_LISTEN == buff[0]))
|
|
{
|
|
s = find_pattern_delimiter(buff + 1);
|
|
if (s)
|
|
{
|
|
if (AMATCH_CMD == buff[0])
|
|
{
|
|
bFoundCommands = true;
|
|
}
|
|
else
|
|
{
|
|
bFoundListens = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we aren't the bottom level, check if we saw this attr
|
|
// before. Also exclude it if the attribute type is PRIVATE.
|
|
//
|
|
if ( check_exclude
|
|
&& ( (ap->flags & AF_PRIVATE)
|
|
|| (aflags & AF_PRIVATE)
|
|
|| mudstate.parent_htab.find(ap->number) != mudstate.parent_htab.end()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If we aren't the top level, remember this attr so we
|
|
// exclude it from now on.
|
|
//
|
|
if (hash_insert)
|
|
{
|
|
mudstate.parent_htab.emplace(ap->number, &atr);
|
|
}
|
|
|
|
if (aflags & AF_NOPROG)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check for the leadin character after excluding the attrib.
|
|
// This lets non-command attribs on the child block commands
|
|
// on the parent.
|
|
//
|
|
if (buff[0] != type)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Was there a ':'?
|
|
//
|
|
if (!s)
|
|
{
|
|
continue;
|
|
}
|
|
*s++ = '\0';
|
|
unescape_pattern_colons(buff + 1);
|
|
// For ^-listens, normalize the pattern so ASCII quotes match
|
|
// fancy quotes. Match against a normalized copy of the message
|
|
// text, but re-capture from the original so %0 retains fancy
|
|
// quotes for display.
|
|
//
|
|
UTF8 *match_str = (aflags & AF_NOPARSE) ? raw_str : str;
|
|
UTF8 *strNorm = nullptr;
|
|
if (AMATCH_LISTEN == type)
|
|
{
|
|
strip_fancy_quotes(buff + 1);
|
|
strNorm = alloc_lbuf("atr_match1.norm");
|
|
mux_strncpy(strNorm, match_str, LBUF_SIZE - 1);
|
|
strip_fancy_quotes(strNorm);
|
|
}
|
|
UTF8 *match_subject = strNorm ? strNorm : match_str;
|
|
|
|
UTF8 *args[NUM_ENV_VARS];
|
|
if ( ( 0 != (aflags & AF_REGEXP)
|
|
&& regexp_match(buff + 1, match_subject,
|
|
((aflags & AF_CASE) ? PCRE2_CASELESS : 0), args, NUM_ENV_VARS))
|
|
|| ( 0 == (aflags & AF_REGEXP)
|
|
&& wild(buff + 1, match_subject,
|
|
args, NUM_ENV_VARS)))
|
|
{
|
|
// If we matched on normalized text, re-capture from the
|
|
// original so %0 etc. contain fancy quotes.
|
|
//
|
|
if (strNorm)
|
|
{
|
|
for (int j = 0; j < NUM_ENV_VARS; j++)
|
|
{
|
|
if (args[j]) { free_lbuf(args[j]); args[j] = nullptr; }
|
|
}
|
|
if (0 != (aflags & AF_REGEXP))
|
|
{
|
|
regexp_match(buff + 1, match_str,
|
|
((aflags & AF_CASE) ? PCRE2_CASELESS : 0), args, NUM_ENV_VARS);
|
|
}
|
|
else
|
|
{
|
|
wild(buff + 1, match_str, args, NUM_ENV_VARS);
|
|
}
|
|
}
|
|
|
|
match = 1;
|
|
CLinearTimeAbsolute lta;
|
|
wait_que(thing, player, player, AttrTrace(aflags, 0), false, lta,
|
|
NOTHING, 0,
|
|
s,
|
|
NUM_ENV_VARS, (const UTF8 **)args,
|
|
mudstate.global_regs);
|
|
|
|
for (int i = 0; i < NUM_ENV_VARS; i++)
|
|
{
|
|
if (args[i])
|
|
{
|
|
free_lbuf(args[i]);
|
|
}
|
|
}
|
|
}
|
|
if (strNorm)
|
|
{
|
|
free_lbuf(strNorm);
|
|
}
|
|
}
|
|
atr_pop();
|
|
|
|
if (bFoundCommands)
|
|
{
|
|
mudstate.bfNoCommands.Clear(parent);
|
|
mudstate.bfCommands.Set(parent);
|
|
}
|
|
else
|
|
{
|
|
mudstate.bfCommands.Clear(parent);
|
|
mudstate.bfNoCommands.Set(parent);
|
|
}
|
|
|
|
if (bFoundListens)
|
|
{
|
|
mudstate.bfNoListens.Clear(parent);
|
|
mudstate.bfListens.Set(parent);
|
|
}
|
|
else
|
|
{
|
|
mudstate.bfListens.Clear(parent);
|
|
mudstate.bfNoListens.Set(parent);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
bool atr_match
|
|
(
|
|
dbref thing,
|
|
dbref player,
|
|
UTF8 type,
|
|
UTF8 *str,
|
|
UTF8 *raw_str,
|
|
bool check_parents
|
|
)
|
|
{
|
|
int lev, result;
|
|
bool exclude, insert;
|
|
dbref parent;
|
|
|
|
// If thing is halted or we are matching $-commands on a NO_COMMAND
|
|
// object, don't check anything
|
|
//
|
|
if ( Halted(thing)
|
|
|| ( AMATCH_CMD == type
|
|
&& No_Command(thing)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we're matching ^-commands, strip ANSI
|
|
//
|
|
if (AMATCH_LISTEN == type)
|
|
{
|
|
// Remember, strip_color returns a pointer to a static buffer
|
|
// within itself.
|
|
//
|
|
str = strip_color(str);
|
|
}
|
|
|
|
// If not checking parents, just check the thing
|
|
//
|
|
bool match = false;
|
|
if (!check_parents)
|
|
{
|
|
return (atr_match1(thing, thing, player, type, str, raw_str, false, false) > 0);
|
|
}
|
|
|
|
// Check parents, ignoring halted objects
|
|
//
|
|
exclude = false;
|
|
insert = true;
|
|
mudstate.parent_htab.clear();
|
|
ITER_PARENTS(thing, parent, lev)
|
|
{
|
|
if (!Good_obj(Parent(parent)))
|
|
{
|
|
insert = false;
|
|
}
|
|
result = atr_match1(thing, parent, player, type, str, raw_str,
|
|
exclude, insert);
|
|
if (result > 0)
|
|
{
|
|
match = true;
|
|
}
|
|
else if (result < 0)
|
|
{
|
|
return match;
|
|
}
|
|
exclude = true;
|
|
}
|
|
return match;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* notify_check: notifies the object #target of the message msg, and
|
|
* optionally notify the contents, neighbors, and location also.
|
|
*/
|
|
|
|
static bool check_filter(dbref object, dbref player, int filter, const UTF8 *msg)
|
|
{
|
|
int aflags;
|
|
dbref aowner;
|
|
LBuf buf = LBuf_Adopt(atr_pget(object, filter, &aowner, &aflags));
|
|
if (!*buf.get())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
reg_ref **preserve = nullptr;
|
|
preserve = PushRegisters(MAX_GLOBAL_REGS);
|
|
save_global_regs(preserve);
|
|
|
|
LBuf nbuf = LBuf_Src("check_filter");
|
|
UTF8 *dp = nbuf;
|
|
if ((aflags & AF_NOEVAL) || NoEval(object))
|
|
{
|
|
mux_strncpy(nbuf, buf, LBUF_SIZE-1);
|
|
dp = nbuf.get() + strlen((const char *)nbuf.get());
|
|
}
|
|
else
|
|
{
|
|
mux_exec(buf, LBUF_SIZE-1, nbuf, &dp, object, player, player,
|
|
AttrTrace(aflags, EV_FIGNORE|EV_EVAL|EV_TOP),
|
|
nullptr, 0);
|
|
}
|
|
*dp = '\0';
|
|
strip_fancy_quotes(nbuf);
|
|
dp = nbuf;
|
|
|
|
restore_global_regs(preserve);
|
|
PopRegisters(preserve, MAX_GLOBAL_REGS);
|
|
preserve = nullptr;
|
|
|
|
if (!(aflags & AF_REGEXP))
|
|
{
|
|
do
|
|
{
|
|
UTF8 *cp = parse_to(&dp, ',', EV_STRIP_CURLY);
|
|
mudstate.wild_invk_ctr = 0;
|
|
if ( alarm_clock.alarmed
|
|
|| quick_wild(cp, msg))
|
|
{
|
|
return false;
|
|
}
|
|
} while (dp != nullptr);
|
|
}
|
|
else
|
|
{
|
|
int case_opt = (aflags & AF_CASE) ? PCRE2_CASELESS : 0;
|
|
do
|
|
{
|
|
PCRE2_SIZE erroffset;
|
|
int errcode;
|
|
UTF8 *cp = parse_to(&dp, ',', EV_STRIP_CURLY);
|
|
pcre2_code *re;
|
|
if (!alarm_clock.alarmed)
|
|
{
|
|
re = pcre2_compile_8(cp, PCRE2_ZERO_TERMINATED, PCRE2_UTF|case_opt,
|
|
&errcode, &erroffset, nullptr);
|
|
if (re != nullptr)
|
|
{
|
|
// Create match data block for storing results
|
|
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr);
|
|
if (match_data != nullptr)
|
|
{
|
|
int matches = pcre2_match(re, msg, PCRE2_ZERO_TERMINATED, 0, 0,
|
|
match_data, nullptr);
|
|
|
|
if (0 <= matches)
|
|
{
|
|
pcre2_match_data_free(match_data);
|
|
pcre2_code_free(re);
|
|
return false;
|
|
}
|
|
pcre2_match_data_free(match_data);
|
|
}
|
|
pcre2_code_free(re);
|
|
}
|
|
}
|
|
} while (dp != nullptr);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static UTF8 *make_prefix(dbref object, dbref player, int prefix, const UTF8 *dflt)
|
|
{
|
|
int aflags;
|
|
dbref aowner;
|
|
UTF8 *buf, *nbuf, *cp;
|
|
|
|
buf = atr_pget(object, prefix, &aowner, &aflags);
|
|
if (!*buf)
|
|
{
|
|
cp = buf;
|
|
if (nullptr == dflt)
|
|
{
|
|
safe_str(T("From "), buf, &cp);
|
|
if (Good_obj(object))
|
|
{
|
|
safe_str(Moniker(object), buf, &cp);
|
|
}
|
|
else
|
|
{
|
|
safe_str(Moniker(player), buf, &cp);
|
|
}
|
|
safe_chr(',', buf, &cp);
|
|
}
|
|
else
|
|
{
|
|
safe_str(dflt, buf, &cp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((aflags & AF_NOEVAL) || NoEval(object))
|
|
{
|
|
cp = buf + strlen((const char *)buf);
|
|
}
|
|
else
|
|
{
|
|
reg_ref **preserve = nullptr;
|
|
preserve = PushRegisters(MAX_GLOBAL_REGS);
|
|
save_global_regs(preserve);
|
|
|
|
nbuf = cp = alloc_lbuf("add_prefix");
|
|
mux_exec(buf, LBUF_SIZE-1, nbuf, &cp, object, player, player,
|
|
AttrTrace(aflags, EV_FIGNORE|EV_EVAL|EV_TOP),
|
|
nullptr, 0);
|
|
free_lbuf(buf);
|
|
|
|
restore_global_regs(preserve);
|
|
PopRegisters(preserve, MAX_GLOBAL_REGS);
|
|
|
|
buf = nbuf;
|
|
}
|
|
}
|
|
if (cp != buf)
|
|
{
|
|
safe_chr(' ', buf, &cp);
|
|
}
|
|
*cp = '\0';
|
|
return buf;
|
|
}
|
|
|
|
/* Do HTML escaping, converting < to <, etc. 'dest' needs to be
|
|
* allocated & freed by the caller.
|
|
*
|
|
* If you're using this to append to a string, you can pass in the
|
|
* safe_{str|chr} (UTF8 **) so we can just do the append directly,
|
|
* saving you an alloc_lbuf()...free_lbuf(). If you want us to append
|
|
* from the start of 'dest', just pass in a 0 for 'destp'.
|
|
*
|
|
* Returns 0 if the copy succeeded, 1 if it failed.
|
|
*/
|
|
bool html_escape(const UTF8 *src, UTF8 *dest, UTF8 **destp)
|
|
{
|
|
const UTF8 *msg_orig;
|
|
bool ret = false;
|
|
|
|
if (destp == 0)
|
|
{
|
|
UTF8 *temp = dest;
|
|
destp = &temp;
|
|
}
|
|
|
|
for (msg_orig = src; msg_orig && *msg_orig && !ret; msg_orig++)
|
|
{
|
|
UTF8 *p = *destp;
|
|
switch (*msg_orig)
|
|
{
|
|
case '<':
|
|
safe_str(T("<"), dest, destp);
|
|
break;
|
|
|
|
case '>':
|
|
safe_str(T(">"), dest, destp);
|
|
break;
|
|
|
|
case '&':
|
|
safe_str(T("&"), dest, destp);
|
|
break;
|
|
|
|
case '\"':
|
|
safe_str(T("""), dest, destp);
|
|
break;
|
|
|
|
default:
|
|
safe_chr(*msg_orig, dest, destp);
|
|
break;
|
|
}
|
|
|
|
// For <>&\, this may cause an extra loop around before it figures out that we are
|
|
// out of buffer, but no harm is done in this, and the common case is a single character.
|
|
//
|
|
if (p == *destp)
|
|
{
|
|
ret = true;
|
|
}
|
|
}
|
|
**destp = 0;
|
|
return ret;
|
|
}
|
|
|
|
void notify_check(dbref target, dbref sender, const UTF8 *msg, int key)
|
|
{
|
|
// If speaker is invalid or message is empty, just exit.
|
|
//
|
|
if ( !Good_obj(target)
|
|
|| !msg
|
|
|| !*msg)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef WOD_REALMS
|
|
if ((key & MSG_OOC) == 0)
|
|
{
|
|
if ((key & MSG_SAYPOSE) != 0)
|
|
{
|
|
if (REALM_DO_HIDDEN_FROM_YOU == DoThingToThingVisibility(target, sender, ACTION_IS_TALKING))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (REALM_DO_HIDDEN_FROM_YOU == DoThingToThingVisibility(target, sender, ACTION_IS_MOVING))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif // WOD_REALMS
|
|
|
|
// Enforce a recursion limit
|
|
//
|
|
mudstate.ntfy_nest_lev++;
|
|
if (mudconf.ntfy_nest_lim <= mudstate.ntfy_nest_lev)
|
|
{
|
|
mudstate.ntfy_nest_lev--;
|
|
return;
|
|
}
|
|
|
|
// msg_ns: NOSPOOF prefix + msg. All strings are PUA-encoded UTF-8.
|
|
//
|
|
LBuf msg_ns = LBuf_Src("notify_check.msg_ns");
|
|
UTF8 *bp_ns = msg_ns;
|
|
LBuf msgFinal = LBuf_Src("notify_check.final");
|
|
UTF8 *bp_final;
|
|
UTF8 *tp;
|
|
UTF8 *prefix;
|
|
dbref aowner, recip, obj;
|
|
int i, nargs, aflags;
|
|
FWDLIST *fp;
|
|
|
|
// If we want NOSPOOF output, generate it. It is only needed if we are
|
|
// sending the message to the target object.
|
|
//
|
|
if (key & MSG_ME)
|
|
{
|
|
if ( Nospoof(target)
|
|
&& target != sender
|
|
&& target != mudstate.curr_enactor
|
|
&& target != mudstate.curr_executor)
|
|
{
|
|
if ( mudconf.terse_nospoof
|
|
&& (key & MSG_SAYPOSE))
|
|
{
|
|
// Terse: just the dbref for say/pose.
|
|
//
|
|
safe_chr('[', msg_ns, &bp_ns);
|
|
safe_chr('#', msg_ns, &bp_ns);
|
|
safe_ltoa(sender, msg_ns, &bp_ns);
|
|
safe_str(T("] "), msg_ns, &bp_ns);
|
|
}
|
|
else
|
|
{
|
|
safe_chr('[', msg_ns, &bp_ns);
|
|
safe_str(Moniker(sender), msg_ns, &bp_ns);
|
|
safe_chr('(', msg_ns, &bp_ns);
|
|
safe_chr('#', msg_ns, &bp_ns);
|
|
safe_ltoa(sender, msg_ns, &bp_ns);
|
|
safe_chr(')', msg_ns, &bp_ns);
|
|
|
|
if (sender != Owner(sender))
|
|
{
|
|
safe_chr('{', msg_ns, &bp_ns);
|
|
safe_str(Moniker(Owner(sender)), msg_ns, &bp_ns);
|
|
safe_chr('}', msg_ns, &bp_ns);
|
|
}
|
|
|
|
if (sender != mudstate.curr_enactor)
|
|
{
|
|
safe_str(T("<-("), msg_ns, &bp_ns);
|
|
safe_chr('#', msg_ns, &bp_ns);
|
|
safe_ltoa(mudstate.curr_enactor, msg_ns, &bp_ns);
|
|
safe_chr(')', msg_ns, &bp_ns);
|
|
}
|
|
|
|
switch (DecodeMsgSource(key))
|
|
{
|
|
case MSG_SRC_COMSYS:
|
|
safe_str(T(",comsys"), msg_ns, &bp_ns);
|
|
break;
|
|
|
|
case MSG_SRC_KILL:
|
|
safe_str(T(",kill"), msg_ns, &bp_ns);
|
|
break;
|
|
|
|
case MSG_SRC_GIVE:
|
|
safe_str(T(",give"), msg_ns, &bp_ns);
|
|
break;
|
|
|
|
case MSG_SRC_PAGE:
|
|
safe_str(T(",page"), msg_ns, &bp_ns);
|
|
break;
|
|
|
|
default:
|
|
if (key & MSG_SAYPOSE)
|
|
{
|
|
safe_str(T(",saypose"), msg_ns, &bp_ns);
|
|
}
|
|
break;
|
|
}
|
|
|
|
safe_str(T("] "), msg_ns, &bp_ns);
|
|
}
|
|
}
|
|
}
|
|
safe_str(msg, msg_ns, &bp_ns);
|
|
*bp_ns = '\0';
|
|
|
|
// msg contains the raw message, msg_ns contains the NOSPOOFed msg.
|
|
//
|
|
bool check_listens = !Halted(target);
|
|
switch (Typeof(target))
|
|
{
|
|
case TYPE_PLAYER:
|
|
if (key & MSG_ME)
|
|
{
|
|
if (key & MSG_HTML)
|
|
{
|
|
raw_notify_html(target, msg_ns);
|
|
}
|
|
else if (Html(target))
|
|
{
|
|
bp_final = msgFinal;
|
|
html_escape(msg_ns, msgFinal, &bp_final);
|
|
raw_notify(target, msgFinal);
|
|
}
|
|
else
|
|
{
|
|
raw_notify(target, msg_ns);
|
|
}
|
|
}
|
|
if (!mudconf.player_listen)
|
|
{
|
|
check_listens = false;
|
|
}
|
|
|
|
// FALLTHROUGH
|
|
|
|
case TYPE_THING:
|
|
case TYPE_ROOM:
|
|
|
|
// If we're in a pipe, objects can receive raw_notify if
|
|
// they're not a player. (players were already notified
|
|
// above.
|
|
//
|
|
if ( mudstate.inpipe
|
|
&& !isPlayer(target))
|
|
{
|
|
raw_notify(target, msg_ns);
|
|
}
|
|
|
|
// Forward puppet message if it is for me.
|
|
//
|
|
bool has_neighbors = Has_location(target);
|
|
dbref targetloc = where_is(target);
|
|
bool is_audible = Audible(target);
|
|
|
|
if ( (key & MSG_ME)
|
|
&& Puppet(target)
|
|
&& (target != Owner(target))
|
|
&& ( (key & MSG_PUP_ALWAYS)
|
|
|| ( targetloc != Location(Owner(target))
|
|
&& targetloc != Owner(target))))
|
|
{
|
|
bp_final = msgFinal;
|
|
safe_str(Moniker(target), msgFinal, &bp_final);
|
|
safe_str(T("> "), msgFinal, &bp_final);
|
|
safe_str(msg_ns, msgFinal, &bp_final);
|
|
*bp_final = '\0';
|
|
raw_notify(Owner(target), msgFinal);
|
|
}
|
|
|
|
// Check for @Listen match if it will be useful.
|
|
// Strip PUA color from msg to get plain text for matching.
|
|
//
|
|
LBuf msgPlain = LBuf_Src("notify_check.plain");
|
|
co_strip_color(reinterpret_cast<unsigned char *>(msgPlain.get()),
|
|
reinterpret_cast<const unsigned char *>(msg),
|
|
mux_strlen(msg));
|
|
|
|
// Normalized copy for pattern matching — ASCII quotes only.
|
|
// msgPlain retains fancy quotes for %0 capture and display.
|
|
//
|
|
LBuf msgNorm = LBuf_Src("notify_check.norm");
|
|
mux_strncpy(msgNorm, msgPlain, LBUF_SIZE - 1);
|
|
strip_fancy_quotes(msgNorm);
|
|
|
|
bool pass_listen = false;
|
|
UTF8 *args[NUM_ENV_VARS];
|
|
nargs = 0;
|
|
if ( check_listens
|
|
&& (key & (MSG_ME | MSG_INV_L))
|
|
&& H_Listen(target))
|
|
{
|
|
{
|
|
LBuf tp = LBuf_Adopt(atr_get("notify_check.790", target, A_LISTEN, &aowner, &aflags));
|
|
strip_fancy_quotes(tp);
|
|
if (*tp.get() && wild(tp, msgNorm, args, NUM_ENV_VARS))
|
|
{
|
|
// Re-capture from original text so %0 has fancy quotes.
|
|
//
|
|
for (int j = 0; j < NUM_ENV_VARS; j++)
|
|
{
|
|
if (args[j]) { free_lbuf(args[j]); args[j] = nullptr; }
|
|
}
|
|
wild(tp, msgPlain, args, NUM_ENV_VARS);
|
|
for (nargs = NUM_ENV_VARS; nargs && (!args[nargs - 1] || !(*args[nargs - 1])); nargs--)
|
|
{
|
|
; // Nothing
|
|
}
|
|
pass_listen = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we matched the @listen or are monitoring, check the
|
|
// USE lock.
|
|
//
|
|
bool pass_uselock = false;
|
|
if ( (key & MSG_ME)
|
|
&& check_listens
|
|
&& ( pass_listen
|
|
|| Monitor(target)))
|
|
{
|
|
pass_uselock = could_doit(sender, target, A_LUSE);
|
|
}
|
|
|
|
// Process AxHEAR if we pass LISTEN, USElock and it's for me.
|
|
//
|
|
if ( (key & MSG_ME)
|
|
&& pass_listen
|
|
&& pass_uselock
|
|
&& mudstate.nHearNest <= 2)
|
|
{
|
|
mudstate.nHearNest++;
|
|
if (sender != target)
|
|
{
|
|
did_it( sender, target, 0, nullptr, 0, nullptr, A_AHEAR, 0,
|
|
(const UTF8 **)args, nargs);
|
|
}
|
|
else
|
|
{
|
|
did_it( sender, target, 0, nullptr, 0, nullptr, A_AMHEAR, 0,
|
|
(const UTF8 **)args, nargs);
|
|
}
|
|
did_it( sender, target, 0, nullptr, 0, nullptr, A_AAHEAR, 0,
|
|
(const UTF8 **)args, nargs);
|
|
mudstate.nHearNest--;
|
|
}
|
|
|
|
// Get rid of match arguments. We don't need them anymore.
|
|
//
|
|
if (pass_listen)
|
|
{
|
|
for (i = 0; i < nargs; i++)
|
|
{
|
|
if (args[i] != nullptr)
|
|
{
|
|
free_lbuf(args[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process ^-listens if for me, MONITOR, and we pass USElock.
|
|
//
|
|
if ( (key & MSG_ME)
|
|
&& pass_uselock
|
|
&& sender != target
|
|
&& Monitor(target))
|
|
{
|
|
atr_match(target, sender, AMATCH_LISTEN, msgPlain, msgPlain, false);
|
|
}
|
|
|
|
// Deliver message to forwardlist members.
|
|
//
|
|
if ( (key & MSG_FWDLIST)
|
|
&& is_audible
|
|
&& check_filter(target, sender, A_FILTER, msgPlain))
|
|
{
|
|
fp = fwdlist_get(target);
|
|
if (nullptr != fp)
|
|
{
|
|
prefix = make_prefix(target, sender, A_PREFIX, nullptr);
|
|
bp_final = msgFinal;
|
|
safe_str(prefix, msgFinal, &bp_final);
|
|
free_lbuf(prefix);
|
|
safe_str(msg, msgFinal, &bp_final);
|
|
*bp_final = '\0';
|
|
|
|
for (i = 0; i < fp->count; i++)
|
|
{
|
|
recip = fp->data[i];
|
|
if ( !Good_obj(recip)
|
|
|| recip == target)
|
|
{
|
|
continue;
|
|
}
|
|
notify_check(recip, sender, msgFinal,
|
|
MSG_ME | MSG_F_UP | MSG_F_CONTENTS | MSG_S_INSIDE | (key & (MSG_SRC_MASK | MSG_SAYPOSE | MSG_OOC)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deliver message through audible exits.
|
|
//
|
|
if (key & MSG_INV_EXITS)
|
|
{
|
|
DOLIST(obj, Exits(target))
|
|
{
|
|
recip = Location(obj);
|
|
if ( Audible(obj)
|
|
&& ( recip != target
|
|
&& check_filter(obj, sender, A_FILTER, msgPlain)))
|
|
{
|
|
prefix = make_prefix(obj, target, A_PREFIX, T("From a distance,"));
|
|
bp_final = msgFinal;
|
|
safe_str(prefix, msgFinal, &bp_final);
|
|
free_lbuf(prefix);
|
|
safe_str(msg, msgFinal, &bp_final);
|
|
*bp_final = '\0';
|
|
|
|
notify_check(recip, sender, msgFinal,
|
|
MSG_ME | MSG_F_UP | MSG_F_CONTENTS | MSG_S_INSIDE | (key & (MSG_SRC_MASK | MSG_SAYPOSE | MSG_OOC)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deliver message through neighboring audible exits.
|
|
//
|
|
if ( has_neighbors
|
|
&& ( (key & MSG_NBR_EXITS)
|
|
|| ( (key & MSG_NBR_EXITS_A)
|
|
&& is_audible)))
|
|
{
|
|
// If from inside, we have to add the prefix string of
|
|
// the container.
|
|
//
|
|
if (key & MSG_S_INSIDE)
|
|
{
|
|
prefix = make_prefix(target, sender, A_PREFIX, nullptr);
|
|
bp_final = msgFinal;
|
|
safe_str(prefix, msgFinal, &bp_final);
|
|
free_lbuf(prefix);
|
|
safe_str(msg, msgFinal, &bp_final);
|
|
*bp_final = '\0';
|
|
}
|
|
else
|
|
{
|
|
mux_strncpy(msgFinal, msg, LBUF_SIZE - 1);
|
|
}
|
|
|
|
LBuf msgPrefixed2 = LBuf_Src("notify_check.pfx2");
|
|
UTF8 *bp_pfx2;
|
|
|
|
DOLIST(obj, Exits(Location(target)))
|
|
{
|
|
recip = Location(obj);
|
|
if ( Good_obj(recip)
|
|
&& Audible(obj)
|
|
&& recip != targetloc
|
|
&& recip != target
|
|
&& check_filter(obj, sender, A_FILTER, msgPlain))
|
|
{
|
|
prefix = make_prefix(obj, target, A_PREFIX, T("From a distance,"));
|
|
bp_pfx2 = msgPrefixed2;
|
|
safe_str(prefix, msgPrefixed2, &bp_pfx2);
|
|
free_lbuf(prefix);
|
|
safe_str(msgFinal, msgPrefixed2, &bp_pfx2);
|
|
*bp_pfx2 = '\0';
|
|
|
|
notify_check(recip, sender, msgPrefixed2,
|
|
MSG_ME | MSG_F_UP | MSG_F_CONTENTS | MSG_S_INSIDE | (key & (MSG_SRC_MASK | MSG_SAYPOSE | MSG_OOC)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deliver message to contents.
|
|
//
|
|
if ( ( (key & MSG_INV)
|
|
|| ( (key & MSG_INV_L)
|
|
&& pass_listen))
|
|
&& check_filter(target, sender, A_INFILTER, msgPlain))
|
|
{
|
|
// Don't prefix the message if we were given the MSG_NOPREFIX key.
|
|
//
|
|
if (key & MSG_S_OUTSIDE)
|
|
{
|
|
prefix = make_prefix(target, sender, A_INPREFIX, T(""));
|
|
bp_final = msgFinal;
|
|
safe_str(prefix, msgFinal, &bp_final);
|
|
free_lbuf(prefix);
|
|
safe_str(msg, msgFinal, &bp_final);
|
|
*bp_final = '\0';
|
|
}
|
|
else
|
|
{
|
|
mux_strncpy(msgFinal, msg, LBUF_SIZE - 1);
|
|
}
|
|
|
|
DOLIST(obj, Contents(target))
|
|
{
|
|
if ( obj != target
|
|
&& !(isPlayer(obj) && Alone(target)))
|
|
{
|
|
notify_check(obj, sender, msgFinal,
|
|
MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE | (key & (MSG_HTML | MSG_SRC_MASK | MSG_SAYPOSE | MSG_OOC)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deliver message to neighbors.
|
|
//
|
|
if ( has_neighbors
|
|
&& ( (key & MSG_NBR)
|
|
|| ( (key & MSG_NBR_A)
|
|
&& is_audible
|
|
&& check_filter(target, sender, A_FILTER, msgPlain))))
|
|
{
|
|
if (key & MSG_S_INSIDE)
|
|
{
|
|
prefix = make_prefix(target, sender, A_PREFIX, T(""));
|
|
bp_final = msgFinal;
|
|
safe_str(prefix, msgFinal, &bp_final);
|
|
free_lbuf(prefix);
|
|
safe_str(msg, msgFinal, &bp_final);
|
|
*bp_final = '\0';
|
|
}
|
|
else
|
|
{
|
|
mux_strncpy(msgFinal, msg, LBUF_SIZE - 1);
|
|
}
|
|
|
|
DOLIST(obj, Contents(targetloc))
|
|
{
|
|
if ( obj != target
|
|
&& obj != targetloc
|
|
&& !(isPlayer(obj) && Alone(targetloc)))
|
|
{
|
|
notify_check(obj, sender, msgFinal,
|
|
MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE | (key & (MSG_SRC_MASK | MSG_SAYPOSE | MSG_OOC)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deliver message to container.
|
|
//
|
|
if ( has_neighbors
|
|
&& ( (key & MSG_LOC)
|
|
|| ( (key & MSG_LOC_A)
|
|
&& is_audible
|
|
&& check_filter(target, sender, A_FILTER, msgPlain))))
|
|
{
|
|
if (key & MSG_S_INSIDE)
|
|
{
|
|
prefix = make_prefix(target, sender, A_PREFIX, nullptr);
|
|
bp_final = msgFinal;
|
|
safe_str(prefix, msgFinal, &bp_final);
|
|
free_lbuf(prefix);
|
|
safe_str(msg, msgFinal, &bp_final);
|
|
*bp_final = '\0';
|
|
}
|
|
else
|
|
{
|
|
mux_strncpy(msgFinal, msg, LBUF_SIZE - 1);
|
|
}
|
|
|
|
notify_check(targetloc, sender, msgFinal,
|
|
MSG_ME | MSG_F_UP | MSG_S_INSIDE | (key & (MSG_SRC_MASK | MSG_SAYPOSE | MSG_OOC)));
|
|
}
|
|
}
|
|
mudstate.ntfy_nest_lev--;
|
|
}
|
|
|
|
void notify_except(dbref loc, dbref player, dbref exception, const UTF8 *msg, int key)
|
|
{
|
|
dbref first;
|
|
|
|
if (loc != exception)
|
|
{
|
|
notify_check(loc, player, msg, MSG_ME_ALL | MSG_F_UP | MSG_S_INSIDE | MSG_NBR_EXITS_A | key);
|
|
}
|
|
DOLIST(first, Contents(loc))
|
|
{
|
|
if ( first != exception
|
|
&& !(isPlayer(first) && Alone(loc)))
|
|
{
|
|
notify_check(first, player, msg, MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE | key);
|
|
}
|
|
}
|
|
}
|
|
|
|
void notify_except2(dbref loc, dbref player, dbref exc1, dbref exc2, const UTF8 *msg)
|
|
{
|
|
dbref first;
|
|
|
|
if ( loc != exc1
|
|
&& loc != exc2)
|
|
{
|
|
notify_check(loc, player, msg, MSG_ME_ALL | MSG_F_UP | MSG_S_INSIDE | MSG_NBR_EXITS_A);
|
|
}
|
|
DOLIST(first, Contents(loc))
|
|
{
|
|
if ( first != exc1
|
|
&& first != exc2
|
|
&& !(isPlayer(first) && Alone(loc)))
|
|
{
|
|
notify_check(first, player, msg, MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void notify_except_N(dbref loc, dbref player, dbref aExclude[], int nExclude, const UTF8 *msg, int key)
|
|
{
|
|
dbref first;
|
|
|
|
// Notify loc itself unless it's in the exclude list.
|
|
//
|
|
bool bExcludeLoc = false;
|
|
int i;
|
|
for (i = 0; i < nExclude; i++)
|
|
{
|
|
if (loc == aExclude[i])
|
|
{
|
|
bExcludeLoc = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bExcludeLoc)
|
|
{
|
|
notify_check(loc, player, msg, MSG_ME_ALL | MSG_F_UP | MSG_S_INSIDE | MSG_NBR_EXITS_A | key);
|
|
}
|
|
|
|
DOLIST(first, Contents(loc))
|
|
{
|
|
if (!(isPlayer(first) && Alone(loc)))
|
|
{
|
|
bool bExclude = false;
|
|
for (i = 0; i < nExclude; i++)
|
|
{
|
|
if (first == aExclude[i])
|
|
{
|
|
bExclude = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bExclude)
|
|
{
|
|
notify_check(first, player, msg, MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE | key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* Reporting of CPU information.
|
|
*/
|
|
|
|
static void report_timecheck
|
|
(
|
|
dbref player,
|
|
bool yes_screen,
|
|
bool yes_log,
|
|
bool yes_clear
|
|
)
|
|
{
|
|
int thing, obj_counted;
|
|
CLinearTimeDelta ltdPeriod, ltdTotal;
|
|
CLinearTimeAbsolute ltaNow;
|
|
ltaNow.GetUTC();
|
|
ltdPeriod = ltaNow - mudstate.cpu_count_from;
|
|
|
|
if ( yes_log
|
|
&& (LOG_TIMEUSE & mudconf.log_options))
|
|
{
|
|
start_log(T("OBJ"), T("CPU"));
|
|
log_name(player);
|
|
log_text(T(" checks object time use over "));
|
|
log_number(ltdPeriod.ReturnSeconds());
|
|
log_text(T(" seconds" ENDLINE));
|
|
}
|
|
else
|
|
{
|
|
yes_log = false;
|
|
STARTLOG(LOG_ALWAYS, "WIZ", "TIMECHECK");
|
|
log_name(player);
|
|
log_text(T(" checks object time use over "));
|
|
log_number(ltdPeriod.ReturnSeconds());
|
|
log_text(T(" seconds"));
|
|
ENDLOG;
|
|
}
|
|
|
|
obj_counted = 0;
|
|
ltdTotal.Set100ns(0);
|
|
|
|
// Step through the db. Care only about the ones that are nonzero.
|
|
//
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
CLinearTimeDelta <d = db[thing].cpu_time_used;
|
|
if (ltd.Return100ns())
|
|
{
|
|
ltdTotal += ltd;
|
|
long used_msecs = ltd.ReturnMilliseconds();
|
|
obj_counted++;
|
|
if (yes_log)
|
|
{
|
|
Log.tinyprintf(T("#%d\t%ld" ENDLINE), thing, used_msecs);
|
|
}
|
|
if (yes_screen)
|
|
{
|
|
raw_notify(player, tprintf(T("#%d\t%ld"), thing, used_msecs));
|
|
}
|
|
if (yes_clear)
|
|
{
|
|
ltd.Set100ns(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
long lTotal = ltdTotal.ReturnMilliseconds();
|
|
long lPeriod = ltdPeriod.ReturnSeconds();
|
|
|
|
if (yes_screen)
|
|
{
|
|
raw_notify(player,
|
|
tprintf(T("Counted %d objects using %ld msecs over %d seconds."),
|
|
obj_counted, lTotal, lPeriod));
|
|
}
|
|
|
|
if (yes_log)
|
|
{
|
|
Log.tinyprintf(T("Counted %d objects using %ld msecs over %d seconds."),
|
|
obj_counted, lTotal, lPeriod);
|
|
end_log();
|
|
}
|
|
|
|
if (yes_clear)
|
|
{
|
|
mudstate.cpu_count_from = ltaNow;
|
|
}
|
|
}
|
|
|
|
void do_timecheck(dbref executor, dbref caller, dbref enactor, int eval, int key)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
|
|
bool yes_screen, yes_log, yes_clear;
|
|
|
|
yes_screen = yes_log = yes_clear = false;
|
|
|
|
if (key == 0)
|
|
{
|
|
// No switches, default to printing to screen and clearing counters.
|
|
//
|
|
yes_screen = true;
|
|
yes_clear = true;
|
|
}
|
|
else
|
|
{
|
|
if (key & TIMECHK_RESET)
|
|
{
|
|
yes_clear = true;
|
|
}
|
|
if (key & TIMECHK_SCREEN)
|
|
{
|
|
yes_screen = true;
|
|
}
|
|
if (key & TIMECHK_LOG)
|
|
{
|
|
yes_log = true;
|
|
}
|
|
}
|
|
report_timecheck(executor, yes_screen, yes_log, yes_clear);
|
|
}
|
|
|
|
void do_shutdown
|
|
(
|
|
dbref executor,
|
|
dbref caller,
|
|
dbref enactor,
|
|
int eval,
|
|
int key,
|
|
UTF8 *message,
|
|
const UTF8 *cargs[],
|
|
int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (!Can_SiteAdmin(executor))
|
|
{
|
|
notify(executor, NOPERM_MESSAGE);
|
|
return;
|
|
}
|
|
|
|
raw_broadcast(0, T("GAME: Shutdown by %s"), Moniker(Owner(executor)));
|
|
STARTLOG(LOG_ALWAYS, "WIZ", "SHTDN");
|
|
log_text(T("Shutdown by "));
|
|
log_name(executor);
|
|
ENDLOG;
|
|
|
|
STARTLOG(LOG_ALWAYS, "WIZ", "SHTDN");
|
|
log_text(T("Shutdown status: "));
|
|
log_text(message);
|
|
ENDLOG;
|
|
|
|
int fd;
|
|
if (mux_open(&fd, mudconf.status_file, O_RDWR|O_CREAT|O_TRUNC|O_BINARY))
|
|
{
|
|
mux_write(fd, message, static_cast<unsigned int>(strlen(reinterpret_cast<char *>(message))));
|
|
mux_write(fd, ENDLINE, sizeof(ENDLINE)-1);
|
|
mux_close(fd);
|
|
}
|
|
|
|
// Do we perform a normal or an emergency shutdown? Normal
|
|
// shutdown is handled by exiting the GANL main loop,
|
|
// emergency shutdown is done here.
|
|
//
|
|
if (key & SHUTDN_PANIC)
|
|
{
|
|
// Close down the network interface.
|
|
//
|
|
emergency_shutdown();
|
|
|
|
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();
|
|
|
|
// Close the attribute text db and dump the header db.
|
|
//
|
|
pcache_sync();
|
|
SYNC;
|
|
CLOSE;
|
|
|
|
STARTLOG(LOG_ALWAYS, "DMP", "PANIC");
|
|
log_text(T("Panic dump: "));
|
|
log_text(mudconf.crashdb);
|
|
ENDLOG;
|
|
dump_database_internal(DUMP_I_PANIC);
|
|
STARTLOG(LOG_ALWAYS, "DMP", "DONE");
|
|
log_text(T("Panic dump complete: "));
|
|
log_text(mudconf.crashdb);
|
|
ENDLOG;
|
|
}
|
|
|
|
// Request normal shutdown via COM call to the driver.
|
|
//
|
|
request_shutdown();
|
|
}
|
|
|
|
// There are several types of dumps:
|
|
//
|
|
// Type 0 - Normal mudstate.dumping controlled
|
|
// Type 1 - Panic uncontrolled but only one of these happening at a time.
|
|
// Type 2 - Restart mudstate.dumping controlled.
|
|
// Type 3 - FLAT mudstate.dumping controlled.
|
|
// Type 4 - signal uncontrolled and if we fault twice, the game ends --
|
|
// see check_panicking.
|
|
//
|
|
// When changing this function and to keep forking dumps safe, keep in mind
|
|
// that the following combinations can be occuring at the same time. Don't
|
|
// touch each other's files.
|
|
//
|
|
// Type 0 and 2 are allowed to touch each other's files. Type 1 and 4 should not
|
|
// touch files used in Type 0 or Type 2.
|
|
//
|
|
typedef struct
|
|
{
|
|
UTF8 **ppszOutputBase;
|
|
const UTF8 *szOutputSuffix;
|
|
bool bUseTemporary;
|
|
int fType;
|
|
const UTF8 *pszErrorMessage;
|
|
} DUMP_PROCEDURE;
|
|
|
|
static DUMP_PROCEDURE DumpProcedures[NUM_DUMP_TYPES] =
|
|
{
|
|
{ nullptr, T(""), false, 0, T("") }, // 0 -- Handled specially.
|
|
{ &mudconf.crashdb, T(""), false, UNLOAD_VERSION | UNLOAD_FLAGS, T("Opening crash file") }, // 1
|
|
{ &mudconf.indb, T(""), true, OUTPUT_VERSION | OUTPUT_FLAGS, T("Opening input file") }, // 2
|
|
{ &mudconf.indb, T(".FLAT"), false, UNLOAD_VERSION | UNLOAD_FLAGS, T("Opening flatfile") }, // 3
|
|
{ &mudconf.indb, T(".SIG"), false, UNLOAD_VERSION | UNLOAD_FLAGS, T("Opening signalled flatfile")} // 4
|
|
};
|
|
|
|
void dump_database_internal(int dump_type)
|
|
{
|
|
UTF8 tmpfile[SIZEOF_PATHNAME+32];
|
|
UTF8 outfn[SIZEOF_PATHNAME+32];
|
|
UTF8 prevfile[SIZEOF_PATHNAME+32];
|
|
FILE *f;
|
|
|
|
if ( dump_type < 0
|
|
|| NUM_DUMP_TYPES <= dump_type)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bPotentialConflicts = false;
|
|
#if defined(HAVE_WORKING_FORK)
|
|
// If we are already dumping for some reason, and suddenly get a type 1 or
|
|
// type 4 dump, basically don't touch mail and comsys files. The other
|
|
// dump will take care of them as well as can be expected for now, and if
|
|
// we try to, we'll just step on them.
|
|
//
|
|
if ( mudstate.dumping
|
|
&& ( dump_type == DUMP_I_PANIC
|
|
|| dump_type == DUMP_I_SIGNAL))
|
|
{
|
|
bPotentialConflicts = true;
|
|
}
|
|
#endif // HAVE_WORKING_FORK
|
|
|
|
// Call the local dump function only if another dump is not already
|
|
// in progress.
|
|
//
|
|
local_dump_database(dump_type);
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
while (nullptr != p)
|
|
{
|
|
p->pSink->dump_database(dump_type);
|
|
p = p->pNext;
|
|
}
|
|
|
|
if (0 < dump_type)
|
|
{
|
|
// With SQLite write-through, the database is always durable.
|
|
// Only write a flatfile for DUMP_I_FLAT (explicit export).
|
|
//
|
|
if ( dump_type != DUMP_I_FLAT
|
|
&& dump_type != DUMP_I_SIGNAL)
|
|
{
|
|
STARTLOG(LOG_DBSAVES, "DMP", "SQLT");
|
|
log_text(T("SQLite write-through is authoritative; skipping flatfile write."));
|
|
ENDLOG;
|
|
}
|
|
else
|
|
{
|
|
DUMP_PROCEDURE *dp = &DumpProcedures[dump_type];
|
|
bool bOpen;
|
|
|
|
mux_sprintf(outfn, sizeof(outfn), T("%s%s"), *(dp->ppszOutputBase), dp->szOutputSuffix);
|
|
if (dp->bUseTemporary)
|
|
{
|
|
mux_sprintf(tmpfile, sizeof(tmpfile), T("%s.#%d#"), outfn, mudstate.epoch);
|
|
RemoveFile(tmpfile);
|
|
bOpen = mux_fopen(&f, tmpfile, T("wb"));
|
|
}
|
|
else
|
|
{
|
|
RemoveFile(outfn);
|
|
bOpen = mux_fopen(&f, outfn, T("wb"));
|
|
}
|
|
|
|
if (bOpen)
|
|
{
|
|
setvbuf(f, nullptr, _IOFBF, 16384);
|
|
db_write(f, F_MUX, dp->fType);
|
|
mux_fclose(f);
|
|
|
|
if (dp->bUseTemporary)
|
|
{
|
|
ReplaceFile(tmpfile, outfn);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_perror(T("DMP"), T("FAIL"), dp->pszErrorMessage, outfn);
|
|
}
|
|
}
|
|
|
|
if (!bPotentialConflicts)
|
|
{
|
|
}
|
|
return;
|
|
}
|
|
|
|
// With SQLite write-through, periodic and shutdown dumps don't need
|
|
// to write a flatfile. The WAL checkpoint (via SYNC/cache_sync)
|
|
// is handled by the caller.
|
|
//
|
|
STARTLOG(LOG_DBSAVES, "DMP", "SQLT");
|
|
log_text(T("SQLite checkpoint (no flatfile)."));
|
|
ENDLOG;
|
|
|
|
}
|
|
|
|
void dump_database(void)
|
|
{
|
|
UTF8 *buff;
|
|
|
|
mudstate.epoch++;
|
|
|
|
#if defined(HAVE_WORKING_FORK)
|
|
if (mudstate.dumping)
|
|
{
|
|
STARTLOG(LOG_DBSAVES, "DMP", "DUMP");
|
|
log_text(T("Waiting on previously-forked child before dumping... "));
|
|
ENDLOG;
|
|
|
|
while (mudstate.dumping)
|
|
{
|
|
// We have a forked dump in progress, so we will wait until the
|
|
// child exits.
|
|
//
|
|
alarm_clock.sleep(time_1s);
|
|
}
|
|
}
|
|
mudstate.dumping = true;
|
|
mudstate.dumped = 0;
|
|
#endif // HAVE_WORKING_FORK
|
|
buff = alloc_mbuf("dump_database");
|
|
mux_sprintf(buff, MBUF_SIZE, T("%s.#%d#"), mudconf.outdb, mudstate.epoch);
|
|
|
|
STARTLOG(LOG_DBSAVES, "DMP", "DUMP");
|
|
log_text(T("Dumping: "));
|
|
log_text(buff);
|
|
ENDLOG;
|
|
|
|
local_presync_database();
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
while (nullptr != p)
|
|
{
|
|
p->pSink->presync_database();
|
|
p = p->pNext;
|
|
}
|
|
|
|
pcache_sync();
|
|
|
|
dump_database_internal(DUMP_I_NORMAL);
|
|
SYNC;
|
|
|
|
STARTLOG(LOG_DBSAVES, "DMP", "DONE")
|
|
log_text(T("Dump complete: "));
|
|
log_text(buff);
|
|
ENDLOG;
|
|
free_mbuf(buff);
|
|
|
|
#if defined(HAVE_WORKING_FORK)
|
|
// This doesn't matter. We are about the stop the game. However,
|
|
// leave it in.
|
|
//
|
|
mudstate.dumping = false;
|
|
local_dump_complete_signal();
|
|
#endif // HAVE_WORKING_FORK
|
|
|
|
p = g_pServerEventsSinkListHead;
|
|
while (nullptr != p)
|
|
{
|
|
p->pSink->dump_complete_signal();
|
|
p = p->pNext;
|
|
}
|
|
}
|
|
|
|
void fork_and_dump(int key)
|
|
{
|
|
#if defined(HAVE_WORKING_FORK)
|
|
static volatile bool bRequestAccepted = false;
|
|
|
|
// fork_and_dump is never called with mudstate.dumping true, but we'll
|
|
// ensure that assertion now.
|
|
//
|
|
if ( bRequestAccepted
|
|
|| mudstate.dumping)
|
|
{
|
|
return;
|
|
}
|
|
bRequestAccepted = true;
|
|
#endif // HAVE_WORKING_FORK
|
|
|
|
// If no options were given, then it means DUMP_TEXT+DUMP_STRUCT.
|
|
//
|
|
if (key == 0)
|
|
{
|
|
key = DUMP_TEXT+DUMP_STRUCT;
|
|
}
|
|
|
|
if (*mudconf.dump_msg)
|
|
{
|
|
raw_broadcast(0, T("%s"), mudconf.dump_msg);
|
|
}
|
|
check_mail_expiration();
|
|
LBuf buff = LBuf_Src("fork_and_dump");
|
|
if (key & (DUMP_TEXT|DUMP_STRUCT))
|
|
{
|
|
STARTLOG(LOG_DBSAVES, "DMP", "CHKPT");
|
|
if (key & DUMP_TEXT)
|
|
{
|
|
log_text(T("SYNCing"));
|
|
if (key & DUMP_STRUCT)
|
|
{
|
|
log_text(T(" and "));
|
|
}
|
|
}
|
|
if (key & DUMP_STRUCT)
|
|
{
|
|
mudstate.epoch++;
|
|
mux_sprintf(buff, LBUF_SIZE, T("%s.#%d#"), mudconf.outdb, mudstate.epoch);
|
|
log_text(T("Checkpointing: "));
|
|
log_text(buff);
|
|
}
|
|
ENDLOG;
|
|
}
|
|
if (key & DUMP_FLATFILE)
|
|
{
|
|
STARTLOG(LOG_DBSAVES, "DMP", "FLAT");
|
|
log_text(T("Creating flatfile: "));
|
|
mux_sprintf(buff, LBUF_SIZE, T("%s.FLAT"), mudconf.outdb);
|
|
log_text(buff);
|
|
ENDLOG;
|
|
}
|
|
|
|
local_presync_database();
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
while (nullptr != p)
|
|
{
|
|
p->pSink->presync_database();
|
|
p = p->pNext;
|
|
}
|
|
|
|
pcache_sync();
|
|
SYNC;
|
|
|
|
#if defined(HAVE_WORKING_FORK)
|
|
mudstate.write_protect = true;
|
|
int child = 0;
|
|
bool bChildExists = false;
|
|
mudstate.dumping = true;
|
|
mudstate.dumped = 0;
|
|
bool bAttemptFork = mudconf.fork_dump;
|
|
if (!(key & DUMP_FLATFILE))
|
|
{
|
|
// With SQLite write-through, periodic dumps are just a WAL
|
|
// checkpoint — fast enough that forking is unnecessary.
|
|
//
|
|
bAttemptFork = false;
|
|
}
|
|
#if !defined(HAVE_PREAD) \
|
|
|| !defined(HAVE_PWRITE)
|
|
if (key & DUMP_FLATFILE)
|
|
{
|
|
// Don't attempt a fork()'ed @dump/flat without pread()/pwrite()
|
|
// support.
|
|
//
|
|
bAttemptFork = false;
|
|
}
|
|
#endif // !HAVE_PREAD !HAVE_PWRITE
|
|
#endif // HAVE_WORKING_FORK
|
|
|
|
if (key & (DUMP_STRUCT|DUMP_FLATFILE))
|
|
{
|
|
#if defined(HAVE_WORKING_FORK)
|
|
if (bAttemptFork)
|
|
{
|
|
child = fork();
|
|
}
|
|
if (child == 0)
|
|
{
|
|
// In the forked child the alarm thread does not exist, so the alarm
|
|
// can never fire here. Only reset the lock-free, async-signal-safe
|
|
// flag so the dump runs unabbreviated. Do NOT call
|
|
// alarm_clock.clear() — it locks a std::mutex that may have been
|
|
// inherited locked across fork(), deadlocking the dump child.
|
|
alarm_clock.alarmed.store(false);
|
|
#endif // HAVE_WORKING_FORK
|
|
|
|
if (key & DUMP_STRUCT)
|
|
{
|
|
dump_database_internal(DUMP_I_NORMAL);
|
|
}
|
|
if (key & DUMP_FLATFILE)
|
|
{
|
|
dump_database_internal(DUMP_I_FLAT);
|
|
}
|
|
#if defined(HAVE_WORKING_FORK)
|
|
if (bAttemptFork)
|
|
{
|
|
_exit(0);
|
|
}
|
|
}
|
|
else if (child < 0)
|
|
{
|
|
log_perror(T("DMP"), T("FORK"), nullptr, T("fork()"));
|
|
}
|
|
else
|
|
{
|
|
mudstate.dumper = child;
|
|
if (mudstate.dumper == mudstate.dumped)
|
|
{
|
|
// The child process executed and exited before fork() returned
|
|
// to the parent process. Without a process id, the parent's
|
|
// SIGCHLD handler could not be certain that the pid of the
|
|
// exiting process would match the pid of this child.
|
|
//
|
|
// At the this point, we can be sure, however, there's
|
|
// nothing much left to do.
|
|
//
|
|
// See SIGCHLD handler in bsd.cpp.
|
|
//
|
|
mudstate.dumper = 0;
|
|
mudstate.dumped = 0;
|
|
}
|
|
else
|
|
{
|
|
bChildExists = true;
|
|
}
|
|
}
|
|
#endif // HAVE_WORKING_FORK
|
|
}
|
|
|
|
#if defined(HAVE_WORKING_FORK)
|
|
mudstate.write_protect = false;
|
|
if (!bChildExists)
|
|
{
|
|
// We have the ability to fork children, but we are not configured to
|
|
// use it; or, we tried to fork a child and failed; or, we didn't
|
|
// need to dump the structure or a flatfile; or, the child has finished
|
|
// dumping already.
|
|
//
|
|
mudstate.dumper = 0;
|
|
mudstate.dumping = false;
|
|
local_dump_complete_signal();
|
|
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
|
|
while (nullptr != p)
|
|
{
|
|
p->pSink->dump_complete_signal();
|
|
p = p->pNext;
|
|
}
|
|
}
|
|
bRequestAccepted = false;
|
|
#endif // HAVE_WORKING_FORK
|
|
|
|
if (*mudconf.postdump_msg)
|
|
{
|
|
raw_broadcast(0, T("%s"), mudconf.postdump_msg);
|
|
}
|
|
}
|
|
|
|
#define LOAD_GAME_SUCCESS 0
|
|
#define LOAD_GAME_NO_INPUT_DB (-1)
|
|
#define LOAD_GAME_CANNOT_OPEN (-2)
|
|
#define LOAD_GAME_LOADING_PROBLEM (-3)
|
|
|
|
bool clear_sqlite_after_sync_failure(CSQLiteDB &sqldb)
|
|
{
|
|
if (!sqldb.Begin())
|
|
{
|
|
sqldb.Rollback();
|
|
if (!sqldb.Begin())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( !sqldb.ClearAttributes()
|
|
|| !sqldb.ClearObjectTable()
|
|
|| !sqldb.ClearAttrNames()
|
|
|| !sqldb.PutMeta("attr_next", A_USER_START)
|
|
|| !sqldb.PutMeta("db_top", 0)
|
|
|| !sqldb.PutMeta("record_players", 0)
|
|
|| !sqldb.Commit())
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int load_game(int ccPageFile)
|
|
{
|
|
FILE *f = nullptr;
|
|
UTF8 infile[SIZEOF_PATHNAME+8];
|
|
struct stat statbuf;
|
|
int db_format, db_version, db_flags;
|
|
|
|
mux_strncpy(infile, mudconf.indb, sizeof(infile)-1);
|
|
if (stat(reinterpret_cast<char *>(infile), &statbuf) != 0)
|
|
{
|
|
// Indicate that we couldn't load because the input db didn't
|
|
// exist.
|
|
//
|
|
return LOAD_GAME_NO_INPUT_DB;
|
|
}
|
|
|
|
if (!mux_fopen(&f, infile, T("rb")))
|
|
{
|
|
return LOAD_GAME_CANNOT_OPEN;
|
|
}
|
|
setvbuf(f, nullptr, _IOFBF, 16384);
|
|
|
|
// Ok, read it in.
|
|
//
|
|
STARTLOG(LOG_STARTUP, "INI", "LOAD")
|
|
log_text(T("Loading: "));
|
|
log_text(infile);
|
|
ENDLOG
|
|
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
|
|
// Flatfile import is authoritative for attribute values.
|
|
// Perform clear + import in one transaction so failures rollback.
|
|
//
|
|
if (!sqldb.Begin() || !sqldb.ClearAttributes())
|
|
{
|
|
sqldb.Rollback();
|
|
mux_fclose(f);
|
|
f = 0;
|
|
STARTLOG(LOG_ALWAYS, "INI", "FATAL")
|
|
log_text(T("Error clearing SQLite attributes before flatfile load."));
|
|
ENDLOG
|
|
return LOAD_GAME_LOADING_PROBLEM;
|
|
}
|
|
|
|
mudstate.bSQLiteLoading = true;
|
|
if (db_read(f, &db_format, &db_version, &db_flags) < 0)
|
|
{
|
|
mudstate.bSQLiteLoading = false;
|
|
sqldb.Rollback();
|
|
// Queued attrs targeted the aborted transaction — drop them.
|
|
//
|
|
cache_discard_writes();
|
|
// Everything is not ok.
|
|
//
|
|
mux_fclose(f);
|
|
f = 0;
|
|
|
|
STARTLOG(LOG_ALWAYS, "INI", "FATAL")
|
|
log_text(T("Error loading "));
|
|
log_text(infile);
|
|
ENDLOG
|
|
return LOAD_GAME_LOADING_PROBLEM;
|
|
}
|
|
// Flush remaining write-queue puts into the open import transaction
|
|
// before Commit (#1047). Must run while bSQLiteLoading is still true
|
|
// so flush does not open a nested Begin/Commit.
|
|
//
|
|
if (!cache_flush_writes())
|
|
{
|
|
mudstate.bSQLiteLoading = false;
|
|
sqldb.Rollback();
|
|
cache_discard_writes();
|
|
mux_fclose(f);
|
|
f = 0;
|
|
STARTLOG(LOG_ALWAYS, "INI", "FATAL")
|
|
log_text(T("Error flushing SQLite attributes after flatfile load."));
|
|
ENDLOG
|
|
return LOAD_GAME_LOADING_PROBLEM;
|
|
}
|
|
mudstate.bSQLiteLoading = false;
|
|
if (!sqldb.Commit())
|
|
{
|
|
sqldb.Rollback();
|
|
cache_discard_writes();
|
|
mux_fclose(f);
|
|
f = 0;
|
|
STARTLOG(LOG_ALWAYS, "INI", "FATAL")
|
|
log_text(T("Error committing SQLite attributes after flatfile load."));
|
|
ENDLOG
|
|
return LOAD_GAME_LOADING_PROBLEM;
|
|
}
|
|
|
|
// Everything is ok.
|
|
//
|
|
mux_fclose(f);
|
|
f = 0;
|
|
|
|
// Bulk-sync object and attribute-name metadata from db[] into SQLite.
|
|
// Attribute values are already authoritative from the import transaction.
|
|
//
|
|
if (!sqlite_sync_runtime())
|
|
{
|
|
if (!clear_sqlite_after_sync_failure(sqldb))
|
|
{
|
|
STARTLOG(LOG_ALWAYS, "INI", "FATAL")
|
|
log_text(T("SQLite cleanup failed after runtime sync failure."));
|
|
ENDLOG
|
|
}
|
|
STARTLOG(LOG_ALWAYS, "INI", "FATAL")
|
|
log_text(T("SQLite sync failed after flatfile load."));
|
|
ENDLOG
|
|
return LOAD_GAME_LOADING_PROBLEM;
|
|
}
|
|
|
|
int load_comsys_rc = sqlite_load_comsys();
|
|
if (load_comsys_rc > 0)
|
|
{
|
|
Log.tinyprintf(T("LOADING: comsys (from SQLite)" ENDLINE));
|
|
}
|
|
else if (load_comsys_rc < 0)
|
|
{
|
|
STARTLOG(LOG_ALWAYS, "INI", "FATAL")
|
|
log_text(T("SQLite comsys load failed."));
|
|
ENDLOG
|
|
return LOAD_GAME_LOADING_PROBLEM;
|
|
}
|
|
else
|
|
{
|
|
load_comsys(mudconf.comsys_db);
|
|
}
|
|
|
|
int load_mail_rc = sqlite_load_mail();
|
|
if (load_mail_rc > 0)
|
|
{
|
|
Log.tinyprintf(T("LOADING: mail (from SQLite)" ENDLINE));
|
|
}
|
|
else if (load_mail_rc < 0)
|
|
{
|
|
STARTLOG(LOG_ALWAYS, "INI", "FATAL")
|
|
log_text(T("SQLite mail load failed."));
|
|
ENDLOG
|
|
return LOAD_GAME_LOADING_PROBLEM;
|
|
}
|
|
else
|
|
if (mux_fopen(&f, mudconf.mail_db, T("rb")))
|
|
{
|
|
setvbuf(f, nullptr, _IOFBF, 16384);
|
|
Log.tinyprintf(T("LOADING: %s" ENDLINE), mudconf.mail_db);
|
|
load_mail(f);
|
|
Log.tinyprintf(T("LOADING: %s (done)" ENDLINE), mudconf.mail_db);
|
|
mux_fclose(f);
|
|
f = 0;
|
|
}
|
|
STARTLOG(LOG_STARTUP, "INI", "LOAD");
|
|
log_text(T("Load complete."));
|
|
ENDLOG;
|
|
|
|
return LOAD_GAME_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* match a list of things, using the no_command flag
|
|
*
|
|
* This seems to be always called with type == AMATCH_CMD...
|
|
* So the fact that ansi_strip is done within atr_match only
|
|
* brings about a if () performance hit...
|
|
*
|
|
*/
|
|
|
|
bool list_check
|
|
(
|
|
dbref thing,
|
|
dbref player,
|
|
UTF8 type,
|
|
UTF8 *str,
|
|
UTF8 *raw_str,
|
|
bool check_parent
|
|
)
|
|
{
|
|
bool bMatch = false;
|
|
|
|
int limit = mudstate.db_top;
|
|
while (NOTHING != thing)
|
|
{
|
|
#ifdef REALITY_LVLS
|
|
if ((thing != player)
|
|
&& (!(No_Command(thing)))
|
|
&& IsReal(thing, player))
|
|
#else
|
|
if ( thing != player
|
|
&& !No_Command(thing))
|
|
#endif // REALITY_LVLS
|
|
{
|
|
bMatch |= atr_match(thing, player, type, str, raw_str, check_parent);
|
|
}
|
|
|
|
// Non-authoritative test of circular reference.
|
|
//
|
|
dbref next;
|
|
if ( thing == (next = Next(thing))
|
|
|| --limit < 0
|
|
|| alarm_clock.alarmed)
|
|
{
|
|
break;
|
|
}
|
|
thing = next;
|
|
}
|
|
return bMatch;
|
|
}
|
|
|
|
bool Hearer(dbref thing)
|
|
{
|
|
if ( mudstate.inpipe
|
|
&& thing == mudstate.poutobj)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( Connected(thing)
|
|
|| Puppet(thing)
|
|
|| H_Listen(thing))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (Monitor(thing))
|
|
{
|
|
if (mudstate.bfListens.IsSet(thing))
|
|
{
|
|
return true;
|
|
}
|
|
else if (mudstate.bfNoListens.IsSet(thing))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
bool bFoundCommands = false;
|
|
|
|
LBuf buff = LBuf_Src("Hearer");
|
|
atr_push();
|
|
unsigned char *as;
|
|
for (int atr = atr_head(thing, &as); atr; atr = atr_next(&as))
|
|
{
|
|
ATTR *ap = atr_num(atr);
|
|
if ( !ap
|
|
|| (ap->flags & AF_NOPROG))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int aflags;
|
|
dbref aowner;
|
|
atr_get_str(buff, thing, atr, &aowner, &aflags);
|
|
|
|
if (aflags & AF_NOPROG)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UTF8 *s = nullptr;
|
|
if ( AMATCH_CMD == buff[0]
|
|
|| AMATCH_LISTEN == buff[0])
|
|
{
|
|
s = find_pattern_delimiter(buff + 1);
|
|
if (s)
|
|
{
|
|
if (AMATCH_CMD == buff[0])
|
|
{
|
|
bFoundCommands = true;
|
|
}
|
|
else
|
|
{
|
|
atr_pop();
|
|
mudstate.bfListens.Set(thing);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
atr_pop();
|
|
|
|
mudstate.bfNoListens.Set(thing);
|
|
|
|
if (bFoundCommands)
|
|
{
|
|
mudstate.bfNoCommands.Clear(thing);
|
|
mudstate.bfCommands.Set(thing);
|
|
}
|
|
else
|
|
{
|
|
mudstate.bfCommands.Clear(thing);
|
|
mudstate.bfNoCommands.Set(thing);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void do_readcache(dbref executor, dbref caller, dbref enactor, int eval, int key)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(key);
|
|
|
|
helpindex_load(executor);
|
|
fcache_load(executor);
|
|
}
|
|
|
|
void process_preload(void)
|
|
{
|
|
dbref thing, parent, aowner;
|
|
int aflags, lev;
|
|
UTF8 *tstr;
|
|
|
|
tstr = alloc_lbuf("process_preload.string");
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
// Ignore GOING objects.
|
|
//
|
|
if (Going(thing))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
scheduler.RunTasks(10);
|
|
|
|
// Look for a STARTUP attribute in parents.
|
|
//
|
|
if (mudconf.run_startup)
|
|
{
|
|
ITER_PARENTS(thing, parent, lev)
|
|
{
|
|
if (H_Startup(thing))
|
|
{
|
|
did_it(Owner(thing), thing, 0, nullptr, 0, nullptr, A_STARTUP,
|
|
0, nullptr, 0);
|
|
|
|
// Process queue entries as we add them.
|
|
//
|
|
scheduler.RunTasks(10);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for a FORWARDLIST attribute.
|
|
//
|
|
if (H_Fwdlist(thing))
|
|
{
|
|
atr_get_str(tstr, thing, A_FORWARDLIST, &aowner, &aflags);
|
|
if (*tstr)
|
|
{
|
|
FWDLIST *fp = fwdlist_load(GOD, tstr);
|
|
if (nullptr != fp)
|
|
{
|
|
fwdlist_set(thing, fp);
|
|
|
|
if (fp->data)
|
|
{
|
|
delete [] fp->data;
|
|
}
|
|
delete fp;
|
|
fp = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free_lbuf(tstr);
|
|
}
|