diff --git a/src/core/globals.hpp b/src/core/globals.hpp index baa9dcbd..a3e2bc16 100644 --- a/src/core/globals.hpp +++ b/src/core/globals.hpp @@ -213,6 +213,13 @@ namespace big NLOHMANN_DEFINE_TYPE_INTRUSIVE(player, character_slot, spectating) } 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 script_events @@ -862,7 +869,7 @@ namespace big NLOHMANN_DEFINE_TYPE_INTRUSIVE(stat_editor, stat, packed_stat) } 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(); diff --git a/src/function_types.hpp b/src/function_types.hpp index 7af1c914..e966a27f 100644 --- a/src/function_types.hpp +++ b/src/function_types.hpp @@ -91,6 +91,7 @@ namespace big::functions using fipackfile_mount = bool (*)(rage::fiPackfile* this_, 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_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); diff --git a/src/gta_pointers.hpp b/src/gta_pointers.hpp index be36452b..7c80b3e4 100644 --- a/src/gta_pointers.hpp +++ b/src/gta_pointers.hpp @@ -141,6 +141,7 @@ namespace big 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_matchmaking_find_sessions m_start_matchmaking_find_sessions; functions::join_session_by_info m_join_session_by_info; diff --git a/src/gui/components/components.hpp b/src/gui/components/components.hpp index 00a93065..56f86368 100644 --- a/src/gui/components/components.hpp +++ b/src/gui/components/components.hpp @@ -60,16 +60,22 @@ namespace big } template - static void command_checkbox(std::optional label_override = std::nullopt) + static bool command_checkbox(std::optional label_override = std::nullopt) { static bool_command* command = dynamic_cast(command::get(rage::consteval_joaat(cmd_str.value))); 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(); if (ImGui::IsItemHovered()) ImGui::SetTooltip(command->get_description().c_str()); + + return updated; } template diff --git a/src/main.cpp b/src/main.cpp index 09f3aa25..b082ae61 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -135,6 +135,8 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID) LOG(INFO) << "Dynamic native hooker initialized."; 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) std::this_thread::sleep_for(500ms); diff --git a/src/pointers.cpp b/src/pointers.cpp index c104dafe..f1195eec 100644 --- a/src/pointers.cpp +++ b/src/pointers.cpp @@ -589,6 +589,15 @@ namespace big g_pointers->m_gta.m_send_chat_message = ptr.sub(21).as(); } }, + // 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(); + } + }, // Start Get Session By Gamer Handle { "SGSBGH", diff --git a/src/services/player_database/persistent_player.hpp b/src/services/player_database/persistent_player.hpp index fa1410b1..8284733c 100644 --- a/src/services/player_database/persistent_player.hpp +++ b/src/services/player_database/persistent_player.hpp @@ -1,53 +1,61 @@ -#pragma once -#include "core/data/infractions.hpp" -#include "json_util.hpp" - -#include - -namespace big -{ - enum class PlayerOnlineStatus - { - UNKNOWN, - OFFLINE, - ONLINE - }; - - struct persistent_player - { - std::string name; - std::uint64_t rockstar_id = 0; - bool block_join = false; - int block_join_reason = 1; - bool is_modder = false; - std::unordered_set infractions; - std::optional command_access_level = std::nullopt; - PlayerOnlineStatus online_state = PlayerOnlineStatus::UNKNOWN; - }; - - static void to_json(nlohmann::json& j, const persistent_player& player) - { - j = nlohmann::json{{"name", player.name}, - {"rockstar_id", player.rockstar_id}, - {"block_join", player.block_join}, - {"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(); - }; - - static void from_json(const nlohmann::json& j, persistent_player& player) - { - set_from_key_or_default(j, "name", player.name); - set_from_key_or_default(j, "rockstar_id", player.rockstar_id); - 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); - set_from_key_or_default(j, "infractions", player.infractions); - - if (j.contains("command_access_level") && j["command_access_level"].is_string()) - player.command_access_level = j["command_access_level"].get(); - } -}; +#pragma once +#include "core/data/infractions.hpp" +#include "json_util.hpp" + +#include + +namespace nlohmann +{ + template + struct adl_serializer> + { + static void to_json(json& j, const std::optional& opt) + { + if (opt == std::nullopt) + { + j = nullptr; + } + else + { + j = *opt; + } + } + + static void from_json(const json& j, std::optional& opt) + { + if (j.is_null()) + { + opt = std::nullopt; + } + else + { + opt = j.get(); + } + } + }; +} + +namespace big +{ + enum class PlayerOnlineStatus + { + UNKNOWN, + OFFLINE, + ONLINE + }; + + struct persistent_player + { + std::string name; + std::uint64_t rockstar_id = 0; + bool block_join = false; + int block_join_reason = 1; + bool is_modder = false; + std::unordered_set infractions; + std::optional 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) + }; + +}; diff --git a/src/services/player_database/player_database_service.cpp b/src/services/player_database/player_database_service.cpp index 4ec06820..38c5a893 100644 --- a/src/services/player_database/player_database_service.cpp +++ b/src/services/player_database/player_database_service.cpp @@ -1,11 +1,15 @@ #include "player_database_service.hpp" +#include "backend/bool_command.hpp" #include "file_manager.hpp" #include "pointers.hpp" #include "util/session.hpp" 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() : m_file_path(g_file_manager->get_project_file("./players.json").get_path()) { @@ -42,35 +46,61 @@ namespace big file_stream >> json; file_stream.close(); - for (auto& p : json.items()) + for (auto& [key, value] : json.items()) { - m_players[std::stoll(p.key())] = p.value().get(); + auto player = value.get>(); + 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& player_database_service::get_players() + std::unordered_map>& player_database_service::get_players() { return m_players; } - persistent_player* player_database_service::get_player_by_rockstar_id(std::uint64_t rockstar_id) + std::map>& player_database_service::get_sorted_players() + { + return m_sorted_players; + } + + std::shared_ptr 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(name.data(), rid); + m_players[rid] = player; + + m_sorted_players[lower] = player; + + return player; + } + + std::shared_ptr player_database_service::get_player_by_rockstar_id(std::uint64_t rockstar_id) { if (m_players.contains(rockstar_id)) - return &m_players[rockstar_id]; + return m_players[rockstar_id]; return nullptr; } - persistent_player* player_database_service::get_or_create_player(player_ptr player) + std::shared_ptr player_database_service::get_or_create_player(player_ptr player) { 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 { - m_players[player->get_net_data()->m_gamer_handle.m_rockstar_id] = {player->get_name(), - player->get_net_data()->m_gamer_handle.m_rockstar_id}; + auto player_ptr = add_player(player->get_net_data()->m_gamer_handle.m_rockstar_id, player->get_name()); 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) 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 selected) { m_selected = selected; } - persistent_player* player_database_service::get_selected() + std::shared_ptr player_database_service::get_selected() { return m_selected; } @@ -103,35 +140,70 @@ namespace big void player_database_service::invalidate_player_states() { 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() { invalidate_player_states(); + const auto player_count = m_players.size(); - //fetch current stat for each player.. this will need some time. - for (auto& item : m_players) + std::vector> gamer_handle_buckets; + 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; - rage::rlGamerHandle player_handle(player.rockstar_id); - rage::rlSessionByGamerTaskResult result; - bool success = false; - rage::rlTaskStatus state{}; + gamer_handle_buckets[i / 32].push_back(it->second->rockstar_id); - 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 online; + + if (g_pointers->m_gta.m_get_gamer_online_state(0, bucket.data(), bucket.size(), online.data(), &status)) { - while (state.status == 1) - script::get_current()->yield(); - - if (state.status == 3 && success) + while (status.status == 1) { - player.online_state = PlayerOnlineStatus::ONLINE; - continue; + script::get_current()->yield(); + } + + 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; } } } \ No newline at end of file diff --git a/src/services/player_database/player_database_service.hpp b/src/services/player_database/player_database_service.hpp index 2c294dad..dc8945b2 100644 --- a/src/services/player_database/player_database_service.hpp +++ b/src/services/player_database/player_database_service.hpp @@ -1,34 +1,56 @@ -#pragma once -#include "persistent_player.hpp" -#include "services/players/player.hpp" - -namespace big -{ - class player_database_service - { - std::unordered_map m_players; - persistent_player* m_selected = nullptr; - - public: - std::filesystem::path m_file_path; - player_database_service(); - ~player_database_service(); - - void save(); - void load(); - - std::unordered_map& 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); - void remove_rockstar_id(std::uint64_t rockstar_id); - - void set_selected(persistent_player* selected); - persistent_player* get_selected(); - - void update_player_states(); - void invalidate_player_states(); - }; - - inline player_database_service* g_player_database_service; +#pragma once +#include "persistent_player.hpp" +#include "services/players/player.hpp" + +namespace nlohmann +{ + template + struct adl_serializer> + { + static void to_json(json& j, const std::shared_ptr& value) + { + j = *value; + } + + static void from_json(const json& j, std::shared_ptr& value) + { + value = std::make_shared(); + *value = j.get(); + } + }; +} + +namespace big +{ + class player_database_service + { + std::unordered_map> m_players; + std::map> m_sorted_players; + std::shared_ptr m_selected = nullptr; + + public: + std::filesystem::path m_file_path; + player_database_service(); + ~player_database_service(); + + void save(); + void load(); + + std::shared_ptr add_player(std::int64_t rid, const std::string_view name); + std::unordered_map>& get_players(); + std::map>& get_sorted_players(); + std::shared_ptr get_player_by_rockstar_id(std::uint64_t rockstar_id); + std::shared_ptr 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 selected); + std::shared_ptr get_selected(); + + void start_update_loop(); + void update_player_states(); + void invalidate_player_states(); + }; + + inline player_database_service* g_player_database_service; } \ No newline at end of file diff --git a/src/views/network/view_player_database.cpp b/src/views/network/view_player_database.cpp index 4261b797..cf18c56e 100644 --- a/src/views/network/view_player_database.cpp +++ b/src/views/network/view_player_database.cpp @@ -11,60 +11,70 @@ namespace big { - persistent_player current_player; + char name_buf[32]; + char search[64]; + std::shared_ptr current_player; + + void draw_player_db_entry(std::shared_ptr 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() { - static char name_buf[32]; - static char search[64]; ImGui::SetNextItemWidth(300.f); components::input_text_with_hint("PLAYER"_T, "SEARCH"_T, search, sizeof(search), ImGuiInputTextFlags_None); if (ImGui::ListBoxHeader("###players", {180, static_cast(*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) { std::string lower_search = search; 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; - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - - if (lower_search.empty() || name.find(lower_search) != std::string::npos) - { - 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(); - } + for (auto& player : item_arr | std::ranges::views::values) + { + if (player->online_state != PlayerOnlineStatus::ONLINE) + draw_player_db_entry(player, lower_search); } } else @@ -82,23 +92,27 @@ namespace big { 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, ¤t_player.rockstar_id); - ImGui::Checkbox("IS_MODDER"_T.data(), ¤t_player.is_modder); - ImGui::Checkbox("BLOCK_JOIN"_T.data(), ¤t_player.block_join); + if (ImGui::InputScalar("RID"_T.data(), ImGuiDataType_S64, ¤t_player->rockstar_id) || ImGui::Checkbox("IS_MODDER"_T.data(), ¤t_player->is_modder) || ImGui::Checkbox("BLOCK_JOIN"_T.data(), ¤t_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) { - 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(); } @@ -112,16 +126,17 @@ namespace big 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) { - 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(); } @@ -130,18 +145,18 @@ namespace big ImGui::EndCombo(); } - if (!current_player.infractions.empty()) + if (!current_player->infractions.empty()) { ImGui::Text("INFRACTIONS"_T.data()); - for (auto& infraction : current_player.infractions) + for (auto& infraction : current_player->infractions) { ImGui::BulletText(infraction_desc[(Infraction)infraction]); } } 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]; @@ -160,10 +175,10 @@ namespace big if (ImGui::Button("SAVE"_T.data())) { - if (current_player.rockstar_id != selected->rockstar_id) - g_player_database_service->update_rockstar_id(selected->rockstar_id, current_player.rockstar_id); + if (current_player->rockstar_id != selected->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(); } @@ -181,6 +196,7 @@ namespace big { 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(); } @@ -190,6 +206,9 @@ namespace big 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(); components::sub_title("NEW_ENTRY"_T); @@ -201,7 +220,7 @@ namespace big 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(); } }