tinymux/mux/modules/engine/levels.cpp

886 lines
21 KiB
C++
Raw Permalink Normal View History

/*! \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);
2018-10-03 17:54:51 +00:00
if ( nullptr == buff
|| strlen(reinterpret_cast<const char *>(buff)) != 17)
{
2007-09-26 12:04:34 -07:00
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);
2018-10-03 17:54:51 +00:00
if ( nullptr == buff
|| strlen(reinterpret_cast<const char *>(buff)) != 17)
{
2007-09-26 12:04:34 -07:00
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;
}
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
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);
}
2007-10-09 10:41:18 -07:00
#define NUM_DESC 33
typedef struct DESC_INFO
{
2007-10-09 10:41:18 -07:00
int n;
int descs[NUM_DESC];
} DESC_INFO;
2007-10-09 10:41:18 -07:00
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++)
{
2007-10-09 10:41:18 -07:00
confdata::rlevel_def *rldef = &mudconf.reality_level[i];
if ((match & rldef->value) == rldef->value)
{
2007-10-09 10:41:18 -07:00
ATTR *at = atr_str(rldef->attr);
2018-10-03 17:54:51 +00:00
if (nullptr != at)
{
2007-10-09 10:41:18 -07:00
bool bFound = false;
for (int j = 0; j < descbuffer.n; j++)
{
2007-10-09 10:41:18 -07:00
if (at->number == descbuffer.descs[j])
{
2007-10-09 10:41:18 -07:00
bFound = true;
break;
}
}
2007-10-09 10:41:18 -07:00
if ( !bFound
&& descbuffer.n < NUM_DESC-1)
{
2007-10-09 10:41:18 -07:00
descbuffer.descs[descbuffer.n] = at->number;
descbuffer.n++;
}
}
}
}
2007-10-09 10:41:18 -07:00
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;;
2018-10-03 17:54:51 +00:00
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),
2018-10-03 17:54:51 +00:00
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;
2007-10-09 10:41:18 -07:00
int i;
bool found_a_desc;
2018-10-03 17:54:51 +00:00
reg_ref **preserve = nullptr;
bool need_pres = false;
// Message to player.
//
2007-10-09 10:41:18 -07:00
if (0 < what)
{
// Get description list.
//
2007-10-09 10:41:18 -07:00
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
2007-10-09 10:41:18 -07:00
&& A_DESC == desclist->descs[i])
{
d = atr_pget(thing, A_IDESC, &aowner, &aflags);
}
else
{
2007-10-09 10:41:18 -07:00
d = atr_pget(thing, desclist->descs[i], &aowner, &aflags);
}
if ('\0' != d[0])
{
// No need for the 'def' message.
//
2007-10-09 10:41:18 -07:00
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';
2007-10-09 10:41:18 -07:00
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
//
2007-10-09 10:41:18 -07:00
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).
//
fix(win32): migrate the remaining mux_atol callers to mux_atoi64 (#1373) Completes the sweep the issue called for. mux_atol returns long, which is 32-bit on LLP64, so every caller silently truncated on Windows. Two of those were real defects (the truthiness family and cf_size, fixed in the preceding commits); the rest were latent, waiting for a value large enough to matter. Rather than audit 290 sites for whether each can reach 2^31 today, use the 64-bit parser everywhere and remove the class. A dbref cannot overflow now, but nothing stops a later caller passing that same site a timestamp or a byte count. Pure 1:1 substitution: 285 lines changed, and every removed line contained mux_atol while every added line contains mux_atoi64. No control flow, no types, no behaviour beyond the wider parse. This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so the generated code there is unchanged. It only widens the parse on Windows. Narrowing destinations are unaffected either way: `int x = mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both models. Left alone: mux_atol itself in mathutil, its declaration, and three comments that name it. Callers that genuinely want 32-bit semantics can still ask for them; none appear to. Verified on Windows: full solution builds clean with no new warnings, smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched -- identical to before the sweep, with the same 16 build-configuration failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST). Spot checks after the change: the boolean family returns 1 for multiples of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited, and arithmetic, string and list functions are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:03:45 -06:00
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