tinymux/mux/modules/engine/levels.cpp
Stephen Dennis 8808b4c375 feat(#2136): flip fargs to const UTF8 * const — and convert every site the compiler surfaced
The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module
interfaces take `const UTF8 * const fargs[]`.  Double-const is
load-bearing: C++ qualification conversion needs const at both pointer
levels, so builder-side `UTF8 *[]` arrays convert implicitly — the
evaluator, the JIT marshaller, and every owner site need zero casts,
and slot reassignment inside bodies becomes a compile error for free.

The conversions: the flip landed first so the compiler enumerated every
violation; this commit is that inventory worked to zero — ~250 sites
across funceval, funceval2, functions, funmath, help, mail, session,
powers, levels, predicates, conf, walkdb, stringutil, timeutil/
date_scan (regenerated, one-line diff), exp3, and mux_main, each
classified per docs/campaign-2136-const-fargs.md's four recipes.

New idioms (functions.h): trim_space_sep_n() — non-destructive trim for
(pointer, length) consumers, so trim-then-scan sites need no copy at
all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers.
countwords() and DecodeListOfIntegers() rewritten non-destructive.

The flip deleted more than it added: #2157's fun_munge list1 copy, the
engine_com help-topic copy, fun_index's in-place NUL write, and five
const_casts (process_sex x4, sha1_helper).  const_cast budget: zero
added.

Trap recorded in the brief: an old-signature definition doesn't fail
the build — it becomes a C++ overload, and the new-signature symbol
stays undefined until dlopen(RTLD_NOW).  delim_check, the conn_bridge
bridges, the dbt_spike stub, and exp3::Call were all silently shadowed;
muxscript was the only host that noticed, because netmux's own net.cpp
resolved the flat-namespace lookup.  After any signature flip, grep the
old spelling.

Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and
make test-scenario, including the new tests/scenario/sidefx_fargs.py
that live-probes the class-3 wrappers smoke never touches (pemit/
trigger/link/tel/wipe/destroy).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 14:34:23 -06:00

885 lines
21 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*! \file levels.cpp
* \brief Reality levels stuff.
*
* See mux/REALITY.SETUP in the distribution.
*/
#ifdef REALITY_LVLS
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
RLEVEL RxLevel(dbref thing)
{
const UTF8 *buff = atr_get_raw(thing, A_RLEVEL);
if ( nullptr == buff
|| strlen(reinterpret_cast<const char *>(buff)) != 17)
{
switch (Typeof(thing))
{
case TYPE_ROOM:
return(mudconf.def_room_rx);
case TYPE_PLAYER:
return(mudconf.def_player_rx);
case TYPE_EXIT:
return(mudconf.def_exit_rx);
default:
return(mudconf.def_thing_rx);
}
}
int i;
RLEVEL rx = 0;
for (i = 0; mux_isxdigit(buff[i]); i++)
{
rx = 16 * rx + mux_hex2dec(buff[i]);
}
return rx;
}
RLEVEL TxLevel(dbref thing)
{
const UTF8 *buff = atr_get_raw(thing, A_RLEVEL);
if ( nullptr == buff
|| strlen(reinterpret_cast<const char *>(buff)) != 17)
{
switch (Typeof(thing))
{
case TYPE_ROOM:
return(mudconf.def_room_tx);
case TYPE_PLAYER:
return(mudconf.def_player_tx);
case TYPE_EXIT:
return(mudconf.def_exit_tx);
default:
return(mudconf.def_thing_tx);
}
}
// Skip the first field.
//
int i;
for (i = 0; buff[i] && !mux_isspace(buff[i]); i++)
{
; // Nothing.
}
RLEVEL tx = 0;
if (buff[i])
{
// Skip space found above.
//
i++;
// Decode second field.
//
for ( ; mux_isxdigit(buff[i]); i++)
{
tx = 16 * tx + mux_hex2dec(buff[i]);
}
}
return tx;
}
void notify_except_rlevel
(
dbref loc,
dbref player,
dbref exception,
const UTF8 *msg,
int xflags
)
{
if ( loc != exception
&& IsReal(loc, player))
{
notify_check(loc, player, msg,
(MSG_ME_ALL | MSG_F_UP | MSG_S_INSIDE | MSG_NBR_EXITS_A| xflags));
}
dbref first;
DOLIST(first, Contents(loc))
{
if ( first != exception
&& IsReal(first, player)
&& !(isPlayer(first) && Alone(loc)))
{
notify_check(first, player, msg,
(MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE | xflags));
}
}
}
void notify_except2_rlevel
(
dbref loc,
dbref player,
dbref exc1,
dbref exc2,
const UTF8 *msg
)
{
if ( loc != exc1
&& loc != exc2
&& IsReal(loc, player))
{
notify_check(loc, player, msg,
(MSG_ME_ALL | MSG_F_UP | MSG_S_INSIDE | MSG_NBR_EXITS_A));
}
dbref first;
DOLIST(first, Contents(loc))
{
if ( first != exc1
&& first != exc2
&& IsReal(first, player)
&& !(isPlayer(first) && Alone(loc)))
{
notify_check(first, player, msg,
(MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE));
}
}
}
void notify_except2_rlevel2
(
dbref loc,
dbref player,
dbref exc1,
dbref exc2,
const UTF8 *msg
)
{
if ( loc != exc1
&& loc != exc2
&& IsReal(loc, player))
{
notify_check(loc, player, msg,
(MSG_ME_ALL | MSG_F_UP | MSG_S_INSIDE | MSG_NBR_EXITS_A));
}
dbref first;
DOLIST(first, Contents(loc))
{
if ( first != exc1
&& first != exc2
&& IsReal(first, player)
&& IsReal(first, exc2)
&& !(isPlayer(first) && Alone(loc)))
{
notify_check(first, player, msg,
(MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE));
}
}
}
/*
* ---------------------------------------------------------------------------
* * rxlevel_description: Return an mbuf containing the RxLevels of the thing.
*/
UTF8 *rxlevel_description(dbref player, dbref target)
{
// Allocate the return buffer.
//
int otype = Typeof(target);
UTF8 *buff = alloc_mbuf("rxlevel_description");
UTF8 *bp = buff;
int i;
RLEVEL rl = RxLevel(target);
for (i = 0; i < mudconf.no_levels; ++i)
{
confdata::rlevel_def *rldef = &mudconf.reality_level[i];
if ((rl & rldef->value) == rldef->value)
{
safe_mb_chr(' ', buff, &bp);
safe_mb_str(rldef->name, buff, &bp);
}
}
// Terminate the string, and return the buffer to the caller.
//
*bp = '\0';
return buff;
}
/*
* ---------------------------------------------------------------------------
* * txlevel_description: Return an mbuf containing the TxLevels of the thing.
*/
UTF8 *txlevel_description(dbref player, dbref target)
{
// Allocate the return buffer.
//
int otype = Typeof(target);
UTF8 *buff = alloc_mbuf("txlevel_description");
UTF8 *bp = buff;
int i;
RLEVEL tl = TxLevel(target);
for (i = 0; i < mudconf.no_levels; ++i)
{
confdata::rlevel_def *rldef = &mudconf.reality_level[i];
if ((tl & rldef->value) == rldef->value)
{
safe_mb_chr(' ', buff, &bp);
safe_mb_str(rldef->name, buff, &bp);
}
}
// Terminate the string, and return the buffer to the caller.
//
*bp = '\0';
return buff;
}
RLEVEL find_rlevel(const UTF8 *name)
{
for (int i = 0; i < mudconf.no_levels; i++)
{
confdata::rlevel_def *rldef = &mudconf.reality_level[i];
if (mux_stricmp(name, rldef->name) == 0)
{
return rldef->value;
}
}
return 0;
}
void do_rxlevel
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *object,
UTF8 *arg,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if (!arg || !*arg)
{
notify_quiet(executor, M_("I dont know what you want to set!"));
return;
}
// Find thing.
//
dbref thing = match_controlled(executor, object);
if (NOTHING == thing)
{
return;
}
UTF8 lname[9];
RLEVEL ormask = 0;
RLEVEL andmask = ~ormask;
while (*arg)
{
int negate = 0;
while ( *arg != '\0'
&& mux_isspace(*arg))
{
arg++;
}
if (*arg == '!')
{
negate = 1;
++arg;
}
int i;
for (i = 0; *arg && !mux_isspace(*arg); arg++)
{
if (i < 8)
{
lname[i++] = *arg;
}
}
lname[i] = '\0';
if (!lname[0])
{
if (negate)
{
notify(executor, M_("You must specify a reality level to clear."));
}
else
{
notify(executor, M_("You must specify a reality level to set."));
}
return;
}
RLEVEL result = find_rlevel(lname);
if (!result)
{
notify(executor, M_("No such reality level."));
continue;
}
if (negate)
{
andmask &= ~result;
notify(executor, M_("Cleared."));
}
else
{
ormask |= result;
notify(executor, M_("Set."));
}
}
// Set the Rx Level.
//
LBuf buff = LBuf_Src("do_rxlevel");
mux_sprintf(buff.get(), LBUF_SIZE, T("%08X %08X"), RxLevel(thing) & andmask | ormask, TxLevel(thing));
atr_add_raw(thing, A_RLEVEL, buff);
}
void do_txlevel
(
dbref executor,
dbref caller,
dbref enactor,
int eval,
int key,
int nargs,
UTF8 *object,
UTF8 *arg,
const UTF8 *cargs[],
int ncargs
)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(cargs);
UNUSED_PARAMETER(ncargs);
if (!arg || !*arg)
{
notify_quiet(executor, M_("I dont know what you want to set!"));
return;
}
// Find thing.
//
dbref thing = match_controlled(executor, object);
if (NOTHING == thing)
{
return;
}
UTF8 lname[9];
RLEVEL ormask = 0;
RLEVEL andmask = ~ormask;
while (*arg)
{
int negate = 0;
while ( *arg
&& mux_isspace(*arg))
{
arg++;
}
if (*arg == '!')
{
negate = 1;
++arg;
}
int i;
for (i = 0; *arg && !mux_isspace(*arg); arg++)
{
if (i < 8)
{
lname[i++] = *arg;
}
}
lname[i] = '\0';
if (!lname[0])
{
if (negate)
{
notify(executor, M_("You must specify a reality level to clear."));
}
else
{
notify(executor, M_("You must specify a reality level to set."));
}
return;
}
RLEVEL result = find_rlevel(lname);
if (!result)
{
notify(executor, M_("No such reality level."));
continue;
}
if (negate)
{
andmask &= ~result;
notify(executor, M_("Cleared."));
}
else
{
ormask |= result;
notify(executor, M_("Set."));
}
}
// Set the Tx Level.
//
LBuf buff = LBuf_Src("do_rxlevel");
mux_sprintf(buff.get(), LBUF_SIZE, T("%08X %08X"), RxLevel(thing), TxLevel(thing) & andmask | ormask);
atr_add_raw(thing, A_RLEVEL, buff);
}
/*
* ---------------------------------------------------------------------------
* * decompile_rlevels: Produce commands to set reality levels on target.
*/
void decompile_rlevels(dbref player, dbref thing, UTF8 *thingname)
{
UTF8 *buf = rxlevel_description(player, thing);
notify(player, tprintf(T("@rxlevel %s=%s"), thingname, buf));
free_mbuf(buf);
buf = txlevel_description(player, thing);
notify(player, tprintf(T("@txlevel %s=%s"), thingname, buf));
free_mbuf(buf);
}
#define NUM_DESC 33
typedef struct DESC_INFO
{
int n;
int descs[NUM_DESC];
} DESC_INFO;
DESC_INFO *desclist_match(dbref player, dbref thing)
{
static DESC_INFO descbuffer;
descbuffer.n = 0;
RLEVEL match = RxLevel(player) & TxLevel(thing);
for (int i = 0; i < mudconf.no_levels; i++)
{
confdata::rlevel_def *rldef = &mudconf.reality_level[i];
if ((match & rldef->value) == rldef->value)
{
ATTR *at = atr_str(rldef->attr);
if (nullptr != at)
{
bool bFound = false;
for (int j = 0; j < descbuffer.n; j++)
{
if (at->number == descbuffer.descs[j])
{
bFound = true;
break;
}
}
if ( !bFound
&& descbuffer.n < NUM_DESC-1)
{
descbuffer.descs[descbuffer.n] = at->number;
descbuffer.n++;
}
}
}
}
return &descbuffer;
}
UTF8 *get_rlevel_desc
(
dbref player,
dbref thing,
int *piDescUsed
)
{
dbref aowner;
int aflags;
UTF8 *buff = alloc_lbuf("get_rlevel_desc.");
UTF8 *bp = buff;;
reg_ref **preserve = nullptr;
bool need_pres = false;
bool bFirst = true;
// Get description list.
//
DESC_INFO *desclist = desclist_match(player, thing);
bool found_a_desc = false;
for (int i = 0; i < desclist->n; i++)
{
LBuf d = LBuf_Adopt(atr_pget(thing, desclist->descs[i], &aowner, &aflags));
if ('\0' != d[0])
{
found_a_desc = true;
if (!need_pres)
{
need_pres = true;
preserve = PushRegisters(MAX_GLOBAL_REGS);
save_global_regs(preserve);
}
if (!bFirst)
{
safe_str(T("\r\n"), buff, &bp);
}
else
{
bFirst = false;
*piDescUsed = desclist->descs[i];
}
mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, thing, player,
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP),
nullptr, 0);
}
}
if (!found_a_desc)
{
LBuf d = LBuf_Adopt(atr_pget(thing, A_DESC, &aowner, &aflags));
if ('\0' != d[0])
{
found_a_desc = true;
if (!need_pres)
{
need_pres = true;
preserve = PushRegisters(MAX_GLOBAL_REGS);
save_global_regs(preserve);
}
mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, thing, player,
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP),
nullptr, 0);
*bp = '\0';
*piDescUsed = A_DESC;
}
}
// If we preserved the state of the global registers, restore them.
//
if (need_pres)
{
restore_global_regs(preserve);
PopRegisters(preserve, MAX_GLOBAL_REGS);
}
*bp = '\0';
return buff;
}
/* ---------------------------------------------------------------------------
* did_it_rlevel: Have player do something to/with thing, watching the
* attributes. 'what' is actually ignored, the desclist match being used
* instead.
*/
void did_it_rlevel
(
dbref player,
dbref thing,
int what,
const UTF8 *def,
int owhat,
const UTF8 *odef,
int awhat,
int ctrl_flags,
const UTF8 *args[],
int nargs
)
{
if (alarm_clock.alarmed)
{
return;
}
UTF8 *d, *buff, *act, *charges, *bp;
dbref aowner;
int64_t num;
int aflags;
int i;
bool found_a_desc;
reg_ref **preserve = nullptr;
bool need_pres = false;
// Message to player.
//
if (0 < what)
{
// Get description list.
//
DESC_INFO *desclist = desclist_match(player, thing);
found_a_desc = false;
for (i = 0; i < desclist->n; i++)
{
// Ok, if it's A_DESC, we need to check against A_IDESC.
//
if ( A_IDESC == what
&& A_DESC == desclist->descs[i])
{
d = atr_pget(thing, A_IDESC, &aowner, &aflags);
}
else
{
d = atr_pget(thing, desclist->descs[i], &aowner, &aflags);
}
if ('\0' != d[0])
{
// No need for the 'def' message.
//
found_a_desc = true;
if (!need_pres)
{
need_pres = true;
preserve = PushRegisters(MAX_GLOBAL_REGS);
save_global_regs(preserve);
}
buff = bp = alloc_lbuf("did_it.1");
mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, thing, player,
AttrTrace(aflags, EV_EVAL|EV_FIGNORE|EV_TOP),
args, nargs);
*bp = '\0';
if ( A_HTDESC == desclist->descs[i]
&& Html(player))
{
safe_str(T("\r\n"), buff, &bp);
*bp = '\0';
notify_html(player, buff);
}
else
{
notify(player, buff);
}
free_lbuf(buff);
}
free_lbuf(d);
}
if (!found_a_desc)
{
// No desc found... try the default desc (again).
// A_DESC or A_HTDESC... the worst case we look for it twice.
//
d = atr_pget(thing, what, &aowner, &aflags);
if ('\0' != d[0])
{
// No need for the 'def' message
//
found_a_desc = true;
if (!need_pres)
{
need_pres = true;
preserve = PushRegisters(MAX_GLOBAL_REGS);
save_global_regs(preserve);
}
buff = bp = alloc_lbuf("did_it.1");
mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, thing, player,
AttrTrace(aflags, EV_EVAL|EV_FIGNORE|EV_TOP),
args, nargs);
*bp = '\0';
if ( A_HTDESC == what
&& Html(player))
{
safe_str(T("\r\n"), buff, &bp);
*bp = '\0';
notify_html(player, buff);
}
else
{
notify(player, buff);
}
free_lbuf(buff);
}
else if (def)
{
notify(player, def);
}
free_lbuf(d);
}
}
else if ( what < 0
&& def)
{
notify(player, def);
}
if (isPlayer(thing))
{
d = atr_pget(mudconf.master_room, get_atr(T("ASSET_DESC")), &aowner, &aflags);
if (*d)
{
if (!need_pres)
{
need_pres = true;
preserve = PushRegisters(MAX_GLOBAL_REGS);
save_global_regs(preserve);
}
buff = bp = alloc_lbuf("did_it.1");
mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, thing, player,
AttrTrace(aflags, EV_EVAL|EV_FIGNORE|EV_TOP),
args, nargs);
*bp = '\0';
notify(player, buff);
free_lbuf(buff);
}
free_lbuf(d);
}
// Message to neighbors.
//
dbref loc;
if ( 0 < owhat
&& Has_location(player)
&& Good_obj(loc = Location(player)))
{
d = atr_pget(thing, owhat, &aowner, &aflags);
if (*d)
{
if (!need_pres)
{
need_pres = true;
preserve = PushRegisters(MAX_GLOBAL_REGS);
save_global_regs(preserve);
}
buff = bp = alloc_lbuf("did_it.2");
mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, thing, player,
AttrTrace(aflags, EV_EVAL|EV_FIGNORE|EV_TOP),
args, nargs);
*bp = '\0';
if (*buff)
{
if (aflags & AF_NONAME)
{
notify_except2_rlevel2(loc, player, player, thing, buff);
}
else
{
notify_except2_rlevel2(loc, player, player, thing,
tprintf(T("%s %s"), Name(player), buff));
}
}
free_lbuf(buff);
}
else if (odef)
{
if (ctrl_flags & VERB_NONAME)
{
notify_except2_rlevel2(loc, player, player, thing, odef);
}
else
{
notify_except2_rlevel2(loc, player, player, thing,
tprintf(T("%s %s"), Name(player), odef));
}
}
free_lbuf(d);
}
else if ( owhat < 0
&& odef
&& Has_location(player)
&& Good_obj(loc = Location(player)))
{
if (ctrl_flags & VERB_NONAME)
{
notify_except2_rlevel2(loc, player, player, thing, odef);
}
else
{
notify_except2_rlevel2(loc, player, player, thing,
tprintf(T("%s %s"), Name(player), odef));
}
}
// If we preserved the state of the global registers, restore them.
//
if (need_pres)
{
restore_global_regs(preserve);
PopRegisters(preserve, MAX_GLOBAL_REGS);
}
// Do the action attribute.
//
if ( awhat > 0
&& IsReal(thing, player))
{
act = atr_pget(thing, awhat, &aowner, &aflags);
if (*act != '\0')
{
charges = atr_pget(thing, A_CHARGES, &aowner, &aflags);
if (*charges)
{
// int64_t, not int (#1402).
//
num = mux_atoi64(charges);
if (num > 0)
{
buff = alloc_sbuf("did_it.charges");
mux_i64toa(num - 1, buff);
atr_add_raw(thing, A_CHARGES, buff);
free_sbuf(buff);
}
else
{
buff = atr_pget(thing, A_RUNOUT, &aowner, &aflags);
if (*buff != '\0')
{
free_lbuf(act);
act = buff;
}
else
{
free_lbuf(act);
free_lbuf(buff);
free_lbuf(charges);
return;
}
}
}
free_lbuf(charges);
CLinearTimeAbsolute lta;
wait_que(thing, player, player, AttrTrace(aflags, 0), false, lta,
NOTHING, 0,
act,
nargs, args,
mudstate.global_regs);
}
free_lbuf(act);
}
}
#endif // REALITY_LVLS