refactor(PlayerDB): Improve code (#1313)

This PR includes some improvements to the player DB service:
 - Added `Get Gamer Online State` function pointer
 - Added sorting of players alphabetically and grouping of players by their online state
 - The player DB service will now update 32 players at a time for their online state
 - Player entries will automatically save when changing any data from them
 - Update the player online states every 5min
This commit is contained in:
Andreas Maerten 2023-05-01 23:23:07 +02:00 committed by GitHub
parent eede8978d7
commit 71892a6fa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 324 additions and 177 deletions

View File

@ -213,6 +213,13 @@ namespace big
NLOHMANN_DEFINE_TYPE_INTRUSIVE(player, character_slot, spectating) NLOHMANN_DEFINE_TYPE_INTRUSIVE(player, character_slot, spectating)
} player{}; } player{};
struct player_db
{
bool update_player_online_states = false;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(player_db, update_player_online_states)
} player_db{};
struct protections struct protections
{ {
struct script_events struct script_events
@ -862,7 +869,7 @@ namespace big
NLOHMANN_DEFINE_TYPE_INTRUSIVE(stat_editor, stat, packed_stat) NLOHMANN_DEFINE_TYPE_INTRUSIVE(stat_editor, stat, packed_stat)
} stat_editor{}; } stat_editor{};
NLOHMANN_DEFINE_TYPE_INTRUSIVE(menu_settings, debug, tunables, notifications, player, protections, self, session, settings, spawn_vehicle, clone_pv, spoofing, vehicle, weapons, window, context_menu, esp, session_browser, ugc, reactions, world, stat_editor) NLOHMANN_DEFINE_TYPE_INTRUSIVE(menu_settings, debug, tunables, notifications, player, player_db, protections, self, session, settings, spawn_vehicle, clone_pv, spoofing, vehicle, weapons, window, context_menu, esp, session_browser, ugc, reactions, world, stat_editor)
}; };
inline auto g = menu_settings(); inline auto g = menu_settings();

View File

@ -91,6 +91,7 @@ namespace big::functions
using fipackfile_mount = bool (*)(rage::fiPackfile* this_, const char* mount_point); using fipackfile_mount = bool (*)(rage::fiPackfile* this_, const char* mount_point);
using fipackfile_unmount = bool (*)(const char* mount_point); using fipackfile_unmount = bool (*)(const char* mount_point);
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, rage::rlQueryPresenceAttributesContext* contexts, int count, rage::rlTaskStatus* state);

View File

@ -141,6 +141,7 @@ namespace big
Network** m_network; Network** m_network;
functions::get_gamer_online_state m_get_gamer_online_state;
functions::start_get_session_by_gamer_handle m_start_get_session_by_gamer_handle; functions::start_get_session_by_gamer_handle m_start_get_session_by_gamer_handle;
functions::start_matchmaking_find_sessions m_start_matchmaking_find_sessions; functions::start_matchmaking_find_sessions m_start_matchmaking_find_sessions;
functions::join_session_by_info m_join_session_by_info; functions::join_session_by_info m_join_session_by_info;

View File

@ -60,16 +60,22 @@ namespace big
} }
template<template_str cmd_str> template<template_str cmd_str>
static void command_checkbox(std::optional<const std::string_view> label_override = std::nullopt) static bool command_checkbox(std::optional<const std::string_view> label_override = std::nullopt)
{ {
static bool_command* command = dynamic_cast<bool_command*>(command::get(rage::consteval_joaat(cmd_str.value))); static bool_command* command = dynamic_cast<bool_command*>(command::get(rage::consteval_joaat(cmd_str.value)));
if (command == nullptr) if (command == nullptr)
return ImGui::Text("INVALID COMMAND"); {
ImGui::Text("INVALID COMMAND");
return false;
}
if (ImGui::Checkbox(label_override.value_or(command->get_label()).data(), &command->is_enabled())) bool updated;
if (updated = ImGui::Checkbox(label_override.value_or(command->get_label()).data(), &command->is_enabled()))
command->refresh(); command->refresh();
if (ImGui::IsItemHovered()) if (ImGui::IsItemHovered())
ImGui::SetTooltip(command->get_description().c_str()); ImGui::SetTooltip(command->get_description().c_str());
return updated;
} }
template<ImVec2 size = ImVec2(0, 0), ImVec4 color = ImVec4(0.24f, 0.23f, 0.29f, 1.00f)> template<ImVec2 size = ImVec2(0, 0), ImVec4 color = ImVec4(0.24f, 0.23f, 0.29f, 1.00f)>

View File

@ -135,6 +135,8 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID)
LOG(INFO) << "Dynamic native hooker initialized."; LOG(INFO) << "Dynamic native hooker initialized.";
g_running = true; g_running = true;
// start update loop after setting g_running to true to prevent it from exiting instantly
g_player_database_service->start_update_loop();
while (g_running) while (g_running)
std::this_thread::sleep_for(500ms); std::this_thread::sleep_for(500ms);

View File

@ -589,6 +589,15 @@ namespace big
g_pointers->m_gta.m_send_chat_message = ptr.sub(21).as<functions::send_chat_message>(); g_pointers->m_gta.m_send_chat_message = ptr.sub(21).as<functions::send_chat_message>();
} }
}, },
// Get Gamer Online State
{
"GGOS",
"48 8B 44 24 70 44 8B CD 4D 8B C6 41 8B D7 48 8B CF 48 89 47 40",
[](memory::handle ptr)
{
g_pointers->m_gta.m_get_gamer_online_state = ptr.sub(0x40).as<functions::get_gamer_online_state>();
}
},
// Start Get Session By Gamer Handle // Start Get Session By Gamer Handle
{ {
"SGSBGH", "SGSBGH",

View File

@ -1,53 +1,61 @@
#pragma once #pragma once
#include "core/data/infractions.hpp" #include "core/data/infractions.hpp"
#include "json_util.hpp" #include "json_util.hpp"
#include <unordered_set> #include <unordered_set>
namespace big namespace nlohmann
{ {
enum class PlayerOnlineStatus template<typename T>
{ struct adl_serializer<std::optional<T>>
UNKNOWN, {
OFFLINE, static void to_json(json& j, const std::optional<big::CommandAccessLevel>& opt)
ONLINE {
}; if (opt == std::nullopt)
{
struct persistent_player j = nullptr;
{ }
std::string name; else
std::uint64_t rockstar_id = 0; {
bool block_join = false; j = *opt;
int block_join_reason = 1; }
bool is_modder = false; }
std::unordered_set<int> infractions;
std::optional<CommandAccessLevel> command_access_level = std::nullopt; static void from_json(const json& j, std::optional<big::CommandAccessLevel>& opt)
PlayerOnlineStatus online_state = PlayerOnlineStatus::UNKNOWN; {
}; if (j.is_null())
{
static void to_json(nlohmann::json& j, const persistent_player& player) opt = std::nullopt;
{ }
j = nlohmann::json{{"name", player.name}, else
{"rockstar_id", player.rockstar_id}, {
{"block_join", player.block_join}, opt = j.get<big::CommandAccessLevel>();
{"block_join_reason", player.block_join_reason}, }
{"is_modder", player.is_modder}, }
{"infractions", player.infractions}}; };
}
if (player.command_access_level.has_value())
j["command_access_level"] = player.command_access_level.value(); namespace big
}; {
enum class PlayerOnlineStatus
static void from_json(const nlohmann::json& j, persistent_player& player) {
{ UNKNOWN,
set_from_key_or_default(j, "name", player.name); OFFLINE,
set_from_key_or_default(j, "rockstar_id", player.rockstar_id); ONLINE
set_from_key_or_default(j, "block_join", player.block_join); };
set_from_key_or_default(j, "block_join_reason", player.block_join_reason);
set_from_key_or_default(j, "is_modder", player.is_modder); struct persistent_player
set_from_key_or_default(j, "infractions", player.infractions); {
std::string name;
if (j.contains("command_access_level") && j["command_access_level"].is_string()) std::uint64_t rockstar_id = 0;
player.command_access_level = j["command_access_level"].get<CommandAccessLevel>(); bool block_join = false;
} int block_join_reason = 1;
}; bool is_modder = false;
std::unordered_set<int> infractions;
std::optional<CommandAccessLevel> command_access_level = std::nullopt;
PlayerOnlineStatus online_state = PlayerOnlineStatus::UNKNOWN;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(persistent_player, name, rockstar_id, block_join, block_join_reason, is_modder, infractions, command_access_level)
};
};

View File

@ -1,11 +1,15 @@
#include "player_database_service.hpp" #include "player_database_service.hpp"
#include "backend/bool_command.hpp"
#include "file_manager.hpp" #include "file_manager.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.",
g.player_db.update_player_online_states);
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())
{ {
@ -42,35 +46,61 @@ namespace big
file_stream >> json; file_stream >> json;
file_stream.close(); file_stream.close();
for (auto& p : json.items()) for (auto& [key, value] : json.items())
{ {
m_players[std::stoll(p.key())] = p.value().get<persistent_player>(); auto player = value.get<std::shared_ptr<persistent_player>>();
m_players[std::stoll(key)] = player;
std::string lower = player->name;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
m_sorted_players[lower] = player;
} }
} }
} }
std::unordered_map<std::uint64_t, persistent_player>& player_database_service::get_players() std::unordered_map<std::uint64_t, std::shared_ptr<persistent_player>>& player_database_service::get_players()
{ {
return m_players; return m_players;
} }
persistent_player* player_database_service::get_player_by_rockstar_id(std::uint64_t rockstar_id) std::map<std::string, std::shared_ptr<persistent_player>>& player_database_service::get_sorted_players()
{
return m_sorted_players;
}
std::shared_ptr<persistent_player> player_database_service::add_player(std::int64_t rid, const std::string_view name)
{
std::string lower = name.data();
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
if (m_players.contains(rid))
{
m_sorted_players.erase(lower);
}
auto player = std::make_shared<persistent_player>(name.data(), rid);
m_players[rid] = player;
m_sorted_players[lower] = player;
return player;
}
std::shared_ptr<persistent_player> player_database_service::get_player_by_rockstar_id(std::uint64_t rockstar_id)
{ {
if (m_players.contains(rockstar_id)) if (m_players.contains(rockstar_id))
return &m_players[rockstar_id]; return m_players[rockstar_id];
return nullptr; return nullptr;
} }
persistent_player* player_database_service::get_or_create_player(player_ptr player) std::shared_ptr<persistent_player> player_database_service::get_or_create_player(player_ptr player)
{ {
if (m_players.contains(player->get_net_data()->m_gamer_handle.m_rockstar_id)) if (m_players.contains(player->get_net_data()->m_gamer_handle.m_rockstar_id))
return &m_players[player->get_net_data()->m_gamer_handle.m_rockstar_id]; return m_players[player->get_net_data()->m_gamer_handle.m_rockstar_id];
else else
{ {
m_players[player->get_net_data()->m_gamer_handle.m_rockstar_id] = {player->get_name(), auto player_ptr = add_player(player->get_net_data()->m_gamer_handle.m_rockstar_id, player->get_name());
player->get_net_data()->m_gamer_handle.m_rockstar_id};
save(); save();
return &m_players[player->get_net_data()->m_gamer_handle.m_rockstar_id]; return player_ptr;
} }
} }
@ -87,15 +117,22 @@ namespace big
if (m_selected && m_selected->rockstar_id == rockstar_id) if (m_selected && m_selected->rockstar_id == rockstar_id)
m_selected = nullptr; m_selected = nullptr;
m_players.erase(rockstar_id); if (auto it = m_players.find(rockstar_id); it != m_players.end())
{
std::string lower = it->second->name;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
m_sorted_players.erase(lower);
m_players.erase(it);
}
} }
void player_database_service::set_selected(persistent_player* selected) void player_database_service::set_selected(std::shared_ptr<persistent_player> selected)
{ {
m_selected = selected; m_selected = selected;
} }
persistent_player* player_database_service::get_selected() std::shared_ptr<persistent_player> player_database_service::get_selected()
{ {
return m_selected; return m_selected;
} }
@ -103,35 +140,70 @@ namespace big
void player_database_service::invalidate_player_states() void player_database_service::invalidate_player_states()
{ {
for (auto& item : m_players) for (auto& item : m_players)
item.second.online_state = PlayerOnlineStatus::UNKNOWN; item.second->online_state = PlayerOnlineStatus::UNKNOWN;
}
void player_database_service::start_update_loop()
{
if (!g.player_db.update_player_online_states)
return;
g_thread_pool->push([this] {
static auto last_update = std::chrono::high_resolution_clock::now() - 5min;
while (g_running && g.player_db.update_player_online_states)
{
const auto cur = std::chrono::high_resolution_clock::now();
if (cur - last_update > 5min)
{
g_fiber_pool->queue_job([this] {
update_player_states();
});
last_update = cur;
}
std::this_thread::sleep_for(1s);
}
});
} }
void player_database_service::update_player_states() void player_database_service::update_player_states()
{ {
invalidate_player_states(); invalidate_player_states();
const auto player_count = m_players.size();
//fetch current stat for each player.. this will need some time. std::vector<std::vector<rage::rlGamerHandle>> gamer_handle_buckets;
for (auto& item : m_players) gamer_handle_buckets.resize(std::ceil(player_count / 32.f));
auto it = m_players.begin();
for (size_t i = 0; i < player_count; ++i)
{ {
auto& player = item.second; gamer_handle_buckets[i / 32].push_back(it->second->rockstar_id);
rage::rlGamerHandle player_handle(player.rockstar_id);
rage::rlSessionByGamerTaskResult result;
bool success = false;
rage::rlTaskStatus state{};
if (g_pointers->m_gta.m_start_get_session_by_gamer_handle(0, &player_handle, 1, &result, 1, &success, &state)) it++;
}
for (auto& bucket : gamer_handle_buckets)
{
rage::rlTaskStatus status;
std::array<int, 32> online;
if (g_pointers->m_gta.m_get_gamer_online_state(0, bucket.data(), bucket.size(), online.data(), &status))
{ {
while (state.status == 1) while (status.status == 1)
script::get_current()->yield();
if (state.status == 3 && success)
{ {
player.online_state = PlayerOnlineStatus::ONLINE; script::get_current()->yield();
continue; }
for (size_t i = 0; i < bucket.size(); ++i)
{
if (const auto& it = m_players.find(bucket[i].m_rockstar_id); it != m_players.end())
{
it->second->online_state = PlayerOnlineStatus::OFFLINE;
if (online[i] == 1)
it->second->online_state = PlayerOnlineStatus::ONLINE;
}
} }
} }
player.online_state = PlayerOnlineStatus::OFFLINE;
} }
} }
} }

View File

@ -1,34 +1,56 @@
#pragma once #pragma once
#include "persistent_player.hpp" #include "persistent_player.hpp"
#include "services/players/player.hpp" #include "services/players/player.hpp"
namespace big namespace nlohmann
{ {
class player_database_service template<typename T>
{ struct adl_serializer<std::shared_ptr<T>>
std::unordered_map<std::uint64_t, persistent_player> m_players; {
persistent_player* m_selected = nullptr; static void to_json(json& j, const std::shared_ptr<T>& value)
{
public: j = *value;
std::filesystem::path m_file_path; }
player_database_service();
~player_database_service(); static void from_json(const json& j, std::shared_ptr<T>& value)
{
void save(); value = std::make_shared<T>();
void load(); *value = j.get<T>();
}
std::unordered_map<std::uint64_t, persistent_player>& get_players(); };
persistent_player* get_player_by_rockstar_id(std::uint64_t rockstar_id); }
persistent_player* get_or_create_player(player_ptr player);
void update_rockstar_id(std::uint64_t old, std::uint64_t _new); namespace big
void remove_rockstar_id(std::uint64_t rockstar_id); {
class player_database_service
void set_selected(persistent_player* selected); {
persistent_player* get_selected(); std::unordered_map<std::uint64_t, std::shared_ptr<persistent_player>> m_players;
std::map<std::string, std::shared_ptr<persistent_player>> m_sorted_players;
void update_player_states(); std::shared_ptr<persistent_player> m_selected = nullptr;
void invalidate_player_states();
}; public:
std::filesystem::path m_file_path;
inline player_database_service* g_player_database_service; player_database_service();
~player_database_service();
void save();
void load();
std::shared_ptr<persistent_player> add_player(std::int64_t rid, const std::string_view name);
std::unordered_map<std::uint64_t, std::shared_ptr<persistent_player>>& get_players();
std::map<std::string, std::shared_ptr<persistent_player>>& get_sorted_players();
std::shared_ptr<persistent_player> get_player_by_rockstar_id(std::uint64_t rockstar_id);
std::shared_ptr<persistent_player> get_or_create_player(player_ptr player);
void update_rockstar_id(std::uint64_t old, std::uint64_t _new);
void remove_rockstar_id(std::uint64_t rockstar_id);
void set_selected(std::shared_ptr<persistent_player> selected);
std::shared_ptr<persistent_player> get_selected();
void start_update_loop();
void update_player_states();
void invalidate_player_states();
};
inline player_database_service* g_player_database_service;
} }

View File

@ -11,60 +11,70 @@
namespace big namespace big
{ {
persistent_player current_player; char name_buf[32];
char search[64];
std::shared_ptr<persistent_player> current_player;
void draw_player_db_entry(std::shared_ptr<persistent_player> player, const std::string& lower_search)
{
std::string name = player->name;
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
if (lower_search.empty() || name.find(lower_search) != std::string::npos)
{
ImGui::PushID(player->rockstar_id);
float circle_size = 7.5f;
auto cursor_pos = ImGui::GetCursorScreenPos();
auto plyr_state = player->online_state;
//render status circle
ImGui::GetWindowDrawList()->AddCircleFilled(ImVec2(cursor_pos.x + 4.f + circle_size, cursor_pos.y + 4.f + circle_size),
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
ImVec2 cursor = ImGui::GetCursorPos();
ImGui::SetCursorPos(ImVec2(cursor.x + 25.f, cursor.y));
if (components::selectable(player->name, player == g_player_database_service->get_selected()))
{
g_player_database_service->set_selected(player);
current_player = player;
strncpy(name_buf, current_player->name.data(), sizeof(name_buf));
}
ImGui::PopID();
}
}
void view::player_database() void view::player_database()
{ {
static char name_buf[32];
static char search[64];
ImGui::SetNextItemWidth(300.f); ImGui::SetNextItemWidth(300.f);
components::input_text_with_hint("PLAYER"_T, "SEARCH"_T, search, sizeof(search), ImGuiInputTextFlags_None); components::input_text_with_hint("PLAYER"_T, "SEARCH"_T, search, sizeof(search), ImGuiInputTextFlags_None);
if (ImGui::ListBoxHeader("###players", {180, static_cast<float>(*g_pointers->m_gta.m_resolution_y - 400 - 38 * 4)})) if (ImGui::ListBoxHeader("###players", {180, static_cast<float>(*g_pointers->m_gta.m_resolution_y - 400 - 38 * 4)}))
{ {
auto& item_arr = g_player_database_service->get_players(); auto& item_arr = g_player_database_service->get_sorted_players();
if (item_arr.size() > 0) if (item_arr.size() > 0)
{ {
std::string lower_search = search; std::string lower_search = search;
std::transform(lower_search.begin(), lower_search.end(), lower_search.begin(), tolower); std::transform(lower_search.begin(), lower_search.end(), lower_search.begin(), tolower);
for (auto& item : item_arr) for (auto& player : item_arr | std::ranges::views::values)
{ {
auto& player = item.second; if (player->online_state == PlayerOnlineStatus::ONLINE)
draw_player_db_entry(player, lower_search);
}
std::string name = player.name; for (auto& player : item_arr | std::ranges::views::values)
std::transform(name.begin(), name.end(), name.begin(), ::tolower); {
if (player->online_state != PlayerOnlineStatus::ONLINE)
if (lower_search.empty() || name.find(lower_search) != std::string::npos) draw_player_db_entry(player, lower_search);
{
ImGui::PushID(item.first);
float circle_size = 7.5f;
auto cursor_pos = ImGui::GetCursorScreenPos();
auto plyr_state = player.online_state;
//render status circle
ImGui::GetWindowDrawList()->AddCircleFilled(ImVec2(cursor_pos.x + 4.f + circle_size, cursor_pos.y + 4.f + circle_size),
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
ImVec2 cursor = ImGui::GetCursorPos();
ImGui::SetCursorPos(ImVec2(cursor.x + 25.f, cursor.y));
if (components::selectable(player.name, &player == g_player_database_service->get_selected()))
{
g_player_database_service->set_selected(&player);
current_player = player;
strncpy(name_buf, current_player.name.data(), sizeof(name_buf));
}
ImGui::PopID();
}
} }
} }
else else
@ -82,23 +92,27 @@ namespace big
{ {
if (ImGui::InputText("NAME"_T.data(), name_buf, sizeof(name_buf))) if (ImGui::InputText("NAME"_T.data(), name_buf, sizeof(name_buf)))
{ {
current_player.name = name_buf; current_player->name = name_buf;
} }
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("BLOCK_JOIN"_T.data(), &current_player->block_join))
ImGui::Checkbox("IS_MODDER"_T.data(), &current_player.is_modder); {
ImGui::Checkbox("BLOCK_JOIN"_T.data(), &current_player.block_join); 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->save();
}
if (ImGui::BeginCombo("BLOCK_JOIN_ALERT"_T.data(), block_join_reasons[current_player.block_join_reason])) if (ImGui::BeginCombo("BLOCK_JOIN_ALERT"_T.data(), block_join_reasons[current_player->block_join_reason]))
{ {
for (const auto& reason : block_join_reasons) for (const auto& reason : block_join_reasons)
{ {
if (ImGui::Selectable(reason.second, reason.first == current_player.block_join_reason)) if (ImGui::Selectable(reason.second, reason.first == current_player->block_join_reason))
{ {
current_player.block_join_reason = reason.first; current_player->block_join_reason = reason.first;
g_player_database_service->save();
} }
if (reason.first == current_player.block_join_reason) if (reason.first == current_player->block_join_reason)
{ {
ImGui::SetItemDefaultFocus(); ImGui::SetItemDefaultFocus();
} }
@ -112,16 +126,17 @@ namespace big
if (ImGui::BeginCombo("CHAT_COMMAND_PERMISSIONS"_T.data(), if (ImGui::BeginCombo("CHAT_COMMAND_PERMISSIONS"_T.data(),
COMMAND_ACCESS_LEVELS[current_player.command_access_level.value_or(g.session.chat_command_default_access_level)])) COMMAND_ACCESS_LEVELS[current_player->command_access_level.value_or(g.session.chat_command_default_access_level)]))
{ {
for (const auto& [type, name] : COMMAND_ACCESS_LEVELS) for (const auto& [type, name] : COMMAND_ACCESS_LEVELS)
{ {
if (ImGui::Selectable(name, type == current_player.command_access_level.value_or(g.session.chat_command_default_access_level))) if (ImGui::Selectable(name, type == current_player->command_access_level.value_or(g.session.chat_command_default_access_level)))
{ {
current_player.command_access_level = type; current_player->command_access_level = type;
g_player_database_service->save();
} }
if (type == current_player.command_access_level.value_or(g.session.chat_command_default_access_level)) if (type == current_player->command_access_level.value_or(g.session.chat_command_default_access_level))
{ {
ImGui::SetItemDefaultFocus(); ImGui::SetItemDefaultFocus();
} }
@ -130,18 +145,18 @@ namespace big
ImGui::EndCombo(); ImGui::EndCombo();
} }
if (!current_player.infractions.empty()) if (!current_player->infractions.empty())
{ {
ImGui::Text("INFRACTIONS"_T.data()); ImGui::Text("INFRACTIONS"_T.data());
for (auto& infraction : current_player.infractions) for (auto& infraction : current_player->infractions)
{ {
ImGui::BulletText(infraction_desc[(Infraction)infraction]); ImGui::BulletText(infraction_desc[(Infraction)infraction]);
} }
} }
components::button("JOIN_SESSION"_T, [] { components::button("JOIN_SESSION"_T, [] {
session::join_by_rockstar_id(current_player.rockstar_id); session::join_by_rockstar_id(current_player->rockstar_id);
}); });
static char message[256]; static char message[256];
@ -160,10 +175,10 @@ namespace big
if (ImGui::Button("SAVE"_T.data())) if (ImGui::Button("SAVE"_T.data()))
{ {
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);
*selected = current_player; selected = current_player;
g_player_database_service->save(); g_player_database_service->save();
} }
@ -181,6 +196,7 @@ namespace big
{ {
g_player_database_service->set_selected(nullptr); g_player_database_service->set_selected(nullptr);
g_player_database_service->get_players().clear(); g_player_database_service->get_players().clear();
g_player_database_service->get_sorted_players().clear();
g_player_database_service->save(); g_player_database_service->save();
} }
@ -190,6 +206,9 @@ 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">())
g_player_database_service->start_update_loop();
ImGui::Separator(); ImGui::Separator();
components::sub_title("NEW_ENTRY"_T); components::sub_title("NEW_ENTRY"_T);
@ -201,7 +220,7 @@ namespace big
if (ImGui::Button("ADD"_T.data())) if (ImGui::Button("ADD"_T.data()))
{ {
g_player_database_service->get_players()[new_rockstar_id] = persistent_player(new_name, new_rockstar_id); current_player = g_player_database_service->add_player(new_rockstar_id, new_name);
g_player_database_service->save(); g_player_database_service->save();
} }
} }