mirror of
https://github.com/Mr-X-GTA/YimMenu.git
synced 2025-06-16 22:37:22 +08:00

Added additional commands to showcase suggestion system. Added a new util file to operate on strings in a unified manner. Changed input_text_with_hint component to allow for more flags than one. Added more player seeking features to player_service such as get_by_name() * Fixed out of bounds suggestion navigation * Added suggestions to spawn_vehicle command * Created command play_animation * Added suggestion support for multi commands using a semicolon allows for more commands to fire at once, and is now supported with appropriate suggestions * Added rotation to teleport_to_location command * Fixed stupid error & added multiple raw command auto fills * Added sanity checks to avoid nullpointers * Added context identifiers to player commands * Added temporary self inclusion to player commands Needs translation on the translations repo * Applied rudamentary reviews * Experimental proxy globalization * Fixed argument sensitivity on spawn vehicle * Scrapped 2 ideas (maybe for future) * Added true and false suggestions to bool commands --------- Co-authored-by: Andreas Maerten <24669514+Yimura@users.noreply.github.com> Co-authored-by: gir489 <100792176+gir489returns@users.noreply.github.com>
225 lines
5.9 KiB
C++
225 lines
5.9 KiB
C++
#include "command.hpp"
|
|
|
|
#include "fiber_pool.hpp"
|
|
|
|
namespace
|
|
{
|
|
// https://stackoverflow.com/a/7408245
|
|
static std::vector<std::string> split(const std::string& text, char sep)
|
|
{
|
|
std::vector<std::string> tokens;
|
|
std::size_t start = 0, end = 0;
|
|
while ((end = text.find(sep, start)) != std::string::npos)
|
|
{
|
|
if (end != start)
|
|
{
|
|
tokens.push_back(text.substr(start, end - start));
|
|
}
|
|
start = end + 1;
|
|
}
|
|
if (end != start)
|
|
{
|
|
tokens.push_back(text.substr(start));
|
|
}
|
|
return tokens;
|
|
}
|
|
}
|
|
|
|
namespace big
|
|
{
|
|
command::command(const std::string& name, const std::string& label, const std::string& description, std::optional<uint8_t> num_args, bool fiber_pool) :
|
|
m_name(name),
|
|
m_label(label),
|
|
m_label_hash(rage::joaat(label)),
|
|
m_description(description),
|
|
m_description_hash(rage::joaat(description)),
|
|
m_num_args(num_args),
|
|
m_fiber_pool(fiber_pool)
|
|
{
|
|
g_commands[rage::joaat(name)] = this;
|
|
|
|
constexpr bool generate_command_documentation = false;
|
|
if constexpr (generate_command_documentation)
|
|
{
|
|
auto translated_label = g_translation_service.get_translation(m_label);
|
|
auto translated_desc = g_translation_service.get_translation(m_description);
|
|
if (!translated_label.empty())
|
|
{
|
|
LOG(INFO) << "Command | " << m_name << " | " << translated_label << " | " << translated_desc << " | "
|
|
<< std::to_string(m_num_args.value_or(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
void command::call(command_arguments& args, const std::shared_ptr<command_context> ctx)
|
|
{
|
|
if (m_num_args.has_value() && args.size() != m_num_args.value())
|
|
{
|
|
ctx->report_error(std::format("Command {} called with the wrong number of arguments. Expected {}, got {}",
|
|
m_name,
|
|
m_num_args.value(),
|
|
args.size()));
|
|
return;
|
|
}
|
|
|
|
if (ctx->get_access_level() < get_access_level())
|
|
{
|
|
ctx->report_error(std::format("You do not have sufficient permissions to call command {}", m_name));
|
|
return;
|
|
}
|
|
|
|
args.reset_idx();
|
|
if (m_fiber_pool)
|
|
g_fiber_pool->queue_job([this, args, ctx] {
|
|
execute(args, ctx);
|
|
});
|
|
else
|
|
execute(args, ctx);
|
|
}
|
|
|
|
void command::call(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx)
|
|
{
|
|
if (m_num_args.has_value() && args.size() != m_num_args.value())
|
|
{
|
|
ctx->report_error(std::format("Command {} called with the wrong number of arguments. Expected {}, got {}",
|
|
m_name,
|
|
m_num_args.value(),
|
|
args.size()));
|
|
return;
|
|
}
|
|
|
|
if (ctx->get_access_level() < get_access_level())
|
|
{
|
|
ctx->report_error(std::format("You do not have sufficient permissions to call command {}", m_name));
|
|
return;
|
|
}
|
|
|
|
auto parsed = parse_args(args, ctx);
|
|
if (parsed.has_value())
|
|
call(parsed.value(), ctx);
|
|
}
|
|
|
|
command* command::get(rage::joaat_t command)
|
|
{
|
|
return g_commands[command];
|
|
}
|
|
|
|
void command::call(rage::joaat_t command, command_arguments& args, const std::shared_ptr<command_context> ctx)
|
|
{
|
|
g_commands[command]->call(args, ctx);
|
|
}
|
|
|
|
void command::call(rage::joaat_t command, const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx)
|
|
{
|
|
g_commands[command]->call(args, ctx);
|
|
}
|
|
|
|
std::vector<command*> command::get_suggestions(std::string search, const int limit)
|
|
{
|
|
std::vector<command*> result_cmds{};
|
|
for (auto& [hash, command] : g_commands)
|
|
{
|
|
if (command && &command->get_name() && command->get_label().length() == 0)
|
|
continue;
|
|
|
|
std::string cmd_name = command->get_name();
|
|
std::string cmd_label = command->get_label();
|
|
|
|
//transform all strings to lower case
|
|
std::transform(cmd_name.begin(), cmd_name.end(), cmd_name.begin(), tolower);
|
|
std::transform(cmd_label.begin(), cmd_label.end(), cmd_label.begin(), tolower);
|
|
std::transform(search.begin(), search.end(), search.begin(), tolower);
|
|
|
|
for (auto& cmd : split(search, ';'))
|
|
{
|
|
std::string search_label = split(cmd, ' ')[0];
|
|
|
|
if (cmd_name.contains(search_label))
|
|
result_cmds.push_back(command);
|
|
else if (cmd_label.contains(search_label))
|
|
result_cmds.push_back(command);
|
|
}
|
|
|
|
// apply our maximum vector size..
|
|
if (result_cmds.size() >= limit)
|
|
break;
|
|
}
|
|
|
|
auto compare_by_substring_match = [](command*& a, command*& b, const std::string& sub) -> bool {
|
|
std::string cmd_name_a = a->get_name();
|
|
std::string cmd_name_b = b->get_name();
|
|
|
|
if (cmd_name_a == sub && cmd_name_b != sub)
|
|
return true;
|
|
if (cmd_name_b == sub && cmd_name_a != sub)
|
|
return false;
|
|
|
|
auto pos_a = cmd_name_a.find(sub);
|
|
auto pos_b = cmd_name_b.find(sub);
|
|
|
|
if (pos_a != std::string::npos && pos_b != std::string::npos)
|
|
{
|
|
if (pos_a != pos_b)
|
|
return pos_a < pos_b;
|
|
|
|
return cmd_name_a.length() < cmd_name_b.length();
|
|
}
|
|
|
|
return pos_a < pos_b;
|
|
};
|
|
|
|
std::sort(result_cmds.begin(), result_cmds.end(), [&search, &compare_by_substring_match](command*& a, command*& b) {
|
|
return compare_by_substring_match(a, b, search);
|
|
});
|
|
|
|
return result_cmds;
|
|
}
|
|
|
|
bool command::process(const std::string& text, const std::shared_ptr<command_context> ctx, bool use_best_suggestion)
|
|
{
|
|
bool success = true;
|
|
|
|
for (auto& cmd : split(text, ';'))
|
|
{
|
|
auto args = split(cmd, ' ');
|
|
if (args.size() == 0 || args[0].empty())
|
|
{
|
|
ctx->report_error("No command to call");
|
|
success = false;
|
|
continue;
|
|
}
|
|
|
|
//build the best command based on the input
|
|
if (use_best_suggestion)
|
|
{
|
|
const auto cmd_suggestions = get_suggestions(args[0]);
|
|
|
|
//is valid suggestion
|
|
if (cmd_suggestions.size() >= 1)
|
|
{
|
|
args[0] = cmd_suggestions[0]->get_name();
|
|
}
|
|
else
|
|
{
|
|
ctx->report_error(std::format("Command {} does not exist", args[0]));
|
|
success = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
uint32_t hash = rage::joaat(args[0]);
|
|
if (!g_commands.contains(hash))
|
|
{
|
|
ctx->report_error(std::format("Command {} does not exist", args[0]));
|
|
success = false;
|
|
continue;
|
|
}
|
|
|
|
args.erase(args.begin());
|
|
call(hash, args, ctx);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
} |