mirror of
https://github.com/Mr-X-GTA/YimMenu.git
synced 2024-12-22 20:17:24 +08:00
YimCLI (#3348)
* Better command suggestions * Suggestions work at any location in a written command * Multiple commands in a single command * Added spectate command & highlight on suggestion selection * Added no suggestions warning * Added Kamikaze & send squad & join player command
This commit is contained in:
parent
a25e0e7d0a
commit
271ba08093
@ -24,36 +24,41 @@ namespace big
|
|||||||
|
|
||||||
virtual std::optional<command_arguments> parse_args(const std::vector<std::string>& args, const std::shared_ptr<command_context> ctx) override
|
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);
|
command_arguments result(2);
|
||||||
|
// Get possible proxies for the arguments
|
||||||
|
auto first_proxy = get_argument_proxy_value(args[0]);
|
||||||
|
auto second_proxy = get_argument_proxy_value(args[1]);
|
||||||
|
|
||||||
auto first_possible_proxy = get_argument_proxy_value(args[0]);
|
// Add proxies to result if they exist
|
||||||
auto second_possible_proxy = get_argument_proxy_value(args[1]);
|
if (first_proxy)
|
||||||
|
result.push(first_proxy.value());
|
||||||
|
if (second_proxy)
|
||||||
|
result.push(second_proxy.value());
|
||||||
|
|
||||||
if (first_possible_proxy.has_value())
|
// Return result if both proxies are valid
|
||||||
result.push(first_possible_proxy.value());
|
if (first_proxy && second_proxy)
|
||||||
|
|
||||||
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;
|
return result;
|
||||||
|
|
||||||
|
// Resolve players if proxies are not valid
|
||||||
player_ptr sender, target;
|
player_ptr sender, target;
|
||||||
|
if (first_proxy == std::nullopt)
|
||||||
if (!first_possible_proxy.has_value())
|
|
||||||
sender = g_player_service->get_by_name_closest(args[0]);
|
sender = g_player_service->get_by_name_closest(args[0]);
|
||||||
|
if (second_proxy == std::nullopt)
|
||||||
if (!second_possible_proxy.has_value())
|
|
||||||
target = g_player_service->get_by_name_closest(args[1]);
|
target = g_player_service->get_by_name_closest(args[1]);
|
||||||
|
|
||||||
if ((!first_possible_proxy.has_value() && !sender) || (!second_possible_proxy.has_value() && !target))
|
// Error handling for invalid or not found players
|
||||||
|
if ((first_proxy == std::nullopt && !sender) || (second_proxy == std::nullopt && !target))
|
||||||
{
|
{
|
||||||
g_notification_service.push_error(std::string("TELEPORT_PLAYER_TO_PLAYER"_T), (std::string("INVALID_PLAYER_NAME_NOTIFICATION"_T)));
|
g_notification_service.push_error(std::string("TELEPORT_PLAYER_TO_PLAYER"_T), std::string("INVALID_PLAYER_NAME_NOTIFICATION"_T));
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(sender->id());
|
// Add resolved player IDs to result
|
||||||
result.push(target->id());
|
if (sender)
|
||||||
|
result.push(sender->id());
|
||||||
|
if (target)
|
||||||
|
result.push(target->id());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
182
src/backend/commands/player/toxic/kamikaze.cpp
Normal file
182
src/backend/commands/player/toxic/kamikaze.cpp
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
#include "backend/player_command.hpp"
|
||||||
|
#include "natives.hpp"
|
||||||
|
#include "pointers.hpp"
|
||||||
|
#include "services/gta_data/gta_data_service.hpp"
|
||||||
|
#include "util/pathfind.hpp"
|
||||||
|
#include "util/string_operations.hpp"
|
||||||
|
#include "util/teleport.hpp"
|
||||||
|
#include "util/vehicle.hpp"
|
||||||
|
|
||||||
|
namespace big
|
||||||
|
{
|
||||||
|
enum class eKamikazeType
|
||||||
|
{
|
||||||
|
REGULAR,
|
||||||
|
SELF,
|
||||||
|
TOPDOWN
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::map<eKamikazeType, std::string> kamikaze_types = {{eKamikazeType::REGULAR, "Regular"}, {eKamikazeType::SELF, "Self"}, {eKamikazeType::TOPDOWN, "Topdown"}};
|
||||||
|
|
||||||
|
class kamikaze : player_command
|
||||||
|
{
|
||||||
|
using player_command::player_command;
|
||||||
|
|
||||||
|
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
|
||||||
|
{
|
||||||
|
std::vector<std::string> suggestions;
|
||||||
|
if (arg == 1)
|
||||||
|
{
|
||||||
|
for (auto& [_, player] : g_player_service->players())
|
||||||
|
{
|
||||||
|
suggestions.push_back(player->get_name());
|
||||||
|
}
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
else if (arg == 2)
|
||||||
|
{
|
||||||
|
for (auto& [type, name] : kamikaze_types)
|
||||||
|
{
|
||||||
|
suggestions.push_back(name);
|
||||||
|
}
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
else if (arg == 3)
|
||||||
|
{
|
||||||
|
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(3);
|
||||||
|
|
||||||
|
std::string self_name = g_player_service->get_self()->get_name();
|
||||||
|
std::string args_name_lower = args[0];
|
||||||
|
string::operations::to_lower(self_name);
|
||||||
|
string::operations::to_lower(args_name_lower);
|
||||||
|
|
||||||
|
player_ptr target{};
|
||||||
|
if (args[0] == "me" || args[0] == "self" || self_name.find(args_name_lower) != std::string::npos)
|
||||||
|
target = g_player_service->get_self();
|
||||||
|
else
|
||||||
|
target = g_player_service->get_by_name_closest(args[0]);
|
||||||
|
|
||||||
|
eKamikazeType type = (eKamikazeType)-1;
|
||||||
|
|
||||||
|
for (auto it = kamikaze_types.begin(); it != kamikaze_types.end(); ++it)
|
||||||
|
if (it->second == args[1])
|
||||||
|
type = it->first;
|
||||||
|
|
||||||
|
if (!target)
|
||||||
|
{
|
||||||
|
g_notification_service.push_error("Teleport", "Invalid player name(s).");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == (eKamikazeType)-1)
|
||||||
|
{
|
||||||
|
g_notification_service.push_error("Teleport", "Invalid type.");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(target->id());
|
||||||
|
result.push((int)type);
|
||||||
|
|
||||||
|
|
||||||
|
std::string item_name_lower, args_lower;
|
||||||
|
args_lower = args[2];
|
||||||
|
string::operations::to_lower(args_lower);
|
||||||
|
for (auto& item : g_gta_data_service->vehicles())
|
||||||
|
{
|
||||||
|
item_name_lower = item.second.m_name;
|
||||||
|
string::operations::to_lower(item_name_lower);
|
||||||
|
if (item_name_lower.find(args_lower) != std::string::npos)
|
||||||
|
{
|
||||||
|
result.push(rage::joaat(item.first));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual CommandAccessLevel get_access_level() override
|
||||||
|
{
|
||||||
|
return CommandAccessLevel::AGGRESSIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
float get_speed_for_plane(Entity target)
|
||||||
|
{
|
||||||
|
auto ent_speed = ENTITY::GET_ENTITY_SPEED(target);
|
||||||
|
if (ent_speed < 100)
|
||||||
|
return 100;
|
||||||
|
else
|
||||||
|
return ent_speed + 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void execute(player_ptr player, const command_arguments& _args, const std::shared_ptr<command_context> ctx) override
|
||||||
|
{
|
||||||
|
player = g_player_service->get_by_id(_args.get<uint8_t>(0));
|
||||||
|
auto type = (eKamikazeType)_args.get<int>(1);
|
||||||
|
auto vehicle_model = _args.get<rage::joaat_t>(2);
|
||||||
|
|
||||||
|
if (!player || !player->get_ped()->get_position())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto position = player->get_ped()->get_position();
|
||||||
|
auto ped_handle = g_pointers->m_gta.m_ptr_to_handle(player->get_ped());
|
||||||
|
float height = 20;
|
||||||
|
float distance = 30;
|
||||||
|
|
||||||
|
if (type == eKamikazeType::SELF)
|
||||||
|
{
|
||||||
|
height = 100;
|
||||||
|
distance = 100;
|
||||||
|
}
|
||||||
|
else if (type == eKamikazeType::TOPDOWN)
|
||||||
|
{
|
||||||
|
height = 100;
|
||||||
|
distance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto spawn_position = ENTITY::GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(ped_handle, 0.0, -distance, height);
|
||||||
|
|
||||||
|
Entity plane{};
|
||||||
|
if (type == eKamikazeType::SELF && ENTITY::DOES_ENTITY_EXIST(self::veh))
|
||||||
|
{
|
||||||
|
plane = self::veh;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
plane = vehicle::spawn(vehicle_model, spawn_position, ENTITY::GET_ENTITY_HEADING(ped_handle));
|
||||||
|
|
||||||
|
if (ENTITY::DOES_ENTITY_EXIST(plane) && entity::take_control_of(plane))
|
||||||
|
{
|
||||||
|
float angle = type == eKamikazeType::TOPDOWN ? 90 : 30;
|
||||||
|
|
||||||
|
ENTITY::SET_ENTITY_COORDS_NO_OFFSET(plane, spawn_position.x, spawn_position.y, spawn_position.z, 0, 0, 0);
|
||||||
|
auto current_rot = ENTITY::GET_ENTITY_ROTATION(plane, 0);
|
||||||
|
ENTITY::SET_ENTITY_ROTATION(plane, current_rot.x - angle, current_rot.y, current_rot.z, 0, true);
|
||||||
|
VEHICLE::SET_VEHICLE_FORWARD_SPEED(plane, get_speed_for_plane(ped_handle));
|
||||||
|
VEHICLE::SET_VEHICLE_ENGINE_ON(plane, true, true, true);
|
||||||
|
|
||||||
|
if (type != eKamikazeType::SELF)
|
||||||
|
VEHICLE::SET_VEHICLE_OUT_OF_CONTROL(plane, false, true);
|
||||||
|
|
||||||
|
if (type == eKamikazeType::SELF && !ENTITY::DOES_ENTITY_EXIST(self::veh))
|
||||||
|
{
|
||||||
|
teleport::into_vehicle(plane);
|
||||||
|
entity::take_control_of(plane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
kamikaze g_kamikaze("kamikaze", "KAMIKAZE", "KAMIKAZE_DESC", 2);
|
||||||
|
}
|
109
src/backend/commands/player/toxic/send_squad.cpp
Normal file
109
src/backend/commands/player/toxic/send_squad.cpp
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#include "backend/player_command.hpp"
|
||||||
|
#include "natives.hpp"
|
||||||
|
#include "pointers.hpp"
|
||||||
|
#include "services/squad_spawner/squad_spawner.hpp"
|
||||||
|
|
||||||
|
namespace big
|
||||||
|
{
|
||||||
|
class send_squad : player_command
|
||||||
|
{
|
||||||
|
using player_command::player_command;
|
||||||
|
|
||||||
|
virtual std::optional<std::vector<std::string>> get_argument_suggestions(int arg) override
|
||||||
|
{
|
||||||
|
if (arg == 1)
|
||||||
|
{
|
||||||
|
std::vector<std::string> suggestions;
|
||||||
|
for (auto& player : g_player_service->players() | std::ranges::views::values)
|
||||||
|
{
|
||||||
|
suggestions.push_back(player->get_name());
|
||||||
|
}
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg == 2)
|
||||||
|
{
|
||||||
|
std::vector<std::string> suggestions;
|
||||||
|
for (auto& item : g_squad_spawner_service.m_templates)
|
||||||
|
{
|
||||||
|
suggestions.push_back(item.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(2);
|
||||||
|
|
||||||
|
auto proxy_value = get_argument_proxy_value(args[0]);
|
||||||
|
|
||||||
|
if (proxy_value.has_value())
|
||||||
|
{
|
||||||
|
result.push(proxy_value.value());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto player = g_player_service->get_by_name_closest(args[0]);
|
||||||
|
if (player == nullptr)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(player->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
int template_index = -1;
|
||||||
|
for (int i = 0; i < g_squad_spawner_service.m_templates.size(); i++)
|
||||||
|
{
|
||||||
|
if (g_squad_spawner_service.m_templates[i].m_name == args[1])
|
||||||
|
{
|
||||||
|
template_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template_index == -1)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(template_index);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual CommandAccessLevel get_access_level() override
|
||||||
|
{
|
||||||
|
return CommandAccessLevel::AGGRESSIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 template_index = _args.get<int>(1);
|
||||||
|
|
||||||
|
if (sender == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
squad squad{};
|
||||||
|
for (size_t i = 0; i < g_squad_spawner_service.m_templates.size(); i++)
|
||||||
|
{
|
||||||
|
if (i == template_index)
|
||||||
|
{
|
||||||
|
squad = g_squad_spawner_service.m_templates[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_squad_spawner_service.spawn_squad(squad, sender, false, {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
send_squad g_send_squad("squad", "SEND_SQUAD", "SEND_SQUAD_DESC", 1);
|
||||||
|
}
|
@ -20,6 +20,7 @@ namespace big
|
|||||||
if (arg == 1)
|
if (arg == 1)
|
||||||
{
|
{
|
||||||
std::vector<std::string> suggestions;
|
std::vector<std::string> suggestions;
|
||||||
|
suggestions.push_back("stop");
|
||||||
for (auto& item : g_ped_animation_service.all_saved_animations | std::views::values | std::views::join)
|
for (auto& item : g_ped_animation_service.all_saved_animations | std::views::values | std::views::join)
|
||||||
{
|
{
|
||||||
std::string anim_name = item.name;
|
std::string anim_name = item.name;
|
||||||
|
96
src/backend/commands/session/join_player.cpp
Normal file
96
src/backend/commands/session/join_player.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include "backend/command.hpp"
|
||||||
|
#include "network/CNetworkPlayerMgr.hpp"
|
||||||
|
#include "pointers.hpp"
|
||||||
|
#include "services/players/player_service.hpp"
|
||||||
|
#include "socialclub/FriendRegistry.hpp"
|
||||||
|
#include "services/player_database/player_database_service.hpp"
|
||||||
|
#include "services/api/api_service.hpp"
|
||||||
|
#include "util/session.hpp"
|
||||||
|
|
||||||
|
namespace big
|
||||||
|
{
|
||||||
|
class join_player : 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 (size_t i = 0; i < g_pointers->m_gta.m_friend_registry->m_friend_count; i++)
|
||||||
|
{
|
||||||
|
auto f = g_pointers->m_gta.m_friend_registry->get(i);
|
||||||
|
if (f && f->m_friend_state & (1 << 0 | 1 << 1)) // Check if online and playing same game
|
||||||
|
{
|
||||||
|
suggestions.push_back(f->m_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& player : g_player_database_service->get_sorted_players() | std::ranges::views::keys)
|
||||||
|
suggestions.push_back(player);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
uint64_t rid = 0;
|
||||||
|
// Check if the player is a friend
|
||||||
|
for (size_t i = 0; i < g_pointers->m_gta.m_friend_registry->m_friend_count; i++)
|
||||||
|
{
|
||||||
|
auto f = g_pointers->m_gta.m_friend_registry->get(i);
|
||||||
|
if (f && f->m_name == args[0])
|
||||||
|
{
|
||||||
|
rid = f->m_rockstar_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not a friend, check if the player is in the database
|
||||||
|
if (rid == 0)
|
||||||
|
{
|
||||||
|
for (const auto& [name, player] : g_player_database_service->get_sorted_players())
|
||||||
|
if (name == args[0])
|
||||||
|
rid = player->rockstar_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the player is not a friend or in the database, fetch rid from the API
|
||||||
|
if (rid == 0)
|
||||||
|
{
|
||||||
|
auto fetched = g_api_service->get_rid_from_username(args[0], rid);
|
||||||
|
|
||||||
|
if (!fetched || rid == 0)
|
||||||
|
{
|
||||||
|
ctx->report_error("Failed to fetch player's Rockstar ID from the API.");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(rid);
|
||||||
|
|
||||||
|
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 rid = args.get<uint64_t>(0);
|
||||||
|
if (rid)
|
||||||
|
{
|
||||||
|
session::join_by_rockstar_id(rid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
join_player g_join_player("joinplayer", "JOIN_PLAYER", "JOIN_PLAYER_DESC", 1);
|
||||||
|
}
|
32
src/backend/commands/session/specate_player.cpp
Normal file
32
src/backend/commands/session/specate_player.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#include "backend/player_command.hpp"
|
||||||
|
#include "natives.hpp"
|
||||||
|
#include "pointers.hpp"
|
||||||
|
#include "util/globals.hpp"
|
||||||
|
|
||||||
|
namespace big
|
||||||
|
{
|
||||||
|
class spectate_player : player_command
|
||||||
|
{
|
||||||
|
using player_command::player_command;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
if (player == g_player_service->get_self())
|
||||||
|
{
|
||||||
|
g.player.spectating = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_player_service->set_selected(player);
|
||||||
|
g.player.spectating = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
spectate_player g_spectate_player("spectate", "SPECTATE", "SPECTATE_DESC", 0);
|
||||||
|
spectate_player g_spectate_player_shortcut("spec", "SPECTATE", "SPECTATE_DESC", 0);
|
||||||
|
}
|
@ -7,19 +7,280 @@
|
|||||||
|
|
||||||
namespace big
|
namespace big
|
||||||
{
|
{
|
||||||
//TODO Argument suggestions are limited to the last word in the buffer
|
|
||||||
//TODO Allow for optional arguments??
|
//TODO Allow for optional arguments??
|
||||||
|
|
||||||
static std::vector<std::string> current_suggestion_list;
|
static std::vector<std::string> current_suggestion_list;
|
||||||
static std::string command_buffer;
|
static std::string command_buffer;
|
||||||
static std::string auto_fill_suggestion;
|
static std::string auto_fill_suggestion;
|
||||||
static std::string selected_suggestion;
|
static std::string selected_suggestion;
|
||||||
|
bool suggestion_is_history = false;
|
||||||
|
static int cursor_pos = 0;
|
||||||
|
|
||||||
|
struct argument
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
int index;
|
||||||
|
int start_index;
|
||||||
|
int end_index;
|
||||||
|
bool is_argument = true; // If the argument is the command itself, this will be false
|
||||||
|
};
|
||||||
|
|
||||||
|
struct command_scope
|
||||||
|
{
|
||||||
|
command* cmd;
|
||||||
|
std::string raw;
|
||||||
|
std::string name; // If the command is not found, this will be the incomplete command
|
||||||
|
int name_start_index;
|
||||||
|
int name_end_index;
|
||||||
|
int index;
|
||||||
|
int start_index;
|
||||||
|
int end_index;
|
||||||
|
int argument_count;
|
||||||
|
std::vector<argument> arguments;
|
||||||
|
|
||||||
|
argument* get_argument(int cursor_pos)
|
||||||
|
{
|
||||||
|
auto found = std::find_if(arguments.begin(), arguments.end(), [&](const argument& arg) {
|
||||||
|
return cursor_pos >= arg.start_index && cursor_pos <= arg.end_index;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found != arguments.end())
|
||||||
|
return &*found;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void clean_buffer(std::string& buffer)
|
||||||
|
{
|
||||||
|
std::string new_buffer;
|
||||||
|
bool last_char_was_space = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < buffer.size(); ++i)
|
||||||
|
{
|
||||||
|
if (buffer[i] == ' ')
|
||||||
|
{
|
||||||
|
// Skip consecutive spaces
|
||||||
|
if (!last_char_was_space)
|
||||||
|
{
|
||||||
|
new_buffer += ' ';
|
||||||
|
last_char_was_space = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (buffer[i] == ';')
|
||||||
|
{
|
||||||
|
new_buffer += ';';
|
||||||
|
// Skip spaces after a semicolon
|
||||||
|
while (i + 1 < buffer.size() && buffer[i + 1] == ' ')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
last_char_was_space = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new_buffer += buffer[i];
|
||||||
|
last_char_was_space = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading and trailing spaces (optional, if needed)
|
||||||
|
size_t start = new_buffer.find_first_not_of(' ');
|
||||||
|
size_t end = new_buffer.find_last_not_of(' ');
|
||||||
|
if (start == std::string::npos || end == std::string::npos)
|
||||||
|
{
|
||||||
|
buffer.clear(); // No non-space characters found
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer = new_buffer.substr(start, end - start + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class serialized_buffer
|
||||||
|
{
|
||||||
|
std::string buffer;
|
||||||
|
int total_length;
|
||||||
|
int command_count;
|
||||||
|
std::vector<command_scope> command_scopes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
serialized_buffer(std::string buffer) :
|
||||||
|
buffer(buffer),
|
||||||
|
total_length(0),
|
||||||
|
command_count(0)
|
||||||
|
{
|
||||||
|
if (buffer.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
clean_buffer(buffer);
|
||||||
|
parse_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_buffer()
|
||||||
|
{
|
||||||
|
auto separate_commands = string::operations::split(buffer, ';');
|
||||||
|
|
||||||
|
command_count = separate_commands.size();
|
||||||
|
total_length = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < command_count; i++)
|
||||||
|
{
|
||||||
|
auto words = string::operations::split(separate_commands[i], ' ');
|
||||||
|
auto cmd = command::get(rage::joaat(words.front()));
|
||||||
|
|
||||||
|
command_scope scope;
|
||||||
|
scope.cmd = cmd;
|
||||||
|
scope.name = words.front();
|
||||||
|
scope.index = i;
|
||||||
|
scope.start_index = total_length;
|
||||||
|
scope.raw = separate_commands[i];
|
||||||
|
scope.argument_count = words.size() - 1;
|
||||||
|
|
||||||
|
size_t buffer_pos = total_length;
|
||||||
|
|
||||||
|
for (int j = 0; j < words.size(); j++)
|
||||||
|
{
|
||||||
|
size_t word_start = buffer.find(words[j], buffer_pos);
|
||||||
|
|
||||||
|
argument arg;
|
||||||
|
arg.name = words[j];
|
||||||
|
arg.index = j;
|
||||||
|
arg.is_argument = j > 0;
|
||||||
|
arg.start_index = word_start;
|
||||||
|
arg.end_index = word_start + words[j].size();
|
||||||
|
scope.arguments.push_back(arg);
|
||||||
|
|
||||||
|
buffer_pos = word_start + words[j].size();
|
||||||
|
if (j < words.size() - 1)
|
||||||
|
{
|
||||||
|
buffer_pos++; // Move past the space
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.end_index = buffer_pos;
|
||||||
|
total_length = buffer_pos + 1; // Move past the semicolon or end of command
|
||||||
|
|
||||||
|
command_scopes.push_back(scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string deserialize()
|
||||||
|
{
|
||||||
|
if (command_count == 0)
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
std::string deserialized_buffer;
|
||||||
|
for (auto& command : command_scopes)
|
||||||
|
{
|
||||||
|
for (auto& argument : command.arguments)
|
||||||
|
{
|
||||||
|
deserialized_buffer += argument.name;
|
||||||
|
|
||||||
|
if (argument.name != command.arguments.back().name)
|
||||||
|
deserialized_buffer += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.raw != command_scopes.back().raw)
|
||||||
|
deserialized_buffer += ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
return deserialized_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_scope* get_command_scope(int cursor_pos)
|
||||||
|
{
|
||||||
|
auto found = std::find_if(command_scopes.begin(), command_scopes.end(), [&](const command_scope& scope) {
|
||||||
|
return cursor_pos >= scope.start_index && cursor_pos <= scope.end_index;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found != command_scopes.end())
|
||||||
|
return &*found;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_current_index_argument(int cursor_pos)
|
||||||
|
{
|
||||||
|
auto* scope = get_command_scope(cursor_pos);
|
||||||
|
|
||||||
|
if (!scope)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto* argument = scope->get_argument(cursor_pos);
|
||||||
|
|
||||||
|
if (!argument)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return argument->is_argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_argument_index_from_char_index(int cursor_pos)
|
||||||
|
{
|
||||||
|
auto* scope = get_command_scope(cursor_pos);
|
||||||
|
|
||||||
|
if (!scope)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
auto* argument = scope->get_argument(cursor_pos);
|
||||||
|
|
||||||
|
if (!argument)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return argument->index;
|
||||||
|
}
|
||||||
|
|
||||||
|
command* get_command_of_index(int cursor_pos)
|
||||||
|
{
|
||||||
|
auto* scope = get_command_scope(cursor_pos);
|
||||||
|
|
||||||
|
if (!scope)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return scope->cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_argument_of_scope(int index, int arg, std::string new_argument)
|
||||||
|
{
|
||||||
|
auto* scope = get_command_scope(index);
|
||||||
|
|
||||||
|
if (!scope)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto* argument = scope->get_argument(index);
|
||||||
|
|
||||||
|
if (!argument)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto original_arg_textlen = argument->name.length();
|
||||||
|
auto new_arg_textlen = new_argument.length();
|
||||||
|
auto len_diff = new_arg_textlen - original_arg_textlen; // Can be negative
|
||||||
|
argument->name = new_argument;
|
||||||
|
|
||||||
|
for (int i = scope->index; i < command_count; i++)
|
||||||
|
{
|
||||||
|
auto& current_scope = command_scopes[i];
|
||||||
|
|
||||||
|
if (current_scope.index == scope->index)
|
||||||
|
{
|
||||||
|
current_scope.end_index += len_diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& current_argument : current_scope.arguments)
|
||||||
|
{
|
||||||
|
if (current_argument.index == argument->index)
|
||||||
|
{
|
||||||
|
current_argument.end_index += len_diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static serialized_buffer s_buffer(command_buffer);
|
||||||
|
|
||||||
bool does_string_exist_in_list(const std::string& command, std::vector<std::string> list)
|
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) {
|
auto found = std::find(list.begin(), list.end(), command);
|
||||||
return cmd == command;
|
|
||||||
});
|
|
||||||
return found != list.end();
|
return found != list.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,25 +326,28 @@ namespace big
|
|||||||
return std::string();
|
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> suggestion_list_filtered(std::vector<std::string> suggestions, std::string filter)
|
||||||
{
|
{
|
||||||
std::vector<std::string> suggestions_filtered;
|
std::vector<std::string> suggestions_filtered;
|
||||||
std::string filter_lowercase = filter;
|
std::string filter_lowercase = filter;
|
||||||
string::operations::to_lower(filter_lowercase);
|
string::operations::to_lower(filter_lowercase);
|
||||||
|
|
||||||
|
auto current_scope = s_buffer.get_command_scope(cursor_pos);
|
||||||
|
|
||||||
|
if (!current_scope)
|
||||||
|
return suggestions;
|
||||||
|
|
||||||
|
auto argument = current_scope->get_argument(cursor_pos);
|
||||||
|
|
||||||
|
if (!argument)
|
||||||
|
return suggestions;
|
||||||
|
|
||||||
for (auto suggestion : suggestions)
|
for (auto suggestion : suggestions)
|
||||||
{
|
{
|
||||||
std::string suggestion_lowercase = suggestion;
|
std::string suggestion_lowercase = suggestion;
|
||||||
string::operations::to_lower(suggestion_lowercase);
|
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*/)
|
if (suggestion_lowercase.find(filter_lowercase) != std::string::npos || does_string_exist_in_list(argument->name, suggestions) /*Need this to maintain suggestion list while navigating it*/)
|
||||||
suggestions_filtered.push_back(suggestion);
|
suggestions_filtered.push_back(suggestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,38 +356,47 @@ namespace big
|
|||||||
|
|
||||||
void get_appropriate_suggestion(std::string current_buffer, std::string& suggestion_)
|
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 serbuffer = serialized_buffer(current_buffer);
|
||||||
auto words = string::operations::split(separate_commands.back(), ' ');
|
auto current_command = serbuffer.get_command_of_index(cursor_pos);
|
||||||
auto current_command = command::get(rage::joaat(words.front()));
|
auto scope = serbuffer.get_command_scope(cursor_pos);
|
||||||
auto argument_index = current_index(current_buffer);
|
auto argument_index = serbuffer.get_argument_index_from_char_index(cursor_pos);
|
||||||
|
|
||||||
if (argument_index == 1)
|
if (!scope)
|
||||||
{
|
{
|
||||||
suggestion_ = auto_fill_command(words.back());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (argument_index == -1)
|
||||||
{
|
{
|
||||||
if (!current_command)
|
return;
|
||||||
return;
|
}
|
||||||
|
|
||||||
auto suggestions = current_command->get_argument_suggestions(argument_index - 1);
|
if (!scope->get_argument(cursor_pos)->is_argument)
|
||||||
|
{
|
||||||
|
suggestion_ = auto_fill_command(scope->name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (suggestions == std::nullopt)
|
if (!current_command)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (auto suggestion : suggestion_list_filtered(suggestions.value(), words.back()))
|
auto suggestions = current_command->get_argument_suggestions(argument_index);
|
||||||
|
auto argument = scope->get_argument(cursor_pos);
|
||||||
|
|
||||||
|
if (suggestions == std::nullopt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto suggestion : suggestion_list_filtered(suggestions.value(), argument->name))
|
||||||
|
{
|
||||||
|
std::string guess_lowercase = argument->name;
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
std::string guess_lowercase = words.back();
|
suggestion_ = suggestion;
|
||||||
std::string suggestion_lowercase = suggestion;
|
break;
|
||||||
string::operations::to_lower(suggestion_lowercase);
|
|
||||||
string::operations::to_lower(guess_lowercase);
|
|
||||||
|
|
||||||
if (suggestion_lowercase.find(guess_lowercase) != std::string::npos)
|
|
||||||
{
|
|
||||||
suggestion_ = suggestion;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,49 +449,93 @@ namespace big
|
|||||||
current = *(found + 1);
|
current = *(found + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void rebuild_buffer_with_suggestion(ImGuiInputTextCallbackData* data, std::string suggestion)
|
void update_current_argument_with_suggestion(ImGuiInputTextCallbackData* data, std::string suggestion)
|
||||||
{
|
{
|
||||||
auto separate_commands = string::operations::split(data->Buf, ';'); // Split by semicolon to support multiple commands
|
if (!data)
|
||||||
auto words = string::operations::split(separate_commands.back(), ' ');
|
return;
|
||||||
|
|
||||||
std::string new_text;
|
std::string new_text;
|
||||||
|
auto sbuffer = serialized_buffer(data->Buf);
|
||||||
|
auto scope = sbuffer.get_command_scope(data->CursorPos);
|
||||||
|
auto argument_index = sbuffer.get_argument_index_from_char_index(data->CursorPos);
|
||||||
|
|
||||||
// Replace the last word with the suggestion
|
if (!scope)
|
||||||
words.pop_back();
|
return;
|
||||||
words.push_back(suggestion);
|
|
||||||
|
|
||||||
// Replace the last command with the new suggestion
|
if (argument_index == -1)
|
||||||
separate_commands.pop_back();
|
return;
|
||||||
separate_commands.push_back(string::operations::join(words, ' '));
|
|
||||||
|
|
||||||
new_text = string::operations::join(separate_commands, ';');
|
auto argument = scope->get_argument(data->CursorPos);
|
||||||
|
|
||||||
|
if (!argument)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sbuffer.update_argument_of_scope(data->CursorPos, argument_index, suggestion);
|
||||||
|
|
||||||
|
new_text = sbuffer.deserialize();
|
||||||
|
|
||||||
data->DeleteChars(0, data->BufTextLen);
|
data->DeleteChars(0, data->BufTextLen);
|
||||||
data->InsertChars(0, new_text.c_str());
|
data->InsertChars(0, new_text.c_str());
|
||||||
|
data->CursorPos = argument->end_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int apply_suggestion(ImGuiInputTextCallbackData* data)
|
bool buffer_needs_cleaning(const std::string& input)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < input.size(); ++i)
|
||||||
|
{
|
||||||
|
if (input[i] == ' ')
|
||||||
|
{
|
||||||
|
if (i + 1 < input.size() && input[i + 1] == ' ')
|
||||||
|
{
|
||||||
|
return true; // Consecutive spaces
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (input[i] == ';')
|
||||||
|
{
|
||||||
|
if (i + 1 < input.size() && (input[i + 1] == ';' || input[i + 1] == ' '))
|
||||||
|
{
|
||||||
|
return true; // Consecutive semicolons or space after semicolon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int input_callback(ImGuiInputTextCallbackData* data)
|
||||||
{
|
{
|
||||||
if (!data)
|
if (!data)
|
||||||
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor_pos != data->CursorPos)
|
||||||
|
{
|
||||||
|
selected_suggestion = std::string();
|
||||||
|
cursor_pos = data->CursorPos;
|
||||||
|
|
||||||
|
if (buffer_needs_cleaning(data->Buf))
|
||||||
|
{
|
||||||
|
std::string cleaned_buffer = data->Buf;
|
||||||
|
clean_buffer(cleaned_buffer);
|
||||||
|
data->DeleteChars(0, data->BufTextLen);
|
||||||
|
data->InsertChars(0, cleaned_buffer.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion)
|
if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion)
|
||||||
{
|
{
|
||||||
// User has a suggestion selectable higlighted, this takes precedence
|
// User has a suggestion selectable higlighted, this takes precedence
|
||||||
if (!selected_suggestion.empty())
|
if (!selected_suggestion.empty())
|
||||||
{
|
{
|
||||||
// This could be a history suggestion with arguments, so we have to check for it
|
if (suggestion_is_history)
|
||||||
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->DeleteChars(0, data->BufTextLen);
|
||||||
data->InsertChars(0, selected_suggestion.c_str());
|
data->InsertChars(0, selected_suggestion.c_str());
|
||||||
}
|
}
|
||||||
// Its probably an argument suggestion or a raw command, append it
|
|
||||||
else
|
else
|
||||||
rebuild_buffer_with_suggestion(data, selected_suggestion);
|
{
|
||||||
|
update_current_argument_with_suggestion(data, selected_suggestion);
|
||||||
|
}
|
||||||
|
|
||||||
selected_suggestion = std::string();
|
selected_suggestion = std::string();
|
||||||
return 0;
|
return 0;
|
||||||
@ -229,7 +546,7 @@ namespace big
|
|||||||
|
|
||||||
if (auto_fill_suggestion != data->Buf)
|
if (auto_fill_suggestion != data->Buf)
|
||||||
{
|
{
|
||||||
rebuild_buffer_with_suggestion(data, auto_fill_suggestion);
|
update_current_argument_with_suggestion(data, auto_fill_suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory)
|
else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory)
|
||||||
@ -239,11 +556,26 @@ namespace big
|
|||||||
|
|
||||||
if (data->EventKey == ImGuiKey_UpArrow)
|
if (data->EventKey == ImGuiKey_UpArrow)
|
||||||
get_previous_from_list(current_suggestion_list, selected_suggestion);
|
get_previous_from_list(current_suggestion_list, selected_suggestion);
|
||||||
|
|
||||||
else if (data->EventKey == ImGuiKey_DownArrow)
|
else if (data->EventKey == ImGuiKey_DownArrow)
|
||||||
get_next_from_list(current_suggestion_list, selected_suggestion);
|
get_next_from_list(current_suggestion_list, selected_suggestion);
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!selected_suggestion.empty() && !suggestion_is_history)
|
||||||
|
{
|
||||||
|
auto scope = s_buffer.get_command_scope(data->CursorPos);
|
||||||
|
|
||||||
|
if (!scope)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto argument = scope->get_argument(data->CursorPos);
|
||||||
|
|
||||||
|
if (!argument)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
data->SelectionStart = argument->start_index;
|
||||||
|
data->SelectionEnd = argument->end_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,8 +600,8 @@ namespace big
|
|||||||
ImGui::SetKeyboardFocusHere(0);
|
ImGui::SetKeyboardFocusHere(0);
|
||||||
|
|
||||||
ImGui::SetNextItemWidth((screen_x * 0.5f) - 30.f);
|
ImGui::SetNextItemWidth((screen_x * 0.5f) - 30.f);
|
||||||
|
s_buffer = serialized_buffer(command_buffer); // Update serialized buffer every frame
|
||||||
if (components::input_text_with_hint("", "CMD_EXECUTOR_TYPE_CMD"_T, command_buffer, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory, nullptr, apply_suggestion))
|
if (components::input_text_with_hint("", "CMD_EXECUTOR_TYPE_CMD"_T, command_buffer, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways, nullptr, input_callback))
|
||||||
{
|
{
|
||||||
if (command::process(command_buffer, std::make_shared<default_command_context>(), false))
|
if (command::process(command_buffer, std::make_shared<default_command_context>(), false))
|
||||||
{
|
{
|
||||||
@ -282,8 +614,7 @@ namespace big
|
|||||||
|
|
||||||
if (!command_buffer.empty())
|
if (!command_buffer.empty())
|
||||||
{
|
{
|
||||||
auto separate_commands = string::operations::split(command_buffer, ';'); // Split by semicolon to support multiple commands
|
get_appropriate_suggestion(command_buffer, auto_fill_suggestion);
|
||||||
get_appropriate_suggestion(separate_commands.back(), auto_fill_suggestion);
|
|
||||||
|
|
||||||
if (auto_fill_suggestion != command_buffer)
|
if (auto_fill_suggestion != command_buffer)
|
||||||
ImGui::Text("Suggestion: %s", auto_fill_suggestion.data());
|
ImGui::Text("Suggestion: %s", auto_fill_suggestion.data());
|
||||||
@ -294,6 +625,9 @@ namespace big
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
|
if (suggestion_is_history)
|
||||||
|
components::sub_title("CMD_HISTORY_LABEL"_T);
|
||||||
|
|
||||||
if (current_suggestion_list.size() > 0)
|
if (current_suggestion_list.size() > 0)
|
||||||
{
|
{
|
||||||
for (auto suggestion : current_suggestion_list)
|
for (auto suggestion : current_suggestion_list)
|
||||||
@ -301,27 +635,43 @@ namespace big
|
|||||||
components::selectable(suggestion, suggestion == selected_suggestion);
|
components::selectable(suggestion, suggestion == selected_suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (current_index(command_buffer) == 1)
|
|
||||||
{
|
{
|
||||||
|
components::small_text("CMD_EXECUTOR_NO_SUGGESTIONS"_T);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show history if buffer is empty
|
||||||
|
if (command_buffer.empty())
|
||||||
|
{
|
||||||
|
suggestion_is_history = true;
|
||||||
|
|
||||||
if (!g.cmd.command_history.empty())
|
if (!g.cmd.command_history.empty())
|
||||||
{
|
{
|
||||||
current_suggestion_list = deque_to_vector(g.cmd.command_history);
|
current_suggestion_list = deque_to_vector(g.cmd.command_history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we are at any index above the first word, suggest arguments
|
// If buffer isn't empty, we rely on the serialized buffer to suggest arguments or commands
|
||||||
else if (current_index(command_buffer) > 1)
|
else
|
||||||
{
|
{
|
||||||
auto current_buffer_index = current_index(command_buffer);
|
suggestion_is_history = false;
|
||||||
auto separate_commands = string::operations::split(command_buffer, ';'); // Split by semicolon to support multiple commands
|
auto current_scope = s_buffer.get_command_scope(cursor_pos);
|
||||||
auto buffer_words = string::operations::split(separate_commands.back(), ' ');
|
|
||||||
|
|
||||||
if (auto current_command = command::get(rage::joaat(buffer_words.front())))
|
if (!current_scope)
|
||||||
|
goto VIEW_END;
|
||||||
|
|
||||||
|
auto argument = current_scope->get_argument(cursor_pos);
|
||||||
|
|
||||||
|
if (!argument)
|
||||||
|
goto VIEW_END;
|
||||||
|
|
||||||
|
auto current_command = current_scope->cmd;
|
||||||
|
|
||||||
|
if (argument->is_argument && current_command)
|
||||||
{
|
{
|
||||||
auto argument_suggestions = current_command->get_argument_suggestions(current_buffer_index - 1);
|
auto argument_suggestions = current_command->get_argument_suggestions(argument->index);
|
||||||
if (argument_suggestions != std::nullopt)
|
if (argument_suggestions != std::nullopt)
|
||||||
{
|
{
|
||||||
auto filtered_suggestions = suggestion_list_filtered(argument_suggestions.value(), buffer_words.back());
|
auto filtered_suggestions = suggestion_list_filtered(argument_suggestions.value(), argument->name);
|
||||||
if (filtered_suggestions.size() > 10)
|
if (filtered_suggestions.size() > 10)
|
||||||
{
|
{
|
||||||
current_suggestion_list =
|
current_suggestion_list =
|
||||||
@ -333,7 +683,28 @@ namespace big
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto all_commands = g_commands;
|
||||||
|
std::vector<std::string> command_names{};
|
||||||
|
for (auto& [hash, cmd] : all_commands)
|
||||||
|
{
|
||||||
|
if (cmd && cmd->get_name().length() > 0)
|
||||||
|
command_names.push_back(cmd->get_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto filtered_commands = suggestion_list_filtered(command_names, argument->name);
|
||||||
|
if (filtered_commands.size() > 10)
|
||||||
|
{
|
||||||
|
current_suggestion_list = std::vector<std::string>(filtered_commands.begin(), filtered_commands.begin() + 10);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current_suggestion_list = filtered_commands;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
VIEW_END:
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user