Player database improvements (#1705)

* feat(protections): add per-player sync block options
* feat(player_database): improve player tracker
* fix(rapid_fire): remove unnecessary log statement
* fix(player_database): default state should be UNKNOWN, not INVALID
This commit is contained in:
maybegreat48 2023-07-14 09:02:47 +00:00 committed by GitHub
parent 5770466a84
commit 61bb60d1f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 282 additions and 94 deletions

View File

@ -20,8 +20,6 @@ namespace big
if (!weapon_entity) if (!weapon_entity)
return; return;
LOG(INFO) << weapon_entity;
Vector3 dim_min; Vector3 dim_min;
Vector3 dim_max; Vector3 dim_max;
MISC::GET_MODEL_DIMENSIONS(ENTITY::GET_ENTITY_MODEL(weapon_entity), &dim_min, &dim_max); MISC::GET_MODEL_DIMENSIONS(ENTITY::GET_ENTITY_MODEL(weapon_entity), &dim_min, &dim_max);
@ -36,14 +34,15 @@ namespace big
Vector3 end = camera_position + camera_direction * 2000.0; Vector3 end = camera_position + camera_direction * 2000.0;
const auto raycast_handle = const auto raycast_handle =
SHAPETEST::START_EXPENSIVE_SYNCHRONOUS_SHAPE_TEST_LOS_PROBE( SHAPETEST::START_EXPENSIVE_SYNCHRONOUS_SHAPE_TEST_LOS_PROBE(camera_position.x,
camera_position.x,
camera_position.y, camera_position.y,
camera_position.z, camera_position.z,
end.x, end.x,
end.y, end.y,
end.z, end.z,
-1, 0, 7); -1,
0,
7);
int did_raycast_hit = 0; int did_raycast_hit = 0;
Vector3 raycast_hit_position{}; Vector3 raycast_hit_position{};
Vector3 raycast_surface_normal_hit_position{}; Vector3 raycast_surface_normal_hit_position{};

View File

@ -1,12 +1,12 @@
#include "backend/looped/looped.hpp" #include "backend/looped/looped.hpp"
#include "natives.hpp"
#include "services/gta_data/gta_data_service.hpp"
#include "gta/enums.hpp" #include "gta/enums.hpp"
#include "gui.hpp" #include "gui.hpp"
#include "natives.hpp"
#include "services/gta_data/gta_data_service.hpp"
namespace big namespace big
{ {
const int input_array[6] = {(int)ControllerInputs::INPUT_SELECT_WEAPON_UNARMED, (int)ControllerInputs::INPUT_SELECT_WEAPON_MELEE, (int)ControllerInputs::INPUT_SELECT_WEAPON_SHOTGUN, (int)ControllerInputs::INPUT_SELECT_WEAPON_HEAVY, (int)ControllerInputs::INPUT_SELECT_WEAPON_SPECIAL, (int)ControllerInputs::INPUT_SELECT_WEAPON_HANDGUN}; constexpr int input_array[6] = {(int)ControllerInputs::INPUT_SELECT_WEAPON_UNARMED, (int)ControllerInputs::INPUT_SELECT_WEAPON_MELEE, (int)ControllerInputs::INPUT_SELECT_WEAPON_SHOTGUN, (int)ControllerInputs::INPUT_SELECT_WEAPON_HEAVY, (int)ControllerInputs::INPUT_SELECT_WEAPON_SPECIAL, (int)ControllerInputs::INPUT_SELECT_WEAPON_HANDGUN};
static void resolve_weapon_hotkey(Hash weapon) static void resolve_weapon_hotkey(Hash weapon)
{ {
@ -37,7 +37,14 @@ namespace big
PAD::DISABLE_CONTROL_ACTION(0, input_array[iterator_keys], FALSE); PAD::DISABLE_CONTROL_ACTION(0, input_array[iterator_keys], FALSE);
if (PAD::IS_DISABLED_CONTROL_JUST_PRESSED(0, input_array[iterator_keys])) if (PAD::IS_DISABLED_CONTROL_JUST_PRESSED(0, input_array[iterator_keys]))
{ {
auto hotkey_vector = g.weapons.weapon_hotkeys[iterator_keys]; if (!g.weapons.weapon_hotkeys.count(iterator_keys))
continue;
auto& hotkey_vector = g.weapons.weapon_hotkeys[iterator_keys];
if (hotkey_vector.empty())
continue;
Hash weapon_hash_to_select = hotkey_vector[0]; Hash weapon_hash_to_select = hotkey_vector[0];
for (auto vector_iterator = hotkey_vector.begin(); vector_iterator != hotkey_vector.end(); ++vector_iterator) for (auto vector_iterator = hotkey_vector.begin(); vector_iterator != hotkey_vector.end(); ++vector_iterator)
{ {

View File

@ -230,9 +230,15 @@ namespace big
struct player_db struct player_db
{ {
bool update_player_online_states = false; bool update_player_online_states = true;
bool notify_when_online = false;
bool notify_when_joinable = true;
bool notify_when_unjoinable = false;
bool notify_when_offline = false;
bool notify_on_session_type_change = false;
bool notify_on_session_change = false;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(player_db, update_player_online_states) NLOHMANN_DEFINE_TYPE_INTRUSIVE(player_db, update_player_online_states, notify_when_online, notify_when_joinable, notify_when_unjoinable, notify_when_offline, notify_on_session_type_change, notify_on_session_change)
} player_db{}; } player_db{};
struct protections struct protections

View File

@ -98,7 +98,7 @@ namespace big::functions
using get_gamer_online_state = bool (*)(int profile_index, rage::rlGamerHandle* handles, std::uint32_t count, int* online_state, rage::rlTaskStatus* status); using get_gamer_online_state = bool (*)(int profile_index, rage::rlGamerHandle* handles, std::uint32_t count, int* online_state, rage::rlTaskStatus* status);
using start_get_session_by_gamer_handle = bool (*)(int profile_index, rage::rlGamerHandle* handles, int count, rage::rlSessionByGamerTaskResult* result, int unk, bool* success, rage::rlTaskStatus* state); using start_get_session_by_gamer_handle = bool (*)(int profile_index, rage::rlGamerHandle* handles, int count, rage::rlSessionByGamerTaskResult* result, int unk, bool* success, rage::rlTaskStatus* state);
using start_matchmaking_find_sessions = bool (*)(int profile_index, int available_slots, NetworkGameFilterMatchmakingComponent* m_filter, unsigned int max_sessions, rage::rlSessionInfo* result_sessions, int* result_session_count, rage::rlTaskStatus* state); using start_matchmaking_find_sessions = bool (*)(int profile_index, int available_slots, NetworkGameFilterMatchmakingComponent* m_filter, unsigned int max_sessions, rage::rlSessionInfo* result_sessions, int* result_session_count, rage::rlTaskStatus* state);
using start_get_presence_attributes = bool (*)(int profile_index, rage::rlScHandle* handle, rage::rlQueryPresenceAttributesContext* contexts, int count, rage::rlTaskStatus* state); using start_get_presence_attributes = bool (*)(int profile_index, rage::rlScHandle* handle, int num_handles, rage::rlQueryPresenceAttributesContext** contexts, int count, rage::rlTaskStatus* state);
using join_session_by_info = bool (*)(Network* network, rage::rlSessionInfo* info, int unk, int flags, rage::rlGamerHandle* handles, int handlecount); using join_session_by_info = bool (*)(Network* network, rage::rlSessionInfo* info, int unk, int flags, rage::rlGamerHandle* handles, int handlecount);
using generate_uuid = bool (*)(std::uint64_t* uuid); using generate_uuid = bool (*)(std::uint64_t* uuid);

View File

@ -1948,3 +1948,19 @@ enum class eCombatAbilityLevel
}; };
NLOHMANN_JSON_SERIALIZE_ENUM(eCombatAbilityLevel, {{eCombatAbilityLevel::POOR, "poor"}, {eCombatAbilityLevel::AVERAGE, "average"}, {eCombatAbilityLevel::PROFESSIONAL, "professional"}}) NLOHMANN_JSON_SERIALIZE_ENUM(eCombatAbilityLevel, {{eCombatAbilityLevel::POOR, "poor"}, {eCombatAbilityLevel::AVERAGE, "average"}, {eCombatAbilityLevel::PROFESSIONAL, "professional"}})
enum class GSType : int32_t
{
Unknown = -2,
// actual values start here
Invalid = -1,
InviteOnly,
FriendsOnly,
ClosedCrew,
OpenCrew,
Job,
Public,
Max,
Modder = 69 // stand?
};

View File

@ -1,4 +1,5 @@
#include "hooking.hpp" #include "hooking.hpp"
#include "services/players/player_service.hpp"
#include "util/notify.hpp" #include "util/notify.hpp"
namespace big namespace big
@ -11,6 +12,11 @@ namespace big
return true; return true;
} }
auto plyr = g_player_service->get_by_id(src->m_player_id);
if (plyr && plyr->block_clone_create)
return true;
g.m_syncing_player = src; g.m_syncing_player = src;
return g_hooking->get_original<hooks::received_clone_create>()(mgr, src, dst, object_type, object_id, object_flag, buffer, timestamp); return g_hooking->get_original<hooks::received_clone_create>()(mgr, src, dst, object_type, object_id, object_flag, buffer, timestamp);
} }

View File

@ -1,4 +1,5 @@
#include "hooking.hpp" #include "hooking.hpp"
#include "services/players/player_service.hpp"
#include "util/notify.hpp" #include "util/notify.hpp"
namespace big namespace big
@ -17,6 +18,11 @@ namespace big
return eAckCode::ACKCODE_FAIL; return eAckCode::ACKCODE_FAIL;
} }
auto plyr = g_player_service->get_by_id(src->m_player_id);
if (plyr && plyr->block_clone_create)
return eAckCode::ACKCODE_FAIL;
g.m_syncing_player = src; g.m_syncing_player = src;
return g_hooking->get_original<received_clone_sync>()(mgr, src, dst, object_type, object_id, buffer, unk, timestamp); return g_hooking->get_original<received_clone_sync>()(mgr, src, dst, object_type, object_id, buffer, unk, timestamp);
} }

View File

@ -368,6 +368,12 @@ namespace big
auto plyr = g_player_service->get_by_id(source_player->m_player_id); auto plyr = g_player_service->get_by_id(source_player->m_player_id);
if (plyr && plyr->block_net_events)
{
g_pointers->m_gta.m_send_event_ack(event_manager, source_player, target_player, event_index, event_handled_bitset);
return;
}
switch (static_cast<eNetworkEvents>(event_id)) switch (static_cast<eNetworkEvents>(event_id))
{ {
case eNetworkEvents::KICK_VOTES_EVENT: case eNetworkEvents::KICK_VOTES_EVENT:

View File

@ -1389,7 +1389,7 @@ namespace big
// Start Get Presence Attributes // Start Get Presence Attributes
{ {
"SGPA", "SGPA",
"48 8B C4 48 89 58 08 48 89 68 10 48 89 70 18 48 89 78 20 41 54 41 56 41 57 48 83 EC 40 33 DB 41", "48 8B C4 48 89 58 08 48 89 68 10 48 89 70 18 48 89 78 20 41 54 41 56 41 57 48 83 EC 40 33 DB 49",
[](memory::handle ptr) [](memory::handle ptr)
{ {
g_pointers->m_sc.m_start_get_presence_attributes = ptr.as<functions::start_get_presence_attributes>(); g_pointers->m_sc.m_start_get_presence_attributes = ptr.as<functions::start_get_presence_attributes>();

View File

@ -35,15 +35,10 @@ namespace nlohmann
}; };
} }
enum class GSType : int32_t;
namespace big namespace big
{ {
enum class PlayerOnlineStatus
{
UNKNOWN,
OFFLINE,
ONLINE
};
struct persistent_player struct persistent_player
{ {
std::string name; std::string name;
@ -55,7 +50,8 @@ namespace big
std::unordered_set<int> infractions; std::unordered_set<int> infractions;
std::string notes = ""; std::string notes = "";
std::optional<CommandAccessLevel> command_access_level = std::nullopt; std::optional<CommandAccessLevel> command_access_level = std::nullopt;
PlayerOnlineStatus online_state = PlayerOnlineStatus::UNKNOWN; GSType session_type = GSType(-2);
int64_t session_id = -1;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(persistent_player, name, rockstar_id, block_join, block_join_reason, is_modder, notify_online, infractions, notes, command_access_level) NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(persistent_player, name, rockstar_id, block_join, block_join_reason, is_modder, notify_online, infractions, notes, command_access_level)
}; };

View File

@ -2,14 +2,43 @@
#include "backend/bool_command.hpp" #include "backend/bool_command.hpp"
#include "file_manager.hpp" #include "file_manager.hpp"
#include "gta/enums.hpp"
#include "pointers.hpp" #include "pointers.hpp"
#include "util/session.hpp" #include "util/session.hpp"
namespace big namespace big
{ {
bool_command g_player_db_auto_update_online_states("player_db_auto_update_states", "Auto Update Player Online States", "Toggling this feature will automatically update the player online states every 5minutes.", bool_command g_player_db_auto_update_online_states("player_db_auto_update_states", "Auto Update Tracked Player States", "Toggling this feature will automatically update the tracked players' online states every minute",
g.player_db.update_player_online_states); g.player_db.update_player_online_states);
void player_database_service::handle_session_type_change(persistent_player& player, GSType new_session_type)
{
if (!player.notify_online)
return;
if (g.player_db.notify_when_joinable && !is_joinable_session(player.session_type) && is_joinable_session(new_session_type))
{
g_notification_service->push_success("Player DB", std::format("{} is now in a joinable session", player.name));
}
else if (g.player_db.notify_when_online && (player.session_type == GSType::Invalid || player.session_type == GSType::Unknown) && new_session_type != GSType::Invalid)
{
g_notification_service->push_success("Player DB", std::format("{} is now online", player.name));
}
else if (g.player_db.notify_when_unjoinable && is_joinable_session(player.session_type) && !is_joinable_session(new_session_type) && new_session_type != GSType::Invalid)
{
g_notification_service->push("Player DB", std::format("{} is no longer in a joinable session", player.name));
}
else if (g.player_db.notify_when_offline && player.session_type != GSType::Invalid && player.session_type != GSType::Unknown && new_session_type == GSType::Invalid)
{
g_notification_service->push("Player DB", std::format("{} is no longer online", player.name));
}
if (g.player_db.notify_on_session_type_change && (int)new_session_type >= (int)GSType::InviteOnly && (int)new_session_type < (int)GSType::Max)
{
g_notification_service->push("Player DB", std::format("{} is now in a{} {} session", player.name, new_session_type == GSType::InviteOnly ? "n" : "", get_session_type_str(new_session_type)));
}
}
player_database_service::player_database_service() : player_database_service::player_database_service() :
m_file_path(g_file_manager.get_project_file("./players.json").get_path()) m_file_path(g_file_manager.get_project_file("./players.json").get_path())
{ {
@ -150,14 +179,14 @@ namespace big
return; return;
g_thread_pool->push([this] { g_thread_pool->push([this] {
static auto last_update = std::chrono::high_resolution_clock::now() - 5min; static auto last_update = std::chrono::high_resolution_clock::now() - 45s;
while (g_running && g.player_db.update_player_online_states) while (g_running && g.player_db.update_player_online_states)
{ {
const auto cur = std::chrono::high_resolution_clock::now(); const auto cur = std::chrono::high_resolution_clock::now();
if (cur - last_update > 5min) if (cur - last_update > 45s)
{ {
g_fiber_pool->queue_job([this] { g_fiber_pool->queue_job([this] {
update_player_states(); update_player_states(true);
}); });
last_update = cur; last_update = cur;
} }
@ -167,52 +196,105 @@ namespace big
}); });
} }
void player_database_service::update_player_states() void player_database_service::update_player_states(bool tracked_only)
{ {
const auto player_count = m_players.size(); const auto player_count = m_players.size();
constexpr auto bucket_size = 100;
std::vector<std::vector<rage::rlGamerHandle>> gamer_handle_buckets; std::vector<std::vector<rage::rlScHandle>> gamer_handle_buckets;
gamer_handle_buckets.resize(std::ceil(player_count / 32.f)); gamer_handle_buckets.resize(std::ceil(player_count / (float)bucket_size));
auto it = m_players.begin(); size_t i = 0;
for (size_t i = 0; i < player_count; ++i) for (auto& player : m_players)
{ {
gamer_handle_buckets[i / 32].push_back(it->second->rockstar_id); if (!tracked_only || player.second->notify_online)
{
it++; gamer_handle_buckets[i / bucket_size].push_back(player.second->rockstar_id);
i++;
}
} }
if (i == 0)
return;
for (auto& bucket : gamer_handle_buckets) for (auto& bucket : gamer_handle_buckets)
{ {
rage::rlTaskStatus status; rage::rlTaskStatus status{};
std::array<int, 32> online; rage::rlQueryPresenceAttributesContext contexts[bucket_size][2]{};
rage::rlQueryPresenceAttributesContext* contexts_per_player[bucket_size]{};
if (g_pointers->m_gta.m_get_gamer_online_state(0, bucket.data(), bucket.size(), online.data(), &status)) for (int i = 0; i < bucket_size; i++)
{
contexts[i][0].m_presence_attibute_type = 3;
strcpy(contexts[i][0].m_presence_attribute_key, "gstype");
strcpy(contexts[i][0].m_presence_attribute_value, "-1");
contexts[i][1].m_presence_attibute_type = 3;
strcpy(contexts[i][1].m_presence_attribute_key, "gsinfo");
contexts_per_player[i] = contexts[i];
}
if (g_pointers->m_sc.m_start_get_presence_attributes(0, bucket.data(), bucket.size(), contexts_per_player, 2, &status))
{ {
while (status.status == 1) while (status.status == 1)
{ {
script::get_current()->yield(); script::get_current()->yield();
} }
for (size_t i = 0; i < bucket.size(); ++i) if (status.status == 3)
{ {
if (const auto& it = m_players.find(bucket[i].m_rockstar_id); it != m_players.end()) for (size_t i = 0; i < bucket.size(); ++i)
{ {
if (online[i] == 1) if (const auto& it = m_players.find(bucket[i].m_rockstar_id); it != m_players.end())
{ {
if (it->second->online_state == PlayerOnlineStatus::OFFLINE && it->second->notify_online) rage::rlSessionInfo info{};
{ info.m_session_token = -1;
g_notification_service->push_success("Player DB", GSType gstype = (GSType)atoi(contexts[i][0].m_presence_attribute_value);
std::format("{} is now online!", it->second->name));
}
it->second->online_state = PlayerOnlineStatus::ONLINE;
continue; if (!g_pointers->m_gta.m_decode_session_info(&info, contexts[i][1].m_presence_attribute_value, nullptr))
gstype = GSType::Invalid;
if (it->second->session_type != gstype)
{
handle_session_type_change(*it->second, gstype);
}
else if (it->second->notify_online && it->second->session_id != info.m_session_token)
{
g_notification_service->push("Player DB",
std::format("{} has joined a new session", it->second->name));
}
it->second->session_type = gstype;
it->second->session_id = info.m_session_token;
} }
it->second->online_state = PlayerOnlineStatus::OFFLINE;
} }
} }
else
{
LOG(WARNING) << "Presence attribute endpoint failed";
}
} }
} }
} }
bool player_database_service::is_joinable_session(GSType type)
{
return type == GSType::Public || type == GSType::OpenCrew;
}
const char* player_database_service::get_session_type_str(GSType type)
{
switch (type)
{
case GSType::Invalid: return "Offline";
case GSType::InviteOnly: return "Invite Only";
case GSType::FriendsOnly: return "Friends Only";
case GSType::ClosedCrew: return "Closed Crew";
case GSType::OpenCrew: return "Crew";
case GSType::Job: return "In Mission";
case GSType::Public: return "Public";
case GSType::Modder: return "Unknown (Concealed By Modder)";
}
return "Unknown";
}
} }

View File

@ -14,7 +14,7 @@ namespace nlohmann
static void from_json(const json& j, std::shared_ptr<T>& value) static void from_json(const json& j, std::shared_ptr<T>& value)
{ {
value = std::make_shared<T>(); value = std::make_shared<T>();
*value = j.get<T>(); *value = j.get<T>();
} }
}; };
@ -28,6 +28,8 @@ namespace big
std::map<std::string, std::shared_ptr<persistent_player>> m_sorted_players; std::map<std::string, std::shared_ptr<persistent_player>> m_sorted_players;
std::shared_ptr<persistent_player> m_selected = nullptr; std::shared_ptr<persistent_player> m_selected = nullptr;
void handle_session_type_change(persistent_player& player, GSType new_session_type);
public: public:
std::filesystem::path m_file_path; std::filesystem::path m_file_path;
player_database_service(); player_database_service();
@ -46,9 +48,12 @@ namespace big
void set_selected(std::shared_ptr<persistent_player> selected); void set_selected(std::shared_ptr<persistent_player> selected);
std::shared_ptr<persistent_player> get_selected(); std::shared_ptr<persistent_player> get_selected();
void start_update_loop(); void start_update_loop();
void update_player_states(); void update_player_states(bool tracked_only = false);
static bool is_joinable_session(GSType type);
static const char* get_session_type_str(GSType type);
}; };
inline player_database_service* g_player_database_service; inline player_database_service* g_player_database_service;

View File

@ -83,7 +83,12 @@ namespace big
std::optional<std::uint32_t> time_difference; std::optional<std::uint32_t> time_difference;
std::uint32_t num_time_syncs_sent = 9999; std::uint32_t num_time_syncs_sent = 9999;
bool block_explosions = false; bool block_explosions = false;
bool block_clone_create = false;
bool block_clone_sync = false;
bool block_net_events = false;
bool log_clones = false;
bool log_network_events = false;
int spectating_player = -1; int spectating_player = -1;

View File

@ -11,6 +11,8 @@ namespace
"www.", "www.",
".cn", ".cn",
".CN", ".CN",
".cc",
".CC",
".TOP", ".TOP",
".COM", ".COM",
".top", ".top",
@ -25,8 +27,6 @@ namespace
"doit#", "doit#",
"krutka#", "krutka#",
"<b>", "<b>",
// causes false positives for people typing in cyrillic
// "\xD0\xBC\xD0\xB5", // Cyrillic "me"
"P888", "P888",
"gtacash", "gtacash",
"\xE6\x89\xA3\xE6\x89\xA3", // no clue what this is "\xE6\x89\xA3\xE6\x89\xA3", // no clue what this is
@ -39,21 +39,22 @@ namespace
"REP +", "REP +",
"20R$", // Brazil currency? "20R$", // Brazil currency?
"l55.me", "l55.me",
"\xE5\xBA\x97", //"shop" in Chinese "\xE5\xBA\x97", //"shop" in Chinese
"\xE9\x92\xB1", //"money" in Chinese "\xE9\x92\xB1", //"money" in Chinese
"\xE5\x88\xB7", //"make(money)" in Chinese "\xE5\x88\xB7", //"make(money)" in Chinese
// disabled as it's too verbose "\xE8\x90\x9D\xE8\x8E\x89", // "cute girl" in Chinese
// "av", //uknowwhat video "\xE5\xA6\x88", // "mother" in Chinese
"\xE8\x90\x9D\xE8\x8E\x89", //"cute girl" in Chinese "\xE7\xBE\x8E\xE5\xA5\xB3", // "sexy girl" in Chinese
"\xE5\xA6\x88", //"mother" in Chinese "\xE5\xBC\xBA\xE5\xA5\xB8", // "rape" in Chinese
"\xE7\xBE\x8E\xE5\xA5\xB3", //"sexy girl" in Chinese "\xE8\x90\x9D", // "loli" in Chinese
"\xE5\xBC\xBA\xE5\xA5\xB8", //"rape" in Chinese "\xE6\x8C\x82", // "hack" in Chinese
"\xE8\x90\x9D", //"loli" in Chinese "\xE5\x85\x83", // chinese dollar
"\xE6\x8C\x82", //"hack" in Chinese "\xE9\x98\xB4\xE4\xBC\xA0\xE5\xAA\x92", // "Yin Media" in Chinese
"\xE5\x85\x83", //chinese dollar "\xE7\xBD\x91\xE7\xBA\xA2", // "internet celebrities" in Chinese
"TRUSTPILOT", "TRUSTPILOT",
"cashlounge", "cashlounge",
"Fast Delivery", "Fast Delivery",
"yosativa",
}; };
} }

View File

@ -249,6 +249,7 @@ namespace big
global_test.global_appendages.clear(); global_test.global_appendages.clear();
} }
ImGui::EndGroup(); ImGui::EndGroup();
ImGui::EndTabItem();
} }
} }
} }

View File

@ -47,8 +47,10 @@ namespace big
ImGui::SameLine(); ImGui::SameLine();
components::button("JOIN_SESSION_INFO"_T, [] { components::button("JOIN_SESSION_INFO"_T, [] {
rage::rlSessionInfo info; rage::rlSessionInfo info;
g_pointers->m_gta.m_decode_session_info(&info, base64, nullptr); if (g_pointers->m_gta.m_decode_session_info(&info, base64, nullptr))
session::join_session(info); session::join_session(info);
else
g_notification_service->push_error("Join", "Session info is invalid");
}); });
components::button("COPY_SESSION_INFO"_T, [] { components::button("COPY_SESSION_INFO"_T, [] {

View File

@ -2,6 +2,7 @@
#include "core/data/command_access_levels.hpp" #include "core/data/command_access_levels.hpp"
#include "core/data/infractions.hpp" #include "core/data/infractions.hpp"
#include "fiber_pool.hpp" #include "fiber_pool.hpp"
#include "gta/enums.hpp"
#include "pointers.hpp" #include "pointers.hpp"
#include "services/api/api_service.hpp" #include "services/api/api_service.hpp"
#include "services/player_database/player_database_service.hpp" #include "services/player_database/player_database_service.hpp"
@ -17,6 +18,18 @@ namespace big
bool notes_dirty = false; bool notes_dirty = false;
std::shared_ptr<persistent_player> current_player; std::shared_ptr<persistent_player> current_player;
ImVec4 get_player_color(persistent_player& player)
{
if (player.session_type == GSType::Unknown)
return ImVec4(.5f, .5f, .5f, 1.0f);
else if (player.session_type == GSType::Invalid)
return ImVec4(1.f, 0.f, 0.f, 1.f);
else if (!player_database_service::is_joinable_session(player.session_type))
return ImVec4(1.f, 1.f, 0.f, 1.f);
else
return ImVec4(0.f, 1.f, 0.f, 1.f);
}
void draw_player_db_entry(std::shared_ptr<persistent_player> player, const std::string& lower_search) void draw_player_db_entry(std::shared_ptr<persistent_player> player, const std::string& lower_search)
{ {
std::string name = player->name; std::string name = player->name;
@ -28,15 +41,9 @@ namespace big
float circle_size = 7.5f; float circle_size = 7.5f;
auto cursor_pos = ImGui::GetCursorScreenPos(); auto cursor_pos = ImGui::GetCursorScreenPos();
auto plyr_state = player->online_state;
//render status circle //render status circle
ImGui::GetWindowDrawList()->AddCircleFilled(ImVec2(cursor_pos.x + 4.f + circle_size, cursor_pos.y + 4.f + circle_size), ImGui::GetWindowDrawList()->AddCircleFilled(ImVec2(cursor_pos.x + 4.f + circle_size, cursor_pos.y + 4.f + circle_size), circle_size, ImColor(get_player_color(*player)));
circle_size,
ImColor(plyr_state == PlayerOnlineStatus::ONLINE ? ImVec4(0.f, 1.f, 0.f, 1.f) :
plyr_state == PlayerOnlineStatus::OFFLINE ? ImVec4(1.f, 0.f, 0.f, 1.f) :
plyr_state == PlayerOnlineStatus::UNKNOWN ? ImVec4(.5f, .5f, .5f, 1.0f) :
ImVec4(.5f, .5f, .5f, 1.0f)));
//we need some padding //we need some padding
ImVec2 cursor = ImGui::GetCursorPos(); ImVec2 cursor = ImGui::GetCursorPos();
@ -57,6 +64,9 @@ namespace big
strncpy(note_buffer, current_player->notes.data(), sizeof(note_buffer)); strncpy(note_buffer, current_player->notes.data(), sizeof(note_buffer));
} }
if (ImGui::IsItemHovered())
ImGui::SetTooltip(player_database_service::get_session_type_str(player->session_type));
ImGui::PopID(); ImGui::PopID();
} }
} }
@ -76,13 +86,20 @@ namespace big
for (auto& player : item_arr | std::ranges::views::values) for (auto& player : item_arr | std::ranges::views::values)
{ {
if (player->online_state == PlayerOnlineStatus::ONLINE) if (player_database_service::is_joinable_session(player->session_type))
draw_player_db_entry(player, lower_search); draw_player_db_entry(player, lower_search);
} }
for (auto& player : item_arr | std::ranges::views::values) for (auto& player : item_arr | std::ranges::views::values)
{ {
if (player->online_state != PlayerOnlineStatus::ONLINE) if (!player_database_service::is_joinable_session(player->session_type) && player->session_type != GSType::Invalid
&& player->session_type != GSType::Unknown)
draw_player_db_entry(player, lower_search);
}
for (auto& player : item_arr | std::ranges::views::values)
{
if (player->session_type == GSType::Invalid || player->session_type == GSType::Unknown)
draw_player_db_entry(player, lower_search); draw_player_db_entry(player, lower_search);
} }
} }
@ -107,7 +124,7 @@ namespace big
if (ImGui::InputScalar("RID"_T.data(), ImGuiDataType_S64, &current_player->rockstar_id) if (ImGui::InputScalar("RID"_T.data(), ImGuiDataType_S64, &current_player->rockstar_id)
|| ImGui::Checkbox("IS_MODDER"_T.data(), &current_player->is_modder) || ImGui::Checkbox("IS_MODDER"_T.data(), &current_player->is_modder)
|| ImGui::Checkbox("BLOCK_JOIN"_T.data(), &current_player->block_join) || ImGui::Checkbox("BLOCK_JOIN"_T.data(), &current_player->block_join)
|| ImGui::Checkbox("Notify When Online", &current_player->notify_online)) || ImGui::Checkbox("Track Player", &current_player->notify_online))
{ {
if (current_player->rockstar_id != selected->rockstar_id) if (current_player->rockstar_id != selected->rockstar_id)
g_player_database_service->update_rockstar_id(selected->rockstar_id, current_player->rockstar_id); g_player_database_service->update_rockstar_id(selected->rockstar_id, current_player->rockstar_id);
@ -212,10 +229,28 @@ namespace big
if (ImGui::Button("REMOVE_ALL"_T.data())) if (ImGui::Button("REMOVE_ALL"_T.data()))
{ {
g_player_database_service->set_selected(nullptr); ImGui::OpenPopup("##removeall");
g_player_database_service->get_players().clear(); }
g_player_database_service->get_sorted_players().clear();
g_player_database_service->save(); if (ImGui::BeginPopupModal("##removeall"))
{
ImGui::Text("Are you sure?");
if (ImGui::Button("Yes"))
{
g_player_database_service->set_selected(nullptr);
g_player_database_service->get_players().clear();
g_player_database_service->get_sorted_players().clear();
g_player_database_service->save();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("No"))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
} }
ImGui::SameLine(); ImGui::SameLine();
@ -224,8 +259,18 @@ namespace big
g_player_database_service->update_player_states(); g_player_database_service->update_player_states();
}); });
if (components::command_checkbox<"player_db_auto_update_states">()) if (ImGui::TreeNode("Player Tracking"))
g_player_database_service->start_update_loop(); {
if (components::command_checkbox<"player_db_auto_update_states">("Enable"))
g_player_database_service->start_update_loop();
ImGui::Checkbox("Notify When Online", &g.player_db.notify_when_online);
ImGui::Checkbox("Notify When Joinable", &g.player_db.notify_when_joinable);
ImGui::Checkbox("Notify When Unjoinable", &g.player_db.notify_when_unjoinable);
ImGui::Checkbox("Notify When Offline", &g.player_db.notify_when_offline);
ImGui::Checkbox("Notify On Session Type Change", &g.player_db.notify_on_session_type_change);
ImGui::Checkbox("Notify On Session Change", &g.player_db.notify_on_session_change);
ImGui::TreePop();
}
ImGui::Separator(); ImGui::Separator();
components::sub_title("NEW_ENTRY"_T); components::sub_title("NEW_ENTRY"_T);
@ -244,7 +289,7 @@ namespace big
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("SEARCH"_T.data())) if (ImGui::Button("SEARCH"_T.data()))
{ {
g_thread_pool->push([]{ g_thread_pool->push([] {
if (!g_api_service->get_rid_from_username(new_name, *(uint64_t*)&new_rockstar_id)) if (!g_api_service->get_rid_from_username(new_name, *(uint64_t*)&new_rockstar_id))
{ {
g_notification_service->push_error("New Player DB Entry", std::format("No user '{}' called could be found.", new_name)); g_notification_service->push_error("New Player DB Entry", std::format("No user '{}' called could be found.", new_name));

View File

@ -40,11 +40,11 @@ namespace big
if (id != -1) if (id != -1)
{ {
auto& stats = scr_globals::gpbd_fm_1.as<GPBD_FM*>()->Entries[id].PlayerStats; auto& stats = scr_globals::gpbd_fm_1.as<GPBD_FM*>()->Entries[id].PlayerStats;
auto& boss_goon = scr_globals::gpbd_fm_3.as<GPBD_FM_3*>()->Entries[id].BossGoon; auto& boss_goon = scr_globals::gpbd_fm_3.as<GPBD_FM_3*>()->Entries[id].BossGoon;
const auto money = reinterpret_cast<uint64_t&>(stats.Money); const auto money = reinterpret_cast<uint64_t&>(stats.Money);
const auto wallet = reinterpret_cast<uint64_t&>(stats.WalletBalance); const auto wallet = reinterpret_cast<uint64_t&>(stats.WalletBalance);
if (boss_goon.Language >= 0 && boss_goon.Language < 13) if (boss_goon.Language >= 0 && boss_goon.Language < 13)
ImGui::Text("PLAYER_INFO_LANGUAGE"_T.data(), languages[boss_goon.Language].name); ImGui::Text("PLAYER_INFO_LANGUAGE"_T.data(), languages[boss_goon.Language].name);
@ -69,6 +69,11 @@ namespace big
} }
ImGui::Checkbox("Block Explosions", &g_player_service->get_selected()->block_explosions); ImGui::Checkbox("Block Explosions", &g_player_service->get_selected()->block_explosions);
ImGui::Checkbox("Block Clone Creates", &g_player_service->get_selected()->block_clone_create);
ImGui::Checkbox("Block Clone Syncs", &g_player_service->get_selected()->block_clone_sync);
ImGui::Checkbox("Block Network Events", &g_player_service->get_selected()->block_net_events);
ImGui::Separator();
if (ImGui::BeginCombo("CHAT_COMMAND_PERMISSIONS"_T.data(), if (ImGui::BeginCombo("CHAT_COMMAND_PERMISSIONS"_T.data(),
COMMAND_ACCESS_LEVELS[g_player_service->get_selected()->command_access_level.value_or( COMMAND_ACCESS_LEVELS[g_player_service->get_selected()->command_access_level.value_or(

View File

@ -3,12 +3,12 @@
#include "core/data/special_ammo_types.hpp" #include "core/data/special_ammo_types.hpp"
#include "fiber_pool.hpp" #include "fiber_pool.hpp"
#include "gta/joaat.hpp" #include "gta/joaat.hpp"
#include "gta/weapons.hpp"
#include "natives.hpp" #include "natives.hpp"
#include "pointers.hpp" #include "pointers.hpp"
#include "services/gta_data/gta_data_service.hpp" #include "services/gta_data/gta_data_service.hpp"
#include "views/view.hpp"
#include "services/persist_weapons/persist_weapons.hpp" #include "services/persist_weapons/persist_weapons.hpp"
#include "gta/weapons.hpp" #include "views/view.hpp"
namespace big namespace big
{ {
@ -274,7 +274,7 @@ namespace big
} }
if (ImGui::CollapsingHeader("Persist Weapons")) if (ImGui::CollapsingHeader("Persist Weapons"))
{ {
ImGui::Checkbox("Enabled", &g.persist_weapons.enabled); ImGui::Checkbox("Enabled##persist_weapons", &g.persist_weapons.enabled);
static std::string selected_loadout = g.persist_weapons.weapon_loadout_file; static std::string selected_loadout = g.persist_weapons.weapon_loadout_file;
ImGui::PushItemWidth(250); ImGui::PushItemWidth(250);
@ -311,14 +311,14 @@ namespace big
} }
if (ImGui::CollapsingHeader("Weapon Hotkeys")) if (ImGui::CollapsingHeader("Weapon Hotkeys"))
{ {
ImGui::Checkbox("Enabled", &g.weapons.enable_weapon_hotkeys); ImGui::Checkbox("Enabled##weapon_hotkeys", &g.weapons.enable_weapon_hotkeys);
if (ImGui::IsItemHovered()) if (ImGui::IsItemHovered())
{ {
ImGui::SetTooltip("This will select the next weapon in the hotkey list.\r\nThe first weapon in the list is the first weapon it will select, then the second is the one it will select after and so on.\r\nAfter the end of the list, it will wrap back to the first weapon."); ImGui::SetTooltip("This will select the next weapon in the hotkey list.\r\nThe first weapon in the list is the first weapon it will select, then the second is the one it will select after and so on.\r\nAfter the end of the list, it will wrap back to the first weapon.");
} }
static int selected_key = 0; static int selected_key = 0;
const char* const keys[]{ "1", "2", "3", "4", "5", "6" }; const char* const keys[]{"1", "2", "3", "4", "5", "6"};
ImGui::PushItemWidth(250); ImGui::PushItemWidth(250);
ImGui::Combo("Key", &selected_key, keys, IM_ARRAYSIZE(keys)); ImGui::Combo("Key", &selected_key, keys, IM_ARRAYSIZE(keys));