tinymux/mux/modules/engine/powers.cpp

434 lines
12 KiB
C++
Raw Permalink Normal View History

/*! \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);
}
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
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);
2018-10-03 17:54:51 +00:00
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?
*/
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
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));
}
}