Reintegrate accessibility using game-interactor.

This commit is contained in:
Caturria 2023-08-25 22:21:31 -04:00
commit 1a07a3e810
11 changed files with 140 additions and 18 deletions

View file

@ -16,6 +16,11 @@
#include <unordered_set>
#include "soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h"
#include "soh/Enhancements/tts/tts.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
extern "C" {
extern PlayState* gPlayState;
extern bool freezeGame;
}
const char* GetLanguageCode();
@ -50,7 +55,7 @@ typedef struct {
class ActorAccessibility {
public:
bool isOn = false;
uint64_t nextActorID;
uint64_t nextActorID = 0;
SupportedActors_t supportedActors;
TrackedActors_t trackedActors;
AccessibleActorList_t accessibleActorList;
@ -59,7 +64,7 @@ class ActorAccessibility {
AccessibleAudioEngine* audioEngine;
SfxExtractor sfxExtractor;
std::unordered_map<s16, SfxRecord> sfxMap;//Maps internal sfx to external (prerendered) resources.
int extractSfx = 0;
};
static ActorAccessibility* aa;
@ -68,12 +73,45 @@ uint64_t ActorAccessibility_GetNextID() {
aa->nextActorID++;
return result;
}
void ActorAccessibility_Init() {
aa = new ActorAccessibility();
ActorAccessibility_InitAudio();
ActorAccessibility_InitActors();
// Hooks for game-interactor.
void ActorAccessibility_OnActorInit(void* actor) {
ActorAccessibility_TrackNewActor((Actor*)actor);
}
void ActorAccessibility_OnGameFrameUpdate() {
if (gPlayState == NULL)
return;
ActorAccessibility_RunAccessibilityForAllActors(gPlayState);
}
void ActorAccessibility_OnActorDestroy(void* actor)
{
ActorAccessibility_RemoveTrackedActor((Actor*) actor);
}
void ActorAccessibility_OnGameStillFrozen()
{
if (gPlayState == NULL)
return;
if (aa->extractSfx)
ActorAccessibility_HandleSoundExtractionMode(gPlayState);
}
void ActorAccessibility_Init() {
aa = new ActorAccessibility();
aa->extractSfx = CVarGetInteger("gExtractSfx", 0);
if (aa->extractSfx)
freezeGame = true;
ActorAccessibility_InitAudio();
ActorAccessibility_InitActors();
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>(ActorAccessibility_OnActorInit);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorDestroy>(ActorAccessibility_OnActorDestroy);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>(ActorAccessibility_OnGameFrameUpdate);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameStillFrozen>(ActorAccessibility_OnGameStillFrozen);
}
void ActorAccessibility_Shutdown() {
ActorAccessibility_ShutdownAudio();
delete aa;
@ -114,7 +152,7 @@ int ActorAccessibility_GetRandomStartingFrameCount(int min, int max) {
}
void ActorAccessibility_TrackNewActor(Actor* actor) {
void ActorAccessibility_TrackNewActor(Actor * actor) {
// Don't track actors for which no accessibility policy has been configured.
ActorAccessibilityPolicy* policy = ActorAccessibility_GetPolicyForActor(actor->id);
if (policy == NULL)
@ -525,5 +563,3 @@ return NULL;//Resource doesn't exist, user's gotta run the extractor.
}

View file

@ -14,9 +14,10 @@ extern f32 D_801333E0;
extern s8 D_801333E8;
extern u8 D_801333F0;
void AudioMgr_CreateNextAudioBuffer(s16* samples, u32 num_samples);
extern bool freezeGame;
}
enum {
STEP_SETUP,
STEP_SETUP = 0,
STEP_MAIN,
STEP_FINISHED,
STEP_ERROR,
@ -92,6 +93,9 @@ void SfxExtractor::renderOutput() {
}
void SfxExtractor::setup() {
try {
ogMusicVolume = CVarGetFloat("gMainMusicVolume", 1.0);
CVarSetFloat("gMainMusicVolume", 0.0);
SpeechSynthesizer::Instance->Speak("Sfx extraction speedrun initiated. Please wait. This will take a few minutes.", GetLanguageCode());
// Kill the audio thread so we can take control.
captureThreadState = CT_WAITING;
@ -167,6 +171,8 @@ void SfxExtractor::finished() {
SpeechSynthesizer::Instance->Speak(ss.str().c_str(), GetLanguageCode());
} else
Audio_PlayFanfare(NA_BGM_ITEM_GET);
freezeGame = false;
}
void SfxExtractor::maybeGiveProgressReport() {
size_t ripsRemaining = sfxToRip.size() + 1;
@ -182,7 +188,8 @@ void SfxExtractor::maybeGiveProgressReport() {
SfxExtractor::SfxExtractor() {
currentStep = STEP_SETUP;
}
void SfxExtractor::frameCallback() {
void SfxExtractor::frameCallback() {
switch (currentStep) {
case STEP_SETUP:
setup();

View file

@ -10,6 +10,7 @@ class SfxExtractor {
s16 currentSfx;
std::vector<int16_t> tempStorage; // Stores raw audio data for the sfx currently being ripped.
int16_t* tempBuffer; // Raw pointer to the above vector.
f32 ogMusicVolume;
int progressMilestones[9]; // Implements progress reports after every 10 percent.
// Check if a buffer contains meaningful audio output.
bool isAllZero(int16_t* buffer, size_t count);
@ -23,6 +24,7 @@ class SfxExtractor {
void maybeGiveProgressReport();
public:
SfxExtractor();
void frameCallback();
void prime();
// The below is called by the (hijacked) audio thread.

View file

@ -153,6 +153,8 @@ public:
DEFINE_HOOK(OnOcarinaSongAction, void());
DEFINE_HOOK(OnActorInit, void(void* actor));
DEFINE_HOOK(OnActorUpdate, void(void* actor));
DEFINE_HOOK(OnActorDestroy, void(void* actor));
DEFINE_HOOK(OnPlayerBonk, void());
DEFINE_HOOK(OnPlayDestroy, void());
DEFINE_HOOK(OnPlayDrawEnd, void());
@ -180,6 +182,7 @@ public:
DEFINE_HOOK(OnUpdateFileNameSelection, void(int16_t charCode));
DEFINE_HOOK(OnSetGameLanguage, void());
DEFINE_HOOK(OnGameStillFrozen, void());
// Helpers
static bool IsSaveLoaded();

View file

@ -49,8 +49,11 @@ void GameInteractor_ExecuteOnActorInit(void* actor) {
void GameInteractor_ExecuteOnActorUpdate(void* actor) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorUpdate>(actor);
}
void GameInteractor_ExecuteOnActorDestroy(void* actor) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorDestroy>(actor);
}
void GameInteractor_ExecuteOnPlayerBonk() {
void GameInteractor_ExecuteOnPlayerBonk() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerBonk>();
}
@ -149,3 +152,7 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode) {
void GameInteractor_ExecuteOnSetGameLanguage() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
}
void GameInteractor_ExecuteOnGameStillFrozen()
{
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnGameStillFrozen>();
}

View file

@ -15,7 +15,10 @@ void GameInteractor_ExecuteOnSceneSpawnActors();
void GameInteractor_ExecuteOnPlayerUpdate();
void GameInteractor_ExecuteOnOcarinaSongAction();
void GameInteractor_ExecuteOnActorInit(void* actor);
void GameInteractor_ExecuteOnActorInit(void* actor);
void GameInteractor_ExecuteOnActorUpdate(void* actor);
void GameInteractor_ExecuteOnActorDestroy(void* actor);
void GameInteractor_ExecuteOnPlayerBonk();
void GameInteractor_ExecuteOnOcarinaSongAction();
void GameInteractor_ExecuteOnPlayDestroy();
@ -48,6 +51,7 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode);
// MARK: - Game
void GameInteractor_ExecuteOnSetGameLanguage();
void GameInteractor_ExecuteOnGameStillFrozen();
#ifdef __cplusplus
}
#endif

View file

@ -112,7 +112,8 @@ CrowdControl* CrowdControl::Instance;
#include "soh/resource/importer/BackgroundFactory.h"
#include "soh/config/ConfigUpdaters.h"
#include "soh/Enhancements/accessible-actors/ActorAccessibility.h"
#include "Enhancements//accessible-actors/ActorAccessibility.h"
OTRGlobals* OTRGlobals::Instance;
SaveManager* SaveManager::Instance;
CustomMessageManager* CustomMessageManager::Instance;
@ -230,6 +231,11 @@ OTRGlobals::OTRGlobals() {
}
}
}
std::string sohAccessibilityPath = LUS::Context::GetPathRelativeToAppDirectory("accessibility.otr");
if (std::filesystem::exists(sohAccessibilityPath)) {
OTRFiles.push_back(sohAccessibilityPath);
}
std::unordered_set<uint32_t> ValidHashes = {
OOT_PAL_MQ,
OOT_NTSC_JP_MQ,
@ -395,9 +401,13 @@ void OTRAudio_Thread() {
// 3 is the maximum authentic frame divisor.
s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3];
for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) {
AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS), num_audio_samples);
}
AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS),
num_audio_samples);
// Give accessibility a chance to merge its own audio in.
ActorAccessibility_MixAccessibleAudioWithGameAudio(
audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS), num_audio_samples);
}
AudioPlayer_Play((u8*)audio_buffer, num_audio_samples * (sizeof(int16_t) * NUM_AUDIO_CHANNELS * AUDIO_FRAMES_PER_UPDATE));
audio.processing = false;
@ -774,6 +784,7 @@ extern "C" void InitOTR() {
clearMtx = (uintptr_t)&gMtxClear;
OTRMessage_Init();
ActorAccessibility_Init();
OTRAudio_Init();
OTRExtScanner();
VanillaItemTable_Init();
@ -817,7 +828,7 @@ extern "C" void DeinitOTR() {
CrowdControl::Instance->Disable();
CrowdControl::Instance->Shutdown();
#endif
ActorAccessibility_Shutdown();
// Destroying gui here because we have shared ptrs to LUS objects which output to SPDLOG which is destroyed before these shared ptrs.
SohGui::Destroy();
@ -1017,7 +1028,7 @@ extern "C" void Graph_ProcessGfxCommands(Gfx* commands) {
last_update_rate = R_UPDATE_RATE;
{
std::unique_lock<std::mutex> Lock(audio.mutex);
std::unique_lock<std::mutex> Lock(audio.mutex);
while (audio.processing) {
audio.cv_from_thread.wait(Lock);
}
@ -2190,3 +2201,39 @@ extern "C" void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex) {
extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement) {
gfx_register_blended_texture(name, mask, replacement);
}
void OTRAudio_SfxCaptureThread() {
while (audio.running) {
{
std::unique_lock<std::mutex> Lock(audio.mutex);
while (!audio.processing && audio.running) {
audio.cv_to_thread.wait(Lock);
}
if (!audio.running) {
break;
}
}
std::unique_lock<std::mutex> Lock(audio.mutex);
ActorAccessibility_DoSoundExtractionStep();
audio.processing = false;
audio.cv_from_thread.notify_one();
}
}
extern "C" void OTRAudio_InstallSfxCaptureThread() {
OTRAudio_Exit();
audio.running = true;
audio.thread = std::thread(OTRAudio_SfxCaptureThread);
}
extern "C" void OTRAudio_UninstallSfxCaptureThread()
{
OTRAudio_Exit();
audio.running = true;
audio.thread = std::thread(OTRAudio_Thread);
}
std::unique_lock<std::mutex> OTRAudio_Lock()
{
return std::unique_lock<std::mutex>(audio.mutex);
}

View file

@ -156,14 +156,19 @@ void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement);
void SaveManager_ThreadPoolWait();
int32_t GetGIID(uint32_t itemID);
#endif
#ifdef __cplusplus
extern "C" {
#endif
uint64_t GetUnixTimestamp();
void OTRAudio_InstallSfxCaptureThread();
void OTRAudio_UninstallSfxCaptureThread();
#ifdef __cplusplus
};
std::unique_lock<std::mutex> OTRAudio_Lock();
#endif
#endif

View file

@ -3,6 +3,7 @@
#include "soh/mixer.h"
#include "soh/Enhancements/audio/AudioEditor.h"
extern bool freezeGame;
typedef struct {
u8 unk_0;
@ -371,6 +372,9 @@ extern f32 D_80130F28;
void Audio_QueueSeqCmd(u32 cmd)
{
if (freezeGame)
return;//No music during SFX rip.
u8 op = cmd >> 28;
if (op == 0 || op == 2 || op == 12) {
u8 seqId = cmd & 0xFF;

View file

@ -1224,6 +1224,7 @@ void Actor_Init(Actor* actor, PlayState* play) {
}
void Actor_Destroy(Actor* actor, PlayState* play) {
GameInteractor_ExecuteOnActorDestroy(actor);
if (actor->destroy != NULL) {
actor->destroy(actor, play);
actor->destroy = NULL;
@ -2476,6 +2477,7 @@ u32 D_80116068[ACTORCAT_MAX] = {
};
void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) {
Actor* refActor;
Actor* actor;
Player* player;

View file

@ -16,7 +16,7 @@
#include <time.h>
#include <assert.h>
bool freezeGame = false;//Used for SFX ripper.
void* D_8012D1F0 = NULL;
//UNK_TYPE D_8012D1F4 = 0; // unused
Input* D_8012D1F8 = NULL;
@ -753,6 +753,11 @@ void Play_Update(PlayState* play) {
Input* input;
u32 i;
s32 pad2;
if (freezeGame) {
GameInteractor_ExecuteOnGameStillFrozen();
return;
}
input = play->state.input;