tinymux/mux/modules/engine/flags.cpp

1500 lines
50 KiB
C++
Raw Permalink Normal View History

/*! \file flags.cpp
* \brief Flag manipulation routines.
*
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#include "routing.h"
using namespace std;
/* ---------------------------------------------------------------------------
* fh_any: set or clear indicated bit, no security checking
*/
static bool fh_any(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
// Never let God drop his/her own wizbit.
//
if ( God(target)
&& reset
&& flag == WIZARD
&& fflags == FLAG_WORD1)
{
notify(player, M_("You cannot make God mortal."));
return false;
}
// Otherwise we can go do it.
//
if (reset)
{
s_Flags(target, fflags, db[target].fs.word[fflags] & ~flag);
}
else
{
s_Flags(target, fflags, db[target].fs.word[fflags] | flag);
}
return true;
}
/* ---------------------------------------------------------------------------
* fh_room_bit: only settable on rooms (TYPE_ROOM).
*/
static bool fh_room_bit(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if (!isRoom(target))
{
return false;
}
bool result = fh_any(target, player, flag, fflags, reset);
if (result)
{
route_invalidate();
}
return result;
}
/* ---------------------------------------------------------------------------
* fh_god: only GOD may set or clear the bit
*/
static bool fh_god(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if (!God(player))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/*
* ---------------------------------------------------------------------------
* * fh_wiz: only WIZARDS (or GOD) may set or clear the bit
*/
static bool fh_wiz(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if (!Wizard(player))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/*
* ---------------------------------------------------------------------------
* * fh_wizroy: only WIZARDS, ROYALTY, (or GOD) may set or clear the bit
*/
static bool fh_wizroy(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if (!WizRoy(player))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/*
* ---------------------------------------------------------------------------
* * fh_restrict_player (renamed from fh_fixed): Only Wizards can set
* * this on players, but ordinary players can set it on other types
* * of objects.
*/
static bool fh_restrict_player
(
dbref target,
dbref player,
FLAG flag,
int fflags,
bool reset
)
{
if ( isPlayer(target)
&& !Wizard(player))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/* ---------------------------------------------------------------------------
* fh_privileged: You can set this flag on a non-player object, if you
* yourself have this flag and are a player who owns themselves (i.e.,
* no robots). Only God can set this on a player.
*/
static bool fh_privileged
(
dbref target,
dbref player,
FLAG flag,
int fflags,
bool reset
)
{
if (!God(player))
{
if ( isPlayer(target)
|| !isPlayer(player)
|| player != Owner(player)
|| (db[player].fs.word[fflags] & flag) == 0)
{
return false;
}
}
return (fh_any(target, player, flag, fflags, reset));
}
/*
* ---------------------------------------------------------------------------
* * fh_inherit: only players may set or clear this bit.
*/
static bool fh_inherit(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if (!Inherits(player))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/*
* ---------------------------------------------------------------------------
* * fh_dark_bit: manipulate the dark bit. Nonwizards may not set on players.
*/
static bool fh_dark_bit(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if ( !reset
&& isPlayer(target)
&& !( (target == player)
&& (Can_Hide(player) || Can_Dark(player)))
&& !Wizard(player))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/*
* ---------------------------------------------------------------------------
* * fh_going_bit: manipulate the going bit. Non-gods may only clear.
*/
static bool fh_going_bit(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if ( Going(target)
&& reset
&& (Typeof(target) != TYPE_GARBAGE))
{
notify(player, M_("Your object has been spared from destruction."));
return (fh_any(target, player, flag, fflags, reset));
}
if (!God(player))
{
return false;
}
// Even God should not be allowed set protected dbrefs GOING.
//
if ( !reset
&& ( target == 0
|| God(target)
|| target == mudconf.start_home
|| target == mudconf.start_room
|| target == mudconf.default_home
|| target == mudconf.master_room))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/*
* ---------------------------------------------------------------------------
* * fh_hear_bit: set or clear bits that affect hearing.
*/
static bool fh_hear_bit(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if (isPlayer(target) && (flag & MONITOR))
{
if (Can_Monitor(player))
{
return (fh_any(target, player, flag, fflags, reset));
}
else
{
return false;
}
}
bool could_hear = Hearer(target);
bool result = fh_any(target, player, flag, fflags, reset);
handle_ears(target, could_hear, Hearer(target));
return result;
}
/* ---------------------------------------------------------------------------
* fh_player_bit: Can set and reset this on everything but players.
*/
static bool fh_player_bit
(
dbref target,
dbref player,
FLAG flag,
int fflags,
bool reset
)
{
if (isPlayer(target))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/* ---------------------------------------------------------------------------
* fh_staff: only STAFF, WIZARDS, ROYALTY, (or GOD) may set or clear
* the bit.
*/
static bool fh_staff
(
dbref target,
dbref player,
FLAG flag,
int fflags,
bool reset
)
{
if (!Staff(player) && !God(player))
{
return false;
}
return (fh_any(target, player, flag, fflags, reset));
}
/*
* ---------------------------------------------------------------------------
* * fh_unicode: only players may set or clear this bit.
*/
static bool fh_unicode(dbref target, dbref player, FLAG flag, int fflags, bool reset)
{
if (!isPlayer(target))
{
return false;
}
if (fh_any(target, player, flag, fflags, reset))
{
if (!reset)
{
set_player_encoding(target, CHARSET_UTF8);
}
else
{
reset_player_encoding(target);
}
return true;
}
return false;
}
/*
* ---------------------------------------------------------------------------
* * fh_ascii: only players may set or clear this bit.
*/
static bool fh_ascii(const dbref target, const dbref player, FLAG flag, int fflags, const bool reset)
{
if (!isPlayer(target))
{
return false;
}
const bool result = fh_any(target, player, flag, fflags, reset);
if (result)
{
if (!reset)
{
set_player_encoding(target, CHARSET_ASCII);
}
else
{
reset_player_encoding(target);
}
}
return result;
}
static FLAGBITENT fbeAbode = { ABODE, 'A', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeAnsi = { ANSI, 'X', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeAudible = { HEARTHRU, 'a', FLAG_WORD1, 0, fh_hear_bit};
static FLAGBITENT fbeAuditorium = { AUDITORIUM, 'b', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeBlind = { BLIND, 'B', FLAG_WORD2, 0, fh_wiz};
static FLAGBITENT fbeChownOk = { CHOWN_OK, 'C', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeColor256 = { COLOR256, ' ', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeTruecolor = { TRUECOLOR, ' ', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeConnected = { CONNECTED, 'c', FLAG_WORD2, CA_NO_DECOMP, fh_god};
static FLAGBITENT fbeDark = { DARK, 'D', FLAG_WORD1, 0, fh_dark_bit};
static FLAGBITENT fbeDestroyOk = { DESTROY_OK, 'd', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeEnterOk = { ENTER_OK, 'e', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeFixed = { FIXED_FLAG, 'f', FLAG_WORD2, 0, fh_restrict_player};
static FLAGBITENT fbeFloating = { FLOATING, 'F', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeGagged = { GAGGED, 'j', FLAG_WORD2, 0, fh_wiz};
static FLAGBITENT fbeGoing = { GOING, 'G', FLAG_WORD1, CA_NO_DECOMP, fh_going_bit};
static FLAGBITENT fbeHalted = { HALT, 'h', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeHasDaily = { HAS_DAILY, '*', FLAG_WORD2, CA_GOD|CA_NO_DECOMP, fh_god};
static FLAGBITENT fbeHasForwardList = { HAS_FWDLIST, '&', FLAG_WORD2, CA_GOD|CA_NO_DECOMP, fh_god};
static FLAGBITENT fbeHasListen = { HAS_LISTEN, '@', FLAG_WORD2, CA_GOD|CA_NO_DECOMP, fh_god};
static FLAGBITENT fbeHasStartup = { HAS_STARTUP, '+', FLAG_WORD1, CA_GOD|CA_NO_DECOMP, fh_god};
static FLAGBITENT fbeHaven = { HAVEN, 'H', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeHead = { HEAD_FLAG, '?', FLAG_WORD2, 0, fh_wiz};
static FLAGBITENT fbeHtml = { HTML, '(', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeImmortal = { IMMORTAL, 'i', FLAG_WORD1, 0, fh_wiz};
static FLAGBITENT fbeInherit = { INHERIT, 'I', FLAG_WORD1, 0, fh_inherit};
static FLAGBITENT fbeJumpOk = { JUMP_OK, 'J', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeKeepAlive = { CKEEPALIVE, 'k', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeKey = { KEY, 'K', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeLight = { LIGHT, 'l', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeLinkOk = { LINK_OK, 'L', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeMonitor = { MONITOR, 'M', FLAG_WORD1, 0, fh_hear_bit};
static FLAGBITENT fbeMyopic = { MYOPIC, 'm', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeNoCommand = { NO_COMMAND, 'n', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeAscii = { ASCII , '~', FLAG_WORD2, 0, fh_ascii};
static FLAGBITENT fbeNoBleed = { NOBLEED, '-', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeNoSpoof = { NOSPOOF, 'N', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeOpaque = { MUX_OPAQUE, 'O', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeOpenOk = { OPEN_OK, 'z', FLAG_WORD2, 0, fh_wiz};
static FLAGBITENT fbeParentOk = { PARENT_OK, 'Y', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbePlayerMails = { PLAYER_MAILS, ' ', FLAG_WORD2, CA_GOD|CA_NO_DECOMP, fh_god};
static FLAGBITENT fbePuppet = { PUPPET, 'p', FLAG_WORD1, 0, fh_hear_bit};
static FLAGBITENT fbeQuiet = { QUIET, 'Q', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeRobot = { ROBOT, 'r', FLAG_WORD1, 0, fh_player_bit};
static FLAGBITENT fbeRoyalty = { ROYALTY, 'Z', FLAG_WORD1, 0, fh_wiz};
static FLAGBITENT fbeSafe = { SAFE, 's', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeSlave = { SLAVE, 'x', FLAG_WORD2, CA_WIZARD, fh_wiz};
static FLAGBITENT fbeStaff = { STAFF, 'w', FLAG_WORD2, 0, fh_wiz};
static FLAGBITENT fbeSticky = { STICKY, 'S', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeSuspect = { SUSPECT, 'u', FLAG_WORD2, CA_WIZARD, fh_wiz};
static FLAGBITENT fbeTalkMode = { TALKMODE, ' ', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeTerse = { TERSE, 'q', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeTrace = { TRACE, 'T', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeTransparent = { SEETHRU, 't', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeUnfindable = { UNFINDABLE, 'U', FLAG_WORD2, 0, fh_any};
static FLAGBITENT fbeUnicode = { MUX_UNICODE, ' ', FLAG_WORD3, CA_NO_DECOMP, fh_unicode};
static FLAGBITENT fbeUninspected = { UNINSPECTED, 'g', FLAG_WORD2, 0, fh_wizroy};
static FLAGBITENT fbeVacation = { VACATION, '|', FLAG_WORD2, 0, fh_restrict_player};
static FLAGBITENT fbeVerbose = { VERBOSE, 'v', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeVisual = { VISUAL, 'V', FLAG_WORD1, 0, fh_any};
static FLAGBITENT fbeWizard = { WIZARD, 'W', FLAG_WORD1, 0, fh_god};
static FLAGBITENT fbeAlone = { ALONE, ' ', FLAG_WORD3, 0, fh_wiz};
static FLAGBITENT fbeNoExamine = { NOEXAMINE, 'E', FLAG_WORD3, 0, fh_wizroy};
static FLAGBITENT fbeNoModify = { NOMODIFY, ' ', FLAG_WORD3, 0, fh_wizroy};
static FLAGBITENT fbeIndestructible = { INDESTRUCTIBLE, ' ', FLAG_WORD3, 0, fh_wizroy};
static FLAGBITENT fbeNoEval = { NOEVAL, ' ', FLAG_WORD3, 0, fh_any};
static FLAGBITENT fbeNavigable = { NAVIGABLE, ' ', FLAG_WORD3, 0, fh_room_bit};
static FLAGBITENT fbeSitemon = { SITEMON, '$', FLAG_WORD3, 0, fh_wiz};
#ifdef WOD_REALMS
static FLAGBITENT fbeFae = { FAE, '0', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeChimera = { CHIMERA, '1', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbePeering = { PEERING, '2', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeUmbra = { UMBRA, '3', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeShroud = { SHROUD, '4', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeMatrix = { MATRIX, '5', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeObf = { OBF, '6', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeHss = { HSS, '7', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeMedium = { MEDIUM, '8', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeDead = { DEAD, '9', FLAG_WORD3, CA_STAFF, fh_wizroy};
static FLAGBITENT fbeMarker0 = { MARK_0, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker1 = { MARK_1, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker2 = { MARK_2, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker3 = { MARK_3, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker4 = { MARK_4, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker5 = { MARK_5, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker6 = { MARK_6, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker7 = { MARK_7, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker8 = { MARK_8, ' ', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker9 = { MARK_9, ' ', FLAG_WORD3, 0, fh_god};
#else // WOD_REALMS
static FLAGBITENT fbeMarker0 = { MARK_0, '0', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker1 = { MARK_1, '1', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker2 = { MARK_2, '2', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker3 = { MARK_3, '3', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker4 = { MARK_4, '4', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker5 = { MARK_5, '5', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker6 = { MARK_6, '6', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker7 = { MARK_7, '7', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker8 = { MARK_8, '8', FLAG_WORD3, 0, fh_god};
static FLAGBITENT fbeMarker9 = { MARK_9, '9', FLAG_WORD3, 0, fh_god};
#endif // WOD_REALMS
FLAGNAMEENT gen_flag_names[] =
{
{T("ALONE"), true, &fbeAlone },
{T("ABODE"), true, &fbeAbode },
{T("ACCENTS"), false, &fbeAscii },
{T("ANSI"), true, &fbeAnsi },
{T("ASCII"), true, &fbeAscii },
{T("AUDIBLE"), true, &fbeAudible },
{T("AUDITORIUM"), true, &fbeAuditorium },
{T("BLEED"), false, &fbeNoBleed },
{T("BLIND"), true, &fbeBlind },
{T("COMMANDS"), false, &fbeNoCommand },
{T("CHOWN_OK"), true, &fbeChownOk },
{T("COLOR256"), true, &fbeColor256 },
{T("CONNECTED"), true, &fbeConnected },
{T("DARK"), true, &fbeDark },
{T("DESTROY_OK"), true, &fbeDestroyOk },
{T("ENTER_OK"), true, &fbeEnterOk },
{T("FIXED"), true, &fbeFixed },
{T("FLOATING"), true, &fbeFloating },
{T("GAGGED"), true, &fbeGagged },
{T("GOING"), true, &fbeGoing },
{T("HALTED"), true, &fbeHalted },
{T("HAS_DAILY"), true, &fbeHasDaily },
{T("HAS_FORWARDLIST"), true, &fbeHasForwardList },
{T("HAS_LISTEN"), true, &fbeHasListen },
{T("HAS_STARTUP"), true, &fbeHasStartup },
{T("HAVEN"), true, &fbeHaven },
{T("HEAD"), true, &fbeHead },
{T("HTML"), true, &fbeHtml },
{T("IMMORTAL"), true, &fbeImmortal },
{T("INDESTRUCTIBLE"), true, &fbeIndestructible },
{T("INHERIT"), true, &fbeInherit },
{T("JUMP_OK"), true, &fbeJumpOk },
{T("KEEPALIVE"), true, &fbeKeepAlive },
{T("KEY"), true, &fbeKey },
{T("LIGHT"), true, &fbeLight },
{T("LINK_OK"), true, &fbeLinkOk },
{T("MARKER0"), true, &fbeMarker0 },
{T("MARKER1"), true, &fbeMarker1 },
{T("MARKER2"), true, &fbeMarker2 },
{T("MARKER3"), true, &fbeMarker3 },
{T("MARKER4"), true, &fbeMarker4 },
{T("MARKER5"), true, &fbeMarker5 },
{T("MARKER6"), true, &fbeMarker6 },
{T("MARKER7"), true, &fbeMarker7 },
{T("MARKER8"), true, &fbeMarker8 },
{T("MARKER9"), true, &fbeMarker9 },
{T("MONITOR"), true, &fbeMonitor },
{T("MYOPIC"), true, &fbeMyopic },
{T("NAVIGABLE"), true, &fbeNavigable },
{T("NO_COMMAND"), true, &fbeNoCommand },
{T("NO_EXAMINE"), true, &fbeNoExamine },
{T("NOBLEED"), true, &fbeNoBleed },
{T("NOEVAL"), true, &fbeNoEval },
{T("NOEXAMINE"), true, &fbeNoExamine },
{T("NOMODIFY"), true, &fbeNoModify },
{T("NO_MODIFY"), true, &fbeNoModify },
{T("NOSPOOF"), true, &fbeNoSpoof },
{T("OPAQUE"), true, &fbeOpaque },
{T("OPEN_OK"), true, &fbeOpenOk },
{T("PARENT_OK"), true, &fbeParentOk },
{T("PLAYER_MAILS"), true, &fbePlayerMails },
{T("PUPPET"), true, &fbePuppet },
{T("QUIET"), true, &fbeQuiet },
{T("ROBOT"), true, &fbeRobot },
{T("ROYALTY"), true, &fbeRoyalty },
{T("SAFE"), true, &fbeSafe },
{T("SITEMON"), true, &fbeSitemon },
{T("SLAVE"), true, &fbeSlave },
{T("SPOOF"), false, &fbeNoSpoof },
{T("STAFF"), true, &fbeStaff },
{T("STICKY"), true, &fbeSticky },
{T("SUSPECT"), true, &fbeSuspect },
{T("TALKMODE"), true, &fbeTalkMode },
{T("TERSE"), true, &fbeTerse },
{T("TRACE"), true, &fbeTrace },
{T("TRANSPARENT"), true, &fbeTransparent },
{T("TRUECOLOR"), true, &fbeTruecolor },
{T("UNFINDABLE"), true, &fbeUnfindable },
{T("UNICODE"), true, &fbeUnicode },
{T("UNINSPECTED"), true, &fbeUninspected },
{T("VACATION"), true, &fbeVacation },
{T("VERBOSE"), true, &fbeVerbose },
{T("VISUAL"), true, &fbeVisual },
{T("WIZARD"), true, &fbeWizard },
#ifdef WOD_REALMS
{T("FAE"), true, &fbeFae },
{T("CHIMERA"), true, &fbeChimera },
{T("PEERING"), true, &fbePeering },
{T("UMBRA"), true, &fbeUmbra },
{T("SHROUD"), true, &fbeShroud },
{T("MATRIX"), true, &fbeMatrix },
{T("OBF"), true, &fbeObf },
{T("HSS"), true, &fbeHss },
{T("MEDIUM"), true, &fbeMedium },
{T("DEAD"), true, &fbeDead },
#endif // WOD_REALMS
{nullptr, false, nullptr}
};
OBJENT object_types[8] =
{
{T("ROOM"), 'R', CA_PUBLIC, OF_CONTENTS|OF_EXITS|OF_DROPTO|OF_HOME},
{T("THING"), ' ', CA_PUBLIC, OF_CONTENTS|OF_LOCATION|OF_EXITS|OF_HOME|OF_SIBLINGS},
{T("EXIT"), 'E', CA_PUBLIC, OF_SIBLINGS},
{T("PLAYER"), 'P', CA_PUBLIC, OF_CONTENTS|OF_LOCATION|OF_EXITS|OF_HOME|OF_OWNER|OF_SIBLINGS},
{T("TYPE5"), '+', CA_GOD, 0},
{T("GARBAGE"), '-', CA_PUBLIC, OF_CONTENTS|OF_LOCATION|OF_EXITS|OF_HOME|OF_SIBLINGS},
{T("GARBAGE"), '#', CA_GOD, 0},
{T("GARBAGE"), '=', CA_GOD, 0}
};
/* ---------------------------------------------------------------------------
* init_flagtab: initialize flag hash tables.
*/
void init_flagtab(void)
{
for (FLAGNAMEENT *flag_name_entity = gen_flag_names; flag_name_entity->pOrigName; flag_name_entity++)
{
flag_name_entity->flagname = const_cast<UTF8*>(flag_name_entity->pOrigName);
const size_t flag_name_length = strlen(reinterpret_cast<const char*>(flag_name_entity->pOrigName));
vector<UTF8> flag_name(flag_name_entity->pOrigName, flag_name_entity->pOrigName + flag_name_length);
auto it = mudstate.flag_names_map.find(flag_name);
if (it == mudstate.flag_names_map.end())
{
mudstate.flag_names_map.insert(make_pair(flag_name, flag_name_entity));
}
}
}
/* ---------------------------------------------------------------------------
* display_flags: display available flags.
*/
void display_flagtab(dbref player)
{
FLAGNAMEENT *fp;
LBuf buf = LBuf_Src("display_flagtab");
UTF8 *bp = buf.get();
safe_str(M_("Flags:"), buf, &bp);
for (fp = gen_flag_names; fp->flagname; fp++)
{
FLAGBITENT *fbe = fp->fbe;
if ( ( (fbe->listperm & CA_WIZARD)
&& !Wizard(player))
|| ( (fbe->listperm & CA_GOD)
&& !God(player)))
{
continue;
}
safe_chr(' ', buf, &bp);
safe_str(fp->flagname, buf, &bp);
if (fbe->flaglett != ' ')
{
safe_chr('(', buf, &bp);
if (!fp->bPositive)
{
safe_chr('!', buf, &bp);
}
safe_chr(fbe->flaglett, buf, &bp);
safe_chr(')', buf, &bp);
}
}
*bp = '\0';
notify(player, buf);
}
UTF8 *MakeCanonicalFlagName
(
const UTF8 *pName,
int *pnName,
bool *pbValid
)
{
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 buff[SBUF_SIZE];
UTF8 *p = buff;
int nName = 0;
// Uppercase flag name one code point at a time (Unicode-aware).
//
while ('\0' != *pName)
{
size_t n = utf8_FirstByte[static_cast<unsigned char>(*pName)];
if (n >= UTF8_CONTINUE)
{
*pnName = 0;
*pbValid = false;
return nullptr;
}
for (size_t j = 1; j < n; j++)
{
if ( '\0' == pName[j]
|| UTF8_CONTINUE != utf8_FirstByte[static_cast<unsigned char>(pName[j])])
{
*pnName = 0;
*pbValid = false;
return nullptr;
}
}
bool bXor;
const string_desc *qDesc = mux_toupper(pName, bXor);
size_t m;
if (nullptr == qDesc)
{
m = n;
if (nName + static_cast<int>(m) >= SBUF_SIZE)
{
break;
}
for (size_t j = 0; j < m; j++)
{
*p++ = pName[j];
}
}
else
{
m = qDesc->n_bytes;
if ( bXor
&& m != n)
{
*pnName = 0;
*pbValid = false;
return nullptr;
}
if (nName + static_cast<int>(m) >= SBUF_SIZE)
{
break;
}
if (bXor)
{
for (size_t j = 0; j < m; j++)
{
*p++ = pName[j] ^ qDesc->p[j];
}
}
else
{
for (size_t j = 0; j < m; j++)
{
*p++ = qDesc->p[j];
}
}
}
nName += static_cast<int>(m);
pName += n;
}
*p = '\0';
if ( 0 < nName
&& nName < SBUF_SIZE)
{
*pnName = nName;
*pbValid = true;
return buff;
}
else
{
*pnName = 0;
*pbValid = false;
2018-10-03 17:54:51 +00:00
return nullptr;
}
}
static FLAGNAMEENT* find_flag(const UTF8* input_flag_name)
{
// Convert input_flag_name to canonical lowercase format.
//
int flag_name_length;
bool valid;
UTF8* flag_name = MakeCanonicalFlagName(input_flag_name, &flag_name_length, &valid);
if (valid)
{
const vector<UTF8> name(flag_name, flag_name + flag_name_length);
const auto it = mudstate.flag_names_map.find(name);
if (it != mudstate.flag_names_map.end())
{
return it->second;
}
}
return nullptr;
}
// ---------------------------------------------------------------------------
// flag_set: Set or clear a specified flag on an object.
//
void flag_set(dbref target, dbref player, UTF8 *flag, int key)
{
bool bDone = false;
do
{
// Trim spaces, and handle the negation character.
//
while (mux_isspace(*flag))
{
flag++;
}
bool bNegate = false;
if (*flag == '!')
{
bNegate = true;
do
{
flag++;
} while (mux_isspace(*flag));
}
// Beginning of flag name is now 'flag'.
//
UTF8 *nflag = flag;
while ( *nflag != '\0'
&& !mux_isspace(*nflag))
{
nflag++;
}
if (*nflag == '\0')
{
bDone = true;
}
else
{
*nflag = '\0';
}
// Make sure a flag name was specified.
//
if (*flag == '\0')
{
if (bNegate)
{
notify(player, M_("You must specify a flag to clear."));
}
else
{
notify(player, M_("You must specify a flag to set."));
}
}
else
{
FLAGNAMEENT *fp = find_flag(flag);
if (!fp)
{
notify(player, M_("I do not understand that flag."));
}
else
{
FLAGBITENT *fbe = fp->fbe;
bool bClearSet = bNegate;
if (!fp->bPositive)
{
bNegate = !bNegate;
}
// Check if the flag is already in the desired state.
//
bool bCurrentlySet = (db[target].fs.word[fbe->flagflag] & fbe->flagvalue) != 0;
if (bNegate == bCurrentlySet)
{
// State needs to change. Check NOMODIFY first.
//
if (NoModify(target) && !WizRoy(player))
{
notify(player, NOPERM_MESSAGE);
bDone = true;
continue;
}
// Invoke the flag handler, and print feedback.
//
if (!fbe->handler(target, player, fbe->flagvalue, fbe->flagflag, bNegate))
{
notify(player, NOPERM_MESSAGE);
}
else if (!(key & SET_QUIET) && !Quiet(player))
{
notify(player, (bClearSet ? M_("Cleared.") : M_("Set.")));
}
}
else if (!(key & SET_QUIET) && !Quiet(player))
{
notify(player, (bClearSet ? M_("Already cleared.") : M_("Already set.")));
}
}
}
flag = nflag + 1;
} while (!bDone);
}
/*
* ---------------------------------------------------------------------------
* * decode_flags: converts a flags word into corresponding letters.
*/
UTF8 *decode_flags(dbref player, FLAGSET *fs)
{
UTF8 *buf, *bp;
buf = bp = alloc_sbuf("decode_flags");
*bp = '\0';
if (!Good_obj(player))
{
mux_strncpy(buf, S_("#-2 ERROR"), SBUF_SIZE-1);
return buf;
}
int flagtype = fs->word[FLAG_WORD1] & TYPE_MASK;
bool bNeedColon = true;
if (object_types[flagtype].lett != ' ')
{
safe_sb_chr(object_types[flagtype].lett, buf, &bp);
bNeedColon = false;
}
FLAGNAMEENT *fp;
for (fp = gen_flag_names; fp->flagname; fp++)
{
FLAGBITENT *fbe = fp->fbe;
if ( !fp->bPositive
|| fbe->flaglett == ' ')
{
// Only look at positive-sense entries that have non-space flag
// letters.
//
continue;
}
if (fs->word[fbe->flagflag] & fbe->flagvalue)
{
if ( ( (fbe->listperm & CA_STAFF)
&& !Staff(player))
|| ( (fbe->listperm & CA_ADMIN)
&& !WizRoy(player))
|| ( (fbe->listperm & CA_WIZARD)
&& !Wizard(player))
|| ( (fbe->listperm & CA_GOD)
&& !God(player)))
{
continue;
}
fix: resolve Pass 8 user-facing defects #1182, #1184, #1186, #1187 Four unclaimed Pass 8 findings in four separate files, chosen to avoid the files the open PR queue is already touching. Each premise was re-verified against current source before fixing. #1182 p6h_vaht_crypt OOB read (player.cpp). The guard only required szSetting to be at least the 13-byte "$P6H$$1:sha1:" prefix, but the timestamp is copied from a constant offset 54 bytes in (prefix + 40 hex digits + separator). Any A_PASS carrying that prefix with a total length of 13..53 passed the check and then ran safe_str off the end of the attribute value. Reachable because mux_crypt classifies anything starting "$P6H$" (and not "XX") as CRYPT_P6H_VAHT, so a truncated or corrupt A_PASS -- raw attribute write or a damaged import, not @password -- reaches it on the login path. Require the whole fixed layout including the separator, and fail closed to szFail. #1184 CONNECTED leak via decode_flags (flags.cpp). has_flag() and flag_description() both hide the 'c' letter on Hidden(target) && !See_Hidden(player). decode_flags() required (WIZARD | DARK) together, so every dark non-wizard -- royalty, staff, any mortal able to set itself DARK -- still emitted 'c' to examiners. Hidden(x) is exactly (Flags(x) & DARK), and decode_flags takes a FLAGSET rather than a dbref, so the aligned test is DARK on the caller's flagset (unparse_object passes the target's). #1186 moniker injection in look_contents (look.cpp). look_exits() html_escape()s exit names inside xch_cmd="...", and the anchor text in look_contents() was already escaped, but CONTENTS_LOCAL and CONTENTS_NESTED inserted Moniker() raw into the attribute. A moniker containing a double quote closed the attribute early and let the rest become further Pueblo/HTML markup for HTML-capable clients. CONTENTS_REMOTE was already safe (it emits #dbref). #1187 page_check charged before validating (speech.cpp). payfor() ran first; the not-connected and both A_LPAGE lock failures then returned false with no refund, so a page that was never delivered still cost page_cost -- once per recipient, since do_page() calls page_check() per target. Reordered to validate first and charge last; payfor() deducts only on success, so no refund path is needed. The wizard "can't return your page" warning is now held until after payment, so a sender who cannot afford the page is not told about one that never happens. Behaviour change worth noting: when a sender both lacks funds and the target is offline, the message is now "Sorry, X is not connected." rather than "You don't have enough coins." -- the actual reason rather than the one that happened to be checked first. Adds tests/scenario/page_cost.py (wired into run.sh) for #1187, the only one of the four reachable without an HTML client or manufactured connection state. It needs a mortal sender, since payfor() exempts wizards outright and a Wizard-only test would pass against any implementation. Test validated against the unfixed build: cases 2 and 4 fail there (offline page charged 5; two offline recipients charged 10, showing the per-recipient amplification) while case 3 passes in both -- so it pins the charge, not merely the absence of one. The other three are not smoke-reachable: #1182 needs a crafted A_PASS, #1184 needs live CONNECTED state on a dark player, #1186 needs an HTML client. Their normal paths are covered -- every scenario driver logs in through mux_crypt, and all four suites pass. Verified: build clean, smoke 1404/1404 0 crashes, scenario 4/4 drivers (wild_capture, site_threshold, jit_perms, telnet_negotiation) plus the new page_cost 4/4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:35:29 -06:00
// Don't show CONNECT on hidden players to those who can't see
// them. Test DARK alone -- that is what Hidden() means -- to
// match has_flag() and flag_description(). Requiring WIZARD too
// leaked the 'c' letter for every dark non-wizard: royalty,
// staff, and any mortal able to set itself DARK (#1184). This
// takes a FLAGSET rather than a dbref, so the test is on the
// flagset the caller passed, which is the target's.
//
if ( flagtype == TYPE_PLAYER
&& fbe->flagflag == FLAG_WORD2
&& fbe->flagvalue == CONNECTED
fix: resolve Pass 8 user-facing defects #1182, #1184, #1186, #1187 Four unclaimed Pass 8 findings in four separate files, chosen to avoid the files the open PR queue is already touching. Each premise was re-verified against current source before fixing. #1182 p6h_vaht_crypt OOB read (player.cpp). The guard only required szSetting to be at least the 13-byte "$P6H$$1:sha1:" prefix, but the timestamp is copied from a constant offset 54 bytes in (prefix + 40 hex digits + separator). Any A_PASS carrying that prefix with a total length of 13..53 passed the check and then ran safe_str off the end of the attribute value. Reachable because mux_crypt classifies anything starting "$P6H$" (and not "XX") as CRYPT_P6H_VAHT, so a truncated or corrupt A_PASS -- raw attribute write or a damaged import, not @password -- reaches it on the login path. Require the whole fixed layout including the separator, and fail closed to szFail. #1184 CONNECTED leak via decode_flags (flags.cpp). has_flag() and flag_description() both hide the 'c' letter on Hidden(target) && !See_Hidden(player). decode_flags() required (WIZARD | DARK) together, so every dark non-wizard -- royalty, staff, any mortal able to set itself DARK -- still emitted 'c' to examiners. Hidden(x) is exactly (Flags(x) & DARK), and decode_flags takes a FLAGSET rather than a dbref, so the aligned test is DARK on the caller's flagset (unparse_object passes the target's). #1186 moniker injection in look_contents (look.cpp). look_exits() html_escape()s exit names inside xch_cmd="...", and the anchor text in look_contents() was already escaped, but CONTENTS_LOCAL and CONTENTS_NESTED inserted Moniker() raw into the attribute. A moniker containing a double quote closed the attribute early and let the rest become further Pueblo/HTML markup for HTML-capable clients. CONTENTS_REMOTE was already safe (it emits #dbref). #1187 page_check charged before validating (speech.cpp). payfor() ran first; the not-connected and both A_LPAGE lock failures then returned false with no refund, so a page that was never delivered still cost page_cost -- once per recipient, since do_page() calls page_check() per target. Reordered to validate first and charge last; payfor() deducts only on success, so no refund path is needed. The wizard "can't return your page" warning is now held until after payment, so a sender who cannot afford the page is not told about one that never happens. Behaviour change worth noting: when a sender both lacks funds and the target is offline, the message is now "Sorry, X is not connected." rather than "You don't have enough coins." -- the actual reason rather than the one that happened to be checked first. Adds tests/scenario/page_cost.py (wired into run.sh) for #1187, the only one of the four reachable without an HTML client or manufactured connection state. It needs a mortal sender, since payfor() exempts wizards outright and a Wizard-only test would pass against any implementation. Test validated against the unfixed build: cases 2 and 4 fail there (offline page charged 5; two offline recipients charged 10, showing the per-recipient amplification) while case 3 passes in both -- so it pins the charge, not merely the absence of one. The other three are not smoke-reachable: #1182 needs a crafted A_PASS, #1184 needs live CONNECTED state on a dark player, #1186 needs an HTML client. Their normal paths are covered -- every scenario driver logs in through mux_crypt, and all four suites pass. Verified: build clean, smoke 1404/1404 0 crashes, scenario 4/4 drivers (wild_capture, site_threshold, jit_perms, telnet_negotiation) plus the new page_cost 4/4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:35:29 -06:00
&& (fs->word[FLAG_WORD1] & DARK) != 0
&& !See_Hidden(player))
{
continue;
}
if ( bNeedColon
&& mux_isdigit(fbe->flaglett))
{
// We can't allow numerical digits at the beginning.
//
safe_sb_chr(':', buf, &bp);
}
safe_sb_chr(fbe->flaglett, buf, &bp);
bNeedColon = false;
}
}
*bp = '\0';
return buf;
}
/*
* ---------------------------------------------------------------------------
* * has_flag: does object have flag visible to player?
*/
bool has_flag(dbref player, dbref it, const UTF8 *flagname)
{
FLAGNAMEENT *fp = find_flag(flagname);
if (!fp)
{
return false;
}
FLAGBITENT *fbe = fp->fbe;
if ( ( fp->bPositive
&& (db[it].fs.word[fbe->flagflag] & fbe->flagvalue))
|| ( !fp->bPositive
&& (db[it].fs.word[fbe->flagflag] & fbe->flagvalue) == 0))
{
if ( ( (fbe->listperm & CA_STAFF)
&& !Staff(player))
|| ( (fbe->listperm & CA_ADMIN)
&& !WizRoy(player))
|| ( (fbe->listperm & CA_WIZARD)
&& !Wizard(player))
|| ( (fbe->listperm & CA_GOD)
&& !God(player)))
{
return false;
}
// Don't show CONNECT on dark wizards to mortals
//
if ( isPlayer(it)
&& (fbe->flagvalue == CONNECTED)
&& (fbe->flagflag == FLAG_WORD2)
&& Hidden(it)
&& !See_Hidden(player))
{
return false;
}
return true;
}
return false;
}
/*
* ---------------------------------------------------------------------------
* * flag_description: Return an mbuf containing the type and flags on thing.
*/
UTF8 *flag_description(dbref player, dbref target)
{
// Allocate the return buffer.
//
int otype = Typeof(target);
UTF8 *buff = alloc_mbuf("flag_description");
UTF8 *bp = buff;
// Store the header strings and object type.
//
safe_mb_str(T("Type: "), buff, &bp);
safe_mb_str(object_types[otype].name, buff, &bp);
safe_mb_str(T(" Flags:"), buff, &bp);
if (object_types[otype].perm != CA_PUBLIC)
{
*bp = '\0';
return buff;
}
// Store the type-invariant flags.
//
FLAGNAMEENT *fp;
for (fp = gen_flag_names; fp->flagname; fp++)
{
if (!fp->bPositive)
{
continue;
}
FLAGBITENT *fbe = fp->fbe;
if (db[target].fs.word[fbe->flagflag] & fbe->flagvalue)
{
if ( ( (fbe->listperm & CA_STAFF)
&& !Staff(player))
|| ( (fbe->listperm & CA_ADMIN)
&& !WizRoy(player))
|| ( (fbe->listperm & CA_WIZARD)
&& !Wizard(player))
|| ( (fbe->listperm & CA_GOD)
&& !God(player)))
{
continue;
}
// Don't show CONNECT on dark wizards to mortals.
//
if ( isPlayer(target)
&& (fbe->flagvalue == CONNECTED)
&& (fbe->flagflag == FLAG_WORD2)
&& Hidden(target)
&& !See_Hidden(player))
{
continue;
}
safe_mb_chr(' ', buff, &bp);
safe_mb_str(fp->flagname, buff, &bp);
}
}
// Terminate the string, and return the buffer to the caller.
//
*bp = '\0';
return buff;
}
/*
* ---------------------------------------------------------------------------
* * Return an lbuf containing the name and number of an object
*/
UTF8 *unparse_object_numonly(dbref target)
{
engine: add LBufPtr owning handle; convert a verified subset to RAII (#717) Incremental progress on the alloc_lbuf/free_lbuf -> LBuf RAII migration, adding the structural enabler the issue calls for and converting a small, individually verified set of the harder (ownership-escaping) sites. alloc.h: - LBuf gains release() (relinquish ownership to a caller) and reset() (free now, become empty). release() lets a producer that returns an lbuf use RAII for its early/error paths and hand the buffer off at the success return. - New LBufPtr: an owning, nullable, movable handle. Unlike LBuf (which always allocates and suits scoped scratch), LBufPtr can be empty, reseated, and released — for conditional ownership, storing an lbuf in a struct/queue entry, or producing a buffer to return. LBufPtr_Src / LBufPtr_Adopt mirror the LBuf macros. Converted sites (each verified to neither double-free nor leak on any path): - flags.cpp: unparse_object_numonly, unparse_object — single-exit producers, now LBuf + release(). - comsys.cpp: call_mogrifier — LBufPtr + release() (its early nullptr returns precede the allocation, so they are unaffected). - functions.cpp: switch_handler / switchall_handler — the ping-pong mbuff/tbuff scratch buffers now use LBuf RAII, removing six manual free_lbuf() calls including the error-prone early-return path in switch_handler. mbuff is only transiently aliased into mudstate.switch_token (restored before scope exit), so RAII scope-exit freeing preserves its lifetime. The remaining ~145 manual sites (fargs[] stores, did_it() charge/runout swaps, cross-function/struct lifetimes) still need per-site structural work; #717 stays open. Build clean, all 1053 smoke tests pass (the corpus exercises switch()/ case()/unparse_object heavily). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:52:07 -06:00
LBuf buf = LBuf_Src("unparse_object_numonly");
if (target < 0)
{
mux_strncpy(buf, aszSpecialDBRefNames[-target], LBUF_SIZE-1);
}
else if (!Good_obj(target))
{
mux_sprintf(buf, LBUF_SIZE, T("*ILLEGAL*(#%d)"), target);
}
else
{
mux_sprintf(buf, LBUF_SIZE, T("%s(#%d)"), PureName(target), target);
}
engine: add LBufPtr owning handle; convert a verified subset to RAII (#717) Incremental progress on the alloc_lbuf/free_lbuf -> LBuf RAII migration, adding the structural enabler the issue calls for and converting a small, individually verified set of the harder (ownership-escaping) sites. alloc.h: - LBuf gains release() (relinquish ownership to a caller) and reset() (free now, become empty). release() lets a producer that returns an lbuf use RAII for its early/error paths and hand the buffer off at the success return. - New LBufPtr: an owning, nullable, movable handle. Unlike LBuf (which always allocates and suits scoped scratch), LBufPtr can be empty, reseated, and released — for conditional ownership, storing an lbuf in a struct/queue entry, or producing a buffer to return. LBufPtr_Src / LBufPtr_Adopt mirror the LBuf macros. Converted sites (each verified to neither double-free nor leak on any path): - flags.cpp: unparse_object_numonly, unparse_object — single-exit producers, now LBuf + release(). - comsys.cpp: call_mogrifier — LBufPtr + release() (its early nullptr returns precede the allocation, so they are unaffected). - functions.cpp: switch_handler / switchall_handler — the ping-pong mbuff/tbuff scratch buffers now use LBuf RAII, removing six manual free_lbuf() calls including the error-prone early-return path in switch_handler. mbuff is only transiently aliased into mudstate.switch_token (restored before scope exit), so RAII scope-exit freeing preserves its lifetime. The remaining ~145 manual sites (fargs[] stores, did_it() charge/runout swaps, cross-function/struct lifetimes) still need per-site structural work; #717 stays open. Build clean, all 1053 smoke tests pass (the corpus exercises switch()/ case()/unparse_object heavily). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:52:07 -06:00
return buf.release();
}
/*
* ---------------------------------------------------------------------------
* * Return an lbuf pointing to the object name and possibly the db# and flags
*/
2021-02-24 16:17:42 -07:00
UTF8 *unparse_object(dbref player, dbref target, bool obey_myopic)
{
engine: add LBufPtr owning handle; convert a verified subset to RAII (#717) Incremental progress on the alloc_lbuf/free_lbuf -> LBuf RAII migration, adding the structural enabler the issue calls for and converting a small, individually verified set of the harder (ownership-escaping) sites. alloc.h: - LBuf gains release() (relinquish ownership to a caller) and reset() (free now, become empty). release() lets a producer that returns an lbuf use RAII for its early/error paths and hand the buffer off at the success return. - New LBufPtr: an owning, nullable, movable handle. Unlike LBuf (which always allocates and suits scoped scratch), LBufPtr can be empty, reseated, and released — for conditional ownership, storing an lbuf in a struct/queue entry, or producing a buffer to return. LBufPtr_Src / LBufPtr_Adopt mirror the LBuf macros. Converted sites (each verified to neither double-free nor leak on any path): - flags.cpp: unparse_object_numonly, unparse_object — single-exit producers, now LBuf + release(). - comsys.cpp: call_mogrifier — LBufPtr + release() (its early nullptr returns precede the allocation, so they are unaffected). - functions.cpp: switch_handler / switchall_handler — the ping-pong mbuff/tbuff scratch buffers now use LBuf RAII, removing six manual free_lbuf() calls including the error-prone early-return path in switch_handler. mbuff is only transiently aliased into mudstate.switch_token (restored before scope exit), so RAII scope-exit freeing preserves its lifetime. The remaining ~145 manual sites (fargs[] stores, did_it() charge/runout swaps, cross-function/struct lifetimes) still need per-site structural work; #717 stays open. Build clean, all 1053 smoke tests pass (the corpus exercises switch()/ case()/unparse_object heavily). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:52:07 -06:00
LBuf buf = LBuf_Src("unparse_object");
if (NOPERM <= target && target < 0)
{
mux_strncpy(buf, aszSpecialDBRefNames[-target], LBUF_SIZE-1);
}
else if (!Good_obj(target))
{
mux_sprintf(buf, LBUF_SIZE, T("*ILLEGAL*(#%d)"), target);
}
else
{
bool exam;
if (obey_myopic)
{
exam = MyopicExam(player, target);
}
else
{
exam = Examinable(player, target);
}
// Leave and extra 100 bytes for the dbref and flags at the end and
// color at the beginning if necessary..
//
mux_field fldName = StripTabsAndTruncate( Moniker(target), buf,
LBUF_SIZE-100, LBUF_SIZE-100);
UTF8 *bp = buf + fldName.m_byte;
if ( exam
|| (Flags(target) & (CHOWN_OK | JUMP_OK | LINK_OK | DESTROY_OK))
|| (Flags2(target) & ABODE))
{
// Show everything.
//
UTF8 *fp = decode_flags(player, &(db[target].fs));
safe_str(T("(#"), buf, &bp);
safe_ltoa(target, buf, &bp);
safe_str(fp, buf, &bp);
safe_chr(')', buf, &bp);
free_sbuf(fp);
}
*bp = '\0';
}
engine: add LBufPtr owning handle; convert a verified subset to RAII (#717) Incremental progress on the alloc_lbuf/free_lbuf -> LBuf RAII migration, adding the structural enabler the issue calls for and converting a small, individually verified set of the harder (ownership-escaping) sites. alloc.h: - LBuf gains release() (relinquish ownership to a caller) and reset() (free now, become empty). release() lets a producer that returns an lbuf use RAII for its early/error paths and hand the buffer off at the success return. - New LBufPtr: an owning, nullable, movable handle. Unlike LBuf (which always allocates and suits scoped scratch), LBufPtr can be empty, reseated, and released — for conditional ownership, storing an lbuf in a struct/queue entry, or producing a buffer to return. LBufPtr_Src / LBufPtr_Adopt mirror the LBuf macros. Converted sites (each verified to neither double-free nor leak on any path): - flags.cpp: unparse_object_numonly, unparse_object — single-exit producers, now LBuf + release(). - comsys.cpp: call_mogrifier — LBufPtr + release() (its early nullptr returns precede the allocation, so they are unaffected). - functions.cpp: switch_handler / switchall_handler — the ping-pong mbuff/tbuff scratch buffers now use LBuf RAII, removing six manual free_lbuf() calls including the error-prone early-return path in switch_handler. mbuff is only transiently aliased into mudstate.switch_token (restored before scope exit), so RAII scope-exit freeing preserves its lifetime. The remaining ~145 manual sites (fargs[] stores, did_it() charge/runout swaps, cross-function/struct lifetimes) still need per-site structural work; #717 stays open. Build clean, all 1053 smoke tests pass (the corpus exercises switch()/ case()/unparse_object heavily). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:52:07 -06:00
return buf.release();
}
/* ---------------------------------------------------------------------------
* cf_flag_access: Modify who can set a flag.
*/
CF_HAND(cf_flag_access)
{
UNUSED_PARAMETER(vp);
UNUSED_PARAMETER(pExtra);
UNUSED_PARAMETER(nExtra);
string_token st(str, T(" \t=,"));
UTF8 *fstr = st.parse();
UTF8 *permstr = st.parse();
2018-10-03 17:54:51 +00:00
if ( nullptr == fstr
|| '\0' == fstr[0]
2018-10-03 17:54:51 +00:00
|| nullptr == permstr
|| '\0' == permstr[0])
{
return -1;
}
FLAGNAMEENT *fp;
2018-10-03 17:54:51 +00:00
if ((fp = find_flag(fstr)) == nullptr)
{
cf_log_notfound(player, cmd, T("No such flag"), fstr);
return -1;
}
FLAGBITENT *fbe = fp->fbe;
// Don't change the handlers on special things.
//
if ( (fbe->handler != fh_any)
&& (fbe->handler != fh_wizroy)
&& (fbe->handler != fh_wiz)
&& (fbe->handler != fh_god)
&& (fbe->handler != fh_restrict_player)
&& (fbe->handler != fh_privileged))
{
STARTLOG(LOG_CONFIGMODS, "CFG", "PERM");
log_text(T("Cannot change access for flag: "));
log_text(fp->flagname);
ENDLOG;
notify(player, M_("Special flags cannot be modified with flag_access."));
return -1;
}
bool negate = false;
if ('!' == *permstr)
{
negate = true;
permstr++;
}
if (!strcmp(reinterpret_cast<char *>(permstr), "any"))
{
fbe->handler = fh_any;
}
else if (!strcmp(reinterpret_cast<char *>(permstr), "dark"))
{
if (negate)
{
fbe->listperm &= ~CA_GOD;
}
else
{
fbe->listperm |= CA_GOD;
}
}
else if (!strcmp(reinterpret_cast<char *>(permstr), "hidden"))
{
if (negate)
{
fbe->listperm &= ~CA_WIZARD;
}
else
{
fbe->listperm |= CA_WIZARD;
}
}
else if (!strcmp(reinterpret_cast<char *>(permstr), "royalty"))
{
fbe->handler = fh_wizroy;
}
else if (!strcmp(reinterpret_cast<char *>(permstr), "wizard"))
{
fbe->handler = fh_wiz;
}
else if (!strcmp(reinterpret_cast<char *>(permstr), "god"))
{
fbe->handler = fh_god;
}
else if (!strcmp(reinterpret_cast<char *>(permstr), "restrict_player"))
{
fbe->handler = fh_restrict_player;
}
else if (!strcmp(reinterpret_cast<char *>(permstr), "privileged"))
{
fbe->handler = fh_privileged;
}
else if (!strcmp(reinterpret_cast<char *>(permstr), "staff"))
{
fbe->handler = fh_staff;
}
else
{
cf_log_notfound(player, cmd, T("Flag access"), permstr);
return -1;
}
return 0;
}
/*
* ---------------------------------------------------------------------------
* * convert_flags: convert a list of flag letters into its bit pattern.
* * Also set the type qualifier if specified and not already set.
*/
bool convert_flags(dbref player, UTF8 *flaglist, FLAGSET *fset, FLAG *p_type)
{
FLAG type = NOTYPE;
FLAGSET flagmask;
memset(&flagmask, 0, sizeof(flagmask));
int i;
UTF8 *s;
bool handled;
for (s = flaglist; *s; s++)
{
handled = false;
// Check for object type.
//
for (i = 0; i <= 7 && !handled; i++)
{
if ( object_types[i].lett == *s
&& !( ( (object_types[i].perm & CA_STAFF)
&& !Staff(player))
|| ( (object_types[i].perm & CA_ADMIN)
&& !WizRoy(player))
|| ( (object_types[i].perm & CA_WIZARD)
&& !Wizard(player))
|| ( (object_types[i].perm & CA_GOD)
&& !God(player))))
{
if ( type != NOTYPE
&& type != i)
{
UTF8 *p = tprintf(M_("%c: Conflicting type specifications."),
*s);
notify(player, p);
return false;
}
type = i;
handled = true;
}
}
// Check generic flags.
//
if (handled)
{
continue;
}
FLAGNAMEENT *fp;
for (fp = gen_flag_names; fp->flagname && !handled; fp++)
{
FLAGBITENT *fbe = fp->fbe;
if ( !fp->bPositive
|| fbe->flaglett == ' ')
{
continue;
}
if ( fbe->flaglett == *s
&& !( ( (fbe->listperm & CA_STAFF)
&& !Staff(player))
|| ( (fbe->listperm & CA_ADMIN)
&& !WizRoy(player))
|| ( (fbe->listperm & CA_WIZARD)
&& !Wizard(player))
|| ( (fbe->listperm & CA_GOD)
&& !God(player))))
{
flagmask.word[fbe->flagflag] |= fbe->flagvalue;
handled = true;
}
}
if (!handled)
{
notify(player,
tprintf(M_("%c: Flag unknown or not valid for specified object type"),
*s));
return false;
}
}
// Return flags to search for and type.
//
*fset = flagmask;
*p_type = type;
return true;
}
/*
* ---------------------------------------------------------------------------
* * decompile_flags: Produce commands to set flags on target.
*/
void decompile_flags(dbref player, dbref thing, UTF8 *thingname)
{
// Report generic flags.
//
FLAGNAMEENT *fp;
for (fp = gen_flag_names; fp->flagname; fp++)
{
FLAGBITENT *fbe = fp->fbe;
// Only handle positive-sense entries.
// Skip if we shouldn't decompile this flag.
// Skip if this flag isn't set.
// Skip if we can't see this flag.
//
if ( !fp->bPositive
|| (fbe->listperm & CA_NO_DECOMP)
|| (db[thing].fs.word[fbe->flagflag] & fbe->flagvalue) == 0
|| !check_access(player, fbe->listperm))
{
continue;
}
// Report this flag.
//
notify(player, tprintf(T("@set %s=%s"), thingname, fp->flagname));
}
}
// do_flag: Rename flags or remove flag aliases.
// Based on RhostMUSH code.
//
static bool flag_rename(UTF8 *existing_flag_name, UTF8 *new_flag_name)
{
int existing_flag_name_length;
bool valid;
UTF8 *canonical_existing_flag_name = MakeCanonicalFlagName(existing_flag_name, &existing_flag_name_length, &valid);
if (valid)
{
const vector<UTF8> existing_name(canonical_existing_flag_name, canonical_existing_flag_name + existing_flag_name_length);
const auto it_existing = mudstate.flag_names_map.find(existing_name);
if (it_existing != mudstate.flag_names_map.end())
{
int new_flag_name_length;
UTF8* canonical_new_flag_name = MakeCanonicalFlagName(new_flag_name, &new_flag_name_length, &valid);
if (valid)
{
vector<UTF8> new_name(canonical_new_flag_name, canonical_new_flag_name + new_flag_name_length);
const auto it_new = mudstate.flag_names_map.find(new_name);
if ( it_new != mudstate.flag_names_map.end()
&& it_new->second == it_existing->second)
{
// New name is an alias for the same flag.
// Remove the alias so the rename can proceed.
//
mudstate.flag_names_map.erase(it_new);
}
if (mudstate.flag_names_map.find(new_name) == mudstate.flag_names_map.end())
{
FLAGNAMEENT* flag_name_entity = it_existing->second;
mudstate.flag_names_map.insert(make_pair(new_name, flag_name_entity));
if (flag_name_entity->flagname != flag_name_entity->pOrigName)
{
MEMFREE(flag_name_entity->flagname);
}
flag_name_entity->flagname = StringCloneLen(canonical_new_flag_name, new_flag_name_length);
return true;
}
}
}
}
return false;
}
void do_flag(dbref executor, dbref caller, dbref enactor, int eval, int key, int nargs,
UTF8 *existing_flag_name, UTF8 *new_flag_name, const UTF8 *cargs[], int ncargs)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if (key & FLAG_REMOVE)
{
if (nargs == 2)
{
notify(executor, M_("Extra argument ignored."));
}
int flag_name_length;
bool valid;
UTF8 *canonical_flag_name = MakeCanonicalFlagName(existing_flag_name, &flag_name_length, &valid);
if (valid)
{
const vector<UTF8> name(canonical_flag_name, canonical_flag_name + flag_name_length);
const auto it = mudstate.flag_names_map.find(name);
if (it != mudstate.flag_names_map.end())
{
FLAGNAMEENT* flag_name_entity = it->second;
if ( flag_name_entity->flagname != flag_name_entity->pOrigName
&& mux_stricmp(flag_name_entity->flagname, canonical_flag_name) == 0)
{
MEMFREE(flag_name_entity->flagname);
flag_name_entity->flagname = const_cast<UTF8*>(flag_name_entity->pOrigName);
mudstate.flag_names_map.erase(it);
notify(executor, tprintf(M_("Flag name %s removed from the hash table."), canonical_flag_name));
}
else
{
notify(executor, M_("Error: You cant remove the present flag name from the hash table."));
}
}
Fix command-side verb correctness bugs (@clone, @ps, whisper, @flag, @mark) Correctness sweep of the command-side verb handlers. Five confirmed bugs plus a help-text correction, verified by dual-lens review and code reading (the read-only smoke harness can't exercise these verbs directly): - @clone/cost on an exit bypassed the "must control current location" check (it lived only on the non-/cost path), letting a builder splice a cloned exit into a room they do not control. (#855) - The @mark/@mark_all/@apply_marked DB-cleaning refusal cited @unmark_all, which does not exist (produces "Huh?"); corrected to @mark_all/clear. (#856) - @ps <object> reported nothing for a controlled object owned by another player; do_ps was missing the non-player-target else clause that the sibling @halt has (clear the owner filter). (#857) - whisper "<quoted name>" skipped the locality/connected gate the unquoted form applies, giving a success confirmation plus a delivery error and polluting A_LASTWHISPER; also fixed an adjacent quoted-name continue that did not advance the parser. (#858) - @flag/remove of an unknown/empty flag name was silent; now reports an error like the other flag-name failure paths. (#859) - report help said 8-hour segments but the code uses 4 (deliberately, per 4a845139f); corrected the help. (#860) Also restores the "## JIT / DBT Engine" CHANGES heading dropped during an earlier 2.14.0.8 edit. Build clean, all 1264 smoke tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 06:29:11 -06:00
else
{
notify(executor, M_("Error: Bad flagname given or flag not found."));
Fix command-side verb correctness bugs (@clone, @ps, whisper, @flag, @mark) Correctness sweep of the command-side verb handlers. Five confirmed bugs plus a help-text correction, verified by dual-lens review and code reading (the read-only smoke harness can't exercise these verbs directly): - @clone/cost on an exit bypassed the "must control current location" check (it lived only on the non-/cost path), letting a builder splice a cloned exit into a room they do not control. (#855) - The @mark/@mark_all/@apply_marked DB-cleaning refusal cited @unmark_all, which does not exist (produces "Huh?"); corrected to @mark_all/clear. (#856) - @ps <object> reported nothing for a controlled object owned by another player; do_ps was missing the non-player-target else clause that the sibling @halt has (clear the owner filter). (#857) - whisper "<quoted name>" skipped the locality/connected gate the unquoted form applies, giving a success confirmation plus a delivery error and polluting A_LASTWHISPER; also fixed an adjacent quoted-name continue that did not advance the parser. (#858) - @flag/remove of an unknown/empty flag name was silent; now reports an error like the other flag-name failure paths. (#859) - report help said 8-hour segments but the code uses 4 (deliberately, per 4a845139f); corrected the help. (#860) Also restores the "## JIT / DBT Engine" CHANGES heading dropped during an earlier 2.14.0.8 edit. Build clean, all 1264 smoke tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 06:29:11 -06:00
}
}
else
{
notify(executor, M_("Error: Bad flagname given or flag not found."));
}
}
else
{
if (nargs < 2)
{
notify(executor, M_("You must specify a flag and a name."));
return;
}
if (flag_rename(existing_flag_name, new_flag_name))
{
notify(executor, M_("Flag name changed."));
}
else
{
notify(executor, M_("Error: Bad flagname given or flag not found."));
}
}
}
/* ---------------------------------------------------------------------------
* cf_flag_name: Rename a flag. Counterpart to @flag.
*/
CF_HAND(cf_flag_name)
{
UNUSED_PARAMETER(vp);
UNUSED_PARAMETER(pExtra);
UNUSED_PARAMETER(nExtra);
UNUSED_PARAMETER(player);
UNUSED_PARAMETER(cmd);
string_token st(str, T(" \t=,"));
UTF8 *flagstr = st.parse();
UTF8 *namestr = st.parse();
if ( !flagstr
|| !*flagstr
|| !namestr
|| !*namestr)
{
return -1;
}
if (flag_rename(flagstr, namestr))
{
return 0;
}
else
{
return -1;
}
}