tinymux/mux/modules/engine/powers.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

433 lines
12 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 powers.cpp
* \brief Power manipulation routines.
*
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
/* ---------------------------------------------------------------------------
* ph_any: set or clear indicated bit, no security checking
*/
static bool ph_any(dbref target, dbref player, POWER power, int fpowers, bool reset)
{
UNUSED_PARAMETER(player);
if (fpowers & POWER_EXT)
{
if (reset)
{
s_Powers2(target, Powers2(target) & ~power);
}
else
{
s_Powers2(target, Powers2(target) | power);
}
}
else
{
if (reset)
{
s_Powers(target, Powers(target) & ~power);
}
else
{
s_Powers(target, Powers(target) | power);
}
}
return true;
}
/* ---------------------------------------------------------------------------
* ph_god: only GOD may set or clear the bit
*/
static bool ph_god(dbref target, dbref player, POWER power, int fpowers, bool reset)
{
if (!God(player))
{
return false;
}
return (ph_any(target, player, power, fpowers, reset));
}
/* ---------------------------------------------------------------------------
* ph_wiz: only WIZARDS (or GOD) may set or clear the bit
*/
static bool ph_wiz(dbref target, dbref player, POWER power, int fpowers, bool reset)
{
if (!Wizard(player))
{
return false;
}
return (ph_any(target, player, power, fpowers, reset));
}
static POWERENT gen_powers[] =
{
{T("announce"), POW_ANNOUNCE, 0, 0, ph_wiz},
{T("boot"), POW_BOOT, 0, 0, ph_wiz},
{T("builder"), POW_BUILDER, POWER_EXT, 0, ph_wiz},
{T("chown_anything"), POW_CHOWN_ANY, 0, 0, ph_wiz},
{T("comm_all"), POW_COMM_ALL, 0, 0, ph_wiz},
{T("control_all"), POW_CONTROL_ALL,0, 0, ph_god},
{T("dark"), POW_DARK, 0, 0, ph_wiz},
{T("expanded_who"), POW_WIZARD_WHO, 0, 0, ph_wiz},
{T("find_unfindable"), POW_FIND_UNFIND,0, 0, ph_wiz},
{T("free_money"), POW_FREE_MONEY, 0, 0, ph_wiz},
{T("free_quota"), POW_FREE_QUOTA, 0, 0, ph_wiz},
{T("guest"), POW_GUEST, 0, 0, ph_god},
{T("halt"), POW_HALT, 0, 0, ph_wiz},
{T("hide"), POW_HIDE, 0, 0, ph_wiz},
{T("idle"), POW_IDLE, 0, 0, ph_wiz},
{T("link_anywhere"), POW_LINK_ANYWHR,0, 0, ph_wiz},
{T("long_fingers"), POW_LONGFINGERS,0, 0, ph_wiz},
{T("monitor"), POW_MONITOR, 0, 0, ph_wiz},
{T("no_destroy"), POW_NO_DESTROY, 0, 0, ph_wiz},
{T("no_mail_expire"), POW_NO_MAIL_EXPIRE, POWER_EXT, 0, ph_wiz},
{T("pass_locks"), POW_PASS_LOCKS, 0, 0, ph_wiz},
{T("poll"), POW_POLL, 0, 0, ph_wiz},
{T("prog"), POW_PROG, 0, 0, ph_wiz},
{T("quota"), POW_CHG_QUOTAS, 0, 0, ph_wiz},
{T("search"), POW_SEARCH, 0, 0, ph_wiz},
{T("see_all"), POW_EXAM_ALL, 0, 0, ph_wiz},
{T("see_hidden"), POW_SEE_HIDDEN, 0, 0, ph_wiz},
{T("see_queue"), POW_SEE_QUEUE, 0, 0, ph_wiz},
{T("siteadmin"), POW_SITEADMIN, 0, 0, ph_wiz},
{T("stat_any"), POW_STAT_ANY, 0, 0, ph_wiz},
{T("steal_money"), POW_STEAL, 0, 0, ph_wiz},
{T("tel_anything"), POW_TEL_UNRST, 0, 0, ph_wiz},
{T("tel_anywhere"), POW_TEL_ANYWHR, 0, 0, ph_wiz},
{T("unkillable"), POW_UNKILLABLE, 0, 0, ph_wiz},
{nullptr, 0, 0, 0, 0}
};
/* ---------------------------------------------------------------------------
* init_powertab: initialize power hash tables.
*/
void init_powertab(void)
{
POWERENT *fp;
for (fp = gen_powers; fp->powername; fp++)
{
size_t nCased;
UTF8 *pCased = mux_strupr(fp->powername, nCased);
auto it_powers = mudstate.powers_htab.find(std::vector<UTF8>(pCased, pCased + nCased));
if (it_powers == mudstate.powers_htab.end())
{
mudstate.powers_htab.emplace(std::vector<UTF8>(pCased, pCased + nCased), fp);
}
}
}
/* ---------------------------------------------------------------------------
* display_powers: display available powers.
*/
void display_powertab(dbref player)
{
LBuf buf = LBuf_Src("display_powertab");
UTF8 *bp = buf.get();
POWERENT *fp;
safe_str(M_("Powers:"), buf, &bp);
for (fp = gen_powers; fp->powername; fp++)
{
if ((fp->listperm & CA_WIZARD) && !Wizard(player))
{
continue;
}
if ((fp->listperm & CA_GOD) && !God(player))
{
continue;
}
safe_chr(' ', buf, &bp);
safe_str(fp->powername, buf, &bp);
}
*bp = '\0';
notify(player, buf);
}
static POWERENT *find_power(dbref thing, const UTF8 *powername)
{
UNUSED_PARAMETER(thing);
// Convert powername to canonical lowercase.
//
size_t nCased;
UTF8 *pCased = mux_strupr(powername, nCased);
auto it_powers = mudstate.powers_htab.find(std::vector<UTF8>(pCased, pCased + nCased));
POWERENT *p = (it_powers != mudstate.powers_htab.end()) ? static_cast<POWERENT*>(it_powers->second) : nullptr;
return p;
}
bool decode_power(dbref player, UTF8 *powername, POWERSET *pset)
{
pset->word1 = 0;
pset->word2 = 0;
size_t nPowername = strlen(reinterpret_cast<char *>(powername));
auto it_powers = mudstate.powers_htab.find(std::vector<UTF8>(powername, powername + nPowername));
POWERENT *pent = (it_powers != mudstate.powers_htab.end()) ? static_cast<POWERENT*>(it_powers->second) : nullptr;
if (!pent)
{
notify(player, tprintf(M_("%s: Power not found."), powername));
return false;
}
if (pent->powerpower & POWER_EXT)
{
pset->word2 = pent->powervalue;
}
else
{
pset->word1 = pent->powervalue;
}
return true;
}
/* ---------------------------------------------------------------------------
* power_set: Set or clear a specified power on an object.
*/
void power_set(dbref target, dbref player, UTF8 *power, int key)
{
bool bDone = false;
do
{
// Trim spaces, and handle the negation character.
//
while (mux_isspace(*power))
{
power++;
}
bool bNegate = false;
if (*power == '!')
{
bNegate = true;
do
{
power++;
} while (mux_isspace(*power));
}
// Beginning of power name is now 'power'.
//
UTF8 *npower = power;
while ( *npower != '\0'
&& !mux_isspace(*npower))
{
npower++;
}
if (*npower == '\0')
{
bDone = true;
}
else
{
*npower = '\0';
}
// Make sure a power name was specified.
//
if (*power == '\0')
{
if (bNegate)
{
notify(player, M_("You must specify a power to clear."));
}
else
{
notify(player, M_("You must specify a power to set."));
}
}
else
{
POWERENT *fp = find_power(target, power);
if (fp == nullptr)
{
notify(player, M_("I dont understand that power."));
}
else
{
// Check if the power is already in the desired state.
//
POWER fv = (fp->powerpower & POWER_EXT) ? Powers2(target) : Powers(target);
bool bCurrentlySet = (fv & fp->powervalue) != 0;
if (bNegate == bCurrentlySet)
{
// State needs to change. Invoke the power handler,
// and print feedback.
//
if (!fp->handler(target, player, fp->powervalue, fp->powerpower, bNegate))
{
notify(player, NOPERM_MESSAGE);
}
else if (!(key & SET_QUIET) && !Quiet(player))
{
notify(player, (bNegate ? M_("Cleared.") : M_("Set.")));
}
}
else if (!(key & SET_QUIET) && !Quiet(player))
{
notify(player, (bNegate ? M_("Already cleared.") : M_("Already set.")));
}
}
}
power = npower + 1;
} while (!bDone);
}
/* ---------------------------------------------------------------------------
* has_power: does object have power visible to player?
*/
bool has_power(dbref player, dbref it, const UTF8 *powername)
{
POWERENT *fp = find_power(it, powername);
if (!fp)
{
return false;
}
POWER fv;
if (fp->powerpower & POWER_EXT)
{
fv = Powers2(it);
}
else
{
fv = Powers(it);
}
if (fv & fp->powervalue)
{
if ((fp->listperm & CA_WIZARD) && !Wizard(player))
{
return false;
}
if ((fp->listperm & CA_GOD) && !God(player))
{
return false;
}
return true;
}
return false;
}
/* ---------------------------------------------------------------------------
* powers_list: Return an LBUG containing the type and powers on thing.
*/
UTF8 *powers_list(dbref player, dbref target)
{
// Allocate the return buffer.
//
UTF8 *buff = alloc_lbuf("powers_list");
UTF8 *bp = buff;
bool bFirst = true;
POWERENT *fp;
for (fp = gen_powers; fp->powername; fp++)
{
POWER fv;
if (fp->powerpower & POWER_EXT)
{
fv = Powers2(target);
}
else
{
fv = Powers(target);
}
if (fv & fp->powervalue)
{
if ( (fp->listperm & CA_WIZARD)
&& !Wizard(player))
{
continue;
}
if ( (fp->listperm & CA_GOD)
&& !God(player))
{
continue;
}
if (bFirst)
{
bFirst = false;
}
else
{
safe_chr(' ', buff, &bp);
}
safe_str(fp->powername, buff, &bp);
}
}
// Terminate the string, and return the buffer to the caller.
//
*bp = '\0';
return buff;
}
/* ---------------------------------------------------------------------------
* decompile_powers: Produce commands to set powers on target.
*/
void decompile_powers(dbref player, dbref thing, UTF8 *thingname)
{
POWERENT *fp;
// Report generic powers.
//
POWER f1 = Powers(thing);
POWER f2 = Powers2(thing);
for (fp = gen_powers; fp->powername; fp++)
{
// Skip if we shouldn't decompile this power
//
if (fp->listperm & CA_NO_DECOMP)
{
continue;
}
// Skip if this power is not set.
//
if (fp->powerpower & POWER_EXT)
{
if (!(f2 & fp->powervalue))
{
continue;
}
}
else
{
if (!(f1 & fp->powervalue))
{
continue;
}
}
// Skip if we can't see this power.
//
if (!check_access(player, fp->listperm))
{
continue;
}
// We made it this far, report this power.
//
notify(player, tprintf(T("@power %s=%s"), thingname, fp->powername));
}
}