Merge pull request #155 from igor725/trophies-list

Trophies list
This commit is contained in:
SysRay 2024-05-08 14:21:49 +02:00 committed by GitHub
commit 6895ae9764
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 489 additions and 202 deletions

View File

@ -35,6 +35,8 @@ class Hotkeys: public IHotkeys {
Hotkeys() { Hotkeys() {
static std::unordered_map<std::string, HkCommand> map = { static std::unordered_map<std::string, HkCommand> map = {
{"gamereport.send", HkCommand::GAMEREPORT_SEND_REPORT}, {"gamereport.send", HkCommand::GAMEREPORT_SEND_REPORT},
{"overlay.open", HkCommand::OVERLAY_MENU},
{"controller.l3", HkCommand::CONTROLLER_L3}, {"controller.l3", HkCommand::CONTROLLER_L3},
{"controller.r3", HkCommand::CONTROLLER_R3}, {"controller.r3", HkCommand::CONTROLLER_R3},
{"controller.options", HkCommand::CONTROLLER_OPTIONS}, {"controller.options", HkCommand::CONTROLLER_OPTIONS},

View File

@ -10,6 +10,7 @@ enum class HkCommand {
// System commands // System commands
GAMEREPORT_SEND_REPORT, GAMEREPORT_SEND_REPORT,
OVERLAY_MENU,
// Gamepad emulation // Gamepad emulation
CONTROLLER_L3, CONTROLLER_L3,

View File

@ -30,7 +30,6 @@ class Trophies: public ITrophies {
bool m_bIgnoreMissingLocale = false; bool m_bIgnoreMissingLocale = false;
uint8_t m_trkey[TR_AES_BLOCK_SIZE] = {}; uint8_t m_trkey[TR_AES_BLOCK_SIZE] = {};
std::string m_localizedTrophyFile; std::string m_localizedTrophyFile;
std::filesystem::path m_trophyInfoPath;
std::filesystem::path m_npidCachePath; std::filesystem::path m_npidCachePath;
std::vector<callback> m_callbacks = {}; std::vector<callback> m_callbacks = {};
std::mutex m_mutexParse; std::mutex m_mutexParse;
@ -38,17 +37,19 @@ class Trophies: public ITrophies {
private: private:
struct usr_context { struct usr_context {
struct trophy { struct trophy {
int32_t id; int32_t id = -1;
uint32_t re; // reserved uint32_t re = 0; // reserved
uint64_t ts; uint64_t ts = 0;
}; };
bool created; bool created = false;
uint32_t label; uint32_t label = 0;
int32_t userId; int32_t userId = -1;
std::vector<struct trophy> trophies; std::vector<trophy> trophies = {};
} m_ctx[4]; };
std::array<usr_context, 4> m_ctx {};
struct trp_header { struct trp_header {
uint32_t magic; // should be 0xDCA24D00 uint32_t magic; // should be 0xDCA24D00
@ -469,7 +470,7 @@ class Trophies: public ITrophies {
}; };
callUnlockCallback(&cbdata); callUnlockCallback(&cbdata);
m_ctx[userId].trophies.push_back({plat.id, 0, uTime}); m_ctx[userId - 1].trophies.push_back({plat.id, 0, uTime});
saveTrophyData(userId); saveTrophyData(userId);
} }
} }
@ -501,10 +502,7 @@ class Trophies: public ITrophies {
if (systemlang != SystemParamLang::EnglishUS) m_localizedTrophyFile = std::format("TROP_{:02}.ESFM", (uint32_t)systemlang); if (systemlang != SystemParamLang::EnglishUS) m_localizedTrophyFile = std::format("TROP_{:02}.ESFM", (uint32_t)systemlang);
} }
auto gfd = accessFileManager().getGameFilesDir(); m_npidCachePath = accessFileManager().getGameFilesDir() / ".npcommid";
m_trophyInfoPath = gfd / "tropinfo";
m_npidCachePath = gfd / ".npcommid";
} }
// Callbacks // Callbacks
@ -598,10 +596,11 @@ class Trophies: public ITrophies {
int32_t loadTrophyData(int32_t userId) { int32_t loadTrophyData(int32_t userId) {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT; if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId]; auto& ctx = m_ctx[userId - 1];
if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT; if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
auto path = accessFileManager().getGameFilesDir() / std::format("tropinfo.{}", userId);
std::ifstream file(m_trophyInfoPath, std::ios::in | std::ios::binary); std::ifstream file(path, std::ios::in | std::ios::binary);
if (file.is_open()) { if (file.is_open()) {
uint32_t tc = 0; uint32_t tc = 0;
@ -619,11 +618,12 @@ class Trophies: public ITrophies {
} }
int32_t saveTrophyData(int32_t userId) { int32_t saveTrophyData(int32_t userId) {
if (userId < 1 || userId > 4) return false; if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId]; auto& ctx = m_ctx[userId - 1];
if (!ctx.created) return false; if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
auto path = accessFileManager().getGameFilesDir() / std::format("tropinfo.{}", userId);
std::ofstream file(m_trophyInfoPath, std::ios::out | std::ios::binary); std::ofstream file(path, std::ios::out | std::ios::binary);
if (file.is_open()) { if (file.is_open()) {
uint32_t num = ctx.trophies.size(); uint32_t num = ctx.trophies.size();
@ -634,16 +634,16 @@ class Trophies: public ITrophies {
if (!file.write((char*)&trop.ts, 8)) return Err::NpTrophy::INSUFFICIENT_SPACE; if (!file.write((char*)&trop.ts, 8)) return Err::NpTrophy::INSUFFICIENT_SPACE;
} }
return true; return Ok;
} }
return false; return Err::NpTrophy::INSUFFICIENT_SPACE;
} }
int32_t createContext(int32_t userId, uint32_t label) final { int32_t createContext(int32_t userId, uint32_t label) final {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_USER_ID; if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_USER_ID;
auto& ctx = m_ctx[userId]; auto& ctx = m_ctx[userId - 1];
if (ctx.created) return Err::NpTrophy::CONTEXT_ALREADY_EXISTS; if (ctx.created) return Ok;
ctx.userId = userId; ctx.userId = userId;
ctx.created = true; ctx.created = true;
@ -660,7 +660,7 @@ class Trophies: public ITrophies {
int32_t destroyContext(int32_t userId) final { int32_t destroyContext(int32_t userId) final {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT; if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId]; auto& ctx = m_ctx[userId - 1];
if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT; if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
if (!saveTrophyData(userId)) return Err::NpTrophy::INSUFFICIENT_SPACE; if (!saveTrophyData(userId)) return Err::NpTrophy::INSUFFICIENT_SPACE;
ctx.created = false; ctx.created = false;
@ -670,7 +670,7 @@ class Trophies: public ITrophies {
int32_t getProgress(int32_t userId, uint32_t progress[4], uint32_t* count) final { int32_t getProgress(int32_t userId, uint32_t progress[4], uint32_t* count) final {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT; if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId]; auto& ctx = m_ctx[userId - 1];
if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT; if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
if (count != nullptr) { if (count != nullptr) {
@ -698,7 +698,7 @@ class Trophies: public ITrophies {
uint64_t getUnlockTime(int32_t userId, int32_t trophyId) final { uint64_t getUnlockTime(int32_t userId, int32_t trophyId) final {
if (userId < 1 || userId > 4) return 0ull; if (userId < 1 || userId > 4) return 0ull;
auto& ctx = m_ctx[userId]; auto& ctx = m_ctx[userId - 1];
if (!ctx.created) return false; if (!ctx.created) return false;
for (auto& trop: ctx.trophies) { for (auto& trop: ctx.trophies) {
@ -733,7 +733,7 @@ class Trophies: public ITrophies {
int32_t unlockTrophy(int32_t userId, int32_t trophyId, int32_t* platinumId) final { int32_t unlockTrophy(int32_t userId, int32_t trophyId, int32_t* platinumId) final {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT; if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId]; auto& ctx = m_ctx[userId - 1];
if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT; if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
if (getUnlockTime(ctx.userId, trophyId) != 0ull) return Err::NpTrophy::ALREADY_UNLOCKED; if (getUnlockTime(ctx.userId, trophyId) != 0ull) return Err::NpTrophy::ALREADY_UNLOCKED;
*platinumId = -1; *platinumId = -1;

View File

@ -7,6 +7,7 @@ add_library(videoout OBJECT
overlay/overlay.cpp overlay/overlay.cpp
overlay/overtrophy/overtrophy.cpp overlay/overtrophy/overtrophy.cpp
overlay/gamemenu/gamemenu.cpp
) )
add_dependencies(videoout third_party psOff_utility initParams config_emu psoff_render) add_dependencies(videoout third_party psOff_utility initParams config_emu psoff_render)

View File

@ -0,0 +1,206 @@
#include "gamemenu.h"
#include "../imhelper.h"
#include "core/hotkeys/hotkeys.h"
#include "core/trophies/trophies.h"
#include <chrono>
#include <imgui/imgui.h>
void GameMenu::init() {
accessHotkeys().registerCallback(HkCommand::OVERLAY_MENU, [this](HkCommand) { toggle(); });
m_buttons.emplace_back("Show trophies list", [this]() { switchTo(MenuState::TROPHIES); });
m_buttons.emplace_back("Close this menu", [this]() { toggle(); });
m_fontTitle = _ImGuiCreateFont(18.0f);
m_fontSubTitle = _ImGuiCreateFont(15.0f);
}
// todo: pack grade and hidden in one short value?
void GameMenu::_PushTrophy(int32_t trophyId, uint8_t grade, bool hidden, std::string& name, std::string& detail) {
m_trophyList.emplace_back(trophyId, grade, hidden, name, detail);
}
void GameMenu::toggle() {
m_bShown = !m_bShown; // todo: Pause the game when m_bShown is true
}
void GameMenu::switchTo(MenuState state) {
switch (m_state) { // Close the previous state
case MenuState::TROPHIES: {
m_trophyList.clear();
} break;
default: break;
}
switch (state) {
case MenuState::MAIN: break; // We have nothing to do when switching to main
case MenuState::TROPHIES: {
ITrophies::trp_context ctx = {
.entry =
{
.func = [this](ITrophies::trp_ent_cb::data_t* data) -> bool {
_PushTrophy(data->id, data->grade, data->hidden, data->name, data->detail);
return false;
},
},
};
ITrophies::ErrCodes ec;
if ((ec = accessTrophies().parseTRP(&ctx)) != ITrophies::ErrCodes::SUCCESS) {
m_trophyList.clear();
m_trophyList.emplace_back(0, 'e', false, "Failed to load trophies", accessTrophies().getError(ec));
}
} break;
}
m_state = state;
}
void GameMenu::_DrawTrophiesFor(int32_t userId) {
struct TrophyUnlock {
int32_t id;
std::string time;
};
static int32_t prev_user = -1;
static std::vector<TrophyUnlock> unlocks = {};
auto& trophies = accessTrophies();
if (prev_user != userId) {
prev_user = userId;
unlocks.clear();
if (trophies.createContext(userId, 0) == 0) {
for (auto& trophy: m_trophyList) {
if (auto utime = trophies.getUnlockTime(userId, trophy.id)) {
unlocks.emplace_back(trophy.id, std::format("{0:%x}", std::chrono::system_clock::from_time_t(utime / 1000)));
}
}
}
}
const auto wxp = ImGui::GetContentRegionAvail().x;
for (auto& trophy: m_trophyList) {
auto it = unlocks.begin();
for (; it != unlocks.end(); ++it) {
if (it->id == trophy.id) {
break;
}
}
const char* status = "Locked";
if (it != unlocks.end()) status = it->time.c_str();
switch (trophy.grade) {
case 'b': {
ImGui::PushStyleColor(ImGuiCol_Text, TR_BRONZE_COLOR);
ImGui::Text("Bronze trophy");
ImGui::PopStyleColor();
} break;
case 's': {
ImGui::PushStyleColor(ImGuiCol_Text, TR_SILVER_COLOR);
ImGui::Text("Silver trophy");
ImGui::PopStyleColor();
} break;
case 'g': {
ImGui::PushStyleColor(ImGuiCol_Text, TR_GOLD_COLOR);
ImGui::Text("Gold trophy");
ImGui::PopStyleColor();
} break;
case 'p': {
ImGui::PushStyleColor(ImGuiCol_Text, TR_PLATINUM_COLOR);
ImGui::Text("Platinum trophy");
ImGui::PopStyleColor();
} break;
case 'e': {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255));
ImGui::Text("Error!!1!!!1!");
ImGui::PopStyleColor();
} break;
default: {
ImGui::PushStyleColor(ImGuiCol_Text, TR_UNKNOWN_COLOR);
ImGui::Text("Unknown trophy");
ImGui::PopStyleColor();
} break;
}
ImGui::SameLine();
ImGui::SetCursorPosX(wxp - ImGui::CalcTextSize(status).x);
ImGui::Text("%s", status);
ImGui::PushFont(m_fontTitle);
ImGui::Text("%s", trophy.title.c_str());
ImGui::PopFont();
ImGui::PushFont(m_fontSubTitle);
// Hide the trophy description if it is not unlocked yet
ImGui::TextWrapped("%s", trophy.hidden && (it == unlocks.end()) ? "[HIDDEN]" : trophy.subtitle.c_str());
ImGui::PopFont();
ImGui::Separator(); // Separate trophies
}
}
void GameMenu::draw() {
if (!m_bShown) return;
auto& io = ImGui::GetIO();
auto const ds = io.DisplaySize;
static const ImVec2 btn_size(150.0f, ImGui::GetFrameHeightWithSpacing());
static const ImVec2 win_size(400.0f, 320.0f);
static const float win_center_x((win_size.x - btn_size.x) * 0.5f);
ImGui::SetNextWindowSize(win_size);
ImGui::SetNextWindowPos(ImVec2(ds.x / 2.0f - 200.0f, ds.y / 2.0f - 160.0f));
ImGui::PushFont(m_fontSubTitle);
ImGui::Begin("ingame_menu", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize);
// todo: Put actual user names there?
static const char* names[] = {"User #1", "User #2", "User #3", "User #4"};
switch (m_state) {
case MenuState::MAIN: {
if (auto btncnt = m_buttons.size()) {
ImGui::SetCursorPosY((win_size.y - btncnt * btn_size.y) * 0.5f);
for (auto& btn: m_buttons) {
ImGui::SetCursorPosX(win_center_x);
if (ImGui::Button(btn.title.c_str(), btn_size)) {
btn.func();
}
}
}
} break;
case MenuState::TROPHIES: {
if (ImGui::BeginTabBar("trophies_tab")) {
for (int i = 0; i < 4; ++i) {
if (ImGui::BeginTabItem(names[i])) {
static const ImVec2 child_size(0, win_size.y - btn_size.y - 45.0f);
ImVec2 backbtn_size(ImGui::GetContentRegionAvail().x, btn_size.y);
if (ImGui::BeginChild("user_trophies", child_size, ImGuiChildFlags_Border)) {
_DrawTrophiesFor(i + 1);
ImGui::EndChild();
}
if (ImGui::Button("Go back", backbtn_size)) {
switchTo(MenuState::MAIN);
}
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
} break;
}
ImGui::End();
ImGui::PopFont();
};

View File

@ -0,0 +1,48 @@
#pragma once
#include <functional>
#include <imgui/imgui.h>
#include <string>
class GameMenu {
struct MenuButton {
std::string title;
std::function<void(void)> func;
};
enum class MenuState {
MAIN,
TROPHIES,
};
struct Trophy {
int32_t id;
uint8_t grade;
bool hidden;
std::string title;
std::string subtitle;
};
MenuState m_state = MenuState::MAIN;
bool m_bShown = false;
std::vector<MenuButton> m_buttons = {};
std::vector<Trophy> m_trophyList = {};
ImFont* m_fontTitle = nullptr;
ImFont* m_fontSubTitle = nullptr;
void _DrawTrophiesFor(int32_t userId);
public:
GameMenu() = default;
virtual ~GameMenu() = default;
void toggle();
void _PushTrophy(int32_t trophyId, uint8_t grade, bool hidden, std::string& name, std::string& detail);
void init();
void draw();
void switchTo(MenuState state);
};

View File

@ -0,0 +1,28 @@
#pragma once
#include <imgui/imgui.h>
#define TR_BRONZE_COLOR IM_COL32(199, 124, 48, 255)
#define TR_SILVER_COLOR IM_COL32(150, 164, 182, 255)
#define TR_GOLD_COLOR IM_COL32(228, 194, 78, 255)
#define TR_PLATINUM_COLOR IM_COL32(191, 191, 191, 255)
#define TR_UNKNOWN_COLOR TR_BRONZE_COLOR
static inline ImFont* _ImGuiCreateFont(float px) {
ImGuiIO& io = ImGui::GetIO();
ImFontConfig cfg;
cfg.MergeMode = true;
cfg.FontDataOwnedByAtlas = false;
ImVector<ImWchar> ranges;
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesCyrillic());
builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
builder.BuildRanges(&ranges);
auto font = io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\arial.ttf", px, nullptr, ranges.Data);
io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\Msyhl.ttc", px, &cfg, io.Fonts->GetGlyphRangesChineseFull());
io.Fonts->Build();
return font;
}

View File

@ -1,5 +1,6 @@
#include "overlay.h" #include "overlay.h"
#include "gamemenu/gamemenu.h"
#include "logging.h" #include "logging.h"
#include "overtrophy/overtrophy.h" #include "overtrophy/overtrophy.h"
@ -14,6 +15,9 @@ class OverlayHandler: public IOverlayHandler {
std::shared_ptr<vulkan::DeviceInfo> m_deviceInfo; std::shared_ptr<vulkan::DeviceInfo> m_deviceInfo;
std::shared_ptr<IImageHandler> m_imageHandler; std::shared_ptr<IImageHandler> m_imageHandler;
OverTrophy m_overTrophy;
GameMenu m_gameMenu;
VkDescriptorPool m_descriptorPool; VkDescriptorPool m_descriptorPool;
bool m_isInit = false, m_isStop = false; bool m_isInit = false, m_isStop = false;
@ -124,7 +128,8 @@ void OverlayHandler::init(SDL_Window* window, vulkan::QueueInfo* queue, VkFormat
}; };
ImGui_ImplVulkan_Init(&initInfo); ImGui_ImplVulkan_Init(&initInfo);
(void)accessTrophyOverlay(); // Should be called there to avoid initialization inside NewFrame() m_overTrophy.init();
m_gameMenu.init();
m_isInit = true; m_isInit = true;
} }
@ -168,5 +173,7 @@ void OverlayHandler::submit(ImageData const& imageData) {
void OverlayHandler::draw() { void OverlayHandler::draw() {
// ImGui::ShowDemoWindow(); // ImGui::ShowDemoWindow();
accessTrophyOverlay().draw(m_imageHandler->getFPS()); auto const fps = m_imageHandler->getFPS();
m_overTrophy.draw(fps);
m_gameMenu.draw();
} }

View File

@ -1,142 +1,97 @@
#include "overtrophy.h" #include "overtrophy.h"
#include "../imhelper.h"
#include "core/trophies/trophies.h" #include "core/trophies/trophies.h"
#include <imgui/imgui.h> void OverTrophy::addNotify(uint8_t grade, std::string title, std::string details) {
m_notifications.emplace_back(0.0f, 5.0f, false, grade, std::move(title), std::move(details), nullptr, 0ull);
class OverTrophy: public IOverTrophy { }
struct notify {
float slide = 1.0f; void OverTrophy::init() {
float timer = 5.0f; m_defaultFont = _ImGuiCreateFont(15);
bool animdone = false; m_titleFont = _ImGuiCreateFont(24);
m_textFont = _ImGuiCreateFont(17);
uint8_t grade;
std::string title; accessTrophies().addTrophyUnlockCallback([this](const void* data) {
std::string subtext; auto unlock = (ITrophies::trp_unlock_data*)data;
void* pngdata; addNotify(unlock->grade, unlock->name, unlock->descr);
size_t pngsize; if (unlock->platGained) addNotify('p', unlock->pname, unlock->pdescr);
});
~notify() { }
if (pngdata != nullptr) ::free(pngdata);
} void OverTrophy::draw(double fps) {
}; if (m_notifications.size() == 0ull) return; // Nothing to draw
fps = std::max(1.0, fps);
std::vector<notify> m_notifications = {};
const double delta = (1 / fps);
ImFont* m_defaultFont; const float dsw = ImGui::GetIO().DisplaySize.x;
ImFont* m_titleFont; const float slidemod = delta * 2.78f;
ImFont* m_textFont; float wpos = 0.0f;
ImFont* CreateFont(float px) { for (auto it = m_notifications.begin(); it != m_notifications.end();) {
ImGuiIO& io = ImGui::GetIO(); auto& notify = *it;
ImFontConfig cfg;
cfg.SizePixels = px; if (notify.animdone == false) {
cfg.OversampleH = cfg.OversampleV = 1; if (notify.slide < 1.0f) {
cfg.PixelSnapH = true; notify.slide += slidemod;
static const ImWchar ranges[] = { } else if (notify.slide > 1.0f) {
0x0020, // Range start notify.animdone = true;
0xFFFC, // Range end notify.slide = 1.0f;
0x0000, }
}; } else {
cfg.GlyphRanges = ranges; if ((notify.timer -= delta) < 0.0f) {
return io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\tahoma.ttf", px, &cfg); if (notify.slide > 0.0f) {
} notify.slide -= slidemod;
} else {
public: it = m_notifications.erase(it);
OverTrophy() { continue;
m_defaultFont = CreateFont(15); }
m_titleFont = CreateFont(24); }
m_textFont = CreateFont(17); }
accessTrophies().addTrophyUnlockCallback([this](const void* data) { const char* title;
auto unlock = (ITrophies::trp_unlock_data*)data; switch (notify.grade) {
addNotify(unlock->grade, unlock->name, unlock->descr); case 'b': {
if (unlock->platGained) addNotify('p', unlock->pname, unlock->pdescr); title = "Bronze trophy unlocked!";
}); ImGui::PushStyleColor(ImGuiCol_Text, TR_BRONZE_COLOR);
} } break;
case 's': {
void addNotify(uint8_t grade, std::string title, std::string details) { title = "Silver trophy unlocked!";
m_notifications.emplace_back(0.0f, 5.0f, false, grade, std::move(title), std::move(details), nullptr, 0ull); ImGui::PushStyleColor(ImGuiCol_Text, TR_SILVER_COLOR);
} } break;
case 'g': {
void draw(double fps) final { title = "Gold trophy unlocked!";
fps = std::max(1.0, fps); ImGui::PushStyleColor(ImGuiCol_Text, TR_GOLD_COLOR);
} break;
const double delta = (1 / fps); case 'p': {
const float dsw = ImGui::GetIO().DisplaySize.x; title = "Platinum trophy unlocked!";
const float slidemod = delta * 2.78f; ImGui::PushStyleColor(ImGuiCol_Text, TR_PLATINUM_COLOR);
float wpos = 0.0f; } break;
for (auto it = m_notifications.begin(); it != m_notifications.end();) { default: {
auto& notify = *it; title = "New trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, TR_UNKNOWN_COLOR);
if (notify.animdone == false) { } break;
if (notify.slide < 1.0f) { }
notify.slide += slidemod;
} else if (notify.slide > 1.0f) { ImGui::PushFont(m_defaultFont);
notify.animdone = true; ImGui::SetNextWindowPos(ImVec2(dsw - 360 * notify.slide, wpos + 10.0f));
notify.slide = 1.0f; ImGui::SetNextWindowSize(ImVec2(350, 0));
} ImGui::Begin(title, nullptr,
} else { ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove |
if ((notify.timer -= delta) < 0.0f) { ImGuiWindowFlags_NoFocusOnAppearing);
if (notify.slide > 0.0f) { ImGui::PopStyleColor();
notify.slide -= slidemod; ImGui::PushFont(m_titleFont);
} else { ImGui::Text("%s", notify.title.c_str());
it = m_notifications.erase(it); ImGui::PopFont();
continue;
} ImGui::PushFont(m_textFont);
} ImGui::TextWrapped("%s", notify.subtext.c_str());
} ImGui::PopFont();
const char* title; wpos += ImGui::GetWindowHeight() + 10.0f;
switch (notify.grade) { ImGui::End();
case 'b': { ImGui::PopFont();
title = "Bronze trophy unlocked!"; ++it;
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(199, 124, 48, 255)); }
} break;
case 's': {
title = "Silver trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(150, 164, 182, 255));
} break;
case 'g': {
title = "Gold trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(228, 194, 78, 255));
} break;
case 'p': {
title = "Platinum trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(191, 191, 191, 255));
} break;
default: {
title = "New trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(199, 124, 48, 255));
} break;
}
ImGui::PushFont(m_defaultFont);
ImGui::SetNextWindowPos(ImVec2(dsw - 360 * notify.slide, wpos + 10.0f));
ImGui::SetNextWindowSize(ImVec2(350, 0));
ImGui::Begin(title, nullptr,
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoFocusOnAppearing);
ImGui::PopStyleColor();
ImGui::PushFont(m_titleFont);
ImGui::Text("%s", notify.title.c_str());
ImGui::PopFont();
ImGui::PushFont(m_textFont);
ImGui::TextWrapped("%s", notify.subtext.c_str());
ImGui::PopFont();
wpos += ImGui::GetWindowHeight() + 10.0f;
ImGui::End();
ImGui::PopFont();
++it;
}
}
};
IOverTrophy& accessTrophyOverlay() {
static OverTrophy ot;
return ot;
} }

View File

@ -1,16 +1,39 @@
#pragma once #pragma once
#include "utility/utility.h" #include <imgui/imgui.h>
#include <string>
#include <vector>
class IOverTrophy { class OverTrophy {
CLASS_NO_COPY(IOverTrophy); struct notify {
CLASS_NO_MOVE(IOverTrophy); float slide = 1.0f;
float timer = 5.0f;
bool animdone = false;
uint8_t grade;
std::string title;
std::string subtext;
void* pngdata;
size_t pngsize;
~notify() {
if (pngdata != nullptr) ::free(pngdata);
}
};
std::vector<notify> m_notifications;
ImFont* m_defaultFont;
ImFont* m_titleFont;
ImFont* m_textFont;
ImFont* CreateFont(float px);
void addNotify(uint8_t grade, std::string title, std::string details);
public: public:
IOverTrophy() = default; OverTrophy() = default;
virtual ~IOverTrophy() = default; virtual ~OverTrophy() = default;
virtual void draw(double fps) = 0; void init();
void draw(double fps);
}; };
IOverTrophy& accessTrophyOverlay();

View File

@ -39,6 +39,9 @@
"gamereport.send": { "gamereport.send": {
"$ref": "#/definitions/button" "$ref": "#/definitions/button"
}, },
"overlay.open": {
"$ref": "#/definitions/button"
},
"controller.circle": { "controller.circle": {
"$ref": "#/definitions/button" "$ref": "#/definitions/button"
}, },

View File

@ -16,30 +16,32 @@ You can change the layout now in _controls.json_, if you want to.
// These binds will be used to emulate gamepad actions for the pad with `type` set to "keyboard" // These binds will be used to emulate gamepad actions for the pad with `type` set to "keyboard"
// Key names can be obtained here: https://wiki.libsdl.org/SDL2/SDL_Scancode // Key names can be obtained here: https://wiki.libsdl.org/SDL2/SDL_Scancode
"keybinds": { "keybinds": {
"circle": "l", "gamereport.send": "f11",
"cross": "k", "overlay.open": "tab",
"dpad_down": "down", "controller.circle": "l",
"dpad_left": "left", "controller.cross": "k",
"dpad_right": "right", "controller.dpad_down": "down",
"dpad_up": "up", "controller.dpad_left": "left",
"l1": "f3", "controller.dpad_right": "right",
"l2": "f5", "controller.dpad_up": "up",
"l3": "space", "controller.l1": "f3",
"lx+": "d", "controller.l2": "f5",
"lx-": "a", "controller.l3": "space",
"ly+": "s", "controller.lx+": "d",
"ly-": "w", "controller.lx-": "a",
"options": "f1", "controller.ly+": "s",
"r1": "f2", "controller.ly-": "w",
"r2": "f6", "controller.options": "f1",
"r3": "home", "controller.r1": "f2",
"rx+": "f", "controller.r2": "f6",
"rx-": "h", "controller.r3": "home",
"ry+": "g", "controller.rx+": "f",
"ry-": "t", "controller.rx-": "h",
"square": "j", "controller.ry+": "g",
"touchpad": "f4", "controller.ry-": "t",
"triangle": "i" "controller.square": "j",
"controller.touchpad": "f4",
"controller.triangle": "i"
}, },
// This array contains 4 objects with the similar structure. // This array contains 4 objects with the similar structure.
// These objects describes the parameters for each pad // These objects describes the parameters for each pad
@ -125,7 +127,18 @@ Game language can be changed with systemlang : *, default is EnglishUS. Game mus
```jsonc ```jsonc
{ {
"device": "[default]", // Audio device name, [default] means system default output device "device": "[default]", // Audio device name, [default] means system default output device
"volume": 0.05 // Master volume, 0...1 "volume": 0.05, // Master volume, 0...1
/**
* The game will use these audio devices as pad speaker. Starting with psOff v.0.5 you can use
* the actual DualSense audio device there, it will play the audio that the game supposed
* to play through gamepad speaker. Note that DualShock 4 spaker is NOT supported!
*/
"padspeakers": [
"[null]", // Your full audio device name e.g. "DualSense Wireless Controller (DualSense Wireless Controller)"
"[null]",
"[null]",
"[null]"
]
} }
``` ```

View File

@ -278,7 +278,7 @@ Config::Config() {
{"controller.l3", "space"}, {"controller.r1", "f2"}, {"controller.r2", "f6"}, {"controller.r3", "home"}, {"controller.l3", "space"}, {"controller.r1", "f2"}, {"controller.r2", "f6"}, {"controller.r3", "home"},
{"controller.lx-", "a"}, {"controller.lx+", "d"}, {"controller.ly-", "w"}, {"controller.ly+", "s"}, {"controller.lx-", "a"}, {"controller.lx+", "d"}, {"controller.ly-", "w"}, {"controller.ly+", "s"},
{"controller.rx-", "f"}, {"controller.rx+", "h"}, {"controller.ry-", "t"}, {"controller.ry+", "g"}, {"controller.rx-", "f"}, {"controller.rx+", "h"}, {"controller.ry-", "t"}, {"controller.ry+", "g"},
{"gamereport.send", "f11"}, {"gamereport.send", "f11"}, {"overlay.open", "tab"},
}}, }},
}), }),
ConfigModFlag::CONTROLS); ConfigModFlag::CONTROLS);