Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dll/dll/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ class Settings {
bool overlay_always_show_playtime = false;
// keys used to toggle the overlay, default = Shift + Tab
std::vector<std::string> overlay_toggle_keys{};
// minimum time interval between achievement notifications (in milliseconds)
int achievement_notification_delay_ms = 0;

// free weekend
bool free_weekend = false;
Expand Down
6 changes: 5 additions & 1 deletion dll/settings_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,10 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti
} else if (name.compare("Achievement_Unlock_Datetime_Format") == 0) {
settings_client->overlay_appearance.ach_unlock_datetime_format = value;
settings_server->overlay_appearance.ach_unlock_datetime_format = value;
} else if (name.compare("Achievement_Notification_Delay") == 0) {
float delay_sec = std::stof(value, NULL);
settings_client->achievement_notification_delay_ms = static_cast<int>(delay_sec * 1000.0f);
settings_server->achievement_notification_delay_ms = static_cast<int>(delay_sec * 1000.0f);
} else if (name.compare("Background_R") == 0) {
float nbackground_r = std::stof(value, NULL);
settings_client->overlay_appearance.background_r = nbackground_r;
Expand Down Expand Up @@ -1644,7 +1648,7 @@ static void parse_overlay_general_config(class Settings *settings_client, class
settings_server->overlay_fps_avg_window = val;
}
}

}

// main::misc::steam_game_stats_reports_dir
Expand Down
15 changes: 15 additions & 0 deletions overlay_experimental/overlay/steam_overlay.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "dll/base.h"
#include <map>
#include <queue>
#include <deque>

#ifdef EMU_OVERLAY

Expand Down Expand Up @@ -148,6 +149,16 @@ class Steam_Overlay
// used when the button "Invite all" is clicked
std::atomic<bool> invite_all_friends_clicked = false;

// Rate-limiting queue for achievement notifications
struct ScheduledAchievement {
Overlay_Achievement ach;
bool for_progress;
std::chrono::milliseconds trigger_time; // when the achievement was triggered
std::chrono::milliseconds scheduled_show_time; // when the notification should be shown
};
std::deque<ScheduledAchievement> achievement_queue{};
std::chrono::milliseconds last_scheduled_show_time{}; // tracks the last scheduled show time for spacing

bool overlay_state_changed = false;

std::atomic<bool> i_have_lobby = false;
Expand Down Expand Up @@ -290,6 +301,9 @@ class Steam_Overlay
void FriendDisconnect(Friend _friend);

void AddAchievementNotification(const std::string &ach_name, nlohmann::json const& ach, bool for_progress);

// Rate-limiting queue functions
void process_achievement_queue();
};

#else // EMU_OVERLAY
Expand Down Expand Up @@ -324,6 +338,7 @@ class Steam_Overlay
void FriendDisconnect(Friend _friend) {}

void AddAchievementNotification(const std::string &ach_name, nlohmann::json const& ach, bool for_progress) {}
void process_achievement_queue() {}
};

#endif // EMU_OVERLAY
Expand Down
96 changes: 84 additions & 12 deletions overlay_experimental/steam_overlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -735,8 +735,7 @@ void Steam_Overlay::show_test_achievement()
}

post_achievement_notification(ach, for_progress);
// here we always play the sound for testing
notify_sound_user_achievement();
// sound is now played when notification is actually shown (delayed with queue)
}

void Steam_Overlay::build_friend_context_menu(Friend const& frd, friend_window_state& state)
Expand Down Expand Up @@ -1319,16 +1318,86 @@ void Steam_Overlay::post_achievement_notification(Overlay_Achievement &ach, bool
PRINT_DEBUG_ENTRY();
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!Ready()) return;
// Get current time
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());

// Calculate scheduled show time based on rate limiting
std::chrono::milliseconds scheduled_show_time;
int delay_ms = settings->achievement_notification_delay_ms;

PRINT_DEBUG("Achievement delay: %d ms", delay_ms);

if (delay_ms <= 0) {
// No delay - show immediately
scheduled_show_time = now;
} else {
// Apply rate limiting: earliest show time is last_scheduled_show_time + delay
scheduled_show_time = std::max(now, last_scheduled_show_time + std::chrono::milliseconds(delay_ms));
}

// Create scheduled achievement entry
ScheduledAchievement scheduled_ach;
scheduled_ach.ach = ach;
scheduled_ach.for_progress = for_progress;
scheduled_ach.trigger_time = now;
scheduled_ach.scheduled_show_time = scheduled_show_time;

// Add to queue
achievement_queue.push_back(scheduled_ach);

// Update last scheduled show time for next item
last_scheduled_show_time = scheduled_show_time;

PRINT_DEBUG("Achievement queued: '%s', scheduled for %lld ms, delay=%d ms",
ach.name.c_str(), (long long)scheduled_show_time.count(), delay_ms);
}

void Steam_Overlay::process_achievement_queue()
{
std::lock_guard<std::recursive_mutex> lock(overlay_mutex);
if (!Ready()) return;
if (achievement_queue.empty()) return;

// Get current time
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());

// Process all ready achievements
while (!achievement_queue.empty()) {
auto& scheduled_ach = achievement_queue.front();

// Check if it's time to show this notification
PRINT_DEBUG("Queue check: scheduled=%lld, now=%lld, diff=%lld",
(long long)scheduled_ach.scheduled_show_time.count(),
(long long)now.count(),
(long long)(now - scheduled_ach.scheduled_show_time).count());
if (scheduled_ach.scheduled_show_time <= now) {
// Show the notification
bool achieved = !scheduled_ach.for_progress;
// force upload to GPU if the pagination is request-based
try_load_ach_icon(scheduled_ach.ach, achieved, settings->paginated_achievements_icons == 0);

submit_notification(
scheduled_ach.for_progress ? notification_type::achievement_progress : notification_type::achievement,
scheduled_ach.ach.title + "\n" + scheduled_ach.ach.description,
{},
&scheduled_ach.ach
);

// Play sound when notification is actually shown (delayed with queue)
notify_sound_user_achievement();

PRINT_DEBUG("Achievement shown: '%s' at %lld ms",
scheduled_ach.ach.name.c_str(), (long long)now.count());

// Remove from queue
achievement_queue.pop_front();
} else {
// This achievement is not ready yet, and queue is ordered by scheduled time,
// so no more achievements will be ready either
break;
}
}

bool achieved = !for_progress; // for progress notifications we want to load the gray icon
// force upload to GPU if the pagination is request-based
try_load_ach_icon(ach, achieved, settings->paginated_achievements_icons == 0);
submit_notification(
for_progress ? notification_type::achievement_progress : notification_type::achievement,
ach.title + "\n" + ach.description,
{},
&ach
);
}

bool Steam_Overlay::try_load_ach_icon(Overlay_Achievement &ach, bool achieved, bool upload_new_icon_to_gpu)
Expand Down Expand Up @@ -1366,6 +1435,9 @@ void Steam_Overlay::overlay_render_proc()

if (!Ready()) return;

// Process achievement queue to show scheduled notifications
process_achievement_queue();

if (show_overlay) {
render_main_window();
}
Expand Down Expand Up @@ -2029,7 +2101,7 @@ void Steam_Overlay::AddAchievementNotification(const std::string &ach_name, nloh

if (a.achieved && !for_progress) { // here we don't show the progress indications
post_achievement_notification(a, for_progress);
notify_sound_user_achievement();
// sound is now played when notification is actually shown (delayed with queue)
} else if (for_progress && !settings->disable_overlay_achievement_progress) { // progress indication is shown for locked achievements only
// post notification if this isn't a progress, or a progress and the user didn't disable these notifications
post_achievement_notification(a, for_progress);
Expand Down
5 changes: 5 additions & 0 deletions post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ Notification_Duration_Invitation=8.0
# duration of chat message
Notification_Duration_Chat=4.0

# minimum time interval between achievement notifications (in seconds)
# set to 0 to show all achievements immediately without delay
# default=0
Achievement_Notification_Delay=0

# format for the achievement unlock date/time, limited to 79 characters
# if the output formatted string exceeded this limit, the builtin format will be used
# look for the format here: https://en.cppreference.com/w/cpp/chrono/c/strftime
Expand Down