Files
DarkflameServer/dGame/dUtilities/SlashCommandHandler.h
Aaron Kimbrell e00d22152e 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>
2026-09-29 01:16:49 -05:00

119 lines
5.2 KiB
C++

/*
* Darkflame Universe
* Copyright 2018
*/
#ifndef SLASHCOMMANDHANDLER_H
#define SLASHCOMMANDHANDLER_H
#include "RakNetTypes.h"
#include "eGameMasterLevel.h"
#include "dCommonVars.h"
#include "AccountRules.h"
#include "SlashCommandLevels.h"
#include "PermissionGrants.h"
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
class Entity;
struct Command {
std::string help;
std::string info;
std::vector<std::string> aliases;
std::function<void(Entity*, const SystemAddress&,const std::string)> handle;
// The default level; command_level_<name> in the settings (e.g. from the dashboard) can change it
eGameMasterLevel requiredLevel = eGameMasterLevel::OPERATOR;
// The lowest level the settings may give it; unset: GM 1 for staff commands (never players), GM 0 for the rest
std::optional<eGameMasterLevel> minLevel;
// Always requiredLevel, whatever the settings say
bool fixedLevel = false;
// Set by RegisterCommand for commands the game client acts on by itself (their level is fixed)
bool clientHandled = false;
// Why the command has a floor or a fixed level, shown on the dashboard
std::string levelNote;
// The dashboard permission (Permissions.cpp key) that does the same thing. The command then needs that permission's
// level (one setting for both); a command_level_<name> value still overrides it, so older setups don't change silently
std::string dashboardPermission;
// Who it may be used on when it acts on another player: the dashboard's self and rank rules (the handler checks them
// with SlashCommandHandler::MayActOn; this tells the dashboard which rule it follows)
SlashCommandLevels::eTargetRule targetRule = SlashCommandLevels::eTargetRule::NONE;
// Set by RegisterCommand: the command's name in the settings, from its first alias
std::string name;
};
// A player a command acts on, in this world or not
struct CommandTarget {
uint32_t accountId{};
uint8_t gmLevel{}; // the account's GM level (the character's in-game level if that is higher)
LWOOBJID characterId{};
std::string name; // the character's name
Entity* entity{}; // set when the player is in this world
};
namespace SlashCommandHandler {
void HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr);
void SendAnnouncement(const std::string& title, const std::string& message);
void RegisterCommand(Command info);
void Startup();
// The command an alias runs (nullptr: none)
const Command* FindCommand(const std::string& alias);
// The level needed to use a command right now: its default unless the settings change it
eGameMasterLevel GetRequiredLevel(const Command& command);
// Store the registered commands in the database for the dashboard (only what changed)
void ReportCommands();
// A player in this world, as a command target
std::optional<CommandTarget> TargetOf(Entity* player);
// The character with this name: in this world if they are here, else from the database. nullopt: no such character
std::optional<CommandTarget> FindTarget(const std::string& name);
/**
* Pure (apart from the permission levels): whether staff at actorLevel may use a command with this rule on the target
* account. The same rules as the dashboard's tools: GM 9 may act on anyone; below GM 9 never on a higher GM level, on
* their own level only with manage_equal_rank, and on themselves only with the self_* permission for the rule
* (OTHERS: on themselves always, as before).
*/
AccountRules::eManageDenial TargetDenial(uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId, SlashCommandLevels::eTargetRule rule,
const PermissionGrants::Held* actorGrants = nullptr);
// Why a command may not be used on someone, for the chat; empty when it may
std::string TargetRefusal(AccountRules::eManageDenial denial, SlashCommandLevels::eTargetRule rule, std::string_view command);
/**
* Whether the actor may use the command on the target (TargetDenial with the actor's current GM level and account).
* When not, tells whoever typed the command (sysAddr) why in the chat.
*/
bool MayActOn(Entity* actor, const SystemAddress& sysAddr, const CommandTarget& target, SlashCommandLevels::eTargetRule rule, std::string_view command);
// The account of the player using a command (0 if unknown)
uint32_t AccountOf(Entity* player);
/**
* The permission grants of a player's account and logged-in character (PermissionGrants.h), loaded from the database
* the first time they are needed and kept on the User until the dashboard changes them (DashboardActions). nullptr:
* not a player.
*/
const PermissionGrants::Held* GrantsOf(Entity* player);
// Whether a player may use a command now: its level, or a grant for it (the command, a command group, or the
// dashboard permission it follows), unless a deny takes it away (PermissionGrants::MayUseCommand)
bool MayUse(Entity* player, const Command& command);
// The command as the grant rules see it: its name, level now, floor, and the dashboard permission it follows
PermissionGrants::Command GrantRules(const Command& command);
};
namespace GMZeroCommands {
void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}
#endif // SLASHCOMMANDHANDLER_H