Expand Cmd Executor (#2884)
Some checks failed
Nightly Build / Check Recent Commit (push) Failing after 5s
Nightly Build / Build Nightly (push) Has been skipped
Nightly Build / Recreate Release (push) Has been skipped

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>
This commit is contained in:
DayibBaba 2024-07-13 00:26:34 +02:00 committed by GitHub
parent 67203b8fca
commit b90ce402a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 919 additions and 34 deletions

View File

@ -20,6 +20,16 @@ namespace big
return m_toggle;
}
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
{
if (arg == 1) // First argument of all bool commands is true or false
{
return std::vector<std::string>{"true", "false"};
}
return std::nullopt;
};
virtual void on_enable(){};
virtual void on_disable(){};
virtual void refresh();

View File

@ -119,7 +119,7 @@ namespace big
std::vector<command*> result_cmds{};
for (auto& [hash, command] : g_commands)
{
if (command->get_label().length() == 0)
if (command && &command->get_name() && command->get_label().length() == 0)
continue;
std::string cmd_name = command->get_name();

View File

@ -4,6 +4,9 @@
#include "context/default_command_context.hpp"
#include "core/enums.hpp"
#include "gta/joaat.hpp"
#include "services/players/player_service.hpp"
#include "util/math.hpp"
#include "util/string_operations.hpp"
namespace big
{
@ -55,6 +58,52 @@ namespace big
return m_num_args;
}
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg)
{
return std::nullopt;
};
inline std::optional<int> get_argument_proxy_value(const std::string proxy)
{
std::string local_player_name_lower = g_player_service->get_self()->get_name();
std::string proxy_lower = proxy;
string::operations::to_lower(local_player_name_lower);
string::operations::to_lower(proxy_lower);
switch (proxy_lower[0])
{
case '@': return g_player_service->get_selected()->id();
case '!': return g_player_service->get_closest(true)->id();
case '#':
float distance = std::numeric_limits<float>::max();
player_ptr closest = nullptr;
for (auto p : g_player_service->players())
{
if (p.second->is_friend() && p.second->get_ped() && p.second->get_ped()->get_position())
{
auto distance_ = math::distance_between_vectors(*g_player_service->get_self()->get_ped()->get_position(),
*p.second->get_ped()->get_position());
if (distance_ < distance)
{
closest = p.second;
distance = distance_;
}
}
}
if (closest)
return closest->id();
break;
}
if (proxy_lower == "me" || proxy_lower == "self" || local_player_name_lower.find(proxy_lower) != std::string::npos)
{
return g_player_service->get_self()->id();
}
return std::nullopt;
}
void call(command_arguments& args, const std::shared_ptr<command_context> ctx = std::make_shared<default_command_context>());
void call(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx = std::make_shared<default_command_context>());
static std::vector<command*> get_suggestions(std::string, int limit = 7);

View File

@ -0,0 +1,87 @@
#include "backend/player_command.hpp"
#include "util/teleport.hpp"
namespace big
{
class tp_to_player : player_command
{
using player_command::player_command;
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
{
if (arg == 1 || arg == 2)
{
std::vector<std::string> suggestions;
for (auto& player : g_player_service->players() | std::ranges::views::values)
{
suggestions.push_back(player->get_name());
}
return suggestions;
}
return std::nullopt;
}
virtual std::optional<command_arguments> parse_args(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx) override
{
command_arguments result(2);
auto first_possible_proxy = get_argument_proxy_value(args[0]);
auto second_possible_proxy = get_argument_proxy_value(args[1]);
if (first_possible_proxy.has_value())
result.push(first_possible_proxy.value());
if (second_possible_proxy.has_value())
result.push(second_possible_proxy.value());
if (first_possible_proxy.has_value() && second_possible_proxy.has_value())
return result;
player_ptr sender, target;
if (!first_possible_proxy.has_value())
sender = g_player_service->get_by_name_closest(args[0]);
if (!second_possible_proxy.has_value())
target = g_player_service->get_by_name_closest(args[1]);
if ((!first_possible_proxy.has_value() && !sender) || (!second_possible_proxy.has_value() && !target))
{
g_notification_service.push_error(std::string("TELEPORT_PLAYER_TO_PLAYER"_T), (std::string("INVALID_PLAYER_NAME_NOTIFICATION"_T)));
return std::nullopt;
}
result.push(sender->id());
result.push(target->id());
return result;
}
virtual CommandAccessLevel get_access_level() override
{
return CommandAccessLevel::ADMIN;
}
virtual void execute(player_ptr player, const command_arguments& _args, const std::shared_ptr<command_context> ctx) override
{
auto sender =
_args.get<uint8_t>(0) == self::id ? g_player_service->get_self() : g_player_service->get_by_id(_args.get<uint8_t>(0));
auto target =
_args.get<uint8_t>(1) == self::id ? g_player_service->get_self() : g_player_service->get_by_id(_args.get<uint8_t>(1));
if (target && target->get_ped() && target->get_ped()->get_position())
{
auto coords = target->get_ped()->get_position();
Vector3 coords_ = {coords->x, coords->y, coords->z};
teleport::teleport_player_to_coords(sender, coords_);
auto sender_name = sender->get_name();
auto target_name = target->get_name();
const std::string message = std::vformat("TELEPORT_PLAYER_TO_PLAYER_NOTIFICATION"_T, std::make_format_args(sender_name, target_name));
g_notification_service.push(std::string("TELEPORT_PLAYER_TO_PLAYER"_T), message);
}
}
};
tp_to_player tp_to_player_shortcut("tp", "TELEPORT_PLAYER_TO_PLAYER", "TELEPORT_PLAYER_TO_PLAYER_DESC", 1);
}

View File

@ -0,0 +1,94 @@
#include "backend/bool_command.hpp"
#include "backend/command.hpp"
#include "natives.hpp"
#include "pointers.hpp"
#include "services/mobile/mobile_service.hpp"
#include "services/ped_animations/ped_animations_service.hpp"
#include "util/mobile.hpp"
#include "util/string_operations.hpp"
#include "util/vehicle.hpp"
#include "util/ped.hpp"
namespace big
{
class play_animation : command
{
using command::command;
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
{
if (arg == 1)
{
std::vector<std::string> suggestions;
for (auto& item : g_ped_animation_service.all_saved_animations | std::views::values | std::views::join)
{
std::string anim_name = item.name;
string::operations::remove_whitespace(anim_name);
string::operations::to_lower(anim_name);
suggestions.push_back(anim_name);
}
return suggestions;
}
return std::nullopt;
}
virtual std::optional<command_arguments> parse_args(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx) override
{
command_arguments result(1);
const std::string anim_name = args[0];
if (anim_name == "stop")
{
result.push<int>(-1);
return result;
}
int anim_index = 0;
for (auto& item : g_ped_animation_service.all_saved_animations | std::views::values | std::views::join)
{
std::string display_name = item.name;
display_name = string::operations::remove_whitespace(display_name);
display_name = string::operations::to_lower(display_name);
if (display_name.find(anim_name) != std::string::npos)
{
result.push<int>(anim_index);
break;
}
anim_index++;
}
return result;
}
virtual CommandAccessLevel get_access_level() override
{
return CommandAccessLevel::ADMIN;
}
virtual void execute(const command_arguments& args, const std::shared_ptr<command_context> ctx) override
{
const auto anim_index = args.get<int>(0);
if (anim_index == -1)
{
TASK::CLEAR_PED_TASKS(self::ped);
return;
}
int count = 0;
for (auto& item : g_ped_animation_service.all_saved_animations | std::views::values | std::views::join)
{
if (count == anim_index)
g_ped_animation_service.play_saved_ped_animation(item, self::ped);
count++;
}
}
};
play_animation g_play_animation("anim", "PLAY_ANIMATION", "PLAY_ANIMATION_DESC", 1);
}

View File

@ -50,6 +50,21 @@ namespace big
return valid_args;
}
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
{
if (arg == 1)
{
std::vector<std::string> suggestions;
for (const auto& session_type_string : m_session_types | std::ranges::views::values)
{
suggestions.push_back(session_type_string);
}
return suggestions;
}
return std::nullopt;
}
virtual std::optional<command_arguments> parse_args(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx) override
{
command_arguments result(1);

View File

@ -0,0 +1,70 @@
#include "backend/bool_command.hpp"
#include "backend/command.hpp"
#include "natives.hpp"
#include "pointers.hpp"
#include "services/mobile/mobile_service.hpp"
#include "util/mobile.hpp"
#include "util/string_operations.hpp"
#include "util/vehicle.hpp"
namespace big
{
class spawn_personal_vehicle : command
{
using command::command;
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
{
if (g_mobile_service->personal_vehicles().empty())
g_mobile_service->refresh_personal_vehicles();
if (arg == 1)
{
std::vector<std::string> suggestions;
for (auto& item : g_mobile_service->personal_vehicles() | std::ranges::views::values)
{
std::string display_name = item.get()->get_display_name();
display_name = string::operations::remove_whitespace(display_name);
display_name = string::operations::to_lower(display_name);
suggestions.push_back(display_name);
}
return suggestions;
}
return std::nullopt;
}
virtual std::optional<command_arguments> parse_args(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx) override
{
command_arguments result(1);
const std::string personal_veh_display_name = args[0];
for (auto& item : g_mobile_service->personal_vehicles() | std::ranges::views::values)
{
std::string display_name = item.get()->get_display_name();
display_name = string::operations::remove_whitespace(display_name);
display_name = string::operations::to_lower(display_name);
if (display_name.find(personal_veh_display_name) != std::string::npos)
{
result.push(item->get_id());
break;
}
}
return result;
}
virtual CommandAccessLevel get_access_level() override
{
return CommandAccessLevel::ADMIN;
}
virtual void execute(const command_arguments& args, const std::shared_ptr<command_context> ctx) override
{
const auto personal_veh_index = args.get<int>(0);
mobile::mechanic::summon_vehicle_by_index(personal_veh_index);
}
};
spawn_personal_vehicle g_spawn_personal_vehicle("spawnpv", "GUI_TAB_SPAWN_VEHICLE", "BACKEND_SPAWN_VEHICLE_DESC", 1);
}

View File

@ -2,6 +2,8 @@
#include "backend/command.hpp"
#include "natives.hpp"
#include "pointers.hpp"
#include "services/gta_data/gta_data_service.hpp"
#include "util/string_operations.hpp"
#include "util/vehicle.hpp"
namespace big
@ -10,10 +12,44 @@ namespace big
{
using command::command;
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
{
if (arg == 1)
{
std::vector<std::string> suggestions;
for (auto& item : g_gta_data_service->vehicles())
{
suggestions.push_back(item.second.m_name);
}
return suggestions;
}
return std::nullopt;
}
virtual std::optional<command_arguments> parse_args(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx) override
{
command_arguments result(1);
result.push(rage::joaat(args[0]));
if (g_gta_data_service->vehicle_by_hash(rage::joaat(args[0])).m_hash != 0)
{
result.push(rage::joaat(args[0]));
return result;
}
for (auto& item : g_gta_data_service->vehicles())
{
std::string item_name_lower, args_lower;
item_name_lower = item.second.m_name;
args_lower = args[0];
string::operations::to_lower(item_name_lower);
string::operations::to_lower(args_lower);
if (item_name_lower.find(args_lower) != std::string::npos)
{
result.push(rage::joaat(item.first));
return result;
}
}
return result;
}

View File

@ -0,0 +1,82 @@
#include "backend/bool_command.hpp"
#include "backend/command.hpp"
#include "natives.hpp"
#include "pointers.hpp"
#include "services/custom_teleport/custom_teleport_service.hpp"
#include "util/string_operations.hpp"
#include "util/teleport.hpp"
namespace big
{
class teleport_to_location : command
{
using command::command;
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
{
if (arg == 1)
{
std::vector<std::string> suggestions;
for (auto& location : g_custom_teleport_service.all_saved_locations | std::views::values | std::views::join)
{
std::string name = location.name;
string::operations::to_lower(name);
string::operations::remove_whitespace(name);
suggestions.push_back(name);
}
return suggestions;
}
return std::nullopt;
}
virtual std::optional<command_arguments> parse_args(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx) override
{
m_num_args = 6; // This is retarded but it works
command_arguments result(6);
const std::string location_name = args[0];
for (auto& location : g_custom_teleport_service.all_saved_locations | std::views::values | std::views::join)
{
std::string name = location.name;
string::operations::to_lower(name);
string::operations::remove_whitespace(name);
if (name.find(location_name) != std::string::npos)
{
result.push<float>(location.x);
result.push<float>(location.y);
result.push<float>(location.z);
result.push<float>(location.yaw);
result.push<float>(location.pitch);
result.push<float>(location.roll);
return result;
}
}
return result;
}
virtual CommandAccessLevel get_access_level() override
{
return CommandAccessLevel::ADMIN;
}
virtual void execute(const command_arguments& args, const std::shared_ptr<command_context> ctx) override
{
const float x = args.get<float>(0);
const float y = args.get<float>(1);
const float z = args.get<float>(2);
const float yaw = args.get<float>(3);
const float pitch = args.get<float>(4);
const float roll = args.get<float>(5);
teleport::teleport_player_to_coords(g_player_service->get_self(), Vector3(x, y, z), Vector3(yaw, pitch, roll));
m_num_args = 1; // This is retarded but it works
}
};
teleport_to_location g_teleport_to_location("location", "Teleport To Location", "TELEPORT_TO_LOCATION_DESC", 1);
}

View File

@ -1,6 +1,8 @@
#include "player_command.hpp"
#include "fiber_pool.hpp"
#include "util/math.hpp"
#include "util/string_operations.hpp"
namespace big
{
@ -62,9 +64,11 @@ namespace big
std::vector<std::string> new_args;
command_arguments result(m_num_args.value());
if (args[0] == "me" || args[0] == "self")
auto proxy_result = get_argument_proxy_value(args[0]);
if (proxy_result.has_value())
{
result.push(ctx->get_sender()->id());
result.push(proxy_result.value());
}
else
{

View File

@ -34,6 +34,21 @@ namespace big
return {0};
};
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
{
if (arg == 1) // First argument of all player commands is the player name
{
std::vector<std::string> suggestions;
for (auto& player : g_player_service->players() | std::ranges::views::values)
{
suggestions.push_back(player->get_name());
}
return suggestions;
}
return std::nullopt;
};
public:
static player_command* get(rage::joaat_t command)
{

View File

@ -1168,7 +1168,13 @@ namespace big
NLOHMANN_DEFINE_TYPE_INTRUSIVE(vfx, enable_custom_sky_color, azimuth_east, azimuth_west, azimuth_transition, zenith, stars_intensity)
} vfx{};
NLOHMANN_DEFINE_TYPE_INTRUSIVE(menu_settings, debug, tunables, notifications, player, player_db, protections, self, session, settings, spawn_vehicle, clone_pv, persist_car, spoofing, vehicle, weapons, window, context_menu, esp, session_browser, ugc, reactions, world, stat_editor, lua, persist_weapons, vfx)
struct cmd
{
std::deque<std::string> command_history;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(cmd, command_history)
} cmd{};
NLOHMANN_DEFINE_TYPE_INTRUSIVE(menu_settings, debug, tunables, notifications, player, player_db, protections, self, session, settings, spawn_vehicle, clone_pv, persist_car, spoofing, vehicle, weapons, window, context_menu, esp, session_browser, ugc, reactions, world, stat_editor, lua, persist_weapons, vfx, cmd)
};
inline auto g = menu_settings();

View File

@ -23,8 +23,8 @@ namespace big
static void title(const std::string_view);
static void nav_item(std::pair<tabs, navigation_struct>&, int);
static bool input_text_with_hint(const std::string_view label, const std::string_view hint, char* buf, size_t buf_size, ImGuiInputTextFlags_ flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr);
static bool input_text_with_hint(const std::string_view label, const std::string_view hint, std::string& buf, ImGuiInputTextFlags_ flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr);
static bool input_text_with_hint(const std::string_view label, const std::string_view hint, char* buf, size_t buf_size, int flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr);
static bool input_text_with_hint(const std::string_view label, const std::string_view hint, std::string& buf, int flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr, ImGuiInputTextCallback callback = nullptr);
static bool input_text(const std::string_view label, char* buf, size_t buf_size, ImGuiInputTextFlags_ flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr);
static bool input_text(const std::string_view label, std::string& buf, ImGuiInputTextFlags_ flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr);

View File

@ -5,7 +5,7 @@
namespace big
{
bool components::input_text_with_hint(const std::string_view label, const std::string_view hint, char* buf, size_t buf_size, ImGuiInputTextFlags_ flag, std::function<void()> cb)
bool components::input_text_with_hint(const std::string_view label, const std::string_view hint, char* buf, size_t buf_size, int flag, std::function<void()> cb)
{
bool returned = false;
if (returned = ImGui::InputTextWithHint(label.data(), hint.data(), buf, buf_size, flag); returned && cb)
@ -16,10 +16,10 @@ namespace big
return returned;
}
bool components::input_text_with_hint(const std::string_view label, const std::string_view hint, std::string& buf, ImGuiInputTextFlags_ flag, std::function<void()> cb)
bool components::input_text_with_hint(const std::string_view label, const std::string_view hint, std::string& buf, int flag, std::function<void()> cb, ImGuiInputTextCallback callback)
{
bool returned = false;
if (returned = ImGui::InputTextWithHint(label.data(), hint.data(), &buf, flag); returned && cb)
if (returned = ImGui::InputTextWithHint(label.data(), hint.data(), &buf, flag, callback); returned && cb)
g_fiber_pool->queue_job(std::move(cb));
if (ImGui::IsItemActive())

View File

@ -1,9 +1,11 @@
#include "player_service.hpp"
#include "gta_util.hpp"
#include "util/math.hpp"
#include <network/CNetworkPlayerMgr.hpp>
namespace big
{
player_service::player_service() :
@ -63,7 +65,7 @@ namespace big
player_ptr player_service::get_by_host_token(uint64_t token) const
{
for (const auto& [_, player] : m_players)
for (const auto& player : m_players | std::ranges::views::values)
{
if (auto net_data = player->get_net_data())
{
@ -76,6 +78,69 @@ namespace big
return nullptr;
}
player_ptr player_service::get_by_name(std::string_view name) const
{
std::string self_name = g_player_service->get_self()->get_name();
std::string name_lower = name.data();
std::transform(self_name.begin(), self_name.end(), self_name.begin(), ::tolower);
std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
for (auto& [_, player] : m_players)
{
std::string player_name = player->get_name();
std::transform(player_name.begin(), player_name.end(), player_name.begin(), ::tolower);
if (player_name == name_lower)
return player;
}
return nullptr;
}
player_ptr player_service::get_by_name_closest(std::string_view guess) const
{
std::string self_name = g_player_service->get_self()->get_name();
std::string lower_guess = guess.data();
std::transform(self_name.begin(), self_name.end(), self_name.begin(), ::tolower);
std::transform(lower_guess.begin(), lower_guess.end(), lower_guess.begin(), ::tolower);
for (auto& [_, player] : m_players)
{
std::string player_name = player->get_name();
std::transform(player_name.begin(), player_name.end(), player_name.begin(), ::tolower);
if (player_name.find(lower_guess) != std::string::npos)
{
return player;
}
}
return nullptr;
}
player_ptr player_service::get_closest(bool exclude_friends) const
{
float closest_distance = std::numeric_limits<float>::max();
player_ptr closest_player = nullptr;
for (auto player : m_players | std::ranges::views::values)
{
if (exclude_friends && player->is_friend())
continue;
if (player && player->get_ped() && player->get_ped()->get_position())
{
if (math::distance_between_vectors(*player->get_ped()->get_position(),
*g_player_service->get_self()->get_ped()->get_position())
< closest_distance)
{
closest_distance = math::distance_between_vectors(*player->get_ped()->get_position(),
*g_player_service->get_self()->get_ped()->get_position());
closest_player = player;
}
}
}
return closest_player;
}
player_ptr player_service::get_selected() const
{
return m_selected_player;

View File

@ -39,6 +39,9 @@ namespace big
[[nodiscard]] player_ptr get_by_id(uint32_t id) const;
[[nodiscard]] player_ptr get_by_host_token(uint64_t token) const;
[[nodiscard]] player_ptr get_selected() const;
[[nodiscard]] player_ptr get_by_name(const std::string_view name) const;
[[nodiscard]] player_ptr get_by_name_closest(const std::string_view name) const;
[[nodiscard]] player_ptr get_closest(bool exclude_friends = false) const;
void player_join(CNetGamePlayer* net_game_player);
void player_leave(CNetGamePlayer* net_game_player);

View File

@ -0,0 +1,78 @@
#pragma once
namespace big::string::operations
{
inline std::string to_lower(std::string& str)
{
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
str = result;
return result;
}
inline std::string to_upper(std::string& str)
{
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
str = result;
return result;
}
inline std::string trim(std::string& str)
{
std::string result = str;
result.erase(result.begin(), std::find_if(result.begin(), result.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
result.erase(std::find_if(result.rbegin(),
result.rend(),
[](unsigned char ch) {
return !std::isspace(ch);
})
.base(),
result.end());
str = result;
return result;
}
inline std::string remove_whitespace(std::string& str)
{
std::string result = str;
result.erase(std::remove_if(result.begin(), result.end(), isspace), result.end());
str = result;
return result;
}
static std::vector<std::string> split(const std::string text, char delimiter)
{
std::vector<std::string> tokens;
std::size_t start = 0, end = 0;
while ((end = text.find(delimiter, 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;
}
static std::string join(const std::vector<std::string>& tokens, char delimiter)
{
std::string result;
for (size_t i = 0; i < tokens.size(); i++)
{
result += tokens[i];
if (i != tokens.size() - 1)
{
result += delimiter;
}
}
return result;
}
}

View File

@ -2,10 +2,251 @@
#include "gui.hpp"
#include "pointers.hpp"
#include "services/hotkey/hotkey_service.hpp"
#include "util/string_operations.hpp"
#include "views/view.hpp"
namespace big
{
//TODO Argument suggestions are limited to the last word in the buffer
//TODO Allow for optional arguments??
static std::vector<std::string> current_suggestion_list;
static std::string command_buffer;
static std::string auto_fill_suggestion;
static std::string selected_suggestion;
bool does_string_exist_in_list(const std::string& command, std::vector<std::string> list)
{
auto found = std::find_if(list.begin(), list.end(), [&](const std::string& cmd) {
return cmd == command;
});
return found != list.end();
}
std::vector<std::string> deque_to_vector(std::deque<std::string> deque)
{
std::vector<std::string> vector;
for (auto& element : deque)
{
vector.push_back(element);
}
return vector;
}
static void add_to_last_used_commands(const std::string& command)
{
if (does_string_exist_in_list(command, deque_to_vector(g.cmd.command_history)))
{
return;
}
if (g.cmd.command_history.size() >= 10)
{
g.cmd.command_history.pop_back();
}
g.cmd.command_history.push_front(command);
}
std::string auto_fill_command(std::string current_buffer)
{
if (command::get(rage::joaat(current_buffer)) != nullptr)
return current_buffer;
for (auto [key, cmd] : g_commands)
{
if (cmd && cmd->get(key) && &cmd->get_name())
{
if (cmd->get_name().find(current_buffer) != std::string::npos)
return cmd->get_name();
}
}
return std::string();
}
// What word in the sentence are we currently at
int current_index(std::string current_buffer)
{
auto separate_commands = string::operations::split(current_buffer, ';'); // Split by semicolon to support multiple commands
auto words = string::operations::split(separate_commands.back(), ' ');
return words.size();
}
std::vector<std::string> suggestion_list_filtered(std::vector<std::string> suggestions, std::string filter)
{
std::vector<std::string> suggestions_filtered;
std::string filter_lowercase = filter;
string::operations::to_lower(filter_lowercase);
for (auto suggestion : suggestions)
{
std::string suggestion_lowercase = suggestion;
string::operations::to_lower(suggestion_lowercase);
auto words = string::operations::split(command_buffer, ' ');
if (suggestion_lowercase.find(filter_lowercase) != std::string::npos || does_string_exist_in_list(words.back(), current_suggestion_list) /*Need this to maintain suggestion list while navigating it*/)
suggestions_filtered.push_back(suggestion);
}
return suggestions_filtered;
}
void get_appropriate_suggestion(std::string current_buffer, std::string& suggestion_)
{
auto separate_commands = string::operations::split(current_buffer, ';'); // Split by semicolon to support multiple commands
auto words = string::operations::split(separate_commands.back(), ' ');
auto current_command = command::get(rage::joaat(words.front()));
auto argument_index = current_index(current_buffer);
if (argument_index == 1)
{
suggestion_ = auto_fill_command(words.back());
return;
}
else
{
if (!current_command)
return;
auto suggestions = current_command->get_argument_suggestions(argument_index - 1);
if (suggestions == std::nullopt)
return;
for (auto suggestion : suggestion_list_filtered(suggestions.value(), words.back()))
{
std::string guess_lowercase = words.back();
std::string suggestion_lowercase = suggestion;
string::operations::to_lower(suggestion_lowercase);
string::operations::to_lower(guess_lowercase);
if (suggestion_lowercase.find(guess_lowercase) != std::string::npos)
{
suggestion_ = suggestion;
break;
}
}
}
}
void get_previous_from_list(std::vector<std::string>& list, std::string& current)
{
auto found = std::find_if(list.begin(), list.end(), [&](const std::string& cmd) {
return cmd == current;
});
if (found == list.end())
{
if (list.size() > 0)
current = list.back();
return;
}
if (*found == list.front())
{
current = list.back();
return;
}
if (found - 1 != list.end())
current = *(found - 1);
}
void get_next_from_list(std::vector<std::string>& list, std::string& current)
{
auto found = std::find_if(list.begin(), list.end(), [&](const std::string& cmd) {
return cmd == current;
});
if (found == list.end())
{
if (list.size() > 0)
current = list.front();
return;
}
if (*found == list.back())
{
current = list.front();
return;
}
if (found + 1 != list.end())
current = *(found + 1);
}
void rebuild_buffer_with_suggestion(ImGuiInputTextCallbackData* data, std::string suggestion)
{
auto separate_commands = string::operations::split(data->Buf, ';'); // Split by semicolon to support multiple commands
auto words = string::operations::split(separate_commands.back(), ' ');
std::string new_text;
// Replace the last word with the suggestion
words.pop_back();
words.push_back(suggestion);
// Replace the last command with the new suggestion
separate_commands.pop_back();
separate_commands.push_back(string::operations::join(words, ' '));
new_text = string::operations::join(separate_commands, ';');
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, new_text.c_str());
}
static int apply_suggestion(ImGuiInputTextCallbackData* data)
{
if (!data)
return 0;
if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion)
{
// User has a suggestion selectable higlighted, this takes precedence
if (!selected_suggestion.empty())
{
// This could be a history suggestion with arguments, so we have to check for it
auto words = string::operations::split(selected_suggestion, ' ');
auto command = command::get(rage::joaat(words.front()));
// Its a command, lets rewrite the entire buffer (history command potentially with arguments)
if (command)
{
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, selected_suggestion.c_str());
}
// Its probably an argument suggestion or a raw command, append it
else
rebuild_buffer_with_suggestion(data, selected_suggestion);
selected_suggestion = std::string();
return 0;
}
std::string auto_fill_suggestion;
get_appropriate_suggestion(data->Buf, auto_fill_suggestion);
if (auto_fill_suggestion != data->Buf)
{
rebuild_buffer_with_suggestion(data, auto_fill_suggestion);
}
}
else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory)
{
if (current_suggestion_list.empty())
return 0;
if (data->EventKey == ImGuiKey_UpArrow)
get_previous_from_list(current_suggestion_list, selected_suggestion);
else if (data->EventKey == ImGuiKey_DownArrow)
get_next_from_list(current_suggestion_list, selected_suggestion);
}
return 0;
}
void view::cmd_executor()
{
if (!g.cmd_executor.enabled)
@ -20,7 +261,6 @@ namespace big
if (ImGui::Begin("cmd_executor", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMouseInputs))
{
static std::string command_buffer;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {10.f, 15.f});
components::sub_title("CMD_EXECUTOR_TITLE"_T);
@ -28,46 +268,77 @@ namespace big
ImGui::SetKeyboardFocusHere(0);
ImGui::SetNextItemWidth((screen_x * 0.5f) - 30.f);
if (components::input_text_with_hint("", "CMD_EXECUTOR_TYPE_CMD"_T, command_buffer, ImGuiInputTextFlags_EnterReturnsTrue))
if (components::input_text_with_hint("", "CMD_EXECUTOR_TYPE_CMD"_T, command_buffer, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory, nullptr, apply_suggestion))
{
if (command::process(command_buffer, std::make_shared<default_command_context>(), true))
if (command::process(command_buffer, std::make_shared<default_command_context>(), false))
{
g.cmd_executor.enabled = false;
command_buffer = {};
add_to_last_used_commands(command_buffer);
command_buffer = {};
selected_suggestion = std::string();
}
}
if (!command_buffer.empty())
{
auto separate_commands = string::operations::split(command_buffer, ';'); // Split by semicolon to support multiple commands
get_appropriate_suggestion(separate_commands.back(), auto_fill_suggestion);
if (auto_fill_suggestion != command_buffer)
ImGui::Text("Suggestion: %s", auto_fill_suggestion.data());
}
components::small_text("CMD_EXECUTOR_MULTIPLE_CMDS"_T);
components::small_text("CMD_EXECUTOR_INSTRUCTIONS"_T);
ImGui::Separator();
ImGui::Spacing();
auto possible_commands = command::get_suggestions(command_buffer);
if (possible_commands.size() == 0)
if (current_suggestion_list.size() > 0)
{
ImGui::Text("CMD_EXECUTOR_NO_CMD"_T.data());
}
else
{
for (auto cmd : possible_commands)
for (auto suggestion : current_suggestion_list)
{
auto cmd_name = cmd->get_name();
auto cmd_label = cmd->get_label();
auto cmd_description = cmd->get_description();
auto cmd_num_args = cmd->get_num_args() ? cmd->get_num_args().value() : 0;
ImGui::Text(std::vformat("CMD_EXECUTOR_CMD_TEMPLATE"_T, std::make_format_args(cmd_name, cmd_label, cmd_description, cmd_num_args)).data());
// check if we aren't on the last iteration
if (cmd != possible_commands.back())
ImGui::Separator();
components::selectable(suggestion, suggestion == selected_suggestion);
}
}
if (current_index(command_buffer) == 1)
{
if (!g.cmd.command_history.empty())
{
current_suggestion_list = deque_to_vector(g.cmd.command_history);
}
}
// If we are at any index above the first word, suggest arguments
else if (current_index(command_buffer) > 1)
{
auto current_buffer_index = current_index(command_buffer);
auto separate_commands = string::operations::split(command_buffer, ';'); // Split by semicolon to support multiple commands
auto buffer_words = string::operations::split(separate_commands.back(), ' ');
if (auto current_command = command::get(rage::joaat(buffer_words.front())))
{
auto argument_suggestions = current_command->get_argument_suggestions(current_buffer_index - 1);
if (argument_suggestions != std::nullopt)
{
auto filtered_suggestions = suggestion_list_filtered(argument_suggestions.value(), buffer_words.back());
if (filtered_suggestions.size() > 10)
{
current_suggestion_list =
std::vector<std::string>(filtered_suggestions.begin(), filtered_suggestions.begin() + 10);
}
else
{
current_suggestion_list = filtered_suggestions;
}
}
}
}
ImGui::PopStyleVar();
}
ImGui::End();
}
bool_command
g_cmd_executor("cmdexecutor", "CMD_EXECUTOR", "CMD_EXECUTOR_DESC", g.cmd_executor.enabled, false);
bool_command g_cmd_executor("cmdexecutor", "CMD_EXECUTOR", "CMD_EXECUTOR_DESC", g.cmd_executor.enabled, false);
}