/*! \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(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 =,, // UTF8 *pname = parse_to(&searchfor, '=', EV_STRIP_TS); if (!pname || !*pname) { pname = const_cast(T("me")); } else { size_t nCased; pname = mux_strlwr(pname, nCased); } UTF8 *searchtype; if (searchfor && *searchfor) { searchtype = reinterpret_cast(strrchr(reinterpret_cast(pname), ' ')); if (searchtype) { *searchtype++ = '\0'; } else { searchtype = pname; pname = const_cast(T("")); } } else { searchtype = const_cast(T("")); } // If the player name is quoted, strip the quotes. // if (*pname == '"') { size_t k = strlen(reinterpret_cast(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(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(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(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(M_("Day"))); pos = mux_table_append_bytes(header, sizeof(header), pos, " "); pos = mux_table_append_bytes(header, sizeof(header), pos, reinterpret_cast(M_("Hours"))); pos = mux_table_append_bytes(header, sizeof(header), pos, " "); pos = mux_table_append_bytes(header, sizeof(header), pos, reinterpret_cast(M_("Players"))); pos = mux_table_append_bytes(header, sizeof(header), pos, " "); pos = mux_table_append_bytes(header, sizeof(header), pos, reinterpret_cast(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); }