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() {
static std::unordered_map<std::string, HkCommand> map = {
{"gamereport.send", HkCommand::GAMEREPORT_SEND_REPORT},
{"overlay.open", HkCommand::OVERLAY_MENU},
{"controller.l3", HkCommand::CONTROLLER_L3},
{"controller.r3", HkCommand::CONTROLLER_R3},
{"controller.options", HkCommand::CONTROLLER_OPTIONS},

View File

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

View File

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

View File

@ -7,6 +7,7 @@ add_library(videoout OBJECT
overlay/overlay.cpp
overlay/overtrophy/overtrophy.cpp
overlay/gamemenu/gamemenu.cpp
)
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 "gamemenu/gamemenu.h"
#include "logging.h"
#include "overtrophy/overtrophy.h"
@ -14,6 +15,9 @@ class OverlayHandler: public IOverlayHandler {
std::shared_ptr<vulkan::DeviceInfo> m_deviceInfo;
std::shared_ptr<IImageHandler> m_imageHandler;
OverTrophy m_overTrophy;
GameMenu m_gameMenu;
VkDescriptorPool m_descriptorPool;
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);
(void)accessTrophyOverlay(); // Should be called there to avoid initialization inside NewFrame()
m_overTrophy.init();
m_gameMenu.init();
m_isInit = true;
}
@ -168,5 +173,7 @@ void OverlayHandler::submit(ImageData const& imageData) {
void OverlayHandler::draw() {
// 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 "../imhelper.h"
#include "core/trophies/trophies.h"
#include <imgui/imgui.h>
class OverTrophy: public IOverTrophy {
struct notify {
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) {
ImGuiIO& io = ImGui::GetIO();
ImFontConfig cfg;
cfg.SizePixels = px;
cfg.OversampleH = cfg.OversampleV = 1;
cfg.PixelSnapH = true;
static const ImWchar ranges[] = {
0x0020, // Range start
0xFFFC, // Range end
0x0000,
};
cfg.GlyphRanges = ranges;
return io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\tahoma.ttf", px, &cfg);
}
public:
OverTrophy() {
m_defaultFont = CreateFont(15);
m_titleFont = CreateFont(24);
m_textFont = CreateFont(17);
accessTrophies().addTrophyUnlockCallback([this](const void* data) {
auto unlock = (ITrophies::trp_unlock_data*)data;
addNotify(unlock->grade, unlock->name, unlock->descr);
if (unlock->platGained) addNotify('p', unlock->pname, unlock->pdescr);
});
}
void 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);
}
void draw(double fps) final {
fps = std::max(1.0, fps);
const double delta = (1 / fps);
const float dsw = ImGui::GetIO().DisplaySize.x;
const float slidemod = delta * 2.78f;
float wpos = 0.0f;
for (auto it = m_notifications.begin(); it != m_notifications.end();) {
auto& notify = *it;
if (notify.animdone == false) {
if (notify.slide < 1.0f) {
notify.slide += slidemod;
} else if (notify.slide > 1.0f) {
notify.animdone = true;
notify.slide = 1.0f;
}
} else {
if ((notify.timer -= delta) < 0.0f) {
if (notify.slide > 0.0f) {
notify.slide -= slidemod;
} else {
it = m_notifications.erase(it);
continue;
}
}
}
const char* title;
switch (notify.grade) {
case 'b': {
title = "Bronze trophy unlocked!";
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;
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);
}
void OverTrophy::init() {
m_defaultFont = _ImGuiCreateFont(15);
m_titleFont = _ImGuiCreateFont(24);
m_textFont = _ImGuiCreateFont(17);
accessTrophies().addTrophyUnlockCallback([this](const void* data) {
auto unlock = (ITrophies::trp_unlock_data*)data;
addNotify(unlock->grade, unlock->name, unlock->descr);
if (unlock->platGained) addNotify('p', unlock->pname, unlock->pdescr);
});
}
void OverTrophy::draw(double fps) {
if (m_notifications.size() == 0ull) return; // Nothing to draw
fps = std::max(1.0, fps);
const double delta = (1 / fps);
const float dsw = ImGui::GetIO().DisplaySize.x;
const float slidemod = delta * 2.78f;
float wpos = 0.0f;
for (auto it = m_notifications.begin(); it != m_notifications.end();) {
auto& notify = *it;
if (notify.animdone == false) {
if (notify.slide < 1.0f) {
notify.slide += slidemod;
} else if (notify.slide > 1.0f) {
notify.animdone = true;
notify.slide = 1.0f;
}
} else {
if ((notify.timer -= delta) < 0.0f) {
if (notify.slide > 0.0f) {
notify.slide -= slidemod;
} else {
it = m_notifications.erase(it);
continue;
}
}
}
const char* title;
switch (notify.grade) {
case 'b': {
title = "Bronze trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, TR_BRONZE_COLOR);
} break;
case 's': {
title = "Silver trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, TR_SILVER_COLOR);
} break;
case 'g': {
title = "Gold trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, TR_GOLD_COLOR);
} break;
case 'p': {
title = "Platinum trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, TR_PLATINUM_COLOR);
} break;
default: {
title = "New trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, TR_UNKNOWN_COLOR);
} 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;
}
}

View File

@ -1,16 +1,39 @@
#pragma once
#include "utility/utility.h"
#include <imgui/imgui.h>
#include <string>
#include <vector>
class IOverTrophy {
CLASS_NO_COPY(IOverTrophy);
CLASS_NO_MOVE(IOverTrophy);
class OverTrophy {
struct notify {
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:
IOverTrophy() = default;
virtual ~IOverTrophy() = default;
OverTrophy() = 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": {
"$ref": "#/definitions/button"
},
"overlay.open": {
"$ref": "#/definitions/button"
},
"controller.circle": {
"$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"
// Key names can be obtained here: https://wiki.libsdl.org/SDL2/SDL_Scancode
"keybinds": {
"circle": "l",
"cross": "k",
"dpad_down": "down",
"dpad_left": "left",
"dpad_right": "right",
"dpad_up": "up",
"l1": "f3",
"l2": "f5",
"l3": "space",
"lx+": "d",
"lx-": "a",
"ly+": "s",
"ly-": "w",
"options": "f1",
"r1": "f2",
"r2": "f6",
"r3": "home",
"rx+": "f",
"rx-": "h",
"ry+": "g",
"ry-": "t",
"square": "j",
"touchpad": "f4",
"triangle": "i"
"gamereport.send": "f11",
"overlay.open": "tab",
"controller.circle": "l",
"controller.cross": "k",
"controller.dpad_down": "down",
"controller.dpad_left": "left",
"controller.dpad_right": "right",
"controller.dpad_up": "up",
"controller.l1": "f3",
"controller.l2": "f5",
"controller.l3": "space",
"controller.lx+": "d",
"controller.lx-": "a",
"controller.ly+": "s",
"controller.ly-": "w",
"controller.options": "f1",
"controller.r1": "f2",
"controller.r2": "f6",
"controller.r3": "home",
"controller.rx+": "f",
"controller.rx-": "h",
"controller.ry+": "g",
"controller.ry-": "t",
"controller.square": "j",
"controller.touchpad": "f4",
"controller.triangle": "i"
},
// This array contains 4 objects with the similar structure.
// 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
{
"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.lx-", "a"}, {"controller.lx+", "d"}, {"controller.ly-", "w"}, {"controller.ly+", "s"},
{"controller.rx-", "f"}, {"controller.rx+", "h"}, {"controller.ry-", "t"}, {"controller.ry+", "g"},
{"gamereport.send", "f11"},
{"gamereport.send", "f11"}, {"overlay.open", "tab"},
}},
}),
ConfigModFlag::CONTROLS);