mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Found while translating mail (#1419). The tell was a line wrong in English before translation is even involved: MAIL: 1 messages in folder 0 [unnamed] (1 unread, 0 cleared). MN_() exists for this (#1622) and #1655 converted the @mail/stats family; this set was missed. A translator cannot work around it -- the msgid has no msgid_plural, so there is no second form for a catalogue to supply, and es must pick one wording for all counts while ru and pl need three and can express none of them. The fix is in the C++, not the .po. ## Converted, 11 sites mail.cpp, mail_mod.cpp mailbox is full; URGENT MAIL urgent messages walkdb.cpp universe contains; objects @chowned; objects marked object.cpp objects @chowned to you wiz.cpp You toaded ... objects @chowned command.cpp at most N players logged in The mail module duplicates are converted alongside the engine's so the two implementations do not drift (#1614). ## One was a different bug than the issue described walkdb.cpp built its message as M_("%d objects %smarked") with %s = "" or "un". That is fragment assembly, not a missing plural: a translator handed that %s cannot use it, because the prefix is English morphology rather than a word. Split into two whole sentences, each pluralised -- the #1575 / #1588 rule. Now renders "1 object marked" / "8 objects unmarked" instead of a %s a catalogue has no way to fill. ## Two deliberately NOT converted MAIL: %d messages in folder %d [%s] (%d unread, %d cleared). %d objects = %d rooms, %d exits, %d things, %d players. (%d garbage) MN_(s, p, n) selects one form from one n. Four independent counts are four independent plural decisions, and in Russian each noun needs its own form. These want rewording, which is a design call rather than a mechanical conversion, so they are left and recorded rather than half-fixed. ## Catalogue fallout .pot 18 -> 27 plural entries; all three catalogues msgmerge'd. xx is `complete` policy, so its nine new entries had to be filled and unfuzzied or the guard fails -- which it duly did, first try. msgmerge had copied the old singular-only text into BOTH forms, so msgstr[0] said "messages"; each form is now prefixed from its own msgid. es and ko received real translations for all nine. es.po 190 -> 198/976 (20.3%) ko.po 229 -> 236/976 (24.2%) ## Verified end to end, at n=1 and n>1 en 1 object marked 8 objects marked es 1 objeto marcado 8 objetos marcados ko 사물 1개를 표시했습니다 사물 8개를 표시했습니다 xx [xx] 1 object marked [xx] 8 objects marked Korean renders identically for both, which is correct: it declares nplurals=1, so the catalogue's own rule is being honoured rather than English's imposed on it. The unconverted multi-count line still reads "1 messages", as documented. make test: Smoke 1561 x3, tests/nls 7, ko 4, plural 43. TESTEXIT=0. Refs #1419, #1575, #1588, #1614, #1622, #1655, #1702. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1551 lines
40 KiB
C++
1551 lines
40 KiB
C++
/*! \file walkdb.cpp
|
||
* \brief Support for commands that walk the entire db.
|
||
*
|
||
*/
|
||
|
||
#include "copyright.h"
|
||
#include "autoconf.h"
|
||
#include "config.h"
|
||
#include "externs.h"
|
||
#include "mux_table.h"
|
||
|
||
#include "sqlite_backend.h"
|
||
|
||
// Bind occurances of the universal var in ACTION to ARG, then run ACTION.
|
||
// Cmds run in low-prio Q after a 1 sec delay for the first one.
|
||
//
|
||
static void bind_and_queue(dbref executor, dbref caller, dbref enactor,
|
||
int eval, UTF8 *action, UTF8 *argstr,
|
||
const UTF8 *cargs[], int ncargs, int number)
|
||
{
|
||
CLinearTimeAbsolute lta;
|
||
wait_que(executor, caller, enactor, eval, false, lta, NOTHING, 0,
|
||
action,
|
||
ncargs, cargs,
|
||
mudstate.global_regs,
|
||
nullptr, // named_sargs
|
||
mudconf.safer_iter ? nullptr : argstr, // iter_token
|
||
number, // iter_number
|
||
nullptr); // switch_token
|
||
}
|
||
|
||
static void bind_and_process(dbref executor, dbref caller, dbref enactor,
|
||
int eval, UTF8 *action, UTF8 *argstr,
|
||
const UTF8 *cargs[], int ncargs, int number)
|
||
{
|
||
bool bLoopInBounds = ( 0 <= mudstate.in_loop
|
||
&& mudstate.in_loop < MAX_ITEXT);
|
||
if (bLoopInBounds)
|
||
{
|
||
mudstate.itext[mudstate.in_loop] = argstr;
|
||
mudstate.inum[mudstate.in_loop] = number;
|
||
}
|
||
mudstate.in_loop++;
|
||
|
||
// Execute the body as an action list: split on ';' and run each command
|
||
// inline, honoring @break/@assert and ;| piping (#788). parse_to()
|
||
// rewrites its buffer in place, so work on a private copy — do_dolist
|
||
// reuses the same 'action' buffer for every iteration.
|
||
//
|
||
LBuf tbuf = LBuf_Src("dolist.now");
|
||
mux_strncpy(tbuf, action, LBUF_SIZE - 1);
|
||
process_command_list_inline(executor, caller, enactor, eval, tbuf.get(),
|
||
cargs, ncargs);
|
||
|
||
mudstate.in_loop--;
|
||
if (bLoopInBounds)
|
||
{
|
||
mudstate.itext[mudstate.in_loop] = nullptr;
|
||
mudstate.inum[mudstate.in_loop] = 0;
|
||
}
|
||
}
|
||
|
||
// New @dolist. i.e.:
|
||
// @dolist #12 #34 #45 #123 #34644=@emit [name(##)]
|
||
//
|
||
// New switches added 12/92, /space (default) delimits list using spaces,
|
||
// and /delimit allows specification of a delimiter.
|
||
//
|
||
void do_dolist(dbref executor, dbref caller, dbref enactor, int eval, int key,
|
||
int nargs, UTF8 *list, UTF8 *command, const UTF8 *cargs[], int ncargs)
|
||
{
|
||
UNUSED_PARAMETER(nargs);
|
||
|
||
if (!list || *list == '\0')
|
||
{
|
||
notify(executor, M_("That’s terrific, but what should I do with the list?"));
|
||
return;
|
||
}
|
||
UTF8 *objstring, delimiter = ' ';
|
||
int number = 0;
|
||
UTF8 *curr = list;
|
||
|
||
if (key & DOLIST_DELIMIT)
|
||
{
|
||
UTF8 *tempstr = parse_to(&curr, ' ', EV_STRIP_CURLY);
|
||
if (1 < strlen(reinterpret_cast<char *>(tempstr)))
|
||
{
|
||
notify(executor, M_("The delimiter must be a single character!"));
|
||
return;
|
||
}
|
||
delimiter = *tempstr;
|
||
}
|
||
|
||
// Inline (/now) bodies run synchronously and may mutate the executor's
|
||
// own attributes, which can free the lbufs that 'list' and 'command' (the
|
||
// @dolist arguments) point into and corrupt our walk. Iterate over
|
||
// private copies so body execution cannot pull the rug out from under us.
|
||
// (The queued path is immune — wait_que() copies the body — but copying
|
||
// for both keeps the loop uniform and an lbuf alloc is a freelist pop.)
|
||
//
|
||
LBuf listcopy = LBuf_Src("dolist.list");
|
||
LBuf cmdcopy = LBuf_Src("dolist.cmd");
|
||
mux_strncpy(listcopy, curr, LBUF_SIZE - 1);
|
||
curr = listcopy.get();
|
||
if (nullptr != command)
|
||
{
|
||
mux_strncpy(cmdcopy, command, LBUF_SIZE - 1);
|
||
command = cmdcopy.get();
|
||
}
|
||
|
||
// For inline (/now) iteration, isolate @break/@assert to the loop: save
|
||
// and clear break_called so a break inside the body stops the dolist
|
||
// without leaking to the surrounding command list, then restore it.
|
||
//
|
||
bool save_break = break_called;
|
||
if (key & DOLIST_NOW)
|
||
{
|
||
break_called = false;
|
||
}
|
||
while (curr && *curr)
|
||
{
|
||
while (*curr == delimiter)
|
||
{
|
||
curr++;
|
||
}
|
||
if (*curr)
|
||
{
|
||
number++;
|
||
objstring = parse_to(&curr, delimiter, EV_STRIP_CURLY);
|
||
if (key & DOLIST_NOW)
|
||
{
|
||
bind_and_process(executor, caller, enactor, eval, command,
|
||
objstring, cargs, ncargs, number);
|
||
if (break_called)
|
||
{
|
||
// @break/@assert fired inside the body; stop the loop.
|
||
//
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
bind_and_queue(executor, caller, enactor, eval, command,
|
||
objstring, cargs, ncargs, number);
|
||
}
|
||
}
|
||
}
|
||
if (key & DOLIST_NOW)
|
||
{
|
||
// Default: contain @break to the loop (it stopped the loop above
|
||
// but the enclosing action list continues). With /break, an
|
||
// inner @break also propagates outward and aborts the rest of
|
||
// the enclosing list, like @include's default (#788).
|
||
if ( (key & DOLIST_BREAK)
|
||
&& break_called)
|
||
{
|
||
// Leave break_called set.
|
||
}
|
||
else
|
||
{
|
||
break_called = save_break;
|
||
}
|
||
}
|
||
|
||
if (key & DOLIST_NOTIFY)
|
||
{
|
||
LBuf tbuf = LBuf_Src("dolist.notify_cmd");
|
||
mux_strncpy(tbuf, T("@notify/quiet me"), LBUF_SIZE-1);
|
||
CLinearTimeAbsolute lta;
|
||
wait_que(executor, caller, enactor, eval, false, lta, NOTHING,
|
||
A_SEMAPHORE,
|
||
tbuf,
|
||
ncargs, cargs,
|
||
mudstate.global_regs);
|
||
}
|
||
}
|
||
|
||
// Regular @find command
|
||
//
|
||
void do_find(dbref executor, 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);
|
||
|
||
UTF8 *buff;
|
||
|
||
if (!payfor(executor, mudconf.searchcost))
|
||
{
|
||
buff = tprintf(M_("You don’t have enough %s."), mudconf.many_coins);
|
||
notify_quiet(executor, buff);
|
||
return;
|
||
}
|
||
|
||
dbref i, low_bound, high_bound;
|
||
parse_range(&name, &low_bound, &high_bound);
|
||
for (i = low_bound; i <= high_bound; i++)
|
||
{
|
||
if ( (Typeof(i) != TYPE_EXIT)
|
||
&& Controls(executor, i)
|
||
&& (!*name || string_match(PureName(i), name)))
|
||
{
|
||
buff = unparse_object(executor, i, false);
|
||
notify(executor, buff);
|
||
free_lbuf(buff);
|
||
}
|
||
}
|
||
notify(executor, M_("***End of List***"));
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// get_stats, do_stats: Get counts of items in the db.
|
||
//
|
||
bool get_stats(dbref player, dbref who, STATS *info)
|
||
{
|
||
// Do we have permission?
|
||
//
|
||
if (Good_obj(who) && !Controls(player, who) && !Stat_Any(player))
|
||
{
|
||
notify(player, NOPERM_MESSAGE);
|
||
return false;
|
||
}
|
||
|
||
// Can we afford it?
|
||
//
|
||
if (!payfor(player, mudconf.searchcost))
|
||
{
|
||
notify(player, tprintf(M_("You don’t have enough %s."), mudconf.many_coins));
|
||
return false;
|
||
}
|
||
info->s_total = 0;
|
||
info->s_rooms = 0;
|
||
info->s_exits = 0;
|
||
info->s_things = 0;
|
||
info->s_players = 0;
|
||
info->s_garbage = 0;
|
||
|
||
dbref i;
|
||
DO_WHOLE_DB(i)
|
||
{
|
||
if ((who == NOTHING) || (who == Owner(i)))
|
||
{
|
||
info->s_total++;
|
||
if (Going(i) && (Typeof(i) != TYPE_ROOM))
|
||
{
|
||
info->s_garbage++;
|
||
continue;
|
||
}
|
||
switch (Typeof(i))
|
||
{
|
||
case TYPE_ROOM:
|
||
|
||
info->s_rooms++;
|
||
break;
|
||
|
||
case TYPE_EXIT:
|
||
|
||
info->s_exits++;
|
||
break;
|
||
|
||
case TYPE_THING:
|
||
|
||
info->s_things++;
|
||
break;
|
||
|
||
case TYPE_PLAYER:
|
||
|
||
info->s_players++;
|
||
break;
|
||
|
||
default:
|
||
|
||
info->s_garbage++;
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// Reworked by R'nice
|
||
//
|
||
void do_stats(dbref executor, 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(cargs);
|
||
UNUSED_PARAMETER(ncargs);
|
||
|
||
dbref owner;
|
||
|
||
switch (key)
|
||
{
|
||
case STAT_ALL:
|
||
|
||
owner = NOTHING;
|
||
break;
|
||
|
||
case STAT_ME:
|
||
|
||
owner = Owner(executor);
|
||
break;
|
||
|
||
case STAT_PLAYER:
|
||
|
||
if (!(name && *name))
|
||
{
|
||
int nNextFree = mudstate.freelist;
|
||
if (mudstate.freelist == NOTHING)
|
||
{
|
||
nNextFree = mudstate.db_top;
|
||
}
|
||
notify(executor, tprintf(MN_(
|
||
"The universe contains %d object (next free is #%d).",
|
||
"The universe contains %d objects (next free is #%d).",
|
||
mudstate.db_top),
|
||
mudstate.db_top, nNextFree));
|
||
return;
|
||
}
|
||
owner = lookup_player(executor, name, true);
|
||
if (owner == NOTHING)
|
||
{
|
||
notify(executor, M_("Not found."));
|
||
return;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
|
||
notify(executor, M_("Illegal combination of switches."));
|
||
return;
|
||
}
|
||
|
||
STATS statinfo;
|
||
if (!get_stats(executor, owner, &statinfo))
|
||
{
|
||
return;
|
||
}
|
||
notify(executor,
|
||
tprintf(M_("%d objects = %d rooms, %d exits, %d things, %d players. (%d garbage)"),
|
||
statinfo.s_total, statinfo.s_rooms, statinfo.s_exits,
|
||
statinfo.s_things, statinfo.s_players,
|
||
statinfo.s_garbage));
|
||
}
|
||
|
||
int chown_all(dbref from_player, dbref to_player, dbref acting_player, int key)
|
||
{
|
||
if (!isPlayer(from_player))
|
||
{
|
||
from_player = Owner(from_player);
|
||
}
|
||
|
||
if (!isPlayer(to_player))
|
||
{
|
||
to_player = Owner(to_player);
|
||
}
|
||
|
||
int count = 0;
|
||
if ( God(from_player)
|
||
&& !God(acting_player))
|
||
{
|
||
notify(acting_player, NOPERM_MESSAGE);
|
||
}
|
||
else
|
||
{
|
||
int i;
|
||
int quota_out = 0;
|
||
int quota_in = 0;
|
||
|
||
FLAGSET clearflags;
|
||
FLAGSET setflags;
|
||
bool bClearPowers;
|
||
|
||
TranslateFlags_Chown(clearflags.word, setflags.word, &bClearPowers, acting_player, key);
|
||
|
||
DO_WHOLE_DB(i)
|
||
{
|
||
if ( Owner(i) == from_player
|
||
&& Owner(i) != i)
|
||
{
|
||
switch (Typeof(i))
|
||
{
|
||
case TYPE_PLAYER:
|
||
|
||
s_Owner(i, i);
|
||
quota_out += mudconf.player_quota;
|
||
break;
|
||
|
||
case TYPE_THING:
|
||
|
||
s_Owner(i, to_player);
|
||
quota_out += mudconf.thing_quota;
|
||
quota_in -= mudconf.thing_quota;
|
||
break;
|
||
|
||
case TYPE_ROOM:
|
||
|
||
s_Owner(i, to_player);
|
||
quota_out += mudconf.room_quota;
|
||
quota_in -= mudconf.room_quota;
|
||
break;
|
||
|
||
case TYPE_EXIT:
|
||
|
||
s_Owner(i, to_player);
|
||
quota_out += mudconf.exit_quota;
|
||
quota_in -= mudconf.exit_quota;
|
||
break;
|
||
|
||
default:
|
||
|
||
s_Owner(i, to_player);
|
||
}
|
||
|
||
if (key & CHOWN_NOZONE)
|
||
{
|
||
s_Zone(i, NOTHING);
|
||
}
|
||
|
||
SetClearFlags(i, clearflags.word, setflags.word);
|
||
if (bClearPowers)
|
||
{
|
||
s_Powers(i, 0);
|
||
s_Powers2(i, 0);
|
||
}
|
||
|
||
// Always halt the queue.
|
||
//
|
||
halt_que(NOTHING, i);
|
||
|
||
count++;
|
||
}
|
||
}
|
||
add_quota(from_player, quota_out);
|
||
add_quota(to_player, quota_in);
|
||
}
|
||
return count;
|
||
}
|
||
|
||
void do_chownall
|
||
(
|
||
dbref executor,
|
||
dbref caller,
|
||
dbref enactor,
|
||
int eval,
|
||
int key,
|
||
int nargs,
|
||
UTF8 *from,
|
||
UTF8 *to,
|
||
const UTF8 *cargs[],
|
||
int ncargs
|
||
)
|
||
{
|
||
UNUSED_PARAMETER(caller);
|
||
UNUSED_PARAMETER(enactor);
|
||
UNUSED_PARAMETER(eval);
|
||
UNUSED_PARAMETER(nargs);
|
||
UNUSED_PARAMETER(cargs);
|
||
UNUSED_PARAMETER(ncargs);
|
||
|
||
init_match(executor, from, TYPE_PLAYER);
|
||
match_neighbor();
|
||
match_absolute();
|
||
match_player();
|
||
|
||
dbref victim = noisy_match_result();
|
||
if (NOTHING == victim)
|
||
{
|
||
return;
|
||
}
|
||
else if (!isPlayer(victim))
|
||
{
|
||
notify(executor, M_("Victim must be a player."));
|
||
return;
|
||
}
|
||
|
||
dbref recipient = executor;
|
||
if ( nullptr != to
|
||
&& to[0] != '\0')
|
||
{
|
||
init_match(executor, to, TYPE_PLAYER);
|
||
match_neighbor();
|
||
match_absolute();
|
||
match_player();
|
||
recipient = noisy_match_result();
|
||
if (NOTHING == recipient)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
int count = chown_all(victim, recipient, executor, key);
|
||
if (!Quiet(executor))
|
||
{
|
||
notify(executor, tprintf(MN_("%d object @chowned.",
|
||
"%d objects @chowned.", count), count));
|
||
}
|
||
}
|
||
|
||
#define ANY_OWNER (-2)
|
||
|
||
static void er_mark_disabled(dbref player)
|
||
{
|
||
notify(player,
|
||
M_("The mark commands are not allowed while DB cleaning is enabled."));
|
||
notify(player,
|
||
M_("Use the ‘@disable cleaning’ command to disable automatic cleaning."));
|
||
notify(player,
|
||
M_("Remember to ‘@mark_all/clear’ before re-enabling automatic cleaning."));
|
||
}
|
||
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// do_search: Walk the db reporting various things (or setting/clearing mark
|
||
// bits)
|
||
//
|
||
bool search_setup(dbref player, UTF8 *searchfor, SEARCH *parm)
|
||
{
|
||
// Crack arg into <pname> <type>=<targ>,<low>,<high>
|
||
//
|
||
UTF8 *pname = parse_to(&searchfor, '=', EV_STRIP_TS);
|
||
if (!pname || !*pname)
|
||
{
|
||
pname = const_cast<UTF8 *>(T("me"));
|
||
}
|
||
else
|
||
{
|
||
size_t nCased;
|
||
pname = mux_strlwr(pname, nCased);
|
||
}
|
||
|
||
UTF8 *searchtype;
|
||
if (searchfor && *searchfor)
|
||
{
|
||
searchtype = reinterpret_cast<UTF8 *>(strrchr(reinterpret_cast<char *>(pname), ' '));
|
||
if (searchtype)
|
||
{
|
||
*searchtype++ = '\0';
|
||
}
|
||
else
|
||
{
|
||
searchtype = pname;
|
||
pname = const_cast<UTF8 *>(T(""));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
searchtype = const_cast<UTF8 *>(T(""));
|
||
}
|
||
|
||
// If the player name is quoted, strip the quotes.
|
||
//
|
||
if (*pname == '"')
|
||
{
|
||
size_t k = strlen(reinterpret_cast<char *>(pname)) - 1;
|
||
if (pname[k] == '"')
|
||
{
|
||
pname[k] = '\0';
|
||
pname++;
|
||
}
|
||
}
|
||
|
||
// Strip any range arguments.
|
||
//
|
||
parse_range(&searchfor, &parm->low_bound, &parm->high_bound);
|
||
|
||
|
||
// Set limits on who we search.
|
||
//
|
||
parm->s_owner = Owner(player);
|
||
parm->s_wizard = Search(player);
|
||
parm->s_rst_owner = NOTHING;
|
||
if (!*pname)
|
||
{
|
||
parm->s_rst_owner = parm->s_wizard ? ANY_OWNER : player;
|
||
}
|
||
else if (pname[0] == '#')
|
||
{
|
||
parm->s_rst_owner = mux_atoi64(&pname[1]);
|
||
if (!Good_obj(parm->s_rst_owner))
|
||
{
|
||
parm->s_rst_owner = NOTHING;
|
||
}
|
||
else if (Typeof(parm->s_rst_owner) != TYPE_PLAYER)
|
||
{
|
||
parm->s_rst_owner = NOTHING;
|
||
}
|
||
|
||
}
|
||
else if (strcmp(reinterpret_cast<char *>(pname), "me") == 0)
|
||
{
|
||
parm->s_rst_owner = player;
|
||
}
|
||
else
|
||
{
|
||
parm->s_rst_owner = lookup_player(player, pname, true);
|
||
}
|
||
|
||
if (parm->s_rst_owner == NOTHING)
|
||
{
|
||
notify(player, tprintf(M_("%s: No such player"), pname));
|
||
return false;
|
||
}
|
||
|
||
// Set limits on what we search for.
|
||
//
|
||
int err = 0;
|
||
parm->s_rst_name = nullptr;
|
||
parm->s_rst_eval = nullptr;
|
||
parm->s_rst_type = NOTYPE;
|
||
parm->s_parent = NOTHING;
|
||
parm->s_zone = NOTHING;
|
||
for (int i = FLAG_WORD1; i <= FLAG_WORD3; i++)
|
||
{
|
||
parm->s_fset.word[i] = 0;
|
||
}
|
||
parm->s_pset.word1 = 0;
|
||
parm->s_pset.word2 = 0;
|
||
|
||
switch (searchtype[0])
|
||
{
|
||
case '\0':
|
||
|
||
// The no class requested class :)
|
||
//
|
||
break;
|
||
|
||
case 'e':
|
||
|
||
if (string_prefix(T("exits"), searchtype))
|
||
{
|
||
parm->s_rst_name = searchfor;
|
||
parm->s_rst_type = TYPE_EXIT;
|
||
}
|
||
else if (string_prefix(T("evaluate"), searchtype))
|
||
{
|
||
parm->s_rst_eval = searchfor;
|
||
}
|
||
else if (string_prefix(T("eplayer"), searchtype))
|
||
{
|
||
parm->s_rst_type = TYPE_PLAYER;
|
||
parm->s_rst_eval = searchfor;
|
||
}
|
||
else if (string_prefix(T("eroom"), searchtype))
|
||
{
|
||
parm->s_rst_type = TYPE_ROOM;
|
||
parm->s_rst_eval = searchfor;
|
||
}
|
||
else if (string_prefix(T("eobject"), searchtype))
|
||
{
|
||
parm->s_rst_type = TYPE_THING;
|
||
parm->s_rst_eval = searchfor;
|
||
}
|
||
else if (string_prefix(T("ething"), searchtype))
|
||
{
|
||
parm->s_rst_type = TYPE_THING;
|
||
parm->s_rst_eval = searchfor;
|
||
}
|
||
else if (string_prefix(T("eexit"), searchtype))
|
||
{
|
||
parm->s_rst_type = TYPE_EXIT;
|
||
parm->s_rst_eval = searchfor;
|
||
}
|
||
else
|
||
{
|
||
err = 1;
|
||
}
|
||
break;
|
||
|
||
case 'f':
|
||
|
||
if (string_prefix(T("flags"), searchtype))
|
||
{
|
||
// convert_flags ignores previous values of flag_mask and
|
||
// s_rst_type while setting them.
|
||
//
|
||
if ( !convert_flags( player, searchfor, &parm->s_fset,
|
||
&parm->s_rst_type) )
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
err = 1;
|
||
}
|
||
break;
|
||
|
||
case 'n':
|
||
|
||
if (string_prefix(T("name"), searchtype))
|
||
{
|
||
parm->s_rst_name = searchfor;
|
||
}
|
||
else
|
||
{
|
||
err = 1;
|
||
}
|
||
break;
|
||
|
||
case 'o':
|
||
|
||
if (string_prefix(T("objects"), searchtype))
|
||
{
|
||
parm->s_rst_name = searchfor;
|
||
parm->s_rst_type = TYPE_THING;
|
||
}
|
||
else
|
||
{
|
||
err = 1;
|
||
}
|
||
break;
|
||
|
||
case 'p':
|
||
|
||
if (string_prefix(T("players"), searchtype))
|
||
{
|
||
parm->s_rst_name = searchfor;
|
||
parm->s_rst_type = TYPE_PLAYER;
|
||
if (!*pname)
|
||
{
|
||
parm->s_rst_owner = ANY_OWNER;
|
||
}
|
||
}
|
||
else if (string_prefix(T("parent"), searchtype))
|
||
{
|
||
parm->s_parent = match_controlled(player, searchfor);
|
||
if (!Good_obj(parm->s_parent))
|
||
{
|
||
return false;
|
||
}
|
||
if (!*pname)
|
||
{
|
||
parm->s_rst_owner = ANY_OWNER;
|
||
}
|
||
}
|
||
else if (string_prefix(T("power"), searchtype))
|
||
{
|
||
if (!decode_power(player, searchfor, &parm->s_pset))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
err = 1;
|
||
}
|
||
break;
|
||
|
||
case 'r':
|
||
|
||
if (string_prefix(T("rooms"), searchtype))
|
||
{
|
||
parm->s_rst_name = searchfor;
|
||
parm->s_rst_type = TYPE_ROOM;
|
||
}
|
||
else
|
||
{
|
||
err = 1;
|
||
}
|
||
break;
|
||
|
||
case 't':
|
||
|
||
if (string_prefix(T("type"), searchtype))
|
||
{
|
||
if (searchfor[0] == '\0')
|
||
{
|
||
break;
|
||
}
|
||
if (string_prefix(T("rooms"), searchfor))
|
||
{
|
||
parm->s_rst_type = TYPE_ROOM;
|
||
}
|
||
else if (string_prefix(T("exits"), searchfor))
|
||
{
|
||
parm->s_rst_type = TYPE_EXIT;
|
||
}
|
||
else if (string_prefix(T("objects"), searchfor))
|
||
{
|
||
parm->s_rst_type = TYPE_THING;
|
||
}
|
||
else if (string_prefix(T("things"), searchfor))
|
||
{
|
||
parm->s_rst_type = TYPE_THING;
|
||
}
|
||
else if (string_prefix(T("players"), searchfor))
|
||
{
|
||
parm->s_rst_type = TYPE_PLAYER;
|
||
if (!*pname)
|
||
{
|
||
parm->s_rst_owner = ANY_OWNER;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
notify(player, tprintf(M_("%s: unknown type"), searchfor));
|
||
return false;
|
||
}
|
||
}
|
||
else if (string_prefix(T("things"), searchtype))
|
||
{
|
||
parm->s_rst_name = searchfor;
|
||
parm->s_rst_type = TYPE_THING;
|
||
}
|
||
else
|
||
{
|
||
err = 1;
|
||
}
|
||
break;
|
||
|
||
case 'z':
|
||
|
||
if (string_prefix(T("zone"), searchtype))
|
||
{
|
||
parm->s_zone = match_controlled(player, searchfor);
|
||
if (!Good_obj(parm->s_zone))
|
||
{
|
||
return false;
|
||
}
|
||
if (!*pname)
|
||
{
|
||
parm->s_rst_owner = ANY_OWNER;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
err = 1;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
|
||
err = 1;
|
||
}
|
||
|
||
if (err)
|
||
{
|
||
notify(player, tprintf(M_("%s: unknown class"), searchtype));
|
||
return false;
|
||
}
|
||
|
||
// Make sure player is authorized to do the search.
|
||
//
|
||
if ( !parm->s_wizard
|
||
&& (parm->s_rst_type != TYPE_PLAYER)
|
||
&& (parm->s_rst_owner != player)
|
||
&& (parm->s_rst_owner != ANY_OWNER))
|
||
{
|
||
notify(player, M_("You need a search warrant to do that!"));
|
||
return false;
|
||
}
|
||
|
||
// Make sure player has money to do the search.
|
||
//
|
||
if (!payfor(player, mudconf.searchcost))
|
||
{
|
||
notify(player,
|
||
tprintf(M_("You don’t have enough %s to search. (You need %d)"),
|
||
mudconf.many_coins, mudconf.searchcost));
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
void search_perform(dbref executor, dbref caller, dbref enactor, SEARCH *parm)
|
||
{
|
||
POWER thing1powers, thing2powers;
|
||
UTF8 *bp;
|
||
|
||
UTF8 *buff = alloc_sbuf("search_perform.num");
|
||
int save_invk_ctr = mudstate.func_invk_ctr;
|
||
|
||
// SQLite fast path for simple searches.
|
||
//
|
||
// A search qualifies for the fast path when there is no name match,
|
||
// no eval expression, and no power filter — i.e., the criteria can be
|
||
// expressed purely in terms of object metadata columns. The SQL query
|
||
// uses indexes on owner, zone, or parent and avoids a full db[] scan.
|
||
//
|
||
if ( nullptr == parm->s_rst_name
|
||
&& nullptr == parm->s_rst_eval
|
||
&& 0 == parm->s_pset.word1
|
||
&& 0 == parm->s_pset.word2)
|
||
{
|
||
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
||
bool used_sql = false;
|
||
|
||
const bool has_flags = parm->s_fset.word[FLAG_WORD1] != 0
|
||
|| parm->s_fset.word[FLAG_WORD2] != 0
|
||
|| parm->s_fset.word[FLAG_WORD3] != 0;
|
||
const bool has_owner = parm->s_rst_owner != ANY_OWNER;
|
||
const bool has_type = parm->s_rst_type != NOTYPE;
|
||
const bool has_zone = parm->s_zone != NOTHING;
|
||
const bool has_parent = parm->s_parent != NOTHING;
|
||
|
||
// Case 1: Owner search (with optional type, no other filters).
|
||
//
|
||
if (has_owner && !has_flags && !has_zone && !has_parent)
|
||
{
|
||
if (sqldb.SearchByOwner(parm->s_rst_owner,
|
||
has_type ? static_cast<int>(parm->s_rst_type) : -1,
|
||
parm->low_bound, parm->high_bound,
|
||
[](dbref thing) { olist_add(thing); }))
|
||
{
|
||
used_sql = true;
|
||
}
|
||
}
|
||
|
||
// Case 2: Type-only search (no owner, no other filters).
|
||
//
|
||
else if (!has_owner && has_type && !has_flags && !has_zone && !has_parent)
|
||
{
|
||
if (sqldb.SearchByType(static_cast<int>(parm->s_rst_type),
|
||
parm->low_bound, parm->high_bound,
|
||
[](dbref thing) { olist_add(thing); }))
|
||
{
|
||
used_sql = true;
|
||
}
|
||
}
|
||
|
||
// Case 3: Zone search (no other filters).
|
||
//
|
||
else if (has_zone && !has_owner && !has_type && !has_flags && !has_parent)
|
||
{
|
||
if (sqldb.SearchByZone(parm->s_zone,
|
||
parm->low_bound, parm->high_bound,
|
||
[](dbref thing) { olist_add(thing); }))
|
||
{
|
||
used_sql = true;
|
||
}
|
||
}
|
||
|
||
// Case 4: Parent search (no other filters).
|
||
//
|
||
else if (has_parent && !has_owner && !has_type && !has_flags && !has_zone)
|
||
{
|
||
if (sqldb.SearchByParent(parm->s_parent,
|
||
parm->low_bound, parm->high_bound,
|
||
[](dbref thing) { olist_add(thing); }))
|
||
{
|
||
used_sql = true;
|
||
}
|
||
}
|
||
|
||
// Case 5: Flags-only search (no name, eval, power, zone, parent, owner).
|
||
//
|
||
else if (has_flags && !has_owner && !has_type && !has_zone && !has_parent)
|
||
{
|
||
if (sqldb.SearchByFlags(
|
||
parm->s_fset.word[FLAG_WORD1],
|
||
parm->s_fset.word[FLAG_WORD2],
|
||
parm->s_fset.word[FLAG_WORD3],
|
||
parm->low_bound, parm->high_bound,
|
||
[](dbref thing) { olist_add(thing); }))
|
||
{
|
||
used_sql = true;
|
||
}
|
||
}
|
||
|
||
if (used_sql)
|
||
{
|
||
free_sbuf(buff);
|
||
mudstate.func_invk_ctr = save_invk_ctr;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Full linear scan fallback for complex/combined searches.
|
||
//
|
||
dbref thing;
|
||
for (thing = parm->low_bound; thing <= parm->high_bound; thing++)
|
||
{
|
||
mudstate.func_invk_ctr = save_invk_ctr;
|
||
|
||
// Check for matching type.
|
||
//
|
||
if ( (parm->s_rst_type != NOTYPE)
|
||
&& (parm->s_rst_type != Typeof(thing)))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Check for matching owner.
|
||
//
|
||
if ( (parm->s_rst_owner != ANY_OWNER)
|
||
&& (parm->s_rst_owner != Owner(thing)))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Toss out destroyed things.
|
||
//
|
||
if (Going(thing))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Check for matching parent.
|
||
//
|
||
if ( (parm->s_parent != NOTHING)
|
||
&& (parm->s_parent != Parent(thing)))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Check for matching zone.
|
||
//
|
||
if ( (parm->s_zone != NOTHING)
|
||
&& (parm->s_zone != Zone(thing)))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Check for matching flags.
|
||
//
|
||
bool b = false;
|
||
for (int i = FLAG_WORD1; i <= FLAG_WORD3; i++)
|
||
{
|
||
FLAG f = parm->s_fset.word[i];
|
||
if ((db[thing].fs.word[i] & f) != f)
|
||
{
|
||
b = true;
|
||
break;
|
||
}
|
||
}
|
||
if (b)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Check for matching power.
|
||
//
|
||
thing1powers = Powers(thing);
|
||
thing2powers = Powers2(thing);
|
||
if ((thing1powers & parm->s_pset.word1) != parm->s_pset.word1)
|
||
{
|
||
continue;
|
||
}
|
||
if ((thing2powers & parm->s_pset.word2) != parm->s_pset.word2)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Check for matching name.
|
||
//
|
||
if (parm->s_rst_name != nullptr)
|
||
{
|
||
if (!string_prefix(PureName(thing), parm->s_rst_name))
|
||
continue;
|
||
}
|
||
|
||
// Check for successful evaluation.
|
||
//
|
||
if (parm->s_rst_eval != nullptr)
|
||
{
|
||
buff[0] = '#';
|
||
mux_ltoa(thing, buff+1);
|
||
bool bLoopInBounds = ( 0 <= mudstate.in_loop
|
||
&& mudstate.in_loop < MAX_ITEXT);
|
||
if (bLoopInBounds)
|
||
{
|
||
mudstate.itext[mudstate.in_loop] = buff;
|
||
mudstate.inum[mudstate.in_loop] = 0;
|
||
}
|
||
mudstate.in_loop++;
|
||
LBuf result = LBuf_Src("search_perform");
|
||
bp = result.get();
|
||
mux_exec(parm->s_rst_eval, LBUF_SIZE-1, result, &bp, executor, caller, enactor,
|
||
EV_FCHECK | EV_EVAL | EV_NOTRACE, nullptr, 0);
|
||
*bp = '\0';
|
||
mudstate.in_loop--;
|
||
if (bLoopInBounds)
|
||
{
|
||
mudstate.itext[mudstate.in_loop] = nullptr;
|
||
mudstate.inum[mudstate.in_loop] = 0;
|
||
}
|
||
if (!*result || !xlate(result))
|
||
{
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// It passed everything. Amazing.
|
||
//
|
||
olist_add(thing);
|
||
}
|
||
free_sbuf(buff);
|
||
mudstate.func_invk_ctr = save_invk_ctr;
|
||
}
|
||
|
||
static void search_mark(dbref player, int key)
|
||
{
|
||
dbref thing;
|
||
bool is_marked;
|
||
|
||
int nchanged = 0;
|
||
for (thing = olist_first(); thing != NOTHING; thing = olist_next())
|
||
{
|
||
is_marked = Marked(thing);
|
||
|
||
// Don't bother checking if marking and already marked (or if
|
||
// unmarking and not marked)
|
||
//
|
||
if ( ((key == SRCH_MARK) && is_marked)
|
||
|| ((key == SRCH_UNMARK) && !is_marked))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Toggle the mark bit and update the counters.
|
||
//
|
||
if (key == SRCH_MARK)
|
||
{
|
||
Mark(thing);
|
||
nchanged++;
|
||
}
|
||
else
|
||
{
|
||
Unmark(thing);
|
||
nchanged++;
|
||
}
|
||
}
|
||
// Two whole sentences rather than "%smarked" with %s = "" / "un"
|
||
// (#1717). A translator handed that %s cannot use it -- the prefix is
|
||
// English morphology, not a word -- and the count needs a plural form on
|
||
// top. Same rule as #1575 / #1588: mark the sentence, never a piece.
|
||
//
|
||
if (SRCH_MARK == key)
|
||
{
|
||
notify(player, tprintf(MN_("%d object marked",
|
||
"%d objects marked", nchanged), nchanged));
|
||
}
|
||
else
|
||
{
|
||
notify(player, tprintf(MN_("%d object unmarked",
|
||
"%d objects unmarked", nchanged), nchanged));
|
||
}
|
||
return;
|
||
}
|
||
|
||
void do_search(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *arg, const UTF8 *cargs[], int ncargs)
|
||
{
|
||
UNUSED_PARAMETER(eval);
|
||
UNUSED_PARAMETER(cargs);
|
||
UNUSED_PARAMETER(ncargs);
|
||
|
||
UTF8 *buff, *bp;
|
||
dbref thing, from, to;
|
||
SEARCH searchparm;
|
||
|
||
if ((key != SRCH_SEARCH) && (mudconf.control_flags & CF_DBCHECK))
|
||
{
|
||
er_mark_disabled(executor);
|
||
return;
|
||
}
|
||
if (!search_setup(executor, arg, &searchparm))
|
||
{
|
||
return;
|
||
}
|
||
olist_push();
|
||
search_perform(executor, caller, enactor, &searchparm);
|
||
bool destitute = true;
|
||
bool flag;
|
||
|
||
// If we are doing a @mark command, handle that here.
|
||
//
|
||
if (key != SRCH_SEARCH)
|
||
{
|
||
search_mark(executor, key);
|
||
olist_pop();
|
||
return;
|
||
}
|
||
LBuf outbuf = LBuf_Src("do_search.outbuf");
|
||
|
||
int rcount = 0;
|
||
int ecount = 0;
|
||
int tcount = 0;
|
||
int pcount = 0;
|
||
|
||
// Room search.
|
||
//
|
||
if ( searchparm.s_rst_type == TYPE_ROOM
|
||
|| searchparm.s_rst_type == NOTYPE)
|
||
{
|
||
flag = true;
|
||
for (thing = olist_first(); thing != NOTHING; thing = olist_next())
|
||
{
|
||
if (Typeof(thing) != TYPE_ROOM)
|
||
{
|
||
continue;
|
||
}
|
||
if (flag)
|
||
{
|
||
flag = false;
|
||
destitute = false;
|
||
notify(executor, M_("\nROOMS:"));
|
||
}
|
||
buff = unparse_object(executor, thing, false);
|
||
notify(executor, buff);
|
||
free_lbuf(buff);
|
||
rcount++;
|
||
}
|
||
}
|
||
|
||
// Exit search.
|
||
//
|
||
if ( searchparm.s_rst_type == TYPE_EXIT
|
||
|| searchparm.s_rst_type == NOTYPE)
|
||
{
|
||
flag = true;
|
||
for (thing = olist_first(); thing != NOTHING; thing = olist_next())
|
||
{
|
||
if (Typeof(thing) != TYPE_EXIT)
|
||
{
|
||
continue;
|
||
}
|
||
if (flag)
|
||
{
|
||
flag = false;
|
||
destitute = false;
|
||
notify(executor, M_("\nEXITS:"));
|
||
}
|
||
from = Exits(thing);
|
||
to = Location(thing);
|
||
|
||
bp = outbuf;
|
||
buff = unparse_object(executor, thing, false);
|
||
safe_str(buff, outbuf, &bp);
|
||
free_lbuf(buff);
|
||
|
||
safe_str(T(" [from "), outbuf, &bp);
|
||
buff = unparse_object(executor, from, false);
|
||
safe_str(((from == NOTHING) ? T("NOWHERE") : buff), outbuf, &bp);
|
||
free_lbuf(buff);
|
||
|
||
safe_str(T(" to "), outbuf, &bp);
|
||
buff = unparse_object(executor, to, false);
|
||
safe_str(((to == NOTHING) ? T("NOWHERE") : buff), outbuf, &bp);
|
||
free_lbuf(buff);
|
||
|
||
safe_chr(']', outbuf, &bp);
|
||
*bp = '\0';
|
||
notify(executor, outbuf);
|
||
ecount++;
|
||
}
|
||
}
|
||
|
||
// Object search
|
||
//
|
||
if ( searchparm.s_rst_type == TYPE_THING
|
||
|| searchparm.s_rst_type == NOTYPE)
|
||
{
|
||
flag = true;
|
||
for (thing = olist_first(); thing != NOTHING; thing = olist_next())
|
||
{
|
||
if (Typeof(thing) != TYPE_THING)
|
||
{
|
||
continue;
|
||
}
|
||
if (flag)
|
||
{
|
||
flag = false;
|
||
destitute = false;
|
||
notify(executor, M_("\nOBJECTS:"));
|
||
}
|
||
bp = outbuf;
|
||
buff = unparse_object(executor, thing, false);
|
||
safe_str(buff, outbuf, &bp);
|
||
free_lbuf(buff);
|
||
|
||
safe_str(T(" [owner: "), outbuf, &bp);
|
||
buff = unparse_object(executor, Owner(thing), false);
|
||
safe_str(buff, outbuf, &bp);
|
||
free_lbuf(buff);
|
||
|
||
safe_chr(']', outbuf, &bp);
|
||
*bp = '\0';
|
||
notify(executor, outbuf);
|
||
tcount++;
|
||
}
|
||
}
|
||
|
||
// Player search
|
||
//
|
||
if ( searchparm.s_rst_type == TYPE_PLAYER
|
||
|| searchparm.s_rst_type == NOTYPE)
|
||
{
|
||
flag = true;
|
||
for (thing = olist_first(); thing != NOTHING; thing = olist_next())
|
||
{
|
||
if (Typeof(thing) != TYPE_PLAYER)
|
||
{
|
||
continue;
|
||
}
|
||
if (flag)
|
||
{
|
||
flag = false;
|
||
destitute = false;
|
||
notify(executor, M_("\nPLAYERS:"));
|
||
}
|
||
bp = outbuf;
|
||
buff = unparse_object(executor, thing, 0);
|
||
safe_str(buff, outbuf, &bp);
|
||
free_lbuf(buff);
|
||
if (searchparm.s_wizard)
|
||
{
|
||
safe_str(T(" [location: "), outbuf, &bp);
|
||
buff = unparse_object(executor, Location(thing), false);
|
||
safe_str(buff, outbuf, &bp);
|
||
free_lbuf(buff);
|
||
safe_chr(']', outbuf, &bp);
|
||
}
|
||
*bp = '\0';
|
||
notify(executor, outbuf);
|
||
pcount++;
|
||
}
|
||
}
|
||
|
||
// If nothing found matching search criteria.
|
||
//
|
||
if (destitute)
|
||
{
|
||
notify(executor, M_("Nothing found."));
|
||
}
|
||
else
|
||
{
|
||
mux_sprintf(outbuf, LBUF_SIZE,
|
||
M_("\nFound: Rooms...%d Exits...%d Objects...%d Players...%d"),
|
||
rcount, ecount, tcount, pcount);
|
||
notify(executor, outbuf);
|
||
}
|
||
olist_pop();
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// do_markall: set or clear the mark bits of all objects in the db.
|
||
//
|
||
void do_markall(dbref executor, dbref caller, dbref enactor, int eval, int key)
|
||
{
|
||
UNUSED_PARAMETER(caller);
|
||
UNUSED_PARAMETER(enactor);
|
||
UNUSED_PARAMETER(eval);
|
||
|
||
int i;
|
||
|
||
if (mudconf.control_flags & CF_DBCHECK)
|
||
{
|
||
er_mark_disabled(executor);
|
||
return;
|
||
}
|
||
if (key == MARK_SET)
|
||
{
|
||
Mark_all(i);
|
||
}
|
||
else if (key == MARK_CLEAR)
|
||
{
|
||
Unmark_all(i);
|
||
}
|
||
if (!Quiet(executor))
|
||
{
|
||
notify(executor, M_("Done."));
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// do_apply_marked: Perform a command for each marked obj in the db.
|
||
//
|
||
void do_apply_marked( dbref executor, dbref caller, dbref enactor, int eval,
|
||
int key, UTF8 *command, const UTF8 *cargs[], int ncargs)
|
||
{
|
||
UNUSED_PARAMETER(eval);
|
||
UNUSED_PARAMETER(key);
|
||
|
||
if (mudconf.control_flags & CF_DBCHECK)
|
||
{
|
||
er_mark_disabled(executor);
|
||
return;
|
||
}
|
||
UTF8 *buff = alloc_sbuf("do_apply_marked");
|
||
int i;
|
||
int number = 0;
|
||
DO_WHOLE_DB(i)
|
||
{
|
||
if (Marked(i))
|
||
{
|
||
buff[0] = '#';
|
||
mux_ltoa(i, buff+1);
|
||
number++;
|
||
bind_and_queue(executor, caller, enactor, eval, command, buff,
|
||
cargs, ncargs, number);
|
||
}
|
||
}
|
||
free_sbuf(buff);
|
||
if (!Quiet(executor))
|
||
{
|
||
notify(executor, M_("Done."));
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Object list management routines: olist_push, olist_pop, olist_add,
|
||
// olist_first, olist_next
|
||
//
|
||
|
||
// olist_push: Create a new object list at the top of the object list stack.
|
||
//
|
||
void olist_push(void)
|
||
{
|
||
try
|
||
{
|
||
mudstate.olist.emplace();
|
||
}
|
||
catch (const std::bad_alloc &)
|
||
{
|
||
STARTLOG(LOG_PROBLEMS, "OBJ", "MEM");
|
||
log_printf(T("olist_push: out of memory."));
|
||
ENDLOG;
|
||
}
|
||
}
|
||
|
||
// olist_pop: Pop one entire list off the object list stack.
|
||
//
|
||
void olist_pop(void)
|
||
{
|
||
if (!mudstate.olist.empty())
|
||
{
|
||
mudstate.olist.pop();
|
||
}
|
||
}
|
||
|
||
// olist_add: Add an entry to the object list.
|
||
//
|
||
void olist_add(dbref item)
|
||
{
|
||
if (mudstate.olist.empty())
|
||
{
|
||
olist_push();
|
||
}
|
||
mudstate.olist.top().entries.push_back(item);
|
||
}
|
||
|
||
// olist_first: Return the first entry in the object list.
|
||
//
|
||
dbref olist_first(void)
|
||
{
|
||
if (mudstate.olist.empty())
|
||
{
|
||
return NOTHING;
|
||
}
|
||
auto ¤t = mudstate.olist.top();
|
||
if (current.entries.empty())
|
||
{
|
||
return NOTHING;
|
||
}
|
||
current.cursor = 0;
|
||
return current.entries[current.cursor++];
|
||
}
|
||
|
||
dbref olist_next(void)
|
||
{
|
||
if (mudstate.olist.empty())
|
||
{
|
||
return NOTHING;
|
||
}
|
||
auto ¤t = mudstate.olist.top();
|
||
if (current.cursor >= current.entries.size())
|
||
{
|
||
return NOTHING;
|
||
}
|
||
return current.entries[current.cursor++];
|
||
}
|
||
|
||
#define HOURS_PER_PERIOD 4
|
||
#define DAYS_PER_REPORT (2*7)
|
||
#define NPERIODS (24*DAYS_PER_REPORT/HOURS_PER_PERIOD)
|
||
|
||
void do_report(dbref executor, dbref caller, dbref enactor, int eval, int key)
|
||
{
|
||
UNUSED_PARAMETER(caller);
|
||
UNUSED_PARAMETER(enactor);
|
||
UNUSED_PARAMETER(eval);
|
||
UNUSED_PARAMETER(key);
|
||
|
||
UTF8 *buff = alloc_mbuf("do_report");
|
||
int nBin[NPERIODS];
|
||
int i;
|
||
|
||
for (i = 0; i < NPERIODS; i++)
|
||
{
|
||
nBin[i] = 0;
|
||
}
|
||
|
||
CLinearTimeAbsolute ltaNow, ltaPlayer;
|
||
ltaNow.GetLocal();
|
||
|
||
const int SecondsPerPeriod = HOURS_PER_PERIOD*60*60;
|
||
|
||
int iPlayer;
|
||
DO_WHOLE_DB(iPlayer)
|
||
{
|
||
if (isPlayer(iPlayer))
|
||
{
|
||
int aowner, aflags;
|
||
LBuf player_last = LBuf_Adopt(atr_get("do_report.43", iPlayer, A_LAST, &aowner, &aflags));
|
||
|
||
if (ltaPlayer.SetString(player_last))
|
||
{
|
||
CLinearTimeDelta ltd(ltaPlayer, ltaNow);
|
||
int ltdSeconds = ltd.ReturnSeconds();
|
||
int iBin = ltdSeconds / SecondsPerPeriod;
|
||
if (0 <= iBin && iBin < NPERIODS)
|
||
{
|
||
nBin[iBin]++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
int iHour, nSum = 0;
|
||
// Activity report (#1667 Phase 4 C8) — freeform numeric columns with
|
||
// separate header labels (no pre-spaced blob).
|
||
//
|
||
{
|
||
UTF8 header[MBUF_SIZE];
|
||
size_t pos = 0;
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos,
|
||
reinterpret_cast<const char *>(M_("Day")));
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos,
|
||
reinterpret_cast<const char *>(M_("Hours")));
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos,
|
||
reinterpret_cast<const char *>(M_("Players")));
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
||
pos = mux_table_append_bytes(header, sizeof(header), pos,
|
||
reinterpret_cast<const char *>(M_("Total")));
|
||
notify(executor, header);
|
||
}
|
||
for (i = 0, iHour = 0; i < NPERIODS; i++, iHour += HOURS_PER_PERIOD)
|
||
{
|
||
nSum += nBin[i];
|
||
mux_sprintf(buff, MBUF_SIZE, T("%3d %03d - %03d: %6d %6d"),
|
||
iHour/24 + 1, iHour, iHour + HOURS_PER_PERIOD, nBin[i], nSum);
|
||
notify(executor, buff);
|
||
}
|
||
free_mbuf(buff);
|
||
}
|