Skip to content

Commit

Permalink
Add simple notifications system
Browse files Browse the repository at this point in the history
  • Loading branch information
garrettjoecox committed Oct 21, 2024
1 parent 4663bd1 commit 92c791b
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 26 deletions.
16 changes: 16 additions & 0 deletions soh/soh/Enhancements/randomizer/hook_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include "soh/Enhancements/randomizer/fishsanity.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ImGuiUtils.h"
#include "soh/Notification/Notification.h"

extern "C" {
#include "macros.h"
Expand Down Expand Up @@ -837,6 +839,20 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l
Randomizer_Item_Give(gPlayState, item00->itemEntry);
}
}

if (item00->itemEntry.modIndex == MOD_NONE) {
Notification::Emit({
.itemIcon = GetTextureForItemId(item00->itemEntry.itemId),
.message = "You found ",
.suffix = SohUtils::GetItemName(item00->itemEntry.itemId),
});
} else if (item00->itemEntry.modIndex == MOD_RANDOMIZER) {
Notification::Emit({
.message = "You found ",
.suffix = Rando::StaticData::RetrieveItem((RandomizerGet)item00->itemEntry.getItemId).GetName().english,
});
}

// This is typically called when you close the text box after getting an item, in case a previous
// function hid the interface.
Interface_ChangeAlpha(gSaveContext.unk_13EE);
Expand Down
36 changes: 34 additions & 2 deletions soh/soh/ImGuiUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,36 @@ std::map<uint32_t, ItemMapEntry> itemMapping = {
ITEM_MAP_ENTRY(ITEM_WALLET_GIANT),
ITEM_MAP_ENTRY(ITEM_SEEDS),
ITEM_MAP_ENTRY(ITEM_FISHING_POLE),
ITEM_MAP_ENTRY(ITEM_SONG_MINUET),
ITEM_MAP_ENTRY(ITEM_SONG_BOLERO),
ITEM_MAP_ENTRY(ITEM_SONG_SERENADE),
ITEM_MAP_ENTRY(ITEM_SONG_REQUIEM),
ITEM_MAP_ENTRY(ITEM_SONG_NOCTURNE),
ITEM_MAP_ENTRY(ITEM_SONG_PRELUDE),
ITEM_MAP_ENTRY(ITEM_SONG_LULLABY),
ITEM_MAP_ENTRY(ITEM_SONG_EPONA),
ITEM_MAP_ENTRY(ITEM_SONG_SARIA),
ITEM_MAP_ENTRY(ITEM_SONG_SUN),
ITEM_MAP_ENTRY(ITEM_SONG_TIME),
ITEM_MAP_ENTRY(ITEM_SONG_STORMS),
ITEM_MAP_ENTRY(ITEM_MEDALLION_FOREST),
ITEM_MAP_ENTRY(ITEM_MEDALLION_FIRE),
ITEM_MAP_ENTRY(ITEM_MEDALLION_WATER),
ITEM_MAP_ENTRY(ITEM_MEDALLION_SPIRIT),
ITEM_MAP_ENTRY(ITEM_MEDALLION_SHADOW),
ITEM_MAP_ENTRY(ITEM_MEDALLION_LIGHT),
ITEM_MAP_ENTRY(ITEM_KOKIRI_EMERALD),
ITEM_MAP_ENTRY(ITEM_GORON_RUBY),
ITEM_MAP_ENTRY(ITEM_ZORA_SAPPHIRE),
ITEM_MAP_ENTRY(ITEM_STONE_OF_AGONY),
ITEM_MAP_ENTRY(ITEM_GERUDO_CARD),
ITEM_MAP_ENTRY(ITEM_SKULL_TOKEN),
ITEM_MAP_ENTRY(ITEM_HEART_CONTAINER),
ITEM_MAP_ENTRY(ITEM_HEART_PIECE),
ITEM_MAP_ENTRY(ITEM_KEY_BOSS),
ITEM_MAP_ENTRY(ITEM_COMPASS),
ITEM_MAP_ENTRY(ITEM_DUNGEON_MAP),
ITEM_MAP_ENTRY(ITEM_KEY_SMALL),
ITEM_MAP_ENTRY(ITEM_HEART_CONTAINER),
ITEM_MAP_ENTRY(ITEM_HEART_PIECE),
ITEM_MAP_ENTRY(ITEM_MAGIC_SMALL),
ITEM_MAP_ENTRY(ITEM_MAGIC_LARGE)
};
Expand Down Expand Up @@ -156,6 +180,14 @@ std::array<SongMapEntry, 12> vanillaSongMapping = { {
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_PRELUDE, 255, 240, 100),
} };

const char* GetTextureForItemId(uint32_t itemId) {
auto it = itemMapping.find(itemId);
if (it != itemMapping.end()) {
return it->second.name.c_str();
}
return nullptr;
}

void RegisterImGuiItemIcons() {
for (const auto& entry : itemMapping) {
Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(entry.second.name, entry.second.texturePath, ImVec4(1, 1, 1, 1));
Expand Down
1 change: 1 addition & 0 deletions soh/soh/ImGuiUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extern "C" {
#include "textures/parameter_static/parameter_static.h"
}

const char* GetTextureForItemId(uint32_t itemId);
void RegisterImGuiItemIcons();

typedef struct {
Expand Down
139 changes: 139 additions & 0 deletions soh/soh/Notification/Notification.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@

#include "Notification.h"
#include <libultraship/libultraship.h>
#include "soh/OTRGlobals.h"

extern "C" {
#include "functions.h"
#include "macros.h"
#include "variables.h"
}

namespace Notification {

static uint32_t nextId = 0;
static std::vector<Options> notifications = {};

void Window::Draw() {
auto vp = ImGui::GetMainViewport();

const float margin = 30.0f;
const float padding = 10.0f;

int position = CVarGetInteger(CVAR_SETTING("Notifications.Position"), 0);

// Top Left
ImVec2 basePosition;
switch (position) {
case 0: // Top Left
basePosition = ImVec2(vp->Pos.x + margin, vp->Pos.y + margin);
break;
case 1: // Top Right
basePosition = ImVec2(vp->Pos.x + vp->Size.x - margin, vp->Pos.y + margin);
break;
case 2: // Bottom Left
basePosition = ImVec2(vp->Pos.x + margin, vp->Pos.y + vp->Size.y - margin);
break;
case 3: // Bottom Right
basePosition = ImVec2(vp->Pos.x + vp->Size.x - margin, vp->Pos.y + vp->Size.y - margin);
break;
case 4: // Hidden
return;
}

ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, CVarGetFloat(CVAR_SETTING("Notifications.BgOpacity"), 0.5f)));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);

for (int index = 0; index < notifications.size(); ++index) {
auto& notification = notifications[index];
int inverseIndex = -ABS(index - (notifications.size() - 1));

ImGui::SetNextWindowViewport(vp->ID);
if (notification.remainingTime < 4.0f) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, (notification.remainingTime - 1) / 3.0f);
} else {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f);
}

ImGui::Begin(("notification#" + std::to_string(notification.id)).c_str(), nullptr,
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoScrollWithMouse |
ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar
);

ImGui::SetWindowFontScale(CVarGetFloat(CVAR_SETTING("Notifications.Size"), 1.8f)); // Make this adjustable

ImVec2 notificationPos;
switch (position) {
case 0: // Top Left
notificationPos = ImVec2(basePosition.x, basePosition.y + ((ImGui::GetWindowSize().y + padding) * inverseIndex));
break;
case 1: // Top Right
notificationPos = ImVec2(basePosition.x - ImGui::GetWindowSize().x, basePosition.y + ((ImGui::GetWindowSize().y + padding) * inverseIndex));
break;
case 2: // Bottom Left
notificationPos = ImVec2(basePosition.x, basePosition.y - ((ImGui::GetWindowSize().y + padding) * (inverseIndex + 1)));
break;
case 3: // Bottom Right
notificationPos = ImVec2(basePosition.x - ImGui::GetWindowSize().x, basePosition.y - ((ImGui::GetWindowSize().y + padding) * (inverseIndex + 1)));
break;
}

ImGui::SetWindowPos(notificationPos);

if (notification.itemIcon != nullptr) {
ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(notification.itemIcon), ImVec2(24, 24));
ImGui::SameLine();
}
if (!notification.prefix.empty()) {
ImGui::TextColored(notification.prefixColor, "%s", notification.prefix.c_str());
ImGui::SameLine();
}
ImGui::TextColored(notification.messageColor, "%s", notification.message.c_str());
if (!notification.suffix.empty()) {
ImGui::SameLine();
ImGui::TextColored(notification.suffixColor, "%s", notification.suffix.c_str());
}

ImGui::End();
ImGui::PopStyleVar();
}

ImGui::PopStyleVar();
ImGui::PopStyleColor(2);
}


void Window::UpdateElement() {
for (int index = 0; index < notifications.size(); ++index) {
auto& notification = notifications[index];

// decrement remainingTime
notification.remainingTime -= ImGui::GetIO().DeltaTime;

// remove notification if it has expired
if (notification.remainingTime <= 0) {
notifications.erase(notifications.begin() + index);
--index;
}
}
}

void Emit(Options notification) {
notification.id = nextId++;
if (notification.remainingTime == 0.0f) {
notification.remainingTime = CVarGetFloat(CVAR_SETTING("Notifications.Duration"), 10.0f);
}
notifications.push_back(notification);
Audio_PlaySoundGeneral(NA_SE_SY_METRONOME, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
}

} // namespace Notification
37 changes: 37 additions & 0 deletions soh/soh/Notification/Notification.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#ifndef NOTIFICATION_H
#define NOTIFICATION_H
#ifdef __cplusplus

#include <string>
#include <libultraship/libultraship.h>

namespace Notification {

struct Options {
uint32_t id = 0;
const char* itemIcon = nullptr;
std::string prefix = "";
ImVec4 prefixColor = ImVec4(0.5f, 0.5f, 1.0f, 1.0f);
std::string message = "";
ImVec4 messageColor = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
std::string suffix = "";
ImVec4 suffixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f);
float remainingTime = 0.0f; // Seconds
};

class Window : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;

void InitElement() override {};
void DrawElement() override {};
void Draw() override;
void UpdateElement() override;
};

void Emit(Options notification);

} // namespace Notification

#endif // __cplusplus
#endif // NOTIFICATION_H
10 changes: 8 additions & 2 deletions soh/soh/SohGui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "Enhancements/cosmetics/authenticGfxPatches.h"
#include "Enhancements/resolution-editor/ResolutionEditor.h"
#include "Enhancements/debugger/MessageViewer.h"
#include "soh/Notification/Notification.h"

bool isBetaQuestEnabled = false;

Expand Down Expand Up @@ -132,6 +133,7 @@ namespace SohGui {
std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow;
std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow;
std::shared_ptr<SohModalWindow> mModalWindow;
std::shared_ptr<Notification::Window> mNotificationWindow;

void SetupGuiElements() {
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
Expand All @@ -141,9 +143,9 @@ namespace SohGui {

if (gui->GetMenuBar() && !gui->GetMenuBar()->IsVisible()) {
#if defined(__SWITCH__) || defined(__WIIU__)
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press - to access enhancements menu");
Notification::Emit({ .message = "Press - to access enhancements menu", .remainingTime = 10.0f });
#else
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press F1 to access enhancements menu");
Notification::Emit({ .message = "Press F1 to access enhancements menu", .remainingTime = 10.0f });
#endif
}

Expand Down Expand Up @@ -210,12 +212,16 @@ namespace SohGui {
mModalWindow = std::make_shared<SohModalWindow>(CVAR_WINDOW("ModalWindow"), "Modal Window");
gui->AddGuiWindow(mModalWindow);
mModalWindow->Show();
mNotificationWindow = std::make_shared<Notification::Window>(CVAR_WINDOW("Notifications"), "Notifications Window");
gui->AddGuiWindow(mNotificationWindow);
mNotificationWindow->Show();
}

void Destroy() {
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
gui->RemoveAllGuiWindows();

mNotificationWindow = nullptr;
mModalWindow = nullptr;
mAdvancedResolutionSettingsWindow = nullptr;
mRandomizerSettingsWindow = nullptr;
Expand Down
30 changes: 30 additions & 0 deletions soh/soh/SohMenuBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/presets.h"
#include "soh/Enhancements/mods.h"
#include "soh/Notification/Notification.h"
#include "Enhancements/cosmetics/authenticGfxPatches.h"
#ifdef ENABLE_REMOTE_CONTROL
#include "soh/Network/CrowdControl/CrowdControl.h"
Expand Down Expand Up @@ -564,6 +565,35 @@ void DrawSettingsMenu() {

ImGui::EndMenu();
}

UIWidgets::Spacer(0);

if (ImGui::BeginMenu("Notifications")) {
static const char* notificationPosition[] = {
"Top Left",
"Top Right",
"Bottom Left",
"Bottom Right",
"Hidden",
};

ImGui::Text("Position");
UIWidgets::EnhancementCombobox(CVAR_SETTING("Notifications.Position"), notificationPosition, 0);
UIWidgets::EnhancementSliderFloat("Duration: %.0f seconds", "##NotificationDuration", CVAR_SETTING("Notifications.Duration"), 3.0f, 30.0f, "", 10.0f, false, false, false);
UIWidgets::EnhancementSliderFloat("BG Opacity: %.1f %%", "##NotificaitonBgOpacity", CVAR_SETTING("Notifications.BgOpacity"), 0.0f, 1.0f, "", 0.5f, true, false, false);
UIWidgets::EnhancementSliderFloat("Size: %.1f", "##NotificaitonSize", CVAR_SETTING("Notifications.Size"), 1.0f, 5.0f, "", 1.8f, false, false, false);

UIWidgets::Spacer(0);

if (ImGui::Button("Test Notification", ImVec2(-1.0f, 0.0f))) {
Notification::Emit({
.message = (gPlayState != NULL ? SohUtils::GetSceneName(gPlayState->sceneNum) : "Hyrule") + " looks beautiful today!",
});
}

ImGui::EndMenu();
}

ImGui::EndMenu();
}
}
Expand Down
Loading

0 comments on commit 92c791b

Please sign in to comment.