* allow setting the achievements notifications via config file

* fixed a mistake when discarding ut8 bom marker
This commit is contained in:
otavepto 2024-01-26 05:49:03 +02:00
parent 7e595ce1d9
commit 1bba2c09b0
8 changed files with 250 additions and 85 deletions

View File

@ -1,4 +1,12 @@
* **[Detanup01]** added a new command line option for the tool `generate_emu_config` to disable the generation of `disable_xxx.txt` files * **[Detanup01]** added a new command line option for the tool `generate_emu_config` to disable the generation of `disable_xxx.txt` files,
suggested by **[Vlxst]**
* added new settings to the overlay which allow specifying the notifications positions, check the example file `overlay_appearance.EXAMPLE.txt`
* fixed a mistake when discarding the utf8 bom marker
---
## 2024/1/25
* added new options to the overlay to allow copying a friend's ID, plus current player ID, suggested by **[Vlxst]** * added new options to the overlay to allow copying a friend's ID, plus current player ID, suggested by **[Vlxst]**
* added a new option to the overlay to invite all friends playing the same game, suggested by **[Vlxst]** * added a new option to the overlay to invite all friends playing the same game, suggested by **[Vlxst]**
* added new `auto_accept_invite.txt` setting to automatically accept game/lobby invites from this list, each SteamID64 on a separate line * added new `auto_accept_invite.txt` setting to automatically accept game/lobby invites from this list, each SteamID64 on a separate line

View File

@ -166,6 +166,8 @@ static inline void reset_LastError()
#include "utfcpp/utf8.h" #include "utfcpp/utf8.h"
#include "controller/gamepad.h" #include "controller/gamepad.h"
constexpr const char * const whitespaces = " \t\r\n";
// common includes // common includes
#include "common_helpers/common_helpers.hpp" #include "common_helpers/common_helpers.hpp"
@ -247,12 +249,15 @@ static std::string uint8_vector_to_hex_string(std::vector<uint8_t>& v)
static inline void consume_bom(std::ifstream &input) static inline void consume_bom(std::ifstream &input)
{ {
int bom[3]; if (!input.is_open()) return;
auto pos = input.tellg();
int bom[3]{};
bom[0] = input.get(); bom[0] = input.get();
bom[1] = input.get(); bom[1] = input.get();
bom[2] = input.get(); bom[2] = input.get();
if (bom[0] != 0xEF || bom[1] != 0xBB || bom[2] != 0xBF) { if (bom[0] != 0xEF || bom[1] != 0xBB || bom[2] != 0xBF) {
input.seekg(-3, std::ios::cur); input.seekg(pos, std::ios::beg);
} }
} }
@ -281,6 +286,4 @@ static inline void consume_bom(std::ifstream &input)
#define LOBBY_CONNECT_APPID ((uint32)-2) #define LOBBY_CONNECT_APPID ((uint32)-2)
constexpr const char * const whitespaces = " \t\r\n";
#endif//__INCLUDED_COMMON_INCLUDES__ #endif//__INCLUDED_COMMON_INCLUDES__

View File

@ -111,6 +111,13 @@ struct Group_Clans {
}; };
struct Overlay_Appearance { struct Overlay_Appearance {
enum NotificationPosition {
top_left, top_center, top_right,
bot_left, bot_center, bot_right,
};
constexpr const static NotificationPosition default_pos = NotificationPosition::top_right;
float font_size = 16.0; float font_size = 16.0;
float icon_size = 64.0; float icon_size = 64.0;
float notification_r = 0.16; float notification_r = 0.16;
@ -133,6 +140,23 @@ struct Overlay_Appearance {
float element_active_g = -1.0; float element_active_g = -1.0;
float element_active_b = -1.0; float element_active_b = -1.0;
float element_active_a = -1.0; float element_active_a = -1.0;
NotificationPosition ach_earned_pos = default_pos; // achievement earned
NotificationPosition invite_pos = default_pos; // lobby/game invitation
NotificationPosition chat_msg_pos = default_pos; // chat message from a friend
static NotificationPosition translate_notification_position(const std::string &str)
{
if (str == "top_left") return NotificationPosition::top_left;
else if (str == "top_center") return NotificationPosition::top_center;
else if (str == "top_right") return NotificationPosition::top_right;
else if (str == "bot_left") return NotificationPosition::bot_left;
else if (str == "bot_center") return NotificationPosition::bot_center;
else if (str == "bot_right") return NotificationPosition::bot_right;
PRINT_DEBUG("Invalid position '%s'\n", str.c_str());
return default_pos;
}
}; };
class Settings { class Settings {
@ -261,7 +285,7 @@ public:
bool disable_overlay_warning_bad_appid = false; bool disable_overlay_warning_bad_appid = false;
// disable all overlay warnings // disable all overlay warnings
bool disable_overlay_warning_any = false; bool disable_overlay_warning_any = false;
Overlay_Appearance overlay_appearance; Overlay_Appearance overlay_appearance{};
//app build id //app build id
int build_id = 10; int build_id = 10;

View File

@ -35,8 +35,8 @@ static void load_subscribed_groups_clans(std::string clans_filepath, Settings *s
{ {
PRINT_DEBUG("Group clans file path: %s\n", clans_filepath.c_str()); PRINT_DEBUG("Group clans file path: %s\n", clans_filepath.c_str());
std::ifstream clans_file(utf8_decode(clans_filepath)); std::ifstream clans_file(utf8_decode(clans_filepath));
consume_bom(clans_file);
if (clans_file.is_open()) { if (clans_file.is_open()) {
consume_bom(clans_file);
std::string line; std::string line;
while (std::getline(clans_file, line)) { while (std::getline(clans_file, line)) {
if (line.length() < 0) continue; if (line.length() < 0) continue;
@ -73,22 +73,35 @@ static void load_subscribed_groups_clans(std::string clans_filepath, Settings *s
static void load_overlay_appearance(std::string appearance_filepath, Settings *settings_client, Settings *settings_server) static void load_overlay_appearance(std::string appearance_filepath, Settings *settings_client, Settings *settings_server)
{ {
PRINT_DEBUG("Overlay appearance file path: %s\n", appearance_filepath.c_str());
std::ifstream appearance_file(utf8_decode(appearance_filepath)); std::ifstream appearance_file(utf8_decode(appearance_filepath));
consume_bom(appearance_file);
if (appearance_file.is_open()) { if (appearance_file.is_open()) {
std::string line; PRINT_DEBUG("Parsing overlay appearance file: '%s'\n", appearance_filepath.c_str());
consume_bom(appearance_file);
std::string line{};
while (std::getline(appearance_file, line)) { while (std::getline(appearance_file, line)) {
if (line.length() < 0) continue; if (line.length() <= 0) continue;
std::size_t seperator = line.find(" "); std::size_t seperator = line.find(" ");
std::string name; std::string name{};
std::string value; std::string value{};
if (seperator != std::string::npos) { if (seperator != std::string::npos) {
name = line.substr(0, seperator); name = line.substr(0, seperator);
name.erase(0, name.find_first_not_of(whitespaces));
name.erase(name.find_last_not_of(whitespaces) + 1);
value = line.substr(seperator); value = line.substr(seperator);
value.erase(0, value.find_first_not_of(whitespaces));
value.erase(value.find_last_not_of(whitespaces) + 1);
} }
// comments
if (name.size() && (
name[0] == '#' || name[0] == ';' || name.compare(0, 2, "//") == 0
)) {
continue;
}
PRINT_DEBUG(" Overlay appearance line '%s' = '%s'\n", name.c_str(), value.c_str());
try { try {
if (name.compare("Font_Size") == 0) { if (name.compare("Font_Size") == 0) {
float nfont_size = std::stof(value, NULL); float nfont_size = std::stof(value, NULL);
@ -178,8 +191,20 @@ static void load_overlay_appearance(std::string appearance_filepath, Settings *s
float nelement_active_a = std::stof(value, NULL); float nelement_active_a = std::stof(value, NULL);
settings_client->overlay_appearance.element_active_a = nelement_active_a; settings_client->overlay_appearance.element_active_a = nelement_active_a;
settings_server->overlay_appearance.element_active_a = nelement_active_a; settings_server->overlay_appearance.element_active_a = nelement_active_a;
} else if (name.compare("PosAchievement") == 0) {
auto pos = Overlay_Appearance::translate_notification_position(value);
settings_client->overlay_appearance.ach_earned_pos = pos;
settings_server->overlay_appearance.ach_earned_pos = pos;
} else if (name.compare("PosInvitation") == 0) {
auto pos = Overlay_Appearance::translate_notification_position(value);
settings_client->overlay_appearance.invite_pos = pos;
settings_server->overlay_appearance.invite_pos = pos;
} else if (name.compare("PosChatMsg") == 0) {
auto pos = Overlay_Appearance::translate_notification_position(value);
settings_client->overlay_appearance.chat_msg_pos = pos;
settings_server->overlay_appearance.chat_msg_pos = pos;
} }
PRINT_DEBUG("Overlay appearance %s %s\n", name.c_str(), value.c_str());
} catch (...) { } } catch (...) { }
} }
} }

View File

@ -53,8 +53,8 @@ enum notification_type
struct Notification struct Notification
{ {
static constexpr float width = 0.25; static constexpr float width_percent = 0.25f; // percentage from total width
static constexpr float height = 5.0; static constexpr float height = 5.0f;
static constexpr std::chrono::milliseconds fade_in = std::chrono::milliseconds(2000); static constexpr std::chrono::milliseconds fade_in = std::chrono::milliseconds(2000);
static constexpr std::chrono::milliseconds fade_out = std::chrono::milliseconds(2000); static constexpr std::chrono::milliseconds fade_out = std::chrono::milliseconds(2000);
static constexpr std::chrono::milliseconds show_time = std::chrono::milliseconds(6000) + fade_in + fade_out; static constexpr std::chrono::milliseconds show_time = std::chrono::milliseconds(6000) + fade_in + fade_out;
@ -85,6 +85,12 @@ struct Overlay_Achievement
#ifdef EMU_OVERLAY #ifdef EMU_OVERLAY
#include <future> #include <future>
#include "Renderer_Hook.h" #include "Renderer_Hook.h"
struct NotificationsIndexes {
int top_left = 0, top_center = 0, top_right = 0;
int bot_left = 0, bot_center = 0, bot_right = 0;
};
class Steam_Overlay class Steam_Overlay
{ {
Settings* settings; Settings* settings;
@ -153,8 +159,8 @@ class Steam_Overlay
bool FriendJoinable(std::pair<const Friend, friend_window_state> &f); bool FriendJoinable(std::pair<const Friend, friend_window_state> &f);
bool IHaveLobby(); bool IHaveLobby();
void NotifyUser(friend_window_state& friend_state); void NotifySoundUserInvite(friend_window_state& friend_state);
void NotifyUserAchievement(); void NotifySoundUserAchievement();
void NotifySoundAutoAcceptFriendInvite(); void NotifySoundAutoAcceptFriendInvite();
// Right click on friend // Right click on friend
@ -162,6 +168,7 @@ class Steam_Overlay
// Double click on friend // Double click on friend
void BuildFriendWindow(Friend const& frd, friend_window_state &state); void BuildFriendWindow(Friend const& frd, friend_window_state &state);
// Notifications like achievements, chat and invitations // Notifications like achievements, chat and invitations
void SetNextNotificationPos(float width, float height, float font_size, notification_type type, struct NotificationsIndexes &idx);
void BuildNotifications(int width, int height); void BuildNotifications(int width, int height);
// invite a single friend // invite a single friend
void InviteFriend(uint64 friend_id, class Steam_Friends* steamFriends, class Steam_Matchmaking* steamMatchmaking); void InviteFriend(uint64 friend_id, class Steam_Friends* steamFriends, class Steam_Matchmaking* steamMatchmaking);

View File

@ -336,7 +336,7 @@ void Steam_Overlay::ShowOverlay(bool state)
overlay_state_changed = true; overlay_state_changed = true;
} }
void Steam_Overlay::NotifyUser(friend_window_state& friend_state) void Steam_Overlay::NotifySoundUserInvite(friend_window_state& friend_state)
{ {
if (settings->disable_overlay_friend_notification) return; if (settings->disable_overlay_friend_notification) return;
if (!(friend_state.window_state & window_state_show) || !show_overlay) if (!(friend_state.window_state & window_state_show) || !show_overlay)
@ -352,7 +352,7 @@ void Steam_Overlay::NotifyUser(friend_window_state& friend_state)
} }
} }
void Steam_Overlay::NotifyUserAchievement() void Steam_Overlay::NotifySoundUserAchievement()
{ {
if (settings->disable_overlay_achievement_notification) return; if (settings->disable_overlay_achievement_notification) return;
if (!show_overlay) if (!show_overlay)
@ -391,7 +391,7 @@ void Steam_Overlay::SetLobbyInvite(Friend friendId, uint64 lobbyId)
// Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows) // Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows)
frd.window_state &= ~window_state_rich_invite; frd.window_state &= ~window_state_rich_invite;
AddInviteNotification(*i); AddInviteNotification(*i);
NotifyUser(i->second); NotifySoundUserInvite(i->second);
} }
} }
@ -410,7 +410,7 @@ void Steam_Overlay::SetRichInvite(Friend friendId, const char* connect_str)
// Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows) // Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows)
frd.window_state &= ~window_state_lobby_invite; frd.window_state &= ~window_state_lobby_invite;
AddInviteNotification(*i); AddInviteNotification(*i);
NotifyUser(i->second); NotifySoundUserInvite(i->second);
} }
} }
@ -487,7 +487,7 @@ void Steam_Overlay::AddAchievementNotification(nlohmann::json const& ach)
notif.message = ach["displayName"].get<std::string>() + "\n" + ach["description"].get<std::string>(); notif.message = ach["displayName"].get<std::string>() + "\n" + ach["description"].get<std::string>();
notif.start_time = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()); notif.start_time = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
notifications.emplace_back(notif); notifications.emplace_back(notif);
NotifyUserAchievement(); NotifySoundUserAchievement();
have_notifications = true; have_notifications = true;
} }
else else
@ -744,50 +744,105 @@ void Steam_Overlay::BuildFriendWindow(Friend const& frd, friend_window_state& st
ImFont *font_default; ImFont *font_default;
ImFont *font_notif; ImFont *font_notif;
// set the position of the next notification
void Steam_Overlay::SetNextNotificationPos(float width, float height, float font_size, notification_type type, struct NotificationsIndexes &idx)
{
// 0 on the y-axis is top, 0 on the x-axis is left
// get the required position
Overlay_Appearance::NotificationPosition pos = Overlay_Appearance::default_pos;
switch (type) {
case notification_type::notification_type_achievement: pos = settings->overlay_appearance.ach_earned_pos; break;
case notification_type::notification_type_invite: pos = settings->overlay_appearance.invite_pos; break;
case notification_type::notification_type_message: pos = settings->overlay_appearance.chat_msg_pos; break;
default: /* satisfy compiler warning */ break;
}
float x = 0.0f;
float y = 0.0f;
const float noti_width = width * Notification::width_percent;
const float noti_height = Notification::height * font_size;
switch (pos) {
// top
case Overlay_Appearance::NotificationPosition::top_left:
x = 0.0f;
y = noti_height * idx.top_left;
++idx.top_left;
break;
case Overlay_Appearance::NotificationPosition::top_center:
x = (width / 2) - (noti_width / 2);
y = noti_height * idx.top_center;
++idx.top_center;
break;
case Overlay_Appearance::NotificationPosition::top_right:
x = width - noti_width;
y = noti_height * idx.top_right;
++idx.top_right;
break;
// bot
case Overlay_Appearance::NotificationPosition::bot_left:
x = 0.0f;
y = height - noti_height * (idx.bot_left + 1);
++idx.bot_left;
break;
case Overlay_Appearance::NotificationPosition::bot_center:
x = (width / 2) - (noti_width / 2);
y = height - noti_height * (idx.bot_center + 1);
++idx.bot_center;
break;
case Overlay_Appearance::NotificationPosition::bot_right:
x = width - noti_width;
y = height - noti_height * (idx.bot_right + 1);
++idx.bot_right;
break;
default: /* satisfy compiler warning */ break;
}
ImGui::SetNextWindowPos(ImVec2( x, y ));
}
void Steam_Overlay::BuildNotifications(int width, int height) void Steam_Overlay::BuildNotifications(int width, int height)
{ {
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()); auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
int i = 0;
int font_size = ImGui::GetFontSize(); float font_size = ImGui::GetFontSize();
std::queue<Friend> friend_actions_temp; std::queue<Friend> friend_actions_temp{};
{ {
std::lock_guard<std::recursive_mutex> lock(notifications_mutex); std::lock_guard<std::recursive_mutex> lock(notifications_mutex);
for (auto it = notifications.begin(); it != notifications.end(); ++it, ++i) NotificationsIndexes idx{};
for (auto it = notifications.begin(); it != notifications.end(); ++it)
{ {
auto elapsed_notif = now - it->start_time; auto elapsed_notif = now - it->start_time;
if ( elapsed_notif < Notification::fade_in) if ( elapsed_notif < Notification::fade_in) {
{
float alpha = settings->overlay_appearance.notification_a * (elapsed_notif.count() / static_cast<float>(Notification::fade_in.count())); float alpha = settings->overlay_appearance.notification_a * (elapsed_notif.count() / static_cast<float>(Notification::fade_in.count()));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, alpha)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, alpha));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(settings->overlay_appearance.notification_r, settings->overlay_appearance.notification_g, settings->overlay_appearance.notification_b, alpha)); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(settings->overlay_appearance.notification_r, settings->overlay_appearance.notification_g, settings->overlay_appearance.notification_b, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, alpha*2)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, alpha*2));
} }
else if ( elapsed_notif > Notification::fade_out_start) else if ( elapsed_notif > Notification::fade_out_start) {
{
float alpha = settings->overlay_appearance.notification_a * ((Notification::show_time - elapsed_notif).count() / static_cast<float>(Notification::fade_out.count())); float alpha = settings->overlay_appearance.notification_a * ((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_Border, ImVec4(0, 0, 0, alpha));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(settings->overlay_appearance.notification_r, settings->overlay_appearance.notification_g, settings->overlay_appearance.notification_b, alpha)); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(settings->overlay_appearance.notification_r, settings->overlay_appearance.notification_g, settings->overlay_appearance.notification_b, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, alpha*2)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, alpha*2));
} } else {
else
{
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, settings->overlay_appearance.notification_a)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, settings->overlay_appearance.notification_a));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(settings->overlay_appearance.notification_r, settings->overlay_appearance.notification_g, settings->overlay_appearance.notification_b, settings->overlay_appearance.notification_a)); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(settings->overlay_appearance.notification_r, settings->overlay_appearance.notification_g, settings->overlay_appearance.notification_b, settings->overlay_appearance.notification_a));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, settings->overlay_appearance.notification_a*2)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, settings->overlay_appearance.notification_a*2));
} }
ImGui::SetNextWindowPos(ImVec2((float)width - width * Notification::width, Notification::height * font_size * i )); SetNextNotificationPos(width, height, font_size, (notification_type)it->type, idx);
ImGui::SetNextWindowSize(ImVec2( width * Notification::width, Notification::height * font_size )); ImGui::SetNextWindowSize(ImVec2( width * Notification::width_percent, Notification::height * font_size ));
ImGui::Begin(std::to_string(it->id).c_str(), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | std::string wnd_name = "NotiPopupShow" + std::to_string(it->id);
ImGui::Begin(wnd_name.c_str(), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoDecoration); ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoDecoration);
switch (it->type) switch (it->type) {
{
case notification_type_achievement: case notification_type_achievement:
{ {
if (!it->icon.expired()) { if (!it->icon.expired()) {
@ -820,9 +875,11 @@ void Steam_Overlay::BuildNotifications(int width, int height)
} }
break; break;
case notification_type_message: case notification_type_message:
ImGui::TextWrapped("%s", it->message.c_str()); break; ImGui::TextWrapped("%s", it->message.c_str());
break;
case notification_type_auto_accept_invite: case notification_type_auto_accept_invite:
ImGui::TextWrapped("%s", it->message.c_str()); break; ImGui::TextWrapped("%s", it->message.c_str());
break;
} }
ImGui::End(); ImGui::End();
@ -1308,7 +1365,7 @@ void Steam_Overlay::Callback(Common_Message *msg)
} }
AddMessageNotification(friend_info->first.name() + ": " + steam_message.message()); AddMessageNotification(friend_info->first.name() + ": " + steam_message.message());
NotifyUser(friend_info->second); NotifySoundUserInvite(friend_info->second);
} }
} }
} }

View File

@ -422,6 +422,7 @@ Second, the project is not a malware, if your antivirus software complains, be s
--- ---
## Overlay warnings: ## Overlay warnings:
**Note: at the moment this feature is only enabled in the windows experimental builds**
These configuration files allow disabling various overlay warnings: These configuration files allow disabling various overlay warnings:
* `disable_overlay_warning_forced_setting.txt`: * `disable_overlay_warning_forced_setting.txt`:
@ -435,6 +436,22 @@ Check the exaxmple files in the `steam_settings` folder
--- ---
## Overlay appearance:
**Note: at the moment this feature is only enabled in the windows experimental builds**
These configuration file `overlay_appearance.txt` has various options to set for the overlay appearance.
The notifications positions could be set to one of these values:
* `top_left`
* `top_center`
* `top_right`
* `bot_left`
* `bot_center`
* `bot_right`
Check the exaxmple files in the `steam_settings` folder
---
## Auto accept game/lobby invites: ## Auto accept game/lobby invites:
When the overlay is enabled and working, you can bypass it and auto-accept invites (lobby or game) from a list of Steam IDs (SteamID64 format). When the overlay is enabled and working, you can bypass it and auto-accept invites (lobby or game) from a list of Steam IDs (SteamID64 format).

View File

@ -1,22 +1,46 @@
Font_Size 16.0 Font_Size 16.0
Icon_Size 64.0 Icon_Size 64.0
Notification_R 0.16 Notification_R 0.16
Notification_G 0.29 Notification_G 0.29
Notification_B 0.48 Notification_B 0.48
Notification_A 1.0 Notification_A 1.0
Background_R 0.0
Background_G 0.0 Background_R -1.0
Background_B 0.1 Background_G -1.0
Background_A 0.5 Background_B -1.0
Element_R 0.0 Background_A -1.0
Element_G 0.1
Element_B 0.0 Element_R -1.0
Element_A 1.0 Element_G -1.0
ElementHovered_R 0.0 Element_B -1.0
ElementHovered_G 0.5 Element_A -1.0
ElementHovered_B 0.0
ElementHovered_A 1.0 ElementHovered_R -1.0
ElementActive_R 0.0 ElementHovered_G -1.0
ElementActive_G 0.75 ElementHovered_B -1.0
ElementActive_B 0.0 ElementHovered_A -1.0
ElementActive_A 1.0
ElementActive_R -1.0
ElementActive_G -1.0
ElementActive_B -1.0
ElementActive_A -1.0
; available options:
; top_left
; top_center
; top_right
; bot_left
; bot_center
; bot_right
; position of achievements
PosAchievement top_right
; position of invitations
PosInvitation top_right
; position of chat messages
PosChatMsg top_right