// // audio/speechmanager.cpp // // Copyright (C) 1999-2006 Rockstar Games. All Rights Reserved. // #include "northaudioengine.h" #include "speechmanager.h" #include "scriptaudioentity.h" #include "control/replay/audio/SpeechAudioPacket.h" #include "audiohardware/waveslot.h" #include "audioengine/engine.h" #include "audioengine/engineutil.h" #include "speechaudioentity.h" #include "virtcxtresolves.h" #include "debug/debug.h" #include "vehicles/vehicle.h" #include "peds/ped.h" #include "streaming/streamingengine.h" #include "gameobjects.h" #include "profile/profiler.h" #include "debugaudio.h" AUDIO_PEDS_OPTIMISATIONS() audCurve g_VehicleOpennessToSpeechFilterCurve; audCurve g_VehicleOpennessToSpeechVolumeCurve; audCurve g_DistanceSqToPriorityScalarCurve; #if __BANK bool g_StressTestSpeech = false; audSound *g_StressTestSpeechSound = NULL; u32 g_StressTestSpeechVariation = 0; char g_StressTestSpeechVoiceName[128] = {0}; char g_StressTestSpeechContext[128] = {0}; u32 g_StressTestMaxStartOffset = 0; bool g_TestAnimalBankPriority = false; bool g_DrawSpeechSlots = false; bool g_DrawSpecificPainInfo = false; bool g_DrawPainPlayedTotals = false; bool g_DisplayAnimalBanks = false; bool g_ForceAnimalBankUpdate = false; bool g_OverrideSpecificPainLoaded = false; u8 g_OverrideSpecificPainLevel = 0; bool g_OverrideSpecificPainIsMale = false; bool g_ForceSpecificPainBankSwap = false; bool g_DebugTennisLoad = false; bool g_DebugTennisUnload = false; f32 g_LastRecordedPainValue; atRangeArray g_DebugNumTimesPainTypePlayed; extern bool g_UseTestPainBanks; bool g_HavePreparedPainBanksForTesting = false; bool g_PrintVoiceRefInfo = false; #endif u8 g_OverridePainVsAimScalar = 10; audSpeechManager g_SpeechManager; bool g_PrintUnknownContexts = false; u32 g_NumPlayingAmbientSpeech = 0; u32 g_StreamingIsBusy = 0; u32 g_TimeBeforeWallaCanPlay = 1500; u32 g_MaxWallaPlays = 15; extern u32 g_WallaReuseTime; //bool g_ChurnSpeech = true; u32 g_LastTimeSpeechWasPlaying = 0; u32 g_TimeBeforeChurningSpeech = 5000; u32 g_LastTimeStreamingWasBusy = 0; u32 g_StreamingBusyTime = 60; u32 g_TimeBetweenSpecificPainBankSelection = 10000; u32 g_TimeBetweenSpecificPainBankLoadAndSelection = 5000; bool g_ActivatePainSpecificBankSwapping = true; u32 g_MinTimeBetweenSameVariationOfPhoneCall = 180000; extern bool g_StreamingSchmeaming; extern audCategory *g_SpeechNormalCategory; extern const u32 g_NoVoiceHash; extern bool g_bSpeechAsserts; extern const char* g_PainContexts[AUD_PAIN_ID_NUM_TOTAL]; extern const char* g_PlayerPainContexts[AUD_PAIN_ID_NUM_TOTAL]; extern u32 g_PainContextHashs[AUD_PAIN_ID_NUM_TOTAL]; extern u32 g_PainContextPHashs[AUD_PAIN_ID_NUM_TOTAL]; extern u32 g_PlayerPainContextHashs[AUD_PAIN_ID_NUM_TOTAL]; extern u32 g_PlayerPainContextPHashs[AUD_PAIN_ID_NUM_TOTAL]; f32 g_SpeechRolloffWhenInACar = 2.0f; const u32 g_AnythingDullPHash = ATPARTIALSTRINGHASH("ANYTHING_DULL", 0x839929D9); #define AUD_NUM_MICHAEL_PAIN_BANKS (4) #define AUD_NUM_FRANKLIN_PAIN_BANKS (4) #define AUD_NUM_TREVOR_PAIN_BANKS (4) //#define AUD_NUM_MALE_PAIN_BANKS (3) #define AUD_NUM_MALE_WEAK_PAIN_BANKS (4) #define AUD_NUM_MALE_NORMAL_PAIN_BANKS (6) #define AUD_NUM_MALE_TOUGH_PAIN_BANKS (5) #define AUD_NUM_MALE_MIXED_PAIN_BANKS (9) #define AUD_NUM_FEMALE_WEAK_PAIN_BANKS (0) #define AUD_NUM_FEMALE_NORMAL_PAIN_BANKS (12) #define AUD_NUM_FEMALE_MIXED_PAIN_BANKS (2) const char* g_PainSlotType[4]; atRangeArray g_PainLevelString; char g_PlayerPainRootBankName[32]; const char* g_BreathSlotType[AUD_BREATHING_TYPE_TOTAL]; u8 g_NumBreathingBanks[AUD_BREATHING_TYPE_TOTAL]; const char* g_BreathContexts[AUD_NUM_PLAYER_VFX_TYPES]; #define NUM_MINI_DRIVING_CONTEXTS 15 #define NUM_MINI_NON_DRIVING_CONTEXTS 9 #define NUM_GANG_CONTEXTS 52 #define NUM_LOUD_CONTEXTS 16 //25 #define NUM_TIME_LIMITED_ANIMAL_FAR_CONTEXTS 1 u32 g_MiniDrivingContextHashes[NUM_MINI_DRIVING_CONTEXTS]; u32 g_MiniNonDrivingContextHashes[NUM_MINI_NON_DRIVING_CONTEXTS]; u32 g_GangContextHashes[NUM_GANG_CONTEXTS]; u32 g_LoudContextHashes[NUM_LOUD_CONTEXTS]; const char* g_MiniDrivingContexts[NUM_MINI_DRIVING_CONTEXTS] = { "RUN_FROM_FIGHT", "FIGHT", "DODGE", "BUMP", "GUN_RUN", "GUN_COOL", "TRAPPED", "VEHICLE_ATTACKED", "SURPRISED", "CRASH_CAR", "BLOCKED_VEHICLE", "BLOCKED_PED", "BLOCKED_GENERIC", "JACKED_IN_CAR", "JACK_CAR_BACK" }; const char* g_MiniNonDrivingContexts[NUM_MINI_NON_DRIVING_CONTEXTS] = { "RUN_FROM_FIGHT", "FIGHT", "DODGE", "BUMP", "GUN_RUN", "GUN_COOL", "TRAPPED", "VEHICLE_ATTACKED", "SURPRISED" }; const char* g_GangContexts[NUM_GANG_CONTEXTS] = { "ABUSE_DRIVER", "BEEN_SHOT_SUBMIT", "BLOCKED_PED", "BLOCKED_VEHICLE", "BUMP", "CONV_GANG_RESP", "CONV_GANG_STATE", "CONV_SMOKE_RESP", "CONV_SMOKE_STATE", "COVER_ME", "CRASH_BIKE", "CRASH_CAR", "CRASH_GENERIC", "DODGE", "DUCK", "FIGHT", "FOLLOWED_NO", "GANG_ASK_PLAYER_LEAVE", "GANG_ATTACK_WARNING", "GANG_BUMP", "GANG_CHASE", "GANG_DODGE_WARNING", "GANG_FIGHT_CHEER", "GANG_INTERVENE", "GANG_WATCH_THIS_GUY", "GANG_WATCH_THIS_GUY_RESP", "GANG_WATCH_THIS_GUY_SOLO", "GANG_WEAPON_WARNING", "GANG_YOU_DROP_WEAPON", "GENERIC_BYE", "GENERIC_FUCK_OFF", "GENERIC_HI", "GENERIC_NO", "GENERIC_YES", "INTIMIDATE_RESP", "JACKED_IN_CAR", "JACKED_ON_STREET", "JACKING_CAR_BACK", "JACKING_GENERIC_BACK", "MOVE_IN", "OVER_HANDLEBARS", "PLAYER_OVER_THERE", "PLAYER_UP_THERE", "SHIT", "SHOT_IN_LEG", "SHUT_UP_HORN", "SURROUNDED", "TAKE_COVER", "TARGET", "THANKS", "TRAPPED", "VEHICLE_ATTACKED" }; const char* g_LoudContexts[] = { "GET_DOWN", "SURROUNDED" , "MOVE_IN", "COVER_ME", "TARGET", "DUCK", "FALL_BACK", "TAKE_COVER", "ON_FIRE", "PANIC", "MEGAPHONE_FOOT_PURSUIT", "MEGAPHONE_PED_CLEAR_STREET", "PULL_OVER", "PULL_OVER_WARNING", "COP_HELI_MEGAPHONE", "COP_HELI_MEGAPHONE_WARNING" }; u32 g_TimeLimitedAnimalFarContextsPHashes[NUM_TIME_LIMITED_ANIMAL_FAR_CONTEXTS]; u32 g_TimeLimitedAnimalFarContextsLastPlayTime[NUM_TIME_LIMITED_ANIMAL_FAR_CONTEXTS]; u32 g_TimeLimitedAnimalFarContextsMinTimeBetween[NUM_TIME_LIMITED_ANIMAL_FAR_CONTEXTS] = { 0 }; const char* g_TimeLimitedAnimalFarContexts[NUM_TIME_LIMITED_ANIMAL_FAR_CONTEXTS] = { "" }; enum { AUD_MINI_VOICE_TYPE_NORMAL, AUD_MINI_VOICE_TYPE_DRIVING, AUD_MINI_VOICE_TYPE_HOOKER, AUD_MINI_VOICE_TYPE_GANG }; struct tVoice { u32 VoiceName; u32 ReferenceCount; u32 RunningTab; }; const char* g_AnimalBankNames[ANIMALTYPE_MAX] = { "NONE", "BOAR", "CHICKEN", "DOG", "DOG_ROTTWEILER", "HORSE", "COW", "COYOTE", "DEER", "EAGLE", "FISH", "HEN", "MONKEY", "MOUNTAIN_LION", "PIGEON", "RAT", "SEAGULL", "CROW", "PIG", "Chickenhawk", "Cormorant", "Rhesus", "Retriever", "Chimp", "Husky", "Shepherd", "Cat", "Whale", "Dolphin", "SmallDog", "Hammerhead", "Stingray", "Rabbit", "KillerWhale" }; static SpeechContext* g_DefaultSpeechContext = NULL; PF_PAGE(SpeechSoundsPage, "SpeechSounds"); PF_GROUP(SpeechSoundsGroup); PF_LINK(SpeechSoundsPage, SpeechSoundsGroup); PF_VALUE_INT(PlayingAmbientSpeech, SpeechSoundsGroup); PF_VALUE_INT(StreamingIsBusy, SpeechSoundsGroup); void audSpeechManager::Init() { audEntity::Init(); // Set up our slots for (s32 i=0; i(audNorthAudioEngine::GetMetadataManager().GetObjectMetadataPtr("DEFAULT_SPEECH_CONTEXT")); naAssertf(g_DefaultSpeechContext, "Failed to find default speech context object. Any speech played through the ambient speech system without its own object will fail."); for(int i=0; i(objName); #if __BANK if(!m_AnimalInfoArray[i].Data) { Warningf("Failed to find animal audio game object %s. Alert audio.", objName); } else { Warningf("Found animal audio game object %s.", objName); } #endif m_NextAnimalBankNumToLoad[i] = 1; m_NumAnimalAppearancesSinceLastUpdate[i] = 0; m_NearestAnimalAppearancesSinceLastUpdate[i] = -1.0f; m_NumAnimalBanks[i] = 0; bool bankFound = true; while(bankFound) { char bankName[64]; formatf(bankName, "ANIMALS_NEAR\\%s_NEAR_%.2i", g_AnimalBankNames[i], m_NumAnimalBanks[i]+1); if(audWaveSlot::FindBankId(bankName) != ~0U) { Displayf("Found animal bank %s, index(%d)", bankName, i); m_NumAnimalBanks[i]++; } else bankFound = false; } m_NumAnimalsMakingSound[i] = 0; } m_MaxNumAnimalSlots = 3; m_AnimalBankSlots.Reset(); m_AnimalBankSlots.Reserve(m_MaxNumAnimalSlots); for(u32 i = 0; i < m_MaxNumAnimalSlots; i++) { if(!Verifyf(m_AnimalBankSlots.GetCount() < ANIMALTYPE_MAX, "Audio is set up to allow more animal bank slots than there are animals. This should be fixed.")) { break; } formatf(slotName, sizeof(slotName), "ANIMAL_NEAR_%d", i); audWaveSlot* waveSlot = audWaveSlot::FindWaveSlot(slotName); if(waveSlot) { audAnimalBankSlot animalSlot; animalSlot.WaveSlot = waveSlot; m_AnimalBankSlots.PushAndGrow(animalSlot); //LoadAnimalBank((AnimalType)(s8)m_AnimalBankSlots.GetCount(), (s8)m_AnimalBankSlots.GetCount()-1); } else { Assertf(0, "Failed to find animal bank slot %s", slotName); } } m_TimeOfLastFullAnimalBankUpdate = 0; m_ForceAnimalBankUpdate = false; m_LoadingAfterAnimalUpdate = false; for(int i=0; i<2; i++) { for(int j=0; j 0) { audSpeechSound *speechSound = (audSpeechSound*)g_StressTestSpeechSound; naAssertf(speechSound->GetSoundTypeID() == SpeechSound::TYPE_ID, "Speech sound was expected to have type %d but instead had type %d", SpeechSound::TYPE_ID, speechSound->GetSoundTypeID()); g_StressTestSpeechVariation = ((g_StressTestSpeechVariation + 1) % numVariations); if(speechSound->InitSpeech(g_StressTestSpeechVoiceName, g_StressTestSpeechContext, g_StressTestSpeechVariation+1)) { speechSound->PrepareAndPlay(audWaveSlot::FindWaveSlot("SCRIPT_SPEECH_0"), true, -1); } else { speechSound->StopAndForget(); } } } } } if(g_DrawSpeechSlots) DrawSpeechSlots(); if(g_DrawSpecificPainInfo) DrawSpecificPainInfo(); if(g_DrawPainPlayedTotals) DrawPainPlayedTotals(); if(g_DisplayAnimalBanks) DrawAnimalBanks(); #endif // Go through our slots, and if any are marked as STOPPING, see if they're done, and free them up. for (s32 i=0; iGetReferenceCount() == 0) { // Slot's finished m_AmbientSpeechSlots[i].PlayState = AUD_SPEECH_SLOT_VACANT; } } if(m_AmbientSpeechSlots[i].PlayState == AUD_SPEECH_SLOT_ALLOCATED && m_AmbientSpeechSlots[i].SpeechAudioEntity && m_AmbientSpeechSlots[i].SpeechAudioEntity->GetParent() && m_AmbientSpeechSlots[i].SpeechAudioEntity->GetParent()->m_nDEflags.bFrozen) { m_AmbientSpeechSlots[i].SpeechAudioEntity->MarkToStopSpeech(); } } for (s32 i=0; iGetReferenceCount() == 0) { // Slot's finished m_ScriptedSpeechSlots[i].PlayState = AUD_SPEECH_SLOT_VACANT; } } } //if (m_PlayerSpeechSlot.PlayState == AUD_SPEECH_SLOT_STOPPING) //{ // naAssertf(m_PlayerSpeechSlot.WaveSlot, "Player speech slot has invalid WaveSlot"); // if (m_PlayerSpeechSlot.WaveSlot->GetReferenceCount() == 0) // { // // Slot's finished // m_PlayerSpeechSlot.PlayState = AUD_SPEECH_SLOT_VACANT; // } //} u32 currentTime = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0); if(g_ActivatePainSpecificBankSwapping && currentTime - m_LastTimeSpecificPainBankSelected > g_TimeBetweenSpecificPainBankSelection && currentTime - m_TimeSpecificBankLastLoaded > g_TimeBetweenSpecificPainBankLoadAndSelection) SelectSpecificPainBank(currentTime); #if __BANK else if(g_ForceSpecificPainBankSwap) { SelectSpecificPainBank(currentTime); g_ForceSpecificPainBankSwap = false; } #endif UpdatePainSlots(); UpdateBreathingBanks(); //UpdateGlobalSpeechMetrics(); #if __BANK audSpeechAudioEntity::UpdateVFXDebugDrawStructs(); audSpeechAudioEntity::UpdateAmbSpeechDebugDrawStructs(); #endif } void audSpeechManager::UpdateGlobalSpeechMetrics() { // See how in a car we are, and scale up speech rolloffs accordingly f32 inACarRatio = 0.0f; bool isPlayerVehicleATrain = false; audNorthAudioEngine::GetGtaEnvironment()->IsPlayerInVehicle(&inACarRatio, &isPlayerVehicleATrain); f32 rolloff = (1.0f - inACarRatio) + (inACarRatio*g_SpeechRolloffWhenInACar); if (isPlayerVehicleATrain) { rolloff = 1.0f; } // Get our relevant categories - don't scale up loud or high-rolloff, as these are already really big if (g_SpeechNormalCategory) { g_SpeechNormalCategory->SetDistanceRollOffScale(rolloff); } } const char* audSpeechManager::GetRandomContextForChurn() { return "BUMP"; } void audSpeechManager::ResetSpecificPainBankTrackingVars() { for(u8 i=0; i<3; ++i) { for(u8 j=0; j<2; ++j) m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[i][j] = 0; } } void audSpeechManager::RegisterPedAimedAtOrHurt(u8 toughness, bool shouldLowerToughness, bool hurt) { if(!g_ActivatePainSpecificBankSwapping) return; u32 amtToInc = hurt ? g_OverridePainVsAimScalar : 1; if(shouldLowerToughness) { switch(toughness) { case AUD_TOUGHNESS_MALE_COWARDLY: case AUD_TOUGHNESS_MALE_NORMAL: case AUD_TOUGHNESS_MALE_BUM: m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[AUD_PAIN_LEVEL_WEAK][0] += amtToInc; break; case AUD_TOUGHNESS_MALE_BRAVE: case AUD_TOUGHNESS_MALE_GANG: case AUD_TOUGHNESS_COP: m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[AUD_PAIN_LEVEL_NORMAL][0] += amtToInc; break; case AUD_TOUGHNESS_FEMALE: case AUD_TOUGHNESS_FEMALE_BUM: m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[AUD_PAIN_LEVEL_NORMAL][1] += amtToInc; break; default: break; } } else { switch(toughness) { case AUD_TOUGHNESS_MALE_COWARDLY: case AUD_TOUGHNESS_MALE_BUM: m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[AUD_PAIN_LEVEL_WEAK][0] += amtToInc; break; case AUD_TOUGHNESS_MALE_NORMAL: m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[AUD_PAIN_LEVEL_NORMAL][0] += amtToInc; break; case AUD_TOUGHNESS_MALE_BRAVE: case AUD_TOUGHNESS_MALE_GANG: case AUD_TOUGHNESS_COP: m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[AUD_PAIN_LEVEL_TOUGH][0] += amtToInc; break; case AUD_TOUGHNESS_FEMALE: case AUD_TOUGHNESS_FEMALE_BUM: m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[AUD_PAIN_LEVEL_NORMAL][1] += amtToInc; break; default: break; } } } void audSpeechManager::SelectSpecificPainBank(u32 currentTime) { m_LastTimeSpecificPainBankSelected = currentTime; #if __BANK if(g_OverrideSpecificPainLoaded) { m_SpecificPainLevelToLoad = g_OverrideSpecificPainLevel; m_SpecificPainToLoadIsMale = g_OverrideSpecificPainIsMale; m_NeedToLoad[AUD_PAIN_VOICE_TYPE_SPECIFIC] = true; ResetSpecificPainBankTrackingVars(); return; } #endif u32 highestCount = 0; u8 iForHighestCount = 0; u8 jForHighestCount = 0; for(u8 i=0; i highestCount) { highestCount = m_PedsAimedAtOrHurtSinceLastSpecificPainBankSelected[i][j]; iForHighestCount = i; jForHighestCount = j; } } } bool shouldLoadMale = jForHighestCount==0; if(highestCount != 0 && (m_SpecificPainLevelLoaded != iForHighestCount || m_SpecificPainLoadedIsMale != shouldLoadMale)) { m_SpecificPainLevelToLoad = iForHighestCount; m_SpecificPainToLoadIsMale = shouldLoadMale; m_NeedToLoad[AUD_PAIN_VOICE_TYPE_SPECIFIC] = true; } //For now, we'll just keep whatever is loaded loaded. Perhaps we'll want to fall back to MALE_NORMAL /*else { }*/ ResetSpecificPainBankTrackingVars(); } void audSpeechManager::UpdatePainSlots() { #if GTA_REPLAY if(CReplayMgr::IsEditModeActive() && (CReplayMgr::GetCurrentPlayBackState().IsSet(REPLAY_DIRECTION_BACK) || CReplayMgr::GetCurrentPlayBackState().IsSet(REPLAY_CURSOR_JUMP))) return; #endif // GTA_REPLAY #if __BANK if(g_UseTestPainBanks && !g_HavePreparedPainBanksForTesting) { m_PainSlots[AUD_PAIN_VOICE_TYPE_MALE].SlotState = AUD_PAIN_SLOT_USED_UP; m_PainSlots[AUD_PAIN_VOICE_TYPE_FEMALE].SlotState = AUD_PAIN_SLOT_USED_UP; g_HavePreparedPainBanksForTesting = true; } else if(!g_UseTestPainBanks && g_HavePreparedPainBanksForTesting) { g_HavePreparedPainBanksForTesting = false; } #endif // Check current state of unused one, and kick off load if required, and we're free to. for (u32 painVoiceType=0; painVoiceType= m_NumPainInCurrentBank[painVoiceType][i]) { m_PainSlots[painVoiceType].SlotState = AUD_PAIN_SLOT_USED_UP; break; } } break; } } s32 nextPainTypeToLoad = AUD_PAIN_VOICE_NUM_TYPES; if(m_PainTypeLoading >= 0) nextPainTypeToLoad = m_PainTypeLoading; else { for(u32 painVoiceType=0; painVoiceTypeGetReferenceCount() == 0) { //may as well cache this, in case we end up setting ToLoad from a different thread, though it will only be used // if loading the specific bank bool loadingSpecificMale = m_SpecificPainToLoadIsMale; // Get the next bank name u8 nextPainBank = (m_CurrentPainBank[nextPainTypeToLoad] % numBanks) + 1; #if GTA_REPLAY if(CReplayMgr::ShouldRecord()) { CReplayMgr::RecordFx(CPacketPainWaveBankChange(m_CurrentPainBank[nextPainTypeToLoad], nextPainBank)); } #endif // GTA_REPLAY char voiceName[64] = ""; if (nextPainTypeToLoad==AUD_PAIN_VOICE_TYPE_PLAYER) { formatf(voiceName, 64, "%s_%02d", g_PlayerPainRootBankName, nextPainBank); } else if (nextPainTypeToLoad==AUD_PAIN_VOICE_TYPE_FEMALE) { formatf(voiceName, 64, "PAIN_FEMALE_NORMAL_%02d", nextPainBank); } else if(nextPainTypeToLoad==AUD_PAIN_VOICE_TYPE_SPECIFIC) { //there are no specific female banks if(loadingSpecificMale) { formatf(voiceName, 64, "PAIN_%s_%s_%02d", "MALE", g_PainLevelString[painLevel], nextPainBank); } else { formatf(voiceName, 64, "PAIN_FEMALE_NORMAL_%02d", nextPainBank); } } else if(nextPainTypeToLoad==AUD_PAIN_VOICE_TYPE_BREATHING) { formatf(voiceName, 64, "%s_%02d", g_BreathSlotType[m_BreathingTypeToLoad], nextPainBank); } else { #if __BANK if(g_UseTestPainBanks) nextPainBank = 1; #endif formatf(voiceName, 64, "%s_MIXED_%02d", g_PainSlotType[nextPainTypeToLoad], nextPainBank); } u32 bankId = audSpeechSound::FindBankIdForVoiceAndContext(voiceName, "PAIN_WHEEZE"); if(nextPainTypeToLoad==AUD_PAIN_VOICE_TYPE_BREATHING) { bankId = audSpeechSound::FindBankIdForVoiceAndContext(voiceName, "EXHALE"); } else if(bankId == AUD_INVALID_BANK_ID) { bankId = audSpeechSound::FindBankIdForVoiceAndContext(voiceName, "PAIN_SHOVE"); if(bankId == AUD_INVALID_BANK_ID) { bankId = audSpeechSound::FindBankIdForVoiceAndContext(voiceName, "PAIN_LOW"); } } if (bankId < AUD_INVALID_BANK_ID) { Assign(loadingSlot.BankId, bankId); loadingSlot.WaveSlot->LoadBank(bankId); loadingSlot.SlotState = AUD_PAIN_SLOT_LOADING; loadingSlot.VoiceNameHash = atStringHash(voiceName); loadingSlot.PainLevel = painLevel; #if __BANK safecpy(loadingSlot.VoiceName, voiceName); #endif m_NextTimeToLoadPain[nextPainTypeToLoad] = audNorthAudioEngine::GetCurrentTimeInMs() + 1500; m_TimeLastLoaded[nextPainTypeToLoad] = audNorthAudioEngine::GetCurrentTimeInMs(); if(nextPainTypeToLoad==AUD_PAIN_VOICE_TYPE_SPECIFIC) m_SpecificPainLoadingIsMale = loadingSpecificMale; } else { m_NextTimeToLoadPain[nextPainTypeToLoad] = audNorthAudioEngine::GetCurrentTimeInMs() + 60000; m_TimeLastLoaded[nextPainTypeToLoad] = audNorthAudioEngine::GetCurrentTimeInMs(); Assign(m_PainTypeLoading, -1); } } else { Assign(m_PainTypeLoading, -1); } } break; case AUD_PAIN_SLOT_LOADING: // See if it's done loadStatus = loadingSlot.WaveSlot->GetBankLoadingStatus(loadingSlot.BankId); switch (loadStatus) { case audWaveSlot::NOT_REQUESTED: naErrorf("Pain bank not requested but thinks it has been"); loadingSlot.SlotState = AUD_PAIN_SLOT_READY; break; case audWaveSlot::FAILED: // Assertf(0, "Audio: Pain bank load failed. Bank: %s, Slot: %d", // audWaveSlot::GetBankName(m_PainSlots[painVoiceType][unusedSlot].BankId), unusedSlot); naErrorf("Pain bank load load failed. Bank: %s", audWaveSlot::GetBankName(loadingSlot.BankId)); loadingSlot.SlotState = AUD_PAIN_SLOT_READY; break; case audWaveSlot::LOADED: // change a few states, and point at that one m_NeedToLoad[m_PainTypeLoading] = false; m_CurrentPainBank[nextPainTypeToLoad] = (m_CurrentPainBank[nextPainTypeToLoad] % numBanks) + 1; if(nextPainTypeToLoad!=AUD_PAIN_VOICE_TYPE_BREATHING) { for (u32 i=0; iGetPedType() == PEDTYPE_PLAYER_0) { voiceNameHash = ATSTRINGHASH("WAVELOAD_PAIN_MICHAEL", 0x6531A692); } else if(ped->GetPedType() == PEDTYPE_PLAYER_1) { voiceNameHash = ATSTRINGHASH("WAVELOAD_PAIN_FRANKLIN", 0x33F65FC3); } else if(ped->GetPedType() == PEDTYPE_PLAYER_2) { voiceNameHash = ATSTRINGHASH("WAVELOAD_PAIN_TREVOR", 0xCF83E9F); } } if(voiceNameHash == 0) { char voiceName[64]; formatf(voiceName, "WAVELOAD_%s", g_PainSlotType[painVoiceType]); voiceNameHash = atStringHash(voiceName); } return voiceNameHash; } void audSpeechManager::UpdateBreathingBanks() { if(!FindPlayerPed() || !FindPlayerPed()->GetSpeechAudioEntity() || FindPlayerPed()->GetSpeechAudioEntity()->IsAnimal()) return; switch(FindPlayerPed()->GetPedType()) { case PEDTYPE_PLAYER_0: m_BreathingTypeToLoad = AUD_BREATHING_TYPE_MICHAEL; break; case PEDTYPE_PLAYER_1: m_BreathingTypeToLoad = AUD_BREATHING_TYPE_FRANKLIN; break; case PEDTYPE_PLAYER_2: m_BreathingTypeToLoad = AUD_BREATHING_TYPE_TREVOR; break; default: m_BreathingTypeToLoad = AUD_BREATHING_TYPE_MICHAEL; } if(m_BreathingTypeToLoad != m_BreathingTypeCurrentlyLoaded) { m_NeedToLoad[AUD_PAIN_VOICE_TYPE_BREATHING] = true; } } const char* audSpeechManager::GetBreathingInfo(PlayerVocalEffectType eEffectType, u32& voiceNameHash, u8& variation) { if(naVerifyf(eEffectType < AUD_NUM_PLAYER_VFX_TYPES, "Invalid vfxType passed into PlayedBreath()")) { variation = m_NextVariationForBreath[eEffectType]; voiceNameHash = m_PainSlots[AUD_PAIN_VOICE_TYPE_BREATHING].VoiceNameHash; return g_BreathContexts[eEffectType]; } return ""; } void audSpeechManager::PlayedBreath(PlayerVocalEffectType eEffectType) { if(naVerifyf(eEffectType < AUD_NUM_PLAYER_VFX_TYPES, "Invalid vfxType passed into PlayedBreath()")) { m_NextVariationForBreath[eEffectType]++; if(m_NextVariationForBreath[eEffectType] > m_BreathVariationsInCurrentBank[eEffectType]) { m_NeedToLoad[AUD_PAIN_VOICE_TYPE_BREATHING] = true; m_NextVariationForBreath[eEffectType] = 1; } } } void audSpeechManager::UpdateAnimalBanks() { static u32 sMinTimeBetweenFullUpdates = 4000; #if __BANK if(g_DebugTennisLoad) { RequestTennisBanks(FindPlayerPed(), true); g_DebugTennisLoad = false; } if(g_DebugTennisUnload) { UnrequestTennisBanks(); g_DebugTennisUnload = false; } #endif if(m_TennisIsUsingAnimalBanks) { UpdateTennisBanks(); return; } for(int i=0; iGetBankLoadingStatus(m_AnimalBankSlots[i].BankdID)) { case audWaveSlot::FAILED: m_AnimalInfoArray[m_AnimalBankSlots[i].Owner].SlotNumber = -1; m_AnimalBankSlots[i].BankdID = ~0U; m_AnimalBankSlots[i].Owner = kAnimalNone; m_AnimalBankSlots[i].IsLoading = false; m_AnimalBankSlots[i].FailedLastTime = true; break; case audWaveSlot::LOADED: m_AnimalInfoArray[m_AnimalBankSlots[i].Owner].SlotNumber = (s8)i; m_AnimalBankSlots[i].IsLoading = false; break; default: break; } } } if(GetIsCurrentlyLoadingAnimalBank()) return; if(m_LoadingAfterAnimalUpdate) { for(int i=0; i= 0) { if(m_AnimalBankSlots[i].ContextInfo[j].UsedUp > 0) m_AnimalBankSlots[i].ContextInfo[j].UsedUp--; if(m_AnimalBankSlots[i].ContextInfo[j].UsedUp==0 && m_AnimalBankSlots[i].ContextInfo[j].ForcesSwap) { LoadAnimalBank(m_AnimalBankSlots[i].Owner, (s8)i); return; } } } } u32 currentTime = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0); if(m_TimeOfLastFullAnimalBankUpdate + sMinTimeBetweenFullUpdates > currentTime && !m_ForceAnimalBankUpdate BANK_ONLY(&& !g_ForceAnimalBankUpdate)) return; m_ForceAnimalBankUpdate = false; BANK_ONLY(g_ForceAnimalBankUpdate = false;) m_TimeOfLastFullAnimalBankUpdate = currentTime; #if __BANK if(g_TestAnimalBankPriority) { for(int i=0; ikAnimalNone && animalId < ANIMALTYPE_MAX, "Invalid animal id %i passed to LoadAnimalBank", animalId)) return; audAnimalBankSlot& animalSlot = m_AnimalBankSlots[lowestPrioritySlotNumber]; if(animalSlot.WaveSlot->GetReferenceCount() != 0) return; if(!Verifyf(!animalSlot.WaveSlot->GetIsLoading(), "Trying to load animal bank that is already currently loading.")) return; //Set the animal info structs so that the animal getting kicked out takes a slot number of -1 if(m_AnimalBankSlots[lowestPrioritySlotNumber].Owner != kAnimalNone) m_AnimalInfoArray[m_AnimalBankSlots[lowestPrioritySlotNumber].Owner].SlotNumber = -1; char voiceName[64]; char bankName[64]; formatf(voiceName, "%s_NEAR_%.2d", g_AnimalBankNames[animalId], m_NextAnimalBankNumToLoad[animalId]); formatf(bankName, "ANIMALS_NEAR\\%s", voiceName); Displayf("Loading animal bank : %s", bankName); animalSlot.BankdID = audWaveSlot::FindBankId(bankName); if(!animalSlot.WaveSlot->LoadBank(animalSlot.BankdID)) return; m_NextAnimalBankNumToLoad[animalId] = (m_NextAnimalBankNumToLoad[animalId] % m_NumAnimalBanks[animalId]) + 1; const AnimalParams* params = m_AnimalInfoArray[animalId].Data; if(!params) return; animalSlot.ContextInfo.Reset(); u32 voiceHash = atStringHash(voiceName); for(int i=0; inumContexts; i++) { audAnimalContextInfo contextInfo; contextInfo.ContextPHash = atPartialStringHash(params->Context[i].ContextName); contextInfo.NumVars = audSpeechSound::FindNumVariationsForVoiceAndContext(voiceHash, params->Context[i].ContextName); if(contextInfo.NumVars != 0) { naAssertf(params->Context[i].MaxTimeBetween >= params->Context[i].MinTimeBetween, "Animal context has min time greater than its max time. Voicehash %u Context %s", voiceHash, params->Context[i].ContextName); contextInfo.NextTimeCanPlay = 0; contextInfo.MinTimeBetween = params->Context[i].MinTimeBetween; contextInfo.MaxTimeBetween = params->Context[i].MaxTimeBetween; contextInfo.LastVarUsed = 0; contextInfo.ForcesSwap = params->Context[i].ContextBits.BitFields.SwapWhenUsedUp; contextInfo.UsedUp = -1; animalSlot.ContextInfo.PushAndGrow(contextInfo); } } //Fill in info for the audAnimalBankSlot animalSlot.Owner = animalId; animalSlot.IsLoading = true; animalSlot.FailedLastTime = false; animalSlot.VoiceName = voiceHash; } bool audSpeechManager::GetIsCurrentlyLoadingAnimalBank() { for(int i=0; iGetBankLoadingStatus(tennisSlot.BankdID); if(loadState == audWaveSlot::LOADING) { return; } else { if(loadState == audWaveSlot::FAILED) { naWarningf("Tennis audio bank failed to load"); } m_CurrentTennisSlotLoading++; if(m_CurrentTennisSlotLoading >= 2) { return; } else { LoadTennisBanks(); } } } } void audSpeechManager::RequestTennisBanks(CPed* opponent BANK_ONLY(, bool debuggingTennis)) { if(!opponent) { naWarningf("Not loading tennis audio bank. Invalid opponent ped."); return; } #if __BANK if(!debuggingTennis) #endif { scrThreadId scriptThreadId = CTheScripts::GetCurrentGtaScriptThread()->GetThreadId(); naCErrorf(scriptThreadId, "Failed to get thread id"); m_TennisScriptIndex = g_ScriptAudioEntity.GetIndexFromScriptThreadId(scriptThreadId); } if (!naVerifyf(m_TennisScriptIndex>=0 BANK_ONLY(|| debuggingTennis), "No valid scriptIndex: %s; %d", CTheScripts::GetCurrentScriptName(), m_TennisScriptIndex)) { return; } m_CurrentTennisSlotLoading = -1; m_TennisIsUsingAnimalBanks = true; for(int i =0; iIsMale(); m_TennisPlayerIsMichael = false; m_TennisPlayerIsFranklin = false; m_TennisPlayerIsTrevor = false; } void audSpeechManager::UnrequestTennisBanks() { m_CurrentTennisSlotLoading = -1; m_TennisIsUsingAnimalBanks = false; m_ForceAnimalBankUpdate = true; m_TennisOpponent = NULL; } void audSpeechManager::LoadTennisBanks() { if(naVerifyf(m_AnimalBankSlots.GetCount() >= 2, "Fewer than 2 animal slots. Not going to try and load tennis.")) { if(m_CurrentTennisSlotLoading >= 2) { naWarningf("Trying to load tennis bank with invalid m_CurrentTennisSlotLoading."); return; } audAnimalBankSlot& tennisSlot = m_AnimalBankSlots[m_CurrentTennisSlotLoading]; if(tennisSlot.WaveSlot->GetReferenceCount() != 0) return; if(!Verifyf(!tennisSlot.WaveSlot->GetIsLoading(), "Trying to load tennis bank that is already currently loading.")) return; char voiceName[64]; char bankName[64]; //0th slot is for player if(m_CurrentTennisSlotLoading == 0) { if(!FindPlayerPed()) { m_CurrentTennisSlotLoading = 1; naWarningf("Can't find player ped. Won't be loading their tennis bank."); return; } switch(FindPlayerPed()->GetPedType()) { case PEDTYPE_PLAYER_0: //MICHAEL safecpy(voiceName, "TENNIS_VFX_MICHAEL"); safecpy(bankName, "ANIMALS_NEAR\\TENNIS_VFX_MICHAEL"); m_TennisPlayerIsMichael = true; break; case PEDTYPE_PLAYER_1: //FRANKLIN safecpy(voiceName, "TENNIS_VFX_FRANKLIN"); safecpy(bankName, "ANIMALS_NEAR\\TENNIS_VFX_FRANKLIN"); m_TennisPlayerIsFranklin = true; break; case PEDTYPE_PLAYER_2: //TREVOR safecpy(voiceName, "TENNIS_VFX_TREVOR"); safecpy(bankName, "ANIMALS_NEAR\\TENNIS_VFX_TREVOR"); m_TennisPlayerIsTrevor = true; break; default: break; } } else if(m_CurrentTennisSlotLoading == 1) { if(m_TennisOpponentIsMale) { safecpy(voiceName, "TENNIS_VFX_MALE1"); safecpy(bankName, "ANIMALS_NEAR\\TENNIS_VFX_MALE1"); } else { safecpy(voiceName, "TENNIS_VFX_FEMALE1"); safecpy(bankName, "ANIMALS_NEAR\\TENNIS_VFX_FEMALE1"); } } else { return; } tennisSlot.BankdID = audWaveSlot::FindBankId(bankName); if(!tennisSlot.WaveSlot->LoadBank(tennisSlot.BankdID)) return; u32 voiceHash = atStringHash(voiceName); //Fill in info for the audAnimalBankSlot tennisSlot.IsLoading = true; tennisSlot.FailedLastTime = false; tennisSlot.VoiceName = voiceHash; char context[64]; formatf(context, "%s_LITE", voiceName); m_NumTennisContextPHashes[m_CurrentTennisSlotLoading][TENNIS_VFX_LITE] = atPartialStringHash(context); m_NumTennisVariations[m_CurrentTennisSlotLoading][TENNIS_VFX_LITE] = audSpeechSound::FindNumVariationsForVoiceAndContext(voiceHash, atPartialStringHash(context)); m_LastTennisVariations[m_CurrentTennisSlotLoading][TENNIS_VFX_LITE] = 0; formatf(context, "%s_MED", voiceName); m_NumTennisContextPHashes[m_CurrentTennisSlotLoading][TENNIS_VFX_MED] = atPartialStringHash(context); m_NumTennisVariations[m_CurrentTennisSlotLoading][TENNIS_VFX_MED] = audSpeechSound::FindNumVariationsForVoiceAndContext(voiceHash, atPartialStringHash(context)); m_LastTennisVariations[m_CurrentTennisSlotLoading][TENNIS_VFX_MED] = 0; formatf(context, "%s_STRONG", voiceName); m_NumTennisContextPHashes[m_CurrentTennisSlotLoading][TENNIS_VFX_STRONG] = atPartialStringHash(context); m_NumTennisVariations[m_CurrentTennisSlotLoading][TENNIS_VFX_STRONG] = audSpeechSound::FindNumVariationsForVoiceAndContext(voiceHash, atPartialStringHash(context)); m_LastTennisVariations[m_CurrentTennisSlotLoading][TENNIS_VFX_STRONG] = 0; } } audWaveSlot* audSpeechManager::GetTennisVocalizationInfo(CPed* m_Parent, const TennisVocalEffectType& tennisVFXType, u32& contextPhash, u32& voiceHash, u32& variation) { if(!m_Parent || tennisVFXType >= AUD_NUM_TENNIS_VFX_TYPES || tennisVFXType < 0) return NULL; u8 speakerTypeIndex = 0; //0 for player switch(m_Parent->GetPedType()) { case PEDTYPE_PLAYER_0: //MICHAEL if(!m_TennisPlayerIsMichael) return NULL; break; case PEDTYPE_PLAYER_1: //FRANKLIN if(!m_TennisPlayerIsFranklin) return NULL; break; case PEDTYPE_PLAYER_2: //TREVOR if(!m_TennisPlayerIsTrevor) return NULL; break; default: speakerTypeIndex = 1; if(m_Parent != m_TennisOpponent) return NULL; if( (m_Parent->IsMale() && !m_TennisOpponentIsMale) || (!m_Parent->IsMale() && m_TennisOpponentIsMale) ) return NULL; break; } contextPhash = m_NumTennisContextPHashes[speakerTypeIndex][tennisVFXType]; voiceHash = m_AnimalBankSlots[speakerTypeIndex].VoiceName; if(m_NumTennisVariations[speakerTypeIndex][tennisVFXType] > 1) { variation = audEngineUtil::GetRandomNumberInRange(1, m_NumTennisVariations[speakerTypeIndex][tennisVFXType]); if(variation == m_LastTennisVariations[speakerTypeIndex][tennisVFXType]) { variation = (variation % m_NumTennisVariations[speakerTypeIndex][tennisVFXType]) + 1; } } else { variation = m_NumTennisVariations[speakerTypeIndex][tennisVFXType]; } m_LastTennisVariations[speakerTypeIndex][tennisVFXType] = variation; return m_AnimalBankSlots[speakerTypeIndex].WaveSlot; } int CbCompareAnimalStructs(const void* pVoidA, const void* pVoidB) { const audAnimalQSortStruct* pA = (const audAnimalQSortStruct*)pVoidA; const audAnimalQSortStruct* pB = (const audAnimalQSortStruct*)pVoidB; return pA->Priority > pB->Priority ? -1 : (pA->Priority == pB->Priority ? 0 : 1); } void audSpeechManager::SortAnimalList(audAnimalQSortStruct* outList) { CPed* playerPed = FindPlayerPed(); s32 playerAnimalIndex = ANIMALTYPE_MAX; if(playerPed && playerPed->GetSpeechAudioEntity() && playerPed->GetSpeechAudioEntity()->IsAnimal()) { playerAnimalIndex = playerPed->GetSpeechAudioEntity()->GetAnimalType(); } for(int i=0; iMaxDistanceToBankLoad * params->MaxDistanceToBankLoad) ), 0.05f, 1.0f ); m_AnimalInfoArray[i].Priority = Max(4.0f, (f32)m_NumAnimalAppearancesSinceLastUpdate[i]) * distanceScalar * (f32)params->BasePriority; } outList[i].AnimalType = (AnimalType)i; outList[i].Priority = m_AnimalInfoArray[i].Priority; m_NumAnimalAppearancesSinceLastUpdate[i] = 0; m_NearestAnimalAppearancesSinceLastUpdate[i] = -1.0f; } qsort(outList,ANIMALTYPE_MAX,sizeof(audAnimalQSortStruct),CbCompareAnimalStructs); } s32 audSpeechManager::GetVariationForNearAnimalContext(AnimalType animalType, const char* context) { return GetVariationForNearAnimalContext(animalType, atPartialStringHash(context)); } s32 audSpeechManager::GetVariationForNearAnimalContext(AnimalType animalType, u32 contextPHash) { if(m_AnimalInfoArray[animalType].SlotNumber == -1) return -1; audAnimalBankSlot& bankSlot = m_AnimalBankSlots[m_AnimalInfoArray[animalType].SlotNumber]; for(int i=0; i= 0 && bankSlot.ContextInfo[i].NextTimeCanPlay > g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0)) return -1; bankSlot.ContextInfo[i].LastVarUsed++; if(bankSlot.ContextInfo[i].LastVarUsed > bankSlot.ContextInfo[i].NumVars) { bankSlot.ContextInfo[i].LastVarUsed = 1; if(bankSlot.ContextInfo[i].UsedUp == -1) bankSlot.ContextInfo[i].UsedUp = 3; } return bankSlot.ContextInfo[i].LastVarUsed; } } return -1; } void audSpeechManager::NearAnimalContextPlayed(AnimalType animalType, u32 contextPHash) { if(m_AnimalInfoArray[animalType].SlotNumber == -1) return; audAnimalBankSlot& bankSlot = m_AnimalBankSlots[m_AnimalInfoArray[animalType].SlotNumber]; for(int i=0; i distSq) { m_NearestAnimalAppearancesSinceLastUpdate[animalType] = distSq; } } void audSpeechManager::IncrementAnimalsMakingSound(AnimalType animalType) { Assert(animalType < ANIMALTYPE_MAX); m_NumAnimalsMakingSound[animalType]++; } void audSpeechManager::DecrementAnimalsMakingSound(AnimalType animalType) { if(m_NumAnimalsMakingSound[animalType] == 0) { Warningf("Attempting to decrement m_NumAnimalsMakingSound when it's already 0."); return; } Assert(animalType < ANIMALTYPE_MAX); m_NumAnimalsMakingSound[animalType]--; } u8 audSpeechManager::GetNumAnimalsMakingSound(AnimalType animalType) { Assert(animalType < ANIMALTYPE_MAX); return m_NumAnimalsMakingSound[animalType]; } bool audSpeechManager::IsStreamingBusyForContext(u32 UNUSED_PARAM(contextPHash)) { if (g_StreamingSchmeaming) { return false; } // Don't play ambient speech if streaming stuff right in front of us. if (strStreamingEngine::GetIsLoadingPriorityObjects()) { return true; } // What the hell, let's just play everything if streaming's not busy - no reason not to return false; /* // For now, just see how fast the player car's going, and don't allow anything other than DIVE or CRASH_CAR/etc if it's quick. if (!strcmp(context, "DIVE") || !strcmp(context, "CRASH_CAR") || !strcmp(context, "CRASH_BIKE") || !strcmp(context, "CRASH_GENERIC") || !strcmp(context, "CRASH_CAR_DRIVEN")) { return false; } CVehicle* pVehicle = CGameWorld::FindLocalPlayerVehicle(); if (pVehicle && pVehicle->GetMoveSpeed().Mag2() > 40.0f) { return true; } return false; */ } void audSpeechManager::ReserveAmbientSpeechSlotForScript() { if(m_ScriptOwningReservedSlot != 0) return; scrThreadId scriptID = CTheScripts::GetCurrentGtaScriptThread()->GetThreadId(); bool hadToStop; s32 slot = GetAmbientSpeechSlot(NULL, &hadToStop, -1.0f); if(!hadToStop) { Assign(m_ScriptReservedSlotNum, (u32)slot); m_ScriptOwningReservedSlot = scriptID; m_AmbientSpeechSlots[m_ScriptReservedSlotNum].PlayState = AUD_SPEECH_SLOT_VACANT; g_SpeechManager.PopulateAmbientSpeechSlotWithPlayingSpeechInfo(slot, 0, #if __BANK "SCRIPT_RESERVED", #endif 0); } else { FreeAmbientSpeechSlot(slot, true); } } void audSpeechManager::ReleaseAmbientSpeechSlotForScript() { scrThreadId scriptID = CTheScripts::GetCurrentGtaScriptThread()->GetThreadId(); if(m_ScriptOwningReservedSlot == 0) { #if !__FINAL GtaThread* pThread = static_cast(scrThread::GetThread(scriptID)); Warningf("Script with id %u and name %s attempting to release reserved speech slot when no speech slot has been reserved.", scriptID, pThread ? pThread->GetScriptName(): "unknown"); #endif return; } if(scriptID != m_ScriptOwningReservedSlot) { #if !__FINAL GtaThread* pThread1 = static_cast(scrThread::GetThread(scriptID)); GtaThread* pThread2 = static_cast(scrThread::GetThread(m_ScriptOwningReservedSlot)); Warningf("Script with id %u and name %s attempting to release reserved speech slot when it is actually reserved by script with id %u and name %s.", scriptID, pThread1 ? pThread1->GetScriptName(): "unknown", m_ScriptOwningReservedSlot, pThread2 ? pThread2->GetScriptName(): "unknown"); #endif return; } FreeAmbientSpeechSlot(m_ScriptReservedSlotNum, true); m_ScriptReservedSlotNum = 0; m_ScriptOwningReservedSlot = THREAD_INVALID; } #if GTA_REPLAY void audSpeechManager::FreeAllAmbientSpeechSlots() { if(m_ChurnSpeechSound) { m_ChurnSpeechSound->StopAndForget(); } for (int i=0; iGetReferenceCount() == 0) { // Slot's finished m_AmbientSpeechSlots[i].PlayState = AUD_SPEECH_SLOT_VACANT; } } } #endif s32 audSpeechManager::GetAmbientSpeechSlot(audSpeechAudioEntity* requestingEntity, bool* hadToStopSpeech, f32 priority, scrThreadId scriptThreadId #if AUD_DEFERRED_SPEECH_DEBUG_PRINT , const char* context #endif , bool isHeli) { if(hadToStopSpeech) *hadToStopSpeech = false; if(m_ScriptOwningReservedSlot != 0 && scriptThreadId == m_ScriptOwningReservedSlot && m_AmbientSpeechSlots[m_ScriptReservedSlotNum].PlayState == AUD_SPEECH_SLOT_VACANT) { m_AmbientSpeechSlots[m_ScriptReservedSlotNum].PlayState = AUD_SPEECH_SLOT_ALLOCATED; m_AmbientSpeechSlots[m_ScriptReservedSlotNum].SpeechAudioEntity = requestingEntity; return m_ScriptReservedSlotNum; } f32 requestingPriority = GetRealAmbientSpeechPriority(requestingEntity, priority); f32 lowestAllocatedPriority = -1.0f; s32 lowestPrioritySlot = -1; #if AUD_DEFERRED_SPEECH_DEBUG_PRINT atRangeArray debugPriority; atRangeArray debugRealPriority; atRangeArray debugDist2; debugPriority[g_MaxAmbientSpeechSlots] = priority; debugRealPriority[g_MaxAmbientSpeechSlots] = requestingPriority; debugDist2[g_MaxAmbientSpeechSlots] = 0.0f; if(requestingEntity) { CPed* parent = requestingEntity->GetParent(); if(parent) debugDist2[g_MaxAmbientSpeechSlots] = (VEC3V_TO_VECTOR3(parent->GetMatrix().d()) - VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition())).Mag2(); } #endif // See if we've got a vacant slot. Don't allocate STOPPING ones for just now, for simplicity. And don't allow forcing out of less important ones. for (s32 index=0; indexGetParent(); if(parent) debugDist2[i] = (VEC3V_TO_VECTOR3(parent->GetMatrix().d() - g_AudioEngine.GetEnvironment().GetVolumeListenerPosition())).Mag2(); } #endif } //Some things, such as animal sounds or requests from script to reserve a slot, should never kick stuff out of slots. if(priority < 0.0f) return -1; // We don't have any free. See if the lowest priority allocated slot is worth kicking out. // For now, don't kick out already playing speech for something that doesn't have a valid requestingEntity // Also, don't allow this to happen if we don't have a valid hadToStopSpeech pointer through which to notify our speechEntity that it // should go into deferred speech mode. // The check for a valid SpeechAudioEntity shouldn't be required, as this is checked before we mark the slot as lowest priority. if(hadToStopSpeech && (requestingEntity || isHeli) && lowestPrioritySlot>-1 && requestingPriority > lowestAllocatedPriority && m_AmbientSpeechSlots[lowestPrioritySlot].SpeechAudioEntity) { m_AmbientSpeechSlots[lowestPrioritySlot].SpeechAudioEntity->FreeSlot(); *hadToStopSpeech = true; #if AUD_DEFERRED_SPEECH_DEBUG_PRINT audWarningf("[DEFERRED SPEECH] Incoming: Context: %s, Prio: %.02f, Real Prio: %.02f, Dist2: %.02f", context, debugPriority[g_MaxAmbientSpeechSlots], debugRealPriority[g_MaxAmbientSpeechSlots], debugDist2[g_MaxAmbientSpeechSlots]); for (u32 i = 0; i < g_MaxAmbientSpeechSlots; i++) { char cxt[64]; cxt[0] = '\0'; if (m_AmbientSpeechSlots[i].SpeechAudioEntity) { m_AmbientSpeechSlots[i].SpeechAudioEntity->GetCurrentlyPlayingWaveName(cxt); } audWarningf("[DEFERRED SPEECH] Slot %d: Context: %s, Prio: %.02f, Real Prio: %.02f, Dist2: %.02f", i, cxt, debugPriority[i], debugRealPriority[i], debugDist2[i]); } audWarningf("[DEFERRED SPEECH] Slot Given: %i, from entity %p to entity %p", lowestPrioritySlot, m_AmbientSpeechSlots[lowestPrioritySlot].SpeechAudioEntity, requestingEntity); #endif return lowestPrioritySlot; } return -1; } f32 audSpeechManager::GetRealAmbientSpeechPriority(audSpeechAudioEntity* requestingEntity, f32 priority) { if(requestingEntity) { CPed* parent = requestingEntity->GetParent(); if(parent) { f32 dist2 = (VEC3V_TO_VECTOR3(parent->GetMatrix().d() - g_AudioEngine.GetEnvironment().GetVolumeListenerPosition())).Mag2(); priority = g_DistanceSqToPriorityScalarCurve.CalculateValue(dist2) * priority; } } return priority; } void audSpeechManager::MarkSlotAsAllocated(int slotIndex, audSpeechAudioEntity* requestingEntity, f32 priority) { naAssertf(m_AmbientSpeechSlots[slotIndex].PlayState==AUD_SPEECH_SLOT_VACANT || (m_AmbientSpeechSlots[slotIndex].PlayState==AUD_SPEECH_SLOT_ALLOCATED && m_AmbientSpeechSlots[slotIndex].SpeechAudioEntity == requestingEntity), "Trying to mark speech slot as allocated when it isn't vacant yet. THIS IS VERY BAD!!!"); m_AmbientSpeechSlots[slotIndex].PlayState = AUD_SPEECH_SLOT_ALLOCATED; m_AmbientSpeechSlots[slotIndex].SpeechAudioEntity = requestingEntity; m_AmbientSpeechSlots[slotIndex].Priority = priority; } void audSpeechManager::FreeAmbientSpeechSlot(s32 slot, bool BANK_ONLY(okForEntityToBeNull)) { // Remove the audio entity reference, and mark the slot as stopping or vacant, depending on the wave slot's state. // (smoother to just let the Update() do the STOPPING->VACANT change, but it'd be good to synchronously free the slot - bound to need that at some point) naAssertf(slot>=0 && slotGetReferenceCount() == 0) { m_AmbientSpeechSlots[slot].PlayState = AUD_SPEECH_SLOT_VACANT; } else { m_AmbientSpeechSlots[slot].PlayState = AUD_SPEECH_SLOT_STOPPING; //RK - TODO: Deal with Walla } } void audSpeechManager::PopulateAmbientSpeechSlotWithPlayingSpeechInfo(s32 slot, u32 voiceName, #if __BANK const char* context, #endif s32 variation) { naAssertf(slot>=0 && slot=0 && slotIndex= 0 && slotIndex < g_MaxAmbientSpeechSlots, "Slot index passed into CanSlotBeReused is out of bounds"); return m_AmbientSpeechSlots[slotIndex].PlayState == AUD_SPEECH_SLOT_ALLOCATED && m_AmbientSpeechSlots[slotIndex].SpeechAudioEntity == entity && m_AmbientSpeechSlots[slotIndex].WaveSlot->GetReferenceCount() == 0; } bool audSpeechManager::IsScriptSpeechSlotVacant(s32 slotIndex) { naAssertf(slotIndex>=0 && slotIndexGetReferenceCount(); } return 0; } s32 audSpeechManager::GetScriptedSpeechSlot(audSpeechAudioEntity* requestingEntity) { // See if we've got a vacant slot. Don't allocate STOPPING ones for just now, for simplicity. // We may do this differently - is it ever acceptable to not give a script a slot when it thinks it needs one? for (s32 i=0; iVACANT change, but it'd be good to synchronously free the slot - bound to need that at some point) naAssertf(slot>=0 && slotGetReferenceCount() == 0) { m_ScriptedSpeechSlots[slot].PlayState = AUD_SPEECH_SLOT_VACANT; } else { m_ScriptedSpeechSlots[slot].PlayState = AUD_SPEECH_SLOT_STOPPING; } } s32 audSpeechManager::WhenWasVariationLastPlayed(u32 voiceHash, u32 contextHash, u32 variationNum, u32& timeInMs) { // Search through the history, and see if the voice and variation combo are in it. // We're currently pointing at the next slot to write into, m_NextPhraseMemoryWriteIndex // Indices are correct but not obvious - we go from the previous index backwards (+g_PhraseMemorySize is there because I don't trust -ve modding) for (u32 i=1; i<=g_PhraseMemorySize; i++) { u32 phraseMemoryIndex = (m_NextPhraseMemoryWriteIndex - i + g_PhraseMemorySize) % g_PhraseMemorySize; if (m_PhraseMemory[phraseMemoryIndex][0] == voiceHash && m_PhraseMemory[phraseMemoryIndex][1] == contextHash && m_PhraseMemory[phraseMemoryIndex][2] == variationNum) { // This combo is in the history, return how long ago (in num phrases, not time) it was played. timeInMs = m_PhraseMemory[phraseMemoryIndex][3]; return (i-1); } } // We never found it, it's not in the history timeInMs = 0; return -1; } void audSpeechManager::BuildMemoryListForVoiceAndContext(u32 voiceHash, u32 contextHash, u32& memorySize, u32 aPhraseMemory[g_PhraseMemorySize][4]) { // Search through the history, and see if the voice and variation combo are in it. // We're currently pointing at the next slot to write into, m_NextPhraseMemoryWriteIndex // Indices are correct but not obvious - we go from the previous index backwards (+g_PhraseMemorySize is there because I don't trust -ve modding) for (u32 i=1; i<=g_PhraseMemorySize; i++) { u32 phraseMemoryIndex = (m_NextPhraseMemoryWriteIndex - i + g_PhraseMemorySize) % g_PhraseMemorySize; if (m_PhraseMemory[phraseMemoryIndex][0] == voiceHash && m_PhraseMemory[phraseMemoryIndex][1] == contextHash ) { aPhraseMemory[memorySize][0] = m_PhraseMemory[phraseMemoryIndex][0]; aPhraseMemory[memorySize][1] = m_PhraseMemory[phraseMemoryIndex][1]; aPhraseMemory[memorySize][2] = m_PhraseMemory[phraseMemoryIndex][2]; aPhraseMemory[memorySize][3] = m_PhraseMemory[phraseMemoryIndex][3]; ++memorySize; } } } void audSpeechManager::AddVariationToHistory(u32 voiceHash, u32 contextHash, u32 variationNum, u32 timeInMs) { m_PhraseMemory[m_NextPhraseMemoryWriteIndex][0] = voiceHash; m_PhraseMemory[m_NextPhraseMemoryWriteIndex][1] = contextHash; m_PhraseMemory[m_NextPhraseMemoryWriteIndex][2] = variationNum; m_PhraseMemory[m_NextPhraseMemoryWriteIndex][3] = timeInMs; m_NextPhraseMemoryWriteIndex = (m_NextPhraseMemoryWriteIndex + 1) % g_PhraseMemorySize; if (m_NextPhraseMemoryWriteIndex==0) { naWarningf("Burnt through all our speech memory. Time: %u", fwTimer::GetTimeInMilliseconds()); } } void audSpeechManager::GetTimeWhenContextCanNextPlay(SpeechContext *speechContext, u32* timeCanNextPlay, bool* isControlledByContext) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid SpeechContext passed into GetTimeWhenContextCanNextPlay"); naAssertf(timeCanNextPlay || !g_bSpeechAsserts, "Invalid u32* passed into GetTimeWhenContextCanNextPlay"); naAssertf(isControlledByContext || !g_bSpeechAsserts, "Invalid bool* parameter passed into GetTimeWhenContextCanNextPlay"); if (speechContext && speechContext->RepeatTime > -1) { *timeCanNextPlay = speechContext->TimeCanNextPlay; *isControlledByContext = true; } else { *timeCanNextPlay = 0; *isControlledByContext = false; } } void audSpeechManager::GetTimeWhenTriggeredPrimaryContextCanNextPlay(TriggeredSpeechContext *speechContext, u32* timeCanNextPlay, bool* isControlledByContext) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid SpeechContext passed into GetTimeWhenContextCanNextPlay"); naAssertf(timeCanNextPlay || !g_bSpeechAsserts, "Invalid u32* passed into GetTimeWhenContextCanNextPlay"); naAssertf(isControlledByContext || !g_bSpeechAsserts, "Invalid bool* parameter passed into GetTimeWhenContextCanNextPlay"); if (speechContext && speechContext->PrimaryRepeatTime > -1) { *timeCanNextPlay = speechContext->TimeCanNextPlayPrimary; *isControlledByContext = true; } else { *timeCanNextPlay = 0; *isControlledByContext = false; } } void audSpeechManager::GetTimeWhenTriggeredBackupContextCanNextPlay(TriggeredSpeechContext *speechContext, u32* timeCanNextPlay, bool* isControlledByContext) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid SpeechContext passed into GetTimeWhenContextCanNextPlay"); naAssertf(timeCanNextPlay || !g_bSpeechAsserts, "Invalid u32* passed into GetTimeWhenContextCanNextPlay"); naAssertf(isControlledByContext || !g_bSpeechAsserts, "Invalid bool* parameter passed into GetTimeWhenContextCanNextPlay"); if (speechContext && speechContext->BackupRepeatTime > -1) { *timeCanNextPlay = speechContext->TimeCanNextPlayBackup; *isControlledByContext = true; } else { *timeCanNextPlay = 0; *isControlledByContext = false; } } u32 audSpeechManager::GetRepeatTimeContextCanPlayOnVoice(SpeechContext *speechContext) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid SpeechContext passed into GetRepeatTimeContextCanPlayOnVoice"); if (speechContext && speechContext->RepeatTimeOnSameVoice > -1) { f32 repeatTime = (f32)speechContext->RepeatTimeOnSameVoice; u32 randomRepeatTime = (u32)(repeatTime * audEngineUtil::GetRandomNumberInRange(0.8f, 1.2f)); if (randomRepeatTime>50000) { randomRepeatTime = 50000; } return randomRepeatTime; } return audEngineUtil::GetRandomNumberInRange(6500, 10000); } u32 audSpeechManager::GetRepeatTimePrimaryTriggeredContextCanPlayOnVoice(TriggeredSpeechContext *speechContext) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid SpeechContext passed into GetRepeatTimeContextCanPlayOnVoice"); if (speechContext && speechContext->PrimaryRepeatTimeOnSameVoice > -1) { f32 repeatTime = (f32)speechContext->PrimaryRepeatTimeOnSameVoice; u32 randomRepeatTime = (u32)(repeatTime * audEngineUtil::GetRandomNumberInRange(0.8f, 1.2f)); if (randomRepeatTime>50000) { randomRepeatTime = 50000; } return randomRepeatTime; } return audEngineUtil::GetRandomNumberInRange(6500, 10000); } u32 audSpeechManager::GetRepeatTimeBackupTriggeredContextCanPlayOnVoice(TriggeredSpeechContext *speechContext) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid SpeechContext passed into GetRepeatTimeContextCanPlayOnVoice"); if (speechContext && speechContext->BackupRepeatTimeOnSameVoice > -1) { f32 repeatTime = (f32)speechContext->BackupRepeatTimeOnSameVoice; u32 randomRepeatTime = (u32)(repeatTime * audEngineUtil::GetRandomNumberInRange(0.8f, 1.2f)); if (randomRepeatTime>50000) { randomRepeatTime = 50000; } return randomRepeatTime; } return audEngineUtil::GetRandomNumberInRange(6500, 10000); } u32 audSpeechManager::GetTimeLastPlayedForContext(u32 contextPHash) { u32 ret = 0; for (u32 i=0; iRepeatTime > -1) { // To avoid things getting all in-sync with each other - our repeat time is randomised between 100% and 125% s32 repeatTime = audEngineUtil::GetRandomNumberInRange(speechContext->RepeatTime, (s32)(((f32)speechContext->RepeatTime)*1.25f)); speechContext->TimeCanNextPlay = timeInMs + repeatTime; } } void audSpeechManager::UpdateTimeTriggeredContextWasLastUsed(TriggeredSpeechContext* speechContext, u32 timeInMs) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid TriggeredSpeechContext passed into UpdateTimeTriggeredContextWasLastUsed"); if (speechContext && speechContext->TriggeredContextRepeatTime > -1) { // To avoid things getting all in-sync with each other - our repeat time is randomised between 100% and 125% s32 repeatTime = audEngineUtil::GetRandomNumberInRange(speechContext->TriggeredContextRepeatTime, (s32)(((f32)speechContext->TriggeredContextRepeatTime)*1.25f)); speechContext->TimeCanNextUseTriggeredContext= timeInMs + repeatTime; } } void audSpeechManager::UpdateTimePrimaryTriggeredContextWasLastUsed(TriggeredSpeechContext* speechContext, u32 timeInMs) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid TriggeredSpeechContext passed into UpdateTimePrimaryTriggeredContextWasLastUsed"); if (speechContext && speechContext->PrimaryRepeatTime > -1) { // To avoid things getting all in-sync with each other - our repeat time is randomised between 100% and 125% s32 repeatTime = audEngineUtil::GetRandomNumberInRange(speechContext->PrimaryRepeatTime, (s32)(((f32)speechContext->PrimaryRepeatTime)*1.25f)); speechContext->TimeCanNextPlayPrimary = timeInMs + repeatTime; } } void audSpeechManager::UpdateTimeBackupTriggeredContextWasLastUsed(TriggeredSpeechContext* speechContext, u32 timeInMs) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid TriggeredSpeechContext passed into UpdateTimeBackupTriggeredContextWasLastUsed"); if (speechContext && speechContext->BackupRepeatTime > -1) { // To avoid things getting all in-sync with each other - our repeat time is randomised between 100% and 125% s32 repeatTime = audEngineUtil::GetRandomNumberInRange(speechContext->BackupRepeatTime, (s32)(((f32)speechContext->BackupRepeatTime)*1.25f)); speechContext->TimeCanNextPlayBackup = timeInMs + repeatTime; } } u32 audSpeechManager::GetVoiceForPhoneConversation(u32 pedVoiceGroup) { PedVoiceGroups* pedVoiceGroupObject = audNorthAudioEngine::GetObject(pedVoiceGroup); if (pedVoiceGroupObject) { //0 is the voiceHash, 1 is the num of refs atMultiRangeArray NumRefsPerVoiceInPhoneHistory; for(int i=0; inumPrimaryVoices; ++i) { if(audSpeechSound::FindNumVariationsForVoiceAndContext(pedVoiceGroupObject->PrimaryVoice[i].VoiceName,ATPARTIALSTRINGHASH("PHONE_CONV1_INTRO", 0x8AADB637)) != 0) { u32 numRef = 0; for(int j=0; jPrimaryVoice[i].VoiceName == m_PhoneConversationMemory[j][0]) numRef++; } //if this voice doesn't appear, return it if(numRef == 0) { return pedVoiceGroupObject->PrimaryVoice[i].VoiceName; } //otherwise, store it NumRefsPerVoiceInPhoneHistory[numPedsFound][0] = pedVoiceGroupObject->PrimaryVoice[i].VoiceName; NumRefsPerVoiceInPhoneHistory[numPedsFound++][1] = numRef; } } u8* p = (u8*)&pedVoiceGroupObject->PrimaryVoice[pedVoiceGroupObject->numPrimaryVoices]; u8 numMiniVoices = *p; p++; tVoice* miniVoices = (tVoice*)p; for(int i=0; i varFound; varFound.Reset(); varFound.Resize(numVariations); for(int i=0; i(numVariations); for(int i = 0; i< g_PhoneConvMemorySize; ++i) { if(m_PhoneConversationMemory[i][0] == 0) { //exit early...the rest of the history is blank break; } if(voiceHash == m_PhoneConversationMemory[i][0]) { numVariationsNotInHistory--; varFound[m_PhoneConversationMemory[i][1]-1] = true; //minus 1, as conversation variations aren't 0 indexed if(variationPlayedLongestAgo==0 || timeLongestVariationAgoPlayed > m_PhoneConversationMemory[i][2]) { variationPlayedLongestAgo = static_cast(m_PhoneConversationMemory[i][1]); timeLongestVariationAgoPlayed = m_PhoneConversationMemory[i][2]; } } } //If the history holds no conversation for this voice, return a randome one if(numVariationsNotInHistory == numVariations) return static_cast((audEngineUtil::GetRandomInteger() % numVariations) + 1); //If there are variations not in the history, pick a random one and return that if(numVariationsNotInHistory != 0) { u8 startPos = static_cast(audEngineUtil::GetRandomInteger() % numVariations); for(u8 i=0; i(audNorthAudioEngine::GetMetadataManager().GetObjectMetadataPtr(contextHash)); naAssertf(obj || !g_bSpeechAsserts, "No SpeechContext object for hash %u", contextHash); if(obj && (obj->ClassId==SpeechContext::TYPE_ID || obj->ClassId==SpeechContextVirtual::TYPE_ID) ) { SpeechContext* speechContext = static_cast(obj); if(returningDefault) *returningDefault = false; return ResolveVirtualSpeechContext(speechContext, speaker, replyingPed); } else if(obj && obj->ClassId==TriggeredSpeechContext::TYPE_ID) { return NULL; } else { if(returningDefault) *returningDefault = true; return g_DefaultSpeechContext; } } SpeechContext* audSpeechManager::ResolveVirtualSpeechContext(const SpeechContext* speechContext, CPed* speaker, CPed* replyingPed) { if(!speechContext) return NULL; if(!Verifyf(speechContext->ClassId == SpeechContextVirtual::TYPE_ID || speechContext->ClassId == SpeechContext::TYPE_ID, "Argument passed into ResolveVirtualSpeechContext isn't a speech context. TYPE_ID %u Name %s", speechContext->ClassId, audNorthAudioEngine::GetMetadataManager().GetObjectName(speechContext->NameTableOffset, 0))) return NULL; if(speechContext->ClassId == SpeechContext::TYPE_ID) return const_cast(speechContext); SpeechContextVirtual* speechContextVirt = audNorthAudioEngine::GetObject(atFinalizeHash(speechContext->ContextNamePHash));//reinterpret_cast(speechContext); if(!Verifyf(speechContextVirt, "Attempting to resolve a speech context with no context name partial hash.")) return NULL; u8* resolvingFunction = (u8*)(&(speechContext->FakeGesture[speechContext->numFakeGestures])); if(!resolvingFunction) return NULL; u8* numSpeechContexts = resolvingFunction; numSpeechContexts++; u8* justBeforeSpeechContexts = numSpeechContexts; justBeforeSpeechContexts++; u32* speechContexts = (u32*)justBeforeSpeechContexts; switch(*resolvingFunction) { case SRF_TIME_OF_DAY: return ResolveVirtualSpeechContext(audVirtCxtResolves::ResolveTimeOfDay(*numSpeechContexts, speechContexts), speaker); case SRF_PED_TOUGHNESS: return ResolveVirtualSpeechContext(audVirtCxtResolves::ResolveSpeakerToughness(*numSpeechContexts, speechContexts, speaker), speaker); case SRF_TARGET_GENDER: return ResolveVirtualSpeechContext(audVirtCxtResolves::ResolveTargetGender(*numSpeechContexts, speechContexts, speaker, replyingPed), speaker, replyingPed); case SRF_DEFAULT: default: if(speechContextVirt->numSpeechContexts > 0) return ResolveVirtualSpeechContext(static_cast(audNorthAudioEngine::GetMetadataManager().GetObjectMetadataPtr(speechContextVirt->SpeechContexts[0].SpeechContext)), speaker); return NULL; } } bool audSpeechManager::DoesContextHaveANoGenderVersion(SpeechContext *speechContext) { naAssertf(speechContext || !g_bSpeechAsserts, "Invalid SpeechContext passed into DoesContextHaveADrunkVersion"); if (speechContext && speechContext->GenderNonSpecificVersion != 0) { return true; } return false; } audWaveSlot* audSpeechManager::GetAmbientWaveSlotFromId(s32 slotId) { naAssertf(slotId>=0 && slotId=0 && slotId(pedVoiceGroup); if (pedVoiceGroupObject && pedVoiceGroupObject->numPrimaryVoices>0) { return pedVoiceGroupObject->PrimaryVoice[0].VoiceName; } return 0; } u32 audSpeechManager::GetGangVoice(u32 pedVoiceGroup) { u8 numGangVoices = 0; tVoice* gangVoices = GetGangVoices(pedVoiceGroup, numGangVoices); if (numGangVoices>0) { return gangVoices[0].VoiceName; } return 0; } u32 audSpeechManager::GetBestPrimaryVoice(PedVoiceGroups* pedVoiceGroupObject, s32& minReferences, u32 contextPartialHash) { if (pedVoiceGroupObject && pedVoiceGroupObject->numPrimaryVoices>0) { // Go through all the primary voices and return the one with fewest references // Don't start at the beginning - it meant when the game cleared everyone out, you'd always hear the same // first voice immediately - specifically a problem with M_M_TAXIDRIVER, with 11 different voices. s32 bestVoiceIndex = -1; s32 fewestReferences = -1; s32 lowestRunningTab = -1; s32 startingIndex = audEngineUtil::GetRandomNumberInRange(0, pedVoiceGroupObject->numPrimaryVoices-1); for (s32 i=0; inumPrimaryVoices; i++) { s32 currentIndex = (startingIndex+i) % pedVoiceGroupObject->numPrimaryVoices; if (fewestReferences==-1 || (s32)pedVoiceGroupObject->PrimaryVoice[currentIndex].ReferenceCount < fewestReferences || ( (s32)pedVoiceGroupObject->PrimaryVoice[currentIndex].ReferenceCount == fewestReferences && pedVoiceGroupObject->PrimaryVoice[currentIndex].RunningTab < lowestRunningTab) ) { if (audSpeechSound::FindNumVariationsForVoiceAndContext(pedVoiceGroupObject->PrimaryVoice[currentIndex].VoiceName, contextPartialHash) != 0 || contextPartialHash == 0) { bestVoiceIndex = currentIndex; fewestReferences = pedVoiceGroupObject->PrimaryVoice[currentIndex].ReferenceCount; lowestRunningTab = pedVoiceGroupObject->PrimaryVoice[currentIndex].RunningTab; if(fewestReferences == 0 && lowestRunningTab == 0 && pedVoiceGroupObject->PrimaryVoice[bestVoiceIndex].VoiceName != 0) { minReferences = fewestReferences; return pedVoiceGroupObject->PrimaryVoice[bestVoiceIndex].VoiceName; } } } } if (bestVoiceIndex>-1) { minReferences = (s32)pedVoiceGroupObject->PrimaryVoice[bestVoiceIndex].ReferenceCount; return pedVoiceGroupObject->PrimaryVoice[bestVoiceIndex].VoiceName; } } minReferences = -1; return 0; } u32 audSpeechManager::GetBestMiniVoice(PedVoiceGroups* pedVoiceGroupObject, s32& minReferences, u32 contextPartialHash) { // Go through all the mini voices and return the one with fewest references - we allow driving ones to be returned, if fewer refs. // We need to skip over the variable length primary voice array. if (pedVoiceGroupObject) { u8* p = (u8*)&pedVoiceGroupObject->PrimaryVoice[pedVoiceGroupObject->numPrimaryVoices]; u8 numMiniVoices = *p; p++; tVoice* miniVoices = (tVoice*)p; // Go through all the mini voices and return the one with fewest references - checking it's a non-driving voice s32 bestVoiceIndex = -1; s32 fewestReferences = -1; s32 lowestRunningTab = -1; if (numMiniVoices>0) { s32 startingIndex = audEngineUtil::GetRandomNumberInRange(0, numMiniVoices-1); for (s32 i=0; i-1) { minReferences = (s32)miniVoices[bestVoiceIndex].ReferenceCount; return miniVoices[bestVoiceIndex].VoiceName; } } minReferences = -1; return 0; } u32 audSpeechManager::GetBestGangVoice(PedVoiceGroups* pedVoiceGroupObject, s32& minReferences, u32 contextPartialHash) { // Go through all the gang voices and return the one with fewest references. // We need to skip over the variable length primary voice array, and the variable length mini voice array. u8 numGangVoices = 0; tVoice* gangVoices = GetGangVoices(pedVoiceGroupObject, numGangVoices); if (gangVoices && numGangVoices>0) { // Go through all the gang voices and return the one with fewest references s32 bestVoiceIndex = -1; s32 fewestReferences = -1; s32 lowestRunningTab = -1; s32 startingIndex = audEngineUtil::GetRandomNumberInRange(0, numGangVoices-1); for (s32 i=0; i-1) { minReferences = (s32)gangVoices[bestVoiceIndex].ReferenceCount; return gangVoices[bestVoiceIndex].VoiceName; } } minReferences = -1; return 0; } u32 audSpeechManager::GetBestVoice(u32 pedVoiceGroup, u32 contextPHash, CPed* speaker, bool allowBackupPVG, s32* overallMinReferenceArg) { PedVoiceGroups* pedVoiceGroupObject = audNorthAudioEngine::GetObject(pedVoiceGroup); if(!pedVoiceGroupObject) { Warningf("Invalid PVG hash passed to GetBestVoice: %u. Setting to MALE_BRAVE", pedVoiceGroup); if(speaker && speaker->GetSpeechAudioEntity()) { switch(speaker->GetSpeechAudioEntity()->GetPedToughnessWithoutSetting()) { case AUD_TOUGHNESS_MALE_NORMAL: case AUD_TOUGHNESS_MALE_COWARDLY: case AUD_TOUGHNESS_MALE_BUM: pedVoiceGroupObject = audNorthAudioEngine::GetObject(ATSTRINGHASH("MALE_COWARDLY_PVG", 0x1F6CDCC4)); break; case AUD_TOUGHNESS_MALE_BRAVE: case AUD_TOUGHNESS_MALE_GANG: pedVoiceGroupObject = audNorthAudioEngine::GetObject(ATSTRINGHASH("MALE_BRAVE_PVG", 0x0087d1fc0)); break; case AUD_TOUGHNESS_COP: pedVoiceGroupObject = audNorthAudioEngine::GetObject(ATSTRINGHASH("COP_PVG", 0xA2468C11)); break; case AUD_TOUGHNESS_FEMALE: case AUD_TOUGHNESS_FEMALE_BUM: pedVoiceGroupObject = audNorthAudioEngine::GetObject(ATSTRINGHASH("FEMALE_PVG", 0x84FE566E)); break; default: if(speaker->IsMale()) pedVoiceGroupObject = audNorthAudioEngine::GetObject(ATSTRINGHASH("MALE_BRAVE_PVG", 0x0087d1fc0)); else pedVoiceGroupObject = audNorthAudioEngine::GetObject(ATSTRINGHASH("FEMALE_PVG", 0x84FE566E)); break; } } if(!pedVoiceGroupObject) return 0; } //Displayf("%s", audNorthAudioEngine::GetMetadataManager().GetObjectNameFromNameTableOffset(pedVoiceGroupObject->NameTableOffset)); u32 bestVoice[3]; s32 minReferences[3]; for (s32 i=0; i<3; i++) { bestVoice[i] = 0; minReferences[i] = -1; } if(audSpeechAudioEntity::IsMultipartContext(contextPHash)) { contextPHash = atPartialStringHash("_01", contextPHash); } bestVoice[0] = GetBestMiniVoice(pedVoiceGroupObject, minReferences[0], contextPHash); if(minReferences[0] == 0 && bestVoice[0] != 0) return bestVoice[0]; bestVoice[1] = GetBestGangVoice(pedVoiceGroupObject, minReferences[1], contextPHash); if(minReferences[1] == 0 && bestVoice[1] != 0) return bestVoice[1]; bestVoice[2] = GetBestPrimaryVoice(pedVoiceGroupObject, minReferences[2], contextPHash); if(minReferences[2] == 0 && bestVoice[2] != 0) return bestVoice[2]; // Now see which has the least references, and go with that one u32 overallBestVoice = 0; s32 overallMinReference = -1; for (s32 i=0; i<3; i++) { if (minReferences[i] > -1 && (minReferences[i] < overallMinReference || overallMinReference == -1)) { overallBestVoice = bestVoice[i]; overallMinReference = minReferences[i]; } } AudBaseObject* obj =(rage::AudBaseObject*)audNorthAudioEngine::GetMetadataManager().GetObjectMetadataPtr(atFinalizeHash(contextPHash)); if(!obj) { //could be a situation where we've been handed a full hash instead of a partial, so let's look using a hash of 0 bestVoice[0] = GetBestMiniVoice(pedVoiceGroupObject, minReferences[0], 0); if(minReferences[0] == 0 && bestVoice[0] != 0) return bestVoice[0]; bestVoice[1] = GetBestGangVoice(pedVoiceGroupObject, minReferences[1], 0); if(minReferences[1] == 0 && bestVoice[1] != 0) return bestVoice[1]; bestVoice[2] = GetBestPrimaryVoice(pedVoiceGroupObject, minReferences[2], 0); if(minReferences[2] == 0 && bestVoice[2] != 0) return bestVoice[2]; // Now see which has the least references, and go with that one overallBestVoice = 0; overallMinReference = -1; for (s32 i=0; i<3; i++) { if (minReferences[i] > -1 && (minReferences[i] < overallMinReference || overallMinReference == -1)) { overallBestVoice = bestVoice[i]; overallMinReference = minReferences[i]; } } } else if(overallBestVoice == 0 || overallMinReference>0) { TriggeredSpeechContext* triggeredSpeechContext = (obj && obj->ClassId == TriggeredSpeechContext::TYPE_ID) ? (TriggeredSpeechContext*)obj : NULL; //see if the speech context has any backup contexts we can use if(triggeredSpeechContext && triggeredSpeechContext->numBackupSpeechContexts > 0) { for(int i=0; i< triggeredSpeechContext->numBackupSpeechContexts; i++) { if(triggeredSpeechContext->BackupSpeechContext[i].SpeechContext == 0) continue; SpeechContext* backupObj =(rage::SpeechContext*)audNorthAudioEngine::GetMetadataManager().GetObjectMetadataPtr(triggeredSpeechContext->BackupSpeechContext[i].SpeechContext); SpeechContext* backupContextObj = ResolveVirtualSpeechContext(backupObj, speaker); if(backupContextObj) { contextPHash = backupContextObj->ContextNamePHash; //Choose full voice before mini, if we're looking to backup context bestVoice[0] = GetBestPrimaryVoice(pedVoiceGroupObject, minReferences[0], contextPHash); if(minReferences[0] == 0 && bestVoice[0] != 0) return bestVoice[0]; bestVoice[1] = GetBestMiniVoice(pedVoiceGroupObject, minReferences[1], contextPHash); if(minReferences[1] == 0 && bestVoice[1] != 0) return bestVoice[1]; bestVoice[2] = GetBestGangVoice(pedVoiceGroupObject, minReferences[2], contextPHash); if(minReferences[2] == 0 && bestVoice[2] != 0) return bestVoice[2]; for (s32 i=0; i<3; i++) { if (minReferences[i] > -1 && (minReferences[i] < overallMinReference || overallMinReference == -1)) { overallBestVoice = bestVoice[i]; overallMinReference = minReferences[i]; } } } if(!(overallBestVoice == 0 || overallMinReference>0)) break; } } } if(overallMinReferenceArg) *overallMinReferenceArg = overallMinReference; u32 overallBestBackupVoice = 0; s32 overallMinBackupReference = -1; if( (overallBestVoice == 0 || overallMinReference>0) && allowBackupPVG) { u8* p = (u8*)&pedVoiceGroupObject->PrimaryVoice[pedVoiceGroupObject->numPrimaryVoices]; u8 numMiniVoices = *p; p++; tVoice* miniVoices = (tVoice*)p; p = (u8*)&miniVoices[numMiniVoices]; u8 numGangVoices = *p; p++; tVoice* gangVoices = (tVoice*)p; p = (u8*)&gangVoices[numGangVoices]; u8 numBackupPVGs = *p; p++; u32* BackupPVGHashes = (u32*)p; if(numBackupPVGs > 0) { u8 PVGStartIndex = audEngineUtil::GetRandomInteger() % numBackupPVGs; for(int i=0; i overallMinRefIn) { overallMinBackupReference = overallMinRefIn; overallBestBackupVoice = bestVoice[0]; } } } } if(overallBestBackupVoice == 0 || overallMinBackupReference >= overallMinReference) return overallBestVoice; return overallBestBackupVoice; } void audSpeechManager::AddReferenceToVoice(u32 pedVoiceGroup, u32 voiceNameHash BANK_ONLY(, audSpeechAudioEntity* speechEnt)) { if (voiceNameHash==0) { return; } // go through our primary, mini and gang voices - return as soon as we find one u8 numVoices = 0; tVoice* voices = GetPrimaryVoices(pedVoiceGroup, numVoices); for (u32 i=0; i(pedVoiceGroup))->NameTableOffset), speechEnt, voices[i].ReferenceCount, voices[i].RunningTab); } #endif return; } } numVoices = 0; voices = GetMiniVoices(pedVoiceGroup, numVoices); for (u32 i=0; i(pedVoiceGroup))->NameTableOffset), speechEnt, voices[i].ReferenceCount, voices[i].RunningTab); } #endif return; } } numVoices = 0; voices = GetGangVoices(pedVoiceGroup, numVoices); for (u32 i=0; i(pedVoiceGroup))->NameTableOffset), speechEnt, voices[i].ReferenceCount, voices[i].RunningTab); } #endif return; } } } void audSpeechManager::RemoveReferenceToVoice(u32 pedVoiceGroup, u32 voiceNameHash) { if (voiceNameHash==0) { return; } // go through our primary, mini and gang voices - return as soon as we find one u8 numVoices = 0; tVoice* voices = GetPrimaryVoices(pedVoiceGroup, numVoices); for (u32 i=0; i(conversationLines[i])) > 0) { // It's valid for them to have this type of conversation validConversationTopics = validConversationTopics | ((u16)conversationTopics[i]); } } return validConversationTopics; } u8 audSpeechManager::GetMiniVoiceType(const u32 voiceNameHash) { // This is a bit shit - we shouldn't do FindVariationInfoForVoiceAndContext() every time - tag them at a higher level. // First, see if it's a gang mini, then if it's a hooker, then if it's driving, then it must (check) be a non-driving. u32 numVariations = 0; const u32 CONV_GANG_STATE = ATPARTIALSTRINGHASH("CONV_GANG_STATE", 0x6E6D0E76); const u32 HOOKER_SEX = ATPARTIALSTRINGHASH("HOOKER_SEX", 0x3D386278); const u32 CRASH_CAR = ATPARTIALSTRINGHASH("CRASH_CAR", 0x251FCF02); if (audSpeechSound::FindVariationInfoForVoiceAndContext(voiceNameHash, CONV_GANG_STATE, numVariations)) { return AUD_MINI_VOICE_TYPE_GANG; } else if (audSpeechSound::FindVariationInfoForVoiceAndContext(voiceNameHash, HOOKER_SEX, numVariations)) { return AUD_MINI_VOICE_TYPE_HOOKER; } else if (audSpeechSound::FindVariationInfoForVoiceAndContext(voiceNameHash, CRASH_CAR, numVariations)) { return AUD_MINI_VOICE_TYPE_DRIVING; } else { // Once all voices are in-game, start doing this again // Assert(audSpeechSound::FindVariationInfoForVoiceAndContext(voiceNameHash, "BUMP", numVariations)); return AUD_MINI_VOICE_TYPE_NORMAL; } } bool audSpeechManager::IsAMiniNonDrivingContext(char* context) { u32 contextHash = atStringHash(context); // See if it's in our list of mini non-driving contexts for (s32 i=0; i(pedVoiceGroup); if (pedVoiceGroupObject) { numPrimaryVoices = pedVoiceGroupObject->numPrimaryVoices; if (numPrimaryVoices>0) { return (tVoice*)&pedVoiceGroupObject->PrimaryVoice[0]; } } return NULL; } tVoice* audSpeechManager::GetMiniVoices(u32 pedVoiceGroup, u8& numMiniVoices) { PedVoiceGroups* pedVoiceGroupObject = audNorthAudioEngine::GetObject(pedVoiceGroup); if (pedVoiceGroupObject) { u8* p = (u8*)&pedVoiceGroupObject->PrimaryVoice[pedVoiceGroupObject->numPrimaryVoices]; numMiniVoices = *p; if (numMiniVoices>0) { p++; return (tVoice*)p; } } return NULL; } tVoice* audSpeechManager::GetGangVoices(u32 pedVoiceGroup, u8& numGangVoices) { PedVoiceGroups* pedVoiceGroupObject = audNorthAudioEngine::GetObject(pedVoiceGroup); if (pedVoiceGroupObject) { u8* p = (u8*)&pedVoiceGroupObject->PrimaryVoice[pedVoiceGroupObject->numPrimaryVoices]; u8 numMiniVoices = *p; p++; tVoice* miniVoices = (tVoice*)p; p = (u8*)&miniVoices[numMiniVoices]; numGangVoices = *p; if (numGangVoices>0) { p++; return (tVoice*)p; } } return NULL; } tVoice* audSpeechManager::GetGangVoices(PedVoiceGroups* pedVoiceGroupObject, u8& numGangVoices) { if (pedVoiceGroupObject) { u8* p = (u8*)&pedVoiceGroupObject->PrimaryVoice[pedVoiceGroupObject->numPrimaryVoices]; u8 numMiniVoices = *p; p++; tVoice* miniVoices = (tVoice*)p; p = (u8*)&miniVoices[numMiniVoices]; numGangVoices = *p; if (numGangVoices>0) { p++; return (tVoice*)p; } } return NULL; } #if __BANK void audSpeechManager::ResetSpecificPainDrawStats() { for(int i=0; iGetLoadedBankName() : "NULL"); color = Color_yellow; break; default: safecat(str, "UNKNOWN", sizeof(str)); color = Color_red; break; } grcDebugDraw::Text(Vector2(0.05f, yPos), color, str); yPos += yInc; } } void audSpeechManager::DrawPainPlayedTotals() { char str[1024]; Color32 color; f32 yPos = 0.05f; f32 yInc = 0.025f; f32 xPos = 0.05f; formatf(str, "Pain Played Totals"); grcDebugDraw::Text(Vector2(xPos, yPos), Color_purple, str); yPos += yInc; for(int i=0; iGetLoadedBankName() : "NULL")); grcDebugDraw::Text(Vector2(xPos, yPos), Color_purple, str); yPos += yInc; } } #endif