feat(game): slash commands accept permission grants

A command may be used when the character's GM level allows it or a grant does: a grant of the command, of every
command up to a GM level, or of the dashboard permission the command follows (so accounts_kick covers /kick). A deny
of any of those takes it away even when the level allows it, except from GM 9 accounts (also while they play at a
lower level). Grants never take anyone below a command's floor above GM 1 (/execute), and commands the client handles
keep their fixed level. Expired grants count for nothing. The grants are the account's and the logged-in
character's, read from the database the first time a command is used and kept on the User. /help lists the commands
a player may use this way. The self and rank rules for commands that act on another player count grants of self_* and
manage_equal_rank too. A denied command says it was taken away.

Check: grant a GM 0 account /spawn (or "every command up to GM 8") on the dashboard, relog, and /spawn works and
shows in /help; deny /spawn from a GM 8 character: "it was taken away"; grant accounts_kick and /kick works (the rank
rules still apply to whom). dGameTests SlashCommandGrantsTest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 01:11:17 -05:00
parent 868f34123f
commit e00d22152e
4 changed files with 186 additions and 10 deletions

View File

@@ -8,10 +8,12 @@
#include "dCommonVars.h"
#include "eFunnessTypes.h"
#include <memory>
#include <unordered_map>
class Character;
enum class eGameMasterLevel : uint8_t;
namespace PermissionGrants { struct Held; }
struct BehaviorParams {
uint32_t behavior;
@@ -59,6 +61,13 @@ public:
void UserOutOfSync(const CaughtFunness& funness);
// The permission grants of this account and of the character they were loaded for (SlashCommandHandler loads them
// when first needed; nullptr until then, and again after ForgetGrants when the dashboard changed them)
const std::shared_ptr<const PermissionGrants::Held>& GetGrants() const { return m_Grants; }
LWOOBJID GetGrantsCharacter() const { return m_GrantsCharacter; }
void SetGrants(std::shared_ptr<const PermissionGrants::Held> grants, LWOOBJID characterId) { m_Grants = std::move(grants); m_GrantsCharacter = characterId; }
void ForgetGrants() { m_Grants.reset(); }
private:
uint32_t m_AccountID;
std::string m_Username;
@@ -78,6 +87,8 @@ private:
uint64_t m_MuteExpire;
std::chrono::steady_clock::time_point m_LastMuteCheck{};
std::vector<CaughtFunness> m_CaughtFunness{};
std::shared_ptr<const PermissionGrants::Held> m_Grants{};
LWOOBJID m_GrantsCharacter{};
};
#endif // USER_H