mirror of
https://github.com/SysRay/psOff_public.git
synced 2024-11-23 06:19:41 +00:00
commit
6895ae9764
@ -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},
|
||||
|
@ -10,6 +10,7 @@ enum class HkCommand {
|
||||
|
||||
// System commands
|
||||
GAMEREPORT_SEND_REPORT,
|
||||
OVERLAY_MENU,
|
||||
|
||||
// Gamepad emulation
|
||||
CONTROLLER_L3,
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
206
core/videoout/overlay/gamemenu/gamemenu.cpp
Normal file
206
core/videoout/overlay/gamemenu/gamemenu.cpp
Normal 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();
|
||||
};
|
48
core/videoout/overlay/gamemenu/gamemenu.h
Normal file
48
core/videoout/overlay/gamemenu/gamemenu.h
Normal 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);
|
||||
};
|
28
core/videoout/overlay/imhelper.h
Normal file
28
core/videoout/overlay/imhelper.h
Normal 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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -39,6 +39,9 @@
|
||||
"gamereport.send": {
|
||||
"$ref": "#/definitions/button"
|
||||
},
|
||||
"overlay.open": {
|
||||
"$ref": "#/definitions/button"
|
||||
},
|
||||
"controller.circle": {
|
||||
"$ref": "#/definitions/button"
|
||||
},
|
||||
|
@ -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]"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user