Overlay: Implement achievement icon display.

Display achievement icons in steam_overlay, and during achievement
notifications.

Add total achievement counts and progress bar to steam_overlay.

Add new global settings for displaying unearned hidden achievements in
the achievement list and displaying achievement descriptions in unlock
notifications. (Both disabled by default.)
This commit is contained in:
redpolline 2024-11-15 13:24:34 -05:00
parent 59b8354f2d
commit 5241fc892f
4 changed files with 316 additions and 34 deletions

View File

@ -334,10 +334,25 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s
bool disable_networking = false;
bool disable_overlay = false;
bool disable_lobby_creation = false;
bool enable_achievement_desc_on_unlock = false;
bool enable_displaying_hidden_achievements = false;
int build_id = 10;
bool warn_forced = false;
{
std::string steam_settings_path = local_storage->get_global_settings_path();
std::vector<std::string> paths = local_storage->get_filenames_path(steam_settings_path);
for (auto & p: paths) {
if (p == "enable_achievement_desc_on_unlock.txt") {
enable_achievement_desc_on_unlock = true;
} else if (p == "enable_displaying_hidden_achievements.txt") {
enable_displaying_hidden_achievements = true;
}
}
}
{
std::string steam_settings_path = Local_Storage::get_game_settings_path();
@ -352,6 +367,10 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s
disable_overlay = true;
} else if (p == "disable_lobby_creation.txt") {
disable_lobby_creation = true;
} else if (p == "enable_achievement_desc_on_unlock.txt") {
enable_achievement_desc_on_unlock = true;
} else if (p == "enable_displaying_hidden_achievements.txt") {
enable_displaying_hidden_achievements = true;
} else if (p == "force_language.txt") {
int len = Local_Storage::get_file_data(steam_settings_path + "force_language.txt", language, sizeof(language) - 1);
if (len > 0) {
@ -410,6 +429,10 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s
settings_server->warn_local_save = local_save;
settings_client->supported_languages = supported_languages;
settings_server->supported_languages = supported_languages;
settings_client->set_show_achievement_desc_on_unlock(enable_achievement_desc_on_unlock);
settings_server->set_show_achievement_desc_on_unlock(enable_achievement_desc_on_unlock);
settings_client->set_show_achievement_hidden_unearned(enable_displaying_hidden_achievements);
settings_server->set_show_achievement_hidden_unearned(enable_displaying_hidden_achievements);
{
std::string dlc_config_path = Local_Storage::get_game_settings_path() + "DLC.txt";
@ -666,8 +689,31 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s
return appid;
}
void save_global_settings(Local_Storage *local_storage, char *name, char *language)
void save_global_settings(Local_Storage *local_storage, Settings * client_settings)
{
local_storage->store_data_settings("account_name.txt", name, strlen(name));
local_storage->store_data_settings("language.txt", language, strlen(language));
if ((local_storage != nullptr) && (client_settings != nullptr)) {
std::string name = client_settings->get_local_name();
std::string language = client_settings->get_language();
local_storage->store_data_settings("account_name.txt", (char*)name.c_str(), name.length());
local_storage->store_data_settings("language.txt", (char*)language.c_str(), language.length());
if (client_settings->get_show_achievement_desc_on_unlock()) {
if (local_storage->data_settings_exists("enable_achievement_desc_on_unlock.txt") != true) {
local_storage->store_data_settings("enable_achievement_desc_on_unlock.txt", " ", sizeof(" "));
}
} else {
if (local_storage->data_settings_exists("enable_achievement_desc_on_unlock.txt") == true) {
local_storage->delete_data_settings("enable_achievement_desc_on_unlock.txt");
}
}
if (client_settings->get_show_achievement_hidden_unearned()) {
if (local_storage->data_settings_exists("enable_displaying_hidden_achievements.txt") != true) {
local_storage->store_data_settings("enable_displaying_hidden_achievements.txt", " ", sizeof(" "));
}
} else {
if (local_storage->data_settings_exists("enable_displaying_hidden_achievements.txt") == true) {
local_storage->delete_data_settings("enable_displaying_hidden_achievements.txt");
}
}
}
}

View File

@ -22,6 +22,6 @@
//returns appid
uint32 create_localstorage_settings(Settings **settings_client_out, Settings **settings_server_out, Local_Storage **local_storage_out);
void save_global_settings(Local_Storage *local_storage, char *name, char *language);
void save_global_settings(Local_Storage *local_storage, Settings *client_settings);
#endif

View File

@ -100,6 +100,85 @@ int find_free_notification_id(std::vector<Notification> const& notifications)
#include "notification.h"
void Steam_Overlay::LoadAchievementImage(Overlay_Achievement & ach)
{
PRINT_DEBUG("LoadAchievementImage() %s.\n", ach.name.c_str());
// Image load.
Steam_Utils* steamUtils = get_steam_client()->steam_utils;
Steam_User_Stats* steamUserStats = get_steam_client()->steam_user_stats;
int32 image_handle = steamUserStats->GetAchievementIcon(ach.name.c_str());
if (image_handle != 0) {
uint32 width = 0;
uint32 height = 0;
if ((steamUtils->GetImageSize(image_handle, &width, &height) == true) &&
(width > 0) && (height > 0)) {
PRINT_DEBUG("LoadAchievementImage() %d %d %d.\n", image_handle, width, height);
if (ach.raw_image != NULL) {
delete ach.raw_image;
ach.raw_image = NULL;
ach.raw_image_width = 0;
ach.raw_image_height = 0;
}
DestroyAchievementImageResource(ach);
uint8 * raw_image = new uint8[(width * height * sizeof(uint32))];
if ((raw_image != NULL) &&
(steamUtils->GetImageRGBA(image_handle,
raw_image,
(width * height * sizeof(uint32))) == true)) {
PRINT_DEBUG("LoadAchievementImage() %d -> %p.\n", image_handle, raw_image);
ach.raw_image = raw_image;
ach.raw_image_width = width;
ach.raw_image_height = height;
} else {
delete ach.raw_image;
PRINT_DEBUG("Image for achievement %s could not get pixel data.\n", ach.name.c_str());
}
} else {
PRINT_DEBUG("Image for achievement %s has an invalid size.\n", ach.name.c_str());
}
} else {
PRINT_DEBUG("Image for achievement %s is not loaded.\n", ach.name.c_str());
}
}
void Steam_Overlay::CreateAchievementImageResource(Overlay_Achievement & ach)
{
PRINT_DEBUG("CreateAchievementImageResource() %s. %d x %d -> %p\n", ach.name.c_str(), ach.raw_image_width, ach.raw_image_height, ach.raw_image);
if (_renderer) {
if (ach.raw_image != NULL && ach.raw_image_width > 0 && ach.raw_image_height > 0 && ach.image_resource.expired() == true) {
std::weak_ptr<uint64_t> test;
test = _renderer->CreateImageResource(ach.raw_image,
ach.raw_image_width,
ach.raw_image_height);
ach.image_resource = test;
std::shared_ptr<uint64_t> test2;
test2 = test.lock();
if (!test2) {
PRINT_DEBUG("CreateAchievementImageResource() Unable to create resource for %s.\n", ach.name.c_str());
} else {
PRINT_DEBUG("CreateAchievementImageResource() created resource for %s -> %PRIu64.\n", ach.name.c_str(), *test2);
}
} else {
PRINT_DEBUG("CreateAchievementImageResource() invalid raw data for %s.\n", ach.name.c_str());
}
}
}
void Steam_Overlay::DestroyAchievementImageResource(Overlay_Achievement & ach)
{
PRINT_DEBUG("DestroyAchievementImageResource() %s.\n", ach.name.c_str());
if (_renderer && ach.image_resource.expired() == false) {
_renderer->ReleaseImageResource(ach.image_resource);
}
}
void Steam_Overlay::steam_overlay_run_every_runcb(void* object)
{
Steam_Overlay* _this = reinterpret_cast<Steam_Overlay*>(object);
@ -129,10 +208,14 @@ Steam_Overlay::Steam_Overlay(Settings* settings, SteamCallResults* callback_resu
show_achievements(false),
show_settings(false),
_renderer(nullptr),
fonts_atlas(nullptr)
fonts_atlas(nullptr),
earned_achievement_count(0)
{
strncpy(username_text, settings->get_local_name(), sizeof(username_text));
show_achievement_desc_on_unlock = settings->get_show_achievement_desc_on_unlock();
show_achievement_hidden_unearned = settings->get_show_achievement_hidden_unearned();
if (settings->warn_forced) {
this->disable_forced = true;
this->warning_forced = true;
@ -167,6 +250,17 @@ Steam_Overlay::Steam_Overlay(Settings* settings, SteamCallResults* callback_resu
Steam_Overlay::~Steam_Overlay()
{
run_every_runcb->remove(&Steam_Overlay::steam_overlay_run_every_runcb, this);
if (achievements.size()) {
for (auto & x : achievements) {
if (x.raw_image != NULL) {
delete x.raw_image;
x.raw_image = NULL;
x.raw_image_width = 0;
x.raw_image_height = 0;
}
DestroyAchievementImageResource(x);
}
}
}
bool Steam_Overlay::Ready() const
@ -422,8 +516,28 @@ void Steam_Overlay::AddAchievementNotification(nlohmann::json const& ach)
Notification notif;
notif.id = id;
notif.type = notification_type_achievement;
// Load achievement image
notif.message = ach["displayName"].get<std::string>() + "\n" + ach["description"].get<std::string>();
// Achievement image
if (achievements.size() > 0) {
for (auto & x : achievements) {
if (x.name == ach["name"].get<std::string>()) {
// Reload the image due to the pop.
LoadAchievementImage(x);
DestroyAchievementImageResource(x);
// Cannot call CreateAchievementImageResource(x) here. OpenGL displays bad texture.
notif.ach_name = x.name;
}
}
}
// Achievement count.
if (total_achievement_count > earned_achievement_count) {
earned_achievement_count++;
}
notif.message = "Achievement Unlocked!\n";
if (show_achievement_desc_on_unlock) {
notif.message += ach["displayName"].get<std::string>() + "\n" + ach["description"].get<std::string>();
} else {
notif.message += "\n" + ach["displayName"].get<std::string>();
}
notif.start_time = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
notifications.emplace_back(notif);
have_notifications = true;
@ -636,46 +750,85 @@ void Steam_Overlay::BuildNotifications(int width, int height)
std::queue<Friend> friend_actions_temp;
ImVec2 notification_rez(((Notification::width >= 1.0f) ? (width / Notification::width) : (width * Notification::width)),
((Notification::height >= 1.0f) ? (height / Notification::height) : (height * Notification::height))); // calc notification screen size.
{
std::lock_guard<std::recursive_mutex> lock(notifications_mutex);
for (auto it = notifications.begin(); it != notifications.end(); ++it, ++i)
{
auto elapsed_notif = now - it->start_time;
float alpha = 0;
ImGuiStyle currentStyle = ImGui::GetStyle();
if ( elapsed_notif < Notification::fade_in)
{
float alpha = Notification::max_alpha * (elapsed_notif.count() / static_cast<float>(Notification::fade_in.count()));
alpha = Notification::max_alpha * (elapsed_notif.count() / static_cast<float>(Notification::fade_in.count()));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, alpha));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(Notification::r, Notification::g, Notification::b, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, alpha*2));
}
else if ( elapsed_notif > Notification::fade_out_start)
{
float alpha = Notification::max_alpha * ((Notification::show_time - elapsed_notif).count() / static_cast<float>(Notification::fade_out.count()));
alpha = Notification::max_alpha * ((Notification::show_time - elapsed_notif).count() / static_cast<float>(Notification::fade_out.count()));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, alpha));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(Notification::r, Notification::g, Notification::b, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, alpha*2));
}
else
{
alpha = Notification::max_alpha;
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, Notification::max_alpha));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(Notification::r, Notification::g, Notification::b, Notification::max_alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, Notification::max_alpha*2));
}
ImGui::SetNextWindowPos(ImVec2((float)width - width * Notification::width, Notification::height * font_size * i ));
ImGui::SetNextWindowSize(ImVec2( width * Notification::width, Notification::height * font_size ));
ImGui::Begin(std::to_string(it->id).c_str(), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoDecoration);
// Notification image.
ImVec4 image_color_multipler(1, 1, 1, (alpha*2)); // fade in / out.
ImVec2 image_offset(((notification_rez.x * 0.04f) + currentStyle.FramePadding.x), ((notification_rez.y * 0.04f) + currentStyle.FramePadding.y)); // calc border.
ImVec2 image_max_resolution((notification_rez.x - image_offset.x), (notification_rez.y - image_offset.y)); // calc total space for image.
if (image_max_resolution.x > image_max_resolution.y) { // fix image aspect ratio. (square)
image_max_resolution.x = image_max_resolution.x - (image_max_resolution.x - image_max_resolution.y);
} else {
image_max_resolution.y = image_max_resolution.y - (image_max_resolution.y - image_max_resolution.x);
}
ImVec2 text_offset(image_offset.x, image_offset.y);
ImGui::SetCursorPos(image_offset);
switch (it->type)
{
case notification_type_achievement:
for (auto &a : achievements) {
if (a.name == it->ach_name) {
CreateAchievementImageResource(a);
if ((a.image_resource.expired() == false) &&
(a.raw_image_width > 0) &&
(a.raw_image_height > 0)) {
ImVec2 image_scale(image_max_resolution.x * 0.4f,
image_max_resolution.y * 0.4f);
// Fix text offset.
text_offset.x = image_offset.x + currentStyle.ItemSpacing.x + image_scale.x;
std::shared_ptr<uint64_t> s_ptr = a.image_resource.lock();
ImGui::Image((ImTextureID)(intptr_t)*s_ptr, image_scale, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), image_color_multipler);
}
break;
}
}
ImGui::SetCursorPos(text_offset);
ImGui::PushTextWrapPos(0.0f);
ImGui::TextWrapped("%s", it->message.c_str());
ImGui::PopTextWrapPos();
break;
case notification_type_invite:
{
ImGui::SetCursorPos(text_offset);
ImGui::TextWrapped("%s", it->message.c_str());
if (ImGui::Button("Join"))
{
@ -686,6 +839,7 @@ void Steam_Overlay::BuildNotifications(int width, int height)
}
break;
case notification_type_message:
ImGui::SetCursorPos(text_offset);
ImGui::TextWrapped("%s", it->message.c_str()); break;
}
@ -771,6 +925,17 @@ void Steam_Overlay::CreateFonts()
reset_LastError();
}
void Steam_Overlay::DestroyAchievementImageResources()
{
for (auto & x : achievements) {
if (x.image_resource.expired()) {
DestroyAchievementImageResource(x);
}
}
return;
}
// Try to make this function as short as possible or it might affect game's fps.
void Steam_Overlay::OverlayProc()
{
@ -809,6 +974,10 @@ void Steam_Overlay::OverlayProc()
ImGui::LabelText("##label", "Renderer: %s", (_renderer == nullptr ? "Unknown" : _renderer->GetLibraryName().c_str()));
ImGui::Text("Achievements earned: %d / %d", earned_achievement_count, total_achievement_count);
ImGui::SameLine();
ImGui::ProgressBar((earned_achievement_count / total_achievement_count), ImVec2((io.DisplaySize.x * 0.20f),0));
ImGui::Spacing();
if (ImGui::Button("Show Achievements")) {
show_achievements = true;
@ -854,28 +1023,66 @@ void Steam_Overlay::OverlayProc()
if (ImGui::Begin("Achievement Window", &show)) {
ImGui::Text("List of achievements");
ImGui::BeginChild("Achievements");
ImGuiStyle currentStyle = ImGui::GetStyle();
float window_x_offset = (ImGui::GetWindowWidth() > currentStyle.ScrollbarSize) ? (ImGui::GetWindowWidth() - currentStyle.ScrollbarSize) : 0;
for (auto & x : achievements) {
bool achieved = x.achieved;
bool hidden = x.hidden && !achieved;
ImGui::Separator();
ImGui::Text("%s", x.title.c_str());
if (hidden) {
ImGui::Text("hidden achievement");
} else {
ImGui::TextWrapped("%s", x.description.c_str());
}
if (!hidden || show_achievement_hidden_unearned) {
if (achieved) {
char buffer[80] = {};
time_t unlock_time = (time_t)x.unlock_time;
std::strftime(buffer, 80, "%Y-%m-%d at %H:%M:%S", std::localtime(&unlock_time));
if (x.raw_image == NULL) {
LoadAchievementImage(x);
}
ImGui::TextColored(ImVec4(0, 255, 0, 255), "achieved on %s", buffer);
} else {
ImGui::TextColored(ImVec4(255, 0, 0, 255), "not achieved");
if (x.image_resource.expired() == true) {
CreateAchievementImageResource(x);
}
ImGui::Separator();
// Anchor the image to the right side of the list.
ImVec2 target = ImGui::GetCursorPos();
target.x = window_x_offset;
if (target.x > (x.raw_image_width * 0.4f)) {
target.x = target.x - (x.raw_image_width * 0.4f);
} else {
target.x = 0;
}
ImGui::PushTextWrapPos(target.x);
ImGui::Text("%s", x.title.c_str());
if (hidden) {
ImGui::Text("Hidden Achievement");
} else {
ImGui::Text("%s", x.description.c_str());
}
ImGui::PopTextWrapPos();
if (achieved) {
char buffer[80] = {};
time_t unlock_time = (time_t)x.unlock_time;
std::strftime(buffer, 80, "%Y-%m-%d at %H:%M:%S", std::localtime(&unlock_time));
ImGui::TextColored(ImVec4(0, 255, 0, 255), "achieved on %s", buffer);
} else {
ImGui::TextColored(ImVec4(255, 0, 0, 255), "not achieved");
}
// Set cursor for image output.
if (target.x != 0) {
ImGui::SetCursorPos(target);
}
if ((x.image_resource.expired() == false) &&
(x.raw_image_width > 0) &&
(x.raw_image_height > 0)) {
std::shared_ptr<uint64_t> s_ptr = x.image_resource.lock();
ImGui::Image((ImTextureID)(intptr_t)*s_ptr, ImVec2(x.raw_image_width * 0.4f, x.raw_image_height * 0.4f));
}
ImGui::Separator();
}
ImGui::Separator();
}
ImGui::EndChild();
}
@ -905,6 +1112,11 @@ void Steam_Overlay::OverlayProc()
ImGui::Separator();
ImGui::Checkbox("Show achievement descriptions on unlock", &show_achievement_desc_on_unlock);
ImGui::Checkbox("Show unearned hidden achievements", &show_achievement_hidden_unearned);
ImGui::Separator();
if (!disable_forced) {
ImGui::Text("You may have to restart the game for these to apply.");
if (ImGui::Button("Save")) {
@ -1004,15 +1216,16 @@ void Steam_Overlay::RunCallbacks()
{
if (!achievements.size()) {
Steam_User_Stats* steamUserStats = get_steam_client()->steam_user_stats;
uint32 achievements_num = steamUserStats->GetNumAchievements();
if (achievements_num) {
total_achievement_count = steamUserStats->GetNumAchievements();
if (total_achievement_count) {
PRINT_DEBUG("POPULATE OVERLAY ACHIEVEMENTS\n");
for (unsigned i = 0; i < achievements_num; ++i) {
for (unsigned i = 0; i < total_achievement_count; ++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 (strlen(hidden) && hidden[0] == '1') {
ach.hidden = true;
} else {
@ -1024,11 +1237,19 @@ void Steam_Overlay::RunCallbacks()
if (steamUserStats->GetAchievementAndUnlockTime(ach.name.c_str(), &achieved, &unlock_time)) {
ach.achieved = achieved;
ach.unlock_time = unlock_time;
if ((achieved == true) && (total_achievement_count > earned_achievement_count)) {
earned_achievement_count++;
}
} else {
ach.achieved = false;
ach.unlock_time = 0;
}
ach.raw_image = nullptr;
ach.raw_image_width = 0;
ach.raw_image_height = 0;
LoadAchievementImage(ach);
achievements.push_back(ach);
}
@ -1062,7 +1283,7 @@ void Steam_Overlay::RunCallbacks()
data.m_bUserInitiated = true;
data.m_nAppID = settings->get_local_game_id().AppID();
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
DestroyAchievementImageResources(); // Don't recreate them here. OpenGL won't display them if we do.
overlay_state_changed = false;
}
@ -1071,11 +1292,15 @@ void Steam_Overlay::RunCallbacks()
if (save_settings) {
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_client->set_show_achievement_desc_on_unlock(show_achievement_desc_on_unlock);
get_steam_client()->settings_client->set_show_achievement_hidden_unearned(show_achievement_hidden_unearned);
get_steam_client()->settings_server->set_local_name(username_text);
get_steam_client()->settings_server->set_language(language_text);
get_steam_client()->settings_server->set_show_achievement_desc_on_unlock(show_achievement_desc_on_unlock);
get_steam_client()->settings_server->set_show_achievement_hidden_unearned(show_achievement_hidden_unearned);
save_global_settings(get_steam_client()->local_storage, get_steam_client()->settings_client);
steamFriends->resend_friend_data();
save_settings = false;
}

View File

@ -68,6 +68,7 @@ struct Notification
std::chrono::seconds start_time;
std::string message;
std::pair<const Friend, friend_window_state>* frd;
std::string ach_name;
};
struct Overlay_Achievement
@ -78,6 +79,10 @@ struct Overlay_Achievement
bool hidden;
bool achieved;
uint32 unlock_time;
uint8 * raw_image;
uint32 raw_image_width;
uint32 raw_image_height;
std::weak_ptr<uint64_t> image_resource;
};
#ifdef EMU_OVERLAY
@ -104,8 +109,8 @@ class Steam_Overlay
bool show_achievements, show_settings;
void *fonts_atlas;
bool disable_forced, local_save, warning_forced;
uint32_t appid;
bool disable_forced, local_save, warning_forced, show_achievement_desc_on_unlock, show_achievement_hidden_unearned;
uint32_t appid, total_achievement_count, earned_achievement_count;
char username_text[256];
std::atomic_bool save_settings;
@ -149,6 +154,12 @@ class Steam_Overlay
void BuildFriendWindow(Friend const& frd, friend_window_state &state);
// Notifications like achievements, chat and invitations
void BuildNotifications(int width, int height);
void LoadAchievementImage(Overlay_Achievement & ach);
void CreateAchievementImageResource(Overlay_Achievement & ach);
void DestroyAchievementImageResource(Overlay_Achievement & ach);
void DestroyAchievementImageResources();
public:
Steam_Overlay(Settings* settings, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking *network);