mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-08-21 13:53:49 -07:00
[Accessibility] Text to Speech (#2487)
This commit is contained in:
parent
72777a0eb2
commit
21466192e5
38 changed files with 2279 additions and 28 deletions
|
@ -5,6 +5,12 @@ set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE)
|
|||
project(soh LANGUAGES C CXX)
|
||||
set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use")
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
enable_language(OBJCXX)
|
||||
set(CMAKE_OBJC_FLAGS "${CMAKE_OBJC_FLAGS} -fobjc-arc")
|
||||
set(CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS} -fobjc-arc")
|
||||
endif()
|
||||
|
||||
set (BUILD_UTILS OFF CACHE STRING "no utilities")
|
||||
set (BUILD_SHARED_LIBS OFF CACHE STRING "install/link shared instead of static libs")
|
||||
|
||||
|
@ -116,6 +122,10 @@ source_group("include" FILES ${Header_Files__include})
|
|||
file(GLOB soh__ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "soh/*.c" "soh/*.cpp" "soh/*.h")
|
||||
source_group("soh" FILES ${soh__})
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
set_source_files_properties(soh/OTRGlobals.cpp PROPERTIES COMPILE_FLAGS "/utf-8")
|
||||
endif()
|
||||
|
||||
# soh/enhancements {{{
|
||||
file(GLOB_RECURSE soh__Enhancements RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
"soh/Enhancements/*.c"
|
||||
|
@ -123,13 +133,25 @@ file(GLOB_RECURSE soh__Enhancements RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
|||
"soh/Enhancements/*.h"
|
||||
"soh/Enhancements/*.hpp"
|
||||
"soh/Enhancements/*_extern.inc"
|
||||
"soh/Enhancements/*.mm"
|
||||
)
|
||||
|
||||
list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/gamecommand.h")
|
||||
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/gfx.*")
|
||||
|
||||
# handle crowd control removals
|
||||
list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/crowd-control/soh.cs")
|
||||
if (!BUILD_CROWD_CONTROL)
|
||||
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/crowd-control/.*")
|
||||
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/crowd-control/*")
|
||||
endif()
|
||||
|
||||
# handle speechsynthesizer removals
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/Darwin*")
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/SAPI*")
|
||||
else()
|
||||
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/(Darwin|SAPI).*")
|
||||
endif()
|
||||
|
||||
source_group("soh\\Enhancements" REGULAR_EXPRESSION "soh/Enhancements/*")
|
||||
|
@ -145,6 +167,12 @@ source_group("soh\\Enhancements\\randomizer" REGULAR_EXPRESSION "soh/Enhancement
|
|||
source_group("soh\\Enhancements\\randomizer\\3drando" REGULAR_EXPRESSION "soh/Enhancements/randomizer/3drando/*")
|
||||
source_group("soh\\Enhancements\\randomizer\\3drando\\hint_list" REGULAR_EXPRESSION "soh/Enhancements/randomizer/3drando/hint_list/*")
|
||||
source_group("soh\\Enhancements\\randomizer\\3drando\\location_access" REGULAR_EXPRESSION "soh/Enhancements/randomizer/3drando/location_access/*")
|
||||
source_group("soh\\Enhancements\\speechsynthesizer" REGULAR_EXPRESSION "soh/Enhancements/speechsynthesizer/*")
|
||||
source_group("soh\\Enhancements\\tts" REGULAR_EXPRESSION "soh/Enhancements/tts/*")
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
set_source_files_properties(soh/Enhancements/tts/tts.cpp PROPERTIES COMPILE_FLAGS "/utf-8")
|
||||
endif()
|
||||
# }}}
|
||||
|
||||
# soh/resource {{{
|
||||
|
@ -225,7 +253,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
|||
endif()
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
OUTPUT_NAME "soh-macos"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES
|
||||
OUTPUT_NAME "soh-macos"
|
||||
)
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#define GameInteractor_h
|
||||
|
||||
#include "GameInteractionEffect.h"
|
||||
#include "z64.h"
|
||||
|
||||
typedef enum {
|
||||
/* 0x00 */ GI_LINK_SIZE_NORMAL,
|
||||
|
@ -87,13 +86,29 @@ public:
|
|||
DEFINE_HOOK(OnLoadGame, void(int32_t fileNum));
|
||||
DEFINE_HOOK(OnExitGame, void(int32_t fileNum));
|
||||
DEFINE_HOOK(OnGameFrameUpdate, void());
|
||||
DEFINE_HOOK(OnReceiveItem, void(u8 item));
|
||||
DEFINE_HOOK(OnSceneInit, void(s16 sceneNum));
|
||||
DEFINE_HOOK(OnReceiveItem, void(uint8_t item));
|
||||
DEFINE_HOOK(OnSceneInit, void(int16_t sceneNum));
|
||||
DEFINE_HOOK(OnPlayerUpdate, void());
|
||||
|
||||
DEFINE_HOOK(OnSaveFile, void(int32_t fileNum));
|
||||
DEFINE_HOOK(OnLoadFile, void(int32_t fileNum));
|
||||
DEFINE_HOOK(OnDeleteFile, void(int32_t fileNum));
|
||||
|
||||
DEFINE_HOOK(OnDialogMessage, void());
|
||||
DEFINE_HOOK(OnPresentTitleCard, void());
|
||||
DEFINE_HOOK(OnInterfaceUpdate, void());
|
||||
DEFINE_HOOK(OnKaleidoscopeUpdate, void(int16_t inDungeonScene));
|
||||
|
||||
DEFINE_HOOK(OnPresentFileSelect, void());
|
||||
DEFINE_HOOK(OnUpdateFileSelectSelection, void(uint16_t optionIndex));
|
||||
DEFINE_HOOK(OnUpdateFileCopySelection, void(uint16_t optionIndex));
|
||||
DEFINE_HOOK(OnUpdateFileCopyConfirmationSelection, void(uint16_t optionIndex));
|
||||
DEFINE_HOOK(OnUpdateFileEraseSelection, void(uint16_t optionIndex));
|
||||
DEFINE_HOOK(OnUpdateFileEraseConfirmationSelection, void(uint16_t optionIndex));
|
||||
DEFINE_HOOK(OnUpdateFileAudioSelection, void(uint8_t optionIndex));
|
||||
DEFINE_HOOK(OnUpdateFileTargetSelection, void(uint8_t optionIndex));
|
||||
|
||||
DEFINE_HOOK(OnSetGameLanguage, void());
|
||||
|
||||
// Helpers
|
||||
static bool IsSaveLoaded();
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
#include "GameInteractor_Hooks.h"
|
||||
|
||||
extern "C" {
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
// MARK: - Gameplay
|
||||
|
||||
void GameInteractor_ExecuteOnLoadGame(int32_t fileNum) {
|
||||
|
@ -18,11 +14,11 @@ void GameInteractor_ExecuteOnGameFrameUpdate() {
|
|||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnGameFrameUpdate>();
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnReceiveItemHooks(u8 item) {
|
||||
void GameInteractor_ExecuteOnReceiveItemHooks(uint8_t item) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnReceiveItem>(item);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnSceneInitHooks(s16 sceneNum) {
|
||||
void GameInteractor_ExecuteOnSceneInitHooks(int16_t sceneNum) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSceneInit>(sceneNum);
|
||||
}
|
||||
|
||||
|
@ -43,3 +39,61 @@ void GameInteractor_ExecuteOnLoadFile(int32_t fileNum) {
|
|||
void GameInteractor_ExecuteOnDeleteFile(int32_t fileNum) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnDeleteFile>(fileNum);
|
||||
}
|
||||
|
||||
// MARK: - Dialog
|
||||
|
||||
void GameInteractor_ExecuteOnDialogMessage() {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnDialogMessage>();
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnPresentTitleCard() {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPresentTitleCard>();
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnInterfaceUpdate() {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnInterfaceUpdate>();
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnKaleidoscopeUpdate(int16_t inDungeonScene) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnKaleidoscopeUpdate>(inDungeonScene);
|
||||
}
|
||||
|
||||
// MARK: - Main Menu
|
||||
|
||||
void GameInteractor_ExecuteOnPresentFileSelect() {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPresentFileSelect>();
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnUpdateFileSelectSelection(uint16_t optionIndex) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileSelectSelection>(optionIndex);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnUpdateFileCopySelection(uint16_t optionIndex) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileCopySelection>(optionIndex);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnUpdateFileCopyConfirmationSelection(uint16_t optionIndex) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileCopyConfirmationSelection>(optionIndex);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnUpdateFileEraseSelection(uint16_t optionIndex) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileEraseSelection>(optionIndex);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnUpdateFileEraseConfirmationSelection(uint16_t optionIndex) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileEraseConfirmationSelection>(optionIndex);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnUpdateFileAudioSelection(uint8_t optionIndex) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileAudioSelection>(optionIndex);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnUpdateFileTargetSelection(uint8_t optionIndex) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileTargetSelection>(optionIndex);
|
||||
}
|
||||
|
||||
// MARK: - Game
|
||||
|
||||
void GameInteractor_ExecuteOnSetGameLanguage() {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
|
||||
}
|
||||
|
|
|
@ -4,11 +4,30 @@
|
|||
extern "C" void GameInteractor_ExecuteOnLoadGame(int32_t fileNum);
|
||||
extern "C" void GameInteractor_ExecuteOnExitGame(int32_t fileNum);
|
||||
extern "C" void GameInteractor_ExecuteOnGameFrameUpdate();
|
||||
extern "C" void GameInteractor_ExecuteOnReceiveItemHooks(u8 item);
|
||||
extern "C" void GameInteractor_ExecuteOnSceneInit(s16 sceneNum);
|
||||
extern "C" void GameInteractor_ExecuteOnReceiveItemHooks(uint8_t item);
|
||||
extern "C" void GameInteractor_ExecuteOnSceneInit(int16_t sceneNum);
|
||||
extern "C" void GameInteractor_ExecuteOnPlayerUpdate();
|
||||
|
||||
// MARK: - Save Files
|
||||
extern "C" void GameInteractor_ExecuteOnSaveFile(int32_t fileNum);
|
||||
extern "C" void GameInteractor_ExecuteOnLoadFile(int32_t fileNum);
|
||||
extern "C" void GameInteractor_ExecuteOnDeleteFile(int32_t fileNum);
|
||||
|
||||
// MARK: - Dialog
|
||||
extern "C" void GameInteractor_ExecuteOnDialogMessage();
|
||||
extern "C" void GameInteractor_ExecuteOnPresentTitleCard();
|
||||
extern "C" void GameInteractor_ExecuteOnInterfaceUpdate();
|
||||
extern "C" void GameInteractor_ExecuteOnKaleidoscopeUpdate(int16_t inDungeonScene);
|
||||
|
||||
// MARK: - Main Menu
|
||||
extern "C" void GameInteractor_ExecuteOnPresentFileSelect();
|
||||
extern "C" void GameInteractor_ExecuteOnUpdateFileSelectSelection(uint16_t optionIndex);
|
||||
extern "C" void GameInteractor_ExecuteOnUpdateFileCopySelection(uint16_t optionIndex);
|
||||
extern "C" void GameInteractor_ExecuteOnUpdateFileCopyConfirmationSelection(uint16_t optionIndex);
|
||||
extern "C" void GameInteractor_ExecuteOnUpdateFileEraseSelection(uint16_t optionIndex);
|
||||
extern "C" void GameInteractor_ExecuteOnUpdateFileEraseConfirmationSelection(uint16_t optionIndex);
|
||||
extern "C" void GameInteractor_ExecuteOnUpdateFileAudioSelection(uint8_t optionIndex);
|
||||
extern "C" void GameInteractor_ExecuteOnUpdateFileTargetSelection(uint8_t optionIndex);
|
||||
|
||||
// MARK: - Game
|
||||
extern "C" void GameInteractor_ExecuteOnSetGameLanguage();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "mods.h"
|
||||
#include <libultraship/bridge.h>
|
||||
#include "game-interactor/GameInteractor.h"
|
||||
#include "tts/tts.h"
|
||||
|
||||
extern "C" {
|
||||
#include <z64.h>
|
||||
|
@ -262,6 +263,7 @@ void RegisterRupeeDash() {
|
|||
}
|
||||
|
||||
void InitMods() {
|
||||
RegisterTTS();
|
||||
RegisterInfiniteMoney();
|
||||
RegisterInfiniteHealth();
|
||||
RegisterInfiniteAmmo();
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// DarwinSpeechSynthesizer.h
|
||||
// libultraship
|
||||
//
|
||||
// Created by David Chavez on 22.11.22.
|
||||
//
|
||||
|
||||
#ifndef SOHDarwinSpeechSynthesizer_h
|
||||
#define SOHDarwinSpeechSynthesizer_h
|
||||
|
||||
#include "SpeechSynthesizer.h"
|
||||
|
||||
class DarwinSpeechSynthesizer : public SpeechSynthesizer {
|
||||
public:
|
||||
DarwinSpeechSynthesizer();
|
||||
|
||||
void Speak(const char* text, const char* language);
|
||||
|
||||
protected:
|
||||
bool DoInit(void);
|
||||
void DoUninitialize(void);
|
||||
|
||||
private:
|
||||
void* mSynthesizer;
|
||||
};
|
||||
|
||||
#endif /* DarwinSpeechSynthesizer_h */
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// DarwinSpeechSynthesizer.mm
|
||||
// libultraship
|
||||
//
|
||||
// Created by David Chavez on 22.11.22.
|
||||
//
|
||||
|
||||
#include "DarwinSpeechSynthesizer.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
DarwinSpeechSynthesizer::DarwinSpeechSynthesizer() {}
|
||||
|
||||
bool DarwinSpeechSynthesizer::DoInit() {
|
||||
mSynthesizer = (__bridge_retained void*)[[AVSpeechSynthesizer alloc] init];
|
||||
return true;
|
||||
}
|
||||
|
||||
void DarwinSpeechSynthesizer::DoUninitialize() {
|
||||
[(__bridge AVSpeechSynthesizer *)mSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
||||
mSynthesizer = nil;
|
||||
}
|
||||
|
||||
void DarwinSpeechSynthesizer::Speak(const char* text, const char* language) {
|
||||
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@(text)];
|
||||
[utterance setVoice:[AVSpeechSynthesisVoice voiceWithLanguage:@(language)]];
|
||||
|
||||
if (@available(macOS 11.0, *)) {
|
||||
[utterance setPrefersAssistiveTechnologySettings:YES];
|
||||
}
|
||||
|
||||
[(__bridge AVSpeechSynthesizer *)mSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
||||
[(__bridge AVSpeechSynthesizer *)mSynthesizer speakUtterance:utterance];
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// SAPISpeechSynthesizer.cpp
|
||||
// libultraship
|
||||
//
|
||||
// Created by David Chavez on 22.11.22.
|
||||
//
|
||||
|
||||
#include "SAPISpeechSynthesizer.h"
|
||||
#include <sapi.h>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
#include <spdlog/fmt/xchar.h>
|
||||
|
||||
ISpVoice* mVoice = NULL;
|
||||
|
||||
SAPISpeechSynthesizer::SAPISpeechSynthesizer() {
|
||||
}
|
||||
|
||||
bool SAPISpeechSynthesizer::DoInit() {
|
||||
CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
|
||||
CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&mVoice);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SAPISpeechSynthesizer::DoUninitialize() {
|
||||
mVoice->Release();
|
||||
mVoice = NULL;
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
std::wstring CharToWideString(std::string text) {
|
||||
int textSize = MultiByteToWideChar(CP_UTF8, 0, &text[0], (int)text.size(), NULL, 0);
|
||||
std::wstring wstrTo(textSize, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, &text[0], (int)text.size(), &wstrTo[0], textSize);
|
||||
return wstrTo;
|
||||
}
|
||||
|
||||
void SpeakThreadTask(std::string text, std::string language) {
|
||||
auto wText = CharToWideString(text);
|
||||
auto wLanguage = CharToWideString(language);
|
||||
|
||||
auto speakText = fmt::format(
|
||||
L"<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='{}'>{}</speak>", wLanguage, wText);
|
||||
mVoice->Speak(speakText.c_str(), SPF_IS_XML | SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
|
||||
}
|
||||
|
||||
void SAPISpeechSynthesizer::Speak(const char* text, const char* language) {
|
||||
// convert to string so char buffers don't have to be kept alive by caller
|
||||
std::string textStr(text);
|
||||
std::string languageStr(language);
|
||||
|
||||
std::thread t1(SpeakThreadTask, textStr, languageStr);
|
||||
t1.detach();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// SAPISpeechSynthesizer.h
|
||||
// libultraship
|
||||
//
|
||||
// Created by David Chavez on 22.11.22.
|
||||
//
|
||||
|
||||
#ifndef SOHSAPISpeechSynthesizer_h
|
||||
#define SOHSAPISpeechSynthesizer_h
|
||||
|
||||
#include "SpeechSynthesizer.h"
|
||||
#include <stdio.h>
|
||||
|
||||
class SAPISpeechSynthesizer : public SpeechSynthesizer {
|
||||
public:
|
||||
SAPISpeechSynthesizer();
|
||||
|
||||
void Speak(const char* text, const char* language);
|
||||
|
||||
protected:
|
||||
bool DoInit(void);
|
||||
void DoUninitialize(void);
|
||||
};
|
||||
|
||||
#endif /* SAPISpeechSynthesizer_h */
|
32
soh/soh/Enhancements/speechsynthesizer/SpeechSynthesizer.cpp
Normal file
32
soh/soh/Enhancements/speechsynthesizer/SpeechSynthesizer.cpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// SpeechSynthesizer.cpp
|
||||
// libultraship
|
||||
//
|
||||
// Created by David Chavez on 22.11.22.
|
||||
//
|
||||
|
||||
#include "SpeechSynthesizer.h"
|
||||
|
||||
SpeechSynthesizer::SpeechSynthesizer() : mInitialized(false){};
|
||||
|
||||
bool SpeechSynthesizer::Init(void) {
|
||||
if (mInitialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mInitialized = DoInit();
|
||||
return mInitialized;
|
||||
}
|
||||
|
||||
void SpeechSynthesizer::Uninitialize(void) {
|
||||
if (!mInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
DoUninitialize();
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
bool SpeechSynthesizer::IsInitialized(void) {
|
||||
return mInitialized;
|
||||
}
|
38
soh/soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h
Normal file
38
soh/soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// SpeechSynthesizer.h
|
||||
// libultraship
|
||||
//
|
||||
// Created by David Chavez on 22.11.22.
|
||||
//
|
||||
|
||||
#ifndef SOHSpeechSynthesizer_h
|
||||
#define SOHSpeechSynthesizer_h
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
class SpeechSynthesizer {
|
||||
public:
|
||||
static SpeechSynthesizer* Instance;
|
||||
SpeechSynthesizer();
|
||||
|
||||
bool Init(void);
|
||||
void Uninitialize(void);
|
||||
virtual void Speak(const char* text, const char* language) = 0;
|
||||
|
||||
bool IsInitialized(void);
|
||||
|
||||
protected:
|
||||
virtual bool DoInit(void) = 0;
|
||||
virtual void DoUninitialize(void) = 0;
|
||||
|
||||
private:
|
||||
bool mInitialized;
|
||||
};
|
||||
|
||||
#endif /* SpeechSynthesizer_h */
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "SAPISpeechSynthesizer.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include "DarwinSpeechSynthesizer.h"
|
||||
#endif
|
724
soh/soh/Enhancements/tts/tts.cpp
Normal file
724
soh/soh/Enhancements/tts/tts.cpp
Normal file
|
@ -0,0 +1,724 @@
|
|||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h"
|
||||
|
||||
#include <OtrFile.h>
|
||||
#include <libultraship/classes.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "message_data_static.h"
|
||||
#include "overlays/gamestates/ovl_file_choose/file_choose.h"
|
||||
|
||||
extern "C" {
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
/* 0x00 */ TEXT_BANK_SCENES,
|
||||
/* 0x01 */ TEXT_BANK_MISC,
|
||||
/* 0x02 */ TEXT_BANK_KALEIDO,
|
||||
/* 0x03 */ TEXT_BANK_FILECHOOSE,
|
||||
} TextBank;
|
||||
|
||||
nlohmann::json sceneMap = nullptr;
|
||||
nlohmann::json miscMap = nullptr;
|
||||
nlohmann::json kaleidoMap = nullptr;
|
||||
nlohmann::json fileChooseMap = nullptr;
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
std::string GetParameritizedText(std::string key, TextBank bank, const char* arg) {
|
||||
switch (bank) {
|
||||
case TEXT_BANK_SCENES: {
|
||||
return sceneMap[key].get<std::string>();
|
||||
break;
|
||||
}
|
||||
case TEXT_BANK_MISC: {
|
||||
auto value = miscMap[key].get<std::string>();
|
||||
|
||||
std::string searchString = "$0";
|
||||
size_t index = value.find(searchString);
|
||||
|
||||
if (index != std::string::npos) {
|
||||
ASSERT(arg != nullptr);
|
||||
value.replace(index, searchString.size(), std::string(arg));
|
||||
return value;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case TEXT_BANK_KALEIDO: {
|
||||
auto value = kaleidoMap[key].get<std::string>();
|
||||
|
||||
std::string searchString = "$0";
|
||||
size_t index = value.find(searchString);
|
||||
|
||||
if (index != std::string::npos) {
|
||||
ASSERT(arg != nullptr);
|
||||
value.replace(index, searchString.size(), std::string(arg));
|
||||
return value;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case TEXT_BANK_FILECHOOSE: {
|
||||
return fileChooseMap[key].get<std::string>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetLanguageCode() {
|
||||
switch (CVarGetInteger("gLanguages", 0)) {
|
||||
case LANGUAGE_FRA:
|
||||
return "fr-FR";
|
||||
break;
|
||||
case LANGUAGE_GER:
|
||||
return "de-DE";
|
||||
break;
|
||||
}
|
||||
|
||||
return "en-US";
|
||||
}
|
||||
|
||||
// MARK: - Boss Title Cards
|
||||
|
||||
std::string NameForSceneId(int16_t sceneId) {
|
||||
auto key = std::to_string(sceneId);
|
||||
auto name = GetParameritizedText(key, TEXT_BANK_SCENES, nullptr);
|
||||
return name;
|
||||
}
|
||||
|
||||
static std::string titleCardText;
|
||||
|
||||
void RegisterOnSceneInitHook() {
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
titleCardText = NameForSceneId(sceneNum);
|
||||
});
|
||||
}
|
||||
|
||||
void RegisterOnPresentTitleCardHook() {
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPresentTitleCard>([]() {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
SpeechSynthesizer::Instance->Speak(titleCardText.c_str(), GetLanguageCode());
|
||||
});
|
||||
}
|
||||
|
||||
// MARK: - Interface Updates
|
||||
|
||||
void RegisterOnInterfaceUpdateHook() {
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnInterfaceUpdate>([]() {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
static uint32_t prevTimer = 0;
|
||||
static char ttsAnnounceBuf[32];
|
||||
|
||||
uint32_t timer = 0;
|
||||
if (gSaveContext.timer1State != 0) {
|
||||
timer = gSaveContext.timer1Value;
|
||||
} else if (gSaveContext.timer2State != 0) {
|
||||
timer = gSaveContext.timer2Value;
|
||||
}
|
||||
|
||||
if (timer > 0) {
|
||||
if (timer > prevTimer || (timer % 30 == 0 && prevTimer != timer)) {
|
||||
uint32_t minutes = timer / 60;
|
||||
uint32_t seconds = timer % 60;
|
||||
char* announceBuf = ttsAnnounceBuf;
|
||||
char arg[8]; // at least big enough where no s8 string will overflow
|
||||
if (minutes > 0) {
|
||||
snprintf(arg, sizeof(arg), "%d", minutes);
|
||||
auto translation = GetParameritizedText((minutes > 1) ? "minutes_plural" : "minutes_singular", TEXT_BANK_MISC, arg);
|
||||
announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s ", translation.c_str());
|
||||
}
|
||||
if (seconds > 0) {
|
||||
snprintf(arg, sizeof(arg), "%d", seconds);
|
||||
auto translation = GetParameritizedText((seconds > 1) ? "seconds_plural" : "seconds_singular", TEXT_BANK_MISC, arg);
|
||||
announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s", translation.c_str());
|
||||
}
|
||||
ASSERT(announceBuf < ttsAnnounceBuf + sizeof(ttsAnnounceBuf));
|
||||
SpeechSynthesizer::Instance->Speak(ttsAnnounceBuf, GetLanguageCode());
|
||||
prevTimer = timer;
|
||||
}
|
||||
}
|
||||
|
||||
prevTimer = timer;
|
||||
|
||||
if (!GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
static int16_t lostHealth = 0;
|
||||
static int16_t prevHealth = 0;
|
||||
|
||||
if (gSaveContext.health - prevHealth < 0) {
|
||||
lostHealth += prevHealth - gSaveContext.health;
|
||||
}
|
||||
|
||||
if (gPlayState->state.frames % 7 == 0) {
|
||||
if (lostHealth >= 16) {
|
||||
Audio_PlaySoundGeneral(NA_SE_SY_CANCEL, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
|
||||
lostHealth -= 16;
|
||||
}
|
||||
}
|
||||
|
||||
prevHealth = gSaveContext.health;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void RegisterOnKaleidoscopeUpdateHook() {
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnKaleidoscopeUpdate>([](int16_t inDungeonScene) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
static uint16_t prevCursorIndex = 0;
|
||||
static uint16_t prevCursorSpecialPos = 0;
|
||||
static uint16_t prevCursorPoint[5] = { 0 };
|
||||
|
||||
PauseContext* pauseCtx = &gPlayState->pauseCtx;
|
||||
Input* input = &gPlayState->state.input[0];
|
||||
|
||||
if (pauseCtx->state != 6) {
|
||||
//reset cursor index to so it is announced when pause is reopened
|
||||
prevCursorIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((pauseCtx->debugState != 1) && (pauseCtx->debugState != 2)) {
|
||||
char arg[8];
|
||||
if (CHECK_BTN_ALL(input->press.button, BTN_DUP)) {
|
||||
snprintf(arg, sizeof(arg), "%d", gSaveContext.health);
|
||||
auto translation = GetParameritizedText("health", TEXT_BANK_KALEIDO, arg);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
} else if (CHECK_BTN_ALL(input->press.button, BTN_DLEFT)) {
|
||||
snprintf(arg, sizeof(arg), "%d", gSaveContext.magic);
|
||||
auto translation = GetParameritizedText("magic", TEXT_BANK_KALEIDO, arg);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
} else if (CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) {
|
||||
snprintf(arg, sizeof(arg), "%d", gSaveContext.rupees);
|
||||
auto translation = GetParameritizedText("rupees", TEXT_BANK_KALEIDO, arg);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
} else if (CHECK_BTN_ALL(input->press.button, BTN_DRIGHT)) {
|
||||
//TODO: announce timer?
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t cursorIndex = (pauseCtx->pageIndex == PAUSE_MAP && !inDungeonScene) ? PAUSE_WORLD_MAP : pauseCtx->pageIndex;
|
||||
if (prevCursorIndex == cursorIndex &&
|
||||
prevCursorSpecialPos == pauseCtx->cursorSpecialPos &&
|
||||
prevCursorPoint[cursorIndex] == pauseCtx->cursorPoint[cursorIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
prevCursorSpecialPos = pauseCtx->cursorSpecialPos;
|
||||
|
||||
if (pauseCtx->cursorSpecialPos > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pauseCtx->pageIndex) {
|
||||
case PAUSE_ITEM:
|
||||
{
|
||||
char arg[8]; // at least big enough where no s8 string will overflow
|
||||
switch (pauseCtx->cursorItem[PAUSE_ITEM]) {
|
||||
case ITEM_STICK:
|
||||
case ITEM_NUT:
|
||||
case ITEM_BOMB:
|
||||
case ITEM_BOMBCHU:
|
||||
case ITEM_SLINGSHOT:
|
||||
case ITEM_BOW:
|
||||
snprintf(arg, sizeof(arg), "%d", AMMO(pauseCtx->cursorItem[PAUSE_ITEM]));
|
||||
break;
|
||||
case ITEM_BEAN:
|
||||
snprintf(arg, sizeof(arg), "%d", 0);
|
||||
break;
|
||||
default:
|
||||
arg[0] = '\0';
|
||||
}
|
||||
|
||||
if (pauseCtx->cursorItem[PAUSE_ITEM] == 999) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string key = std::to_string(pauseCtx->cursorItem[PAUSE_ITEM]);
|
||||
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, arg);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case PAUSE_MAP:
|
||||
if (inDungeonScene) {
|
||||
if (pauseCtx->cursorItem[PAUSE_MAP] != PAUSE_ITEM_NONE) {
|
||||
std::string key = std::to_string(pauseCtx->cursorItem[PAUSE_MAP]);
|
||||
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
}
|
||||
} else {
|
||||
std::string key = std::to_string(0x0100 + pauseCtx->cursorPoint[PAUSE_WORLD_MAP]);
|
||||
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
SPDLOG_INFO("Item: {}", key);
|
||||
}
|
||||
break;
|
||||
case PAUSE_QUEST:
|
||||
{
|
||||
char arg[8]; // at least big enough where no s8 string will overflow
|
||||
switch (pauseCtx->cursorItem[PAUSE_QUEST]) {
|
||||
case ITEM_SKULL_TOKEN:
|
||||
snprintf(arg, sizeof(arg), "%d", gSaveContext.inventory.gsTokens);
|
||||
break;
|
||||
case ITEM_HEART_CONTAINER:
|
||||
snprintf(arg, sizeof(arg), "%d", ((gSaveContext.inventory.questItems & 0xF) & 0xF) >> 0x1C);
|
||||
break;
|
||||
default:
|
||||
arg[0] = '\0';
|
||||
}
|
||||
|
||||
if (pauseCtx->cursorItem[PAUSE_QUEST] == 999) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string key = std::to_string(pauseCtx->cursorItem[PAUSE_QUEST]);
|
||||
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, arg);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case PAUSE_EQUIP:
|
||||
{
|
||||
std::string key = std::to_string(pauseCtx->cursorItem[PAUSE_EQUIP]);
|
||||
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
prevCursorIndex = cursorIndex;
|
||||
memcpy(prevCursorPoint, pauseCtx->cursorPoint, sizeof(prevCursorPoint));
|
||||
});
|
||||
}
|
||||
|
||||
void RegisterOnUpdateMainMenuSelection() {
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPresentFileSelect>([]() {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
auto translation = GetParameritizedText("file1", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
});
|
||||
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileSelectSelection>([](uint16_t optionIndex) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
switch (optionIndex) {
|
||||
case FS_BTN_MAIN_FILE_1: {
|
||||
auto translation = GetParameritizedText("file1", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_MAIN_FILE_2: {
|
||||
auto translation = GetParameritizedText("file2", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_MAIN_FILE_3: {
|
||||
auto translation = GetParameritizedText("file3", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_MAIN_OPTIONS: {
|
||||
auto translation = GetParameritizedText("options", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_MAIN_COPY: {
|
||||
auto translation = GetParameritizedText("copy", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_MAIN_ERASE: {
|
||||
auto translation = GetParameritizedText("erase", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileCopySelection>([](uint16_t optionIndex) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
switch (optionIndex) {
|
||||
case FS_BTN_COPY_FILE_1: {
|
||||
auto translation = GetParameritizedText("file1", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_COPY_FILE_2: {
|
||||
auto translation = GetParameritizedText("file2", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_COPY_FILE_3: {
|
||||
auto translation = GetParameritizedText("file3", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_COPY_QUIT: {
|
||||
auto translation = GetParameritizedText("quit", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileCopyConfirmationSelection>([](uint16_t optionIndex) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
switch (optionIndex) {
|
||||
case FS_BTN_CONFIRM_YES: {
|
||||
auto translation = GetParameritizedText("confirm", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_CONFIRM_QUIT: {
|
||||
auto translation = GetParameritizedText("quit", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileEraseSelection>([](uint16_t optionIndex) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
switch (optionIndex) {
|
||||
case FS_BTN_ERASE_FILE_1: {
|
||||
auto translation = GetParameritizedText("file1", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_ERASE_FILE_2: {
|
||||
auto translation = GetParameritizedText("file2", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_ERASE_FILE_3: {
|
||||
auto translation = GetParameritizedText("file3", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_ERASE_QUIT: {
|
||||
auto translation = GetParameritizedText("quit", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileEraseConfirmationSelection>([](uint16_t optionIndex) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
switch (optionIndex) {
|
||||
case FS_BTN_CONFIRM_YES: {
|
||||
auto translation = GetParameritizedText("confirm", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_BTN_CONFIRM_QUIT: {
|
||||
auto translation = GetParameritizedText("quit", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileAudioSelection>([](uint8_t optionIndex) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
switch (optionIndex) {
|
||||
case FS_AUDIO_STEREO: {
|
||||
auto translation = GetParameritizedText("audio_stereo", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_AUDIO_MONO: {
|
||||
auto translation = GetParameritizedText("audio_mono", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_AUDIO_HEADSET: {
|
||||
auto translation = GetParameritizedText("audio_headset", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_AUDIO_SURROUND: {
|
||||
auto translation = GetParameritizedText("audio_surround", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileTargetSelection>([](uint8_t optionIndex) {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
switch (optionIndex) {
|
||||
case FS_TARGET_SWITCH: {
|
||||
auto translation = GetParameritizedText("target_switch", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
case FS_TARGET_HOLD: {
|
||||
auto translation = GetParameritizedText("target_hold", TEXT_BANK_FILECHOOSE, nullptr);
|
||||
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// MARK: - Dialog Messages
|
||||
|
||||
static uint8_t ttsHasMessage;
|
||||
static uint8_t ttsHasNewMessage;
|
||||
static int8_t ttsCurrentHighlightedChoice;
|
||||
|
||||
std::string remap(uint8_t character) {
|
||||
switch (character) {
|
||||
case 0x80: return "À";
|
||||
case 0x81: return "î";
|
||||
case 0x82: return "Â";
|
||||
case 0x83: return "Ä";
|
||||
case 0x84: return "Ç";
|
||||
case 0x85: return "È";
|
||||
case 0x86: return "É";
|
||||
case 0x87: return "Ê";
|
||||
case 0x88: return "Ë";
|
||||
case 0x89: return "Ï";
|
||||
case 0x8A: return "Ô";
|
||||
case 0x8B: return "Ö";
|
||||
case 0x8C: return "Ù";
|
||||
case 0x8D: return "Û";
|
||||
case 0x8E: return "Ü";
|
||||
case 0x8F: return "ß";
|
||||
case 0x90: return "à";
|
||||
case 0x91: return "á";
|
||||
case 0x92: return "â";
|
||||
case 0x93: return "ä";
|
||||
case 0x94: return "ç";
|
||||
case 0x95: return "è";
|
||||
case 0x96: return "é";
|
||||
case 0x97: return "ê";
|
||||
case 0x98: return "ë";
|
||||
case 0x99: return "ï";
|
||||
case 0x9A: return "ô";
|
||||
case 0x9B: return "ö";
|
||||
case 0x9C: return "ù";
|
||||
case 0x9D: return "û";
|
||||
case 0x9E: return "ü";
|
||||
case 0x9F: return GetParameritizedText("input_button_a", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA0: return GetParameritizedText("input_button_b", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA1: return GetParameritizedText("input_button_c", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA2: return GetParameritizedText("input_button_l", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA3: return GetParameritizedText("input_button_r", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA4: return GetParameritizedText("input_button_z", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA5: return GetParameritizedText("input_button_c_up", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA6: return GetParameritizedText("input_button_c_down", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA7: return GetParameritizedText("input_button_c_left", TEXT_BANK_MISC, nullptr);
|
||||
case 0xA8: return GetParameritizedText("input_button_c_right", TEXT_BANK_MISC, nullptr);
|
||||
case 0xAA: return GetParameritizedText("input_analog_stick", TEXT_BANK_MISC, nullptr);
|
||||
case 0xAB: return GetParameritizedText("input_d_pad", TEXT_BANK_MISC, nullptr);
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Message_TTS_Decode(uint8_t* sourceBuf, uint16_t startOfset, uint16_t size) {
|
||||
std::string output;
|
||||
uint32_t destWriteIndex = 0;
|
||||
uint8_t isListingChoices = 0;
|
||||
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
uint8_t cchar = sourceBuf[i + startOfset];
|
||||
|
||||
if (cchar < ' ') {
|
||||
switch (cchar) {
|
||||
case MESSAGE_NEWLINE:
|
||||
output += (isListingChoices) ? '\n' : ' ';
|
||||
break;
|
||||
case MESSAGE_THREE_CHOICE:
|
||||
case MESSAGE_TWO_CHOICE:
|
||||
output += '\n';
|
||||
isListingChoices = 1;
|
||||
break;
|
||||
case MESSAGE_COLOR:
|
||||
case MESSAGE_SHIFT:
|
||||
case MESSAGE_TEXT_SPEED:
|
||||
case MESSAGE_BOX_BREAK_DELAYED:
|
||||
case MESSAGE_FADE:
|
||||
case MESSAGE_ITEM_ICON:
|
||||
i++;
|
||||
break;
|
||||
case MESSAGE_FADE2:
|
||||
case MESSAGE_SFX:
|
||||
case MESSAGE_TEXTID:
|
||||
i += 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (cchar <= 0x80) {
|
||||
output += cchar;
|
||||
} else {
|
||||
output += remap(cchar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void RegisterOnDialogMessageHook() {
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnDialogMessage>([]() {
|
||||
if (!CVarGetInteger("gA11yTTS", 0)) return;
|
||||
|
||||
MessageContext *msgCtx = &gPlayState->msgCtx;
|
||||
|
||||
if (msgCtx->msgMode == MSGMODE_TEXT_NEXT_MSG || msgCtx->msgMode == MSGMODE_DISPLAY_SONG_PLAYED_TEXT_BEGIN || (msgCtx->msgMode == MSGMODE_TEXT_CONTINUING && msgCtx->stateTimer == 1)) {
|
||||
ttsHasNewMessage = 1;
|
||||
} else if (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING || msgCtx->msgMode == MSGMODE_TEXT_AWAIT_NEXT || msgCtx->msgMode == MSGMODE_TEXT_DONE || msgCtx->msgMode == MSGMODE_TEXT_DELAYED_BREAK
|
||||
|| msgCtx->msgMode == MSGMODE_OCARINA_STARTING || msgCtx->msgMode == MSGMODE_OCARINA_PLAYING
|
||||
|| msgCtx->msgMode == MSGMODE_DISPLAY_SONG_PLAYED_TEXT || msgCtx->msgMode == MSGMODE_DISPLAY_SONG_PLAYED_TEXT || msgCtx->msgMode == MSGMODE_SONG_PLAYED_ACT_BEGIN || msgCtx->msgMode == MSGMODE_SONG_PLAYED_ACT || msgCtx->msgMode == MSGMODE_SONG_PLAYBACK_STARTING || msgCtx->msgMode == MSGMODE_SONG_PLAYBACK || msgCtx->msgMode == MSGMODE_SONG_DEMONSTRATION_STARTING || msgCtx->msgMode == MSGMODE_SONG_DEMONSTRATION_SELECT_INSTRUMENT || msgCtx->msgMode == MSGMODE_SONG_DEMONSTRATION
|
||||
) {
|
||||
if (ttsHasNewMessage) {
|
||||
ttsHasMessage = 1;
|
||||
ttsHasNewMessage = 0;
|
||||
ttsCurrentHighlightedChoice = 0;
|
||||
|
||||
uint16_t size = msgCtx->decodedTextLen;
|
||||
auto decodedMsg = Message_TTS_Decode(msgCtx->msgBufDecoded, 0, size);
|
||||
SpeechSynthesizer::Instance->Speak(decodedMsg.c_str(), GetLanguageCode());
|
||||
} else if (msgCtx->msgMode == MSGMODE_TEXT_DONE && msgCtx->choiceNum > 0 && msgCtx->choiceIndex != ttsCurrentHighlightedChoice) {
|
||||
ttsCurrentHighlightedChoice = msgCtx->choiceIndex;
|
||||
uint16_t startOffset = 0;
|
||||
while (startOffset < msgCtx->decodedTextLen) {
|
||||
if (msgCtx->msgBufDecoded[startOffset] == MESSAGE_TWO_CHOICE || msgCtx->msgBufDecoded[startOffset] == MESSAGE_THREE_CHOICE) {
|
||||
startOffset++;
|
||||
break;
|
||||
}
|
||||
startOffset++;
|
||||
}
|
||||
|
||||
uint16_t endOffset = 0;
|
||||
if (startOffset < msgCtx->decodedTextLen) {
|
||||
uint8_t i = msgCtx->choiceIndex;
|
||||
while (i-- > 0) {
|
||||
while (startOffset < msgCtx->decodedTextLen) {
|
||||
if (msgCtx->msgBufDecoded[startOffset] == MESSAGE_NEWLINE) {
|
||||
startOffset++;
|
||||
break;
|
||||
}
|
||||
startOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
endOffset = startOffset;
|
||||
while (endOffset < msgCtx->decodedTextLen) {
|
||||
if (msgCtx->msgBufDecoded[endOffset] == MESSAGE_NEWLINE) {
|
||||
break;
|
||||
}
|
||||
endOffset++;
|
||||
}
|
||||
|
||||
if (startOffset < msgCtx->decodedTextLen && startOffset != endOffset) {
|
||||
uint16_t size = endOffset - startOffset;
|
||||
auto decodedMsg = Message_TTS_Decode(msgCtx->msgBufDecoded, startOffset, size);
|
||||
SpeechSynthesizer::Instance->Speak(decodedMsg.c_str(), GetLanguageCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (ttsHasMessage) {
|
||||
ttsHasMessage = 0;
|
||||
ttsHasNewMessage = 0;
|
||||
|
||||
if (msgCtx->decodedTextLen < 3 || (msgCtx->msgBufDecoded[msgCtx->decodedTextLen - 2] != MESSAGE_FADE && msgCtx->msgBufDecoded[msgCtx->decodedTextLen - 3] != MESSAGE_FADE2)) {
|
||||
SpeechSynthesizer::Instance->Speak("", GetLanguageCode()); // cancel current speech (except for faded out messages)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// MARK: - Main Registration
|
||||
|
||||
void InitTTSBank() {
|
||||
std::string languageSuffix = "_eng.json";
|
||||
switch (CVarGetInteger("gLanguages", 0)) {
|
||||
case LANGUAGE_FRA:
|
||||
languageSuffix = "_fra.json";
|
||||
break;
|
||||
case LANGUAGE_GER:
|
||||
languageSuffix = "_ger.json";
|
||||
break;
|
||||
}
|
||||
|
||||
auto sceneFile = OTRGlobals::Instance->context->GetResourceManager()->LoadFile("accessibility/texts/scenes" + languageSuffix);
|
||||
if (sceneFile != nullptr) {
|
||||
sceneMap = nlohmann::json::parse(sceneFile->Buffer, nullptr, true, true);
|
||||
}
|
||||
|
||||
auto miscFile = OTRGlobals::Instance->context->GetResourceManager()->LoadFile("accessibility/texts/misc" + languageSuffix);
|
||||
if (miscFile != nullptr) {
|
||||
miscMap = nlohmann::json::parse(miscFile->Buffer, nullptr, true, true);
|
||||
}
|
||||
|
||||
auto kaleidoFile = OTRGlobals::Instance->context->GetResourceManager()->LoadFile("accessibility/texts/kaleidoscope" + languageSuffix);
|
||||
if (kaleidoFile != nullptr) {
|
||||
kaleidoMap = nlohmann::json::parse(kaleidoFile->Buffer, nullptr, true, true);
|
||||
}
|
||||
|
||||
auto fileChooseFile = OTRGlobals::Instance->context->GetResourceManager()->LoadFile("accessibility/texts/filechoose" + languageSuffix);
|
||||
if (fileChooseFile != nullptr) {
|
||||
fileChooseMap = nlohmann::json::parse(fileChooseFile->Buffer, nullptr, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterOnSetGameLanguageHook() {
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSetGameLanguage>([]() {
|
||||
InitTTSBank();
|
||||
});
|
||||
}
|
||||
|
||||
void RegisterTTSModHooks() {
|
||||
RegisterOnSetGameLanguageHook();
|
||||
RegisterOnDialogMessageHook();
|
||||
RegisterOnSceneInitHook();
|
||||
RegisterOnPresentTitleCardHook();
|
||||
RegisterOnInterfaceUpdateHook();
|
||||
RegisterOnKaleidoscopeUpdateHook();
|
||||
RegisterOnUpdateMainMenuSelection();
|
||||
}
|
||||
|
||||
void RegisterTTS() {
|
||||
InitTTSBank();
|
||||
RegisterTTSModHooks();
|
||||
}
|
6
soh/soh/Enhancements/tts/tts.h
Normal file
6
soh/soh/Enhancements/tts/tts.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef TTS_H
|
||||
#define TTS_H
|
||||
|
||||
void RegisterTTS();
|
||||
|
||||
#endif
|
|
@ -33,6 +33,8 @@
|
|||
#include "Enhancements/crowd-control/CrowdControl.h"
|
||||
#endif
|
||||
|
||||
#include "Enhancements/game-interactor/GameInteractor.h"
|
||||
|
||||
#define EXPERIMENTAL() \
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 50, 50, 255)); \
|
||||
UIWidgets::Spacer(3.0f); \
|
||||
|
@ -297,15 +299,25 @@ namespace GameMenuBar {
|
|||
|
||||
if (ImGui::BeginMenu("Languages")) {
|
||||
UIWidgets::PaddedEnhancementCheckbox("Translate Title Screen", "gTitleScreenTranslation");
|
||||
UIWidgets::EnhancementRadioButton("English", "gLanguages", LANGUAGE_ENG);
|
||||
UIWidgets::EnhancementRadioButton("German", "gLanguages", LANGUAGE_GER);
|
||||
UIWidgets::EnhancementRadioButton("French", "gLanguages", LANGUAGE_FRA);
|
||||
if (UIWidgets::EnhancementRadioButton("English", "gLanguages", LANGUAGE_ENG)) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
|
||||
}
|
||||
if (UIWidgets::EnhancementRadioButton("German", "gLanguages", LANGUAGE_GER)) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
|
||||
}
|
||||
if (UIWidgets::EnhancementRadioButton("French", "gLanguages", LANGUAGE_FRA)) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
UIWidgets::Spacer(0);
|
||||
|
||||
if (ImGui::BeginMenu("Accessibility")) {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
UIWidgets::PaddedEnhancementCheckbox("Text to Speech", "gA11yTTS");
|
||||
UIWidgets::Tooltip("Enables text to speech for in game dialog");
|
||||
#endif
|
||||
UIWidgets::PaddedEnhancementCheckbox("Disable Idle Camera Re-Centering", "gA11yDisableIdleCam");
|
||||
UIWidgets::Tooltip("Disables the automatic re-centering of the camera when idle.");
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "OTRGlobals.h"
|
||||
#include "OTRGlobals.h"
|
||||
#include "OTRAudio.h"
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
@ -27,6 +27,7 @@
|
|||
#define DRWAV_IMPLEMENTATION
|
||||
#include <dr_libs/wav.h>
|
||||
#include <AudioPlayer.h>
|
||||
#include "Enhancements/speechsynthesizer/SpeechSynthesizer.h"
|
||||
#include "Enhancements/controls/GameControlEditor.h"
|
||||
#include "Enhancements/cosmetics/CosmeticsEditor.h"
|
||||
#include "Enhancements/audio/AudioCollection.h"
|
||||
|
@ -111,6 +112,7 @@ CustomMessageManager* CustomMessageManager::Instance;
|
|||
ItemTableManager* ItemTableManager::Instance;
|
||||
GameInteractor* GameInteractor::Instance;
|
||||
AudioCollection* AudioCollection::Instance;
|
||||
SpeechSynthesizer* SpeechSynthesizer::Instance;
|
||||
|
||||
extern "C" char** cameraStrings;
|
||||
std::vector<std::shared_ptr<std::string>> cameraStdStrings;
|
||||
|
@ -579,7 +581,14 @@ extern "C" void InitOTR() {
|
|||
ItemTableManager::Instance = new ItemTableManager();
|
||||
GameInteractor::Instance = new GameInteractor();
|
||||
AudioCollection::Instance = new AudioCollection();
|
||||
|
||||
#ifdef __APPLE__
|
||||
SpeechSynthesizer::Instance = new DarwinSpeechSynthesizer();
|
||||
SpeechSynthesizer::Instance->Init();
|
||||
#elif defined(_WIN32)
|
||||
SpeechSynthesizer::Instance = new SAPISpeechSynthesizer();
|
||||
SpeechSynthesizer::Instance->Init();
|
||||
#endif
|
||||
|
||||
clearMtx = (uintptr_t)&gMtxClear;
|
||||
OTRMessage_Init();
|
||||
OTRAudio_Init();
|
||||
|
@ -618,6 +627,9 @@ extern "C" void InitOTR() {
|
|||
|
||||
extern "C" void DeinitOTR() {
|
||||
OTRAudio_Exit();
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
SpeechSynthesizerUninitialize();
|
||||
#endif
|
||||
#ifdef ENABLE_CROWD_CONTROL
|
||||
CrowdControl::Instance->Disable();
|
||||
CrowdControl::Instance->Shutdown();
|
||||
|
@ -718,6 +730,10 @@ extern "C" void Graph_StartFrame() {
|
|||
|
||||
break;
|
||||
}
|
||||
case SDL_SCANCODE_F9: {
|
||||
// Toggle TTS
|
||||
CVarSetInteger("gA11yTTS", !CVarGetInteger("gA11yTTS", 0));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
OTRGlobals::Instance->context->StartFrame();
|
||||
|
|
|
@ -513,7 +513,7 @@ namespace UIWidgets {
|
|||
Spacer(0);
|
||||
}
|
||||
|
||||
void EnhancementRadioButton(const char* text, const char* cvarName, int id) {
|
||||
bool EnhancementRadioButton(const char* text, const char* cvarName, int id) {
|
||||
/*Usage :
|
||||
EnhancementRadioButton("My Visible Name","gMyCVarName", MyID);
|
||||
First arg is the visible name of the Radio button
|
||||
|
@ -528,13 +528,17 @@ namespace UIWidgets {
|
|||
make_invisible += text;
|
||||
make_invisible += cvarName;
|
||||
|
||||
bool ret = false;
|
||||
int val = CVarGetInteger(cvarName, 0);
|
||||
if (ImGui::RadioButton(make_invisible.c_str(), id == val)) {
|
||||
CVarSetInteger(cvarName, id);
|
||||
SohImGui::RequestCvarSaveOnNextTick();
|
||||
ret = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%s", text);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha) {
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace UIWidgets {
|
|||
bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = false, bool disabled = false, const char* disabledTooltipText = "");
|
||||
void PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = false, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = "");
|
||||
bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = false, bool disabled = false, const char* disabledTooltipText = "");
|
||||
void EnhancementRadioButton(const char* text, const char* cvarName, int id);
|
||||
bool EnhancementRadioButton(const char* text, const char* cvarName, int id);
|
||||
|
||||
bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow = true, bool has_alpha=false, bool TitleSameLine=false);
|
||||
void DrawFlagArray32(const std::string& name, uint32_t& flags);
|
||||
|
|
|
@ -1020,8 +1020,6 @@ void TitleCard_InitPlaceName(PlayState* play, TitleCardContext* titleCtx, void*
|
|||
}
|
||||
|
||||
titleCtx->texture = GetResourceDataByName(texture, false);
|
||||
|
||||
//titleCtx->texture = texture;
|
||||
titleCtx->isBossCard = false;
|
||||
titleCtx->hasTranslation = false;
|
||||
titleCtx->x = x;
|
||||
|
@ -1044,6 +1042,10 @@ void TitleCard_Update(PlayState* play, TitleCardContext* titleCtx) {
|
|||
}
|
||||
|
||||
if (DECR(titleCtx->delayTimer) == 0) {
|
||||
if (titleCtx->durationTimer == 80) {
|
||||
GameInteractor_ExecuteOnPresentTitleCard();
|
||||
}
|
||||
|
||||
if (DECR(titleCtx->durationTimer) == 0) {
|
||||
Math_StepToS(&titleCtx->alpha, 0, 30);
|
||||
Math_StepToS(&titleCtx->intensityR, 0, 70);
|
||||
|
|
|
@ -3133,6 +3133,8 @@ void Message_Update(PlayState* play) {
|
|||
if (msgCtx->msgLength == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
GameInteractor_ExecuteOnDialogMessage();
|
||||
|
||||
bool isB_Held = CVarGetInteger("gSkipText", 0) != 0 ? CHECK_BTN_ALL(input->cur.button, BTN_B) && !sTextboxSkipped
|
||||
: CHECK_BTN_ALL(input->press.button, BTN_B);
|
||||
|
|
|
@ -6064,6 +6064,8 @@ void Interface_Update(PlayState* play) {
|
|||
Left_HUD_Margin = CVarGetInteger("gHUDMargin_L", 0);
|
||||
Right_HUD_Margin = CVarGetInteger("gHUDMargin_R", 0);
|
||||
Bottom_HUD_Margin = CVarGetInteger("gHUDMargin_B", 0);
|
||||
|
||||
GameInteractor_ExecuteOnInterfaceUpdate();
|
||||
|
||||
if (CHECK_BTN_ALL(debugInput->press.button, BTN_DLEFT)) {
|
||||
gSaveContext.language = LANGUAGE_ENG;
|
||||
|
|
|
@ -148,6 +148,11 @@ typedef enum {
|
|||
/* 3 */ FS_AUDIO_SURROUND
|
||||
} AudioOption;
|
||||
|
||||
typedef enum {
|
||||
/* 0 */ FS_TARGET_SWITCH,
|
||||
/* 1 */ FS_TARGET_HOLD,
|
||||
} TargetOption;
|
||||
|
||||
typedef enum {
|
||||
/* 0 */ FS_CHAR_PAGE_HIRA,
|
||||
/* 1 */ FS_CHAR_PAGE_KATA,
|
||||
|
@ -209,8 +214,8 @@ void FileChoose_DrawNameEntry(GameState* thisx);
|
|||
void FileChoose_DrawCharacter(GraphicsContext* gfxCtx, void* texture, s16 vtx);
|
||||
|
||||
void HandleMouseInput(Input* input);
|
||||
u8 HandleMouseCursor(FileChooseContext* this, Input* input, int minx, int miny, int maxx, int maxy);
|
||||
Vec2f HandleMouseCursorSplit(FileChooseContext* this, Input* input, int minx, int miny, int maxx, int maxy, int countx,
|
||||
u8 HandleMouseCursor(FileChooseContext* thisx, Input* input, int minx, int miny, int maxx, int maxy);
|
||||
Vec2f HandleMouseCursorSplit(FileChooseContext* thisx, Input* input, int minx, int miny, int maxx, int maxy, int countx,
|
||||
int county);
|
||||
|
||||
extern s16 D_808123F0[];
|
||||
|
|
|
@ -349,6 +349,7 @@ void FileChoose_FinishFadeIn(GameState* thisx) {
|
|||
this->controlsAlpha = 255;
|
||||
this->windowAlpha = 200;
|
||||
this->configMode = CM_MAIN_MENU;
|
||||
GameInteractor_ExecuteOnPresentFileSelect();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,6 +479,8 @@ void FileChoose_UpdateRandomizer() {
|
|||
}
|
||||
}
|
||||
|
||||
uint16_t lastFileChooseButtonIndex;
|
||||
|
||||
/**
|
||||
* Update the cursor and wait for the player to select a button to change menus accordingly.
|
||||
* If an empty file is selected, enter the name entry config mode.
|
||||
|
@ -597,6 +600,11 @@ void FileChoose_UpdateMainMenu(GameState* thisx) {
|
|||
} else {
|
||||
this->warningLabel = FS_WARNING_NONE;
|
||||
}
|
||||
|
||||
if (lastFileChooseButtonIndex != this->buttonIndex) {
|
||||
GameInteractor_ExecuteOnUpdateFileSelectSelection(this->buttonIndex);
|
||||
lastFileChooseButtonIndex = this->buttonIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ void FileChoose_SetupCopySource(GameState* thisx) {
|
|||
}
|
||||
}
|
||||
|
||||
uint16_t lastCopyEraseButtonIndex;
|
||||
|
||||
/**
|
||||
* Allow the player to select a file to copy or exit back to the main menu.
|
||||
* Update function for `CM_SELECT_COPY_SOURCE`
|
||||
|
@ -110,6 +112,11 @@ void FileChoose_SelectCopySource(GameState* thisx) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastCopyEraseButtonIndex != this->buttonIndex) {
|
||||
GameInteractor_ExecuteOnUpdateFileCopySelection(this->buttonIndex);
|
||||
lastCopyEraseButtonIndex = this->buttonIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -379,6 +386,11 @@ void FileChoose_CopyConfirm(GameState* thisx) {
|
|||
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
|
||||
this->buttonIndex ^= 1;
|
||||
}
|
||||
|
||||
if (lastCopyEraseButtonIndex != this->buttonIndex) {
|
||||
GameInteractor_ExecuteOnUpdateFileCopyConfirmationSelection(this->buttonIndex);
|
||||
lastCopyEraseButtonIndex = this->buttonIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -724,6 +736,11 @@ void FileChoose_EraseSelect(GameState* thisx) {
|
|||
this->warningLabel = FS_WARNING_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastCopyEraseButtonIndex != this->buttonIndex) {
|
||||
GameInteractor_ExecuteOnUpdateFileEraseSelection(this->buttonIndex);
|
||||
lastCopyEraseButtonIndex = this->buttonIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -833,6 +850,11 @@ void FileChoose_EraseConfirm(GameState* thisx) {
|
|||
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
|
||||
this->buttonIndex ^= 1;
|
||||
}
|
||||
|
||||
if (lastCopyEraseButtonIndex != this->buttonIndex) {
|
||||
GameInteractor_ExecuteOnUpdateFileEraseConfirmationSelection(this->buttonIndex);
|
||||
lastCopyEraseButtonIndex = this->buttonIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -656,6 +656,7 @@ void FileChoose_StartOptions(GameState* thisx) {
|
|||
}
|
||||
|
||||
static u8 sSelectedSetting;
|
||||
int8_t lastOptionButtonIndex = -1;
|
||||
|
||||
/**
|
||||
* Update the cursor and appropriate settings for the options menu.
|
||||
|
@ -718,6 +719,19 @@ void FileChoose_UpdateOptionsMenu(GameState* thisx) {
|
|||
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
|
||||
sSelectedSetting ^= 1;
|
||||
}
|
||||
|
||||
if (sSelectedSetting == FS_SETTING_AUDIO) {
|
||||
if (lastOptionButtonIndex != gSaveContext.audioSetting) {
|
||||
GameInteractor_ExecuteOnUpdateFileAudioSelection(gSaveContext.audioSetting);
|
||||
lastOptionButtonIndex = gSaveContext.audioSetting;
|
||||
}
|
||||
} else {
|
||||
// offset to detect switching between modes
|
||||
if (lastOptionButtonIndex != FS_BTN_SELECT_QUIT + gSaveContext.zTargetSetting + 1) {
|
||||
GameInteractor_ExecuteOnUpdateFileTargetSelection(gSaveContext.zTargetSetting);
|
||||
lastOptionButtonIndex = FS_BTN_SELECT_QUIT + gSaveContext.zTargetSetting + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
|
|
|
@ -4260,4 +4260,6 @@ void KaleidoScope_Update(PlayState* play)
|
|||
osSyncPrintf(VT_RST);
|
||||
break;
|
||||
}
|
||||
|
||||
GameInteractor_ExecuteOnKaleidoscopeUpdate(sInDungeonScene);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue