tinymux/mux/modules/engine/set.cpp

2509 lines
62 KiB
C++
Raw Permalink Normal View History

/*! \file set.cpp
* \brief Commands which modify objects.
*
* These functions primarily implement commands like \@set and
* \@name that modify attributes or basic properties of an object,
* but this file also includes \@trigger and the use command.
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#include "routing.h"
void set_modified(dbref thing)
{
CLinearTimeAbsolute ltaNow;
ltaNow.GetLocal();
atr_add_raw(thing, A_MODIFIED, ltaNow.ReturnDateString(7));
}
dbref match_controlled_handler(dbref executor, const UTF8 *name, bool bQuiet)
{
dbref mat;
init_match(executor, name, NOTYPE);
match_everything(MAT_EXIT_PARENTS);
if (bQuiet)
{
mat = match_result();
}
else
{
mat = noisy_match_result();
}
if (!Good_obj(mat))
{
return mat;
}
if (Controls(executor, mat))
{
if (NoModify(mat) && !WizRoy(executor))
{
if (!bQuiet)
{
notify_quiet(executor, NOPERM_MESSAGE);
}
return NOTHING;
}
return mat;
}
if (!bQuiet)
{
notify_quiet(executor, NOPERM_MESSAGE);
}
return NOTHING;
}
void do_chzone
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *newobj,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if (!mudconf.have_zones)
{
notify(executor, M_("Zones disabled."));
return;
}
init_match(executor, name, NOTYPE);
match_everything(0);
dbref thing = noisy_match_result();
if (thing == NOTHING)
{
return;
}
dbref zone;
if ( newobj[0] == '\0'
|| !mux_stricmp(newobj, T("none")))
{
zone = NOTHING;
}
else
{
init_match(executor, newobj, NOTYPE);
match_everything(0);
zone = noisy_match_result();
if (zone == NOTHING)
{
return;
}
if ( !isThing(zone)
&& !isRoom(zone))
{
notify(executor, M_("Invalid zone object type."));
return;
}
}
if ( !Wizard(executor)
&& !Controls(executor, thing)
&& !check_zone_handler(executor, thing, true)
&& db[executor].owner != db[thing].owner)
{
notify(executor, M_("You dont have the power to shift reality."));
return;
}
if (NoModify(thing) && !WizRoy(executor))
{
notify(executor, NOPERM_MESSAGE);
return;
}
// A player may change an object's zone to NOTHING or to an object he owns.
//
if ( zone != NOTHING
&& !Wizard(executor)
&& !Controls(executor, zone)
&& db[executor].owner != db[zone].owner)
{
notify(executor, M_("You cannot move that object to that zone."));
return;
}
// Only rooms may be zoned to other rooms.
//
if ( zone != NOTHING
&& isRoom(zone)
&& !isRoom(thing))
{
notify(executor, M_("Only rooms may be zoned to other rooms."));
return;
}
// Everything is okay, do the change.
//
dbref old_zone = Zone(thing);
s_Zone(thing, zone);
// If a room changed zones, invalidate both the old and new zone
// routing tables plus the inter-zone meta-table.
//
if (isRoom(thing) && Navigable(thing) && old_zone != zone)
{
route_invalidate_zone(old_zone);
route_invalidate_zone(zone);
route_invalidate_meta();
}
if (!isPlayer(thing))
{
// If the object is a player, resetting these flags is rather
// inconvenient -- although this may pose a bit of a security risk. Be
// careful when @chzone'ing wizard or royal players.
//
2018-10-03 17:54:51 +00:00
SetClearFlags(thing, mudconf.stripped_flags.word, nullptr);
// Wipe out all powers.
//
Powers(thing) = 0;
Powers2(thing) = 0;
}
notify(executor, M_("Zone changed."));
}
void do_name
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *newname,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing = match_controlled(executor, name);
if (NOTHING == thing)
{
return;
}
// Check for bad name.
//
if ( nargs < 2
|| '\0' == newname[0])
{
notify_quiet(executor, M_("Give it what new name?"));
return;
}
// Check for renaming a player.
//
if (isPlayer(thing))
{
UTF8 *buff = trim_spaces(newname);
if ( !ValidatePlayerName(buff)
|| !badname_check(buff))
{
notify_quiet(executor, M_("You cant use that name."));
free_lbuf(buff);
return;
}
Feature: Add MSSP, GMCP, @protect, and benchmark() Four new features identified from the TinyMUSH/PennMUSH/RhostMUSH survey: MSSP (MUD Server Status Protocol, telnet option 70): - Server sends structured key-value data (NAME, PLAYERS, UPTIME, PORT, CODEBASE, FAMILY) to MU* directory crawlers on IAC DO MSSP - Stateless response via send_mssp() in telnet.cpp using g_dc config basket - start_time_utc added to DRIVER_CONFIG for uptime calculation GMCP (Generic MUD Communication Protocol, telnet option 201): - Protocol negotiation: server offers WILL GMCP, tracks gmcp_enabled per DESC - Inbound: GMCP subneg queued as synthetic "\x01GMCP" command, dispatched to handle_gmcp() which fires A_GMCP attribute with %0=package %1=json - Outbound: gmcp(<player>, <package>, <json>) softcode function sends GMCP frames to all GMCP-enabled descriptors via SendGmcp COM method - Full COM architecture: mux_IConnectionManager::SendGmcp in driver, send_gmcp() bridge in engine, CConnectionManager impl in modules.cpp @protect (player name reservation): - @protect[/add] <name>, @protect/del <name>, @protect/list [<player>] - A_PROTECTNAME attribute (234) stores space-separated protected names - protectname_check() hooked into create_player() and do_name() - max_name_protect config param (default 5) benchmark(<expression>, <iterations>): - FN_NOEVAL, CA_PUBLIC, 10000 iteration cap - Uses clock_gettime(CLOCK_MONOTONIC) / QueryPerformanceCounter - Returns elapsed seconds as floating point Also fixes: add unicode_tables.c to libmux.so LIBMUX_C_SRC (resolves pre-existing tr_tolower_sbt etc. link errors). 505/505 smoke tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 22:08:55 -06:00
if (!protectname_check(buff, thing))
{
notify_quiet(executor, M_("That name is protected by another player."));
Feature: Add MSSP, GMCP, @protect, and benchmark() Four new features identified from the TinyMUSH/PennMUSH/RhostMUSH survey: MSSP (MUD Server Status Protocol, telnet option 70): - Server sends structured key-value data (NAME, PLAYERS, UPTIME, PORT, CODEBASE, FAMILY) to MU* directory crawlers on IAC DO MSSP - Stateless response via send_mssp() in telnet.cpp using g_dc config basket - start_time_utc added to DRIVER_CONFIG for uptime calculation GMCP (Generic MUD Communication Protocol, telnet option 201): - Protocol negotiation: server offers WILL GMCP, tracks gmcp_enabled per DESC - Inbound: GMCP subneg queued as synthetic "\x01GMCP" command, dispatched to handle_gmcp() which fires A_GMCP attribute with %0=package %1=json - Outbound: gmcp(<player>, <package>, <json>) softcode function sends GMCP frames to all GMCP-enabled descriptors via SendGmcp COM method - Full COM architecture: mux_IConnectionManager::SendGmcp in driver, send_gmcp() bridge in engine, CConnectionManager impl in modules.cpp @protect (player name reservation): - @protect[/add] <name>, @protect/del <name>, @protect/list [<player>] - A_PROTECTNAME attribute (234) stores space-separated protected names - protectname_check() hooked into create_player() and do_name() - max_name_protect config param (default 5) benchmark(<expression>, <iterations>): - FN_NOEVAL, CA_PUBLIC, 10000 iteration cap - Uses clock_gettime(CLOCK_MONOTONIC) / QueryPerformanceCounter - Returns elapsed seconds as floating point Also fixes: add unicode_tables.c to libmux.so LIBMUX_C_SRC (resolves pre-existing tr_tolower_sbt etc. link errors). 505/505 smoke tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 22:08:55 -06:00
free_lbuf(buff);
return;
}
else
{
bool bAlias = false;
dbref jwho = lookup_player_name(buff, bAlias);
if ( NOTHING != jwho
&& ( jwho != thing
|| bAlias))
{
notify_quiet(executor, M_("That name is already in use."));
free_lbuf(buff);
return;
}
}
// Everything ok, notify.
//
STARTLOG(LOG_SECURITY, "SEC", "CNAME");
log_name(thing),
log_text(T(" renamed to "));
log_text(buff);
ENDLOG;
if (Suspect(thing))
{
raw_broadcast(WIZARD, M_("[Suspect] %s renamed to %s"), Name(thing), buff);
}
delete_player_name(thing, Name(thing), false);
s_Name(thing, buff);
set_modified(thing);
add_player_name(thing, Name(thing), false);
if (!Quiet(executor) && !Quiet(thing))
{
notify_quiet(executor, M_("Name set."));
}
free_lbuf(buff);
return;
}
else
{
size_t nValidName;
bool bValid;
UTF8 *pValidName;
if (isExit(thing))
{
pValidName = MakeCanonicalExitName(newname, &nValidName, &bValid);
}
else if (isThing(thing))
{
pValidName = MakeCanonicalObjectName(newname, &nValidName, &bValid, mudconf.thing_name_charset);
}
else
{
pValidName = MakeCanonicalObjectName(newname, &nValidName, &bValid, mudconf.room_name_charset);
}
if (!bValid)
{
notify_quiet(executor, M_("That is not a reasonable name."));
return;
}
// Everything ok, change the name.
//
s_Name(thing, pValidName);
set_modified(thing);
if (!Quiet(executor) && !Quiet(thing))
{
notify_quiet(executor, M_("Name set."));
}
}
}
/*
* ---------------------------------------------------------------------------
* * do_alias: Make an alias for a player or object.
*/
void do_alias
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *alias,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing = match_controlled(executor, name);
if (NOTHING == thing)
{
return;
}
// Check for renaming a player.
//
dbref aowner;
int aflags;
ATTR *ap = atr_num(A_ALIAS);
if (isPlayer(thing))
{
// Fetch the old alias.
//
LBuf oldalias = LBuf_Adopt(atr_pget(thing, A_ALIAS, &aowner, &aflags));
UTF8 *trimalias = trim_spaces(alias);
dbref nPlayer;
bool bAlias = false;
if (!Controls(executor, thing))
{
// Make sure we have rights to do it. We can't do the
// normal Set_attr check because ALIAS is only
// writable by GOD and we want to keep people from
// doing &ALIAS and bypassing the player name checks.
//
notify_quiet(executor, NOPERM_MESSAGE);
}
else if (!*trimalias)
{
// New alias is null, just clear it.
//
delete_player_name(thing, oldalias, true);
atr_clr(thing, A_ALIAS);
if (!Quiet(executor))
{
notify_quiet(executor, M_("Alias removed."));
}
}
else if ( (nPlayer = lookup_player_name(trimalias, bAlias)) != NOTHING
&& ( nPlayer != thing
|| !bAlias))
{
// Make sure new alias isn't already in use.
//
notify_quiet(executor, M_("That name is already in use."));
}
2008-05-16 15:25:12 -07:00
else if ( !( badname_check(trimalias)
&& ValidatePlayerName(trimalias)))
{
notify_quiet(executor, M_("Thats a silly name for a player!"));
}
else
{
// Remove the old name and add the new name.
//
delete_player_name(thing, oldalias, true);
atr_add(thing, A_ALIAS, trimalias, Owner(executor), aflags);
if (add_player_name(thing, trimalias, true))
{
if (!Quiet(executor))
{
notify_quiet(executor, M_("Alias set."));
}
}
else
{
notify_quiet(executor,
T("That name is already in use or is illegal, alias cleared."));
atr_clr(thing, A_ALIAS);
}
}
free_lbuf(trimalias);
}
else
{
atr_pget_info(thing, A_ALIAS, &aowner, &aflags);
// Make sure we have rights to do it.
//
if (!bCanSetAttr(executor, thing, ap))
{
notify_quiet(executor, NOPERM_MESSAGE);
}
else
{
atr_add(thing, A_ALIAS, alias, Owner(executor), aflags);
if (!Quiet(executor))
{
notify_quiet(executor, M_("Set."));
}
}
}
}
// ---------------------------------------------------------------------------
// do_forwardlist: Set a forwardlist.
// ---------------------------------------------------------------------------
void do_forwardlist
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *target,
UTF8 *newlist,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing = match_controlled(executor, target);
if (thing == NOTHING)
{
return;
}
dbref aowner, aflags;
atr_pget_info(thing, A_FORWARDLIST, &aowner, &aflags);
if (!Controls(executor, thing))
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
else if (!*newlist)
{
// New forwardlist is null, just clear it.
//
atr_clr(thing, A_FORWARDLIST);
set_modified(thing);
if (!Quiet(executor))
{
notify_quiet(executor, M_("Forwardlist removed."));
}
}
else if (!fwdlist_ck(executor, thing, A_FORWARDLIST, newlist))
{
notify_quiet(executor, M_("Invalid forwardlist."));
return;
}
else
{
atr_add(thing, A_FORWARDLIST, newlist, Owner(executor), aflags);
if (!Quiet(executor))
{
notify_quiet(executor, M_("Set."));
}
}
}
/*
* ---------------------------------------------------------------------------
* * do_lock: Set a lock on an object or attribute.
*/
void do_lock
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *keytext,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing;
ATTR *ap;
if ( parse_attrib(executor, name, &thing, &ap)
&& ap)
{
dbref aowner;
int aflags;
if (!atr_get_info(thing, ap->number, &aowner, &aflags))
{
notify_quiet(executor, M_("Attribute not present on object."));
return;
}
if (bCanLockAttr(executor, thing, ap))
{
aflags |= AF_LOCK;
atr_set_flags(thing, ap->number, aflags);
if ( !Quiet(executor)
&& !Quiet(thing))
{
notify_quiet(executor, M_("Attribute locked."));
}
}
else
{
notify_quiet(executor, NOPERM_MESSAGE);
}
return;
}
init_match(executor, name, NOTYPE);
match_everything(MAT_EXIT_PARENTS);
thing = match_result();
switch (thing)
{
case NOTHING:
notify_quiet(executor, M_("I dont see what you want to lock!"));
return;
case AMBIGUOUS:
notify_quiet(executor, M_("I dont know which one you want to lock!"));
return;
default:
if (!Controls(executor, thing))
{
notify_quiet(executor, M_("You cant lock that!"));
return;
}
if (NoModify(thing) && !WizRoy(executor))
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
}
Convert all static scratch buffers to thread_local 43 static scratch-buffer arrays across 21 files (5 in mux/src, 38 in mux/modules/) changed from `static` to `thread_local`. Under the current single-threaded evaluator this is a zero-behavior-change swap — `thread_local` storage has the same lifetime and zero-allocation properties as `static` — but each thread gets its own copy, which makes these functions safe for a future multi-threaded evaluator without any locking. Read-only constant tables (`aRadix64`, `aRadixPenn36`, `aRadixPenn64`, `Empty`) left as `static` because they are immutable shared data. Affected areas: net.cpp — queue_string co_buf, trimmed_site, dump_users NameField signals.cpp — signal_desc stubslave.cpp — Stub_PipePump attrcache.cpp — sqlite_attr_buf boolexp.cpp — parsestore command.cpp — preserve_cmd, SpaceCompressCommand, LowerCaseCommand comsys.cpp — NewTitle, Buffer, temp db.cpp — tbuff, Buffer (x2) flags.cpp — buff funceval.cpp — textbuff functions.cpp — TimeBuffer64, TimeBuffer80, Buffer help.cpp — Line, Buffer mail.cpp — aFolders, Buffer, res, szFittedMailAliasDesc match.cpp — buffer player.cpp — szSalt, buf (x2), buff plusemail.cpp — buf predicates.cpp — Buf (x2), pName session.cpp — szFittedDoing set.cpp — pRestrictedKeyText unparse.cpp — buf, boolexp_buf mail_mod.cpp — result, res, buf All 21 files verified with g++ -std=c++17 -fsyntax-only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:21:14 -06:00
thread_local UTF8 pRestrictedKeyText[LBUF_SIZE];
StripTabsAndTruncate(keytext, pRestrictedKeyText, LBUF_SIZE-1, LBUF_SIZE-1);
struct boolexp *okey = parse_boolexp(executor, pRestrictedKeyText, false);
if (okey == TRUE_BOOLEXP)
{
notify_quiet(executor, M_("I dont understand that key."));
}
else
{
// Everything ok, do it.
//
if (!key)
{
key = A_LOCK;
}
atr_add_raw(thing, key, unparse_boolexp_quiet(executor, okey));
if ( !Quiet(executor)
&& !Quiet(thing))
{
notify_quiet(executor, M_("Locked."));
}
}
free_boolexp(okey);
}
/*
* ---------------------------------------------------------------------------
* * Remove a lock from an object of attribute.
*/
void do_unlock(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 thing;
ATTR *ap;
if ( parse_attrib(executor, name, &thing, &ap)
&& ap)
{
// We have been asked to unlock an attribute.
//
dbref aowner;
int aflags;
if (!atr_get_info(thing, ap->number, &aowner, &aflags))
{
notify_quiet(executor, M_("Attribute not present on object."));
return;
}
if (bCanLockAttr(executor, thing, ap))
{
aflags &= ~AF_LOCK;
atr_set_flags(thing, ap->number, aflags);
if ( !Quiet(executor)
&& !Quiet(thing))
{
notify_quiet(executor, M_("Attribute unlocked."));
}
}
else
{
notify_quiet(executor, NOPERM_MESSAGE);
}
return;
}
// We have been asked to change the ownership of an object.
//
if (!key)
{
key = A_LOCK;
}
thing = match_controlled(executor, name);
if (thing != NOTHING)
{
atr_clr(thing, key);
set_modified(thing);
if (!Quiet(executor) && !Quiet(thing))
{
notify_quiet(executor, M_("Unlocked."));
}
}
}
/*
* ---------------------------------------------------------------------------
* * do_unlink: Unlink an exit from its destination or remove a dropto.
*/
void do_unlink(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(key);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref exit;
init_match(executor, name, TYPE_EXIT);
match_everything(0);
exit = match_result();
switch (exit)
{
case NOTHING:
notify_quiet(executor, M_("Unlink what?"));
break;
case AMBIGUOUS:
notify_quiet(executor, M_("I dont know which one you mean!"));
break;
default:
if (!Controls(executor, exit))
{
notify_quiet(executor, NOPERM_MESSAGE);
}
else if (NoModify(exit) && !WizRoy(executor))
{
notify_quiet(executor, NOPERM_MESSAGE);
}
else
{
switch (Typeof(exit))
{
case TYPE_EXIT:
s_Location(exit, NOTHING);
if (!Quiet(executor))
{
notify_quiet(executor, M_("Unlinked."));
}
giveto(Owner(exit), mudconf.linkcost);
route_invalidate();
break;
case TYPE_ROOM:
s_Dropto(exit, NOTHING);
if (!Quiet(executor))
{
notify_quiet(executor, M_("Dropto removed."));
}
break;
default:
notify_quiet(executor, M_("You cant unlink that!"));
break;
}
}
}
}
void TranslateFlags_Clone
(
FLAG aClearFlags[3],
dbref executor,
int key
)
{
if (key & CLONE_NOSTRIP)
{
if (God(executor))
{
// #1 using /nostrip causes nothing to be stripped.
//
aClearFlags[FLAG_WORD1] = 0;
}
else
{
// With #1 powers, /nostrip still strips the WIZARD bit.
//
aClearFlags[FLAG_WORD1] = WIZARD;
}
aClearFlags[FLAG_WORD2] = 0;
aClearFlags[FLAG_WORD3] = 0;
}
else
{
if ( (key & CLONE_INHERIT)
&& Inherits(executor))
{
// The /inherit switch specifically allows INHERIT powers through.
//
aClearFlags[FLAG_WORD1] = (WIZARD | mudconf.stripped_flags.word[FLAG_WORD1]) & ~(INHERIT);
}
else
{
// Normally WIZARD and the other stripped_flags are removed.
//
aClearFlags[FLAG_WORD1] = WIZARD | mudconf.stripped_flags.word[FLAG_WORD1];
}
aClearFlags[FLAG_WORD2] = mudconf.stripped_flags.word[FLAG_WORD2];
aClearFlags[FLAG_WORD3] = mudconf.stripped_flags.word[FLAG_WORD3];
}
}
void TranslateFlags_Chown
(
FLAG aClearFlags[3],
FLAG aSetFlags[3],
bool *bClearPowers,
dbref executor,
int key
)
{
if (key & CHOWN_NOSTRIP)
{
if (God(executor))
{
// #1 using /nostrip only clears CHOWN_OK and sets HALT.
//
aClearFlags[FLAG_WORD1] = CHOWN_OK;
*bClearPowers = false;
}
else
{
// With /nostrip, CHOWN_OK and WIZARD are cleared, HALT is
// set, and powers are cleared.
//
aClearFlags[FLAG_WORD1] = CHOWN_OK | WIZARD;
*bClearPowers = true;
}
aClearFlags[FLAG_WORD2] = 0;
aClearFlags[FLAG_WORD3] = 0;
}
else
{
// Without /nostrip, CHOWN_OK, WIZARD, and stripped_flags are
// cleared, HALT is set, powers are cleared.
//
aClearFlags[FLAG_WORD1] = CHOWN_OK | WIZARD
| mudconf.stripped_flags.word[FLAG_WORD1];
aClearFlags[FLAG_WORD2] = mudconf.stripped_flags.word[FLAG_WORD2];
aClearFlags[FLAG_WORD3] = mudconf.stripped_flags.word[FLAG_WORD3];
*bClearPowers = true;
}
aSetFlags[FLAG_WORD1] = HALT;
aSetFlags[FLAG_WORD2] = 0;
aSetFlags[FLAG_WORD3] = 0;
}
void SetClearFlags
(
dbref thing,
FLAG aClearFlags[3],
FLAG aSetFlags[3]
)
{
int j;
for (j = FLAG_WORD1; j <= FLAG_WORD3; j++)
{
FLAG newval = db[thing].fs.word[j];
2018-10-03 17:54:51 +00:00
if (nullptr != aClearFlags)
{
newval &= ~aClearFlags[j];
}
2018-10-03 17:54:51 +00:00
if (nullptr != aSetFlags)
{
newval |= aSetFlags[j];
}
s_Flags(thing, j, newval);
}
}
/*
* ---------------------------------------------------------------------------
* * do_chown: Change ownership of an object or attribute.
*/
void do_chown
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *newown,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref nOwnerOrig, nOwnerNew, thing;
bool bDoit;
ATTR *ap;
if ( parse_attrib(executor, name, &thing, &ap)
&& ap
&& See_attr(executor, thing, ap))
{
// An attribute was given, so we worry about changing the owner of the
// attribute.
//
nOwnerOrig = Owner(thing);
if (!*newown)
{
nOwnerNew = nOwnerOrig;
}
else if (!string_compare(newown, T("me")))
{
nOwnerNew = Owner(executor);
}
else
{
nOwnerNew = lookup_player(executor, newown, true);
}
// You may chown an attr to yourself if you own the object and the attr
// is not locked. You may chown an attr to the owner of the object if
// you own the attribute. To do anything else you must be a wizard.
// Only #1 can chown attributes on #1.
//
dbref aowner;
int aflags;
if (!atr_get_info(thing, ap->number, &aowner, &aflags))
{
notify_quiet(executor, M_("Attribute not present on object."));
return;
}
bDoit = false;
if (nOwnerNew == NOTHING)
{
notify_quiet(executor, M_("I couldnt find that player."));
}
else if ( God(thing)
&& !God(executor))
{
notify_quiet(executor, NOPERM_MESSAGE);
}
else if (Wizard(executor))
{
bDoit = true;
}
else if (nOwnerNew == Owner(executor))
{
// Chown to me: only if I own the obj and !locked
//
if ( !Controls(executor, thing)
|| (aflags & AF_LOCK))
{
notify_quiet(executor, NOPERM_MESSAGE);
}
else
{
bDoit = true;
}
}
else if (nOwnerNew == nOwnerOrig)
{
// chown to obj owner: only if I own attr and !locked
//
if ( Owner(executor) != aowner
|| (aflags & AF_LOCK))
{
notify_quiet(executor, NOPERM_MESSAGE);
}
else
{
bDoit = true;
}
}
else
{
notify_quiet(executor, NOPERM_MESSAGE);
}
if (!bDoit)
{
return;
}
if (!bCanSetAttr(executor, executor, ap))
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
LBuf buff = LBuf_Adopt(atr_get("do_chown.887", thing, ap->number, &aowner, &aflags));
atr_add(thing, ap->number, buff, nOwnerNew, aflags);
if (!Quiet(executor))
{
notify_quiet(executor, M_("Attribute owner changed."));
}
return;
}
// An attribute was not specified, so we are being asked to change the
// owner of the object.
//
init_match(executor, name, TYPE_THING);
match_possession();
match_here();
match_exit();
match_me();
if (Chown_Any(executor))
{
match_player();
match_absolute();
}
switch (thing = match_result())
{
case NOTHING:
notify_quiet(executor, M_("You dont have that!"));
return;
case AMBIGUOUS:
notify_quiet(executor, M_("I dont know which you mean!"));
return;
}
nOwnerOrig = Owner(thing);
if (NoModify(thing) && !WizRoy(executor))
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
if (!*newown || !(string_compare(newown, T("me"))))
{
nOwnerNew = Owner(executor);
}
else
{
nOwnerNew = lookup_player(executor, newown, true);
}
int cost = 1, quota = 1;
switch (Typeof(thing))
{
case TYPE_ROOM:
cost = mudconf.digcost;
quota = mudconf.room_quota;
break;
case TYPE_THING:
cost = OBJECT_DEPOSIT(Pennies(thing));
quota = mudconf.thing_quota;
break;
case TYPE_EXIT:
cost = mudconf.opencost;
quota = mudconf.exit_quota;
break;
case TYPE_PLAYER:
cost = mudconf.robotcost;
quota = mudconf.player_quota;
break;
}
bool bPlayerControlsThing = Controls(executor, thing);
if ( isGarbage(thing)
&& bPlayerControlsThing)
{
notify_quiet(executor, M_("You shouldnt be rummaging through the garbage."));
}
else if (nOwnerNew == NOTHING)
{
notify_quiet(executor, M_("I couldnt find that player."));
}
else if ( isPlayer(thing)
&& !God(executor))
{
notify_quiet(executor, M_("Players always own themselves."));
}
else if ( ( !bPlayerControlsThing
&& !Chown_Any(executor)
&& !Chown_ok(thing))
|| ( isThing(thing)
&& Location(thing) != executor
&& !Chown_Any(executor))
|| !Controls(executor, nOwnerNew)
|| God(thing))
{
notify_quiet(executor, NOPERM_MESSAGE);
}
else if (canpayfees(executor, nOwnerNew, cost, quota))
{
giveto(nOwnerOrig, cost);
if (mudconf.quotas)
{
add_quota(nOwnerOrig, quota);
}
if (!God(executor))
{
nOwnerNew = Owner(nOwnerNew);
}
s_Owner(thing, nOwnerNew);
atr_chown(thing);
FLAGSET clearflags;
FLAGSET setflags;
bool bClearPowers;
TranslateFlags_Chown(clearflags.word, setflags.word, &bClearPowers, executor, key);
SetClearFlags(thing, clearflags.word, setflags.word);
if (bClearPowers)
{
s_Powers(thing, 0);
s_Powers2(thing, 0);
}
// Always halt the queue.
//
halt_que(NOTHING, thing);
// Warn if ROYALTY is left set.
//
if (ROYALTY & db[thing].fs.word[FLAG_WORD1])
{
notify_quiet(executor,
tprintf(M_("Warning: @chown/nostrip on %s(#%d) leaves ROYALTY privilege intact."),
Moniker(thing), thing));
}
// Warn if INHERIT is left set.
//
if (INHERIT & db[thing].fs.word[FLAG_WORD1])
{
notify_quiet(executor,
tprintf(M_("Warning: @chown/nostrip on %s(#%d) leaves INHERIT privilege intact."),
Moniker(thing), thing));
}
if (!Quiet(executor))
{
LBuf buff = LBuf_Src("do_chown.notify");
UTF8 *bp = buff;
safe_tprintf_str(buff, &bp, T("Owner of %s(#%d) changed from "), Moniker(thing), thing);
safe_tprintf_str(buff, &bp, T("%s(#%d) to "), Moniker(nOwnerOrig), nOwnerOrig);
safe_tprintf_str(buff, &bp, T("%s(#%d)."), Moniker(nOwnerNew), nOwnerNew);
*bp = '\0';
notify_quiet(executor, buff);
}
}
}
/*
* ---------------------------------------------------------------------------
* * do_set: Set flags or attributes on objects, or flags on attributes.
*/
static void set_attr_internal(dbref player, dbref thing, int attrnum, const UTF8 *attrtext, int key)
{
dbref aowner;
int aflags;
ATTR *pattr = atr_num(attrnum);
atr_pget_info(thing, attrnum, &aowner, &aflags);
if ( pattr
&& bCanSetAttr(player, thing, pattr))
{
bool could_hear = Hearer(thing);
atr_add(thing, attrnum, attrtext, Owner(player), aflags);
handle_ears(thing, could_hear, Hearer(thing));
if ( !(key & SET_QUIET)
&& !Quiet(player)
&& !Quiet(thing))
{
notify_quiet(player, M_("Set."));
}
}
else
{
notify_quiet(player, NOPERM_MESSAGE);
}
}
bool copy_attr(dbref executor, attr_info &src, attr_info &dest, int key)
{
if ( !Good_obj(src.m_object)
|| !Good_obj(dest.m_object)
2018-10-03 17:54:51 +00:00
|| nullptr == src.m_attr)
{
notify_quiet(executor, M_("No match."));
return false;
}
2018-10-03 17:54:51 +00:00
if ( nullptr == dest.m_attr
|| !See_attr(executor, src.m_object, src.m_attr)
|| !bCanSetAttr(executor, dest.m_object, dest.m_attr))
{
notify_quiet(executor, NOPERM_MESSAGE);
return false;
}
LBuf buff = LBuf_Src("copy_attr");
atr_pget_str(buff, src.m_object, src.m_attr->number, &src.m_aowner, &src.m_aflags);
src.m_bHaveInfo = true;
set_attr_internal(executor, dest.m_object, dest.m_attr->number, buff, key);
return true;
}
void do_set
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *flagname,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing, aowner;
int aflags;
ATTR *pattr;
// See if we have the <obj>/<attr> form, which is how you set
// attribute flags.
//
if (parse_attrib(executor, name, &thing, &pattr))
{
if ( pattr
&& See_attr(executor, thing, pattr))
{
// You must specify a flag name.
//
if ( !flagname
|| flagname[0] == '\0')
{
notify_quiet(executor, M_("I dont know what you want to set!"));
return;
}
// Check for clearing.
//
bool clear = false;
if (flagname[0] == NOT_TOKEN)
{
flagname++;
clear = true;
}
// Make sure player specified a valid attribute flag.
//
int flagvalue;
if (!search_nametab(executor, indiv_attraccess_nametab, flagname, &flagvalue))
{
notify_quiet(executor, M_("You cant set that!"));
return;
}
// Make sure the object has the attribute present.
//
if (!atr_get_info(thing, pattr->number, &aowner, &aflags))
{
notify_quiet(executor, M_("Attribute not present on object."));
return;
}
// Make sure we can write to the attribute.
//
if (!bCanSetAttr(executor, thing, pattr))
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
// Check if the attribute flag is already in the desired
// state.
//
if (clear ? ((aflags & flagvalue) == 0) : ((aflags & flagvalue) == flagvalue))
{
if ( !(key & SET_QUIET)
&& !Quiet(executor)
&& !Quiet(thing))
{
if (clear)
{
notify_quiet(executor, M_("Already cleared."));
}
else
{
notify_quiet(executor, M_("Already set."));
}
}
return;
}
// Go do it.
//
if (clear)
{
aflags &= ~flagvalue;
}
else
{
aflags |= flagvalue;
}
bool could_hear = Hearer(thing);
atr_set_flags(thing, pattr->number, aflags);
// Tell the player about it.
//
handle_ears(thing, could_hear, Hearer(thing));
if ( !(key & SET_QUIET)
&& !Quiet(executor)
&& !Quiet(thing))
{
if (clear)
{
notify_quiet(executor, M_("Cleared."));
}
else
{
notify_quiet(executor, M_("Set."));
}
}
return;
}
}
// Find thing.
//
thing = match_controlled(executor, name);
if (!Good_obj(thing))
{
return;
}
// Check for attribute set first.
//
UTF8 *p;
for (p = flagname; *p && (*p != ':'); p++)
{
; // Nothing.
}
if (*p)
{
*p++ = '\0';
int atr = mkattr(executor, flagname);
if (atr <= 0)
{
notify_quiet(executor, M_("Couldnt create attribute."));
return;
}
pattr = atr_num(atr);
if (!pattr)
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
if (!bCanSetAttr(executor, thing, pattr))
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
// Check for _
//
if (*p == '_')
{
p++;
attr_info src(executor, p, false, false);
attr_info dest(thing, pattr);
copy_attr(executor, src, dest, key);
return;
}
// Go set it.
//
set_attr_internal(executor, thing, atr, p, key);
return;
}
// Set or clear a flag.
//
flag_set(thing, executor, flagname, key);
}
void do_power
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *name,
UTF8 *flag,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if ( !flag
|| !*flag)
{
notify_quiet(executor, M_("I dont know what you want to set!"));
return;
}
// Find thing.
//
dbref thing = match_controlled(executor, name);
if (thing == NOTHING)
{
return;
}
power_set(thing, executor, flag, key);
}
void do_setattr
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int attrnum,
int nargs,
UTF8 *name,
UTF8 *attrtext,
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, name, NOTYPE);
match_everything(MAT_EXIT_PARENTS);
dbref thing = noisy_match_result();
if (!Good_obj(thing))
{
return;
}
set_attr_internal(executor, thing, attrnum, attrtext, 0);
}
void do_cpattr(dbref executor, dbref caller, dbref enactor, int eval, int key,
UTF8 *oldpair, UTF8 *newpair[], int nargs, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if ( isEmpty(oldpair)
|| isEmpty(newpair[0])
|| nargs < 1)
{
return;
}
// Check if source has wildcard in attribute portion.
//
bool bWild = false;
const UTF8 *p = oldpair;
while (*p && *p != '/')
{
p++;
}
if (*p == '/')
{
p++;
while (*p)
{
if (*p == '*' || *p == '?')
{
bWild = true;
break;
}
p++;
}
}
if (bWild)
{
// Wildcard path: iterate all matching attributes on source object.
//
dbref thing;
olist_push();
if (!parse_attrib_wild(executor, oldpair, &thing, false, false, false))
{
olist_pop();
notify_quiet(executor, M_("No match."));
return;
}
int atr;
for (atr = olist_first(); atr != NOTHING; atr = olist_next())
{
ATTR *ap = atr_num(atr);
if (ap)
{
attr_info src(thing, ap);
for (int i = 0; i < nargs; i++)
{
attr_info dest(executor, newpair[i], true, false);
if (!dest.m_bValid)
{
if (Good_obj(dest.m_object))
{
dest.m_attr = src.m_attr;
dest.m_bValid = true;
}
}
if (dest.m_bValid)
{
copy_attr(executor, src, dest, key);
}
}
}
}
olist_pop();
}
else
{
// Single-attribute path.
//
attr_info src(executor, oldpair, false, true);
if (src.m_bValid)
{
for (int i = 0; i < nargs; i++)
{
attr_info dest(executor, newpair[i], true, false);
if (!dest.m_bValid)
{
if (Good_obj(dest.m_object))
{
dest.m_attr = src.m_attr;
dest.m_bValid = true;
}
}
if (dest.m_bValid)
{
copy_attr(executor, src, dest, key);
}
}
}
else
{
notify_quiet(executor, M_("No match."));
}
}
}
void do_mvattr(dbref executor, dbref caller, dbref enactor, int eval, int key,
UTF8 *what, UTF8 *args[], int nargs, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
// Make sure we have something to do.
//
if (nargs < 2)
{
notify_quiet(executor, M_("Nothing to do."));
return;
}
// Find and make sure we control the target object.
//
dbref thing = match_controlled(executor, what);
if (thing == NOTHING)
{
return;
}
// Look up the source attribute. If it either doesn't exist or isn't
// readable, use an empty string.
//
int in_anum = -1;
LBuf astr = LBuf_Src("do_mvattr");
ATTR *in_attr = atr_str(args[0]);
int aflags = 0;
2018-10-03 17:54:51 +00:00
if (in_attr == nullptr)
{
*astr = '\0';
}
else
{
dbref aowner;
atr_get_str(astr, thing, in_attr->number, &aowner, &aflags);
if (See_attr(executor, thing, in_attr))
{
in_anum = in_attr->number;
}
else
{
*astr = '\0';
}
}
// Copy the attribute to each target in turn.
//
bool bCanDelete = true;
int nCopied = 0;
for (int i = 1; i < nargs; i++)
{
int anum = mkattr(executor, args[i]);
if (anum <= 0)
{
notify_quiet(executor, tprintf(M_("%s: Thats not a good name for an attribute."), args[i]));
continue;
}
ATTR *out_attr = atr_num(anum);
if (!out_attr)
{
notify_quiet(executor, tprintf(M_("%s: Permission denied."), args[i]));
}
else if (out_attr->number == in_anum)
{
// It doesn't make sense to delete a source attribute if it's also
// included as a destination.
//
bCanDelete = false;
}
else
{
if (!bCanSetAttr(executor, thing, out_attr))
{
notify_quiet(executor, tprintf(M_("%s: Permission denied."), args[i]));
}
else
{
nCopied++;
atr_add(thing, out_attr->number, astr, Owner(executor), aflags);
if (!Quiet(executor))
{
notify_quiet(executor, tprintf(M_("%s: Set."), out_attr->name));
}
}
}
}
// Remove the source attribute if we were able to copy it successfully to
// even one destination object.
//
if (nCopied <= 0)
{
if (in_attr)
{
notify_quiet(executor, tprintf(M_("%s: Not copied anywhere. Not cleared."), in_attr->name));
}
else
{
notify_quiet(executor, M_("Not copied anywhere. Non-existent attribute."));
}
}
else if ( in_anum > 0
&& bCanDelete)
{
in_attr = atr_num(in_anum);
if (in_attr)
{
if (bCanSetAttr(executor, thing, in_attr))
{
atr_clr(thing, in_attr->number);
if (!Quiet(executor))
{
notify_quiet(executor, tprintf(M_("%s: Cleared."), in_attr->name));
}
}
else
{
notify_quiet(executor,
tprintf(M_("%s: Could not remove old attribute. Permission denied."),
in_attr->name));
}
}
else
{
notify_quiet(executor, M_("Could not remove old attribute. Non-existent attribute."));
}
}
}
/*
* ---------------------------------------------------------------------------
* * parse_attrib, parse_attrib_wild: parse <obj>/<attr> tokens.
*/
bool parse_attrib(dbref player, const UTF8 *str, dbref *thing, ATTR **attr)
{
2018-10-03 17:54:51 +00:00
ATTR *tattr = nullptr;
*thing = NOTHING;
if (!str)
{
*attr = tattr;
return false;
}
// Break apart string into obj and attr.
//
const UTF8 *AttrName;
bool retval = parse_thing_slash(player, str, &AttrName, thing);
// Get the named attribute from the object if we can.
//
if (retval)
{
tattr = atr_str(AttrName);
}
*attr = tattr;
return retval;
}
void find_wild_attrs(dbref player, dbref thing, const UTF8 *str, bool check_exclude, bool hash_insert, bool get_locks)
{
ATTR *pattr;
dbref aowner;
int ca, ok, aflags;
// Walk the attribute list of the object.
//
atr_push();
unsigned char *as;
for (ca = atr_head(thing, &as); ca; ca = atr_next(&as))
{
pattr = atr_num(ca);
// Discard bad attributes and ones we've seen before.
//
if (!pattr)
{
continue;
}
if ( check_exclude
&& ( (pattr->flags & AF_PRIVATE)
|| mudstate.parent_htab.find(ca) != mudstate.parent_htab.end()))
{
continue;
}
// If we aren't the top level remember this attr so we exclude it in
// any parents.
//
atr_get_info(thing, ca, &aowner, &aflags);
if ( check_exclude
&& (aflags & AF_PRIVATE))
{
continue;
}
if (get_locks)
{
ok = bCanReadAttr(player, thing, pattr, false);
}
else
{
ok = See_attr(player, thing, pattr);
}
mudstate.wild_invk_ctr = 0;
if ( ok
&& quick_wild(str, pattr->name))
{
olist_add(ca);
if (hash_insert)
{
mudstate.parent_htab.emplace(ca, pattr);
}
}
}
atr_pop();
}
bool parse_attrib_wild(dbref player, const UTF8 *str, dbref *thing,
bool check_parents, bool get_locks, bool df_star)
{
if (!str)
{
return false;
}
dbref parent;
int lev;
bool check_exclude, hash_insert;
const UTF8 *after = str;
// Separate name and attr portions at the first /.
//
if (!parse_thing_slash(player, str, &after, thing))
{
// Not in obj/attr format, return if not defaulting to.
//
if (!df_star)
{
return false;
}
// Look for the object, return failure if not found.
//
init_match(player, str, NOTYPE);
match_everything(MAT_EXIT_PARENTS);
*thing = match_result();
if (!Good_obj(*thing))
{
return false;
}
after = T("*");
}
// Check the object (and optionally all parents) for attributes.
//
if (check_parents)
{
check_exclude = false;
hash_insert = check_parents;
mudstate.parent_htab.clear();
ITER_PARENTS(*thing, parent, lev)
{
if (!Good_obj(Parent(parent)))
{
hash_insert = false;
}
find_wild_attrs(player, parent, after, check_exclude, hash_insert, get_locks);
check_exclude = true;
}
}
else
{
find_wild_attrs(player, *thing, after, false, false, get_locks);
}
return true;
}
/*
* ---------------------------------------------------------------------------
* * edit_string, edit_string_ansi, do_edit: Modify attributes.
*/
void edit_string(UTF8 *src, UTF8 **dst, UTF8 *from, UTF8 *to)
{
UTF8 *cp;
// Do the substitution. Idea for prefix/suffix from R'nice@TinyTIM.
//
if (!strcmp(reinterpret_cast<char *>(from), "^"))
{
// Prepend 'to' to string.
//
*dst = alloc_lbuf("edit_string.^");
cp = *dst;
safe_str(to, *dst, &cp);
safe_str(src, *dst, &cp);
*cp = '\0';
}
else if (!strcmp(reinterpret_cast<char *>(from), "$"))
{
// Append 'to' to string.
//
*dst = alloc_lbuf("edit_string.$");
cp = *dst;
safe_str(src, *dst, &cp);
safe_str(to, *dst, &cp);
*cp = '\0';
}
else
{
// Replace all occurances of 'from' with 'to'. Handle the special
// cases of from = \$ and \^.
//
if ( ( from[0] == '\\'
|| from[0] == '%')
&& ( from[1] == '$'
|| from[1] == '^')
&& from[2] == '\0')
{
from++;
}
*dst = replace_string(from, to, src);
}
}
static void edit_string_ansi(UTF8 *src, UTF8 **dst, UTF8 **returnstr, UTF8 *from, UTF8 *to)
{
UTF8 *cp, *rp;
// Do the substitution. Idea for prefix/suffix from R'nice@TinyTIM
//
if (!strcmp(reinterpret_cast<char *>(from), "^"))
{
// Prepend 'to' to string.
//
*dst = alloc_lbuf("edit_string.^");
cp = *dst;
safe_str(to, *dst, &cp);
safe_str(src, *dst, &cp);
*cp = '\0';
// Do the ansi string used to notify.
//
*returnstr = alloc_lbuf("edit_string_ansi.^");
rp = *returnstr;
safe_str(reinterpret_cast<const UTF8 *>(COLOR_INTENSE), *returnstr, &rp);
safe_str(to, *returnstr, &rp);
safe_str(reinterpret_cast<const UTF8 *>(COLOR_RESET), *returnstr, &rp);
safe_str(src, *returnstr, &rp);
*rp = '\0';
}
else if (!strcmp(reinterpret_cast<char *>(from), "$"))
{
// Append 'to' to string
//
*dst = alloc_lbuf("edit_string.$");
cp = *dst;
safe_str(src, *dst, &cp);
safe_str(to, *dst, &cp);
*cp = '\0';
// Do the ansi string used to notify.
//
*returnstr = alloc_lbuf("edit_string_ansi.$");
rp = *returnstr;
safe_str(src, *returnstr, &rp);
safe_str(reinterpret_cast<const UTF8 *>(COLOR_INTENSE), *returnstr, &rp);
safe_str(to, *returnstr, &rp);
safe_str(reinterpret_cast<const UTF8 *>(COLOR_RESET), *returnstr, &rp);
*rp = '\0';
}
else
{
// Replace all occurances of 'from' with 'to'. Handle the special
// cases of from = \$ and \^.
//
if ( ((from[0] == '\\') || (from[0] == '%'))
&& ((from[1] == '$') || (from[1] == '^'))
&& ( from[2] == '\0'))
{
from++;
}
*dst = replace_string(from, to, src);
*returnstr = replace_string(from, tprintf(T("%s%s%s"), COLOR_INTENSE,
to, COLOR_RESET), src);
}
}
void do_edit(dbref executor, dbref caller, dbref enactor, int eval, int key,
UTF8 *it, UTF8 *args[], int nargs, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing, aowner;
int atr, aflags;
bool bGotOne;
UTF8 *from, *to, *result, *returnstr, *atext;
ATTR *ap;
// Make sure we have something to do.
//
if ( nargs < 1
|| !*args[0])
{
notify_quiet(executor, M_("Nothing to do."));
return;
}
from = args[0];
to = (nargs >= 2) ? args[1] : const_cast<UTF8 *>(T(""));
// Look for the object and get the attribute (possibly wildcarded)
//
olist_push();
if ( !it
|| !*it
|| !parse_attrib_wild(executor, it, &thing, false, false, false))
{
notify_quiet(executor, M_("No match."));
return;
}
// Iterate through matching attributes, performing edit.
//
bGotOne = 0;
atext = alloc_lbuf("do_edit.atext");
bool could_hear = Hearer(thing);
for (atr = olist_first(); atr != NOTHING; atr = olist_next())
{
ap = atr_num(atr);
if (ap)
{
// Get the attr and make sure we can modify it.
//
atr_get_str(atext, thing, ap->number, &aowner, &aflags);
if (bCanSetAttr(executor, thing, ap))
{
// Do the edit and save the result
//
bGotOne = true;
edit_string_ansi(atext, &result, &returnstr, from, to);
atr_add(thing, ap->number, result, Owner(executor), aflags);
if (!Quiet(executor))
{
notify_quiet(executor, tprintf(M_("Set - %s: %s"), ap->name,
returnstr));
}
free_lbuf(result);
free_lbuf(returnstr);
}
else
{
// No rights to change the attr.
//
if (!Quiet(executor))
{
notify_quiet(executor, tprintf(M_("%s: Permission denied."), ap->name));
}
}
}
}
// Clean up.
//
free_lbuf(atext);
olist_pop();
if (!bGotOne)
{
notify_quiet(executor, M_("No matching attributes."));
}
else
{
handle_ears(thing, could_hear, Hearer(thing));
}
}
void do_wipe(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *it, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing;
olist_push();
if ( !it
|| !*it
|| !parse_attrib_wild(executor, it, &thing, false, false, true))
{
notify_quiet(executor, M_("No match."));
return;
}
if ( mudconf.safe_wipe
&& has_flag(NOTHING, thing, T("SAFE")))
{
notify_quiet(executor, M_("SAFE objects may not be @wiped."));
return;
}
// Iterate through matching attributes, zapping the writable ones
//
int atr;
ATTR *ap;
bool bGotOne = false, could_hear = Hearer(thing);
for (atr = olist_first(); atr != NOTHING; atr = olist_next())
{
ap = atr_num(atr);
if (ap)
{
// Get the attr and make sure we can modify it.
//
if (bCanSetAttr(executor, thing, ap))
{
atr_clr(thing, ap->number);
bGotOne = true;
}
}
}
// Clean up
//
olist_pop();
if (!bGotOne)
{
notify_quiet(executor, M_("No matching attributes."));
}
else
{
set_modified(thing);
handle_ears(thing, could_hear, Hearer(thing));
if (!Quiet(executor))
{
notify_quiet(executor, M_("Wiped."));
}
}
}
void do_trigger(dbref executor, dbref caller, dbref enactor, int eval, int key,
UTF8 *object, UTF8 *argv[], int nargs, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing;
ATTR *pattr;
if (!( parse_attrib(executor, object, &thing, &pattr)
&& pattr))
{
notify_quiet(executor, M_("No match."));
return;
}
if (!Controls(executor, thing))
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
if (key & TRIG_NOW)
{
// Execute the attribute inline instead of enqueuing.
//
dbref aowner;
int aflags;
UTF8 *act = atr_pget(thing, pattr->number, &aowner, &aflags);
if (*act)
{
bool bRun = true;
dbref aowner2;
int aflags2;
UTF8 *charges = atr_pget(thing, A_CHARGES, &aowner2, &aflags2);
if (*charges)
{
// int64_t, not int (#1402): large charges must not wrap
// to a small positive and skip RUNOUT.
//
int64_t num = mux_atoi64(charges);
if (num > 0)
{
UTF8 *buff = alloc_sbuf("trigger.now.charges");
mux_i64toa(num - 1, buff);
atr_add_raw(thing, A_CHARGES, buff);
free_sbuf(buff);
}
else
{
UTF8 *buff = atr_pget(thing, A_RUNOUT, &aowner2, &aflags2);
if (*buff)
{
free_lbuf(act);
act = buff;
}
else
{
free_lbuf(buff);
bRun = false;
}
}
}
free_lbuf(charges);
if (bRun)
{
if ((aflags & AF_NOEVAL) || NoEval(thing))
{
// NOEVAL content is data, not code -- skip execution.
}
else
{
process_command(thing, executor, executor,
AttrTrace(aflags, 0), false, act,
(const UTF8 **)argv, nargs);
}
}
}
free_lbuf(act);
}
else
{
did_it(executor, thing, 0, nullptr, 0, nullptr, pattr->number, 0, (const UTF8 **)argv, nargs);
}
if (key & TRIG_NOTIFY)
{
LBuf tbuf = LBuf_Src("trigger.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,
2018-10-03 17:54:51 +00:00
0, nullptr,
mudstate.global_regs);
}
if ( !(key & TRIG_QUIET)
&& !Quiet(executor))
{
notify_quiet(executor, M_("Triggered."));
}
}
// ---------------------------------------------------------------------------
// do_include: Inline-expand the contents of an attribute into the current
// action list. Unlike @trigger, this does not create a new queue entry --
// the included commands execute immediately in the caller's context, and
// @break/@assert propagate to the parent action list (unless /nobreak).
//
// Syntax: @include[/nobreak][/localize][/clearregs] obj/attr[=arg0,arg1,...]
//
// Without arguments, the parent's %0-%9 pass through unchanged.
// With arguments, %0-%9 are replaced for the duration of the include.
//
void do_include(dbref executor, dbref caller, dbref enactor, int eval, int key,
UTF8 *object, UTF8 *argv[], int nargs, const UTF8 *cargs[], int ncargs)
{
dbref thing;
ATTR *pattr;
if (!( parse_attrib(executor, object, &thing, &pattr)
&& pattr))
{
notify_quiet(executor, M_("No match."));
return;
}
// @include requires read access, not control: the attribute text is
// expanded into the *includer's* action list (executor stays the caller).
// Running as the source object with only See_attr would let a player
// execute with foreign privileges by including any VISUAL attr (#1279).
//
if (!See_attr(executor, thing, pattr))
{
notify_quiet(executor, NOPERM_MESSAGE);
return;
}
dbref aowner;
int aflags;
LBuf act = LBuf_Adopt(atr_pget(thing, pattr->number, &aowner, &aflags));
if (!*act.get())
{
return;
}
if ((aflags & AF_NOEVAL) || NoEval(thing))
{
// NOEVAL content is data, not code -- skip execution.
//
return;
}
// Strip $command: or ^listen: prefix if present, so that the same
// attribute that serves as a $-command can be @included as a
// subroutine.
//
UTF8 *body = act;
if ( '$' == act[0]
|| '^' == act[0])
{
UTF8 *colon = (UTF8 *)strchr((char *)act.get(), ':');
if (colon)
{
body = colon + 1;
}
}
// Determine effective environment: if caller supplies args, use them;
// otherwise pass through the parent's environment.
//
const UTF8 **env;
int nenv;
if ( nargs > 0
&& argv[0]
&& argv[0][0] != '\0')
{
env = (const UTF8 **)argv;
nenv = nargs;
}
else
{
env = cargs;
nenv = ncargs;
}
// Save/clear %q-registers if requested.
//
reg_ref *preserve[MAX_GLOBAL_REGS];
bool bLocalize = (key & INCLUDE_LOCALIZE) != 0;
if (bLocalize)
{
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
{
preserve[i] = mudstate.global_regs[i];
if (preserve[i])
{
RegAddRef(preserve[i]);
}
}
}
if (key & INCLUDE_CLEARREGS)
{
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
{
if (mudstate.global_regs[i])
{
RegRelease(mudstate.global_regs[i]);
mudstate.global_regs[i] = nullptr;
}
}
}
// Save break state so /nobreak can isolate it.
//
bool save_break = break_called;
if (key & INCLUDE_NOBREAK)
{
break_called = false;
}
// Walk the included text, splitting on semicolons and executing
// each command inline as the includer (not the source object —
// #1279), with ;| piping honored like the queued runner (#788).
//
process_command_list_inline(executor, caller, enactor,
engine: @dolist/now/break switch; ;| piping in inline command lists (#788) Two follow-ups to the @dolist/now rewrite (37e18bef5): - New /break switch: an @break inside the body still stops the loop, but with /break it also propagates to the enclosing command list, aborting the commands after the @dolist. This restores the one ability the containment change removed (using a break inside the loop to abort the rest of a $-command), with sane loop-stop semantics, and mirrors @include (propagate by default, /nobreak to contain). Without /now the switch is inert. - ;| pipe segments now work in inline command lists. The queued runner has always special-cased '|' after ';' to capture the previous command's output as %|; the inline loops in @dolist/now and @include treated such a segment as a literal command starting with '|' ("Huh?"). The splitting/pipe/break loop is factored into process_command_list_inline() in cque.cpp, shared by both callers and kept next to its queued twin so the semantics cannot drift. The inline list is its own pipe domain (save/restore of the enclosing pipe context), matching how each queued entry starts with a clean pipe state -- an inline list running inside a piped segment cannot corrupt the outer capture. help @dolist documents /break and the ;| support. Verified: default containment, /break propagation, and /break with no inner break all behave as specified; pipes carry %| through inline dolist and include bodies; smoke 1115/0/0 with three new test cases; 2x200 jit_diff clean. Closes #788. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:00:21 -06:00
AttrTrace(aflags, 0), body, env, nenv);
// Restore break state for /nobreak.
//
if (key & INCLUDE_NOBREAK)
{
break_called = save_break;
}
// Restore %q-registers if /localize.
//
if (bLocalize)
{
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
{
if (mudstate.global_regs[i])
{
RegRelease(mudstate.global_regs[i]);
}
mudstate.global_regs[i] = preserve[i];
}
}
}
void do_use(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *object, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
dbref thing, aowner;
int aflags;
init_match(executor, object, NOTYPE);
match_neighbor();
match_possession();
if (Wizard(executor))
{
match_absolute();
match_player();
}
match_me();
match_here();
thing = noisy_match_result();
if (thing == NOTHING)
return;
// Make sure player can use it.
//
if (!could_doit(executor, thing, A_LUSE))
{
did_it(executor, thing, A_UFAIL,
M_("You cant figure out how to use that."),
2018-10-03 17:54:51 +00:00
A_OUFAIL, nullptr, A_AUFAIL, 0, nullptr, 0);
return;
}
LBuf temp = LBuf_Src("do_use");
bool doit = false;
if (*atr_pget_str(temp, thing, A_USE, &aowner, &aflags))
{
doit = true;
}
else if (*atr_pget_str(temp, thing, A_OUSE, &aowner, &aflags))
{
doit = true;
}
else if (*atr_pget_str(temp, thing, A_AUSE, &aowner, &aflags))
{
doit = true;
}
if (doit)
{
LBuf df_use = LBuf_Src("do_use.use");
LBuf df_ouse = LBuf_Src("do_use.ouse");
mux_sprintf(df_use, LBUF_SIZE, M_("You use %s"), Moniker(thing));
mux_sprintf(df_ouse, LBUF_SIZE, M_("uses %s"), Moniker(thing));
did_it(executor, thing, A_USE, df_use, A_OUSE, df_ouse, A_AUSE, 0,
2018-10-03 17:54:51 +00:00
nullptr, 0);
}
else
{
notify_quiet(executor, M_("You cant figure out how to use that."));
}
}
/*
* ---------------------------------------------------------------------------
* * do_setvattr: Set a user-named (or possibly a predefined) attribute.
*/
void do_setvattr
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *arg1,
UTF8 *arg2,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
UNUSED_PARAMETER(nargs);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
UTF8 *s;
int anum;
// Skip the '&'
//
arg1++;
// Take to the space
//
for (s = arg1; *s && !mux_isspace(*s); s++)
{
; // Nothing.
}
// Split it
//
if (*s)
{
*s++ = '\0';
}
// Get or make attribute
//
anum = mkattr(executor, reinterpret_cast<UTF8 *>(arg1));
if (anum <= 0)
{
notify_quiet(executor, M_("Thats not a good name for an attribute."));
return;
}
2018-10-03 17:54:51 +00:00
do_setattr(executor, caller, enactor, 0, anum, 2, s, arg2, nullptr, 0);
}