18 if (!
error.Text.empty())
22 if (!
error.Key.empty())
32 std::string
key =
"cmd.usage.";
33 const std::string_view name =
Name(
kind);
36 key +=
static_cast<char>(std::tolower(
static_cast<unsigned char>(name.front())));
37 key += name.substr(1);
48 Log::Error(
"A command was registered with no name - ignoring it.");
51 if (_commands.contains(name) || _aliases.contains(name))
53 Log::Error(
"Command '{}' is already registered - ignoring the second registration.",
def.Name);
58 Log::Error(
"Command '{}' is held by another plugin - ignoring this registration.",
def.Name);
63 for (
const std::string&
alias :
def.Aliases)
66 if (
key.empty() ||
key == name)
70 if (_commands.contains(
key))
72 Log::Error(
"Command '{}' asks for alias '{}', which is already a command name - skipping the alias.",
76 if (
auto it = _aliases.find(
key);
it != _aliases.end())
78 Log::Error(
"Command '{}' asks for alias '{}', already taken by '{}' - skipping the alias.",
def.Name,
alias,
84 Log::Error(
"Command '{}' asks for alias '{}', which another plugin owns - skipping the alias.",
def.Name,
88 _aliases.emplace(std::move(
key), name);
106 if (
auto it = _commands.find(
key);
it != _commands.end())
111 if (
auto alias = _aliases.find(
key);
alias != _aliases.end())
113 if (
auto it = _commands.find(
alias->second);
it != _commands.end())
129 return static_cast<size_t>(
130 std::count_if(
def.Args.begin(),
def.Args.end(), [](
const ArgDesc&
arg) { return !arg.Optional; }));
133bool CommandRouter::HasRest(
const CommandDefinition&
def)
135 return std::any_of(
def.Args.begin(),
def.Args.end(), [](
const ArgDesc&
arg) { return arg.Kind == ArgKind::Rest; });
144 for (
const ArgDesc&
arg :
def.Args)
151 args +=
arg.Optional ?
'[' :
'<';
153 args +=
arg.Optional ?
']' :
'>';
162 return {{
"prefix",
prefix}, {
"command",
def.Name}, {
"args",
args}, {
"usage",
line}};
167 if (!
def.UsageKey.empty())
187 if (
caller && !
def.PermissionName.empty())
How a chat line is spelled: the prefix, and splitting one into tokens.
Enumerator names without a hand-written switch.
The one thing command dispatch needs a running server for.
void Dispatch(const CommandDefinition &def, Player *caller, std::span< const std::string > tokens, Origin origin, ArgBinder &binder, const std::function< void(const std::string &)> &say) const
const CommandDefinition * Find(std::string_view name) const
const CommandDefinition * Add(CommandDefinition def)
std::string Usage(const CommandDefinition &def, int slot, Origin origin) const
bool IsForeign(std::string_view name) const
One connected player, owned by PlayerManager for the length of the connection.
int Slot() const noexcept
Result< Authorized > Authorize(PlayerRef caller, std::optional< PlayerRef > target, std::string_view permission) const
The single gate. Commands, target resolution and menu rows call exactly this.
static std::string ToLower(std::string_view str)
Localization system. Loads one JSON file per language; nested objects flatten into dotted keys (categ...
std::string Get(std::string_view key) const
constexpr std::string_view ChatPrefix
void Error(std::format_string< Args... > fmt, Args &&... args)
std::expected< std::vector< BoundArg >, ArgError > BindArgs(const CommandDefinition &def, std::span< const std::string > tokens, Player *caller, ArgBinder &binder)
static std::string FailureLine(const Error &error, const Translations &translations, int slot)
static std::string ReadFile(const std::filesystem::path &path)
static std::string UsagePlaceholderKey(ArgKind kind)
ArgKind
Which binder runs for one declared argument.
std::map< std::string, std::string > Tokens
Who invoked the command, and how to answer them.
One command as the builder assembled it.
One failure: a code to branch on, text for the log, and an optional translation key.
virtual bool RegisterCommand(std::string_view name)=0
virtual bool IsCommandRegistered(std::string_view name) const =0