* initially attempt to load the icons of all achievements, this will slow things down at startup,

but avoids having to load the achievement icon during gameplay which causes micro-stutter
This commit is contained in:
otavepto 2024-03-14 19:23:39 +02:00 committed by otavepto
parent 0b86464374
commit af237639d5
5 changed files with 371 additions and 321 deletions

View File

@ -1,8 +1,8 @@
# 2024/3/11 # 2024/3/14
* manage overlay cursor input/clipping and internal frame processing in a better way, * manage overlay cursor input/clipping and internal frame processing in a better way,
should prevent more games from pausing to display notifications should prevent more games from pausing to display notifications
* load the icons of a single achievement each overlay callback invokation, will slow things down during startup * initially attempt to load the icons of all achievements, this will slow things down at startup,
but this avoids having to load the achievement icon during gameplay which causes micro-stutter but avoids having to load the achievement icon during gameplay which causes micro-stutter
* avoid loading and resizing the achievement icon each time it's unlocked * avoid loading and resizing the achievement icon each time it's unlocked
* Local_Storage: avoid allocating buffers unless `stbi_load()` was successfull * Local_Storage: avoid allocating buffers unless `stbi_load()` was successfull

View File

@ -518,13 +518,12 @@ bool SetAchievement( const char *pchName )
auto ach = user_achievements.find(pch_name); auto ach = user_achievements.find(pch_name);
if (user_achievements.end() == ach || ach->value("earned", false) == false) { if (user_achievements.end() == ach || ach->value("earned", false) == false) {
user_achievements[pch_name]["earned"] = true; user_achievements[pch_name]["earned"] = true;
user_achievements[pch_name]["earned_time"] = std::chrono::duration_cast<std::chrono::duration<uint32>>(std::chrono::system_clock::now().time_since_epoch()).count(); user_achievements[pch_name]["earned_time"] =
std::chrono::duration_cast<std::chrono::duration<uint32>>(std::chrono::system_clock::now().time_since_epoch()).count();
save_achievements(); save_achievements();
#ifdef EMU_OVERLAY if(!settings->disable_overlay) overlay->AddAchievementNotification(it.value());
overlay->AddAchievementNotification(it.value());
#endif
} }
} catch (...) {} } catch (...) {}

View File

@ -270,10 +270,9 @@ HSteamUser Steam_Client::ConnectToGlobalUser( HSteamPipe hSteamPipe )
} }
userLogIn(); userLogIn();
#ifdef EMU_OVERLAY
if(!settings_client->disable_overlay) if (!settings_client->disable_overlay) steam_overlay->SetupOverlay();
steam_overlay->SetupOverlay();
#endif
steam_pipes[hSteamPipe] = Steam_Pipe::CLIENT; steam_pipes[hSteamPipe] = Steam_Pipe::CLIENT;
return CLIENT_HSTEAMUSER; return CLIENT_HSTEAMUSER;
} }
@ -958,10 +957,8 @@ bool Steam_Client::BShutdownIfAllPipesClosed()
} }
steam_controller->Shutdown(); steam_controller->Shutdown();
#ifdef EMU_OVERLAY
if(!settings_client->disable_overlay) if(!settings_client->disable_overlay) steam_overlay->UnSetupOverlay();
steam_overlay->UnSetupOverlay();
#endif
if (joinable) { if (joinable) {
background_keepalive.join(); background_keepalive.join();

View File

@ -111,33 +111,32 @@ class Steam_Overlay
Networking* network; Networking* network;
// friend id, show client window (to chat and accept invite maybe) // friend id, show client window (to chat and accept invite maybe)
std::map<Friend, friend_window_state, Friend_Less> friends; std::map<Friend, friend_window_state, Friend_Less> friends{};
// avoids spam loading on failure
constexpr const static int LOAD_ACHIEVEMENTS_MAX_TRIALS = 3;
std::atomic<int32_t> load_achievements_trials = LOAD_ACHIEVEMENTS_MAX_TRIALS;
bool is_ready = false; bool is_ready = false;
bool show_overlay;
ENotificationPosition notif_position; ENotificationPosition notif_position = ENotificationPosition::k_EPositionBottomLeft;
int h_inset, v_inset; int h_inset = 0;
std::string show_url; int v_inset = 0;
std::vector<Overlay_Achievement> achievements; std::string show_url{};
// index of the next achievement whose icons will be loaded
// used by the callback std::vector<Overlay_Achievement> achievements{};
int next_ach_to_load = 0;
bool show_achievements, show_settings; bool show_overlay = false;
bool show_achievements = false;
bool show_settings = false;
// disable input when force_*.txt file is used // disable input when force_*.txt file is used
bool disable_user_input; bool disable_user_input = false;
// warn when force_*.txt file is used // warn when force_*.txt file is used
bool warn_forced_setting; bool warn_forced_setting = false;
// warn when using local save // warn when using local save
bool warn_local_save; bool warn_local_save = false;
// warn when app ID = 0 // warn when app ID = 0
bool warn_bad_appid; bool warn_bad_appid = false;
char username_text[256]; char username_text[256]{};
std::atomic<bool> save_settings; std::atomic<bool> save_settings = false;
int current_language = 0; int current_language = 0;
@ -149,9 +148,13 @@ class Steam_Overlay
// used when the button "Invite all" is clicked // used when the button "Invite all" is clicked
std::atomic<bool> invite_all_friends_clicked = false; std::atomic<bool> invite_all_friends_clicked = false;
bool overlay_state_changed; bool overlay_state_changed = false;
std::atomic<bool> i_have_lobby; std::atomic<bool> i_have_lobby = false;
// some stuff has to be initialized once the renderer hook is ready
std::atomic<bool> late_init_imgui = false;
bool late_init_ach_icons = false;
// changed each time a notification is posted or overlay is shown/hidden // changed each time a notification is posted or overlay is shown/hidden
std::atomic_uint32_t renderer_frame_processing_requests = 0; std::atomic_uint32_t renderer_frame_processing_requests = 0;
@ -167,11 +170,8 @@ class Steam_Overlay
Steam_Overlay& operator=(Steam_Overlay const&) = delete; Steam_Overlay& operator=(Steam_Overlay const&) = delete;
Steam_Overlay& operator=(Steam_Overlay&&) = delete; Steam_Overlay& operator=(Steam_Overlay&&) = delete;
static void steam_overlay_run_every_runcb(void* object); static void overlay_run_callback(void* object);
static void steam_overlay_callback(void* object, Common_Message* msg); static void overlay_networking_callback(void* object, Common_Message* msg);
void Callback(Common_Message* msg);
void RunCallbacks();
bool is_friend_joinable(std::pair<const Friend, friend_window_state> &f); bool is_friend_joinable(std::pair<const Friend, friend_window_state> &f);
bool got_lobby(); bool got_lobby();
@ -198,12 +198,12 @@ class Steam_Overlay
void create_fonts(); void create_fonts();
void load_audio(); void load_audio();
void load_achievements_data();
void initial_load_achievements_icons();
void overlay_state_hook(bool ready); void overlay_state_hook(bool ready);
void allow_renderer_frame_processing(bool state, bool cleaning_up_overlay = false); void allow_renderer_frame_processing(bool state, bool cleaning_up_overlay = false);
void obscure_cursor_input(bool state); void obscure_game_input(bool state);
void overlay_proc();
void add_auto_accept_invite_notification(); void add_auto_accept_invite_notification();
@ -216,6 +216,10 @@ class Steam_Overlay
bool try_load_ach_icon(Overlay_Achievement &ach); bool try_load_ach_icon(Overlay_Achievement &ach);
bool try_load_ach_gray_icon(Overlay_Achievement &ach); bool try_load_ach_gray_icon(Overlay_Achievement &ach);
void overlay_render_proc();
void networking_msg_received(Common_Message* msg);
void steam_run_callback();
public: public:
Steam_Overlay(Settings* settings, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking *network); Steam_Overlay(Settings* settings, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking *network);

View File

@ -92,17 +92,17 @@ static inline bool ImGuiHelper_BeginListBox(const char* label, int items_count)
} }
void Steam_Overlay::steam_overlay_run_every_runcb(void* object) void Steam_Overlay::overlay_run_callback(void* object)
{ {
PRINT_DEBUG("overlay_run_every_runcb %p\n", object); PRINT_DEBUG("overlay_run_every_runcb %p\n", object);
Steam_Overlay* _this = reinterpret_cast<Steam_Overlay*>(object); Steam_Overlay* _this = reinterpret_cast<Steam_Overlay*>(object);
_this->RunCallbacks(); _this->steam_run_callback();
} }
void Steam_Overlay::steam_overlay_callback(void* object, Common_Message* msg) void Steam_Overlay::overlay_networking_callback(void* object, Common_Message* msg)
{ {
Steam_Overlay* _this = reinterpret_cast<Steam_Overlay*>(object); Steam_Overlay* _this = reinterpret_cast<Steam_Overlay*>(object);
_this->Callback(msg); _this->networking_msg_received(msg);
} }
Steam_Overlay::Steam_Overlay(Settings* settings, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network) : Steam_Overlay::Steam_Overlay(Settings* settings, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network) :
@ -110,17 +110,7 @@ Steam_Overlay::Steam_Overlay(Settings* settings, SteamCallResults* callback_resu
callback_results(callback_results), callback_results(callback_results),
callbacks(callbacks), callbacks(callbacks),
run_every_runcb(run_every_runcb), run_every_runcb(run_every_runcb),
network(network), network(network)
show_overlay(false),
is_ready(false),
notif_position(ENotificationPosition::k_EPositionBottomLeft),
h_inset(0),
v_inset(0),
overlay_state_changed(false),
i_have_lobby(false),
show_achievements(false),
show_settings(false),
_renderer(nullptr)
{ {
// don't even bother initializing the overlay // don't even bother initializing the overlay
if (settings->disable_overlay) return; if (settings->disable_overlay) return;
@ -151,15 +141,15 @@ Steam_Overlay::Steam_Overlay(Settings* settings, SteamCallResults* callback_resu
++i; ++i;
} }
run_every_runcb->add(&Steam_Overlay::steam_overlay_run_every_runcb, this); run_every_runcb->add(&Steam_Overlay::overlay_run_callback, this);
this->network->setCallback(CALLBACK_ID_STEAM_MESSAGES, settings->get_local_steam_id(), &Steam_Overlay::steam_overlay_callback, this); this->network->setCallback(CALLBACK_ID_STEAM_MESSAGES, settings->get_local_steam_id(), &Steam_Overlay::overlay_networking_callback, this);
} }
Steam_Overlay::~Steam_Overlay() Steam_Overlay::~Steam_Overlay()
{ {
if (settings->disable_overlay) return; if (settings->disable_overlay) return;
run_every_runcb->remove(&Steam_Overlay::steam_overlay_run_every_runcb, this); run_every_runcb->remove(&Steam_Overlay::overlay_run_callback, this);
} }
void Steam_Overlay::request_renderer_detector() void Steam_Overlay::request_renderer_detector()
@ -213,18 +203,19 @@ void Steam_Overlay::renderer_hook_init_thread()
} }
// do a one time initialization // do a one time initialization
std::lock_guard<std::recursive_mutex> lock(overlay_mutex); // std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
_renderer = future_renderer.get(); _renderer = future_renderer.get();
PRINT_DEBUG("Steam_Overlay::renderer_hook_init_thread got renderer %p\n", _renderer); PRINT_DEBUG("Steam_Overlay::renderer_hook_init_thread got renderer %p\n", _renderer);
create_fonts(); create_fonts();
load_audio(); load_audio();
load_achievements_data();
// setup renderer callbacks // setup renderer callbacks
const static std::set<InGameOverlay::ToggleKey> overlay_toggle_keys = { const static std::set<InGameOverlay::ToggleKey> overlay_toggle_keys = {
InGameOverlay::ToggleKey::SHIFT, InGameOverlay::ToggleKey::TAB InGameOverlay::ToggleKey::SHIFT, InGameOverlay::ToggleKey::TAB
}; };
auto overlay_toggle_callback = [this]() { open_overlay_hook(true); }; auto overlay_toggle_callback = [this]() { open_overlay_hook(true); };
_renderer->OverlayProc = [this]() { overlay_proc(); }; _renderer->OverlayProc = [this]() { overlay_render_proc(); };
_renderer->OverlayHookReady = [this](InGameOverlay::OverlayHookState state) { _renderer->OverlayHookReady = [this](InGameOverlay::OverlayHookState state) {
PRINT_DEBUG("Steam_Overlay hook state changed to <%i>\n", (int)state); PRINT_DEBUG("Steam_Overlay hook state changed to <%i>\n", (int)state);
overlay_state_hook(state == InGameOverlay::OverlayHookState::Ready || state == InGameOverlay::OverlayHookState::Reset); overlay_state_hook(state == InGameOverlay::OverlayHookState::Ready || state == InGameOverlay::OverlayHookState::Reset);
@ -232,7 +223,6 @@ void Steam_Overlay::renderer_hook_init_thread()
bool started = _renderer->StartHook(overlay_toggle_callback, overlay_toggle_keys, &fonts_atlas); bool started = _renderer->StartHook(overlay_toggle_callback, overlay_toggle_keys, &fonts_atlas);
PRINT_DEBUG("Steam_Overlay::renderer_hook_init_thread started renderer hook (result=%i)\n", (int)started); PRINT_DEBUG("Steam_Overlay::renderer_hook_init_thread started renderer hook (result=%i)\n", (int)started);
} }
void Steam_Overlay::create_fonts() void Steam_Overlay::create_fonts()
@ -369,6 +359,78 @@ void Steam_Overlay::load_audio()
} }
} }
void Steam_Overlay::load_achievements_data()
{
PRINT_DEBUG("Steam_Overlay::load_achievements_data\n");
std::lock_guard<std::recursive_mutex> lock(global_mutex);
Steam_User_Stats* steamUserStats = get_steam_client()->steam_user_stats;
uint32 achievements_num = steamUserStats->GetNumAchievements();
for (uint32 i = 0; i < achievements_num; ++i) {
Overlay_Achievement ach{};
ach.name = steamUserStats->GetAchievementName(i);
ach.title = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "name");
ach.description = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "desc");
const char *hidden = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "hidden");
ach.hidden = hidden && hidden[0] == '1';
bool achieved = false;
uint32 unlock_time = 0;
if (steamUserStats->GetAchievementAndUnlockTime(ach.name.c_str(), &achieved, &unlock_time)) {
ach.achieved = achieved;
ach.unlock_time = unlock_time;
} else {
ach.achieved = false;
ach.unlock_time = 0;
}
ach.icon_name = steamUserStats->get_achievement_icon_name(ach.name.c_str(), true);
ach.icon_gray_name = steamUserStats->get_achievement_icon_name(ach.name.c_str(), false);
achievements.push_back(ach);
if (!setup_overlay_called) return;
}
PRINT_DEBUG("Steam_Overlay::load_achievements_data count=%lu, loaded=%zu\n", achievements_num, achievements.size());
}
void Steam_Overlay::initial_load_achievements_icons()
{
{
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (late_init_ach_icons) return;
}
PRINT_DEBUG("Steam_Overlay::initial_load_achievements_icons\n");
for (auto &ach : achievements) {
{
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!is_ready || !setup_overlay_called) {
PRINT_DEBUG("Steam_Overlay::initial_load_achievements_icons early exit\n");
return;
}
}
try_load_ach_icon(ach);
{
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!is_ready || !setup_overlay_called) {
PRINT_DEBUG("Steam_Overlay::initial_load_achievements_icons early exit\n");
return;
}
}
try_load_ach_gray_icon(ach);
}
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
late_init_ach_icons = true;
}
// called initially and when window size is updated // called initially and when window size is updated
void Steam_Overlay::overlay_state_hook(bool ready) void Steam_Overlay::overlay_state_hook(bool ready)
{ {
@ -380,33 +442,25 @@ void Steam_Overlay::overlay_state_hook(bool ready)
// TODO this also doesn't seem right, no idea why it happens though // TODO this also doesn't seem right, no idea why it happens though
// NOTE after initializing the renderer detector on another thread this was solved // NOTE after initializing the renderer detector on another thread this was solved
std::lock_guard<std::recursive_mutex> lock(overlay_mutex); std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!setup_overlay_called) return; if (!setup_overlay_called) return;
is_ready = ready; is_ready = ready;
static bool initialized_imgui = false; if (ready) {
// Antichamber crashes here because ImGui Context was null! // Antichamber may crash here because ImGui Context is null!, no idea why
// no idea why bool not_yet = false;
if (ready && !initialized_imgui && ImGui::GetCurrentContext()) { if (ImGui::GetCurrentContext() && late_init_imgui.compare_exchange_weak(not_yet, true)) {
initialized_imgui = true; PRINT_DEBUG("Steam_Overlay::overlay_state_hook late init ImGui\n");
PRINT_DEBUG("Steam_Overlay::overlay_state_hook initializing ImGui config\n");
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
ImGuiStyle &style = ImGui::GetStyle();
// disable loading the default ini file // disable loading the default ini file
io.IniFilename = NULL; io.IniFilename = NULL;
ImGuiStyle &style = ImGui::GetStyle();
// Disable round window // Disable round window
style.WindowRounding = 0.0; style.WindowRounding = 0.0;
// TODO: Uncomment this and draw our own cursor (cosmetics)
//io.WantSetMousePos = false;
//io.MouseDrawCursor = false;
//io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
} }
}
} }
// called when the user presses SHIFT + TAB // called when the user presses SHIFT + TAB
@ -423,7 +477,7 @@ bool Steam_Overlay::open_overlay_hook(bool toggle)
void Steam_Overlay::allow_renderer_frame_processing(bool state, bool cleaning_up_overlay) void Steam_Overlay::allow_renderer_frame_processing(bool state, bool cleaning_up_overlay)
{ {
// this is very important internally it calls the necessary fuctions // this is very important internally it calls the necessary fuctions
// to properly update ImGui window size on the next overlay_proc() call // to properly update ImGui window size on the next overlay_render_proc() call
if (state) { if (state) {
auto new_val = ++renderer_frame_processing_requests; auto new_val = ++renderer_frame_processing_requests;
@ -443,21 +497,37 @@ void Steam_Overlay::allow_renderer_frame_processing(bool state, bool cleaning_up
} }
} }
void Steam_Overlay::obscure_cursor_input(bool state) { void Steam_Overlay::obscure_game_input(bool state) {
if (state) { if (state) {
auto new_val = ++obscure_cursor_requests; auto new_val = ++obscure_cursor_requests;
if (new_val == 1) { // only take an action on first request if (new_val == 1) { // only take an action on first request
ImGuiIO &io = ImGui::GetIO();
// force draw the cursor, otherwise games like Truberbrook will not have an overlay cursor
io.MouseDrawCursor = state;
// not necessary, just to be sure
io.WantCaptureMouse = state;
// not necessary, just to be sure
io.WantCaptureKeyboard = state;
// clip the cursor // clip the cursor
_renderer->HideAppInputs(true); _renderer->HideAppInputs(true);
PRINT_DEBUG("Steam_Overlay::obscure_cursor_input obscured app input (count=%u)\n", new_val); PRINT_DEBUG("Steam_Overlay::obscure_game_input obscured app input (count=%u)\n", new_val);
} }
} else { } else {
if (obscure_cursor_requests > 0) { if (obscure_cursor_requests > 0) {
auto new_val = --obscure_cursor_requests; auto new_val = --obscure_cursor_requests;
if (!new_val) { // only take an action when the requests reach 0 if (!new_val) { // only take an action when the requests reach 0
ImGuiIO &io = ImGui::GetIO();
// force draw the cursor, otherwise games like Truberbrook will not have an overlay cursor
io.MouseDrawCursor = state;
// not necessary, just to be sure
io.WantCaptureMouse = state;
// not necessary, just to be sure
io.WantCaptureKeyboard = state;
// restore the old cursor // restore the old cursor
_renderer->HideAppInputs(false); _renderer->HideAppInputs(false);
PRINT_DEBUG("Steam_Overlay::obscure_cursor_input restored app input (count=%u)\n", new_val); PRINT_DEBUG("Steam_Overlay::obscure_game_input restored app input (count=%u)\n", new_val);
} }
} }
} }
@ -572,7 +642,7 @@ bool Steam_Overlay::submit_notification(notification_type type, const std::strin
// // we want to steal focus for these ones // // we want to steal focus for these ones
// case notification_type_message: // case notification_type_message:
// case notification_type_invite: // case notification_type_invite:
// obscure_cursor_input(true); // obscure_game_input(true);
// break; // break;
// // not effective // // not effective
@ -663,7 +733,7 @@ void Steam_Overlay::build_friend_context_menu(Friend const& frd, friend_window_s
if (state.joinable && ImGui::Button(translationJoin_tmp.c_str())) { if (state.joinable && ImGui::Button(translationJoin_tmp.c_str())) {
close_popup = true; close_popup = true;
// don't bother adding this friend if the button "invite all" was clicked // don't bother adding this friend if the button "invite all" was clicked
// we will send them the invitation later in Steam_Overlay::RunCallbacks() // we will send them the invitation later in Steam_Overlay::steam_run_callback()
if (!invite_all_friends_clicked) { if (!invite_all_friends_clicked) {
state.window_state |= window_state_join; state.window_state |= window_state_join;
has_friend_action.push(frd); has_friend_action.push(frd);
@ -955,7 +1025,7 @@ void Steam_Overlay::build_notifications(int width, int height)
// // we want to restore focus for these ones // // we want to restore focus for these ones
// case notification_type_message: // case notification_type_message:
// case notification_type_invite: // case notification_type_invite:
// obscure_cursor_input(false); // obscure_game_input(false);
// break; // break;
// // not effective // // not effective
@ -1080,8 +1150,9 @@ bool Steam_Overlay::try_load_ach_gray_icon(Overlay_Achievement &ach)
} }
// Try to make this function as short as possible or it might affect game's fps. // Try to make this function as short as possible or it might affect game's fps.
void Steam_Overlay::overlay_proc() void Steam_Overlay::overlay_render_proc()
{ {
initial_load_achievements_icons();
std::lock_guard<std::recursive_mutex> lock(overlay_mutex); std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!Ready()) return; if (!Ready()) return;
@ -1119,26 +1190,61 @@ void Steam_Overlay::overlay_proc()
windowTitle.append(tmp); windowTitle.append(tmp);
windowTitle.append(")"); windowTitle.append(")");
if ((settings->overlay_appearance.background_r != -1.0) && (settings->overlay_appearance.background_g != -1.0) && (settings->overlay_appearance.background_b != -1.0) && (settings->overlay_appearance.background_a != -1.0)) { if ((settings->overlay_appearance.background_r >= 0) &&
ImVec4 colorSet = ImVec4(settings->overlay_appearance.background_r, settings->overlay_appearance.background_g, settings->overlay_appearance.background_b, settings->overlay_appearance.background_a); (settings->overlay_appearance.background_g >= 0) &&
(settings->overlay_appearance.background_b >= 0) &&
(settings->overlay_appearance.background_a >= 0)) {
ImVec4 colorSet = ImVec4(
settings->overlay_appearance.background_r,
settings->overlay_appearance.background_g,
settings->overlay_appearance.background_b,
settings->overlay_appearance.background_a
);
ImGui::PushStyleColor(ImGuiCol_WindowBg, colorSet); ImGui::PushStyleColor(ImGuiCol_WindowBg, colorSet);
} }
if ((settings->overlay_appearance.element_r != -1.0) && (settings->overlay_appearance.element_g != -1.0) && (settings->overlay_appearance.element_b != -1.0) && (settings->overlay_appearance.element_a != -1.0)) {
ImVec4 colorSet = ImVec4(settings->overlay_appearance.element_r, settings->overlay_appearance.element_g, settings->overlay_appearance.element_b, settings->overlay_appearance.element_a); if ((settings->overlay_appearance.element_r >= 0) &&
(settings->overlay_appearance.element_g >= 0) &&
(settings->overlay_appearance.element_b >= 0) &&
(settings->overlay_appearance.element_a >= 0)) {
ImVec4 colorSet = ImVec4(
settings->overlay_appearance.element_r,
settings->overlay_appearance.element_g,
settings->overlay_appearance.element_b,
settings->overlay_appearance.element_a
);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, colorSet); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, colorSet);
ImGui::PushStyleColor(ImGuiCol_Button, colorSet); ImGui::PushStyleColor(ImGuiCol_Button, colorSet);
ImGui::PushStyleColor(ImGuiCol_FrameBg, colorSet); ImGui::PushStyleColor(ImGuiCol_FrameBg, colorSet);
ImGui::PushStyleColor(ImGuiCol_ResizeGrip, colorSet); ImGui::PushStyleColor(ImGuiCol_ResizeGrip, colorSet);
} }
if ((settings->overlay_appearance.element_hovered_r != -1.0) && (settings->overlay_appearance.element_hovered_g != -1.0) && (settings->overlay_appearance.element_hovered_b != -1.0) && (settings->overlay_appearance.element_hovered_a != -1.0)) {
ImVec4 colorSet = ImVec4(settings->overlay_appearance.element_hovered_r, settings->overlay_appearance.element_hovered_g, settings->overlay_appearance.element_hovered_b, settings->overlay_appearance.element_hovered_a); if ((settings->overlay_appearance.element_hovered_r >= 0) &&
(settings->overlay_appearance.element_hovered_g >= 0) &&
(settings->overlay_appearance.element_hovered_b >= 0) &&
(settings->overlay_appearance.element_hovered_a >= 0)) {
ImVec4 colorSet = ImVec4(
settings->overlay_appearance.element_hovered_r,
settings->overlay_appearance.element_hovered_g,
settings->overlay_appearance.element_hovered_b,
settings->overlay_appearance.element_hovered_a
);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colorSet); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colorSet);
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, colorSet); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, colorSet);
ImGui::PushStyleColor(ImGuiCol_ResizeGripHovered, colorSet); ImGui::PushStyleColor(ImGuiCol_ResizeGripHovered, colorSet);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, colorSet); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, colorSet);
} }
if ((settings->overlay_appearance.element_active_r != -1.0) && (settings->overlay_appearance.element_active_g != -1.0) && (settings->overlay_appearance.element_active_b != -1.0) && (settings->overlay_appearance.element_active_a != -1.0)) {
ImVec4 colorSet = ImVec4(settings->overlay_appearance.element_active_r, settings->overlay_appearance.element_active_g, settings->overlay_appearance.element_active_b, settings->overlay_appearance.element_active_a); if ((settings->overlay_appearance.element_active_r >= 0) &&
(settings->overlay_appearance.element_active_g >= 0) &&
(settings->overlay_appearance.element_active_b >= 0) &&
(settings->overlay_appearance.element_active_a >= 0)) {
ImVec4 colorSet = ImVec4(
settings->overlay_appearance.element_active_r,
settings->overlay_appearance.element_active_g,
settings->overlay_appearance.element_active_b,
settings->overlay_appearance.element_active_a
);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, colorSet); ImGui::PushStyleColor(ImGuiCol_ButtonActive, colorSet);
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, colorSet); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, colorSet);
ImGui::PushStyleColor(ImGuiCol_ResizeGripActive, colorSet); ImGui::PushStyleColor(ImGuiCol_ResizeGripActive, colorSet);
@ -1148,8 +1254,7 @@ void Steam_Overlay::overlay_proc()
if (ImGui::Begin(windowTitle.c_str(), &show, if (ImGui::Begin(windowTitle.c_str(), &show,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoBringToFrontOnFocus)) ImGuiWindowFlags_NoBringToFrontOnFocus)) {
{
ImGui::LabelText("##playinglabel", translationUserPlaying[current_language], ImGui::LabelText("##playinglabel", translationUserPlaying[current_language],
settings->get_local_name(), settings->get_local_name(),
settings->get_local_steam_id().ConvertToUint64(), settings->get_local_steam_id().ConvertToUint64(),
@ -1256,7 +1361,7 @@ void Steam_Overlay::overlay_proc()
// we want to display the ach text regardless the icons were displayed or not // we want to display the ach text regardless the icons were displayed or not
ImGui::Text("%s", x.title.c_str()); ImGui::Text("%s", x.title.c_str());
if (hidden && !achieved) { if (hidden) {
ImGui::Text("%s", translationHiddenAchievement[current_language]); ImGui::Text("%s", translationHiddenAchievement[current_language]);
} else { } else {
ImGui::TextWrapped("%s", x.description.c_str()); ImGui::TextWrapped("%s", x.description.c_str());
@ -1319,6 +1424,7 @@ void Steam_Overlay::overlay_proc()
ImGui::End(); ImGui::End();
} }
// we have a url to open/display
if (show_url.size()) { if (show_url.size()) {
std::string url = show_url; std::string url = show_url;
bool show = true; bool show = true;
@ -1376,6 +1482,161 @@ void Steam_Overlay::overlay_proc()
ShowOverlay(false); ShowOverlay(false);
} }
void Steam_Overlay::networking_msg_received(Common_Message *msg)
{
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!Ready()) return;
if (msg->has_steam_messages()) {
Friend frd;
frd.set_id(msg->source_id());
auto friend_info = friends.find(frd);
if (friend_info != friends.end()) {
Steam_Messages const& steam_message = msg->steam_messages();
// Change color to cyan for friend
friend_info->second.chat_history.append(friend_info->first.name() + ": " + steam_message.message()).append("\n", 1);
if (!(friend_info->second.window_state & window_state_show)) {
friend_info->second.window_state |= window_state_need_attention;
}
add_chat_message_notification(friend_info->first.name() + ": " + steam_message.message());
notify_sound_user_invite(friend_info->second);
}
}
}
void Steam_Overlay::steam_run_callback()
{
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!Ready()) return;
if (overlay_state_changed) {
overlay_state_changed = false;
GameOverlayActivated_t data{};
data.m_bActive = show_overlay;
data.m_bUserInitiated = true;
data.m_dwOverlayPID = 123;
data.m_nAppID = settings->get_local_game_id().AppID();
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
}
Steam_Friends* steamFriends = get_steam_client()->steam_friends;
Steam_Matchmaking* steamMatchmaking = get_steam_client()->steam_matchmaking;
if (save_settings) {
save_settings = false;
const char *language_text = valid_languages[current_language];
save_global_settings(get_steam_client()->local_storage, username_text, language_text);
get_steam_client()->settings_client->set_local_name(username_text);
get_steam_client()->settings_server->set_local_name(username_text);
get_steam_client()->settings_client->set_language(language_text);
get_steam_client()->settings_server->set_language(language_text);
steamFriends->resend_friend_data();
}
i_have_lobby = got_lobby();
std::for_each(friends.begin(), friends.end(), [this](std::pair<Friend const, friend_window_state> &i)
{
i.second.joinable = is_friend_joinable(i);
});
while (!has_friend_action.empty()) {
auto friend_info = friends.find(has_friend_action.front());
if (friend_info != friends.end()) {
uint64 friend_id = (uint64)friend_info->first.id();
// The user clicked on "Send"
if (friend_info->second.window_state & window_state_send_message) {
char* input = friend_info->second.chat_input;
char* end_input = input + strlen(input);
char* printable_char = std::find_if(input, end_input, [](char c) { return std::isgraph(c); });
// Check if the message contains something else than blanks
if (printable_char != end_input) {
// Handle chat send
Common_Message msg;
Steam_Messages* steam_messages = new Steam_Messages;
steam_messages->set_type(Steam_Messages::FRIEND_CHAT);
steam_messages->set_message(friend_info->second.chat_input);
msg.set_allocated_steam_messages(steam_messages);
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
msg.set_dest_id(friend_id);
network->sendTo(&msg, true);
friend_info->second.chat_history.append(get_steam_client()->settings_client->get_local_name()).append(": ").append(input).append("\n", 1);
}
*input = 0; // Reset the input field
friend_info->second.window_state &= ~window_state_send_message;
}
// The user clicked on "Invite" (but invite all wasn't clicked)
if (friend_info->second.window_state & window_state_invite) {
invite_friend(friend_id, steamFriends, steamMatchmaking);
friend_info->second.window_state &= ~window_state_invite;
}
// The user clicked on "Join"
if (friend_info->second.window_state & window_state_join) {
std::string connect = steamFriends->GetFriendRichPresence(friend_id, "connect");
// The user got a lobby invite and accepted it
if (friend_info->second.window_state & window_state_lobby_invite) {
GameLobbyJoinRequested_t data;
data.m_steamIDLobby.SetFromUint64(friend_info->second.lobbyId);
data.m_steamIDFriend.SetFromUint64(friend_id);
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
friend_info->second.window_state &= ~window_state_lobby_invite;
} else {
// The user got a rich presence invite and accepted it
if (friend_info->second.window_state & window_state_rich_invite) {
GameRichPresenceJoinRequested_t data = {};
data.m_steamIDFriend.SetFromUint64(friend_id);
strncpy(data.m_rgchConnect, friend_info->second.connect, k_cchMaxRichPresenceValueLength - 1);
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
friend_info->second.window_state &= ~window_state_rich_invite;
} else if (connect.length() > 0) {
GameRichPresenceJoinRequested_t data = {};
data.m_steamIDFriend.SetFromUint64(friend_id);
strncpy(data.m_rgchConnect, connect.c_str(), k_cchMaxRichPresenceValueLength - 1);
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
}
//Not sure about this but it fixes sonic racing transformed invites
FriendGameInfo_t friend_game_info = {};
steamFriends->GetFriendGamePlayed(friend_id, &friend_game_info);
uint64 lobby_id = friend_game_info.m_steamIDLobby.ConvertToUint64();
if (lobby_id) {
GameLobbyJoinRequested_t data;
data.m_steamIDLobby.SetFromUint64(lobby_id);
data.m_steamIDFriend.SetFromUint64(friend_id);
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
}
}
friend_info->second.window_state &= ~window_state_join;
}
}
has_friend_action.pop();
}
// if variable == true, then set it to false and return true (because state was changed) in that case
bool yes_clicked = true;
if (invite_all_friends_clicked.compare_exchange_weak(yes_clicked, false)) {
PRINT_DEBUG("Steam_Overlay will send invitations to [%zu] friends if they're using the same app\n", friends.size());
uint32 current_appid = settings->get_local_game_id().AppID();
for (auto &fr : friends) {
if (fr.first.appid() == current_appid) { // friend is playing the same game
uint64 friend_id = (uint64)fr.first.id();
invite_friend(friend_id, steamFriends, steamMatchmaking);
}
}
}
}
void Steam_Overlay::SetupOverlay() void Steam_Overlay::SetupOverlay()
{ {
@ -1408,7 +1669,7 @@ void Steam_Overlay::UnSetupOverlay()
// stop internal frame processing & restore cursor // stop internal frame processing & restore cursor
if (_renderer) { if (_renderer) {
allow_renderer_frame_processing(false, true); allow_renderer_frame_processing(false, true);
obscure_cursor_input(false); obscure_game_input(false);
// for some reason this gets triggered after the overlay instance has been destroyed // for some reason this gets triggered after the overlay instance has been destroyed
// I assume because the game de-initializes DX later after closing Steam APIs // I assume because the game de-initializes DX later after closing Steam APIs
@ -1445,7 +1706,7 @@ void Steam_Overlay::UnSetupOverlay()
bool Steam_Overlay::Ready() const bool Steam_Overlay::Ready() const
{ {
return is_ready; return is_ready && late_init_imgui && late_init_ach_icons;
} }
bool Steam_Overlay::NeedPresent() const bool Steam_Overlay::NeedPresent() const
@ -1520,12 +1781,8 @@ void Steam_Overlay::ShowOverlay(bool state)
PRINT_DEBUG("Steam_Overlay::ShowOverlay %i\n", (int)state); PRINT_DEBUG("Steam_Overlay::ShowOverlay %i\n", (int)state);
ImGuiIO &io = ImGui::GetIO();
// force draw the cursor, otherwise games like Truberbrook will not have an overlay cursor
io.MouseDrawCursor = state;
Steam_Overlay::allow_renderer_frame_processing(state); Steam_Overlay::allow_renderer_frame_processing(state);
Steam_Overlay::obscure_cursor_input(state); Steam_Overlay::obscure_game_input(state);
} }
@ -1637,211 +1894,4 @@ void Steam_Overlay::AddAchievementNotification(nlohmann::json const& ach)
} }
} }
void Steam_Overlay::Callback(Common_Message *msg)
{
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!Ready()) return;
if (msg->has_steam_messages()) {
Friend frd;
frd.set_id(msg->source_id());
auto friend_info = friends.find(frd);
if (friend_info != friends.end()) {
Steam_Messages const& steam_message = msg->steam_messages();
// Change color to cyan for friend
friend_info->second.chat_history.append(friend_info->first.name() + ": " + steam_message.message()).append("\n", 1);
if (!(friend_info->second.window_state & window_state_show)) {
friend_info->second.window_state |= window_state_need_attention;
}
add_chat_message_notification(friend_info->first.name() + ": " + steam_message.message());
notify_sound_user_invite(friend_info->second);
}
}
}
void Steam_Overlay::RunCallbacks()
{
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
std::lock_guard<std::recursive_mutex> lock2(global_mutex);
if (!achievements.size() && load_achievements_trials > 0) {
--load_achievements_trials;
Steam_User_Stats* steamUserStats = get_steam_client()->steam_user_stats;
uint32 achievements_num = steamUserStats->GetNumAchievements();
if (achievements_num) {
PRINT_DEBUG("Steam_Overlay POPULATE OVERLAY ACHIEVEMENTS\n");
for (unsigned i = 0; i < achievements_num; ++i) {
Overlay_Achievement ach{};
ach.name = steamUserStats->GetAchievementName(i);
ach.title = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "name");
ach.description = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "desc");
const char *hidden = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "hidden");
if (hidden && hidden[0] == '1') {
ach.hidden = true;
} else {
ach.hidden = false;
}
bool achieved = false;
uint32 unlock_time = 0;
if (steamUserStats->GetAchievementAndUnlockTime(ach.name.c_str(), &achieved, &unlock_time)) {
ach.achieved = achieved;
ach.unlock_time = unlock_time;
} else {
ach.achieved = false;
ach.unlock_time = 0;
}
ach.icon_name = steamUserStats->get_achievement_icon_name(ach.name.c_str(), true);
ach.icon_gray_name = steamUserStats->get_achievement_icon_name(ach.name.c_str(), false);
achievements.push_back(ach);
}
// don't punish successfull attempts
if (achievements.size()) load_achievements_trials = Steam_Overlay::LOAD_ACHIEVEMENTS_MAX_TRIALS;
PRINT_DEBUG("Steam_Overlay POPULATE OVERLAY ACHIEVEMENTS DONE (count=%lu, loaded=%zu)\n", achievements_num, achievements.size());
}
}
if (!Ready()) return;
// load images/icons for the next ach
if (next_ach_to_load < achievements.size()) {
try_load_ach_icon(achievements[next_ach_to_load]);
try_load_ach_gray_icon(achievements[next_ach_to_load]);
++next_ach_to_load;
// this allows the callback to keep trying forever in case the image resource was reset
// each icon has a limit though, so it won't slow things down forever
if (next_ach_to_load >= achievements.size()) next_ach_to_load = 0;
}
if (overlay_state_changed) {
overlay_state_changed = false;
GameOverlayActivated_t data{};
data.m_bActive = show_overlay;
data.m_bUserInitiated = true;
data.m_dwOverlayPID = 123;
data.m_nAppID = settings->get_local_game_id().AppID();
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
}
Steam_Friends* steamFriends = get_steam_client()->steam_friends;
Steam_Matchmaking* steamMatchmaking = get_steam_client()->steam_matchmaking;
if (save_settings) {
save_settings = false;
const char *language_text = valid_languages[current_language];
save_global_settings(get_steam_client()->local_storage, username_text, language_text);
get_steam_client()->settings_client->set_local_name(username_text);
get_steam_client()->settings_server->set_local_name(username_text);
get_steam_client()->settings_client->set_language(language_text);
get_steam_client()->settings_server->set_language(language_text);
steamFriends->resend_friend_data();
}
i_have_lobby = got_lobby();
std::for_each(friends.begin(), friends.end(), [this](std::pair<Friend const, friend_window_state> &i)
{
i.second.joinable = is_friend_joinable(i);
});
while (!has_friend_action.empty()) {
auto friend_info = friends.find(has_friend_action.front());
if (friend_info != friends.end()) {
uint64 friend_id = (uint64)friend_info->first.id();
// The user clicked on "Send"
if (friend_info->second.window_state & window_state_send_message) {
char* input = friend_info->second.chat_input;
char* end_input = input + strlen(input);
char* printable_char = std::find_if(input, end_input, [](char c) {
return std::isgraph(c);
});
// Check if the message contains something else than blanks
if (printable_char != end_input) {
// Handle chat send
Common_Message msg;
Steam_Messages* steam_messages = new Steam_Messages;
steam_messages->set_type(Steam_Messages::FRIEND_CHAT);
steam_messages->set_message(friend_info->second.chat_input);
msg.set_allocated_steam_messages(steam_messages);
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
msg.set_dest_id(friend_id);
network->sendTo(&msg, true);
friend_info->second.chat_history.append(get_steam_client()->settings_client->get_local_name()).append(": ").append(input).append("\n", 1);
}
*input = 0; // Reset the input field
friend_info->second.window_state &= ~window_state_send_message;
}
// The user clicked on "Invite" (but invite all wasn't clicked)
if (friend_info->second.window_state & window_state_invite) {
invite_friend(friend_id, steamFriends, steamMatchmaking);
friend_info->second.window_state &= ~window_state_invite;
}
// The user clicked on "Join"
if (friend_info->second.window_state & window_state_join) {
std::string connect = steamFriends->GetFriendRichPresence(friend_id, "connect");
// The user got a lobby invite and accepted it
if (friend_info->second.window_state & window_state_lobby_invite) {
GameLobbyJoinRequested_t data;
data.m_steamIDLobby.SetFromUint64(friend_info->second.lobbyId);
data.m_steamIDFriend.SetFromUint64(friend_id);
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
friend_info->second.window_state &= ~window_state_lobby_invite;
} else {
// The user got a rich presence invite and accepted it
if (friend_info->second.window_state & window_state_rich_invite) {
GameRichPresenceJoinRequested_t data = {};
data.m_steamIDFriend.SetFromUint64(friend_id);
strncpy(data.m_rgchConnect, friend_info->second.connect, k_cchMaxRichPresenceValueLength - 1);
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
friend_info->second.window_state &= ~window_state_rich_invite;
} else if (connect.length() > 0) {
GameRichPresenceJoinRequested_t data = {};
data.m_steamIDFriend.SetFromUint64(friend_id);
strncpy(data.m_rgchConnect, connect.c_str(), k_cchMaxRichPresenceValueLength - 1);
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
}
//Not sure about this but it fixes sonic racing transformed invites
FriendGameInfo_t friend_game_info = {};
steamFriends->GetFriendGamePlayed(friend_id, &friend_game_info);
uint64 lobby_id = friend_game_info.m_steamIDLobby.ConvertToUint64();
if (lobby_id) {
GameLobbyJoinRequested_t data;
data.m_steamIDLobby.SetFromUint64(lobby_id);
data.m_steamIDFriend.SetFromUint64(friend_id);
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
}
}
friend_info->second.window_state &= ~window_state_join;
}
}
has_friend_action.pop();
}
// if variable == true, then set it to false and return true (because state was changed) in that case
bool yes_clicked = true;
if (invite_all_friends_clicked.compare_exchange_weak(yes_clicked, false)) {
PRINT_DEBUG("Steam_Overlay will send invitations to [%zu] friends if they're using the same app\n", friends.size());
uint32 current_appid = settings->get_local_game_id().AppID();
for (auto &fr : friends) {
if (fr.first.appid() == current_appid) { // friend is playing the same game
uint64 friend_id = (uint64)fr.first.id();
invite_friend(friend_id, steamFriends, steamMatchmaking);
}
}
}
}
#endif #endif