// // audio/scriptaudioentity.cpp // // Copyright (C) 1999-2006 Rockstar Games. All Rights Reserved. // #include "scriptaudioentity.h" #include "frontendaudioentity.h" #include "planeaudioentity.h" #include "ambience/ambientaudioentity.h" #include "music/musicplayer.h" #include "northaudioengine.h" #include "radioaudioentity.h" #include "policescanner.h" #include "speechmanager.h" #include "debugaudio.h" #include "audioengine/engine.h" #include "audioengine/engineutil.h" #include "audioengine/soundfactory.h" #include "audiosoundtypes/simplesound.h" #include "audiohardware/waveslot.h" #include "audiohardware/driver.h" #include "audio/caraudioentity.h" #include "debug/debug.h" #include "debug/debugglobals.h" #include "dynamicmixer.h" #include "text/messages.h" #include "peds/ped.h" #include "peds/PedPhoneComponent.h" #include "peds/popzones.h" #include "control/replay/Audio/ScriptAudioPacket.h" #include "scene/RefMacros.h" #include "script/script.h" #include "system/FileMgr.h" #include "game/zones.h" #include "vehicles/automobile.h" #include "Vfx/Misc/Fire.h" #include "network/NetworkInterface.h" #include "network/Events/NetworkEventTypes.h" #include "game/user.h" #include "text/TextConversion.h" #include "debugaudio.h" #include AUDIO_OPTIMISATIONS() RAGE_DEFINE_SUBCHANNEL(Audio,Conversation) RAGE_DEFINE_SUBCHANNEL(Audio,NetworkAudio) //OPTIMISATIONS_OFF() namespace rage { XPARAM(audiowidgets); } PF_PAGE(ScriptedSpeechTrackerPage, "Scripted Speech Tracker Page"); PF_GROUP(ScriptedSpeechStats); PF_LINK(ScriptedSpeechTrackerPage, ScriptedSpeechStats); PF_VALUE_INT(TimeToPreloadScriptedSpeech, ScriptedSpeechStats); PF_VALUE_INT(TimeTilTriggeredSpeechPlayed, ScriptedSpeechStats); XPARAM(audiodesigner); PARAM(enableConvInterrupt, "Enable scripted conversation interrupts."); PARAM(disableConvInterrupt, "Disable scripted conversation interrupts."); PARAM(conversationspew, "Ensable scripted conversation spew"); PARAM(noAbortingFailedConv, "Do not abort conversations that fail to load."); PARAM(noOverlapLipsyncAdjustment, "Do not adjust conversation overlap times for lipsync predelay."); PARAM(trackMissingDialogue, "Output info on missing scripted speech lines to a timestamp-named CSV file."); PARAM(trackMissingDialogueMP, "Output info on missing scripted speech lines to a timestamp-named CSV file (including MP)."); PARAM(displayScriptedSpeechLine, "Debug draw the current playing scripted speech line."); PARAM(useRobotSpeechForAllLines, "If not set, robot speech will only be used for non-placeholder conversations"); PARAM(scriptStreamSpew, "Ensable scripted stream spew"); PARAM(scriptSoundsDebug, "Ensable scripted sounds on screen debug"); audScriptAudioEntity g_ScriptAudioEntity; extern audAmbientAudioEntity g_AmbientAudioEntity; extern const char* g_CarCodeUnitTypeStrings[26]; extern f32 g_HealthLostForHighPain; audHashActionTable audScriptAudioEntity::sm_ScriptActionTable; s32 audScriptAudioEntity::sm_ScriptInChargeOfTheStream = -1; const s32 g_PhoneOverlapTime = 250; u32 g_TimeToWaitForInterruptToPrepare = 2500; NOTFINAL_ONLY(bool g_EnablePlaceholderDialogue = true); #if __BANK bool audScriptAudioEntity::sm_DrawScriptTriggeredSounds = false; bool audScriptAudioEntity::sm_DrawScriptStream = false; bool audScriptAudioEntity::sm_InWorldDrawScriptTriggeredSounds = false; bool audScriptAudioEntity::sm_LogScriptTriggeredSounds = false; bool audScriptAudioEntity::sm_DrawScriptBanksLoaded = false; bool audScriptAudioEntity::sm_FilterScriptTriggeredSounds = false; char audScriptAudioEntity::sm_ScriptTriggerOverride[64] = "\0"; char audScriptAudioEntity::sm_ScriptedLineDebugInfo[128] = "\0"; bool audScriptAudioEntity::sm_DrawReplayScriptSpeechSlots = false; bool g_FakeConvAnimTrigger = false; bool g_AnimTriggersControllingConversation = false; bool g_ConversationDebugSpew = false; bool g_ScriptedStreamSpew = false; bool g_ForceMobileInterference = false; bool g_DisplayScriptedLineInfo = false; bool g_DisplayScriptSoundIdInfo = false; bool g_DisplayScriptBankUsage = false; bool g_DrawScriptSpeechSlots = false; f32 g_ScriptSoundIdInfoScroll = 0.0f; static char g_AmbientSpeechDebugContext[128] = "CAR_HIT_PED"; static char g_AmbientSpeechDebugVoice[128] = "MICHAEL_NORMAL"; static char g_AmbientSpeechDebugParams[128] = "SPEECH_PARAMS_STANDARD"; f32 g_AmbientSpeechDebugPosX = 0.0f; f32 g_AmbientSpeechDebugPosY = 0.0f; f32 g_AmbientSpeechDebugPosZ = 0.0f; bool g_TrackMissingDialogue = false; bool g_TrackMissingDialogueMP = false; #endif bool g_ForceScriptedInterrupts = false; bool g_DoNotAbortFailedConversations = false; bool g_AdjustOverlapForLipsyncPredelay = true; bool g_AdjustTriggerTimeForZeroPlaytime = true; bool g_AdjustTriggerForZeroEvenIfMarkedOverlap = true; u32 g_MinPedsHitForRunnedOverReaction = 3; u32 g_TimespanToHitPedForRunnedOverReaction = 6000; u32 g_MinTimeBetweenLastCollAndRunnedOverReaction = 2000; u32 g_TimeToWaitBetweenRunnedOverReactions = 5000; u32 g_AdditionalScriptedSpeechPause = 0; bool g_DuckForConvAfterPreload = false; bool g_DebugPlayingScriptSounds = false; s32 g_MissionCompleteDelay = 1500; extern s32 g_NextFireStartOffset; extern bool g_MuteGameWorldAndPositionedRadioForTV; bool g_AllowUpsideDownConversations = false; bool g_AllowVehicleOnFireConversations = false; bool g_AllowVehicleDrowningConversations = false; s32 g_StopAmbientAnimTime = 1000; bool g_DebugScriptedConvs = false; const u32 g_FrontendGameMobileRingingHash = ATSTRINGHASH("FRONTEND_GAME_MOBILE_RINGING", 0x0980d4ab6); const u32 g_MobilePreRingHash = ATSTRINGHASH("MOBILE_PRERING_SOUND", 0x0b88f89fb); const u32 g_MobileGetSignalHash = ATSTRINGHASH("MOBILE_GET_SIGNAL_SOUND", 0x0428e734b); f32 g_MinLowPainBikeReaction = 45.0f; f32 g_MinLowReactionDamage = 11.0f; f32 g_MinMediumReactionDamage = 20.0f; f32 g_MinHighReactionDamage = 100.0f; u32 g_TimeToPauseConvOnCollision = 1500; u32 g_MinTimeBetweenCollisionReactions = 5000; u32 g_MinTimeBetweenCollisionTriggers = 5000; u32 g_MaxTimeToAllocateSlot = 5000; bool g_DisableInterrupts = false; bool g_DisableAirborneInterrupts = false; bool g_AllowMuliWantedInterrupts = false; s32 g_MinRagdollTimeBeforeConvPause = 500; s32 g_MinNotRagdollTimeBeforeConvRestart = 3000; u32 g_MinTimeCarInAirForInterrupt = 700; u32 g_MinTimeCarOnGroundForInterrupt = 10000; u32 g_MinTimeCarOnGroundToRestart = 750; u32 g_MinAirBorneTimeToKillConversation = 8000; u32 g_MaxAirBorneTimeToApplyRestartOffset = 4000; f32 g_MinCarRollForInterrupt = -0.4f; #if GTA_REPLAY tReplayScriptedSpeechSlots audScriptAudioEntity::sm_ReplayScriptedSpeechSlot[AUD_REPLAY_SCRIPTED_SPEECH_SLOTS]; tReplayScriptedSpeechSlots audScriptAudioEntity::sm_ReplayScriptedSpeechAlreadyPlayed[AUD_REPLAY_SCRIPTED_SPEECH_PLAYED_SLOTS]; char audScriptAudioEntity::sm_CurrentStreamName[128]; u32 audScriptAudioEntity::sm_CurrentStartOffset = 0; char audScriptAudioEntity::sm_CurrentSetName[128]; #endif #if __BANK int g_InterruptSpeakerNum = 0; bool g_ForceNonPlayerInterrupt = false; char g_szLowInterruptContext[256]; char g_szMediumInterruptContext[256]; char g_szHighInterruptContext[256]; #endif AircraftWarningSettings* g_AircraftWarningSettings = NULL; //u32 g_AircraftWarningPHash = ATPARTIALSTRINGHASH("AIRCRAFT_WARNING", 0xF65A0A93); u32 g_AircraftWarningMale1Hash = ATSTRINGHASH("AIRCRAFT_WARNING_MALE_01", 0xA65A6402); u32 g_AircraftWarningFemale1Hash = ATSTRINGHASH("AIRCRAFT_WARNING_FEMALE_01", 0x85A08F9C); bool g_EnableAircraftWarningSpeech = false; const u32 g_BarkPHash = ATPARTIALSTRINGHASH("BARK", 0xB0EF9479); // Create flag map #if RSG_BANK #define SCRIPT_AUDIO_FLAG(n,h) ATSTRINGHASH(#n,h), #n, #else #define SCRIPT_AUDIO_FLAG(n,h) ATSTRINGHASH(#n,h), #endif const audScriptAudioFlags::tFlagNames audScriptAudioFlags::s_FlagNames[audScriptAudioFlags::kNumScriptAudioFlags] = { #include "scriptaudioflags.h" }; #undef SCRIPT_AUDIO_FLAG void audScriptAudioEntity::InitClass() { // default handler handles cases where a sound has to be triggered with no special logic sm_ScriptActionTable.SetDefaultActionHandler(&audScriptAudioEntity::DefaultScriptActionHandler); // phone call functions sm_ScriptActionTable.AddActionHandler("MOBILE_PRERING", &g_ScriptAudioEntity.MobilePreRingHandler); // m_ScriptActionTable.AddActionHandler("START_MOBILE_PHONE_RINGING", &g_ScriptAudioEntity.StartMobilePhoneRinging); // m_ScriptActionTable.AddActionHandler("START_MOBILE_PHONE_CALLING", &g_ScriptAudioEntity.StartMobilePhoneCalling); // m_ScriptActionTable.AddActionHandler("STOP_MOBILE_PHONE_RINGING", &g_ScriptAudioEntity.StopMobilePhoneRinging); } void audScriptAudioEntity::Init() { naAudioEntity::Init(); audEntity::SetName("audScriptAudioEntity"); m_SpeechAudioEntity.Init(NULL); m_ScriptInChargeOfAudio = -1; BANK_ONLY(m_CurrentScriptName = ""); m_MobileInterferenceSound = NULL; m_CellphoneRingSound = NULL; m_ScriptedConversationInProgress = false; m_DuckingForScriptedConversation = false; m_IsScriptedConversationZit = false; m_ScriptedConversationSpeechSound = NULL; m_PlayingScriptedConversationLine = -1; m_IsScriptedConversationAMobileCall = false; m_DisplaySubtitles = false; m_CloneConversation = false; m_AddSubtitlesToBriefing = false; m_PauseConversation = false; m_PausedForRagdoll = false; m_PauseWaitingOnCurrentLine = false; m_ShouldRestartConvAtMarker = false; m_AbortScriptedConversationRequested = false; m_HighestLoadedLine = -1; m_HighestLoadingLine = -1; m_PreLoadingSpeechLine = -1; m_ScriptedConversationOverlapTriggerTime = 0; m_OverlapTriggerTimeAdjustedForPredelay = false; m_DisableReplayScriptStreamRecording = false; m_ScriptedConversationSfx = NULL; m_CurrentPreloadSet = 0; m_LastMobileInterferenceTime = 0; m_NextTimeScriptedConversationCanStart = 0; m_TimeLastScriptedConvFinished = 0; m_TimeLastBeepOffPlayed = 0; for (s32 i=0; iSetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 0.f); m_Interrupter = NULL; m_InterruptingContext[0] = '\0'; m_InterruptingVoiceNameHash = 0; NOTFINAL_ONLY(m_InterruptingPlaceholderVoiceNameHash = 0;) m_TimeToInterrupt = 0; m_TimeToRestartConversation = 0; m_TimeOfLastCollisionReaction = 0; m_TimeOfLastCollisionTrigger = 0; m_TimeToLowerCollisionLevel = 0; m_SpeechRestartOffset = 0; m_LineCheckedForRestartOffset = -1; m_InterruptTriggered = false; m_TimeLastFrame = 0; m_TimeElapsedSinceLastUpdate = 0; m_ConversationIsInterruptible = true; m_TimePlayersCarWasLastOnTheGround = 0; m_TimePlayersCarWasLastInTheAir = 0; m_TimeOfLastInAirInterrupt = 0; m_TimeLastRegisteredCarInAir = 0; m_ScriptSetNoDucking = false; m_HeadSetBeepOnLineIndex = -1; m_HeadSetBeepOffCachedVolume = 0.f; m_TimeOfLastCarPedCollision = 0; m_TimeLastTriggeredPedCollisionScreams = 0; for(int i=0; i(&m_FlagState); m_AnimTriggersControllingConversation = false; m_AnimTriggerReported = false; m_IsConversationPlaceholder = false; m_WaitingForFirstLineToPrepareToDuck = false; m_BlockDeathJingle = false; m_ScriptedSpeechTimerPaused = false; m_ScriptOwnedEnvGroup.Reset(); m_AircraftWarningSpeechEnt.Init(NULL); m_AircraftWarningSpeechEnt.SetAmbientVoiceName(g_AircraftWarningMale1Hash, true); InitAircraftWarningInfo(); m_TimeOfLastCollisionCDI = 0; m_AmbientSpeechAudioEntity.Init(NULL); m_TimeEnteringSlowMo = 0; m_TimeExitingSlowMo = 0; m_InSlowMoForPause = false; m_InSlowMoForSpecialAbility = false; m_TimeOfLastChopCollisionReaction = 0; #if GTA_REPLAY for(int i=0; iStop(); audEntity::Shutdown(); #if __BANK if(m_DialogueFileHandle != INVALID_FILE_HANDLE) { CFileMgr::CloseFile(m_DialogueFileHandle); m_DialogueFileHandle = INVALID_FILE_HANDLE; } #endif } void audScriptAudioEntity::InitAircraftWarningInfo() { safecpy(m_AircraftWarningInfo[AUD_AW_TARGETED_LOCK].Context, "TARGETED_LOCK"); safecpy(m_AircraftWarningInfo[AUD_AW_MISSLE_FIRED].Context, "MISSLE_FIRED"); safecpy(m_AircraftWarningInfo[AUD_AW_ACQUIRING_TARGET].Context, "ACQUIRING_TARGET"); safecpy(m_AircraftWarningInfo[AUD_AW_TARGET_ACQUIRED].Context, "TARGET_ACQUIRED"); safecpy(m_AircraftWarningInfo[AUD_AW_ALL_CLEAR].Context, "ALL_CLEAR"); safecpy(m_AircraftWarningInfo[AUD_AW_PLANE_WARNING_STALL].Context, "PLANE_WARNING_STALL"); safecpy(m_AircraftWarningInfo[AUD_AW_ALTITUDE_WARNING_LOW].Context, "ALTITUDE_WARNING_LOW"); safecpy(m_AircraftWarningInfo[AUD_AW_ALTITUDE_WARNING_HIGH].Context, "ALTITUDE_WARNING_HIGH"); safecpy(m_AircraftWarningInfo[AUD_AW_ENGINE_1_FIRE].Context, "ENGINE_1_FIRE"); safecpy(m_AircraftWarningInfo[AUD_AW_ENGINE_2_FIRE].Context, "ENGINE_2_FIRE"); safecpy(m_AircraftWarningInfo[AUD_AW_ENGINE_3_FIRE].Context, "ENGINE_3_FIRE"); safecpy(m_AircraftWarningInfo[AUD_AW_ENGINE_4_FIRE].Context, "ENGINE_4_FIRE"); safecpy(m_AircraftWarningInfo[AUD_AW_DAMAGED_SERIOUS].Context, "DAMAGED_SERIOUS"); safecpy(m_AircraftWarningInfo[AUD_AW_DAMAGED_CRITICAL].Context, "DAMAGED_CRITICAL"); safecpy(m_AircraftWarningInfo[AUD_AW_OVERSPEED].Context, "OVERSPEED"); safecpy(m_AircraftWarningInfo[AUD_AW_TERRAIN].Context, "TERRAIN"); safecpy(m_AircraftWarningInfo[AUD_AW_PULL_UP].Context, "PULL_UP"); safecpy(m_AircraftWarningInfo[AUD_AW_LOW_FUEL].Context, "LOW_FUEL"); m_ShouldUpdateAircraftWarning = false; InitAircraftWarningRaveSettings(); } void audScriptAudioEntity::InitAircraftWarningRaveSettings() { g_AircraftWarningSettings = audNorthAudioEngine::GetObject("AIRCRAFT_WARNING_SETTINGS"); if(g_AircraftWarningSettings) { m_AircraftWarningInfo[AUD_AW_TARGETED_LOCK].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->TargetedLock.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_TARGETED_LOCK].MinTimeBetweenPlay = g_AircraftWarningSettings->TargetedLock.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_MISSLE_FIRED].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->MissleFired.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_MISSLE_FIRED].MinTimeBetweenPlay = g_AircraftWarningSettings->MissleFired.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_ACQUIRING_TARGET].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->AcquiringTarget.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_ACQUIRING_TARGET].MinTimeBetweenPlay = g_AircraftWarningSettings->AcquiringTarget.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_TARGET_ACQUIRED].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->TargetAcquired.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_TARGET_ACQUIRED].MinTimeBetweenPlay = g_AircraftWarningSettings->TargetAcquired.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_ALL_CLEAR].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->AllClear.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_ALL_CLEAR].MinTimeBetweenPlay = g_AircraftWarningSettings->AllClear.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_PLANE_WARNING_STALL].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->PlaneWarningStall.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_PLANE_WARNING_STALL].MinTimeBetweenPlay = g_AircraftWarningSettings->PlaneWarningStall.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_ALTITUDE_WARNING_LOW].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->AltitudeWarningLow.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_ALTITUDE_WARNING_LOW].MinTimeBetweenPlay = g_AircraftWarningSettings->AltitudeWarningLow.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_ALTITUDE_WARNING_HIGH].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->AltitudeWarningHigh.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_ALTITUDE_WARNING_HIGH].MinTimeBetweenPlay = g_AircraftWarningSettings->AltitudeWarningHigh.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_ENGINE_1_FIRE].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->Engine_1_Fire.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_ENGINE_1_FIRE].MinTimeBetweenPlay = g_AircraftWarningSettings->Engine_1_Fire.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_ENGINE_2_FIRE].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->Engine_2_Fire.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_ENGINE_2_FIRE].MinTimeBetweenPlay = g_AircraftWarningSettings->Engine_2_Fire.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_ENGINE_3_FIRE].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->Engine_3_Fire.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_ENGINE_3_FIRE].MinTimeBetweenPlay = g_AircraftWarningSettings->Engine_3_Fire.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_ENGINE_4_FIRE].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->Engine_4_Fire.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_ENGINE_4_FIRE].MinTimeBetweenPlay = g_AircraftWarningSettings->Engine_4_Fire.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_DAMAGED_SERIOUS].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->DamagedSerious.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_DAMAGED_SERIOUS].MinTimeBetweenPlay = g_AircraftWarningSettings->DamagedSerious.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_DAMAGED_CRITICAL].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->DamagedCritical.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_DAMAGED_CRITICAL].MinTimeBetweenPlay = g_AircraftWarningSettings->DamagedCritical.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_OVERSPEED].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->Overspeed.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_OVERSPEED].MinTimeBetweenPlay = g_AircraftWarningSettings->Overspeed.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_TERRAIN].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->Terrain.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_TERRAIN].MinTimeBetweenPlay = g_AircraftWarningSettings->Terrain.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_PULL_UP].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->PullUp.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_PULL_UP].MinTimeBetweenPlay = g_AircraftWarningSettings->PullUp.MinTimeBetweenPlay; m_AircraftWarningInfo[AUD_AW_LOW_FUEL].MaxTimeBetweenTriggerAndPlay = g_AircraftWarningSettings->LowFuel.MaxTimeBetweenTriggerAndPlay; m_AircraftWarningInfo[AUD_AW_LOW_FUEL].MinTimeBetweenPlay = g_AircraftWarningSettings->LowFuel.MinTimeBetweenPlay; } else naWarningf("Failed to find aircraft warning rave settings. Using default values."); } u32 g_TimeBetweenCollisionReactionAndThresholdDowngrade = 15000; void audScriptAudioEntity::UpdateConversation() { u32 currentTime = g_AudioEngine.GetTimeInMilliseconds(); m_TimeElapsedSinceLastUpdate = currentTime - m_TimeLastFrame; #if __BANK && !RSG_ORBIS if(g_FakeConvAnimTrigger) { HandleConversaionAnimTrigger(NULL); g_FakeConvAnimTrigger = false; } if(!g_TrackMissingDialogue && !g_TrackMissingDialogueMP && m_DialogueFileHandle != INVALID_FILE_HANDLE) { CFileMgr::CloseFile(m_DialogueFileHandle); m_DialogueFileHandle = INVALID_FILE_HANDLE; m_HasInitializedDialogueFile = false; } #endif m_SpeechAudioEntity.Update(); m_AircraftWarningSpeechEnt.Update(); m_AmbientSpeechAudioEntity.Update(); // Update any conversations SlowMoType slowMoType = audNorthAudioEngine::GetActiveSlowMoMode(); bool isSlowMo = audNorthAudioEngine::IsInSlowMo() || fwTimer::IsSystemPaused(); CPed *player = CGameWorld::FindLocalPlayer(); if (player && player->GetSpecialAbility()) { if(m_InSlowMoForSpecialAbility && slowMoType == AUD_SLOWMO_GENERAL) { // Only set m_InSlowMoForSpecialAbility to false if we are no longer in special ability m_InSlowMoForSpecialAbility = !player->GetSpecialAbility()->IsActive() || !audNorthAudioEngine::IsPlayerSpecialAbilityFadingOut(); isSlowMo = isSlowMo && !audNorthAudioEngine::IsPlayerSpecialAbilityFadingOut(); } } if(isSlowMo && !g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::AllowScriptedSpeechInSlowMo)) { if(m_ColLevel != AUD_INTERRUPT_LEVEL_SLO_MO) { if(m_TimeEnteringSlowMo == 0) m_TimeEnteringSlowMo = currentTime; if(currentTime - m_TimeEnteringSlowMo > 250 || slowMoType == AUD_SLOWMO_PAUSEMENU) { RegisterSlowMoInterrupt(slowMoType); } } m_TimeExitingSlowMo = currentTime; } else if(!isSlowMo) { m_TimeEnteringSlowMo = 0; if(m_ColLevel == AUD_INTERRUPT_LEVEL_SLO_MO && currentTime - m_TimeExitingSlowMo > (m_InSlowMoForPause ? 1000 : 500)) StopSlowMoInterrupt(); } if(audNorthAudioEngine::IsAudioPaused() && !g_AudioEngine.GetSoundManager().IsPaused(4)) g_AudioEngine.GetSoundManager().Pause(4); else if(!audNorthAudioEngine::IsAudioPaused() && g_AudioEngine.GetSoundManager().IsPaused(4) && !m_ScriptedSpeechTimerPaused) { m_TimeToRestartConversation = g_AudioEngine.GetTimeInMilliseconds() + 500; g_AudioEngine.GetSoundManager().UnPause(4); } CVehicle* playerVehicle = CGameWorld::FindLocalPlayerVehicle(); if(playerVehicle) { bool notACar = playerVehicle->GetVehicleType() != VEHICLE_TYPE_CAR && playerVehicle->GetVehicleType() != VEHICLE_TYPE_BIKE && playerVehicle->GetVehicleType() != VEHICLE_TYPE_QUADBIKE && playerVehicle->GetVehicleType() != VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE; if (playerVehicle->GetNumContactWheels() != 0 || notACar) { m_TimePlayersCarWasLastOnTheGround = currentTime; } else { m_TimePlayersCarWasLastInTheAir = currentTime; } } // Update headset beep state based on the current script flags before processing the conversation if(!IsFlagSet(audScriptAudioFlags::EnableHeadsetBeep)) { m_HeadSetBeepOnLineIndex = -1; m_TimeLastScriptedConvFinished = 0; m_TimeLastBeepOffPlayed = 0; } if (m_ScriptedConversationInProgress) { if(m_ScriptSetNoDucking && m_ConversationDuckingScene) m_ConversationDuckingScene->SetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 0.f); ProcessScriptedConversation(currentTime); } else if (m_TimeLastScriptedConvFinished + 200 < audNorthAudioEngine::GetCurrentTimeInMs()) { if(m_HeadSetBeepOnLineIndex >= 0 && !IsFlagSet(audScriptAudioFlags::DisableHeadsetOffBeep)) { audSoundInitParams beepInitParams; beepInitParams.Pan = 0; beepInitParams.Predelay = 300; beepInitParams.Volume = m_HeadSetBeepOffCachedVolume; CreateAndPlaySound(ATSTRINGHASH("DLC_HEISTS_HEADSET_CALL_END_BEEP", 0x6511F36C),&beepInitParams); m_TimeLastBeepOffPlayed = audNorthAudioEngine::GetCurrentTimeInMs(); //Reset the cached volume for next time. m_HeadSetBeepOffCachedVolume = 0.f; } m_HeadSetBeepOnLineIndex = -1; m_TimeLastScriptedConvFinished = 0; } m_TimeLastFrame = currentTime; } void audScriptAudioEntity::PreUpdateService(u32 timeInMs) { #if GTA_REPLAY UpdateReplayScriptedSpeechSlots(); #endif // Update our script bank slots and sounds, to manage their states UpdateScriptBankSlots(timeInMs); UpdateScriptSounds(timeInMs); UpdateMissionComplete(); UpdateCollisionInfo(timeInMs); if(m_ShouldUpdateAircraftWarning) UpdateAircraftWarning(); if(m_ScriptStreamState == AUD_SCRIPT_STREAM_PLAYING) { if(!m_StreamSound) { if(m_StreamSlot) { m_StreamSlot->Free(); m_StreamSlot = NULL; m_StreamType = AUD_PLAYSTREAM_UNKNOWN; } m_ScriptStreamState = AUD_SCRIPT_STREAM_IDLE; m_StreamEnvironmentGroup = NULL; #if GTA_REPLAY for(int i=0; iSetRequestedPosition(m_StreamTrackerEntity->GetTransform().GetPosition()); // If it was played on a ped/vehicle, we'd share that envgroup and those positions are updated, // otherwise we need to update the interior information and position ourselves if(m_StreamEnvironmentGroup && !m_StreamTrackerEntity->GetIsTypePed() && !m_StreamTrackerEntity->GetIsTypeVehicle()) { m_StreamEnvironmentGroup->SetSource(m_StreamTrackerEntity->GetTransform().GetPosition()); m_StreamEnvironmentGroup->SetInteriorInfoWithEntity(m_StreamTrackerEntity); } } } } // Make sure the interference gets faded out if the radio does if(m_MobileInterferenceSound) { m_MobileInterferenceSound->SetRequestedVolume(g_RadioAudioEntity.GetVolumeOffset()); CVehicle* playerVehicle = CGameWorld::FindLocalPlayerVehicle(); if(!playerVehicle || playerVehicle->GetVehicleAudioEntity()->GetAudioVehicleType() != AUD_VEHICLE_CAR || !g_RadioAudioEntity.IsVehicleRadioOn()) { m_MobileInterferenceSound->StopAndForget(); } } } void audScriptAudioEntity::PostUpdate() { for (int i=0; iStopAndForget(); } ResetScriptSound(i); } } if(m_ScriptSounds[i].ScriptIndex >= 0 || m_ScriptSounds[i].NetworkId != audScriptSound::INVALID_NETWORK_ID) { // This is currently used - do we need an explicit 'in use' flag rather than just this? (script slot reuse on orphanable sounds causing a problem?) if(!m_ScriptSounds[i].ScriptHasReference) { // The script hasn't got an explicit reference to this sound, so if the sound in it's finished, we can remove the association and free the slot. if (!m_ScriptSounds[i].Sound) { ResetScriptSound(i); } } } } // clear out bank references where the calling script has terminated for (s32 i=0; iGetLoadedBankName(), slot.BankSlot->GetSlotName(), slot.BankId, slot.BankSlot->GetLoadedBankId(), waveSlot ? waveSlot->GetCachedLoadedBankName(): "", waveSlot ? waveSlot->GetSlotName() : "", waveSlot ? waveSlot->GetLoadedBankId() : 0); } } } } } #endif } void audScriptAudioEntity::UpdateMissionComplete() { #if GTA_REPLAY if(CReplayMgr::IsEditorActive()) { StopMissionCompleteSound(); m_MissionCompleteState = AUD_MISSIONCOMPLETE_IDLE; m_ShouldShowMissionCompleteUI = true; if(FreeUpScriptBank(m_MissionCompleteBankSlot)) { m_MissionCompleteBankSlot = NULL; } } #endif switch(m_MissionCompleteState) { case AUD_MISSIONCOMPLETE_PREPARING: m_ShouldShowMissionCompleteUI = false; if(!m_MissionCompleteSound) { audSoundInitParams initParams; m_MissionCompleteBankSlot = AllocateScriptBank(); if(!m_MissionCompleteBankSlot) { m_MissionCompleteTimeOut += (fwTimer::GetTimeStepInMilliseconds()); // we failed to create the sound if(m_MissionCompleteTimeOut > g_MaxTimeToAllocateSlot) { naErrorf("Reached time out when trying to allocate a script slot for the mission complete sound."); m_MissionCompleteState = AUD_MISSIONCOMPLETE_IDLE; m_ShouldShowMissionCompleteUI = true; } break; } else { initParams.WaveSlot = m_MissionCompleteBankSlot; initParams.TimerId = m_MissionCompleteTimerId; // defaults to unpausable; needs to be stopped by the frontend music CreateSound_PersistentReference(m_MissionCompleteHash, &m_MissionCompleteSound, &initParams); } } if(m_MissionCompleteSound) { audPrepareState prepareState = m_MissionCompleteSound->Prepare(m_MissionCompleteBankSlot, true); if(prepareState == AUD_PREPARED) { m_MissionCompleteFadeTime = ~0U; m_MissionCompleteHitTime = 0; m_MissionCompleteTimeOut = 0; const WrapperSound *wrapperSound = SOUNDFACTORY.DecompressMetadata(m_MissionCompleteHash); if(wrapperSound) { const MultitrackSound *multitrack = SOUNDFACTORY.DecompressMetadata(wrapperSound->SoundRef); if(multitrack) { const SimpleSound *simple1 = SOUNDFACTORY.DecompressMetadata(multitrack->SoundRef[0].SoundId); const SimpleSound *simple2 = SOUNDFACTORY.DecompressMetadata(multitrack->SoundRef[1].SoundId); if(simple1 && simple2) { audWaveRef waveRef; audWaveMarkerList markers; u32 sampleRate = 0; if(waveRef.Init(m_MissionCompleteBankSlot, simple1->WaveRef.WaveName)) { waveRef.FindMarkerData(markers); const audWaveFormat *format = waveRef.FindFormat(); if(format) { sampleRate = format->SampleRate; } } if(markers.GetCount() == 0) { if(waveRef.Init(m_MissionCompleteBankSlot, simple2->WaveRef.WaveName)) { const audWaveFormat *format = waveRef.FindFormat(); if(format) { sampleRate = format->SampleRate; } } } for(u32 i=0; i< markers.GetCount(); i++) { const u32 fadeHash = ATSTRINGHASH("FADE", 0x06e9c2bb6); const u32 hitHash = ATSTRINGHASH("UIHIT", 0xD8846402); if(markers[i].categoryNameHash == fadeHash) { m_MissionCompleteFadeTime = audDriverUtil::ConvertSamplesToMs(markers[i].positionSamples, sampleRate); } else if (markers[i].categoryNameHash == hitHash) { m_MissionCompleteHitTime = audDriverUtil::ConvertSamplesToMs(markers[i].positionSamples, sampleRate); } } } } } // we're now prepared; go into a waiting state until the delay is up m_MissionCompleteState = AUD_MISSIONCOMPLETE_DELAY; } else if(prepareState == AUD_PREPARE_FAILED) { m_MissionCompleteSound->StopAndForget(); m_MissionCompleteState = AUD_MISSIONCOMPLETE_IDLE; m_ShouldShowMissionCompleteUI = true; if(FreeUpScriptBank(m_MissionCompleteBankSlot)) { m_MissionCompleteBankSlot = NULL; } } else if ( prepareState == AUD_PREPARING) { // we can also time out when preparing. m_MissionCompleteTimeOut += (fwTimer::GetTimeStepInMilliseconds()); // we failed to create the sound if(m_MissionCompleteTimeOut > g_MaxTimeToAllocateSlot) { naErrorf("Reached time out when preparing the mission complete sound."); m_MissionCompleteSound->StopAndForget(); m_MissionCompleteState = AUD_MISSIONCOMPLETE_IDLE; m_ShouldShowMissionCompleteUI = true; if(FreeUpScriptBank(m_MissionCompleteBankSlot)) { m_MissionCompleteBankSlot = NULL; } } break; } } else { // we failed to create the sound naErrorf("Failed to create mission complete sound"); m_MissionCompleteState = AUD_MISSIONCOMPLETE_IDLE; m_ShouldShowMissionCompleteUI = true; if(FreeUpScriptBank(m_MissionCompleteBankSlot)) { m_MissionCompleteBankSlot = NULL; } } break; case AUD_MISSIONCOMPLETE_DELAY: m_ShouldShowMissionCompleteUI = false; if(!IsFlagSet(audScriptAudioFlags::HoldMissionCompleteWhenPrepared)) { if(IsFlagSet(audScriptAudioFlags::AvoidMissionCompleteDelay) || (fwTimer::GetTimeInMilliseconds() > m_MissionCompleteTriggerTime + g_MissionCompleteDelay)) { if(m_MissionCompleteSound) { m_MissionCompleteSound->Play(); m_MissionCompleteState = AUD_MISSIONCOMPLETE_PLAYING; } else { naErrorf("No mission complete sound during AUD_MISSIONCOMPLETE_DELAY state"); m_MissionCompleteState = AUD_MISSIONCOMPLETE_IDLE; m_ShouldShowMissionCompleteUI = true; if(FreeUpScriptBank(m_MissionCompleteBankSlot)) { m_MissionCompleteBankSlot = NULL; } } } } else { m_MissionCompleteTriggerTime = fwTimer::GetTimeInMilliseconds(); } break; case AUD_MISSIONCOMPLETE_PLAYING: if(!m_MissionCompleteSound) { m_MissionCompleteState = AUD_MISSIONCOMPLETE_IDLE; m_ShouldShowMissionCompleteUI = true; if(FreeUpScriptBank(m_MissionCompleteBankSlot)) { m_MissionCompleteBankSlot = NULL; } } else { const s32 playTimeMs = (s32)m_MissionCompleteSound->GetCurrentPlayTime(g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(m_MissionCompleteTimerId)); if(playTimeMs >= (s32)m_MissionCompleteHitTime) { m_ShouldShowMissionCompleteUI = true; } } break; default: break; } } void audScriptAudioEntity::TriggerMissionComplete(const char * missionCompleteId, const s32 timerId /* = 3 */) { char missionCompleteName[64]; formatf(missionCompleteName, "SCRIPTED_MISSION_COMPLETE_%s", missionCompleteId); if(m_MissionCompleteSound) { naErrorf("Called TriggerMissionComplete with %s while %s is already playing", missionCompleteName, m_MissionCompleteSound->GetName()); } if(naVerifyf(m_MissionCompleteState == AUD_MISSIONCOMPLETE_IDLE, "In TriggerMissionComplete we should be in AUD_MISSIONCOMPLETE_DELAY (%d) but our state is %d", AUD_MISSIONCOMPLETE_IDLE, m_MissionCompleteState)) { #if !__NO_OUTPUT if(!g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectMetadataPtr(missionCompleteName)) { audErrorf("Invalid mission complete identifier: %s (failed to find %s)", missionCompleteId, missionCompleteName); } #endif m_MissionCompleteHash = atStringHash(missionCompleteName); m_MissionCompleteState = AUD_MISSIONCOMPLETE_PREPARING; m_MissionCompleteTriggerTime = fwTimer::GetTimeInMilliseconds(); m_MissionCompleteTimeOut = 0; m_MissionCompleteTimerId = timerId; m_ShouldShowMissionCompleteUI = false; } } bool audScriptAudioEntity::IsPreparingMissionComplete() const { return (m_MissionCompleteSound && m_MissionCompleteSound->GetPlayState() == AUD_SOUND_PREPARING); } void audScriptAudioEntity::StopMissionCompleteSound() { if(m_MissionCompleteSound) { m_MissionCompleteSound->StopAndForget(); } } bool audScriptAudioEntity::IsPlayingMissionComplete() const { return (m_MissionCompleteSound && m_MissionCompleteSound->GetPlayState() == AUD_SOUND_PLAYING); } f32 audScriptAudioEntity::ComputeRadioVolumeForMissionComplete() const { if(m_MissionCompleteState == AUD_MISSIONCOMPLETE_PREPARING || m_MissionCompleteState == AUD_MISSIONCOMPLETE_DELAY) { const u32 timeInMs = fwTimer::GetTimeInMilliseconds(); const s32 timeTilTrigger = (s32)m_MissionCompleteTriggerTime + (s32)g_MissionCompleteDelay - (s32)timeInMs; dev_s32 radioFadeOutTime = 500; if(timeTilTrigger > radioFadeOutTime) { return 0.f; } else { const f32 interp = Max(0.f, (timeTilTrigger / (f32)radioFadeOutTime)); return audDriverUtil::ComputeDbVolumeFromLinear(interp); } } else if(m_MissionCompleteSound && m_MissionCompleteState == AUD_MISSIONCOMPLETE_PLAYING) { const s32 playTimeMs = (s32)m_MissionCompleteSound->GetCurrentPlayTime(g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(m_MissionCompleteTimerId)); if(playTimeMs < (s32)m_MissionCompleteFadeTime) { return -100.f; } else { const s32 duration = m_MissionCompleteSound->ComputeDurationMsIncludingStartOffsetAndPredelay(NULL); if(playTimeMs >= duration) { return 0.f; } else { f32 fadeInterp = ((f32)(playTimeMs - (s32)m_MissionCompleteFadeTime) / (f32)(duration - (s32)m_MissionCompleteFadeTime)); return audDriverUtil::ComputeDbVolumeFromLinear(fadeInterp); } } } return 0.f; } void audScriptAudioEntity::PlayNPCPickup(const u32 pickupHash, const Vector3 &pickupPosition) { audSoundInitParams initParams; initParams.Position = pickupPosition; audSoundSet soundSet; audMetadataRef pickupSoundRef = g_NullSoundRef; soundSet.Init(ATSTRINGHASH("HUD_FRONTEND_WEAPONS_PICKUPS_NPC_SOUNDSET", 0xF35C567B)); pickupSoundRef = soundSet.Find(pickupHash); if(pickupSoundRef == g_NullSoundRef) { soundSet.Init(ATSTRINGHASH("HUD_FRONTEND_VEHICLE_PICKUPS_NPC_SOUNDSET", 0xF5E3A26A)); pickupSoundRef = soundSet.Find(pickupHash); if(pickupSoundRef == g_NullSoundRef) { soundSet.Init(ATSTRINGHASH("HUD_FRONTEND_STANDARD_PICKUPS_NPC_SOUNDSET", 0x1D46A6A2)); pickupSoundRef = soundSet.Find(pickupHash); } } CreateDeferredSound(pickupSoundRef, NULL,&initParams); } void audScriptAudioEntity::SetDesiredPedWallaDensity(f32 desiredDensity, f32 desiredDensityApplyFactor, bool interior) { s32 scriptIndex = AddScript(); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } ASSERT_ONLY(bool success = ) g_AmbientAudioEntity.SetScriptDesiredPedDensity(desiredDensity, desiredDensityApplyFactor, m_Scripts[scriptIndex].ScriptThreadId, interior); audAssertf(success, "Script %s attempting to set ped walla density when another thread is already doing so", CTheScripts::GetCurrentScriptName()); } void audScriptAudioEntity::SetDesiredPedWallaSoundSet(const char* soundSetName, bool interior) { s32 scriptIndex = AddScript(); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } ASSERT_ONLY(bool success = ) g_AmbientAudioEntity.SetScriptWallaSoundSet(atStringHash(soundSetName), m_Scripts[scriptIndex].ScriptThreadId, interior); audAssertf(success, "Script %s failed to set walla sound set to %s - is it a valid sound set name?", CTheScripts::GetCurrentScriptName(), soundSetName); } void audScriptAudioEntity::UpdateScriptBankSlots(u32 UNUSED_PARAM(timeInMs)) { // No need to do anything - as bank requests are called repeatedly, we can sort everything from there. #if GTA_REPLAY if(CReplayMgr::IsRecording()) { CReplayMgr::RecordFx(CPacketSndLoadScriptBank()); } #endif } void audScriptAudioEntity::UpdateScriptSounds(u32 UNUSED_PARAM(timeInMs)) { for (s32 i=0; i= 0 && m_ScriptSounds[i].Sound && m_ScriptSounds[i].EnvironmentGroup) { if(m_ScriptSounds[i].Entity) { // removed the assert and added if check to avoid setting the environment group, which should update automatically for peds and vehicles //naAssertf(!(m_ScriptSounds[i].Entity->GetIsTypePed() || m_ScriptSounds[i].Entity->GetIsTypeVehicle()), // "We shouldn't own an environmentGroup if we're playing off an entity that already has an environmentGroup. Sound: %s", m_ScriptSounds[i].Sound->GetName()); if(!(m_ScriptSounds[i].Entity->GetIsTypePed() || m_ScriptSounds[i].Entity->GetIsTypeVehicle())) { m_ScriptSounds[i].EnvironmentGroup->SetSource(m_ScriptSounds[i].Entity->GetTransform().GetPosition()); m_ScriptSounds[i].EnvironmentGroup->SetInteriorInfoWithEntity(m_ScriptSounds[i].Entity); } } else { m_ScriptSounds[i].EnvironmentGroup->SetSource(m_ScriptSounds[i].Sound->GetRequestedPosition()); } } #if GTA_REPLAY if(CReplayMgr::ShouldRecord() && m_ScriptSounds[i].Sound && m_ScriptSounds[i].ShouldRecord) { RecordUpdateScriptSlotSound(m_ScriptSounds[i].SoundSetHash, m_ScriptSounds[i].SoundNameHash, &m_ScriptSounds[i].InitParams, m_ScriptSounds[i].Entity, m_ScriptSounds[i].Sound); } if(CReplayMgr::ShouldRecord() && m_ScriptSounds[i].Sound && (m_ScriptSounds[i].VariableNameHash[0] != g_NullSoundHash || m_ScriptSounds[i].VariableNameHash[1] != g_NullSoundHash)) { CReplayMgr::RecordPersistantFx( CPacketScriptSetVarOnSound(m_ScriptSounds[i].VariableNameHash, m_ScriptSounds[i].VariableValue), CTrackedEventInfo(m_ScriptSounds[i].Sound)); } #endif } #if GTA_REPLAY if(CReplayMgr::ShouldRecord() && m_StreamSound && (m_StreamVariableNameHash[0] != g_NullSoundHash || m_StreamVariableNameHash[1] != g_NullSoundHash)) { CReplayMgr::RecordPersistantFx( CPacketScriptSetVarOnSound(m_StreamVariableNameHash, m_StreamVariableValue), CTrackedEventInfo(m_StreamSound)); } #if GTA_REPLAY if(CReplayMgr::ShouldRecord() && m_StreamSound && !m_DisableReplayScriptStreamRecording) { u32 currentOffset = m_StreamSound->GetCurrentPlayTime(); switch(m_StreamType) { case AUD_PLAYSTREAM_ENTITY: // Stream tracker entity may have been destroyed since the stream started, in which case it isn't valid to record a packet referencing it. if(m_StreamTrackerEntity) { CReplayMgr::ReplayRecordPlayStreamFromEntity(m_StreamTrackerEntity, m_StreamSound, sm_CurrentStreamName, currentOffset, sm_CurrentSetName); } break; case AUD_PLAYSTREAM_POSITION: CReplayMgr::ReplayRecordPlayStreamFromPosition(m_StreamPosition, m_StreamSound, sm_CurrentStreamName, currentOffset, sm_CurrentSetName); break; case AUD_PLAYSTREAM_FRONTEND: CReplayMgr::ReplayRecordPlayStreamFrontend(m_StreamSound, sm_CurrentStreamName, currentOffset, sm_CurrentSetName); break; default: break; } } #endif #endif } void audScriptAudioEntity::HandleScriptAudioEvent(u32 hash, CEntity* entity) { audScriptAudioEntity::sm_ScriptActionTable.InvokeActionHandler(hash, entity); } void audScriptAudioEntity::DefaultScriptActionHandler(u32 hash, void *context) { CEntity* entity = (CEntity*)context; naEnvironmentGroup* environmentGroup = NULL; audTracker* tracker = NULL; if (entity) { switch (entity->GetType()) { case ENTITY_TYPE_PED: { CPed* ped = (CPed*) entity; environmentGroup = ped->GetPedAudioEntity()->GetEnvironmentGroup(true); tracker = ((CPed*)entity)->GetPlaceableTracker(); break; } case ENTITY_TYPE_VEHICLE: { CVehicle* vehicle = (CVehicle*) entity; environmentGroup = vehicle->GetVehicleAudioEntity()->GetEnvironmentGroup(); tracker = ((CVehicle*)entity)->GetPlaceableTracker(); break; } case ENTITY_TYPE_OBJECT: { environmentGroup = g_ScriptAudioEntity.CreateScriptEnvironmentGroup(entity, entity->GetTransform().GetPosition()); tracker = ((CObject*)entity)->GetPlaceableTracker(); break; } } } // As a default, play the sound, with occlusion group and tracker, where we have one. audSoundInitParams initParams; initParams.EnvironmentGroup = environmentGroup; initParams.Tracker = tracker; g_ScriptAudioEntity.CreateAndPlaySound(hash, &initParams); } void audScriptAudioEntity::UpdateCollisionInfo(u32 timeInMs) { if(m_TimeToLowerCollisionLevel != 0 && g_AudioEngine.GetTimeInMilliseconds() > m_TimeToLowerCollisionLevel) { switch(m_ColLevel) { case AUD_INTERRUPT_LEVEL_COLLISION_HIGH: m_ColLevel = AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM; m_TimeToLowerCollisionLevel += 10000; break; case AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM: m_ColLevel = AUD_INTERRUPT_LEVEL_COLLISION_LOW; m_TimeToLowerCollisionLevel += 10000; break; case AUD_INTERRUPT_LEVEL_COLLISION_LOW: m_ColLevel = AUD_INTERRUPT_LEVEL_COLLISION_NONE; m_TimeToLowerCollisionLevel = 0; break; default: break; } } //Only do this check a couple times a second, and only if we've hit a ped recently if(m_TimeOfLastCarPedCollision != 0 && fwTimer::GetFrameCount() % 15 == 0 && timeInMs - m_TimeLastTriggeredPedCollisionScreams > g_TimeToWaitBetweenRunnedOverReactions ) { u8 numPedsHitRecently= 0; for(int i=0; i timeInMs - g_TimespanToHitPedForRunnedOverReaction) numPedsHitRecently++; } if(numPedsHitRecently >= g_MinPedsHitForRunnedOverReaction) { audSpeechAudioEntity::TriggerCollisionScreams(); m_TimeLastTriggeredPedCollisionScreams = timeInMs; } } } void audScriptAudioEntity::ProcessScriptedConversation(u32 timeInMs) { #if !__FINAL if(m_ShouldSetTimeTriggeredSpeechPlayedStat) { PF_SET(TimeTilTriggeredSpeechPlayed, m_TimeTriggeredSpeechPlayed - m_TimeSpeechTriggered); m_ShouldSetTimeTriggeredSpeechPlayedStat = false; } #endif #if __BANK if(NetworkInterface::IsGameInProgress() && m_PlayingScriptedConversationLine >= 0 && !m_ScriptedConversation[m_PlayingScriptedConversationLine].DisplayedDebug) { m_ScriptedConversation[m_PlayingScriptedConversationLine].DisplayedDebug = true; Displayf("[Conv Spew] ProcessScriptedConversation %s", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); } #endif if(m_ScriptedConversationOverlapTriggerTime == 0 && m_OverlapTriggerTimeAdjustedForPredelay && !m_InterruptTriggered) { m_ScriptedConversationOverlapTriggerTime = timeInMs; } m_DuckingForScriptedConversation = false; // only set later if we're saying something //char convDebug[128] = ""; //formatf(convDebug, sizeof(convDebug), "Processing: line: %d; preload: %d; highestLoaded: %d", m_PlayingScriptedConversationLine, m_PreLoadingSpeechLine, m_HighestLoadedLine); //grcDebugDraw::PrintToScreenCoors(convDebug,15,15); if (g_AudioEngine.GetSoundManager().IsPaused(4)) { if(m_ScriptedConversationOverlapTriggerTime != 0 && m_TimeTrueOverlapWasSet == 0) m_ScriptedConversationOverlapTriggerTime += timeInMs - m_TimeLastFrame; return; } if(m_ExternalStreamConversationSFXPlaying) { if(m_ScriptedConversationSfx) return; else { m_ExternalStreamConversationSFXPlaying = false; } } // Block peds ambient anims, as appropriate for (s32 i=0; i timeInMs) { m_Ped[i]->SetAmbientAnimsBlocked(true); } else if (m_BlockAnimTime[i] > 0) { m_Ped[i]->SetAmbientAnimsBlocked(true); } } } if(m_TimeToInterrupt != 0 && m_TimeToInterrupt < timeInMs) { naConvDebugf1("Pausing/Interrupting conversation - m_TimeToInterrupt (%u) < timeInMs (%u)", m_TimeToInterrupt, timeInMs); PauseScriptedConversation(false, true); InterruptConversation(); } if(m_TimeToRestartConversation != 0 && m_TimeToRestartConversation < timeInMs && !ShouldWePauseScriptedConversation(timeInMs, true)) { naConvDebugf1("Restarting Conversation - m_TimeToRestartConversation (%u) < timeInMs (%u)", m_TimeToRestartConversation, timeInMs); //Now that we kill the conversation in ShouldWePause...if the car has been flipped or in the air too long, this may be false and we need // to return immediately. if(!m_ScriptedConversationInProgress) { return; } //adjust overlap time if(m_ScriptedConversationOverlapTriggerTime != 0 && m_TimeTrueOverlapWasSet == 0) { m_ScriptedConversationOverlapTriggerTime += timeInMs - m_TimeLastFrame; naConvDebugf1("Adding %u to make trigger time %u at point 1 %s %u", timeInMs - m_TimeLastFrame, m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } if(m_ColLevel == AUD_INTERRUPT_LEVEL_SLO_MO) { return; } RestartScriptedConversation(true); } if(m_InterruptTriggered && m_TimeToRestartConversation == 0) { audSpeechAudioEntity* speechAudioEntity = &m_SpeechAudioEntity; if( m_Interrupter && m_Interrupter->GetSpeechAudioEntity()) { speechAudioEntity = m_Interrupter->GetSpeechAudioEntity(); } if(speechAudioEntity) { if(m_CheckInterruptPreloading) { bool playTimeOverZero = false; if(!speechAudioEntity->IsPreloadedSpeechReady() && timeInMs - m_InterruptSpeechRequestTime > g_TimeToWaitForInterruptToPrepare) { naWarningf("Interrupt speech taking too long to load. Killing it and moving on with the conversation."); speechAudioEntity->StopSpeech(); m_ScriptedConversationOverlapTriggerTime = timeInMs; m_TimeToRestartConversation = timeInMs; return; } else if(speechAudioEntity->IsPreloadedSpeechPlaying(&playTimeOverZero) && playTimeOverZero) { m_CheckInterruptPreloading = false; } } if(!speechAudioEntity->IsAmbientSpeechPlaying() && !ShouldWePauseScriptedConversation(timeInMs, true)) { m_ScriptedConversationOverlapTriggerTime = timeInMs; m_TimeToRestartConversation = timeInMs; return; } } else { m_InterruptTriggered = false; } } if(g_AdjustOverlapForLipsyncPredelay && m_PlayingScriptedConversationLine >= 0) { AdjustOverlapForLipsyncPredelay(timeInMs); } // don't play for a wee while after the last conversation - 250ms - this should stop any wave slot batchload asserts // also allows a wee bit of time - 250ms - for ambient anim blocking. if (timeInMs < m_NextTimeScriptedConversationCanStart) { return; } // Also wait until any of the chars have stopped speaking - this'll just be ambient speech - and add on a wee bit of time after that too. for (s32 i=0; iGetSpeechAudioEntity() && m_Ped[i]->GetSpeechAudioEntity()->IsAmbientSpeechPlaying()) { m_NextTimeScriptedConversationCanStart = Max(m_NextTimeScriptedConversationCanStart, timeInMs+250); return; } if (m_Ped[i]->GetSpeechAudioEntity() && m_Ped[i]->GetSpeechAudioEntity()->IsPainPlaying()) { if( m_PlayingScriptedConversationLine == -1 || (m_PlayingScriptedConversationLine >= 0 && strcmp(m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, "heat_CMAC") != 0)) // there is a dude rag dolling in pain screaming that's fucking shit up { // if it's the next ped to speak, we don't want to say anything for a good few seconds s16 nextLineToPlay = m_PlayingScriptedConversationLine+1; if (nextLineToPlay >= 0 && nextLineToPlay < AUD_MAX_SCRIPTED_CONVERSATION_LINES) { if (m_ScriptedConversation[nextLineToPlay].Speaker == i) { m_NextTimeScriptedConversationCanStart = Max(m_NextTimeScriptedConversationCanStart, timeInMs+3000); ShouldWePauseScriptedConversation(timeInMs); //Call this so that we pause if it's ragdoll pain, like getting thrown from a bike return; } } } } } } /* u32 zeroBlocked = 0; u32 oneBlocked = 0; if (m_Ped[0] && m_BlockAnimTime[0] > timeInMs) { zeroBlocked = 1; } if (m_Ped[1] && m_BlockAnimTime[1] > timeInMs) { oneBlocked = 1; } char convDebug[128] = ""; formatf(convDebug, sizeof(convDebug), "%d:%d", zeroBlocked, oneBlocked); grcDebugDraw::PrintToScreenCoors(convDebug,35,35); */ // see if we're in an upside down car CVehicle* playerVehicle = CGameWorld::FindLocalPlayerVehicle(); if (playerVehicle && playerVehicle->IsUpsideDown()&& playerVehicle->GetVehicleType()!=VEHICLE_TYPE_BOAT) { m_CarUpsideDownTimer += fwTimer::GetTimeStepInMilliseconds(); } else { m_CarUpsideDownTimer = 0; } // See if there's a good reason why we should abort if (ShouldWeAbortScriptedConversation()) { #if __BANK if(NetworkInterface::IsGameInProgress() && m_PlayingScriptedConversationLine >= 0) { Displayf("[Conv Spew] AbortScriptedConversation %s", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); } #endif AbortScriptedConversation(false BANK_ONLY(,"reason above.")); // think it's best to return here - we clean everything up in FinishConversation anyway, and don't want to do any preloading return; } // See if there's a reason to pause the conversation - we do this instead of aborting most times now, and simply return, so we // don't kick off another line if (ShouldWePauseScriptedConversation(timeInMs)) { return; } // If we're currently pausing the conversation, just do nothing at all. We don't update anything normally, so if we're still // playing the line we called pause on, it'll merrily carry on playing. if (m_PauseConversation) { // formatf(convDebug, sizeof(convDebug), "Paused"); // grcDebugDraw::PrintToScreenCoors(convDebug,20,20); // We also need to cancel subtitles if no lines are playing. // In the case where a conv is paused, but the line allowed to play out, this is the only place we're able to detect that. if (m_PlayingScriptedConversationLine>=0) // Check we've actually started the conversation { // See if they're still talking if (m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker>=0) { if(m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker] && m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()) { if (!m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()->IsScriptedSpeechPlaying()) { m_PauseWaitingOnCurrentLine = false; if(m_ConversationDuckingScene) m_ConversationDuckingScene->SetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 0.f); if (m_LastDisplayedTextBlock>0 && (m_PlayingScriptedConversationLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES-1 || !m_ScriptedConversation[m_PlayingScriptedConversationLine+1].DoNotRemoveSubtitle)) { #if __BANK sm_ScriptedLineDebugInfo[0] = '\0'; #endif CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(m_LastDisplayedTextBlock, false); m_LastDisplayedTextBlock = -1; // to avoid trying to cancel the same subtitle repeatedly. // Don't think losing our memory of playing it will ever be a problem. } } } else if(!m_SpeechAudioEntity.IsScriptedSpeechPlaying()) { m_PauseWaitingOnCurrentLine = false; if(m_ConversationDuckingScene) m_ConversationDuckingScene->SetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 0.f); if (m_LastDisplayedTextBlock>0 && (m_PlayingScriptedConversationLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES-1 || !m_ScriptedConversation[m_PlayingScriptedConversationLine+1].DoNotRemoveSubtitle)) { #if __BANK sm_ScriptedLineDebugInfo[0] = '\0'; #endif CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(m_LastDisplayedTextBlock, false); m_LastDisplayedTextBlock = -1; // to avoid trying to cancel the same subtitle repeatedly. // Don't think losing our memory of playing it will ever be a problem. } } } } else { if(!HandleCallToPreloading(timeInMs, true)) return; } return; } m_DuckingForScriptedConversation = true; // canceled later if we're having a wee break between lines - doing it here gives us a little // If we're waiting on something to be preloaded, keep preloading until we're done naConvDebugf1("Processing: line: %d; preload: %d; highestLoaded: %d highestLoading %d", m_PlayingScriptedConversationLine, m_PreLoadingSpeechLine, m_HighestLoadedLine, m_HighestLoadingLine); if(!HandleCallToPreloading(timeInMs)) { return; } if(m_PlayingScriptedConversationLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES) { naConvAssertf(0,"Too many lines in this conversation. Aborting. First line: %s", m_ScriptedConversation[0].Context); #if __BANK if(NetworkInterface::IsGameInProgress() && m_PlayingScriptedConversationLine >= 0) { Displayf("[Conv Spew] AbortScriptedConversation - Too many lines %s", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); } #endif AbortScriptedConversation(false BANK_ONLY(,"reason above.")); return; } // Currently playing line m_PlayingScriptedConversationLine // Total quick fudge, as we need to do all this stuff properly and differently later anyway. // For now, just see if we're currently playing our one sound - if not (it's been nulled) we see // if there's more to come, and play the next one if there is. // We now need to see if the previous line has finished - which is now being played via one of // two different audio entities. bool playingDialogue = false; s32 playTime = -1; s32 length = -1; if (m_PlayingScriptedConversationLine>=0 && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0) // Check we've actually started the conversation { // See if they're still talking if (m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker] && m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()) { playingDialogue = m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()->IsScriptedSpeechPlaying(&length, &playTime); } else { playingDialogue = m_SpeechAudioEntity.IsScriptedSpeechPlaying(&length, &playTime); // Sanity check that if we're playing frontend speech, not from a ped, that it doesn't have speaker gestures #if !__FINAL if (playingDialogue) { u32 speakerGestureHash; u32 speakerGestureDuration; if(m_SpeechAudioEntity.GetLastMetadataHash(AUD_SPEECH_METADATA_CATEGORY_GESTURE_SPEAKER, speakerGestureHash, speakerGestureDuration)) { // We've got a speaker gesture on frontend speech - this is likely a bug naConvWarningf("Speaker-gestured frontend speech: line: %d; voice: %s; context: %s", m_PlayingScriptedConversationLine, m_VoiceName[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); naConvErrorf("Gestured speech playing frontend - please assign bug to level designer"); } } #endif } // if we're playing dialogue, print some debug info on how far through we are if (playingDialogue) { // char convDebug[128] = ""; // formatf(convDebug, sizeof(convDebug), "length: %d; playtime: %d", length, playTime); // grcDebugDraw::PrintToScreenCoors(convDebug,25,25); // See if we're about to finish, and make sure the next speaker (if not us) has their ambient anims disabled, // by setting their off time to a bit ahead. Current speaker's always a bit ahead, so the general setting code will sort it naConvAssertf(m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker>=0 && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker-1 && playTime>-1 && ((length-playTime) < g_StopAmbientAnimTime)) { // Mark our next speaker to not do ambient anims for a while, and us too. s32 nextLine = m_PlayingScriptedConversationLine+1; if (nextLine= 0) { m_BlockAnimTime[m_ScriptedConversation[nextLine].Speaker] = timeInMs + g_StopAmbientAnimTime; } } if(playTime>0 && !m_HasShownSubtitles) { ShowSubtitles(); m_HasShownSubtitles = true; } if(g_AdjustTriggerTimeForZeroPlaytime && playTime == 0 && m_ScriptedConversationOverlapTriggerTime != 0 && (m_TimeTrueOverlapWasSet == 0 || g_AdjustTriggerForZeroEvenIfMarkedOverlap)) { m_ScriptedConversationOverlapTriggerTime += timeInMs - m_TimeLastFrame; naConvDebugf1("Adding %u to make trigger time %u at point 10 %s %u", timeInMs - m_TimeLastFrame, m_ScriptedConversationOverlapTriggerTime, m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, g_AudioEngine.GetTimeInMilliseconds()); } } // We're also playing dialogue if we're playing an sfx if (m_ScriptedConversationSfx) { playingDialogue = true; } } // if we've been requested to abort, and have finished playing, stop the conversation. if (m_AbortScriptedConversationRequested && !playingDialogue) { #if __BANK if(NetworkInterface::IsGameInProgress() && m_PlayingScriptedConversationLine >= 0) { Displayf("[Conv Spew] m_AbortScriptedConversationRequested %s", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); } #endif FinishScriptedConversation(); return; } bool animTriggersControllingConv = m_AnimTriggersControllingConversation BANK_ONLY(|| g_AnimTriggersControllingConversation); // We might also want to play the next line because we've got an overlap - explicitly check for that, and flag it. bool timeToOverlap = false; if (!animTriggersControllingConv && !m_AbortScriptedConversationRequested && m_OverlapTriggerTimeAdjustedForPredelay && !m_TriggeredOverlapSpeech && m_ScriptedConversationOverlapTriggerTime > 0 && m_ScriptedConversationOverlapTriggerTime=timeInMs) { formatf(convDebug, sizeof(convDebug), "delay"); grcDebugDraw::PrintToScreenCoors(convDebug,30,30); } */ bool playNextLine = false; if(animTriggersControllingConv) { if(m_AnimTriggerReported) { Assertf(m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES, "Too many triggers in anim triggered conversation. Going to overrun the conversation array."); s16 nextNonSFXLine = m_PlayingScriptedConversationLine+1; for( ; nextNonSFXLine= 0 ? m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker].Get() : NULL; Assertf(ped, "NULL ped conversation speaker in anim-triggered conversation."); if (ped && ped->GetSpeechAudioEntity()) { if(!ped->GetSpeechAudioEntity()->IsScriptedSpeechPlaying(&length, &playtime)) { naConvDebugf1("Reacting to m_AnimTriggerReported 3. %u", g_AudioEngine.GetTimeInMilliseconds()); playNextLine = true; m_AnimTriggerReported = false; } else { ped->GetSpeechAudioEntity()->OrphanScriptedSpeechEcho(); //make sure we ignore lipsync predelay on the next line, for stitching purposes m_TreatNextLineToPlayAsUrgent = true; naConvDebugf1("Play next line is true, but returning early 2. %u", g_AudioEngine.GetTimeInMilliseconds()); return; } } } } //make sure if the last line triggered was the last line of the conversation, we make sure to finish the conversation once the line // is over else if((m_PlayingScriptedConversationLine+1 < AUD_MAX_SCRIPTED_CONVERSATION_LINES) && m_PlayingScriptedConversationLine >= 0 && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0 && ((m_ScriptedConversation[m_PlayingScriptedConversationLine + 1].Speaker) < 0) ) { CPed* ped = m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]; Assertf(ped, "NULL ped conversation speaker in anim-triggered conversation."); if (ped && ped->GetSpeechAudioEntity() && !ped->GetSpeechAudioEntity()->IsScriptedSpeechPlaying()) { naConvDebugf1("Reacting to m_AnimTriggerReported 4. %u", g_AudioEngine.GetTimeInMilliseconds()); playNextLine = true; m_AnimTriggerReported = false; } } } else { if(m_PlayingScriptedConversationLine < 0) { if(IsContextAnSFXCommand(m_ScriptedConversation[0].Context)) { u32 pauseTime = GetPauseTimeForConversationLine(&(m_ScriptedConversation[0].Context[4])); if(pauseTime > 0) { m_PlayingScriptedConversationLine++; m_OverlapTriggerTimeAdjustedForPredelay = true; m_ScriptedConversationOverlapTriggerTime = timeInMs + pauseTime; } else playNextLine = true; } else playNextLine = true; } else playNextLine = m_PlayingScriptedConversationLine < 0 || timeToOverlap; } if (playNextLine) { naConvDisplayf("Play next line is true. %u", g_AudioEngine.GetTimeInMilliseconds()); // We're not playing anything, so if we were, remove any listeners - I think this is the safest easiest place to do this. // Because we sometimes cheat and advance on m_PlayingScriptedConversationLine, just go through them all all remove ANY. // Note that we now might be playing stuff, as this could be an overlap line. for (s32 i=0; iSetSpeakerListenedTo(NULL); m_Ped[i]->SetGlobalSpeakerListenedTo(NULL); m_Ped[i]->SetSpeakerListenedToSecondary(NULL); } } // The line we were just playing's finished, so see if we've more to play m_PlayingScriptedConversationLine++; // store a local flag that we've kicked off a sfx, so things don't break if there isn't the right sound to play bool justTriggeredSFX = false; if ((m_PlayingScriptedConversationLine= 0)) { bool isSFXContext = IsContextAnSFXCommand(m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); if ( isSFXContext && GetOverlapOffsetTime(&m_ScriptedConversation[m_PlayingScriptedConversationLine].Context[4]) < 0 && GetPauseTimeForConversationLine(&m_ScriptedConversation[m_PlayingScriptedConversationLine].Context[4]) <= 0) { // It's not a pause command, so play a sound of the same name, from the ped if we have one audSoundInitParams initParams; if (m_ScriptedConversation[m_PlayingScriptedConversationLine].Listener < AUD_MAX_SCRIPTED_CONVERSATION_SPEAKERS && m_ScriptedConversation[m_PlayingScriptedConversationLine].Listener >= 0) { // We have a listener id - do we have a real ped CPed* listenerPed = m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Listener]; if (listenerPed) { initParams.Tracker = listenerPed->GetPlaceableTracker(); } } if( (m_HeadSetBeepOnLineIndex == m_PlayingScriptedConversationLine) && !IsFlagSet(audScriptAudioFlags::DisableHeadsetOnBeep) ) { CPed* ped = m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]; if (ped && ped->GetSpeechAudioEntity()) { audSoundInitParams beepInitParams; beepInitParams.Pan = 0; Vector3 speakerPos = Vector3(0.f,0.f,0.f); if(ped->GetSpeechAudioEntity()->GetPositionForSpeechSound(speakerPos,(audSpeechVolume)m_ScriptedConversation[m_PlayingScriptedConversationLine].Volume, m_NullPedSpeakerLocations[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_NullPedSpeakerEntities[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker])) { f32 distanceToPed = Max((VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition()) - speakerPos).Mag(),0.f); beepInitParams.Volume = ped->GetSpeechAudioEntity()->ComputePositionedHeadsetVolume(ped, AUD_SPEECH_TYPE_SCRIPTED,distanceToPed,true); m_HeadSetBeepOffCachedVolume = beepInitParams.Volume; } CreateAndPlaySound(ATSTRINGHASH("DLC_HEISTS_HEADSET_CALL_START_BEEP", 0xE3C11EED),&beepInitParams); initParams.Predelay = 300; } } if(!naConvVerifyf(!m_ScriptedConversationSfx, "Trying to stop scripted sfx but there isn't any")) { m_ScriptedConversationSfx->StopAndForget(); } CreateAndPlaySound_Persistent(&m_ScriptedConversation[m_PlayingScriptedConversationLine].Context[4], &m_ScriptedConversationSfx, &initParams); if(CReplayMgr::ShouldRecord()) { char context[64]; strncpy(context, m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, 10); context[6] = 0; if(atStringHash(context) != ATSTRINGHASH("EXPOW_", 0x9DB49833)) { RecordUpdateScriptSlotSound(g_NullSoundHash, atStringHash(&m_ScriptedConversation[m_PlayingScriptedConversationLine].Context[4]), &initParams, NULL, m_ScriptedConversationSfx); } } justTriggeredSFX = true; m_ScriptedConversationOverlapTriggerTime = 0; //We'll return early as long as the conversation SFX sound isn't null m_OverlapTriggerTimeAdjustedForPredelay = false; m_TriggeredOverlapSpeech = false; // Now see if the next line is an overlap, and set our overlap time appropriately //every line will now be treated as if it were overlapping, to compensate for lipsync warmup time. s32 overlapTime = 0; u32 nextLine = m_PlayingScriptedConversationLine+1; if ((nextLine= 0)) { if (IsContextAnSFXCommand(m_ScriptedConversation[nextLine].Context)) { overlapTime = GetOverlapOffsetTime(&m_ScriptedConversation[nextLine].Context[4]); } } if(overlapTime > 0) { m_ScriptedConversationOverlapTriggerTime = timeInMs + (u32)overlapTime; m_TimeTrueOverlapWasSet = timeInMs; naConvDebugf1("Setting trigger time to %u with overlap %u %s %u", m_ScriptedConversationOverlapTriggerTime, (u32)overlapTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } else { m_ScriptedConversationOverlapTriggerTime = 0; } } else if(m_PlayingScriptedConversationLine > 0) { if(isSFXContext) //must be overlap or pause, which we've already accounted for, but still need to increment line m_PlayingScriptedConversationLine++; if(m_PlayingScriptedConversationLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES) { #if __BANK if(NetworkInterface::IsGameInProgress() && m_PlayingScriptedConversationLine >= 0) { Displayf("[Conv Spew] AbortScriptedConversation - Too many lines %s", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); } #endif naConvAssertf(0,"Too many lines in this conversation. Aborting. First line: %s", m_ScriptedConversation[0].Context); AbortScriptedConversation(false BANK_ONLY(,"reason above.")); return; } naConvAssertf(!IsContextAnSFXCommand(m_ScriptedConversation[m_PlayingScriptedConversationLine].Context), "Two SFX contexts found in a row. This could mess up conversation logic."); //since we now have to overlap all lines to compensate for lipsync predelay, things get a little crazy when the same speaker // has two lines in a row. So if they do and they are still talking, do this CPed* nextSpeakingPed = m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0 ? m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker].Get() : NULL; if( (nextSpeakingPed && nextSpeakingPed->GetSpeechAudioEntity() && nextSpeakingPed->GetSpeechAudioEntity()->IsScriptedSpeechPlaying()) || (!nextSpeakingPed && m_SpeechAudioEntity.IsScriptedSpeechPlaying()) ) { if(nextSpeakingPed && nextSpeakingPed->GetSpeechAudioEntity()) nextSpeakingPed->GetSpeechAudioEntity()->OrphanScriptedSpeechEcho(); m_PlayingScriptedConversationLine--; naConvDebugf1("Decrementing playing conversation line 1. %u", g_AudioEngine.GetTimeInMilliseconds()); if(isSFXContext) //undo having accounted for the pause/overlap { m_PlayingScriptedConversationLine--; naConvDebugf1("Decrementing playing conversation line 2. %u", g_AudioEngine.GetTimeInMilliseconds()); } //make sure we ignore lipsync predelay on the next line, for stitching purposes m_TreatNextLineToPlayAsUrgent = true; #if __BANK if(g_ConversationDebugSpew) { char voiceName[64] = "\0"; char waveName[64] = "\0"; if(nextSpeakingPed && nextSpeakingPed->GetSpeechAudioEntity()) { nextSpeakingPed->GetSpeechAudioEntity()->GetCurrentlyPlayingScriptedVoiceName(voiceName); nextSpeakingPed->GetSpeechAudioEntity()->GetCurrentlyPlayingWaveName(waveName); } else if(!nextSpeakingPed && m_SpeechAudioEntity.IsScriptedSpeechPlaying()) { m_SpeechAudioEntity.GetCurrentlyPlayingScriptedVoiceName(voiceName); m_SpeechAudioEntity.GetCurrentlyPlayingWaveName(waveName); } naDisplayf("[Conv Spew] Setting m_TreatNextLineToPlayAsUrgent == true. m_PlayingScriptedConversationLine: %d CurrentlyPlayingVoice: %s CurrentlyPlayingWave: %s", m_PlayingScriptedConversationLine, voiceName, waveName); } #endif m_ScriptedConversationOverlapTriggerTime += m_TimeElapsedSinceLastUpdate; naConvDebugf1("Play next line is true, but returning early 2. %u", g_AudioEngine.GetTimeInMilliseconds()); return; } } } if(!animTriggersControllingConv) { // if ( pauseTime>0 && !animTriggersControllingConv) // { // if (m_LastDisplayedTextBlock>0) // { // CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(m_LastDisplayedTextBlock, false); // } // // and stop ducking the radio for a wee bit // m_DuckingForScriptedConversation = false; //#if __BANK // if(g_ConversationDebugSpew) // naDisplayf("[Conv Spew] Play next line is true, but returning early 3. %u", g_AudioEngine.GetTimeInMilliseconds()); //#endif // return; // } // else if (justTriggeredSFX) { naConvDebugf1("Play next line is true, but returning early 4. %u", g_AudioEngine.GetTimeInMilliseconds()); return; } } if(justTriggeredSFX && animTriggersControllingConv) m_PlayingScriptedConversationLine++; if ((m_PlayingScriptedConversationLine= 0)) { // We've more to play if (g_AudioEngine.IsAudioEnabled() && (strcmp(m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, "")!=0)) { // See if we have more available already loaded - if not, kick off a bunch of loads, and wait until that's done if (m_HighestLoadedLine=0 && (m_PlayingScriptedConversationLine+1)= 0 && m_ScriptedConversation[m_PlayingScriptedConversationLine+1].Speaker < AUD_MAX_SCRIPTED_CONVERSATION_SPEAKERS) { m_BlockAnimTime[m_ScriptedConversation[m_PlayingScriptedConversationLine+1].Speaker] = timeInMs + g_StopAmbientAnimTime; } m_ScriptedConversationOverlapTriggerTime += m_TimeElapsedSinceLastUpdate; // formatf(convDebug, sizeof(convDebug), "Preload"); // grcDebugDraw::PrintToScreenCoors(convDebug,20,20); naConvDebugf1("Play next line is true, but returning early 5. %u", g_AudioEngine.GetTimeInMilliseconds()); return; // think this is fine - we'll just come in again next frame, and keep checking until it's loaded } // Work out who our listener is, if we have one CPed* listenerPed = NULL; if (m_ScriptedConversation[m_PlayingScriptedConversationLine].Listener < AUD_MAX_SCRIPTED_CONVERSATION_SPEAKERS && m_ScriptedConversation[m_PlayingScriptedConversationLine].Listener >= 0) { // We have a listener id - do we have a real ped listenerPed = m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Listener]; } // See what variation we're playing, a use that for subtitling // u32 context=0, fullContext=0; // Find the appropriate speech audio entity if (m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0 && m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]) { CPed* ped = m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]; if (m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex > -1 && ped->GetSpeechAudioEntity()) { s32 predelay = 0; if( (m_HeadSetBeepOnLineIndex == m_PlayingScriptedConversationLine) && !IsFlagSet(audScriptAudioFlags::DisableHeadsetOnBeep) ) { audSoundInitParams beepInitParams; beepInitParams.Pan = 0; Vector3 speakerPos = Vector3(0.f,0.f,0.f); if(ped->GetSpeechAudioEntity()->GetPositionForSpeechSound(speakerPos,(audSpeechVolume)m_ScriptedConversation[m_PlayingScriptedConversationLine].Volume, m_NullPedSpeakerLocations[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_NullPedSpeakerEntities[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker])) { f32 distanceToPed = Max((VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition()) - speakerPos).Mag(),0.f); beepInitParams.Volume = ped->GetSpeechAudioEntity()->ComputePositionedHeadsetVolume(ped, AUD_SPEECH_TYPE_SCRIPTED,distanceToPed,true); m_HeadSetBeepOffCachedVolume = beepInitParams.Volume; } CreateAndPlaySound(ATSTRINGHASH("DLC_HEISTS_HEADSET_CALL_START_BEEP", 0xE3C11EED),&beepInitParams); predelay = 300; } m_ScriptedConversationVariation = m_ScriptedSpeechSlot[m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex][m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet].VariationNumber; ped->GetSpeechAudioEntity()->PlayScriptedSpeech(m_VoiceName[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, m_ScriptedConversationVariation, m_ScriptedSpeechSlot[m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex][m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet].WaveSlot, (audSpeechVolume)m_ScriptedConversation[m_PlayingScriptedConversationLine].Volume, false, (m_ShouldRestartConvAtMarker && m_LineCheckedForRestartOffset == m_PlayingScriptedConversationLine) ? m_SpeechRestartOffset : 0, m_NullPedSpeakerLocations[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_NullPedSpeakerEntities[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], 0, m_ScriptedConversation[m_PlayingScriptedConversationLine].Audibility, m_ScriptedConversation[m_PlayingScriptedConversationLine].Headset, m_ScriptedSpeechSlot[m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex][m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet].AssociatedClip, m_ScriptedConversation[m_PlayingScriptedConversationLine].isPadSpeakerRoute, predelay ); m_OverlapTriggerTimeAdjustedForPredelay = m_ShouldRestartConvAtMarker && m_LineCheckedForRestartOffset == m_PlayingScriptedConversationLine && m_PlayingScriptedConversationLine + 1 < AUD_MAX_SCRIPTED_CONVERSATION_LINES && GetOverlapOffsetTime(&(m_ScriptedConversation[m_PlayingScriptedConversationLine + 1].Context[4])) >= 0; m_MarkerPredelayApplied = m_OverlapTriggerTimeAdjustedForPredelay ? m_SpeechRestartOffset : 0; m_SpeechRestartOffset = 0; m_LineCheckedForRestartOffset = -1; m_LastSpeakingPed = ped; m_TriggeredOverlapSpeech = true; m_TreatNextLineToPlayAsUrgent = false; m_FirstLineHasStarted = true; #if __BANK if(NetworkInterface::IsGameInProgress() && m_PlayingScriptedConversationLine >= 0) { Displayf("[Conv Spew] 1.Playing conversation %s", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); } #endif naConvDisplayf("Playing line conversation %s %u", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, g_AudioEngine.GetTimeInMilliseconds()); #if !__FINAL m_TimeSpeechTriggered = timeInMs; #endif if( ( m_CloneConversation ) && ( NetworkInterface::IsGameInProgress() ) ) { #if __BANK if(NetworkInterface::IsGameInProgress() && m_PlayingScriptedConversationLine >= 0) { Displayf("[Conv Spew] Clone conversation %s %d", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, m_ScriptedConversationVariation); } #endif // See what variation number we just played, and send that across the network too. CPedConversationLineEvent::Trigger( ped, ped->GetSpeechAudioEntity()->GetCurrentlyPlayingVoiceNameHash(), m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, m_ScriptedConversationVariation, true ); } if (ped->IsLocalPlayer() && !m_ScriptedConversation[m_PlayingScriptedConversationLine].Headset && !g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::DoNotBlipScriptedSpeech)) { CMiniMap::CreateSonarBlipAndReportStealthNoise(ped, ped->GetTransform().GetPosition(), CMiniMap::sm_Tunables.Sonar.fSoundRange_ObjectCollision, HUD_COLOUR_BLUEDARK); } } else { m_ScriptedConversationVariation = 0; } // If we have a listener, make them listen to this ped if (listenerPed) { listenerPed->SetSpeakerListenedTo((CEntity*)ped); } // Mark the other peds in the same vehicle as the secondary listener. if(ped->GetIsInVehicle()) { CVehicle* pSpeakerVehicle = ped->GetMyVehicle(); for(int i = 0; i < pSpeakerVehicle->GetSeatManager()->GetMaxSeats(); i++) { CPed* pPedInVehicle = pSpeakerVehicle->GetSeatManager()->GetPedInSeat(i); if(pPedInVehicle && pPedInVehicle != ped && pPedInVehicle!= listenerPed) { pPedInVehicle->SetSpeakerListenedToSecondary((CEntity*)ped); } } } } else { if (m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex > -1) { s32 predelay = 0; if( (m_HeadSetBeepOnLineIndex == m_PlayingScriptedConversationLine) && !IsFlagSet(audScriptAudioFlags::DisableHeadsetOnBeep) ) { audSoundInitParams beepInitParams; beepInitParams.Pan = 0; Vector3 speakerPos = Vector3(0.f,0.f,0.f); if(m_SpeechAudioEntity.GetPositionForSpeechSound(speakerPos,(audSpeechVolume)m_ScriptedConversation[m_PlayingScriptedConversationLine].Volume, m_NullPedSpeakerLocations[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_NullPedSpeakerEntities[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker])) { f32 distanceToPed = Max((VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition()) - speakerPos).Mag(),0.f); beepInitParams.Volume = m_SpeechAudioEntity.ComputePositionedHeadsetVolume(nullptr, AUD_SPEECH_TYPE_SCRIPTED,distanceToPed,true); m_HeadSetBeepOffCachedVolume = beepInitParams.Volume; } CreateAndPlaySound(ATSTRINGHASH("DLC_HEISTS_HEADSET_CALL_START_BEEP", 0xE3C11EED),&beepInitParams); predelay = 300; } m_ScriptedConversationVariation = m_ScriptedSpeechSlot[m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex][m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet].VariationNumber; m_SpeechAudioEntity.PlayScriptedSpeech(m_VoiceName[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, m_ScriptedConversationVariation, m_ScriptedSpeechSlot[m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex][m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet].WaveSlot, (audSpeechVolume)m_ScriptedConversation[m_PlayingScriptedConversationLine].Volume, false, (m_ShouldRestartConvAtMarker && m_LineCheckedForRestartOffset == m_PlayingScriptedConversationLine) ? m_SpeechRestartOffset : 0, m_NullPedSpeakerLocations[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_NullPedSpeakerEntities[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], 0, m_ScriptedConversation[m_PlayingScriptedConversationLine].Audibility, m_ScriptedConversation[m_PlayingScriptedConversationLine].Headset, m_ScriptedSpeechSlot[m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex][m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet].AssociatedClip, m_ScriptedConversation[m_PlayingScriptedConversationLine].isPadSpeakerRoute, predelay ); m_OverlapTriggerTimeAdjustedForPredelay = m_ShouldRestartConvAtMarker && m_LineCheckedForRestartOffset == m_PlayingScriptedConversationLine && m_PlayingScriptedConversationLine + 1 < AUD_MAX_SCRIPTED_CONVERSATION_LINES && GetOverlapOffsetTime(&(m_ScriptedConversation[m_PlayingScriptedConversationLine + 1].Context[4])) >= 0; m_MarkerPredelayApplied = m_OverlapTriggerTimeAdjustedForPredelay ? m_SpeechRestartOffset : 0; m_SpeechRestartOffset = 0; m_LineCheckedForRestartOffset = -1; m_LastSpeakingPed = NULL; m_TriggeredOverlapSpeech = true; m_TreatNextLineToPlayAsUrgent = false; m_FirstLineHasStarted = true; #if __BANK if(NetworkInterface::IsGameInProgress() && m_PlayingScriptedConversationLine >= 0) { Displayf("[Conv Spew] 2.Playing conversation %s", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context); } #endif naConvDisplayf("Playing line conversation %s %u", m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, g_AudioEngine.GetTimeInMilliseconds()); #if !__FINAL m_TimeSpeechTriggered = timeInMs; #endif } else { m_ScriptedConversationVariation = 0; } // If we have a listener, make them listen to the global speech entity if (listenerPed) { // We only have one ped, so let's make that one listen to the global speech audio entity listenerPed->SetGlobalSpeakerListenedTo(&m_SpeechAudioEntity); } } // Now see if the next line is an overlap, and set our overlap time appropriately //every line will now be treated as if it were overlapping, to compensate for lipsync warmup time. s32 overlapTime = 0; u32 nextLine = m_PlayingScriptedConversationLine+1; if ((nextLine= 0)) { if (IsContextAnSFXCommand(m_ScriptedConversation[nextLine].Context)) { overlapTime = GetOverlapOffsetTime(&m_ScriptedConversation[nextLine].Context[4]); } } if(overlapTime > 0) { m_TimeTrueOverlapWasSet = timeInMs; m_ScriptedConversationOverlapTriggerTime = timeInMs + (u32)overlapTime; } else { m_ScriptedConversationOverlapTriggerTime = 0; m_TimeTrueOverlapWasSet = 0; } m_OverlapTriggerTimeAdjustedForPredelay = false; m_TriggeredOverlapSpeech = false; } else { m_ScriptedConversationNoAudioDelay = (timeInMs + 5000); } m_HasShownSubtitles = false; if (m_ScriptedConversationVariation==0) { // We didn't find any valid line - this is probably just because the speech isn't yet recorded/generated, // so just use the first one, and act as if we're running with -noaudio m_ScriptedConversationVariation = 1; m_ScriptedConversationOverlapTriggerTime = (timeInMs + 3000); m_ScriptedConversationNoAudioDelay = (timeInMs + 3000); m_OverlapTriggerTimeAdjustedForPredelay = true; ShowSubtitles(true); m_HasShownSubtitles = true; } } else { // We don't have any more speech to play // Call our tidy up fn // Need this ugly check now that we're compensating for lipsync predelay. Otherwise, some conversations end early for(int i=0; iGetSpeechAudioEntity() && m_Ped[i]->GetSpeechAudioEntity()->IsScriptedSpeechPlaying()) { m_PlayingScriptedConversationLine--; //to avoid array overrun naConvDebugf1("Decrementing playing conversation line 4. %u", g_AudioEngine.GetTimeInMilliseconds()); return; } } FinishScriptedConversation(); } } } s32 audScriptAudioEntity::GetLipsyncPredelay(tScriptedSpeechSlots& scriptedSlot #if !__FINAL , int slotNumber #endif ) { if(!scriptedSlot.ScriptedSpeechSound || !scriptedSlot.WaveSlot) { naAssertf(0, "Null sound or waveslot passed to audScriptAudioEntity::GetLipsyncPredelay"); return 0; } if(scriptedSlot.AssociatedDic) { MEM_USE_USERDATA(MEMUSERDATA_VISEME); // THIS IS A DIRTY HACK to get around the non-stadnard Viseme allocation scheme scriptedSlot.AssociatedDic->Release(); scriptedSlot.AssociatedDic = NULL; } #if !__FINAL bool failedToInitWaveRef = false; #endif scriptedSlot.AssociatedClip = NULL; scriptedSlot.AssociatedDic = audSpeechAudioEntity::GetVisemeData((void **)&(scriptedSlot.AssociatedClip), scriptedSlot.ScriptedSpeechSound #if !__FINAL , failedToInitWaveRef #endif ); u32 predelay = 0; if(scriptedSlot.AssociatedClip) { if(scriptedSlot.AssociatedClip->GetProperties()) { const crProperty* property = scriptedSlot.AssociatedClip->GetProperties()->FindProperty("TimeOfPreDelay"); if(property) { const crPropertyAttribute* attribute = property->GetAttribute("TimeOfPreDelay"); if(attribute && attribute->GetType() == crPropertyAttribute::kTypeFloat) { const crPropertyAttributeFloat* attributeFloat = static_cast(attribute); predelay = (u32)(attributeFloat->GetFloat()*1000); } } } } #if !__FINAL if(failedToInitWaveRef) { naConvErrorf("Failed to init waveRef when trying to get lipsync predelay for scripted speech. Please include next few lines of output and send bug to audio."); for (s32 i=0; iGetSlotName(), m_ScriptedSpeechSlot[i][m_CurrentPreloadSet].WaveSlot->GetLoadedWaveNameHash(), m_ScriptedSpeechSlot[i][m_CurrentPreloadSet].Context, m_ScriptedSpeechSlot[i][m_CurrentPreloadSet].VoiceName ); naConvErrorf("SoundInfo Waveslot: %p, WaveNameHash: %u BankId: %u", m_ScriptedSpeechSlot[i][m_CurrentPreloadSet].ScriptedSpeechSound->GetWaveSlot()->GetSlotName(), m_ScriptedSpeechSlot[i][m_CurrentPreloadSet].ScriptedSpeechSound->GetWaveNameHash(), m_ScriptedSpeechSlot[i][m_CurrentPreloadSet].ScriptedSpeechSound->GetBankId()); } } } #endif //// HACK: Delete the viseme clip dictionary //if (pDictionary) //{ // MEM_USE_USERDATA(MEMUSERDATA_VISEME); // THIS IS A DIRTY HACK to get around the non-stadnard Viseme allocation scheme // pDictionary->Release(); //} return predelay; } void audScriptAudioEntity::AdjustOverlapForLipsyncPredelay(u32 timeInMs) { if(!m_OverlapTriggerTimeAdjustedForPredelay && m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES) { if(m_ScriptedConversationOverlapTriggerTime == 0 || m_TimeTrueOverlapWasSet != 0) { s32 length = -1; if(!IsContextAnSFXCommand(m_ScriptedConversation[m_PlayingScriptedConversationLine].Context)) { s32 playTime = -1; if (m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0 && m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker] && m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()) { if(!m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()->IsScriptedSpeechPlaying(&length, &playTime)) { m_ScriptedConversationOverlapTriggerTime = timeInMs; m_OverlapTriggerTimeAdjustedForPredelay = true; } } else { m_SpeechAudioEntity.IsScriptedSpeechPlaying(&length, &playTime); } if(length <= 0) { return; } //adjust true overlaps for time it takes preceeding line to prepare if(m_TimeTrueOverlapWasSet != 0) { m_ScriptedConversationOverlapTriggerTime += (timeInMs - m_TimeTrueOverlapWasSet); m_TimeTrueOverlapWasSet = 0; naConvDebugf1("Setting trigger time to %u, adjust true overlap for loading time %s %u", m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } else { m_ScriptedConversationOverlapTriggerTime = timeInMs + length; naConvDebugf1("Setting trigger time to %u with length %u %s %u", m_ScriptedConversationOverlapTriggerTime, length, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } } else if(m_ScriptedConversationSfx) { if(m_TimeTrueOverlapWasSet != 0) { m_ScriptedConversationOverlapTriggerTime += (timeInMs - m_TimeTrueOverlapWasSet); m_TimeTrueOverlapWasSet = 0; naConvDebugf1("Setting trigger time to %u, adjust true overlap for loading time %s %u", m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } else { bool isLooping = false; length = m_ScriptedConversationSfx->ComputeDurationMsIncludingStartOffsetAndPredelay(&isLooping); if(isLooping) { //This catches external stream sounds. In this case, we have no choice but to disallow interrupts and just wait until the sound // has played m_ScriptedConversationOverlapTriggerTime = timeInMs; m_ExternalStreamConversationSFXPlaying = true; m_OverlapTriggerTimeAdjustedForPredelay = true; return; } if(length <= 0) { return; } m_ScriptedConversationOverlapTriggerTime = timeInMs + length; m_OverlapTriggerTimeAdjustedForPredelay = true; return; } } } s32 nextLineSpeechSlotIndex = -1; if(m_PlayingScriptedConversationLine+1 < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[m_PlayingScriptedConversationLine+1].Speaker >= 0 && !m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine+1].Speaker] && !IsContextAnSFXCommand(m_ScriptedConversation[m_PlayingScriptedConversationLine+1].Context)) { naConvDebugf1("Setting conversation predelay adjust true without setting time 1. %u", g_AudioEngine.GetTimeInMilliseconds()); m_OverlapTriggerTimeAdjustedForPredelay = true; } else if(m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex >= 0) { s32 thisLineSpeechSlotIndex = m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex; nextLineSpeechSlotIndex = thisLineSpeechSlotIndex + 1; u32 thisLinePreloadSet = m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet; u32 nextLinePreloadSet = thisLinePreloadSet; if(nextLineSpeechSlotIndex >= AUD_MAX_SCRIPTED_SPEECH_SLOTS_TO_LOAD) { nextLinePreloadSet = (m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet + 1) % 2; nextLineSpeechSlotIndex = nextLineSpeechSlotIndex % AUD_MAX_SCRIPTED_SPEECH_SLOTS_TO_LOAD; naConvAssertf(nextLineSpeechSlotIndex == 0, "nextLineSpeechSlotIndex is not zero when adjusting overlap for lipsync predelay and overshooting PreloadSet."); } if(m_ScriptedSpeechSlot[nextLineSpeechSlotIndex][nextLinePreloadSet].LipsyncPredelay != -1) { u32 nextNonSFXLine = m_PlayingScriptedConversationLine; nextNonSFXLine++; while(nextNonSFXLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES) { if( strcmp(m_ScriptedConversation[nextNonSFXLine].Context, "") == 0 || !IsContextAnSFXCommand(m_ScriptedConversation[nextNonSFXLine].Context) ) { break; } nextNonSFXLine++; } if(nextNonSFXLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES || (m_ScriptedConversation[nextNonSFXLine].Speaker >= 0 && m_Ped[m_ScriptedConversation[nextNonSFXLine].Speaker] && !m_Ped[m_ScriptedConversation[nextNonSFXLine].Speaker]->m_nDEflags.bFrozen)) { m_ScriptedConversationOverlapTriggerTime -= m_ScriptedSpeechSlot[nextLineSpeechSlotIndex][nextLinePreloadSet].LipsyncPredelay; } #if !__NO_OUTPUT else { naConvDebugf1("Frozen ped found for speaker. Not really applying predelay at point 3. %s %u", m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } #endif m_ScriptedConversationOverlapTriggerTime += m_ScriptedSpeechSlot[thisLineSpeechSlotIndex][thisLinePreloadSet].PauseTime; //make sure to move this forward if we are restarting the preceding line at an interrupt marker. m_ScriptedConversationOverlapTriggerTime -= m_MarkerPredelayApplied; if(nextNonSFXLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[nextNonSFXLine].Speaker != m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker) { m_ScriptedConversationOverlapTriggerTime += g_AdditionalScriptedSpeechPause; } naConvDebugf1("Adding %u and subtracting %u to make trigger time %u at point 3 %s %u", m_ScriptedSpeechSlot[thisLineSpeechSlotIndex][thisLinePreloadSet].PauseTime, m_ScriptedSpeechSlot[nextLineSpeechSlotIndex][nextLinePreloadSet].LipsyncPredelay, m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); m_OverlapTriggerTimeAdjustedForPredelay = true; } else if(m_PlayingScriptedConversationLine+1 >= AUD_MAX_SCRIPTED_CONVERSATION_LINES || strcmp(m_ScriptedConversation[m_PlayingScriptedConversationLine+1].Context, "") == 0 || m_ScriptedConversation[m_PlayingScriptedConversationLine+1].FailedToLoad || IsContextAnSFXCommand(m_ScriptedConversation[m_PlayingScriptedConversationLine+1].Context) ) { naConvDebugf1("Setting conversation predelay adjust true without setting time 2. %u", g_AudioEngine.GetTimeInMilliseconds()); m_OverlapTriggerTimeAdjustedForPredelay = true; } } } } void audScriptAudioEntity::ShowSubtitles(bool forceShow) { if(m_PlayingScriptedConversationLine < 0 || m_PlayingScriptedConversationLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES || m_ScriptedConversation[m_PlayingScriptedConversationLine].Context[0] == '\0' || IsContextAnSFXCommand(m_ScriptedConversation[m_PlayingScriptedConversationLine].Context)) { return; } // Display subtitles char *pString; const char *subtitle = m_ScriptedConversation[m_PlayingScriptedConversationLine].Subtitle; char subtitleWithVariation[40]; formatf(subtitleWithVariation, "%s_%02d", subtitle, m_ScriptedConversationVariation); if (TheText.DoesTextLabelExist(subtitleWithVariation)) { pString = TheText.Get(subtitleWithVariation); } else { // subtitle with variation doesn't exist, so try it without variation if (!TheText.DoesTextLabelExist(subtitle)) { naConvWarningf("Subtitle doesn't exist - with or without _XX. (%s) Tried to find variation %s", subtitle, subtitleWithVariation); } pString = TheText.Get(subtitle); } if(pString && (forceShow || m_ScriptedConversation[m_PlayingScriptedConversationLine].IsPlaceholder) ) { if(naConvVerifyf(pString[0] == '~' && pString[2] == '~', "Trying to force an invalid subtitle %s.", pString)) { pString = pString + 3; //skip the leading ~z~ } } s32 TextBlock = TheText.GetBlockContainingLastReturnedString(); if (m_DisplaySubtitles) { uiDebugf3("Adding To Brief: Dialogue Line --- %s --- Spoken By --- %s --- During Mission -- %s", pString, m_VoiceName[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], CPauseMenu::GetCurrentMissionLabel()); if (CPauseMenu::GetMenuPreference(PREF_SUBTITLES) || (CMessages::IsThisTextSwitchedOffByFrontendSubtitleSetting(pString) == false) ) { // Graeme - This is something that Imran asked for (Bug 568480). It seems like a bad idea to me. It means that the game will behave differently depending on whether subtitles are off or on. // If subtitles are switched off in the pause menu then the idea is that scripts can be written so that god text is shown while the conversation plays. // I need to avoid calling AddMessage so that the god text doesn't get cleared by a conversation subtitle that would never be shown anyway. if(m_SizeOfSubtitleSubStringArray == 0) { CMessages::AddMessage( pString, TextBlock, forceShow ? 3000 : 10000, true, m_AddSubtitlesToBriefing && !m_ScriptedConversation[m_PlayingScriptedConversationLine].DoNotRemoveSubtitle, PREVIOUS_BRIEF_NO_OVERRIDE, NULL, 0, NULL, 0, false, atStringHash(m_VoiceName[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]), atStringHash(CPauseMenu::GetCurrentMissionLabel()) ); } else { CMessages::AddMessage( pString, TextBlock, forceShow ? 3000 : 10000, true, m_AddSubtitlesToBriefing && !m_ScriptedConversation[m_PlayingScriptedConversationLine].DoNotRemoveSubtitle, PREVIOUS_BRIEF_NO_OVERRIDE, NULL, 0, m_ArrayOfSubtitleSubStrings, m_SizeOfSubtitleSubStringArray, false, atStringHash(m_VoiceName[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]), atStringHash(CPauseMenu::GetCurrentMissionLabel()) ); } } else { CSubtitleMessage PreviousBrief; PreviousBrief.Set(pString, TextBlock, NULL, 0, NULL, 0, false, true, atStringHash(m_VoiceName[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]), atStringHash(CPauseMenu::GetCurrentMissionLabel())); CMessages::AddToPreviousBriefArray(PreviousBrief, PREVIOUS_BRIEF_FORCE_DIALOGUE); } m_LastDisplayedTextBlock = TextBlock; } #if __BANK if(g_DisplayScriptedLineInfo) { formatf(sm_ScriptedLineDebugInfo, "voicename:%s context:%s variation:%u", m_VoiceName[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker], m_ScriptedConversation[m_PlayingScriptedConversationLine].Context, m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex >= 0 ? m_ScriptedSpeechSlot[m_ScriptedConversation[m_PlayingScriptedConversationLine].SpeechSlotIndex][m_ScriptedConversation[m_PlayingScriptedConversationLine].PreloadSet].VariationNumber : 0); } #endif } bool audScriptAudioEntity::IsPreloadedScriptedSpeechReady() { if(m_PreLoadingSpeechLine > -1 || m_HighestLoadedLine > -1) { audPrepareState preloadPrepareState = PreLoadScriptedSpeech(m_PreLoadingSpeechLine); if (preloadPrepareState!=AUD_PREPARED) { if(preloadPrepareState == AUD_PREPARE_FAILED && !g_DoNotAbortFailedConversations) { #if __BANK char txt[64]; formatf(txt,"Scripted speech lines failed to prepare. Aborting conversation."); #endif naConvAssertf(0,"Scripted speech lines failed to prepare. Aborting conversation."); AbortScriptedConversation(false BANK_ONLY(,txt)); } return false; } return true; } return false; } bool audScriptAudioEntity::HandleCallToPreloading(u32 timeInMs, bool isPaused) { if (m_PreLoadingSpeechLine>-1) { audPrepareState preloadPrepareState = PreLoadScriptedSpeech(m_PreLoadingSpeechLine); if (preloadPrepareState!=AUD_PREPARED) { // formatf(convDebug, sizeof(convDebug), "Waiting for preload"); // grcDebugDraw::PrintToScreenCoors(convDebug,20,20); if(preloadPrepareState == AUD_PREPARE_FAILED && !g_DoNotAbortFailedConversations) { #if __BANK char txt[128]; formatf(txt,"Scripted speech lines failed to prepare. Aborting conversation."); #endif naConvAssertf(0,"Scripted speech lines failed to prepare. Aborting conversation."); AbortScriptedConversation(false BANK_ONLY(,txt)); return false; } // Exit out if we're waiting on that line being preloaded - we're probably not, unless this is the first line of the conv if (m_WaitingForPreload) { return false; } } if(m_WaitingForFirstLineToPrepareToDuck && !isPaused) { m_WaitingForFirstLineToPrepareToDuck = false; if(m_ConversationDuckingScene && !m_ScriptSetNoDucking) m_ConversationDuckingScene->SetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 1.f); } } else { if(m_ConversationIsSfxOnly && m_HighestLoadedLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES - 1 && !m_ScriptedConversationSfx) { AbortScriptedConversation(false BANK_ONLY(,"Aborting from HandleCallToPreloading")); return false; } // See if we should be preloading - are we playing something not far behind the highest loaded thing if (m_HighestLoadedLine < (m_PlayingScriptedConversationLine+3)) { // Special-case the first line, so we can quickly block ambient anims if (m_PlayingScriptedConversationLine==-1 && !isPaused) { if(g_DuckForConvAfterPreload) m_WaitingForFirstLineToPrepareToDuck = true; if( m_ScriptedConversation[0].Speaker >= 0 && m_ScriptedConversation[0].Speaker < AUD_MAX_SCRIPTED_CONVERSATION_SPEAKERS && m_Ped[m_ScriptedConversation[0].Speaker]) { m_BlockAnimTime[m_ScriptedConversation[0].Speaker] = timeInMs + g_StopAmbientAnimTime; m_Ped[m_ScriptedConversation[0].Speaker]->SetAmbientAnimsBlocked(true); } } audPrepareState preloadPrepareState = PreLoadScriptedSpeech(m_HighestLoadedLine+1); if(preloadPrepareState == AUD_PREPARE_FAILED) return false; } } return true; } void audScriptAudioEntity::FinishScriptedConversation() { naConvDebugf1("FinishScriptedConversation()"); if(m_ConversationDuckingScene) m_ConversationDuckingScene->SetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 0.f); m_ScriptedConversationInProgress = false; m_DuckingForScriptedConversation = false; m_IsScriptedConversationZit = false; m_PlayingScriptedConversationLine = -1; m_ScriptedConversationNoAudioDelay = 0; m_AbortScriptedConversationRequested = false; m_HighestLoadedLine = -1; m_PreLoadingSpeechLine = -1; m_ScriptedConversationOverlapTriggerTime = 0; m_OverlapTriggerTimeAdjustedForPredelay = false; m_TriggeredOverlapSpeech = false; m_NextTimeScriptedConversationCanStart = g_AudioEngine.GetTimeInMilliseconds()+250; m_AnimTriggersControllingConversation = false; m_IsConversationPlaceholder = false; m_InterruptTriggered = false; m_ScriptSetNoDucking = false; m_IsScriptedConversationAMobileCall = false; for (s32 j=0; j<2; j++) { for (s32 i=0; iStopAndForget(); // This won't actually stop any playing speech, as that's just the preload sound - need to explicitly tell all peds to stop talking } if(m_ScriptedSpeechSlot[i][j].AssociatedDic) { MEM_USE_USERDATA(MEMUSERDATA_VISEME); // THIS IS A DIRTY HACK to get around the non-stadnard Viseme allocation scheme m_ScriptedSpeechSlot[i][j].AssociatedDic->Release(); m_ScriptedSpeechSlot[i][j].AssociatedDic = NULL; } m_ScriptedSpeechSlot[i][j].AssociatedClip = NULL; m_ScriptedSpeechSlot[i][j].LipsyncPredelay = -1; m_ScriptedSpeechSlot[i][j].PauseTime = 0; m_ScriptedSpeechSlot[i][j].VariationNumber = -1; #if !__FINAL m_ScriptedSpeechSlot[i][j].VoiceName[0] = '\0'; m_ScriptedSpeechSlot[i][j].Context[0] = '\0'; #endif } } SetAllPedsHavingAConversation(false); if (m_LastDisplayedTextBlock>0) { #if __BANK sm_ScriptedLineDebugInfo[0] = '\0'; #endif CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(m_LastDisplayedTextBlock, false); } // Clear up our references for (s32 i=0; i 0) { m_Ped[i]->SetAmbientAnimsBlocked(false); } m_Ped[i]->RemoveSpeakerListenedTo(); m_Ped[i]->RemoveSpeakerListenedToSecondary(); if(m_Ped[i]->GetIsInVehicle()) { CVehicle* pSpeakerVehicle = m_Ped[i]->GetMyVehicle(); for(int i = 0; i < pSpeakerVehicle->GetSeatManager()->GetMaxSeats(); i++) { CPed* pPedInVehicle = pSpeakerVehicle->GetSeatManager()->GetPedInSeat(i); if(pPedInVehicle && pPedInVehicle != m_Ped[i]) { pPedInVehicle->RemoveSpeakerListenedToSecondary(); } } } if (m_Ped[i]->GetSpeechAudioEntity()) m_Ped[i]->GetSpeechAudioEntity()->StopPlayingScriptedSpeech(); // Remove the old registered reference // NOTE: this function can be called from script directly or from the audio update thread, we only want to defer tidyref // when this is running on the audio thread m_Ped[i] = NULL; } m_PedIsInvolvedInCurrentConversation[i] = false; } // Stop playing anything from our frontend speech audio entity m_SpeechAudioEntity.StopPlayingScriptedSpeech(); // Check we don't have a sound playing if (m_ScriptedConversationSfx) { m_ScriptedConversationSfx->StopAndForget(); } m_Interrupter = NULL; m_TimeToInterrupt = 0; m_TimeToRestartConversation = 0; m_TimeLastScriptedConvFinished = audNorthAudioEngine::GetCurrentTimeInMs(); } audPrepareState audScriptAudioEntity::PreLoadScriptedSpeech(s16 lineToStartLoadingFrom) { if(!g_AudioEngine.IsAudioEnabled()) { m_HighestLoadedLine = AUD_MAX_SCRIPTED_CONVERSATION_LINES; return AUD_PREPARED; } if (lineToStartLoadingFrom<0) { naConvErrorf("Invalid line to start loading from"); return AUD_PREPARED; } // If we aren't already preloading, clear out all the old line-slot-pairs, set up the new ones, and kick off the loads. // We'll only have valid sounds in the slots if we're preloading. if (m_PreLoadingSpeechLine>-1) { // See how many lines there are remaining, and load that many audSpeechSoundInfo speechSoundInfo[AUD_MAX_SCRIPTED_SPEECH_SLOTS_TO_LOAD]; u32 numSpeechSounds = 0; for (s32 i=0; iGetReferenceCount() == (u32)-1) { #if __BANK char txt[64]; formatf(txt,"Found scripted speech slot with -1 ref count. Aborting conversations."); #endif naConvAssertf(0,"Found scripted speech slot with -1 ref count. Aborting conversations."); AbortScriptedConversation(false BANK_ONLY(,txt)); return AUD_PREPARE_FAILED; } numSpeechSounds++; } } audPrepareState prepareState = audSpeechSound::BatchPrepare(speechSoundInfo, numSpeechSounds); if (prepareState == AUD_PREPARED) { #if !__FINAL u32 currentTime = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0); for (s16 conversationLine = lineToStartLoadingFrom; conversationLine m_ScriptedConversation[conversationLine].TimePrepareFirstRequested && currentTime - m_ScriptedConversation[conversationLine].TimePrepareFirstRequested> 1000) { naConvWarningf("Scripted speech line %s taking too long to load. Took %u ms", m_ScriptedConversation[conversationLine].Context, currentTime - m_ScriptedConversation[conversationLine].TimePrepareFirstRequested); } } #endif // We've loaded everything - clear our preparing sounds, and flag that we're done for (s32 i=0; iStopAndForget(); } m_ScriptedSpeechSlot[i][m_CurrentPreloadSet].ScriptedSpeechSound = NULL; } #if !__FINAL if(lineToStartLoadingFrom >= 0 && lineToStartLoadingFrom < AUD_MAX_SCRIPTED_CONVERSATION_LINES) { PF_SET(TimeToPreloadScriptedSpeech, currentTime - m_ScriptedConversation[lineToStartLoadingFrom].TimePrepareFirstRequested); } #endif m_PreLoadingSpeechLine = -1; m_CurrentPreloadSet = (m_CurrentPreloadSet+1) % 2; m_WaitingForPreload = false; m_HighestLoadedLine = m_HighestLoadingLine; return AUD_PREPARED; } // else, do nothing, we're still loading return prepareState; } else { m_PreLoadingSpeechLine = lineToStartLoadingFrom; // We've not started preparing yet. Clear out our line-slot pairings, set up new ones, create speech sounds, and prepare // Assert if we've any sounds - they should only exist whilst preparing for (s32 i=0; i= 0 && !IsContextAnSFXCommand(m_ScriptedConversation[conversationLine].Context)) { m_ScriptedConversation[conversationLine].SpeechSlotIndex = scriptedSpeechSlot; m_ScriptedConversation[conversationLine].PreloadSet = m_CurrentPreloadSet; char* voiceName = m_VoiceName[m_ScriptedConversation[conversationLine].Speaker]; char* context = m_ScriptedConversation[conversationLine].Context; s32 variationNum = m_SpeechAudioEntity.GetRandomVariation(atStringHash(voiceName), context, NULL, NULL); #if !__FINAL char placeholderVoiceName[128] = ""; if(variationNum <= 0) { if(!CFileMgr::ShouldForceReleaseAudioPacklist()) { if (voiceName && g_EnablePlaceholderDialogue && (!m_IsConversationPlaceholder || PARAM_useRobotSpeechForAllLines.Get())) { formatf(placeholderVoiceName, sizeof(placeholderVoiceName), "%s_PLACEHOLDER", voiceName); variationNum = m_SpeechAudioEntity.GetRandomVariation(atStringHash(placeholderVoiceName), context, NULL, NULL); if (variationNum <=0) { Displayf("Missing scripted speech: voice: %s; context: %s", voiceName, context); } voiceName = placeholderVoiceName; m_ScriptedConversation[conversationLine].IsPlaceholder = true; } } #if __BANK if(!m_HasInitializedDialogueFile) { static bool dialogueFileFailedToOpen = false; if(g_TrackMissingDialogue && m_DialogueFileHandle == INVALID_FILE_HANDLE && !dialogueFileFailedToOpen && (!NetworkInterface::IsGameInProgress() || g_TrackMissingDialogueMP)) { const char* pPathName = NULL; PARAM_trackMissingDialogue.Get(pPathName); if(strlen(pPathName) == 0) { #if RSG_ORBIS formatf(m_DialogueFileName, "common:/audMissingDialogue_%u_%u.csv", audSpeechSound::GetMetadataManager().GetAssetChangelist(), time(0)); #else formatf(m_DialogueFileName, "common:/audMissingDialogue_%u_%ui.csv", audSpeechSound::GetMetadataManager().GetAssetChangelist(), std::time(0)); #endif } else { #if RSG_ORBIS formatf(m_DialogueFileName, "%s/audMissingDialogue_%u_%u.csv",pPathName, audSpeechSound::GetMetadataManager().GetAssetChangelist(), time(0)); #else formatf(m_DialogueFileName, "%s/audMissingDialogue_%u_%u.csv",pPathName, audSpeechSound::GetMetadataManager().GetAssetChangelist(), std::time(0)); #endif } m_DialogueFileHandle = CFileMgr::OpenFileForWriting(m_DialogueFileName); if(m_DialogueFileHandle != INVALID_FILE_HANDLE) { const char *header = "VoiceName,FileName,SpeakerID\r\n"; CFileMgr::Write(m_DialogueFileHandle, header, istrlen(header)); CFileMgr::CloseFile(m_DialogueFileHandle); m_DialogueFileHandle = CFileMgr::OpenFileForAppending(m_DialogueFileName); } else { dialogueFileFailedToOpen = true; } } m_HasInitializedDialogueFile = true; } if(m_DialogueFileHandle != INVALID_FILE_HANDLE && (!NetworkInterface::IsGameInProgress() || g_TrackMissingDialogueMP)) { char buf[256]; formatf(buf,256,"%s,%s,%u\r\n",m_VoiceName[m_ScriptedConversation[conversationLine].Speaker], m_ScriptedConversation[conversationLine].Context,m_ScriptedConversation[conversationLine].Speaker); CFileMgr::Write(m_DialogueFileHandle, buf, istrlen(buf)); CFileMgr::CloseFile(m_DialogueFileHandle); m_DialogueFileHandle = CFileMgr::OpenFileForAppending(m_DialogueFileName); } #endif } #endif //naCErrorf(variationNum>=0, "Missing scripted speech: voice: %s; context: %s", voiceName, context); m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].VariationNumber = variationNum; audSoundInitParams initParams; initParams.WaveSlot = m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].WaveSlot; CreateSound_PersistentReference("PRELOAD_SPEECH_SOUND", (audSound**)&m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].ScriptedSpeechSound, &initParams); if(m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].ScriptedSpeechSound) { bool success = m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].ScriptedSpeechSound->InitSpeech(voiceName, context, variationNum); if (success) { m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].PauseTime = 0; if(conversationLine + 1 < AUD_MAX_SCRIPTED_CONVERSATION_LINES && IsContextAnSFXCommand(m_ScriptedConversation[conversationLine+1].Context)) m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].PauseTime = GetPauseTimeForConversationLine(&m_ScriptedConversation[conversationLine+1].Context[4]); naConvDisplayf("Preloading line %s %u", context, g_AudioEngine.GetTimeInMilliseconds()); #if !__FINAL && __BANK safecpy(m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].VoiceName, voiceName); safecpy(m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].Context, context); #endif//!__FINAL && __BANK scriptedSpeechSlot++; } else { //naAssertf(0, "Incorrect voicename found for conversation line %s. Check that the speaker number in d* is correct.", context); naDisplayf("InitSpeech Failed - Incorrect voicename found for conversation line %s _%d %s. Check that the speaker number in d* is correct.", context, variationNum, voiceName); // ditch this sound, as it failed to find a variation m_ScriptedSpeechSlot[scriptedSpeechSlot][m_CurrentPreloadSet].ScriptedSpeechSound->StopAndForget(); m_ScriptedConversation[conversationLine].SpeechSlotIndex = -1; m_ScriptedConversation[conversationLine].FailedToLoad = true; } } else { #if __BANK char txt[64]; formatf(txt,"No scripted speech sound in slot: %d preloadset: %d. Bug Default Audio Code for any associated dialogue problems.", scriptedSpeechSlot, m_CurrentPreloadSet); naConvWarningf("%s", txt); #endif AbortScriptedConversation(false BANK_ONLY(,txt)); return AUD_PREPARE_FAILED; } } m_HighestLoadingLine = conversationLine; conversationLine++; } // See how many lines there are remaining, and load that many audSpeechSoundInfo speechSoundInfo[AUD_MAX_SCRIPTED_SPEECH_SLOTS_TO_LOAD]; u32 numSpeechSounds = 0; for (s32 i=0; i6 && strncmp(conversationContext, "PAUSE_", 6) == 0) { char number[32] = ""; formatf(number, 31, "%s", &conversationContext[6]); u32 time = atoi(number); return time; } return 0; } s32 audScriptAudioEntity::GetOverlapOffsetTime(char* conversationContext) { // Find the bit before the first underscore const s32 len = ((s32)strlen(conversationContext)); if (len>8 && strncmp(conversationContext, "OVERLAP_", 8) == 0) { char number[32] = ""; formatf(number, 31, "%s", &conversationContext[8]); if (strcmp(number, "PHONE") == 0) { return g_PhoneOverlapTime; } u32 time = atoi(number); return time; } return -1; } bool audScriptAudioEntity::IsContextAnSFXCommand(char* conversationContext) { const s32 len = ((s32)strlen(conversationContext)); if (len>4 && strncmp(conversationContext, "SFX_", 4) == 0) { return true; } return false; } bool audScriptAudioEntity::StartMobilePhoneRinging(s32 ringtoneId) { if (m_CellphoneRingSound) { return false; } char soundName[32]; naCErrorf(ringtoneId>=0 && ringtoneId<10, "Invalid ringtoneId"); formatf(soundName, "MOBILE_PHONE_RING_%02d", ringtoneId); audSoundInitParams initParams; initParams.WaveSlot = audWaveSlot::FindWaveSlot(ATSTRINGHASH("PLAYER_RINGTONE", 0x0d2645e26)); initParams.PrepareTimeLimit = 3000; initParams.AllowLoad = true; audCategory* category = g_AudioEngine.GetCategoryManager().GetCategoryPtr(g_FrontendGameMobileRingingHash); initParams.Category = category; CreateAndPlaySound_Persistent(soundName, &m_CellphoneRingSound, &initParams); if (!m_CellphoneRingSound) { naWarningf("Audio: ringtone %d not found", ringtoneId); audSoundInitParams altInitParams; altInitParams.Category = category; CreateAndPlaySound_Persistent("MOBILE_PHONE_RING", &m_CellphoneRingSound, &altInitParams); } return true; } void audScriptAudioEntity::SetMobileRingType(s32 ringModeId) { m_MobileRingType = (u8)ringModeId; audCategory* category = g_AudioEngine.GetCategoryManager().GetCategoryPtr(g_FrontendGameMobileRingingHash); if (category) { switch (ringModeId) { case AUD_CELLPHONE_NORMAL: category->SetVolume(0.0f); break; case AUD_CELLPHONE_QUIET: category->SetVolume(-12.0f); break; case AUD_CELLPHONE_SILENT: category->SetVolume(g_SilenceVolume); break; case AUD_CELLPHONE_VIBE: category->SetVolume(g_SilenceVolume); break; case AUD_CELLPHONE_VIBE_AND_RING: category->SetVolume(0.0f); break; default: category->SetVolume(0.0f); break; } } } void audScriptAudioEntity::SetPedMobileRingType(CPed *ped, const s32 ringType) { if(ped == NULL) ped = FindPlayerPed(); Assert(ped->GetPhoneComponent()); if (ped->GetPhoneComponent()) ped->GetPhoneComponent()->SetMobileRingType(ringType); } bool audScriptAudioEntity::StartMobilePhoneCalling() { if (m_CellphoneRingSound) { return false; } CreateAndPlaySound_Persistent("CALLING_MOBILE_PHONE_RING", &m_CellphoneRingSound); return true; } void audScriptAudioEntity::StopMobilePhoneRinging() { if (m_CellphoneRingSound) { m_CellphoneRingSound->StopAndForget(); } } void audScriptAudioEntity::StartPedMobileRinging(CPed *ped, const s32 ringtoneId) { if(ped == NULL) ped = FindPlayerPed(); Assert(ped->GetPhoneComponent()); if (ped->GetPhoneComponent()) ped->GetPhoneComponent()->StartMobileRinging(static_cast(ringtoneId)); } void audScriptAudioEntity::StopPedMobileRinging(CPed *ped) { // note: calling sound uses this function to stop... if (m_CellphoneRingSound) { m_CellphoneRingSound->StopAndForget(); } if(ped == NULL) ped = FindPlayerPed(); Assert(ped->GetPhoneComponent()); if (ped->GetPhoneComponent()) ped->GetPhoneComponent()->StopMobileRinging(); } void audScriptAudioEntity::StartNewScriptedConversationBuffer() { naConvDisplayf("StartNewScriptedConversationBuffer()"); for (s32 i=0; iSetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 1.f); } void audScriptAudioEntity::AddLineToScriptedConversation(u32 lineNumber, s32 speakerNumber, const char* pContext, const char* pSubtitle, s32 listenerNumber, u8 volume, bool isRandom, bool interruptible, bool ducksRadio, bool ducksScore, audConversationAudibility audibility, bool headset, bool dontInterruptForSpecialAbility, bool isPadSpeakerRoute) { Displayf("AddLineToScripted...line(%d) speaker(%d) Context(%s) headset(%d) isPad(%d)", lineNumber, speakerNumber, pContext, headset, isPadSpeakerRoute); bool lineHandled = false; u32 realLineNumber = lineNumber + m_ExtraConvLines; naConvAssertf(realLineNumber 0, "Found CODENAME_POST with no preceeding line. Subtitling will be screwed. Game may crash. Alert Audio."); Assertf(((int)m_ConversationCodeLetter) -64 > 0, "Bad ConversationCodeLetter value. Subtitling will be screwed. Game may crash. Alert Audio."); char subString[128]; formatf(subString, 128, "%s-%i (%s)", g_CarCodeUnitTypeStrings[Clamp(((int)m_ConversationCodeLetter) - 65, 0, 25)], m_ConversationCodeNumber, m_ConversationUserName); m_SizeOfSubtitleSubStringArray = 1; m_ArrayOfSubtitleSubStrings[0].SetLiteralString(subString, CSubStringWithinMessage::LITERAL_STRING_TYPE_PERSISTENT, false); char codeContext[64]; formatf(codeContext, "CODENAME_%c", m_ConversationCodeLetter); safecpy(m_ScriptedConversationBuffer[realLineNumber].Context, codeContext); safecpy(m_ScriptedConversationBuffer[realLineNumber].Subtitle, m_ScriptedConversationBuffer[realLineNumber-1].Subtitle); m_ScriptedConversationBuffer[realLineNumber].Speaker = speakerNumber; m_ScriptedConversationBuffer[realLineNumber].Listener = listenerNumber; m_ScriptedConversationBuffer[realLineNumber].Volume = volume; m_ScriptedConversationBuffer[realLineNumber].UnresolvedLineNum = (u8)realLineNumber - 1; m_ScriptedConversationBuffer[realLineNumber].DoNotRemoveSubtitle = true; m_ScriptedConversationBuffer[realLineNumber].Audibility = audibility; m_ScriptedConversationBuffer[realLineNumber].IsInterruptible = interruptible; m_ScriptedConversationBuffer[realLineNumber].DucksScore = ducksScore; m_ScriptedConversationBuffer[realLineNumber].DucksRadio = ducksRadio; m_ScriptedConversationBuffer[realLineNumber].Headset = headset; m_ScriptedConversationBuffer[realLineNumber].DontInterruptForSpecialAbility = dontInterruptForSpecialAbility; m_ScriptedConversationBuffer[realLineNumber].isPadSpeakerRoute = isPadSpeakerRoute; m_ExtraConvLines++; realLineNumber = lineNumber + m_ExtraConvLines; naConvAssertf(realLineNumber 0, "Bad ConversationCodeLetter value. Subtitling will be screwed. Game may crash. Alert Audio."); char subString[128]; formatf(subString, 128, "%s-%i (%s)", g_CarCodeUnitTypeStrings[Clamp(((int)m_ConversationCodeLetter) - 65, 0, 25)], m_ConversationCodeNumber, m_ConversationUserName); m_SizeOfSubtitleSubStringArray = 1; m_ArrayOfSubtitleSubStrings[0].SetLiteralString(subString, CSubStringWithinMessage::LITERAL_STRING_TYPE_PERSISTENT, false); char codeContext[64]; formatf(codeContext, "CODENAME_%c", m_ConversationCodeLetter); safecpy(m_ScriptedConversationBuffer[realLineNumber].Context, codeContext); m_ScriptedConversationBuffer[realLineNumber].Speaker = speakerNumber; m_ScriptedConversationBuffer[realLineNumber].Listener = listenerNumber; m_ScriptedConversationBuffer[realLineNumber].Volume = volume; m_ScriptedConversationBuffer[realLineNumber].Audibility = audibility; m_ScriptedConversationBuffer[realLineNumber].IsInterruptible = interruptible; m_ScriptedConversationBuffer[realLineNumber].DucksScore = ducksScore; m_ScriptedConversationBuffer[realLineNumber].DucksRadio = ducksRadio; m_ScriptedConversationBuffer[realLineNumber].Headset = headset; m_ScriptedConversationBuffer[realLineNumber].UnresolvedLineNum = (u8)realLineNumber + 1; m_ScriptedConversationBuffer[realLineNumber].DontInterruptForSpecialAbility = dontInterruptForSpecialAbility; m_ScriptedConversationBuffer[realLineNumber].isPadSpeakerRoute = isPadSpeakerRoute; m_ExtraConvLines++; realLineNumber = lineNumber + m_ExtraConvLines; naConvAssertf(realLineNumber 1, "Attempting to resolve SFX_CODENAME_PRE with bad line number."); m_ScriptedConversationBuffer[realLineNumber].DoNotRemoveSubtitle = true; safecpy(m_ScriptedConversationBuffer[realLineNumber-2].Subtitle, m_ScriptedConversationBuffer[realLineNumber].Subtitle); safecpy(m_ScriptedConversationBuffer[realLineNumber-1].Subtitle, m_ScriptedConversationBuffer[realLineNumber].Subtitle); m_CodeNamePreLineNeedsResolving = false; } } } void audScriptAudioEntity::GetStreetAndZoneStringsForPos(const Vector3& pos, char* streetName, u32 streetNameSize, char* zoneName, u32 zoneNameSize) { CNodeAddress aNode; s32 NodesFound = ThePaths.RecordNodesInCircle(pos, 5.0f, 1, &aNode, false, false, false); if(NodesFound != 0) { u32 streetID = ThePaths.FindNodePointer(aNode)->m_streetNameHash; if(streetID != 0) { safecpy( streetName, TheText.Get(streetID, "Streetname"), streetNameSize ); for(u32 i = 0; i < streetNameSize; i++) { if(streetName[i] == ' ') { streetName[i] = '_'; } } } } CPopZone *zone = CPopZones::FindSmallestForPosition(&pos, ZONECAT_ANY, ZONETYPE_SP); naAssertf(zone, "Attempted to find smallest zone for position but a null ptr was returned"); if(zone) { safecpy( zoneName, zone->GetTranslatedName(), zoneNameSize ); } } void audScriptAudioEntity::SetConversationCodename(char codeLetter, u8 codeNumber, const char* userName) { naConvDebugf1("SetConversationCodename() codeLetter: %c codeNumber: %u userName: %s", codeLetter, codeNumber, userName); m_ConversationCodeLetter = codeLetter; m_ConversationCodeNumber = codeNumber; safecpy(m_ConversationUserName, userName); } void audScriptAudioEntity::SetConversationLocation(const Vector3& location) { naConvDebugf1("SetConversationLocation() location: %f %f %f", location.x, location.y, location.z); m_ConversationLocation.Set(location); } void audScriptAudioEntity::AddConversationSpeaker(u32 speakerConversationIndex, CPed* speaker, char* voiceName) { naConvDisplayf("AddConversationSpeaker() - speakerConversationIndex: %u ped: %s voiceName: %s", speakerConversationIndex, speaker ? speaker->GetModelName() : "NULL", voiceName); if(speaker && speaker->GetSpeechAudioEntity() && !speaker->GetSpeechAudioEntity()->GetIsOkayTimeToStartConversation()) { m_AbortScriptedConversationRequested = true; } naConvAssertf(speakerConversationIndex=0) { // also need to check that this line isn't an SFX_ lines, as they may reference a speaker number that doesn't exist if (!IsContextAnSFXCommand(m_ScriptedConversationBuffer[i].Context)) { // flag this ped as being involved in the conv (listening doesn't count) - we'll check this before aborting for // missing/dead/etc peds during playback. m_PedIsInvolvedInCurrentConversation[m_ScriptedConversationBuffer[i].Speaker] = true; naConvAssertf(m_ScriptedConversationBuffer[i].Speaker < AUD_MAX_SCRIPTED_CONVERSATION_SPEAKERS, "Speaker index out of bounds"); // it's a valid line, so if it's not frontend, make sure it's a valid ped, and alive (TODO: and not on fire, falling, etc) if (m_PedBuffer[m_ScriptedConversationBuffer[i].Speaker]) { if (m_PedBuffer[m_ScriptedConversationBuffer[i].Speaker]->IsDead()) { naConvWarningf("Didn't start scripted conv due to ped being dead for line %s", m_ScriptedConversationBuffer[i].Context); return false; } else if(m_PedBuffer[m_ScriptedConversationBuffer[i].Speaker]->m_nDEflags.bFrozen) { if(m_ScriptedConversationBuffer[i].Volume == AUD_SPEECH_FRONTEND || m_ScriptedConversationBuffer[i].Headset) m_PedBuffer[m_ScriptedConversationBuffer[i].Speaker] = NULL; else { naConvWarningf("Didn't start scripted conv due to ped being frozen for line %s", m_ScriptedConversationBuffer[i].Context); naConvWarningf("Speaker: %u VoiceName: %s isInteriorFrozen: %s", m_ScriptedConversationBuffer[i].Speaker, m_VoiceNameBuffer[i], m_PedBuffer[m_ScriptedConversationBuffer[i].Speaker]->m_nDEflags.bFrozen ? "TRUE" : "FALSE"); return false; } } } } } m_ScriptedConversation[i].Speaker = m_ScriptedConversationBuffer[i].Speaker; m_ScriptedConversation[i].Listener = m_ScriptedConversationBuffer[i].Listener; m_ScriptedConversation[i].Volume = m_ScriptedConversationBuffer[i].Volume; strcpy(m_ScriptedConversation[i].Context, m_ScriptedConversationBuffer[i].Context); strcpy(m_ScriptedConversation[i].Subtitle, m_ScriptedConversationBuffer[i].Subtitle); m_ScriptedConversation[i].UnresolvedLineNum = m_ScriptedConversationBuffer[i].UnresolvedLineNum; m_ScriptedConversation[i].DoNotRemoveSubtitle = m_ScriptedConversationBuffer[i].DoNotRemoveSubtitle; m_ScriptedConversation[i].IsRandom = m_ScriptedConversationBuffer[i].IsRandom; m_ScriptedConversation[i].Audibility = m_ScriptedConversationBuffer[i].Audibility; m_ScriptedConversation[i].IsInterruptible = m_ScriptedConversationBuffer[i].IsInterruptible; m_ScriptedConversation[i].DucksScore = m_ScriptedConversationBuffer[i].DucksScore; m_ScriptedConversation[i].DucksRadio = m_ScriptedConversationBuffer[i].DucksRadio; m_ScriptedConversation[i].Headset = m_ScriptedConversationBuffer[i].Headset; m_ScriptedConversation[i].DontInterruptForSpecialAbility = m_ScriptedConversationBuffer[i].DontInterruptForSpecialAbility; m_ScriptedConversation[i].isPadSpeakerRoute = m_ScriptedConversationBuffer[i].isPadSpeakerRoute; m_ScriptedConversation[i].HasBeenInterruptedForSpecialAbility = false; m_ScriptedConversation[i].HasRepeatedForRagdoll = false; m_ScriptedConversation[i].FailedToLoad = false; m_ScriptedConversation[i].IsPlaceholder = false; #if !__FINAL m_ScriptedConversation[i].TimePrepareFirstRequested = 0; #endif if(strcmp(m_ScriptedConversation[i].Context, "") != 0) { naConvDisplayf("AddLineToScriptedConversation() - Line#: %u Context: %s Subtitle: %s Speaker: %d Listener: %d Volume: %u audibility: %u " "DoNotRemoveSubtitle: %s IsInterruptible: %s DucksScore: %s DucksRadio: %s Headset: %s, DontInterruptForSpecialAbility: %s", i, m_ScriptedConversation[i].Context, m_ScriptedConversation[i].Subtitle, m_ScriptedConversation[i].Speaker, m_ScriptedConversation[i].Listener, m_ScriptedConversation[i].Volume, m_ScriptedConversation[i].Audibility, m_ScriptedConversation[i].DoNotRemoveSubtitle ? "True" : "False", m_ScriptedConversation[i].IsInterruptible ? "True" : "False", m_ScriptedConversation[i].DucksScore ? "True" : "False", m_ScriptedConversation[i].DucksRadio ? "True" : "False", m_ScriptedConversation[i].Headset ? "True" : "False", m_ScriptedConversation[i].DontInterruptForSpecialAbility ? "True" : "False"); } } // Need to do these things after our last chance to abort and return false m_CloneConversation = cloneConversation; m_DisplaySubtitles = displaySubtitles; m_AddSubtitlesToBriefing = addToBriefing; m_IsScriptedConversationAMobileCall = mobileCall; m_ScriptedConversationInProgress = true; m_PlayingScriptedConversationLine = -1; m_PauseConversation = preload; m_PauseCalledFromScript = preload; m_PausedForRagdoll = false; m_PauseWaitingOnCurrentLine = false; m_ScriptedConversationOverlapTriggerTime = 0; m_TimeTrueOverlapWasSet = 0; m_OverlapTriggerTimeAdjustedForPredelay = false; m_TriggeredOverlapSpeech = false; m_WaitingForPreload = false; // though actually we will be almost instantly m_CarUpsideDownTimer = 0; m_ConversationWasInterruptedForWantedLevel = false; m_ConversationIsInterruptible = interruptible; m_HasShownSubtitles = true; m_LastSpeakingPed = NULL; m_MarkerPredelayApplied = 0; m_TreatNextLineToPlayAsUrgent = false; m_InterruptSpeechRequestTime = 0; m_CheckInterruptPreloading = false; m_FirstLineHasStarted = false; m_FramesVehicleIsInWater = 0; #if !__FINAL m_TimeLastWarnedAboutNonScriptRestart = 0; #endif #if __ASSERT bool allSpeakersHaveValidVoicename = true; u32 exampleLine = 0; #endif for (s32 i=0; iGetSpeechAudioEntity()) { // Control whether they can say anything m_Ped[i]->GetSpeechAudioEntity()->SetIsHavingAConversation(isHavingAConversation); } } } bool audScriptAudioEntity::IsScriptedConversationOngoing() { return m_ScriptedConversationInProgress; } bool audScriptAudioEntity::ShouldDuckForScriptedConversation() { return !IsFlagSet(audScriptAudioFlags::ScriptedSpeechDuckingDisabled) && !m_ScriptSetNoDucking && !ShouldNotDuckRadioThisLine() && m_ScriptedConversationInProgress && !m_IsScriptedConversationZit && (!m_PauseConversation || m_PauseWaitingOnCurrentLine) && m_FirstLineHasStarted;//m_DuckingForScriptedConversation; } bool audScriptAudioEntity::ShouldNotDuckRadioThisLine() { return !m_ScriptedConversationInProgress || m_PlayingScriptedConversationLine < 0 || m_PlayingScriptedConversationLine > AUD_MAX_SCRIPTED_CONVERSATION_LINES-1 || !m_ScriptedConversation[m_PlayingScriptedConversationLine].DucksRadio || m_ScriptSetNoDucking; } bool audScriptAudioEntity::ShouldDuckScoreThisLine() { return m_ScriptedConversationInProgress && m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[m_PlayingScriptedConversationLine].DucksScore && !m_ScriptSetNoDucking; } void audScriptAudioEntity::SetNoDuckingForConversationFromScript(bool enable) { m_ScriptSetNoDucking = enable; } s32 audScriptAudioEntity::GetVariationChosenForScriptedLine(u32 filenameHash) { if(!IsScriptedConversationOngoing()) return -1; for(int i=0; i= 0) { s32 variationNum = m_ScriptedSpeechSlot[entry.SpeechSlotIndex][entry.PreloadSet].VariationNumber; return (variationNum > 0) ? variationNum : 0; } } return -1; } bool audScriptAudioEntity::IsPedInCurrentConversation(const CPed* ped) { if(!IsScriptedConversationOngoing() || !ped) return false; for(int i=0; i= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0 ? m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker].Get() : NULL; if(!currentSpeaker || !interrupter || !interrupter->GetSpeechAudioEntity() || !vehicle->ContainsPed(currentSpeaker) || !vehicle->ContainsPed(interrupter)) return; u32 playTime = ~0U; bool hasNoMarkers = false; m_SpeechRestartOffset = currentSpeaker->GetSpeechAudioEntity()->GetConversationInterruptionOffset(playTime, hasNoMarkers); m_LineCheckedForRestartOffset = m_PlayingScriptedConversationLine; if(hasNoMarkers) m_SpeechRestartOffset = 0; u32 currentTime = g_AudioEngine.GetTimeInMilliseconds(); m_ColLevel = AUD_INTERRUPT_LEVEL_IN_AIR; m_Interrupter = interrupter; m_TriggeredAirborneInterrupt = true; m_TimeToInterrupt = currentTime; m_TimeLastRegisteredCarInAir = currentTime; naConvDisplayf("RegisterCarInAir() - time: %u m_SpeechRestartOffset: %u m_TimeToInterrupt: %u, interrupter: %s", currentTime, m_SpeechRestartOffset, m_TimeToInterrupt, interrupter ? interrupter->GetModelName() : "NULL"); } CPed* audScriptAudioEntity::PickRandomInterrupterInVehicle(CVehicle* vehicle) { if(vehicle) { if( CNetwork::IsGameInProgress() && vehicle->GetSeatManager()) { const s32 iNumSeats = vehicle->GetSeatManager()->GetMaxSeats(); s32 Random = fwRandom::GetRandomNumberInRange(0, iNumSeats); for (s32 C = 0; C < vehicle->GetSeatManager()->GetMaxSeats(); C++) { s32 TestPassenger = (Random + C) % iNumSeats; if (vehicle->GetSeatManager()->GetPedInSeat(TestPassenger)&& TestPassenger != vehicle->GetDriverSeat()) { CPed * pedInSeat = vehicle->GetSeatManager()->GetPedInSeat(TestPassenger); if (!pedInSeat->IsPlayer()) { return pedInSeat; } } } } else { return audEngineUtil::ResolveProbability(0.5f) BANK_ONLY(|| g_ForceNonPlayerInterrupt) ? vehicle->PickRandomPassenger() : vehicle->GetDriver(); } } return NULL; } void audScriptAudioEntity::RegisterSlowMoInterrupt(SlowMoType slowMoType) { if(!IsScriptedConversationOngoing() || m_AnimTriggersControllingConversation) return; if(m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[m_PlayingScriptedConversationLine].DontInterruptForSpecialAbility) return; //if(m_ScriptedConversation[m_PlayingScriptedConversationLine].HasBeenInterruptedForSpecialAbility) //{ if(!g_AudioEngine.GetSoundManager().IsPaused(4)) { naConvDisplayf("Registering conversation slow mo interrupt. %u", g_AudioEngine.GetTimeInMilliseconds()); g_AudioEngine.GetSoundManager().Pause(4); m_ColLevel = AUD_INTERRUPT_LEVEL_SLO_MO; m_ScriptedSpeechTimerPaused = true; m_PauseConversation = true; //make sure we don't get stuck if( slowMoType == AUD_SLOWMO_SPECIAL) { m_InSlowMoForSpecialAbility = true; } if(slowMoType == AUD_SLOWMO_PAUSEMENU) m_InSlowMoForPause = true; if (m_LastDisplayedTextBlock>0 && (m_PlayingScriptedConversationLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES-1 || !m_ScriptedConversation[m_PlayingScriptedConversationLine+1].DoNotRemoveSubtitle)) { #if __BANK sm_ScriptedLineDebugInfo[0] = '\0'; #endif CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(m_LastDisplayedTextBlock, false); m_LastDisplayedTextBlock = -1; // to avoid trying to cancel the same subtitle repeatedly. } } //return; //} /*m_ScriptedConversation[m_PlayingScriptedConversationLine].HasBeenInterruptedForSpecialAbility = true; u32 currentTime = g_AudioEngine.GetTimeInMilliseconds(); CPed* currentSpeaker = m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES ? m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker].Get() : NULL; u32 playTime = ~0U; bool hasNoMarkers = false; m_SpeechRestartOffset = currentSpeaker ? currentSpeaker->GetSpeechAudioEntity()->GetConversationInterruptionOffset(playTime, hasNoMarkers) : 0; m_LineCheckedForRestartOffset = m_PlayingScriptedConversationLine; if(hasNoMarkers) m_SpeechRestartOffset = 0; m_ColLevel = AUD_INTERRUPT_LEVEL_SLO_MO; m_Interrupter = currentSpeaker; m_TimeToInterrupt = currentTime;*/ } void audScriptAudioEntity::StopSlowMoInterrupt() { naConvDisplayf("Unregistering conversation slow mo interrupt. %u", g_AudioEngine.GetTimeInMilliseconds()); m_ColLevel = AUD_INTERRUPT_LEVEL_COLLISION_NONE; if(m_TimeToRestartConversation == 0) m_TimeToRestartConversation = g_AudioEngine.GetTimeInMilliseconds() + 500; m_InSlowMoForPause = false; if(g_AudioEngine.GetSoundManager().IsPaused(4)) { if(!m_PausedAndRepeatingLine && m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && !IsContextAnSFXCommand(m_ScriptedConversation[m_PlayingScriptedConversationLine].Context)) ShowSubtitles(); g_AudioEngine.GetSoundManager().UnPause(4); m_ScriptedSpeechTimerPaused = false; } } void audScriptAudioEntity::RegisterThrownFromVehicle(CPed* ped) { if(!ped) return; //if we can catch this early enough, but right now, all the vehicle occupants are showing up null...bummer /*if(ped->GetMyVehicle() && ped->GetMyVehicle()->GetDriver() == ped && ped->GetMyVehicle()->GetVehicleType() == VEHICLE_TYPE_BIKE) { const CSeatManager* seatManager = ped->GetMyVehicle()->GetSeatManager(); if(seatManager) { for (s32 i = 0; i < seatManager->GetMaxSeats(); i++) { if (seatManager->GetPedInSeat(i) && i != ped->GetMyVehicle()->GetDriverSeat()) { RegisterThrownFromVehicle(seatManager->GetPedInSeat(i)); } } } }*/ u32 currentTime = g_AudioEngine.GetTimeInMilliseconds(); if(!m_ScriptedConversationInProgress) { audSpeechAudioEntity* speechEnt = ped->GetSpeechAudioEntity(); if(speechEnt) { audDamageStats damageStats; damageStats.Fatal = false; damageStats.IsFromAnim = true; //stop any pain level randomization damageStats.RawDamage = g_HealthLostForHighPain + 1.0f; damageStats.DamageReason = AUD_DAMAGE_REASON_DEFAULT; damageStats.PedWasAlreadyDead = ped->ShouldBeDead(); speechEnt->InflictPain(damageStats); speechEnt->SetHasBeenThrownFromVehicle(); } } else { for(int i=0; i= g_MinHighReactionDamage) { colLevel = AUD_INTERRUPT_LEVEL_COLLISION_HIGH; } else if(applyDamage >= g_MinMediumReactionDamage) { colLevel = AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM; } else if(applyDamage < g_MinLowReactionDamage) { return; } if(colLevel >= AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM && currentTime - m_TimeOfLastCollisionCDI > 30000) { g_AudioScannerManager.TriggerCopDispatchInteraction(SCANNER_CDIT_REPORT_SUSPECT_CRASHED_VEHICLE); m_TimeOfLastCollisionCDI = currentTime; } if(currentTime - m_TimeOfLastChopCollisionReaction > 4000) { fwEntity* childAttachment = vehicle->GetChildAttachment(); while(childAttachment) { if(childAttachment->GetType() == ENTITY_TYPE_PED) { CPed* childAttPed = static_cast(childAttachment); if(childAttPed && childAttPed->GetPedType() == PEDTYPE_ANIMAL && childAttPed->GetSpeechAudioEntity()) { if(colLevel == AUD_INTERRUPT_LEVEL_COLLISION_HIGH) { childAttPed->GetSpeechAudioEntity()->PlayAnimalVocalization(0x5E666895); //painDeath phash } else { childAttPed->GetSpeechAudioEntity()->PlayAnimalVocalization(g_BarkPHash); } m_TimeOfLastChopCollisionReaction = currentTime; break; } } childAttachment = childAttachment->GetSiblingAttachment(); } } //if not a scripted conversation, we still might play something if (!IsScriptedConversationOngoing()) { if(vehicle->GetVehicleType() == VEHICLE_TYPE_PLANE || vehicle->GetVehicleType() == VEHICLE_TYPE_HELI) return; if(inflictor && inflictor->GetIsTypeVehicle()) { CVehicle* vehicle = static_cast(inflictor); if(vehicle && vehicle->GetVehicleType() == VEHICLE_TYPE_BICYCLE && vehicle->GetDriver() && vehicle->GetDriver()->GetSpeechAudioEntity()) { vehicle->GetDriver()->GetSpeechAudioEntity()->Say("GENERIC_INSULT", "SPEECH_PARAMS_INTERRUPT"); return; } } s32 numPassengers = vehicle->GetNumberOfPassenger(); CPed* driver = vehicle->GetDriver(); if(numPassengers <= 0 || audEngineUtil::ResolveProbability(0.5f)) { if(driver) { if(applyDamage >= g_MinLowPainBikeReaction && vehicle->GetVehicleType() == VEHICLE_TYPE_BIKE && driver->IsLocalPlayer()) { audDamageStats damageStats; damageStats.Fatal = false; damageStats.IsFromAnim = true; //not really, but this forces the pain level to stay low damageStats.RawDamage = 1.0f; damageStats.DamageReason = AUD_DAMAGE_REASON_DEFAULT; driver->GetSpeechAudioEntity()->InflictPain(damageStats); } else if(colLevel == AUD_INTERRUPT_LEVEL_COLLISION_HIGH && driver->GetSpeechAudioEntity()) driver->GetSpeechAudioEntity()->Say("GENERIC_CURSE_MED", "SPEECH_PARAMS_INTERRUPT"); } } else if(colLevel >= AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM && vehicle->GetSeatManager()) { s32 maxSeats = vehicle->GetSeatManager()->GetMaxSeats(); if(maxSeats > 0) { int startSeat = audEngineUtil::GetRandomInteger() % maxSeats; for( s32 iSeat = 0; iSeat < maxSeats; iSeat++ ) { int index = (startSeat + iSeat) % maxSeats; CPed* pPed = vehicle->GetSeatManager()->GetPedInSeat(index); // Special case - allow Franklin and Lamar to comment on crashes when being driven in multiplayer even if the player is controlling them const bool allowSpeechFromPlayerPed = CNetwork::IsGameInProgress() && audSpeechAudioEntity::IsMPPlayerPedAllowedToSpeak(pPed); if( pPed && (!pPed->IsLocalPlayer() || allowSpeechFromPlayerPed) && pPed != driver && pPed->GetSpeechAudioEntity()) { if( pPed->GetSpeechAudioEntity()->Say("CRASH_GENERIC_DRIVEN", "SPEECH_PARAMS_INTERRUPT") ) { break; } } } } } m_TimeOfLastCollisionReaction = currentTime; return; } if(g_DisableInterrupts || !IsScriptedConversationOngoing() || m_PauseConversation || !m_ConversationIsInterruptible || (m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && !m_ScriptedConversation[m_PlayingScriptedConversationLine].IsInterruptible)) { naConvDisplayf("Current script conversation interrupted due to a car collision with the player (audScriptAudioEntity::RegisterPlayerCarCollision)."); return; } //Collision triggers tend to come in bunches, which leads to a couple issues. //We don't want to play interruptions right in a row, but once the min time between has passed, we don't want to start an interruption // in the middle of a series of collision triggers...we should wait for the next initial trigger. //randomly pick a person in the car to interrupt in SP CPed* interrupter = PickRandomInterrupterInVehicle(vehicle); CPed* currentSpeaker = m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES ? m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker].Get() : NULL; if(!currentSpeaker || !interrupter || !interrupter->GetSpeechAudioEntity() || !vehicle->ContainsPed(currentSpeaker) || !vehicle->ContainsPed(interrupter)) return; if(inflictor && inflictor->GetIsTypePed()) { //hitting peds should be handled through RegisterCarPedCollision return; } u32 playTime = ~0U; bool hasNoMarkers = false; m_SpeechRestartOffset = currentSpeaker->GetSpeechAudioEntity()->GetConversationInterruptionOffset(playTime, hasNoMarkers); m_LineCheckedForRestartOffset = m_PlayingScriptedConversationLine; if(hasNoMarkers) return; if(colLevel > m_ColLevel) m_ColLevel = colLevel; else return; if( m_ColLevel != AUD_INTERRUPT_LEVEL_COLLISION_HIGH && (m_TimeOfLastCollisionReaction + g_MinTimeBetweenCollisionReactions > currentTime || m_TimeOfLastCollisionTrigger + g_MinTimeBetweenCollisionTriggers > currentTime)) return; //naDisplayf("Player car collision %u", currentTime); m_TimeOfLastCollisionTrigger = currentTime; m_Interrupter = interrupter; m_TimeToLowerCollisionLevel = currentTime + 10000; m_LastInterruptTimeDiff = 0; s32 timeDiff = playTime - m_SpeechRestartOffset; naConvDisplayf("Car crash interrupt hit with time diff %i %s %u", timeDiff, m_ScriptedConversation[0].Context, g_AudioEngine.GetTimeInMilliseconds()); //if we've been given a marker in the future, then there's a small enough amount of time until the next marker to let us finish out // playing then pause if(timeDiff < 0) { m_TimeToInterrupt = currentTime - timeDiff; } else if(timeDiff < 2000) { m_TimeToInterrupt = currentTime; //Adjust overlap time for repeated part of line if(m_ScriptedConversationOverlapTriggerTime != 0) { m_ScriptedConversationOverlapTriggerTime += timeDiff; m_LastInterruptTimeDiff = timeDiff; naConvDebugf2("Adding %u to make trigger time %u at point 5 %s %u", timeDiff, m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } } else { m_Interrupter = NULL; m_TimeToInterrupt = 0; m_TimeToRestartConversation = 0; m_InterruptTriggered = false; } } void audScriptAudioEntity::RegisterCarPedCollision(CVehicle* vehicle, f32 applyDamage, CPed* victim) { //Collision triggers tend to come in bunches, which leads to a couple issues. //We don't want to play interruptions right in a row, but once the min time between has passed, we don't want to start an interruption // in the middle of a series of collision triggers...we should wait for the next initial trigger. u32 currentTime = g_AudioEngine.GetTimeInMilliseconds(); m_TimeOfLastCollision = currentTime; if(victim) { u32 timeOfOldestCollision = ~0U; u32 indexOfOldestCollision = 0; bool victimFound = false; //we use non-paused time for this bookkeeping, as it is compared to the time handed in to PreUpdateService for(int i = 0; i= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[m_PlayingScriptedConversationLine].IsInterruptible)) { naConvDisplayf("Current script conversation interrupted due to a car collision with a ped (audScriptAudioEntity::RegisterCarPedCollision)."); return; } //randomly pick a person in the car to interrupt in SP CPed* interrupter = PickRandomInterrupterInVehicle(vehicle); CPed* currentSpeaker = m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0 ? m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker].Get() : NULL; if(!currentSpeaker || !interrupter || !interrupter->GetSpeechAudioEntity() || !vehicle->ContainsPed(currentSpeaker) || !vehicle->ContainsPed(interrupter)) return; u32 playTime = ~0U; bool hasNoMarkers = false; m_SpeechRestartOffset = currentSpeaker->GetSpeechAudioEntity()->GetConversationInterruptionOffset(playTime, hasNoMarkers); m_LineCheckedForRestartOffset = m_PlayingScriptedConversationLine; if(hasNoMarkers) return; if(AUD_INTERRUPT_LEVEL_COLLISION_LOW > m_ColLevel) m_ColLevel = AUD_INTERRUPT_LEVEL_COLLISION_LOW; else return; if( m_TimeOfLastCollisionReaction + g_MinTimeBetweenCollisionReactions > currentTime || m_TimeOfLastCollisionTrigger + g_MinTimeBetweenCollisionTriggers > currentTime) return; m_TimeOfLastCollisionTrigger = currentTime; m_Interrupter = interrupter; m_TimeToLowerCollisionLevel = currentTime + 10000; m_LastInterruptTimeDiff = 0; s32 timeDiff = playTime - m_SpeechRestartOffset; naConvDebugf1("Ped crash interrupt hit with time diff %i %s %u", timeDiff, m_ScriptedConversation[0].Context, g_AudioEngine.GetTimeInMilliseconds()); //if we've been given a marker in the future, then there's a small enough amount of time until the next marker to let us finish out // playing then pause if(timeDiff < 0) { m_TimeToInterrupt = currentTime - timeDiff; } else if(timeDiff < 2000) { m_TimeToInterrupt = currentTime; //Adjust overlap time for repeated part of line if(m_ScriptedConversationOverlapTriggerTime != 0) { m_ScriptedConversationOverlapTriggerTime += timeDiff; m_LastInterruptTimeDiff = timeDiff; naConvDebugf2("Adding %u to make trigger time %u at point 6 %s %u", timeDiff, m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } } else { m_Interrupter = NULL; m_TimeToInterrupt = 0; m_TimeToRestartConversation = 0; m_InterruptTriggered = false; } } void audScriptAudioEntity::RegisterWantedLevelIncrease(CVehicle* vehicle) { u32 currentTime = g_AudioEngine.GetTimeInMilliseconds(); if(g_DisableInterrupts || !IsScriptedConversationOngoing() || m_PauseConversation || !vehicle || m_IsScriptedConversationAMobileCall || (m_ConversationWasInterruptedForWantedLevel BANK_ONLY(&& !g_AllowMuliWantedInterrupts)) || !m_ConversationIsInterruptible || m_TriggeredAirborneInterrupt || m_ColLevel == AUD_INTERRUPT_LEVEL_SCRIPTED || m_ColLevel == AUD_INTERRUPT_LEVEL_SLO_MO || (m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && !m_ScriptedConversation[m_PlayingScriptedConversationLine].IsInterruptible)) { naConvDisplayf("Current script conversation interrupted due to wanted leave increase (audScriptAudioEntity::RegisterWantedLevelIncrease)."); return; } m_ConversationWasInterruptedForWantedLevel = true; //Collision triggers tend to come in bunches, which leads to a couple issues. //We don't want to play interruptions right in a row, but once the min time between has passed, we don't want to start an interruption // in the middle of a series of collision triggers...we should wait for the next initial trigger. //randomly pick a person in the car to interrupt in SP CPed* interrupter = PickRandomInterrupterInVehicle(vehicle); CPed* currentSpeaker = m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0 ? m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker].Get() : NULL; if(!currentSpeaker || !interrupter || !interrupter->GetSpeechAudioEntity() || !vehicle->ContainsPed(currentSpeaker) || !vehicle->ContainsPed(interrupter)) return; u32 playTime = ~0U; bool hasNoMarkers = false; m_SpeechRestartOffset = currentSpeaker->GetSpeechAudioEntity()->GetConversationInterruptionOffset(playTime, hasNoMarkers); m_LineCheckedForRestartOffset = m_PlayingScriptedConversationLine; if(hasNoMarkers) return; m_ColLevel = AUD_INTERRUPT_LEVEL_WANTED; m_Interrupter = interrupter; m_LastInterruptTimeDiff = 0; s32 timeDiff = playTime - m_SpeechRestartOffset; //if we've been given a marker in the future, then there's a small enough amount of time until the next marker to let us finish out // playing then pause if(timeDiff < 0) { m_TimeToInterrupt = currentTime - timeDiff; } else if(timeDiff < 2000) { m_TimeToInterrupt = currentTime; //Adjust overlap time for repeated part of line if(m_ScriptedConversationOverlapTriggerTime != 0) { m_ScriptedConversationOverlapTriggerTime += timeDiff; m_LastInterruptTimeDiff = timeDiff; naConvDebugf2("Adding %u to make trigger time %u at point 7 %s %u", timeDiff, m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } } else { m_Interrupter = NULL; m_TimeToInterrupt = 0; m_TimeToRestartConversation = 0; m_InterruptTriggered = false; } } void audScriptAudioEntity::RegisterScriptRequestedInterrupt(CPed* interrupter, const char* context, const char* voiceName, bool pause BANK_ONLY(, bool force)) { u32 currentTime = g_AudioEngine.GetTimeInMilliseconds(); if(m_ColLevel == AUD_INTERRUPT_LEVEL_SLO_MO) return; #if !__FINAL u32 placeholderVoiceNameHash = 0; char placeholderVoiceName[128] = ""; formatf(placeholderVoiceName, sizeof(placeholderVoiceName), "%s_PLACEHOLDER", voiceName); placeholderVoiceNameHash = atStringHash(placeholderVoiceName); #endif u32 voiceNameHash = atStringHash(voiceName); if(!IsScriptedConversationOngoing() || m_PauseConversation || m_PlayingScriptedConversationLine < 0 || m_PlayingScriptedConversationLine > AUD_MAX_SCRIPTED_CONVERSATION_LINES) { NOTFINAL_ONLY(bool success = true); if(interrupter->GetSpeechAudioEntity()) NOTFINAL_ONLY(success = AUD_SPEECH_SAY_FAILED != )interrupter->GetSpeechAudioEntity()->Say(context, g_ForceScriptedInterrupts ? "SPEECH_PARAMS_FORCE" : "SPEECH_PARAMS_INTERRUPT", voiceNameHash); #if !__FINAL if(!success && g_EnablePlaceholderDialogue) interrupter->GetSpeechAudioEntity()->Say(context, g_ForceScriptedInterrupts ? "SPEECH_PARAMS_FORCE" : "SPEECH_PARAMS_INTERRUPT", placeholderVoiceNameHash); #endif return; } CPed* currentSpeaker = m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0 ? m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker].Get() : NULL; if(g_DisableInterrupts || !currentSpeaker || (m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && !m_ScriptedConversation[m_PlayingScriptedConversationLine].IsInterruptible)) { naConvDisplayf("Current script conversation interrupted from script (audScriptAudioEntity::RegisterScriptReqestedInterrupt)"); } if(!FindPlayerPed() || !FindPlayerPed()->GetMyVehicle() || (interrupter && (FindPlayerPed()->GetMyVehicle() != interrupter->GetMyVehicle()))) { BANK_ONLY(if(!force)) return; } u32 playTime = ~0U; bool hasNoMarkers = false; m_SpeechRestartOffset = currentSpeaker ? currentSpeaker->GetSpeechAudioEntity()->GetConversationInterruptionOffset(playTime, hasNoMarkers) : 0; m_LineCheckedForRestartOffset = m_PlayingScriptedConversationLine; if(hasNoMarkers && !g_ForceScriptedInterrupts && !IsFlagSet(audScriptAudioFlags::ForceConversationInterrupt)) return; if(pause) { PauseScriptedConversation(false,true,true); } if(m_TimeToInterrupt != 0) { //We're overriding another interrupt, so make sure we adjust overlap trigger time m_ScriptedConversationOverlapTriggerTime -= m_LastInterruptTimeDiff; naConvDebugf2("Subtracting %u to make trigger time %u at point 9 %s %u", m_LastInterruptTimeDiff, m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } m_ColLevel = AUD_INTERRUPT_LEVEL_SCRIPTED; m_Interrupter = interrupter; safecpy(m_InterruptingContext, context); m_InterruptingVoiceNameHash = voiceNameHash; NOTFINAL_ONLY(m_InterruptingPlaceholderVoiceNameHash = placeholderVoiceNameHash;) s32 timeDiff = playTime - m_SpeechRestartOffset; naConvDebugf1("Scripted interrupt hit with time diff %i %s %u", timeDiff, m_ScriptedConversation[0].Context, g_AudioEngine.GetTimeInMilliseconds()); //if we've been given a marker in the future, then there's a small enough amount of time until the next marker to let us finish out // playing then pause if(timeDiff < 0) { m_TimeToInterrupt = currentTime - timeDiff; } else { m_TimeToInterrupt = currentTime; //Adjust overlap time for repeated part of line if(m_ScriptedConversationOverlapTriggerTime != 0) { m_ScriptedConversationOverlapTriggerTime += timeDiff; naConvDebugf2("Adding %u to make trigger time %u at point 8 %s %u", timeDiff, m_ScriptedConversationOverlapTriggerTime, m_PlayingScriptedConversationLine >= 0 ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "[line -1]", g_AudioEngine.GetTimeInMilliseconds()); } } } void audScriptAudioEntity::InterruptConversation() { u32 currentTime = g_AudioEngine.GetTimeInMilliseconds(); m_LastInterruptTimeDiff = 0; if(m_ColLevel != AUD_INTERRUPT_LEVEL_SLO_MO && m_TimeOfLastCollisionReaction + g_MinTimeBetweenCollisionReactions > currentTime) return; naConvDisplayf("Interrupting conversation %s %u", m_ScriptedConversation[0].Context, g_AudioEngine.GetTimeInMilliseconds()); audSpeechAudioEntity* speechAudioEntity = &m_SpeechAudioEntity; bool success = false; bool isPlayerInterrupter = m_Interrupter && m_Interrupter->IsPlayer(); m_TimeOfLastCollisionReaction = currentTime; m_InterruptTriggered = true; m_TimeToInterrupt = 0; CVehicle* playerVehicle = CGameWorld::FindLocalPlayerVehicle(); bool playVocal = true; if(playerVehicle && playerVehicle->InheritsFromHeli()) { playVocal = false; // we don't want the vocalizations in some cases because they are no radio voice and the scripted speech is } switch(m_ColLevel) { case AUD_INTERRUPT_LEVEL_COLLISION_HIGH: if(isPlayerInterrupter || !m_Interrupter) m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; else { if(playVocal) { success = m_Interrupter->NewSayWithParams( #if __BANK g_szHighInterruptContext #else "CRASH_GENERIC_INTERRUPT" #endif , "SPEECH_PARAMS_INTERRUPT"); } if(!success) m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; } break; case AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM: if(isPlayerInterrupter || !m_Interrupter) m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; else { if(playVocal) { success = m_Interrupter->NewSayWithParams( #if __BANK g_szMediumInterruptContext #else "CRASH_GENERIC_INTERRUPT" #endif , "SPEECH_PARAMS_INTERRUPT"); } if(!success) m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; } break; case AUD_INTERRUPT_LEVEL_COLLISION_LOW: //if(isPlayerInterrupter || !m_Interrupter) m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; /*else m_Interrupter->NewSayWithParams( #if __BANK g_szLowInterruptContext #else "GENERIC_SHOCKED_MED" #endif , "SPEECH_PARAMS_INTERRUPT");*/ break; case AUD_INTERRUPT_LEVEL_WANTED: success = m_Interrupter->NewSayWithParams("GENERIC_SHOCKED_HIGH", "SPEECH_PARAMS_INTERRUPT"); if(!success) m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; m_ColLevel = m_TimeToLowerCollisionLevel != 0 ? AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM : AUD_INTERRUPT_LEVEL_COLLISION_NONE; case AUD_INTERRUPT_LEVEL_IN_AIR: m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; m_ColLevel = m_TimeToLowerCollisionLevel != 0 ? AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM : AUD_INTERRUPT_LEVEL_COLLISION_NONE; break; case AUD_INTERRUPT_LEVEL_SLO_MO: m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; break; case AUD_INTERRUPT_LEVEL_SCRIPTED: if( m_Interrupter && m_Interrupter->GetSpeechAudioEntity()) { speechAudioEntity = m_Interrupter->GetSpeechAudioEntity(); } if(speechAudioEntity) { success = speechAudioEntity->Say(m_InterruptingContext, "SPEECH_PARAMS_INTERRUPT", m_InterruptingVoiceNameHash, -1, NULL, 0, -1, 1.0f, true) != AUD_SPEECH_SAY_FAILED; } #if !__FINAL if(!success && speechAudioEntity) { success = speechAudioEntity->Say(m_InterruptingContext, "SPEECH_PARAMS_INTERRUPT", m_InterruptingPlaceholderVoiceNameHash, -1, NULL, 0, -1, 1.0f, true) != AUD_SPEECH_SAY_FAILED; } #endif if(!success) { m_TimeToRestartConversation = currentTime + g_TimeToPauseConvOnCollision; } m_ColLevel = m_TimeToLowerCollisionLevel != 0 ? AUD_INTERRUPT_LEVEL_COLLISION_MEDIUM : AUD_INTERRUPT_LEVEL_COLLISION_NONE; m_InterruptingContext[0] = '\0'; m_InterruptingVoiceNameHash = 0; NOTFINAL_ONLY(m_InterruptingPlaceholderVoiceNameHash = 0;) break; default: m_SpeechRestartOffset = 0; m_LineCheckedForRestartOffset = -1; break; } if(success) { m_InterruptSpeechRequestTime = currentTime; m_CheckInterruptPreloading = true; } //kick off restarted line immediately. We'll cache the predelay applied and use it to adjust the following line's trigger tiem. m_ScriptedConversationOverlapTriggerTime = m_TimeToRestartConversation; if(m_PlayingScriptedConversationLine < 0) { m_ScriptedConversationOverlapTriggerTime = 0; m_OverlapTriggerTimeAdjustedForPredelay = false; } } bool audScriptAudioEntity::IsScriptedConversationAMobileCall() { return m_IsScriptedConversationAMobileCall; } bool audScriptAudioEntity::IsScriptBankLoaded(u32 bankID) { for(u32 loop = 0; loop < AUD_MAX_SCRIPT_BANK_SLOTS; loop++) { if(m_BankSlots[loop].BankSlot->GetBankLoadingStatus(bankID) == audWaveSlot::LOADED) { return true; } } return false; } bool audScriptAudioEntity::IsScriptedConversationPaused() { return m_PauseConversation; } bool audScriptAudioEntity::IsScriptedConversationLoaded() { return m_HighestLoadedLine != -1; } s32 audScriptAudioEntity::GetCurrentScriptedConversationLine() { // If we're not mid-conversation, do nothing if (!m_ScriptedConversationInProgress) { return -1; } return m_PlayingScriptedConversationLine; } s32 audScriptAudioEntity::GetCurrentUnresolvedScriptedConversationLine() { // If we're not mid-conversation, do nothing if (!m_ScriptedConversationInProgress || m_PlayingScriptedConversationLine < 0 || m_PlayingScriptedConversationLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES) { return -1; } return m_ScriptedConversationBuffer[m_PlayingScriptedConversationLine].UnresolvedLineNum; } s32 audScriptAudioEntity::AbortScriptedConversation(bool finishCurrentLine BANK_ONLY(,const char* reason)) { // If we're not mid-conversation, do nothing if (!m_ScriptedConversationInProgress) { return -1; } s32 currentLine = m_PlayingScriptedConversationLine; if (finishCurrentLine) { // We're going to abort asynchronously, after the current line. // On the first Process after we've finished the current line, this'll abort. m_AbortScriptedConversationRequested = true; } else { // We need to finish right now, and be available for another conversation request immediately. if (m_PlayingScriptedConversationLine>=0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0) // Check we've actually started the conversation { // See if they're still talking if (m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker] && m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()) { m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()->StopSpeech(); } else { m_SpeechAudioEntity.StopSpeech(); } } FinishScriptedConversation(); } #if __BANK if( g_ConversationDebugSpew ) { naConvDisplayf("[Conv Spew] Aborting conversation %s Time: %u Reason :%s", m_ScriptedConversation[0].Context, g_AudioEngine.GetTimeInMilliseconds(),reason); } #endif return currentLine; } bool audScriptAudioEntity::ShouldWeAbortScriptedConversation() { CVehicle* playerVehicle = CGameWorld::FindLocalPlayerVehicle(); if (playerVehicle) { if(playerVehicle->IsOnFire() && !g_AllowVehicleOnFireConversations) { naConvWarningf("Aborted conv: vehicle on fire"); return true; } if(playerVehicle->m_nVehicleFlags.bIsDrowning && !g_AllowVehicleDrowningConversations) { if(playerVehicle->GetVehicleType() != VEHICLE_TYPE_PLANE && playerVehicle->GetVehicleType() != VEHICLE_TYPE_HELI && playerVehicle->GetVehicleType() != VEHICLE_TYPE_BLIMP) { naConvWarningf("Aborted conv: vehicle drowning"); return true; } else { m_FramesVehicleIsInWater++; } if(m_FramesVehicleIsInWater > 15) { naConvWarningf("Aborted conv: vehicle drowning"); return true; } } else { m_FramesVehicleIsInWater = 0; } } else { m_FramesVehicleIsInWater = 0; } // Check if all our peds are alive, none of them are on fire, we haven't been busted, and the car's not on fire naConvCErrorf(m_ScriptedConversationInProgress, "Trying to abort scripted conversion but there isn't one active"); for (s32 i=0; iGetIsDeadOrDying()|| m_Ped[i]->IsFatallyInjured() || m_Ped[i]->IsInjured()) && !g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::DisableAbortConversationForDeathAndInjury)) || (g_fireMan.IsEntityOnFire(m_Ped[i]) && !g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::DisableAbortConversationForOnFire)) || m_Ped[i]->m_nDEflags.bFrozen) { if(m_Ped[i]->m_nDEflags.bFrozen) { if(m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && i == m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker && (m_ScriptedConversation[m_PlayingScriptedConversationLine].Volume != AUD_SPEECH_FRONTEND && !m_ScriptedConversationBuffer[i].Headset)) { naConvWarningf("Aborted conv. Ped frozen: voice: %s line: %s interiorFrozen: %s ", m_VoiceName[i], m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "UNKNOWN", m_Ped[i]->m_nDEflags.bFrozenByInterior ? "TRUE" : "FALSE"); return true; } } else { naConvWarningf("Aborted conv: voice: %s line: %s. Ped dead, dying, (fatally) injured, on fire, or ragdolling", m_VoiceName[i], m_PlayingScriptedConversationLine >= 0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES ? m_ScriptedConversation[m_PlayingScriptedConversationLine].Context : "UNKNOWN"); return true; } } } } // Check we're not busted CPed* player = CGameWorld::FindLocalPlayer(); if (player && player->GetIsArrested()) { naConvWarningf("Aborted conv: player arrested"); return true; } return false; } bool audScriptAudioEntity::ShouldWePauseScriptedConversation(u32 timeInMs, bool checkForInAir) { naConvCErrorf(m_ScriptedConversationInProgress, "Asking if we can pause scripted conversation but there isn't one active"); if(m_PlayingScriptedConversationLine >= AUD_MAX_SCRIPTED_CONVERSATION_LINES) { return false; } if(m_PlayingScriptedConversationLine < 0) { if(!m_PauseConversation || !m_PausedForRagdoll) { return false; } } bool isAnyoneRagdolling = false; //Not such an aptly named flag, now that we pause isntead of aborting, but you get the idea if(!g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::DisableAbortConversationForRagdoll) && m_ConversationIsInterruptible) { for (s32 i=0; iGetUsingRagdoll()) { if(m_ConvPedRagDollTimer[i] == 0) m_ConvPedRagDollTimer[i] = timeInMs; //we've been ragdolling for a long time...we better just abort this. if(timeInMs - m_ConvPedRagDollTimer[i] > 5000) { AbortScriptedConversation(false BANK_ONLY(,"Ragdoling for a long time")); } m_ConvPedNotInRagDollTimer[i] = 0; } else { if(m_ConvPedRagDollTimer[i] != 0) { m_ConvPedNotInRagDollTimer[i] = timeInMs; } m_ConvPedRagDollTimer[i] = 0; } } bool thrownFromVehicle = m_TimeThrownFromVehicle[i] != 0 && m_TimeThrownFromVehicle[i] < timeInMs; bool hasBeenRagdollingLongEnough = m_ConvPedRagDollTimer[i] != 0 && timeInMs - m_ConvPedRagDollTimer[i] > g_MinRagdollTimeBeforeConvPause; bool recoveringFromRagdollPause = m_PausedForRagdoll && m_ConvPedNotInRagDollTimer[i] != timeInMs && timeInMs - m_ConvPedNotInRagDollTimer[i] < g_MinNotRagdollTimeBeforeConvRestart; if( thrownFromVehicle || hasBeenRagdollingLongEnough || recoveringFromRagdollPause) { isAnyoneRagdolling = true; if(m_PlayingScriptedConversationLine >= 0 && (thrownFromVehicle || ( hasBeenRagdollingLongEnough && i == m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker )) ) { // B* 1976594 - If we hit 2 interrupts in a row, then it's possible for m_PauseConversation to be set true before // m_PausedForRagdoll is set to true. This causes the m_TimeThrownFromVehicle[i] above to never be reset to 0, // so the conversation never unpauses. m_PausedForRagdoll = true; if(!m_PauseConversation) { bool shouldRepeatForRagolling = false; if(m_Ped[i]->IsLocalPlayer()) { shouldRepeatForRagolling = !g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::DontRepeatLineForPlayerRagdolling); } else { shouldRepeatForRagolling = g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::RepeatLineForRagdolling); } if(shouldRepeatForRagolling) { if(m_ScriptedConversation[m_PlayingScriptedConversationLine].HasRepeatedForRagdoll) { shouldRepeatForRagolling = false; } else { m_ScriptedConversation[m_PlayingScriptedConversationLine].HasRepeatedForRagdoll = true; } } PauseScriptedConversation(false, shouldRepeatForRagolling); } } return true; } } } } //If currently playing line is -1 and we've made it this far, we've already checked that we are currently paused for ragdolling if(m_PlayingScriptedConversationLine < 0 && !isAnyoneRagdolling) { RestartScriptedConversation(); m_PausedForRagdoll = false; return false; } CVehicle* playerVehicle = CGameWorld::FindLocalPlayerVehicle(); if(playerVehicle) { if (playerVehicle->GetVehicleDamage()->GetIsStuck(VEH_STUCK_ON_ROOF|VEH_STUCK_ON_SIDE, 2000)) { return true; } if (!playerVehicle->InheritsFromSubmarine() && !playerVehicle->InheritsFromSubmarineCar() && playerVehicle->GetVehicleAudioEntity() && playerVehicle->GetVehicleAudioEntity()->GetDrowningFactor() > 0.0f) { return true; } //naDisplayf("time in air: %f", playerVehicle->GetTimeInAir()); if(!g_DisableAirborneInterrupts && (checkForInAir || timeInMs - m_TimeOfLastCollision < 2000)) { u32 timePlayersCarInAir = timeInMs - m_TimePlayersCarWasLastOnTheGround; u32 timePlayersCarOnGround = timeInMs - m_TimePlayersCarWasLastInTheAir; if(timePlayersCarInAir > g_MinTimeCarInAirForInterrupt && m_TimeOfLastInAirInterrupt < timeInMs - g_MinTimeCarOnGroundForInterrupt && playerVehicle->GetTransform().GetC().GetZf() <= g_MinCarRollForInterrupt) { if(g_DisableAirborneInterrupts && timeInMs - m_TimeOfLastCollision > 2000) m_TriggeredAirborneInterrupt = true; //trigger interrupt once, then just return true; if(!m_TriggeredAirborneInterrupt) { RegisterCarInAir(playerVehicle); } return true; } else if(m_TriggeredAirborneInterrupt && timePlayersCarInAir == 0) { u32 totalTimeAirborne = timeInMs - m_TimeLastRegisteredCarInAir; //swear and set variables this upon landing if(m_Interrupter) { m_Interrupter->NewSayWithParams( #if __BANK g_szMediumInterruptContext #else "CRASH_GENERIC_INTERRUPT" #endif , "SPEECH_PARAMS_INTERRUPT"); m_InterruptSpeechRequestTime = timeInMs; } //If we've been up for a while, just swear and then kill the conversation if(totalTimeAirborne > g_MinAirBorneTimeToKillConversation) { AbortScriptedConversation(true BANK_ONLY(,"totalTimeAirborne > g_MinAirBorneTimeToKillConversation")); m_TimeOfLastInAirInterrupt = timeInMs; m_TriggeredAirborneInterrupt = false; return false; } //if we're in the air a medium time, restart last line from the start else if(totalTimeAirborne >= g_MaxAirBorneTimeToApplyRestartOffset) { m_SpeechRestartOffset = 0; } m_TimeOfLastInAirInterrupt = timeInMs; m_TriggeredAirborneInterrupt = false; } if(timePlayersCarOnGround < g_MinTimeCarOnGroundToRestart) { return true; } } } if(m_PauseConversation && !m_PauseCalledFromScript && !m_InterruptTriggered) RestartScriptedConversation(); m_PausedForRagdoll = false; return false; } void audScriptAudioEntity::PauseScriptedConversation(bool finishCurrentLine, bool repeatCurrentLine, bool fromScript) { naConvDisplayf("Conversation pause called. FinishLine: %s, repeatLine: %s fromScript: %s inProgress: %s", finishCurrentLine ? "TRUE" : "FALSE", repeatCurrentLine ? "TRUE" : "FALSE", fromScript ? "TRUE" : "FALSE", m_ScriptedConversationInProgress ? "TRUE" : "FALSE"); // If we're not mid-conversation, do nothing if (!m_ScriptedConversationInProgress) { return; } if(!m_PauseCalledFromScript) m_PauseCalledFromScript = fromScript; if(m_PauseConversation && finishCurrentLine) return; if (finishCurrentLine) { // We're going to pause asynchronously, after the current line. m_PauseConversation = true; m_PauseWaitingOnCurrentLine = true; } else { naConvDisplayf("Pausing conversation %s %u", m_ScriptedConversation[0].Context, g_AudioEngine.GetTimeInMilliseconds()); // We need to finish right now. if (m_PlayingScriptedConversationLine>=0 && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0) // Check we've actually started the conversation { // See if they're still talking if (m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker] && m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()) { m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()->StopSpeech(); } else { m_SpeechAudioEntity.StopSpeech(); } // and cancel the current subtitle if (m_LastDisplayedTextBlock>0) { #if __BANK sm_ScriptedLineDebugInfo[0] = '\0'; #endif CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(m_LastDisplayedTextBlock, false); m_LastDisplayedTextBlock = -1; // to avoid trying to cancel the same subtitle repeatedly. // Don't think losing our memory of playing it will ever be a problem. } // higher level code decides whether or not to repeat the line. if (repeatCurrentLine && !m_PausedAndRepeatingLine) { naConvDebugf2("Decrementing playing conversation line 5. %u", g_AudioEngine.GetTimeInMilliseconds()); m_PlayingScriptedConversationLine--; } m_PausedAndRepeatingLine = repeatCurrentLine; } if(m_ConversationDuckingScene) m_ConversationDuckingScene->SetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 0.f); m_PauseConversation = true; m_PauseWaitingOnCurrentLine = false; } SetAllPedsHavingAConversation(false); return; } void audScriptAudioEntity::RestartScriptedConversation(bool startAtMarker, bool fromScript) { if(!m_PauseConversation) { m_TimeToRestartConversation = 0; return; } if(m_PauseCalledFromScript && !fromScript) { #if !__FINAL if(g_AudioEngine.GetTimeInMilliseconds() - m_TimeLastWarnedAboutNonScriptRestart > 3000) { naConvWarningf("Restarting a conversation from code when it was paused from script. Ignoring call to restart."); m_TimeLastWarnedAboutNonScriptRestart = g_AudioEngine.GetTimeInMilliseconds(); } #endif return; } for (s32 i=0; iSetVariableValue(ATSTRINGHASH("apply", 0xe865cde8), 1.f); naConvDisplayf("Restarting conversation %s %u", m_ScriptedConversation[0].Context, g_AudioEngine.GetTimeInMilliseconds()); } void audScriptAudioEntity::SkipToNextScriptedConversationLine() { // If we're not mid-conversation, do nothing if (!m_ScriptedConversationInProgress) { return; } // Stop the current line if (m_PlayingScriptedConversationLine>=0 && m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker >= 0) // Check we've actually started the conversation { // See if they're still talking if (m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker] && m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()) { m_Ped[m_ScriptedConversation[m_PlayingScriptedConversationLine].Speaker]->GetSpeechAudioEntity()->StopSpeech(); } else { m_SpeechAudioEntity.StopSpeech(); } } m_ScriptedConversationOverlapTriggerTime = g_AudioEngine.GetTimeInMilliseconds(); // Don't touch m_PlayingScriptedConversationLine - our next update will trigger that. } void audScriptAudioEntity::SetAircraftWarningSpeechVoice(CVehicle* vehicle) { if(!vehicle || !(vehicle->GetVehicleType() == VEHICLE_TYPE_HELI || vehicle->GetVehicleType() == VEHICLE_TYPE_PLANE) || !vehicle->GetVehicleAudioEntity()) return; m_AircraftWarningSpeechEnt.SetAmbientVoiceName(vehicle->GetVehicleAudioEntity()->AircraftWarningVoiceIsMale() ? g_AircraftWarningMale1Hash : g_AircraftWarningFemale1Hash, true); } void audScriptAudioEntity::TriggerAircraftWarningSpeech(audAircraftWarningTypes warningType) { if(!g_EnableAircraftWarningSpeech) return; if(!FindPlayerPed() || !FindPlayerPed()->GetVehiclePedInside() || (FindPlayerPed()->GetVehiclePedInside()->GetVehicleType() != VEHICLE_TYPE_PLANE && FindPlayerPed()->GetVehiclePedInside()->GetVehicleType() != VEHICLE_TYPE_HELI) ) return; if(naVerifyf(warningType >= 0 && warningType < AUD_AW_NUM_WARNINGS, "Invalid warning type passed to TriggerAircraftWarningSpeech")) { m_AircraftWarningInfo[warningType].ShouldPlay = true; m_AircraftWarningInfo[warningType].LastTimeTriggered = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0); m_ShouldUpdateAircraftWarning = true; } } void audScriptAudioEntity::UpdateAircraftWarning() { if(m_AircraftWarningSpeechEnt.IsAmbientSpeechPlaying()) return; #if __BANK if(PARAM_audiodesigner.Get()) InitAircraftWarningRaveSettings(); #endif u32 currentTime = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0); s8 mostRecent = -1; for(s8 i=0; i info.MaxTimeBetweenTriggerAndPlay || currentTime - info.LastTimePlayed < info.MinTimeBetweenPlay) info.ShouldPlay = false; else if(mostRecent == -1 || info.LastTimeTriggered < m_AircraftWarningInfo[mostRecent].LastTimeTriggered) mostRecent = i; } } if(mostRecent == -1) m_ShouldUpdateAircraftWarning = false; else { m_AircraftWarningSpeechEnt.SayWhenSafe(m_AircraftWarningInfo[mostRecent].Context, "SPEECH_PARAMS_FORCE_FRONTEND"); m_AircraftWarningInfo[mostRecent].LastTimePlayed = currentTime; m_AircraftWarningInfo[mostRecent].ShouldPlay = false; } } s32 audScriptAudioEntity::GetIndexFromScriptThreadId(scrThreadId scriptThreadId) { // Find our index, from the thread id for (s32 i=0; iGetThreadId(); naCErrorf(scriptThreadId, "Failed to get thread id"); BANK_ONLY(m_CurrentScriptName = const_cast(CTheScripts::GetCurrentScriptName())); s32 index = GetIndexFromScriptThreadId(scriptThreadId); // DEBUG // Make sure we're not already in the list of scripts if (index >= 0) { naAssertf(!m_Scripts[index].m_ScriptisShuttingDown, "Trying to add a script that is shutting down, probably something is calling AddScript that doesn't want to"); return index; } // Look through for a free slot s32 i=0; for (i=0; iGetThreadId(); naCErrorf(scriptThreadId, "Failed to get thread id"); s32 index = GetIndexFromScriptThreadId(scriptThreadId); if (index < 0) { return; } m_Scripts[index].m_ScriptShutDownDelay = delay; } void audScriptAudioEntity::RemoveScript(scrThreadId scriptThreadId) { naCErrorf(scriptThreadId, "Trying to remove script with invalid thread id"); s32 scriptIndex = GetIndexFromScriptThreadId(scriptThreadId); if (scriptIndex < 0) { return; } BANK_ONLY(m_CurrentScriptName = ""); if(m_Scripts[scriptIndex].m_ScriptShutDownDelay > 0 && !audNorthAudioEngine::IsScreenFadedOut()) { m_Scripts[scriptIndex].m_ScriptisShuttingDown = true; return; } // Tidy up. // If we're using any banks, dereference them, and stop any sounds that we're currently playing (we may want to reconsider that) bool hasNetworkBanks = false; for(int i=0; i< AUD_USABLE_SCRIPT_BANK_SLOTS; i++) { hasNetworkBanks |= ((m_Scripts[scriptIndex].BankOverNetwork & BIT(i)) != 0); if(m_Scripts[scriptIndex].BankSlotIndex & BIT(i)) { if(m_BankSlots[i].HintLoaded) { naAssertf(m_BankSlots[i].ReferenceCount == 0, "Hinted bankslot with a ref count, looks like something leaked."); m_BankSlots[i].HintLoaded = false; } else { naAssertf(m_BankSlots[i].ReferenceCount, "About to decrement a ref count of 0 - looks like something leaked"); m_BankSlots[i].ReferenceCount--; } if(hasNetworkBanks) { for (s32 j=0; j(CTheScripts::GetCurrentGtaScriptHandler()->GetScriptId())) { m_BankSlots[i].NetworkScripts[j].Invalidate(); Warningf("Invalidating network script id due to script getting removed, this will hopefully fix B* 2355623"); } } } m_Scripts[scriptIndex].BankSlotIndex &= ~BIT(i); m_Scripts[scriptIndex].BankOverNetwork &= ~BIT(i); BANK_ONLY(LogScriptBankRequest("RemoveScript",scriptIndex,m_BankSlots[i].BankId,i)); } } for (s32 i=0; i(i)); } m_ScriptSounds[i].Sound->StopAndForget(); } ResetScriptSound(i); } } // If we're in charge of audio, give that up, and reset anything if (m_ScriptInChargeOfAudio>-1 && m_ScriptInChargeOfAudio==scriptIndex) { ClearUpScriptSetVariables(); m_ScriptInChargeOfAudio = -1; } m_Scripts[scriptIndex].Shutdown(); } void audScriptAudioEntity::ClearUpScriptSetVariables() { DontAbortCarConversationsWithScriptId(false, false, false, m_ScriptInChargeOfAudio); } bool audScriptAudioEntity::RequestScriptBank(const char* bankName, bool bOverNetwork, unsigned playerBits, bool hintRequest) { if(naVerifyf(bankName, "NULL string passed to RequestScriptbank from script %s",const_cast(CTheScripts::GetCurrentScriptName()))) { char validatedBankName[256]; formatf(validatedBankName, "%s", bankName); // Make sure we have the right type of slash for (u32 i=0; i=AUD_INVALID_BANK_ID) { char validatedBankNameWithScript[256]; formatf(validatedBankNameWithScript, "SCRIPT\\%s", validatedBankName); bankId = SOUNDFACTORY.GetBankIndexFromName(validatedBankNameWithScript); //Displayf("%s %x", validatedBankNameWithScript, atStringHash(validatedBankNameWithScript) ); } if(naVerifyf(bankId < AUD_INVALID_BANK_ID, "Audio: Script bank not found: %s: %s from script %s", bankName, validatedBankName,const_cast(CTheScripts::GetCurrentScriptName()))) { if(!hintRequest) { return RequestScriptBank(bankId, bOverNetwork, playerBits); } else { HintScriptBank(bankId, bOverNetwork, playerBits); } } } return false; } void audScriptAudioEntity::HintScriptBank(const char* bankName, bool bOverNetwork, unsigned playerBits) { RequestScriptBank(bankName, bOverNetwork, playerBits, true); } void audScriptAudioEntity::HintScriptBank(u32 bankId, bool bOverNetwork, unsigned playerBits) { // Abort early if audio disabled if ( Unlikely(!g_AudioEngine.IsAudioEnabled()) ) return; s32 scriptIndex = AddScript(); s32 freeBankSlot = -1; //First of all look if the bank is loaded or already hinted. We will get a freeBankSlot if there is one that isn't loaded or hinted. for(int i=0; iGetReferenceCount() == 0 && !m_BankSlots[i].HintLoaded) { freeBankSlot = i; } } } } else if (m_BankSlots[i].BankId == bankId ) { // already loaded or hinted. if((m_BankSlots[i].ReferenceCount != 0 || m_BankSlots[i].HintLoaded)) { return; } else { // bank is free but still referencing to bankId, use it. freeBankSlot = i; break; } } else if (m_BankSlots[i].ReferenceCount == 0) { // as a first pass only get the freeBankSlot if the bank isn't hinted. if (m_BankSlots[i].BankSlot) { if (m_BankSlots[i].BankSlot->GetReferenceCount() == 0 && !m_BankSlots[i].HintLoaded) { freeBankSlot = i; } } } } if (freeBankSlot == -1) { // If all the banks are loaded or hinted, look if we can kick off any hinted one for(int i=0; iGetReferenceCount() == 0) { ScriptBankNoLongerNeeded(m_BankSlots[i].BankId); freeBankSlot = i; break; } } } } if (freeBankSlot == -1) { audWarningf("No free bank slots available"); BANK_ONLY(LogScriptBanks()); return; } else { audWarningf("No free bank slots avaliable, altough some are hinted."); BANK_ONLY(LogScriptBanks()); return; } } // We have a free slot - load into it, up its ref count, and point our script at it. // We're free to load the bank into this slot. // Don't bother instantly seeing if it's loaded - it won't be. m_BankSlots[freeBankSlot].BankSlot->LoadBank(bankId); m_BankSlots[freeBankSlot].BankNameHash = atStringHash(audWaveSlot::GetBankName(bankId)); Assign(m_BankSlots[freeBankSlot].BankId, bankId); m_BankSlots[freeBankSlot].HintLoaded = true; m_BankSlots[freeBankSlot].State = AUD_SCRIPT_BANK_LOADING; BANK_ONLY(LogScriptBankRequest("HINT",scriptIndex,bankId,freeBankSlot)); if(bOverNetwork && NetworkInterface::IsGameInProgress()) { CAudioBankRequestEvent::Trigger(atStringHash(audWaveSlot::GetBankName(bankId)), static_cast(CTheScripts::GetCurrentGtaScriptHandler()->GetScriptId()), true, playerBits); } // don't record hinted banks to replay } audWaveSlot *audScriptAudioEntity::AllocateScriptBank() { for (int i=0; i<(AUD_USABLE_SCRIPT_BANK_SLOTS); i++) { // See if this slot's empty, and keep a note of it, in case we don't find the one we want already loaded if (m_BankSlots[i].ReferenceCount == 0) { Assign(m_BankSlots[i].BankId, AUD_INVALID_BANK_ID); m_BankSlots[i].BankNameHash = g_NullSoundHash; m_BankSlots[i].ReferenceCount = 1; m_BankSlots[i].State = AUD_SCRIPT_BANK_QUEUED; return m_BankSlots[i].BankSlot; } } return NULL; } bool audScriptAudioEntity::FreeUpScriptBank(audWaveSlot *slot) { for(s32 i=0; i< AUD_MAX_SCRIPT_BANK_SLOTS; i++) { if(slot == m_BankSlots[i].BankSlot) { naAssertf(m_BankSlots[i].ReferenceCount, "About to decrement a ref count of 0 on slot %d - looks like something leaked", i); m_BankSlots[i].ReferenceCount--; return true; } } return false; } bool audScriptAudioEntity::RequestScriptBank(u32 bankId, bool bOverNetwork, unsigned playerBits) { // Abort early if audio disabled if ( Unlikely(!g_AudioEngine.IsAudioEnabled()) ) return true; s32 scriptIndex = AddScript(); s32 freeBankSlot = -1; // We request this over and over again, until it returns true, so only want to up the ref count on the slot the first time. // After the first time, we have the slot index stored in our script array, so use that to determine if we need to up the count. for(int i=0; iGetLoadedBankName() : "NULL BANK SLOT"); BANK_ONLY(m_TimeLoadingScriptBank = 0); if(m_BankSlots[i].HintLoaded) { m_BankSlots[i].HintLoaded = false; m_BankSlots[i].ReferenceCount = 1; } //BANK_ONLY(LogScriptBankRequest("REQUEST WITH BANK SLOT LOADED",scriptIndex,bankId,i)); return true; } else { // It's loading - see if it's done already audWaveSlot::audWaveSlotLoadStatus loadStatus = m_BankSlots[i].BankSlot->GetBankLoadingStatus(bankId); if (loadStatus == audWaveSlot::LOADED) { naAssertf(audWaveSlot::FindLoadedBankWaveSlot(bankId), "Loaded wave slot not found in the loaded bank waveslot table: Id: %u name %s",bankId,m_BankSlots[i].BankSlot ? m_BankSlots[i].BankSlot->GetLoadedBankName() : "NULL BANK SLOT"); // It's loaded m_BankSlots[i].State = AUD_SCRIPT_BANK_LOADED; BANK_ONLY(m_TimeLoadingScriptBank = 0); if(m_BankSlots[i].HintLoaded) { m_BankSlots[i].HintLoaded = false; m_BankSlots[i].ReferenceCount = 1; } BANK_ONLY(LogScriptBankRequest("REQUEST WITH BANKSLOT LOADING",scriptIndex,bankId,i)); return true; } else { // We should defo be loading, or we're in a state muddle naCErrorf(loadStatus == audWaveSlot::LOADING, "We should be loading, or we're in state middle, loadstatus: %d", loadStatus); #if __BANK m_TimeLoadingScriptBank += fwTimer::GetTimeStepInMilliseconds(); if(m_TimeLoadingScriptBank > 16000) { LogScriptBanks(); } #endif return false; } } } } // We don't already have a bank slot allocated. // Cycle through all our script slots for (int i=0; i<(AUD_USABLE_SCRIPT_BANK_SLOTS); i++) { if (m_BankSlots[i].BankId == bankId) { // Something's already using it, up the ref count, point at it, and return depending on whether it's already loaded. m_BankSlots[i].ReferenceCount++; m_Scripts[scriptIndex].BankSlotIndex |= BIT(i); if(bOverNetwork && NetworkInterface::IsGameInProgress()) { BANK_ONLY(LogScriptBankRequest("REQUEST OVER NETWORK:",scriptIndex,bankId,i)); m_Scripts[scriptIndex].BankOverNetwork |= BIT(i); m_Scripts[scriptIndex].PlayerBits |= playerBits; CAudioBankRequestEvent::Trigger(atStringHash(audWaveSlot::GetBankName(bankId)), static_cast(CTheScripts::GetCurrentGtaScriptHandler()->GetScriptId()), true, playerBits); } if (m_BankSlots[i].State == AUD_SCRIPT_BANK_LOADED) { naAssertf(audWaveSlot::FindLoadedBankWaveSlot(m_BankSlots[i].BankId), "Loaded script bank: Id %u name %s points to a slot not found in the loaded bank waveslot table",bankId,m_BankSlots[i].BankSlot ? m_BankSlots[i].BankSlot->GetLoadedBankName() : "NULL BANK SLOT"); BANK_ONLY(m_TimeLoadingScriptBank = 0); m_BankSlots[i].HintLoaded = false; BANK_ONLY(LogScriptBankRequest("REQUEST IN USE LOADED",scriptIndex,bankId,i)); return true; } else { // It's loading - see if it's done already audWaveSlot::audWaveSlotLoadStatus loadStatus = m_BankSlots[i].BankSlot->GetBankLoadingStatus(bankId); if (loadStatus == audWaveSlot::LOADED) { naAssertf(audWaveSlot::FindLoadedBankWaveSlot(bankId), "Loaded wave slot not found in the loaded bank waveslot table: Id: %u name %s",bankId,m_BankSlots[i].BankSlot ? m_BankSlots[i].BankSlot->GetLoadedBankName() : "NULL BANK SLOT"); // It's loaded m_BankSlots[i].State = AUD_SCRIPT_BANK_LOADED; BANK_ONLY(m_TimeLoadingScriptBank = 0); m_BankSlots[i].HintLoaded = false; BANK_ONLY(LogScriptBankRequest("REQUEST IN USE LOADING",scriptIndex,bankId,i)); return true; } else { // We should defo be loading, or we're in a state muddle naCErrorf(loadStatus == audWaveSlot::LOADING, "We should be loading, or we're in state middle, loadstatus: %d", loadStatus); #if __BANK m_TimeLoadingScriptBank += fwTimer::GetTimeStepInMilliseconds(); if(m_TimeLoadingScriptBank > 16000) { LogScriptBanks(); } #endif return false; } } } // See if this slot's empty, and keep a note of it, in case we don't find the one we want already loaded if (m_BankSlots[i].ReferenceCount == 0) { // Make sure nothing's still playing out of it - might take a few frames to free up, or even longer for a sound to release. if (m_BankSlots[i].BankSlot) { if (m_BankSlots[i].BankSlot->GetReferenceCount() == 0 && !m_BankSlots[i].HintLoaded) { freeBankSlot = i; } } else { // We're probably running -noaudiothread - pretend we're loaded already Assign(m_BankSlots[i].BankId, bankId); m_BankSlots[i].BankNameHash = atStringHash(audWaveSlot::GetBankName(bankId)); m_BankSlots[i].ReferenceCount = 1; m_BankSlots[i].HintLoaded = false; m_BankSlots[i].State = AUD_SCRIPT_BANK_LOADED; m_Scripts[scriptIndex].BankSlotIndex |= BIT(i); if(bOverNetwork && NetworkInterface::IsGameInProgress()) { BANK_ONLY(LogScriptBankRequest("REQUEST OVER NETWORK: NOAUDIO",scriptIndex,bankId,i)); m_Scripts[scriptIndex].BankOverNetwork |= BIT(i); m_Scripts[scriptIndex].PlayerBits |= playerBits; CAudioBankRequestEvent::Trigger(atStringHash(audWaveSlot::GetBankName(bankId)), static_cast(CTheScripts::GetCurrentGtaScriptHandler()->GetScriptId()), true, playerBits); } BANK_ONLY(LogScriptBankRequest("REQUEST NOAUDIO",scriptIndex,bankId,i)); BANK_ONLY(m_TimeLoadingScriptBank = 0); return true; } } } // We don't have the bank already loaded (or loading), so see if we found a free one while searching. if (freeBankSlot == -1) { // Check if we have any slot loaded with a hint request, if so, use it. for (int i=0; i<(AUD_USABLE_SCRIPT_BANK_SLOTS); i++) { // See if this slot's empty, and keep a note of it, in case we don't find the one we want already loaded if (m_BankSlots[i].HintLoaded) { // Make sure nothing's still playing out of it - might take a few frames to free up, or even longer for a sound to release. if (m_BankSlots[i].BankSlot) { if (m_BankSlots[i].BankSlot->GetReferenceCount() == 0) { ScriptBankNoLongerNeeded(m_BankSlots[i].BankId); freeBankSlot = i; } } } } if (freeBankSlot == -1) { naAssertf(false,"No free bank slots available"); #if 0 // __BANK for (int i=0; i<(AUD_MAX_AMBIENT_BANK_SLOTS); i++) { Displayf("Ref Count for bank %d = %d", i, m_BankSlots[i].ReferenceCount); if (m_BankSlots[i].BankSlot) { Displayf("Sound Ref count %d", m_BankSlots[i].BankSlot->GetReferenceCount()); } } #endif BANK_ONLY(LogScriptBanks()); return false; } } // We have a free slot - load into it, up its ref count, and point our script at it. // We're free to load the bank into this slot. // Don't bother instantly seeing if it's loaded - it won't be. m_BankSlots[freeBankSlot].BankSlot->LoadBank(bankId); Assign(m_BankSlots[freeBankSlot].BankId, bankId); m_BankSlots[freeBankSlot].BankNameHash = atStringHash(audWaveSlot::GetBankName(bankId)); m_BankSlots[freeBankSlot].ReferenceCount = 1; m_BankSlots[freeBankSlot].State = AUD_SCRIPT_BANK_LOADING; m_Scripts[scriptIndex].BankSlotIndex |= BIT(freeBankSlot); BANK_ONLY(LogScriptBankRequest("REQUEST NEW",scriptIndex,bankId,freeBankSlot)); if(bOverNetwork && NetworkInterface::IsGameInProgress()) { BANK_ONLY(LogScriptBankRequest("REQUEST OVER NETWORK: NEW",scriptIndex,bankId,freeBankSlot)); m_Scripts[scriptIndex].BankOverNetwork |= BIT(freeBankSlot); m_Scripts[scriptIndex].PlayerBits |= playerBits; CAudioBankRequestEvent::Trigger(atStringHash(audWaveSlot::GetBankName(bankId)), static_cast(CTheScripts::GetCurrentGtaScriptHandler()->GetScriptId()), true, playerBits); } #if GTA_REPLAY if(CReplayMgr::ShouldRecord()) { CReplayMgr::RecordFx(CPacketSndLoadScriptWaveBank(freeBankSlot, m_BankSlots[freeBankSlot].BankNameHash)); } #endif return false; // as its not actually loaded yet. } #if GTA_REPLAY bool audScriptAudioEntity::ReplayLoadScriptBank(u32 bankNameHash, u32 bankSlot) { u32 bankId = SOUNDFACTORY.GetBankIndexFromName(bankNameHash); if(bankId == ~0U) { Displayf("Attempt to load script wave bank that does not exist %u(name hash)", bankNameHash); // we return true which stops the preload attempts return true; } if(m_BankSlots[bankSlot].ReferenceCount == 0) { // Make sure nothing's still playing out of it - might take a few frames to free up, or even longer for a sound to release. if (m_BankSlots[bankSlot].BankSlot) { if (m_BankSlots[bankSlot].BankSlot->GetReferenceCount() != 0) { Displayf("Stalling due to sounds playing from an existing script bank %u(name hash) ref count(%d)", bankNameHash, m_BankSlots[bankSlot].BankSlot->GetReferenceCount()); return false; } } m_BankSlots[bankSlot].BankSlot->LoadBank(bankId); Assign(m_BankSlots[bankSlot].BankId, bankId); m_BankSlots[bankSlot].BankNameHash = atStringHash(audWaveSlot::GetBankName(bankId)); m_BankSlots[bankSlot].ReferenceCount = 1; m_BankSlots[bankSlot].State = AUD_SCRIPT_BANK_LOADING; m_BankSlots[bankSlot].HintLoaded = false; // we won't have loaded yet return false; } else if(m_BankSlots[bankSlot].BankNameHash == bankNameHash) { audWaveSlot::audWaveSlotLoadStatus loadStatus = m_BankSlots[bankSlot].BankSlot->GetBankLoadingStatus(bankId); if(loadStatus == audWaveSlot::LOADED) { m_BankSlots[bankSlot].State = AUD_SCRIPT_BANK_LOADED; return true; } else if(loadStatus == audWaveSlot::FAILED) { return true; } } else if(m_BankSlots[bankSlot].ReferenceCount > 0 && m_BankSlots[bankSlot].BankNameHash != bankNameHash) { if (m_BankSlots[bankSlot].BankSlot) { if (m_BankSlots[bankSlot].BankSlot->GetReferenceCount() != 0) { // sound still ref bank so we can't load yet, return true for preload return false; } } // setup new bank load - only do this if the bank isn't already loaded, to prevent spamming load requests (which may block other legitimate loads) if(m_BankSlots[bankSlot].BankSlot->GetLoadedBankId() != bankId) { m_BankSlots[bankSlot].BankSlot->LoadBank(bankId); } Assign(m_BankSlots[bankSlot].BankId, bankId); m_BankSlots[bankSlot].BankNameHash = atStringHash(audWaveSlot::GetBankName(bankId)); m_BankSlots[bankSlot].ReferenceCount = 1; m_BankSlots[bankSlot].State = AUD_SCRIPT_BANK_LOADING; m_BankSlots[bankSlot].HintLoaded = false; // we won't have loaded yet return false; } return false; } void audScriptAudioEntity::ClearRecentPedCollisions() { m_TimeOfLastCarPedCollision = 0; m_TimeLastTriggeredPedCollisionScreams = 0; for(int i=0; i=AUD_INVALID_BANK_ID) { char validatedBankNameWithScript[256]; formatf(validatedBankNameWithScript, "SCRIPT\\%s", validatedBankName); bankId = SOUNDFACTORY.GetBankIndexFromName(validatedBankNameWithScript); } ScriptBankNoLongerNeeded(bankId); } void audScriptAudioEntity::ScriptBankNoLongerNeeded(u32 bankId) { #if GTA_REPLAY if(CReplayMgr::IsEditModeActive()) { return; } #endif s32 scriptIndex = AddScript(); if (naVerifyf(scriptIndex >= 0 && scriptIndex(CTheScripts::GetCurrentGtaScriptHandler()->GetScriptId()), false, m_Scripts[scriptIndex].PlayerBits); } BANK_ONLY(LogScriptBankRequest("FREE BANK ID",scriptIndex,m_BankSlots[i].BankId,i)); } } } } } void audScriptAudioEntity::ScriptBankNoLongerNeeded() { #if GTA_REPLAY if(CReplayMgr::IsEditModeActive()) { return; } #endif // Abort early if audio disabled if ( Unlikely(!g_AudioEngine.IsAudioEnabled()) ) return; s32 scriptIndex = AddScript(); bool hasNetworkBanks = false; u32 playerBits = AUD_NET_ALL_PLAYERS; if (naVerifyf(scriptIndex >= 0 && scriptIndex(CTheScripts::GetCurrentGtaScriptHandler()->GetScriptId()), false, playerBits); } } bool audScriptAudioEntity::RequestScriptBankFromNetwork(u32 bankHash, CGameScriptId& scriptId) { // Abort early if audio disabled if ( Unlikely(!g_AudioEngine.IsAudioEnabled()) ) return true; s32 bankSlot = -1; bool hadBank = false; u32 bankId = g_AudioEngine.GetSoundManager().GetFactory().GetBankNameIndexFromHash(bankHash); // Cycle through all our bank slots for (int i=0; i<(AUD_USABLE_SCRIPT_BANK_SLOTS); i++) { if (m_BankSlots[i].BankId == bankId) { bankSlot = i; naDebugf3("RequestBank: Bank ID in use. BankSlot:%d, BankID:%d", i, bankId); BANK_ONLY(LogNetworkScriptBankRequest("REQUEST OVER NETWORK: IN USE",scriptId.GetScriptName(),bankId,i)); hadBank = true; break; } } // no bank slot, so find one if(bankSlot == -1) { // See if this slot's empty, and keep a note of it, in case we don't find the one we want already loaded for (int i=0; i<(AUD_USABLE_SCRIPT_BANK_SLOTS); i++) { if (m_BankSlots[i].ReferenceCount == 0) { // Make sure nothing's still playing out of it - might take a few frames to free up, or even longer for a sound to release. if (m_BankSlots[i].BankSlot) { if (m_BankSlots[i].BankSlot->GetReferenceCount() == 0) { bankSlot = i; break; } } else { // We're probably running -noaudiothread - pretend we're loaded already Assign(m_BankSlots[i].BankId, bankId); m_BankSlots[i].BankNameHash = bankHash; m_BankSlots[i].ReferenceCount = 1; m_BankSlots[i].State = AUD_SCRIPT_BANK_LOADED; bankSlot = i; BANK_ONLY(LogNetworkScriptBankRequest("REQUEST NOAUDIO OVER NETWORk",scriptId.GetScriptName(),bankId,i)); break; } } } } naDebugf3("RequestBank: Using Slot - BankSlot:%d, BankID:%d", bankSlot, bankId); BANK_ONLY(LogNetworkScriptBankRequest("REQUEST OVER NETWORK: USING SLOT",scriptId.GetScriptName(),bankId,bankSlot)); // check that we have a valid bank slot if(!(bankSlot >= 0)) { Warningf("Could not allocate a bank slot!"); return false; } // if we didn't already have this bank, load it if(!hadBank) { m_BankSlots[bankSlot].BankSlot->LoadBank(bankId); Assign(m_BankSlots[bankSlot].BankId, bankId); m_BankSlots[bankSlot].BankNameHash = bankHash; } // check if we have a reference from this script - if not, add one bool bFound = false; for (int i=0; i<(AUD_MAX_SCRIPT_NETWORK_BANK_SLOTS);i++) { if (m_BankSlots[bankSlot].NetworkScripts[i] == scriptId) { bFound = true; naDisplayf("RequestBank: Found Script Reference - NetSlot:%d", i); BANK_ONLY(LogNetworkScriptBankRequest("RequestBank: Found Script Reference",scriptId.GetScriptName(),bankId,bankSlot)); break; } } if(!bFound) { // find an empty slot for (int i=0; i<(AUD_MAX_SCRIPT_NETWORK_BANK_SLOTS);i++) { if (!m_BankSlots[bankSlot].NetworkScripts[i].IsValid()) { m_BankSlots[bankSlot].ReferenceCount++; m_BankSlots[bankSlot].NetworkScripts[i] = scriptId; bFound = true; naDisplayf("RequestBank: Adding Script Reference - NetSlot:%d, Ref:%d", i, m_BankSlots[bankSlot].ReferenceCount); BANK_ONLY(LogNetworkScriptBankRequest("RequestBank: Adding Script Reference",scriptId.GetScriptName(),bankId,bankSlot)); break; } } } // check that we have a valid network script slot if(!bFound) { BANK_ONLY(LogScriptBanks();); audAssertf(false, "Could not allocate a network script slot for bank slot %d! Check preceeding output for Info", bankSlot); return false; } #if GTA_REPLAY if(CReplayMgr::ShouldRecord()) { CReplayMgr::RecordFx(CPacketSndLoadScriptWaveBank(bankSlot, bankHash)); } #endif // success return true; } void audScriptAudioEntity::ReleaseScriptBankFromNetwork(u32 bankHash, CGameScriptId& scriptId) { u32 bankId = g_AudioEngine.GetSoundManager().GetFactory().GetBankNameIndexFromHash(bankHash); // Cycle through all our bank slots for (int i=0; i<(AUD_USABLE_SCRIPT_BANK_SLOTS); i++) { // If we are wiping out all bank slots for this script or a particular bank slot if ((bankId == audScriptBankSlot::INVALID_BANK_ID) || (m_BankSlots[i].BankId == bankId)) { for (s32 j=0; j=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return -1; } s32 freeSoundId = FindFreeSoundId(); if (freeSoundId == -1) { naDisplayf("Script: %s index: %d requesting sound id (none free)",CTheScripts::GetCurrentScriptName(), scriptIndex); return -1; } else { naDisplayf("Script: %s index: %d requesting sound id (returned %d)",CTheScripts::GetCurrentScriptName(), scriptIndex, freeSoundId); } BANK_ONLY(formatf(m_ScriptSounds[freeSoundId].ScriptName, CTheScripts::GetCurrentScriptName());) BANK_ONLY(m_ScriptSounds[freeSoundId].TimeAllocated = fwTimer::GetTimeInMilliseconds()); BANK_ONLY(m_ScriptSounds[freeSoundId].FrameAllocated = fwTimer::GetFrameCount()); m_ScriptSounds[freeSoundId].ScriptIndex = scriptIndex; m_ScriptSounds[freeSoundId].ScriptHasReference = true; return freeSoundId; } s32 audScriptAudioEntity::FindFreeSoundId() { // Trawl through for a free sound s32 i=0; for (i=0; i=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } naDisplayf("Script: %s index: %d releasing sound id %d", CTheScripts::GetCurrentScriptName(), scriptIndex, soundId); // Make sure it's our sound to free, and that we had a reference to it naAssertf(m_ScriptSounds[soundId].ScriptHasReference, "%s:Level design: You appear to be calling RELEASE_SOUND_ID() on a sound id that has already been released", CTheScripts::GetCurrentScriptNameAndProgramCounter()); naAssertf(!m_ScriptSounds[soundId].ScriptHasReference || m_ScriptSounds[soundId].ScriptIndex == scriptIndex, "%s:Level design: Check you're calling RELEASE_SOUND_ID() on the correct SoundID. This script is %s (%u) but sound ID %d is owned by %s (%u)", CTheScripts::GetCurrentScriptNameAndProgramCounter(), GetScriptName(scriptIndex), scriptIndex, soundId, GetScriptName(m_ScriptSounds[soundId].ScriptIndex), m_ScriptSounds[soundId].ScriptIndex); m_ScriptSounds[soundId].ScriptHasReference = false; } audSound* audScriptAudioEntity::PlaySoundAndPopulateScriptSoundSlot(s32 soundId, const char* soundSetName, const char* soundName, audSoundInitParams* initParams, bool bOverNetwork, int nNetworkRange, naEnvironmentGroup* environmentGroup, const CEntity* pEntity, const bool ownsEnvGroup, bool enableOnReplay) { return PlaySoundAndPopulateScriptSoundSlot(soundId, soundSetName ? atStringHash(soundSetName) : 0, soundName ? atStringHash(soundName) : g_NullSoundHash, initParams, bOverNetwork, nNetworkRange, environmentGroup, pEntity, ownsEnvGroup, enableOnReplay OUTPUT_ONLY(, soundSetName, soundName)); } audSound* audScriptAudioEntity::PlaySoundAndPopulateScriptSoundSlot(s32 soundId, const u32 soundSetNameHash, const u32 soundNameHash, audSoundInitParams* initParams, bool bOverNetwork, int nNetworkRange, naEnvironmentGroup* environmentGroup, const CEntity* pEntity, const bool ownsEnvGroup ,bool enableOnReplay OUTPUT_ONLY(, const char* soundSetName, const char* soundName)) { audSoundSet soundSet; audMetadataRef soundRef = g_NullSoundRef; if(soundSetNameHash) { if(soundSet.Init(soundSetNameHash)) { #if __BANK if(!soundSetName) { soundSetName = SOUNDFACTORY.GetMetadataManager().GetObjectName(soundSetNameHash); } #endif soundRef = soundSet.Find(soundNameHash); if(soundRef == g_NullSoundRef) { Displayf("[SFX] Script triggered sound request failed to find valid sound in soundset (Field %s(%u) - SoundSet %s(%u))", soundName, soundNameHash, soundSetName, soundSetNameHash); return NULL; } } else { Displayf("[SFX] Script triggered sound request failed to find soundset (Field %s(%u) - SoundSet %s(%u))", soundName, soundNameHash, soundSetName, soundSetNameHash); return NULL; } } else { soundRef = g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectMetadataRefFromHash(soundNameHash); if(soundRef == g_NullSoundRef) { Displayf("[SFX] Script triggered sound request failed to find valid sound (Sound %s(%u))", soundName, soundNameHash); return NULL; } else { #if __BANK if(!soundName) { soundName = SOUNDFACTORY.GetMetadataManager().GetObjectName(soundNameHash); } #endif } } BANK_ONLY(audDisplayf("PlaySoundAndPopulateScriptSoundSlot soundId[%d] soundSetName[%s] soundSetNameHash[%u] soundName[%s] soundNameHash[%u] bOverNetwork[%d] nNetworkRange[%d]",soundId, soundSetName, soundSetNameHash, soundName, soundNameHash, bOverNetwork, nNetworkRange);) naAssertf(initParams, "initParams must be a valid pointer"); s32 scriptIndex = AddScript(); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return NULL; } // If we're not passed in a soundId, we need to rustle one up if (soundId == -1) { s32 freeSoundId = FindFreeSoundId(); if (freeSoundId == -1) { BANK_ONLY(naErrorf("Tried to play sound but no free sound id (Sound %s(%u) - SoundSet %s(%u))", soundName, soundNameHash, soundSetName, soundSetNameHash);) return NULL; } // Set up the sound slot m_ScriptSounds[freeSoundId].ScriptIndex = scriptIndex; m_ScriptSounds[freeSoundId].ScriptHasReference = false; // From here on, we do the same as if we're passing a soundId in, so set it to our free one. soundId = freeSoundId; } // To avoid cache misses, we're tracking which environmentGroups to update via a bitset if(ownsEnvGroup) { m_ScriptOwnedEnvGroup.Set(soundId); } else { m_ScriptOwnedEnvGroup.Clear(soundId); } m_ScriptSounds[soundId].EnvironmentGroup = environmentGroup; m_ScriptSounds[soundId].Entity = const_cast(pEntity); // If we're currently playing something out of this slot, stop it. if (m_ScriptSounds[soundId].Sound) { // Stop the sound over the network. if(m_ScriptSounds[soundId].OverNetwork && NetworkInterface::IsGameInProgress()) { CStopSoundEvent::Trigger(static_cast(soundId)); } m_ScriptSounds[soundId].Sound->StopAndForget(); #if GTA_REPLAY m_ScriptSounds[soundId].SoundNameHash = g_NullSoundHash; m_ScriptSounds[soundId].SoundSetHash = g_NullSoundHash; m_ScriptSounds[soundId].ShouldRecord = enableOnReplay; m_ScriptSounds[soundId].ClearReplayVariableNameHashs(); #endif } m_ScriptSounds[soundId].InitParams = *initParams; m_ScriptSounds[soundId].SoundRef = soundRef; m_ScriptSounds[soundId].OverNetwork = bOverNetwork; #if __BANK if(soundSetName) { formatf(m_ScriptSounds[soundId].SoundSetTriggeredName, soundSetName); } else { formatf(m_ScriptSounds[soundId].SoundSetTriggeredName, "%u", soundSetNameHash); } if(soundName) { formatf(m_ScriptSounds[soundId].SoundTriggeredName, soundName); } else { formatf(m_ScriptSounds[soundId].SoundTriggeredName, "%u", soundNameHash); } #endif #if GTA_REPLAY m_ScriptSounds[soundId].SoundNameHash = soundNameHash; m_ScriptSounds[soundId].SoundSetHash = soundSetNameHash; m_ScriptSounds[soundId].ShouldRecord = enableOnReplay; #endif // Play the sound over the network. if(m_ScriptSounds[soundId].OverNetwork && NetworkInterface::IsGameInProgress()) { m_ScriptSounds[soundId].NetworkId = (NetworkInterface::GetLocalPhysicalPlayerIndex() << 16) | soundId; CGameScriptId& scriptId = static_cast(CTheScripts::GetCurrentGtaScriptHandler()->GetScriptId()); if(pEntity != NULL) { CPlaySoundEvent::Trigger(pEntity, soundSetNameHash,soundNameHash, static_cast(soundId), scriptId, nNetworkRange); } else { CPlaySoundEvent::Trigger(initParams->Position, soundSetNameHash, soundNameHash, static_cast(soundId), scriptId, nNetworkRange); } } else { m_ScriptSounds[soundId].NetworkId = audScriptSound::INVALID_NETWORK_ID; } if(pEntity && pEntity->GetAudioEntity()) { if(pEntity->GetIsTypeVehicle()) { // Allocate a variable block in-case this sound wants to control fake revs/throttle via parameters ((audVehicleAudioEntity*)pEntity->GetAudioEntity())->AllocateVehicleVariableBlock(); } pEntity->GetAudioEntity()->CreateAndPlaySound_Persistent(m_ScriptSounds[soundId].SoundRef, &m_ScriptSounds[soundId].Sound, initParams); } else { CreateAndPlaySound_Persistent(m_ScriptSounds[soundId].SoundRef, &m_ScriptSounds[soundId].Sound, initParams); } #if __BANK if (g_DebugPlayingScriptSounds) { if(m_ScriptSounds[soundId].Sound) { Displayf("Successfully played script sound %s", SOUNDFACTORY.GetMetadataManager().GetObjectName(soundNameHash)); } else { Displayf("Failed to play script sound %s", SOUNDFACTORY.GetMetadataManager().GetObjectName(soundNameHash)); } } #endif return m_ScriptSounds[soundId].Sound; } bool audScriptAudioEntity::PlaySoundAndPopulateScriptSoundSlotFromNetwork(u32 setNameHash, u32 soundNameHash, audSoundInitParams* initParams, u32 nNetworkId, CGameScriptId& scriptId, naEnvironmentGroup* environmentGroup, CEntity* entity, const bool ownsEnvGroup) { networkAudioDebugf3("PlaySoundAndPopulateScriptSoundSlotFromNetwork nNetworkId[%u]",nNetworkId); audSoundSet soundSet; audMetadataRef soundRef = g_NullSoundRef; OUTPUT_ONLY(const char* soundSetName = "";) OUTPUT_ONLY(const char* soundName = "";) if(setNameHash) { if(soundSet.Init(setNameHash)) { #if __BANK if(!soundSetName) { soundSetName = SOUNDFACTORY.GetMetadataManager().GetObjectName(setNameHash); } #endif soundRef = soundSet.Find(soundNameHash); if(soundRef == g_NullSoundRef) { Displayf("[SFX] Script triggered sound request failed to find valid sound in soundset (Field: %u - Sound Set: %s (%u))", soundNameHash, soundSetName, setNameHash); return false; } } else { Displayf("[SFX] Script triggered sound request failed to find soundset (Field: %u - Sound Set: %u)", soundNameHash, setNameHash); return false; } } else { soundRef = g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectMetadataRefFromHash(soundNameHash); if(soundRef == g_NullSoundRef) { Displayf("[SFX] Script triggered sound request failed to find valid sound (Sound: %u)", soundNameHash); return false; } else { #if __BANK if(!soundName) { soundName = SOUNDFACTORY.GetMetadataManager().GetObjectName(soundNameHash); } #endif } } // check if we have any pending bank loads on this script for (s32 i=0; iGetAudioEntity()) { if (entity->GetIsTypeVehicle()) { // Allocate a variable block in-case this sound wants to control fake revs/throttle via parameters ((audVehicleAudioEntity*)entity->GetAudioEntity())->AllocateVehicleVariableBlock(); } entity->GetAudioEntity()->CreateAndPlaySound_Persistent(m_ScriptSounds[soundId].SoundRef, &m_ScriptSounds[soundId].Sound, initParams); } else { CreateAndPlaySound_Persistent(m_ScriptSounds[soundId].SoundRef, &m_ScriptSounds[soundId].Sound, initParams); } if (g_DebugPlayingScriptSounds) { if(m_ScriptSounds[soundId].Sound) { Displayf("Successfully played script sound from network (Sound: %s (%u) - Sound Set: %s (%u)", soundName, soundNameHash, soundSetName, setNameHash); } else { Displayf("Failed to play script sound from network (Sound: %s (%u) - Sound Set: %s (%u)", soundName, soundNameHash, soundSetName, setNameHash); } } // doesn't matter if the sound played or not, that we tried is enough naDebugf3("RequestSound: Played - SoundID:%d, NetID:%d, Set:%u Sound:%u", soundId, nNetworkId, setNameHash, soundNameHash); networkAudioDebugf3("RequestSound: Played - SoundID:%d, NetID:%d, Set:%u Sound:%u", soundId, nNetworkId, setNameHash, soundNameHash); return true; } void audScriptAudioEntity::SetAmbientZoneNonPersistentState(u32 ambientZone, bool enabled, bool forceUpdate) { s32 scriptIndex = AddScript(); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } s32 currentIndex = m_Scripts[scriptIndex].m_ScriptAmbientZoneChanges.Find(ambientZone); if(currentIndex >= 0) { TristateValue currentSetting = g_AmbientAudioEntity.GetZoneNonPersistentStatus(ambientZone); if((currentSetting == AUD_TRISTATE_TRUE && enabled) || (currentSetting == AUD_TRISTATE_FALSE && !enabled)) { // We've already set this zone to the desired value, nothing to do return; } // We can only set the status on zones with no status set, so we must clear the flag before resetting it g_AmbientAudioEntity.ClearZoneNonPersistentStatus(ambientZone, forceUpdate); m_Scripts[scriptIndex].m_ScriptAmbientZoneChanges.Delete(currentIndex); } if(g_AmbientAudioEntity.SetZoneNonPersistentStatus(ambientZone, enabled, forceUpdate)) { m_Scripts[scriptIndex].m_ScriptAmbientZoneChanges.PushAndGrow(ambientZone); } } void audScriptAudioEntity::ClearAmbientZoneNonPersistentState(u32 ambientZone, bool forceUpdate) { scrThreadId scriptThreadId = CTheScripts::GetCurrentGtaScriptThread()->GetThreadId(); naCErrorf(scriptThreadId, "Failed to get thread id"); s32 scriptIndex = GetIndexFromScriptThreadId(scriptThreadId); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } s32 ambientZoneIndex = m_Scripts[scriptIndex].m_ScriptAmbientZoneChanges.Find(ambientZone); if(ambientZoneIndex >= 0) { if(g_AmbientAudioEntity.ClearZoneNonPersistentStatus(ambientZone, forceUpdate)) { m_Scripts[scriptIndex].m_ScriptAmbientZoneChanges.Delete(ambientZoneIndex); } } } void audScriptAudioEntity::SetVehicleScriptPriority(audVehicleAudioEntity* vehicleEntity, audVehicleScriptPriority priorityLevel, scrThreadId scriptThreadId) { s32 scriptIndex = GetIndexFromScriptThreadId(scriptThreadId); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } if(vehicleEntity) { if(vehicleEntity->SetScriptPriority(priorityLevel, scriptThreadId)) { s32 currentIndex = m_Scripts[scriptIndex].m_PriorityVehicles.Find(vehicleEntity); if(currentIndex >= 0) { if(priorityLevel == AUD_VEHICLE_SCRIPT_PRIORITY_NONE) { m_Scripts[scriptIndex].m_PriorityVehicles.Delete(currentIndex); } } else if(priorityLevel > AUD_VEHICLE_SCRIPT_PRIORITY_NONE) { m_Scripts[scriptIndex].m_PriorityVehicles.PushAndGrow(vehicleEntity); } } } } void audScriptAudioEntity::SetVehicleScriptPriorityForCurrentScript(audVehicleAudioEntity* vehicleEntity, audVehicleScriptPriority priorityLevel) { scrThreadId scriptThreadId = CTheScripts::GetCurrentGtaScriptThread()->GetThreadId(); naCErrorf(scriptThreadId, "Failed to get thread id"); SetVehicleScriptPriority(vehicleEntity, priorityLevel, scriptThreadId); } void audScriptAudioEntity::SetPlayerAngry(audSpeechAudioEntity* speechEntity, bool isAngry) { scrThreadId scriptThreadId = CTheScripts::GetCurrentGtaScriptThread()->GetThreadId(); naCErrorf(scriptThreadId, "Failed to get thread id"); s32 scriptIndex = GetIndexFromScriptThreadId(scriptThreadId); if (!naVerifyf(scriptIndex>=0 || !isAngry, "No valid scriptIndex: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } if(speechEntity) { speechEntity->SetPedIsAngry(isAngry); if(scriptIndex >= 0) { int index = m_Scripts[scriptIndex].m_PedsSetToAngry.Find(speechEntity); if(isAngry) { if(index == -1) { m_Scripts[scriptIndex].m_PedsSetToAngry.PushAndGrow(speechEntity); } } else { if(index != -1) { m_Scripts[scriptIndex].m_PedsSetToAngry.Delete(index); } } } else if(!isAngry) { for(int i=0; iSetPedIsAngryShortTime(timeInMs); } void audScriptAudioEntity::PlaySoundFromEntity(s32 soundId, const char* soundName, const CEntity* entity, const char * setName, bool bOverNetwork, int nNetworkRange) { PlaySoundFromEntity(soundId, soundName ? atStringHash(soundName) : g_NullSoundHash, entity, setName ? atStringHash(setName) : 0, bOverNetwork, nNetworkRange OUTPUT_ONLY(, setName, soundName)); } void audScriptAudioEntity::PlaySoundFromEntity(s32 soundId, const u32 soundNameHash, const CEntity* entity, const u32 setNameHash, bool bOverNetwork, int nNetworkRange OUTPUT_ONLY(, const char* soundSetName, const char* soundName)) { // Grab an existing environmentGroup if we already have one (ped or vehicle) otherwise create a new one. bool ownsEnvGroup = false; naEnvironmentGroup* environmentGroup = (naEnvironmentGroup*)entity->GetAudioEnvironmentGroup(); if(!environmentGroup && entity) { environmentGroup = CreateScriptEnvironmentGroup(entity, entity->GetTransform().GetPosition()); ownsEnvGroup = true; } PlaySoundFromEntity(soundId, soundNameHash, entity, environmentGroup, setNameHash, bOverNetwork, nNetworkRange, ownsEnvGroup OUTPUT_ONLY(, soundSetName, soundName)); } void audScriptAudioEntity::PlaySoundFromEntity(s32 soundId, const u32 soundNameHash, const CEntity* entity, naEnvironmentGroup* environmentGroup, const u32 setNameHash, bool bOverNetwork, int nNetworkRange, const bool ownsEnvGroup OUTPUT_ONLY(, const char* soundSetName, const char* soundName)) { BANK_ONLY(networkAudioDebugf3("PlaySoundFromEntity soundId[%d] soundName[%s] entity[%p][%s] setName[%s] bOverNetwork[%d] nNetworkRange[%d] ownsEnvGroup[%d]",soundId,SOUNDFACTORY.GetMetadataManager().GetObjectName(soundNameHash),entity,entity ? entity->GetModelName() : "",SOUNDFACTORY.GetMetadataManager().GetObjectName(setNameHash),bOverNetwork,nNetworkRange,ownsEnvGroup);) naAssertf(entity, "entity must be a valid pointer"); //if( entity->GetIsTypeObject() ) //{ // if(!((CObject*)entity)->GetObjectAudioEntity()->HasRegisteredWithController()) // { // ((CObject*)entity)->GetObjectAudioEntity()->Init((CObject*)entity); // } //} //naAssertf(((CObject*)entity)->GetObjectAudioEntity()->HasRegisteredWithController(), "trying to play a sound from an object that hasn't got a valid audio entity"); audSound* sound = NULL; audSoundInitParams initParams; Vec3V position = entity->GetTransform().GetPosition(); initParams.Tracker = entity->GetPlaceableTracker(); initParams.EnvironmentGroup = environmentGroup; initParams.Position = VEC3V_TO_VECTOR3(position); sound = PlaySoundAndPopulateScriptSoundSlot(soundId, setNameHash, soundNameHash, &initParams, bOverNetwork, nNetworkRange, environmentGroup, entity, ownsEnvGroup, true OUTPUT_ONLY(, soundSetName, soundName)); #if GTA_REPLAY if(sound) { RecordScriptSlotSound(setNameHash, soundNameHash, &initParams, entity, sound); } #endif #if __BANK if(sound) { Displayf("[SFX] PLAY_SOUND_FROM_ENTITY - Sound: %s(%u) - Set:%s(%u): Played successfully (%s)", soundName, soundNameHash, soundSetName, setNameHash, CTheScripts::GetCurrentScriptNameAndProgramCounter()); } else { Displayf("[SFX] PLAY_SOUND_FROM_ENTITY - Sound: %s(%u) - Set:%s(%u): Failed to play (%s)", soundName, soundNameHash, soundSetName, setNameHash, CTheScripts::GetCurrentScriptNameAndProgramCounter()); } #endif } void audScriptAudioEntity::PlaySoundFromPosition(s32 soundId, const char* soundName, const Vector3* position, const char * setName, bool bOverNetwork, int nNetworkRange, bool isExteriorLoc) { networkAudioDebugf3("PlaySoundFromPosition soundId[%d] soundName[%s] setName[%s] bOverNetwork[%d] nNetworkRange[%d] isExteriorLoc[%d]",soundId,soundName,setName,bOverNetwork,nNetworkRange,isExteriorLoc); // If it's an exterior location, then we can use an environmentGroup since we have accurate interior information naEnvironmentGroup* environmentGroup = NULL; bool ownsEnvGroup = false; if(isExteriorLoc) { environmentGroup = CreateScriptEnvironmentGroup(NULL, VECTOR3_TO_VEC3V(*position)); ownsEnvGroup = true; } audSound* sound = NULL; audSoundInitParams initParams; initParams.Position = *position; initParams.EnvironmentGroup = environmentGroup; sound = PlaySoundAndPopulateScriptSoundSlot(soundId, setName, soundName, &initParams, bOverNetwork, nNetworkRange, environmentGroup, NULL, ownsEnvGroup, true); #if GTA_REPLAY if(sound && CReplayMgr::IsRecording()) { if(soundId != -1) { if(setName) { m_ScriptSounds[soundId].SoundSetHash = atStringHash(setName); } else { m_ScriptSounds[soundId].SoundSetHash = g_NullSoundHash; } m_ScriptSounds[soundId].SoundNameHash = atStringHash(soundName); m_ScriptSounds[soundId].InitParams = initParams; } RecordScriptSlotSound(setName, soundName, &initParams, NULL, sound); } #endif #if __BANK if(sound) { Displayf("[SFX] PLAY_SOUND_FROM_COORD - Sound:%s - Set:%s: Played successfully (%s)", soundName, setName? setName : "null", CTheScripts::GetCurrentScriptNameAndProgramCounter()); } else { Displayf("[SFX] PLAY_SOUND_FROM_COORD - Sound:%s - Set:%s: Failed to play (%s)", soundName, setName? setName : "null", CTheScripts::GetCurrentScriptNameAndProgramCounter()); } #endif } void audScriptAudioEntity::UpdateSoundPosition(s32 soundId, const Vector3* position) { if (soundId<0) { return; } s32 scriptIndex = AddScript(); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } // Make sure it's our sound ref to stop naCErrorf(m_ScriptSounds[soundId].ScriptIndex == scriptIndex, "%s:Level design: Check you're calling UPDATE_SOUND_POSITION(soundId, vector) on the correct SoundID", CTheScripts::GetCurrentScriptName()); if (m_ScriptSounds[soundId].Sound && m_ScriptSounds[soundId].ScriptIndex == scriptIndex) { m_ScriptSounds[soundId].Sound->SetRequestedPosition(VECTOR3_TO_VEC3V(*position)); } } void audScriptAudioEntity::PlayFireSoundFromPosition(s32 soundId, const Vector3* position) { audSoundInitParams initParams; initParams.Position = *position; initParams.StartOffset = g_NextFireStartOffset; initParams.IsStartOffsetPercentage = true; g_NextFireStartOffset = (g_NextFireStartOffset+70)%100; audSound* sound = PlaySoundAndPopulateScriptSoundSlot(soundId, "", "SCRIPT_FIRE", &initParams, false, 0, NULL, NULL, false, true); if(sound) { REPLAY_ONLY(RecordScriptSlotSound(NULL, "SCRIPT_FIRE", &initParams, NULL, sound);) } } void audScriptAudioEntity::PlaySound(s32 soundId, const char* soundName, const char * setName, bool bOverNetwork, int nNetworkRange,bool REPLAY_ONLY(enableOnReplay)) { audSound* sound = NULL; audSoundInitParams initParams; sound = PlaySoundAndPopulateScriptSoundSlot(soundId, setName, soundName, &initParams, bOverNetwork, nNetworkRange, NULL, NULL, false, enableOnReplay); #if GTA_REPLAY if(sound && enableOnReplay) { if(soundId != -1) { if(setName) { m_ScriptSounds[soundId].SoundSetHash = atStringHash(setName); } else { m_ScriptSounds[soundId].SoundSetHash = g_NullSoundHash; } m_ScriptSounds[soundId].SoundNameHash = atStringHash(soundName); m_ScriptSounds[soundId].InitParams = initParams; } RecordScriptSlotSound(setName, soundName, &initParams, NULL, sound); } #endif #if __BANK if(sound) { Displayf("[SFX] PLAY_SOUND - Sound:%s - Set:%s: Played successfully (%s)", soundName, setName? setName : "null", CTheScripts::GetCurrentScriptNameAndProgramCounter()); } else { Displayf("[SFX] PLAY_SOUND - Sound:%s - Set:%s: Failed to play (%s)", soundName, setName? setName : "null", CTheScripts::GetCurrentScriptNameAndProgramCounter()); } #endif } void audScriptAudioEntity::PlaySoundFrontend(s32 soundId, const char* soundName, const char * setName, bool REPLAY_ONLY(enableOnReplay)) { audSound* sound = NULL; audSoundInitParams initParams; initParams.Pan = 0; if(setName) { const u32 soundSetNameHash = atStringHash(setName); if( soundSetNameHash == ATSTRINGHASH("MissionFailedSounds", 0x5A23F3D5) || soundSetNameHash == ATSTRINGHASH("BustedSounds", 0xDF84A53C) || soundSetNameHash == ATSTRINGHASH("WastedSounds", 0xFD4C28)) { initParams.TimerId = 1; // don't record sounds played from these sets. REPLAY_ONLY(enableOnReplay = false;) } if( soundSetNameHash == ATSTRINGHASH("HUD_FRONTEND_CUSTOM_SOUNDSET", 0x832CAA0F) || soundSetNameHash == ATSTRINGHASH("HUD_FRONTEND_DEFAULT_SOUNDSET", 0x17BD10F1) || soundSetNameHash == ATSTRINGHASH("MP_LOBBY_SOUNDS", 0xFA4A5AA0) || soundSetNameHash == ATSTRINGHASH("HUD_FRONTEND_MP_COLLECTABLE_SOUNDS", 0x2B8F97E3) || soundSetNameHash == ATSTRINGHASH("HUD_FRONTEND_STANDARD_PICKUPS_NPC_SOUNDSET", 0x1D46A6A2) || soundSetNameHash == ATSTRINGHASH("HUD_FRONTEND_VEHICLE_PICKUPS_NPC_SOUNDSET", 0xF5E3A26A) || soundSetNameHash == ATSTRINGHASH("HUD_FRONTEND_WEAPONS_PICKUPS_NPC_SOUNDSET", 0xF35C567B) || soundSetNameHash == ATSTRINGHASH("RESPAWN_ONLINE_SOUNDSET", 0x71F56AB4) || soundSetNameHash == ATSTRINGHASH("MP_MISSION_COUNTDOWN_SOUNDSET", 0xC55C68A0) || soundSetNameHash == ATSTRINGHASH("HUD_FREEMODE_SOUNDSET", 0x54C522AD) || soundSetNameHash == ATSTRINGHASH("HUD_MINI_GAME_SOUNDSET", 0xD382DF7C) || soundSetNameHash == ATSTRINGHASH("HUD_AWARDS", 0x2A508F9C) || soundSetNameHash == ATSTRINGHASH("CELEBRATION_SOUNDSET", 0xE8F24AFD) || soundSetNameHash == ATSTRINGHASH("LONG_PLAYER_SWITCH_SOUNDS", 0x8DDBFC96) || soundSetNameHash == ATSTRINGHASH("PHONE_SOUNDSET_DEFAULT", 0x28C8633) || soundSetNameHash == ATSTRINGHASH("PHONE_SOUNDSET_PROLOGUE", 0x596B8EBB) || soundSetNameHash == ATSTRINGHASH("Phone_SoundSet_Franklin", 0x8A73028A) || soundSetNameHash == ATSTRINGHASH("Phone_SoundSet_Michael", 0x578FE4D7) || soundSetNameHash == ATSTRINGHASH("Phone_SoundSet_Trevor", 0xE52306DE) || soundSetNameHash == ATSTRINGHASH("Phone_SoundSet_Glasses_Cam", 0x10109BEB)) { // don't record sounds played from these sets. REPLAY_ONLY(enableOnReplay = false;) } } sound = PlaySoundAndPopulateScriptSoundSlot(soundId, setName, soundName, &initParams, false, 0, NULL, NULL, false, enableOnReplay); #if GTA_REPLAY if(sound && enableOnReplay) { if(soundId != -1) { if(setName) { m_ScriptSounds[soundId].SoundSetHash = atStringHash(setName); } else { m_ScriptSounds[soundId].SoundSetHash = g_NullSoundHash; } m_ScriptSounds[soundId].SoundNameHash = atStringHash(soundName); m_ScriptSounds[soundId].InitParams = initParams; } RecordScriptSlotSound(setName, soundName, &initParams, NULL, sound); } #endif #if __BANK if(sound) { Displayf("[SFX] PLAY_SOUND_FRONTEND - Sound:%s - Set:%s: Played successfully (%s)", soundName, setName? setName : "null", CTheScripts::GetCurrentScriptNameAndProgramCounter()); } else { Displayf("[SFX] PLAY_SOUND_FRONTEND - Sound:%s - Set:%s: Failed to play (%s)", soundName, setName? setName : "null", CTheScripts::GetCurrentScriptNameAndProgramCounter()); } #endif } void audScriptAudioEntity::StopSound(s32 soundId) { //Assertf(soundId >=0, "Level design: you're calling StopSound with soundId -1" ); if (soundId<0) { return; } // Stop sounds started via the network from network function if (m_ScriptSounds[soundId].NetworkId != audScriptSound::INVALID_NETWORK_ID && !m_ScriptSounds[soundId].OverNetwork) { StopSoundFromNetwork(m_ScriptSounds[soundId].NetworkId); return; } s32 scriptIndex = AddScript(); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } // Make sure it's our sound ref to stop naCErrorf(m_ScriptSounds[soundId].ScriptIndex == scriptIndex, "%s:Level design: Check you're calling STOP_SOUND() on the correct SoundID", CTheScripts::GetCurrentScriptName()); if (m_ScriptSounds[soundId].Sound && m_ScriptSounds[soundId].ScriptIndex == scriptIndex) { Displayf("[SFX] STOP_SOUND - Sound:%s - Id %u ScriptId: %u OverNetwork: %s", m_ScriptSounds[soundId].Sound->GetName(), soundId, m_ScriptSounds[soundId].ScriptIndex,m_ScriptSounds[soundId].OverNetwork ? "Yes" : "No"); // Stop the sound over the network. if(m_ScriptSounds[soundId].OverNetwork && NetworkInterface::IsGameInProgress()) { CStopSoundEvent::Trigger(static_cast(soundId)); } m_ScriptSounds[soundId].Sound->StopAndForget(); m_ScriptSounds[soundId].EnvironmentGroup = NULL; m_ScriptSounds[soundId].Entity = NULL; m_ScriptOwnedEnvGroup.Clear(soundId); #if GTA_REPLAY m_ScriptSounds[soundId].SoundNameHash = g_NullSoundHash; m_ScriptSounds[soundId].SoundSetHash = g_NullSoundHash; m_ScriptSounds[soundId].ClearReplayVariableNameHashs(); #endif } } bool audScriptAudioEntity::PlaySoundFromNetwork(u32 nNetworkId, CGameScriptId& scriptId, CEntity* entity, u32 setNameHash, u32 soundNameHash) { networkAudioDisplayf("PlaySoundFromNetwork nNetworkId[%u] entity[%p][%s] sound %u (set %u)",nNetworkId,entity,entity ? entity->GetModelName() : "", soundNameHash, setNameHash); if(naVerifyf(entity, "CEntity* must be a valid")) { audSoundInitParams initParams; initParams.Tracker = entity->GetPlaceableTracker(); bool ownsEnvGroup = false; naEnvironmentGroup* environmentGroup = (naEnvironmentGroup*)entity->GetAudioEnvironmentGroup(); if(!environmentGroup) { environmentGroup = CreateScriptEnvironmentGroup(entity, entity->GetTransform().GetPosition()); ownsEnvGroup = true; } initParams.Position = VEC3V_TO_VECTOR3(entity->GetTransform().GetPosition()); return PlaySoundAndPopulateScriptSoundSlotFromNetwork(setNameHash, soundNameHash, &initParams, nNetworkId, scriptId, environmentGroup, entity, ownsEnvGroup); } return false; } bool audScriptAudioEntity::PlaySoundFromNetwork(u32 nNetworkId, CGameScriptId& scriptId, const Vector3* position, u32 setNameHash, u32 soundNameHash) { networkAudioDisplayf("PlaySoundFromNetwork nNetworkId[%u] sound %u (set %u)",nNetworkId, setNameHash, soundNameHash); audSoundInitParams initParams; initParams.Position = *position; return PlaySoundAndPopulateScriptSoundSlotFromNetwork(setNameHash, soundNameHash, &initParams, nNetworkId, scriptId, NULL, NULL, false); } void audScriptAudioEntity::StopSoundFromNetwork(u32 nNetworkId) { networkAudioDisplayf("StopSoundFromNetwork nNetworkId[%u]",nNetworkId); if (nNetworkId == audScriptSound::INVALID_NETWORK_ID) return; for (s32 i=0; iStopAndForget(); } ResetScriptSound(i); } } } u32 audScriptAudioEntity::GetNetworkIdFromSoundId(s32 soundId) { // bounds check if(!naVerifyf(soundId >= 0 && soundId < AUD_MAX_SCRIPT_SOUNDS, "GetNetworkIdFromSoundId - Invalid soundId: %d", soundId)) return audScriptSound::INVALID_NETWORK_ID; return m_ScriptSounds[soundId].NetworkId; } s32 audScriptAudioEntity::GetSoundIdFromNetworkId(u32 nNetworkId) { if (nNetworkId == audScriptSound::INVALID_NETWORK_ID) { return -1; } for (s32 i=0; i=0, "Level design: you're calling StopSound with soundId -1" ); if (soundId<0) { return; } s32 scriptIndex = AddScript(); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return; } // Make sure it's our sound ref to do stuff to naCErrorf(m_ScriptSounds[soundId].ScriptIndex == scriptIndex, "Level design: Check you're calling SET_VARIABLE on the correct SoundID"); if (m_ScriptSounds[soundId].Sound && m_ScriptSounds[soundId].ScriptIndex == scriptIndex) { m_ScriptSounds[soundId].Sound->SetVariableValueDownHierarchyFromName(variableName, variableValue); #if GTA_REPLAY u32 variableNameHash = atStringHash(variableName); m_ScriptSounds[soundId].StoreSoundVariable(variableNameHash, variableValue); #endif } } void audScriptAudioEntity::SetVariableOnStream(const char * variableName, f32 variableValue) { if(m_StreamSound) { m_StreamSound->SetVariableValueDownHierarchyFromName(variableName, variableValue); #if GTA_REPLAY u32 variableNameHash = atStringHash(variableName); StoreSoundVariable(variableNameHash, variableValue); #endif } } #if GTA_REPLAY void audScriptAudioEntity::StoreSoundVariable(u32 variableNameHash, f32 value) { for(int i=0; i=0, "Level design: you're calling StopSound with soundId -1" ); if (soundId<0) { return true; } s32 scriptIndex = AddScript(); if (!naVerifyf(scriptIndex>=0, "No valid scriptIndex, even with automated registering: %s; %d", CTheScripts::GetCurrentScriptName(), scriptIndex)) { return true; } bool bCanStop = false; // can stop local sounds with valid script index bCanStop |= (m_ScriptSounds[soundId].ScriptIndex == scriptIndex); // can stop network sounds not started by the local player bCanStop |= (m_ScriptSounds[soundId].NetworkId != audScriptSound::INVALID_NETWORK_ID && !m_ScriptSounds[soundId].OverNetwork); if (!naVerifyf(bCanStop, "Level design: Check you're calling HAS_SOUND_FINISHED() on the correct SoundID. This script is %s (%d) but SoundID %d is owned by script %s (%d). Network ID = %d, OverNetwork = %s", GetScriptName(scriptIndex), scriptIndex, soundId, GetScriptName(m_ScriptSounds[soundId].ScriptIndex), m_ScriptSounds[soundId].ScriptIndex, m_ScriptSounds[soundId].NetworkId, m_ScriptSounds[soundId].OverNetwork ? "true" : "false")) { return true; } // Return that it's finished if we no longer have a ptr to it, or it's in the waiting to be deleted state if (!m_ScriptSounds[soundId].Sound) { return true; } if (m_ScriptSounds[soundId].Sound->GetPlayState() == AUD_SOUND_WAITING_TO_BE_DELETED) { return true; } // Else, we have a sound, and it's not done yet return false; } void audScriptAudioEntity::ResetScriptSound(const s32 soundId) { m_ScriptSounds[soundId].Reset(); m_ScriptOwnedEnvGroup.Clear(soundId); } audScript * audScriptAudioEntity::GetScript(s32 index) { if(index >= 0) { if(audVerifyf(index < m_Scripts.GetMaxCount(), "Invalid script index passed to audScriptAudioEntity::GetScript! (%d >= %d)", index, m_Scripts.GetMaxCount())) { return &m_Scripts[index]; } } return NULL; } bool audScriptAudioEntity::StartScene(u32 sceneHash) { s32 index = AddScript(); if (!naVerifyf(index >= 0, "No valid script, even with automated registering: %s", CTheScripts::GetCurrentScriptName())) { return false; } audScript * script = GetScript(index); return script->StartScene(sceneHash); } void audScriptAudioEntity::StopScene(u32 sceneHash) { scrThreadId scriptThreadId = CTheScripts::GetCurrentGtaScriptThread()->GetThreadId(); naAssertf(scriptThreadId, "Failed to get thread id"); s32 index = GetIndexFromScriptThreadId(scriptThreadId); if(index < 0) { return; } audScript* script = GetScript(index); if(script) { script->StopScene(sceneHash); } } void audScriptAudioEntity::StopScenes() { scrThreadId scriptThreadId = CTheScripts::GetCurrentGtaScriptThread()->GetThreadId(); naAssertf(scriptThreadId, "Failed to get thread id"); s32 index = GetIndexFromScriptThreadId(scriptThreadId); if(index < 0) { return; } audScript* script = GetScript(index); if(script) { script->StopScenes(); } } void audScriptAudioEntity::SetSceneVariable(u32 sceneHash, u32 variableHash, f32 value) { audScript* script = GetScript(AddScript()); if(script) { script->SetSceneVariable(sceneHash, variableHash, value); } } bool audScriptAudioEntity::SceneHasFinishedFading(u32 sceneHash) { audScript* script = GetScript(AddScript()); if(script) { return script->SceneHasFinishedFade(sceneHash); } return true; } void audScriptAudioEntity::SetSceneVariable(const char * sceneName, const char * variableName, f32 value) { SetSceneVariable(atStringHash(sceneName), atStringHash(variableName), value); } void audScriptAudioEntity::SetScriptVariable(const char * variableName, f32 value) { audScript* script = GetScript(AddScript()); if(script) { script->SetVariableValue(atStringHash(variableName), value); } } bool audScriptAudioEntity::IsSceneActive(const u32 sceneHash) { audScript *script = GetScript(AddScript()); if(script) { if((script->GetScene(sceneHash)) != NULL) { return true; } } return DYNAMICMIXER.GetActiveSceneFromHash(sceneHash) != NULL; } #if NA_POLICESCANNER_ENABLED void audScriptAudioEntity::SetPoliceScannerAudioScene(const char* sceneName) { if(!Verifyf(m_PoliceScannerSceneHash == 0 && m_PoliceScannerSceneScriptIndex == -1, "Trying to set police scanner audio scene when on has already been set.")) return; u32 sceneHash = atStringHash(sceneName); if(StartScene(sceneHash)) { s32 index = AddScript(); if (!naVerifyf(index >= 0, "No valid script, even with automated registering: %s", CTheScripts::GetCurrentScriptName())) { StopScene(sceneHash); return; } audScript* script = GetScript(index); if(Verifyf(script, "Failed to grab script pointer in spite of a valid index.")) { audScene* scene = script->GetScene(sceneHash); if(Verifyf(scene, "Failed to grab audio scene despite successful creation. Something is very wrong.")) { scene->SetVariableValue("apply", 0.0f); m_PoliceScannerSceneHash = sceneHash; m_PoliceScannerSceneScriptIndex = index; } else { StopScene(sceneHash); } } else { StopScene(sceneHash); } } } void audScriptAudioEntity::StopAndRemovePoliceScannerAudioScene() { StopScene(m_PoliceScannerSceneHash); ResetPoliceScannerVariables(); } void audScriptAudioEntity::ResetPoliceScannerVariables() { m_PoliceScannerSceneHash = 0; m_PoliceScannerSceneScriptIndex = -1; } void audScriptAudioEntity::ApplyPoliceScannerAudioScene() { if(m_PoliceScannerSceneHash == 0 || m_PoliceScannerSceneScriptIndex == -1 || m_PoliceScannerIsApplied) return; audScript* script = GetScript(m_PoliceScannerSceneScriptIndex); if(Verifyf(script, "Failed to grab script pointer in spite of a valid index.")) { audScene* scene = script->GetScene(m_PoliceScannerSceneHash); if(Verifyf(scene, "Failed to grab audio scene despite successful creation. Something is very wrong.")) { scene->SetVariableValue("apply", g_AudioScannerManager.GetPoliceScannerApplyValue()); m_PoliceScannerIsApplied = true; } } } void audScriptAudioEntity::DeactivatePoliceScannerAudioScene() { if(m_PoliceScannerSceneHash == 0 || m_PoliceScannerSceneScriptIndex == -1) return; audScript* script = GetScript(m_PoliceScannerSceneScriptIndex); if(Verifyf(script, "Failed to grab script pointer in spite of a valid index.")) { audScene* scene = script->GetScene(m_PoliceScannerSceneHash); if(Verifyf(scene, "Failed to grab audio scene despite successful creation. Something is very wrong.")) { scene->SetVariableValue("apply", SMALL_FLOAT); m_PoliceScannerIsApplied = false; } } } #endif // Custom code handlers void audScriptAudioEntity::GetSpeechForEmergencyServiceCall(char* RetString) { CPopZone *pZone; Vector3 playerCoors = CGameWorld::FindLocalPlayerCoors(); pZone = CPopZones::FindSmallestForPosition(&playerCoors, ZONECAT_ANY, ZONETYPE_SP); if(pZone) { strncpy(RetString, pZone->m_associatedTextId.TryGetCStr(), 8); } else { strcpy(RetString, ""); } } void audScriptAudioEntity::EnableChaseAudio(bool enable) { audScript *script = GetScript(); if(naVerifyf(script, "Failed to register script for audio")) { script->SetAudioFlag(audScriptAudioFlags::BoostPlayerCarVolume, enable); script->SetAudioFlag(audScriptAudioFlags::AggressiveHorns, enable); script->SetAudioFlag(audScriptAudioFlags::CarsStartFirstTime, enable);; } } void audScriptAudioEntity::EnableAggressiveHorns(bool enable) { audScript *script = GetScript(); if(naVerifyf(script, "Failed to register script for audio")) { script->SetAudioFlag(audScriptAudioFlags::AggressiveHorns, enable); } } void audScriptAudioEntity::PlayPreviewRingtone(const u32 ringtoneId) { StopPreviewRingtone(); char soundName[64]; formatf(soundName, sizeof(soundName)-1, "MOBILE_PHONE_RING_%02d", ringtoneId); audSoundInitParams initParams; initParams.WaveSlot = audWaveSlot::FindWaveSlot(ATSTRINGHASH("PLAYER_RINGTONE", 0x0d2645e26)); initParams.Pan = 0; initParams.AllowLoad = true; initParams.EffectRoute = EFFECT_ROUTE_FRONT_END; initParams.PrepareTimeLimit = 3500; CreateAndPlaySound_Persistent(soundName, &m_PreviewRingtoneSound, &initParams); } void audScriptAudioEntity::StopPreviewRingtone() { if(m_PreviewRingtoneSound) { m_PreviewRingtoneSound->StopAndForget(); } } void audScriptAudioEntity::DontAbortCarConversations(bool allowUpsideDown, bool allowOnFire, bool allowDrowning) { s32 scriptIndex = AddScript(); DontAbortCarConversationsWithScriptId(allowUpsideDown, allowOnFire, allowDrowning, scriptIndex); } void audScriptAudioEntity::DontAbortCarConversationsWithScriptId(bool allowUpsideDown, bool allowOnFire, bool allowDrowning, s32 scriptIndex) { naConvDisplayf("DontAbortCarConversations() allowUpsideDown: %s allowOnFire: %s allowDrowning: %s", allowUpsideDown ? "True" : "False", allowOnFire ? "True" : "False", allowDrowning? "True" : "False"); if(allowOnFire || allowUpsideDown || allowDrowning) { if (scriptIndex < 0 || m_ScriptInChargeOfAudio != -1) { naErrorf("Couldn't set persistent car conversations...has another script already called this or have we failed to get an audScript? (scriptIndex: %d, scriptInchargeOfAudio: %d", scriptIndex, m_ScriptInChargeOfAudio); return; } m_ScriptInChargeOfAudio = scriptIndex; g_AllowUpsideDownConversations = allowUpsideDown; g_AllowVehicleOnFireConversations = allowOnFire; g_AllowVehicleDrowningConversations = allowDrowning; } else { m_ScriptInChargeOfAudio = -1; BANK_ONLY(m_CurrentScriptName = ""); g_AllowUpsideDownConversations = allowUpsideDown; g_AllowVehicleOnFireConversations = allowOnFire; g_AllowVehicleDrowningConversations = allowDrowning; } } void audScriptAudioEntity::MuteGameWorldAndPositionedRadioForTV(bool mute) { audNorthAudioEngine::MuteGameWorldAndPositionedRadioForTv(mute); } bool audScriptAudioEntity::RequestScriptWeaponAudio(const char * itemName) { s32 scriptIndex = AddScript(); audScript * script = GetScript(scriptIndex); if(!script) { return false; } return script->RequestScriptWeaponAudio(itemName); } void audScriptAudioEntity::ReleaseScriptWeaponAudio() { s32 scriptIndex = AddScript(); audScript * script = GetScript(scriptIndex); if(!script) { return; } script->ReleaseScriptWeaponAudio(); } bool audScriptAudioEntity::RequestScriptVehicleAudio(u32 vehicleModelName) { s32 scriptIndex = AddScript(); audScript * script = GetScript(scriptIndex); if(!script) { return false; } return script->RequestScriptVehicleAudio(vehicleModelName); } #if !__FINAL void audScriptAudioEntity::DebugScriptStream(s32 scriptIndex) { GtaThread* currentThread = static_cast(scrThread::GetThread(m_Scripts[scriptIndex].ScriptThreadId)); const char * currentScriptName = currentThread ? currentThread->GetScriptName() : "none"; GtaThread* streamThread = static_cast(scrThread::GetThread(m_Scripts[sm_ScriptInChargeOfTheStream].ScriptThreadId)); const char *streamScriptName = streamThread ? streamThread->GetScriptName() : "none"; naWarningf("Attempting to preload as stream from script %s but another script (%s) is in charge of script stream", currentScriptName, streamScriptName); } #endif bool audScriptAudioEntity::PreloadStream(const char *streamName, const u32 startOffset, const char * setName) { #if __BANK if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] Preload stream call, state: %s, frame : %u",audScriptAudioEntity::GetStreamStateName(m_ScriptStreamState) , fwTimer::GetFrameCount()); } #endif #if GTA_REPLAY if(CReplayMgr::ShouldRecord()) { safecpy(sm_CurrentStreamName, streamName); safecpy(sm_CurrentSetName, setName); sm_CurrentStartOffset = startOffset; } #endif s32 scriptIndex = AddScript(); if((sm_ScriptInChargeOfTheStream != -1 && sm_ScriptInChargeOfTheStream != scriptIndex)) { #if !__FINAL DebugScriptStream(scriptIndex); #endif return true; } audScript *script = GetScript(scriptIndex); if(!script) return false; audScriptAudioEntity::sm_ScriptInChargeOfTheStream = scriptIndex; return PreloadStreamInternal(streamName, startOffset, setName); } bool audScriptAudioEntity::PreloadStreamInternal(const char *streamName, const u32 startOffsetMs, const char *setName) { naCErrorf(m_ScriptStreamState != AUD_SCRIPT_STREAM_PLAYING, "Shouldn't try to preload while script stream state is set to playing"); audMetadataRef streamDataRef; if(setName) { audSoundSet soundSet; soundSet.Init(setName); #if GTA_REPLAY if(CReplayMgr::IsPlaying()) { char buf[64] = {0}; formatf(buf, "%s_REPLAY", streamName); streamDataRef = soundSet.Find(buf); } #endif if(!streamDataRef.IsValid()) { streamDataRef = soundSet.Find(streamName); } } else { #if GTA_REPLAY if(CReplayMgr::IsPlaying()) { char buf[64] = {0}; formatf(buf, "%s_REPLAY", streamName); streamDataRef = g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectMetadataRefFromHash(atStringHash(buf)); } #endif if(!streamDataRef.IsValid()) { streamDataRef = g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectMetadataRefFromHash(atStringHash(streamName)); } } if(m_ScriptStreamState == AUD_SCRIPT_STREAM_PREPARED && streamDataRef == m_LoadedStreamRef) { #if __BANK if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] Stream %s successfully prepared",SOUNDFACTORY.GetMetadataManager().GetObjectName(streamDataRef)); } #endif return true; } if(m_ScriptStreamState == AUD_SCRIPT_STREAM_IDLE) { // grab a slot if(m_StreamSlot == NULL) { audStreamClientSettings settings; settings.priority = audStreamSlot::STREAM_PRIORITY_SCRIPT; settings.stopCallback = &OnStopCallback; settings.hasStoppedCallback = &HasStoppedCallback; settings.userData = 0; m_StreamSlot = audStreamSlot::AllocateSlot(&settings); } if(m_StreamSlot && (m_StreamSlot->GetState() == audStreamSlot::STREAM_SLOT_STATUS_ACTIVE)) { audSoundInitParams initParams; initParams.StartOffset = startOffsetMs; initParams.IsStartOffsetPercentage = false; Assign(initParams.BucketId, audNorthAudioEngine::GetBucketIdForStreamingSounds()); CreateSound_PersistentReference(streamDataRef, &m_StreamSound, &initParams); if(!m_StreamSound) { #if __BANK if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] Failed to create the sound: %s",SOUNDFACTORY.GetMetadataManager().GetObjectName(streamDataRef)); } #endif return false; } else { m_ScriptStreamState = AUD_SCRIPT_STREAM_PREPARING; } } } if(m_ScriptStreamState == AUD_SCRIPT_STREAM_PREPARING) { naAssertf(m_StreamSlot, "About to access a null ptr...."); naAssertf(m_StreamSound, "About to access a null ptr...."); if(m_StreamSound) { audPrepareState ret = m_StreamSound->Prepare(m_StreamSlot->GetWaveSlot(), true); if(ret == AUD_PREPARED) { naDisplayf("Scripted stream %s prepared", streamName); m_LoadedStreamRef = streamDataRef; m_ScriptStreamState = AUD_SCRIPT_STREAM_PREPARED; #if __BANK if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] Stream %s successfully prepared",SOUNDFACTORY.GetMetadataManager().GetObjectName(streamDataRef)); } #endif return true; } } else { #if __BANK if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] Preparing a stream with no sound: %s going into IDLE",SOUNDFACTORY.GetMetadataManager().GetObjectName(streamDataRef)); } #endif m_ScriptStreamState = AUD_SCRIPT_STREAM_IDLE; } } #if GTA_REPLAY //During replay playback when scrubbing around the timeline its possible for the streamstate to get stuck //in the prepared state, if we're in this state and trying to preload a different stream then stop the current one. if(CReplayMgr::IsEditModeActive()) { if( m_ScriptStreamState == AUD_SCRIPT_STREAM_PREPARED && streamDataRef != m_LoadedStreamRef ) { StopStream(); return false; } } #endif //GTA_REPLAY return false; } void audScriptAudioEntity::PlayStreamFromEntity(CEntity *entity) { s32 scriptIndex = AddScript(); if((sm_ScriptInChargeOfTheStream != -1 && sm_ScriptInChargeOfTheStream != scriptIndex)) { #if !__FINAL DebugScriptStream(scriptIndex); #endif return; } audScript *script = GetScript(scriptIndex); if(!script) return; PlayStreamFromEntityInternal(entity); } void audScriptAudioEntity::PlayStreamFrontend() { s32 scriptIndex = AddScript(); if((sm_ScriptInChargeOfTheStream != -1 && sm_ScriptInChargeOfTheStream != scriptIndex)) { #if !__FINAL DebugScriptStream(scriptIndex); #endif return; } audScript *script = GetScript(scriptIndex); if(!script) return; PlayStreamFrontendInternal(); } void audScriptAudioEntity::PlayStreamFromEntityInternal(CEntity *entity) { if(naVerifyf(entity, "CEntity* must be valid, can't play stream")) { if(naVerifyf((m_ScriptStreamState == AUD_SCRIPT_STREAM_PREPARED), "Can't play a stream that hasn't been prepared, m_ScriptStreamState: %d", m_ScriptStreamState)) { naAssertf(m_StreamSound, "About to access a null ptr..."); naAssertf(m_StreamSlot, "About to access a null ptr..."); if(!m_StreamSlot && g_DebugPlayingScriptSounds) { naWarningf("Must have a stream slot to play stream"); } m_StreamTrackerEntity = entity; m_StreamEnvironmentGroup = (naEnvironmentGroup*)entity->GetAudioEnvironmentGroup(); if(!m_StreamEnvironmentGroup) { m_StreamEnvironmentGroup = CreateScriptEnvironmentGroup(entity, entity->GetTransform().GetPosition()); } if(m_StreamSound) { m_StreamSound->GetRequestedSettings()->SetEnvironmentGroup(m_StreamEnvironmentGroup); m_StreamSound->SetRequestedPosition(m_StreamTrackerEntity->GetTransform().GetPosition()); m_StreamSound->Play(); } m_StreamType = AUD_PLAYSTREAM_ENTITY; m_ScriptStreamState = AUD_SCRIPT_STREAM_PLAYING; m_LoadedStreamRef.SetInvalid(); #if GTA_REPLAY m_DisableReplayScriptStreamRecording = IsFlagSet(audScriptAudioFlags::DisableReplayScriptStreamRecording); if(!m_DisableReplayScriptStreamRecording && m_StreamSound) { REPLAY_ONLY(CReplayMgr::ReplayRecordPlayStreamFromEntity(entity, m_StreamSound, sm_CurrentStreamName, sm_CurrentStartOffset, sm_CurrentSetName);) } #endif #if __USEDEBUGAUDIO if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] Playing stream: %s from entity",SOUNDFACTORY.GetMetadataManager().GetObjectNameFromNameTableOffset(m_StreamSound->GetBaseMetadata()->NameTableOffset)); } #endif } else if(g_DebugPlayingScriptSounds) { naWarningf("Can't play a stream that hasn't been prepared, m_ScriptStreamState: %d", m_ScriptStreamState); } } } void audScriptAudioEntity::PlayStreamFrontendInternal() { if(naVerifyf((m_ScriptStreamState == AUD_SCRIPT_STREAM_PREPARED), "Can't play a stream that hasn't been prepared, m_ScriptStreamState: %d", m_ScriptStreamState)) { naAssertf(m_StreamSound, "Must have a stream sound to play: about to access a null ptr...."); if(!m_StreamSound && g_DebugPlayingScriptSounds) { Displayf("Must have a stream sound to play: about to access a null ptr...."); } naAssertf(m_StreamSlot, "Must have a stream slot"); if(!m_StreamSlot && g_DebugPlayingScriptSounds) { naDisplayf("Must have a stream slot to play stream"); } if(m_StreamTrackerEntity) { m_StreamTrackerEntity = NULL; } m_StreamEnvironmentGroup = NULL; if(m_StreamSound) { m_StreamSound->Play(); } m_StreamType = AUD_PLAYSTREAM_FRONTEND; m_ScriptStreamState = AUD_SCRIPT_STREAM_PLAYING; m_LoadedStreamRef.SetInvalid(); #if GTA_REPLAY m_DisableReplayScriptStreamRecording = IsFlagSet(audScriptAudioFlags::DisableReplayScriptStreamRecording) || IsFlagSet(audScriptAudioFlags::DisableReplayScriptFrontendStreamRecording); if(!m_DisableReplayScriptStreamRecording && m_StreamSound) { REPLAY_ONLY(CReplayMgr::ReplayRecordPlayStreamFrontend(m_StreamSound, sm_CurrentStreamName, sm_CurrentStartOffset, sm_CurrentSetName);) } #endif #if __USEDEBUGAUDIO if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] Playing stream: %s frontend",SOUNDFACTORY.GetMetadataManager().GetObjectNameFromNameTableOffset(m_StreamSound->GetBaseMetadata()->NameTableOffset)); } #endif } else if(g_DebugPlayingScriptSounds) { Displayf("Can't play a stream that hasn't been prepared, m_ScriptStreamState: %d", m_ScriptStreamState); } } const u32 audScriptAudioEntity::GetStreamPlayTime() const { if(m_StreamSound && m_ScriptStreamState == AUD_SCRIPT_STREAM_PLAYING) { #if __USEDEBUGAUDIO if(naVerifyf(m_StreamSound->GetSoundTypeID() == StreamingSound::TYPE_ID,"Script stream %s, with a parent sound that it's not a stream sound. Please bug default audio.",g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectName(m_StreamSound->GetBaseMetadata()->NameTableOffset))) #endif { return ((audStreamingSound*)m_StreamSound)->GetCurrentPlayTimeOfWave(); } } return 0; } bool audScriptAudioEntity::IsPlayingStream() { bool isPlayingStream = false; if(m_StreamSound && m_ScriptStreamState == AUD_SCRIPT_STREAM_PLAYING) { isPlayingStream = true; } return isPlayingStream; } void audScriptAudioEntity::PlayStreamFromPosition(const Vector3 &pos) { s32 scriptIndex = AddScript(); if((sm_ScriptInChargeOfTheStream != -1 && sm_ScriptInChargeOfTheStream != scriptIndex)) { #if !__FINAL DebugScriptStream(scriptIndex); #endif return; } audScript *script = GetScript(scriptIndex); if(!script) return; PlayStreamFromPositionInternal(pos); } void audScriptAudioEntity::PlayStreamFromPositionInternal(const Vector3 &pos) { if(naVerifyf((m_ScriptStreamState == AUD_SCRIPT_STREAM_PREPARED), "Can't play a stream that hasn't been prepared, m_ScriptStreamState: %d", m_ScriptStreamState)) { naAssertf(m_StreamSound, "Must have a stream sound to play: about to access a null ptr...."); if(!m_StreamSound && g_DebugPlayingScriptSounds) { Displayf("Must have a stream sound to play: about to access a null ptr...."); } naAssertf(m_StreamSlot, "Must have a stream slot"); if(!m_StreamSlot && g_DebugPlayingScriptSounds) { naDisplayf("Must have a stream slot to play stream"); } if(m_StreamTrackerEntity) { m_StreamTrackerEntity = NULL; } m_StreamEnvironmentGroup = NULL; m_StreamPosition.Set(pos); if(m_StreamSound) { m_StreamSound->SetRequestedPosition(VECTOR3_TO_VEC3V(pos)); m_StreamSound->Play(); } m_StreamType = AUD_PLAYSTREAM_POSITION; m_ScriptStreamState = AUD_SCRIPT_STREAM_PLAYING; m_LoadedStreamRef.SetInvalid(); #if GTA_REPLAY m_DisableReplayScriptStreamRecording = IsFlagSet(audScriptAudioFlags::DisableReplayScriptStreamRecording); if(!m_DisableReplayScriptStreamRecording && m_StreamSound) { REPLAY_ONLY(CReplayMgr::ReplayRecordPlayStreamFromPosition(pos, m_StreamSound, sm_CurrentStreamName, sm_CurrentStartOffset, sm_CurrentSetName);) } #endif #if __USEDEBUGAUDIO if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] Playing stream: %s from position",SOUNDFACTORY.GetMetadataManager().GetObjectNameFromNameTableOffset(m_StreamSound->GetBaseMetadata()->NameTableOffset)); } #endif } else if(g_DebugPlayingScriptSounds) { Displayf("Can't play a stream that hasn't been prepared, m_ScriptStreamState: %d", m_ScriptStreamState); } } void audScriptAudioEntity::StopStream() { if(m_StreamSound) { m_StreamSound->StopAndForget(); } if(m_StreamSlot) { m_StreamSlot->Free(); m_StreamSlot = NULL; } if(m_StreamTrackerEntity) { m_StreamTrackerEntity = NULL; } m_StreamType = AUD_PLAYSTREAM_UNKNOWN; m_StreamEnvironmentGroup = NULL; m_ScriptStreamState = AUD_SCRIPT_STREAM_IDLE; m_LoadedStreamRef.SetInvalid(); audScriptAudioEntity::sm_ScriptInChargeOfTheStream = -1; #if __BANK if(g_ScriptedStreamSpew) { audDebugf1("[audScriptedStream] StopStream called frame %u",fwTimer::GetFrameCount()); } #endif #if GTA_REPLAY for(int i=0; iStopAndForget(); } g_ScriptAudioEntity.m_StreamType = AUD_PLAYSTREAM_UNKNOWN; #if GTA_REPLAY for(int i=0; iGetWaveSlot(); if(waveSlot && (waveSlot->GetIsLoading() || (waveSlot->GetReferenceCount() > 0))) { hasStopped = false; } } return hasStopped; } void audScriptAudioEntity::StopPedSpeaking(CPed *ped, bool shouldDisable ) { // for now, just flag them to be completely silent if (ped && ped->GetSpeechAudioEntity()) { ped->GetSpeechAudioEntity()->DisableSpeaking(shouldDisable ); } } void audScriptAudioEntity::BlockAllSpeechFromPed(CPed *ped, bool shouldDisable, bool shouldSuppressOutgoingNetworkSpeech) { if (ped && ped->GetSpeechAudioEntity()) { ped->GetSpeechAudioEntity()->BlockAllSpeechFromPed(shouldDisable, shouldSuppressOutgoingNetworkSpeech); } } void audScriptAudioEntity::StopPedSpeakingSynced(CPed *ped, bool shouldDisable) { // for now, just flag them to be completely silent if (ped && ped->GetSpeechAudioEntity()) { ped->GetSpeechAudioEntity()->DisableSpeakingSynced(shouldDisable ); } } void audScriptAudioEntity::StopPedPainAudio(CPed *ped, bool shouldDisable ) { if (ped && ped->GetSpeechAudioEntity()) { ped->GetSpeechAudioEntity()->DisablePainAudio(shouldDisable ); } } void audScriptAudioEntity::MobilePreRingHandler(u32 UNUSED_PARAM(hash), void *UNUSED_PARAM(context)) { g_ScriptAudioEntity.PlayMobilePreRing(g_MobilePreRingHash, 0.3f); } void audScriptAudioEntity::PlayMobileGetSignal(f32 probability) { g_ScriptAudioEntity.PlayMobilePreRing(g_MobileGetSignalHash, probability); } void audScriptAudioEntity::PlayMobilePreRing(u32 NA_RADIO_ENABLED_ONLY(mobileInterferenceHash), f32 NA_RADIO_ENABLED_ONLY(probability)) { #if NA_RADIO_ENABLED u32 timeNow = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0); // Purposefully leave a fair bit of a delay between triggers - don't want it constantly happening when popping in and out of tunnels if(timeNow - m_LastMobileInterferenceTime > 10000 BANK_ONLY(|| g_ForceMobileInterference)) { // If the radio's playing frontend, and we're not already playing a prering, play one now if(g_RadioAudioEntity.IsVehicleRadioOn() && !m_MobileInterferenceSound && (audEngineUtil::ResolveProbability(probability) BANK_ONLY(|| g_ForceMobileInterference))) { CVehicle* playerVehicle = CGameWorld::FindLocalPlayerVehicle(); if(playerVehicle && playerVehicle->GetVehicleAudioEntity()->GetAudioVehicleType() == AUD_VEHICLE_CAR) { CarAudioSettings* settings = ((audCarAudioEntity*)playerVehicle->GetVehicleAudioEntity())->GetCarAudioSettings(); if(settings && AUD_GET_TRISTATE_VALUE(settings->Flags, FLAG_ID_CARAUDIOSETTINGS_MOBILECAUSESRADIOINTERFERENCE) == AUD_TRISTATE_TRUE BANK_ONLY(|| g_ForceMobileInterference)) { CreateAndPlaySound_Persistent(mobileInterferenceHash, &m_MobileInterferenceSound); } } } m_LastMobileInterferenceTime = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0); } #endif } naEnvironmentGroup* audScriptAudioEntity::CreateScriptEnvironmentGroup(const CEntity* entity, const Vec3V& pos) const { naEnvironmentGroup* environmentGroup = naEnvironmentGroup::Allocate("Script"); if(environmentGroup) // Need to check for NULL because we could run out of space in the naEnvironmentGroup pool { // NULL entity; will be deleted when the sound is deleted environmentGroup->Init(NULL, 20); // Force a source environment update giving it our entity and interior information, since we don't normally have one if(entity) { environmentGroup->ForceSourceEnvironmentUpdate(entity); } // We only create script environmentGroups without an entity if we know they're outside, so set that info on the group else { environmentGroup->SetInteriorInfoWithInteriorInstance(NULL, INTLOC_ROOMINDEX_LIMBO); } environmentGroup->SetSource(pos); environmentGroup->SetUsePortalOcclusion(true); environmentGroup->SetMaxPathDepth(audNorthAudioEngine::GetOcclusionManager()->GetMaxOcclusionPathDepth()); } return environmentGroup; } #if GTA_REPLAY bool audScriptAudioEntity::HasAlreadyPlayed(u32 voiceNameHash, u32 contextNameHash, s32 variationNumber) { for(int i=0; iStopAndForget(); sm_ReplayScriptedSpeechSlot[i].ScriptedSpeechSound = NULL; } } g_SpeechManager.FreeAllScriptedSpeechSlots(); } ePreloadResult audScriptAudioEntity::PreloadReplayScriptedSpeech(u32 voiceNameHash, u32 contextHash, u32 variationNumber, s32 UNUSED_PARAM(waveSlotIndex )) { //Displayf("PreloadReplayScriptedSpeech time:%d - %u - %d - %d", CReplayMgr::GetCurrentTimeRelativeMs(), m_uContextNameHash, m_sWaveSlotIndex, m_sPreDelay); //Displayf("PreloadReplayScriptedSpeech time:%u - %u - %d - evenet:%u, %u, %u game:%u", CReplayMgr::GetCurrentTimeRelativeMs(), m_uContextNameHash, m_sWaveSlotIndex, // eventInfo.GetReplayTime(), eventInfo.GetPreloadOffsetTime(), eventInfo.GetIsFirstFrame(), // GetGameTime()); if(CReplayMgr::IsCursorFastForwarding() || CReplayMgr::IsCursorRewinding() || CReplayMgr::IsScrubbing()) { return PRELOAD_SUCCESSFUL; } bool hasAlreadyPlayed = g_ScriptAudioEntity.HasAlreadyPlayed(voiceNameHash, contextHash, variationNumber); if(hasAlreadyPlayed) { return PRELOAD_SUCCESSFUL; } s32 waveSlot = g_ScriptAudioEntity.LookupReplayPreloadScriptedSpeechSlot(voiceNameHash, contextHash, (s32)variationNumber); if( waveSlot == -1) { waveSlot = g_SpeechManager.GetScriptedSpeechSlot(NULL); } if(waveSlot == -1) { return PRELOAD_SUCCESSFUL; } s32 sSpeechSoundIndex = g_ScriptAudioEntity.GetFreeReplayScriptedSpeechSlot(voiceNameHash, contextHash, (s32)variationNumber, waveSlot); if(sSpeechSoundIndex < 0) { return PRELOAD_FAIL; } s32 sBaseWaveSlotIndex = audWaveSlot::GetWaveSlotIndex(audWaveSlot::FindWaveSlot("SCRIPT_SPEECH_0")); waveSlot += sBaseWaveSlotIndex; if(g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ScriptedSpeechSound == NULL && !hasAlreadyPlayed) { audSoundInitParams initParams; g_ScriptAudioEntity.CreateSound_PersistentReference( "PRELOAD_SPEECH_SOUND", (audSound**)&g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ScriptedSpeechSound, &initParams ); if( g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ScriptedSpeechSound ) { bool bSuccess = g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ScriptedSpeechSound->InitSpeech( voiceNameHash, contextHash, variationNumber ); if( !bSuccess ) { // ditch this sound, as it failed to find a variation g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ScriptedSpeechSound->StopAndForget(); return PRELOAD_SUCCESSFUL; } audWaveSlot* pWaveSlot = audWaveSlot::GetWaveSlotFromIndex(waveSlot); if( pWaveSlot ) { g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].WaveSlot = pWaveSlot; g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].WaveSlotIndex = waveSlot; } } } if( g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ScriptedSpeechSound ) { audWaveSlot* pWaveSlot = audWaveSlot::GetWaveSlotFromIndex(waveSlot); if(pWaveSlot) { audPrepareState prepareState = g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ScriptedSpeechSound->Prepare(pWaveSlot, true, naWaveLoadPriority::Speech); if( prepareState == AUD_PREPARE_FAILED ) { g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ScriptedSpeechSound->StopAndForget(); } else { g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].VoiceNameHash = voiceNameHash; g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].ContextNameHash = contextHash; g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[sSpeechSoundIndex].VariationNumber = (s32)variationNumber; if(prepareState == AUD_PREPARING) { return PRELOAD_FAIL; } } } } return PRELOAD_SUCCESSFUL; } int audScriptAudioEntity::LookupReplayPreloadScriptedSpeechSlot( u32 uVoiceNameHash, u32 uContextNameHash, s32 sVariationNumber) { for(int i=0; iPrepare(sm_ReplayScriptedSpeechSlot[i].WaveSlot, true, naWaveLoadPriority::Speech); if(state == AUD_PREPARE_FAILED || (state == AUD_PREPARED && g_ScriptAudioEntity.HasAlreadyPlayed(sm_ReplayScriptedSpeechSlot[i].VoiceNameHash, sm_ReplayScriptedSpeechSlot[i].ContextNameHash, sm_ReplayScriptedSpeechSlot[i].VariationNumber))) { sm_ReplayScriptedSpeechSlot[i].ScriptedSpeechSound->StopAndForget(); sm_ReplayScriptedSpeechSlot[i].ScriptedSpeechSound = NULL; } } } } u32 currentTime = g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(1); for(int i=0; i sm_ReplayScriptedSpeechAlreadyPlayed[i].TimeStamp + 10000) { //Displayf("RemoveFromAreadyPlayedList auto %u, %u, %u", CReplayMgr::GetCurrentTimeRelativeMs(), sm_ReplayScriptedSpeechAlreadyPlayed[i].ContextNameHash, sm_ReplayScriptedSpeechAlreadyPlayed[i].VoiceNameHash); sm_ReplayScriptedSpeechAlreadyPlayed[i].ContextNameHash = 0; sm_ReplayScriptedSpeechAlreadyPlayed[i].VariationNumber = -1; sm_ReplayScriptedSpeechAlreadyPlayed[i].VoiceNameHash = 0; sm_ReplayScriptedSpeechAlreadyPlayed[i].TimeStamp = 0; } } } void audScriptAudioEntity::CleanUpReplayScriptedSpeechWaveSlot(s32 waveSlotIndex) { s32 sBaseWaveSlotIndex = audWaveSlot::GetWaveSlotIndex(audWaveSlot::FindWaveSlot("SCRIPT_SPEECH_0")); replayAssert(waveSlotIndex >= sBaseWaveSlotIndex); s32 i = waveSlotIndex - sBaseWaveSlotIndex; CleanUpReplayScriptedSpeechSlot(i, true); } void audScriptAudioEntity::CleanUpReplayScriptedSpeechSlot(s32 slotIndex, bool force) { if(sm_ReplayScriptedSpeechSlot[slotIndex].ScriptedSpeechSound) { if(force) { sm_ReplayScriptedSpeechSlot[slotIndex].ScriptedSpeechSound->StopAndForget(); sm_ReplayScriptedSpeechSlot[slotIndex].ScriptedSpeechSound = NULL; } else if(sm_ReplayScriptedSpeechSlot[slotIndex].WaveSlot) { audWaveSlot* pWaveSlot = sm_ReplayScriptedSpeechSlot[slotIndex].WaveSlot; audPrepareState prepareState = g_ScriptAudioEntity.sm_ReplayScriptedSpeechSlot[slotIndex].ScriptedSpeechSound->Prepare(pWaveSlot, true, naWaveLoadPriority::Speech); if( prepareState != AUD_PREPARING ) { sm_ReplayScriptedSpeechSlot[slotIndex].ScriptedSpeechSound->StopAndForget(); sm_ReplayScriptedSpeechSlot[slotIndex].ScriptedSpeechSound = NULL; } } } g_SpeechManager.FreeScriptedSpeechSlot(slotIndex); } naEnvironmentGroup* audScriptAudioEntity::CreateReplayScriptedEnvironmentGroup(const CEntity* entity, const Vec3V& pos) const { return CreateScriptEnvironmentGroup(entity, pos); } bool audScriptAudioEntity::IsPlayerConversationActive(const CPed* pPlayer, s16& currentLine) { //Check if the player is in the conversation if(IsPedInCurrentConversation(pPlayer)) { // Check we've actually started the conversation if ( m_PlayingScriptedConversationLine>=0 && m_PlayingScriptedConversationLine < AUD_MAX_SCRIPTED_CONVERSATION_LINES && (currentLine == -1 || currentLine == m_PlayingScriptedConversationLine) ) { currentLine = m_PlayingScriptedConversationLine; return true; } } currentLine = -1; return false; } void audScriptAudioEntity::RecordScriptSlotSound(const char* setName, const char* soundName, audSoundInitParams* initParams, const CEntity* pEntity, audSound* sound) { RecordScriptSlotSound(setName ? atStringHash(setName) : 0, soundName ? atStringHash(soundName) : g_NullSoundHash,initParams,pEntity,sound); } void audScriptAudioEntity::RecordScriptSlotSound(const int setNameHash, const int soundNameHash, audSoundInitParams* initParams, const CEntity* pEntity, audSound* sound) { if(pEntity && pEntity->GetAudioEntity()) { if(setNameHash != 0) { CReplayMgr::ReplayRecordSoundPersistant(setNameHash,soundNameHash, initParams, sound, pEntity, eNoGlobalSoundEntity, ReplaySound::CONTEXT_INVALID); } else { CReplayMgr::ReplayRecordSoundPersistant(soundNameHash, initParams, sound, pEntity, eNoGlobalSoundEntity, ReplaySound::CONTEXT_INVALID); } } else { if(setNameHash != 0) { CReplayMgr::ReplayRecordSoundPersistant(setNameHash,soundNameHash, initParams, sound, pEntity, eScriptSoundEntity, ReplaySound::CONTEXT_INVALID); } else { CReplayMgr::ReplayRecordSoundPersistant(soundNameHash, initParams, sound, pEntity, eScriptSoundEntity, ReplaySound::CONTEXT_INVALID); } } } void audScriptAudioEntity::RecordUpdateScriptSlotSound(const u32 setNameHash, const u32 soundNameHash, audSoundInitParams* initParams, const CEntity* pEntity, audSound* sound) { if(pEntity && pEntity->GetAudioEntity()) { if(setNameHash != g_NullSoundHash) { CReplayMgr::ReplayRecordSoundPersistant(setNameHash, soundNameHash, initParams, sound, pEntity, eNoGlobalSoundEntity, ReplaySound::CONTEXT_INVALID, true); } else { CReplayMgr::ReplayRecordSoundPersistant(soundNameHash, initParams, sound, pEntity, eNoGlobalSoundEntity, ReplaySound::CONTEXT_INVALID, true); } } else { if(setNameHash != g_NullSoundHash) { CReplayMgr::ReplayRecordSoundPersistant(setNameHash, soundNameHash, initParams, sound, pEntity, eScriptSoundEntity, ReplaySound::CONTEXT_INVALID, true); } else { CReplayMgr::ReplayRecordSoundPersistant(soundNameHash, initParams, sound, pEntity, eScriptSoundEntity, ReplaySound::CONTEXT_INVALID, true); } } } #endif #if __BANK const char* audScriptAudioEntity::GetStreamStateName(audScriptStreamState state) { switch(state) { case AUD_SCRIPT_STREAM_IDLE: return "AUD_SCRIPT_STREAM_IDLE"; case AUD_SCRIPT_STREAM_PREPARING: return "AUD_SCRIPT_STREAM_PREPARING"; case AUD_SCRIPT_STREAM_PREPARED: return "AUD_SCRIPT_STREAM_PREPARED"; case AUD_SCRIPT_STREAM_PLAYING: return "AUD_SCRIPT_STREAM_PLAYING"; default: return "UNKNOWN"; } } void audScriptAudioEntity::DebugScriptedConversation() { naConvWarningf("Debugging scripted conversation:"); if (m_ScriptedConversationInProgress) { naConvWarningf("Scripted conversation in progress"); } if (m_IsScriptedConversationAMobileCall) { naConvWarningf("Scripted conversation is a mobile call"); } if (m_WaitingForPreload) { naConvWarningf("WaitingForPreload"); } if (m_WaitingForPreload) { naConvWarningf("WaitingForPreload"); } if (m_PauseConversation) { naConvWarningf("PauseConversation"); } naConvWarningf("PSCL:%d; HighestLoadedLine:%d; HLoadingL:%d; PLSL:%d", m_PlayingScriptedConversationLine, m_HighestLoadedLine, m_HighestLoadingLine, m_PreLoadingSpeechLine); for (s32 i=0; iGetLoadedBankName(), m_BankSlots[i].BankSlot->GetLoadedBankId()); naDisplayf(" State: %s", stateNames[m_BankSlots[i].State]); naDisplayf(" Ref Count: %u", m_BankSlots[i].ReferenceCount); for(u32 j = 0; j < AUD_MAX_SCRIPT_NETWORK_BANK_SLOTS; j++) { if(m_BankSlots[i].NetworkScripts[j].IsValid()) { naDisplayf(" Network Script %d: %s (%u)", j, m_BankSlots[i].NetworkScripts[j].GetScriptName(), (u32)m_BankSlots[i].NetworkScripts[j].GetScriptNameHash()); } else { naDisplayf(" Network Script %d: none", j); } } } } const char* audScriptAudioEntity::GetScriptName(s32 scriptIndex) { audScript * script = GetScript(scriptIndex); if (script) { GtaThread* pThread = static_cast(scrThread::GetThread(script->ScriptThreadId)); if (pThread) { return pThread->GetScriptName(); } } return "none"; } void audScriptAudioEntity::LogScriptBankRequest(const char *request,const u32 scriptIndex,const u32 bankId, const u32 bankIndex) { const char * scriptName = "none"; audScript * script = GetScript(scriptIndex); if(script) { GtaThread* pThread = static_cast(scrThread::GetThread(script->ScriptThreadId)); scriptName = pThread ? pThread->GetScriptName() : "none"; } if(audVerifyf(bankIndex < m_BankSlots.GetMaxCount(), "Bank index %u exceeds slot count %u, Request %s, Script %s", bankIndex, m_BankSlots.GetMaxCount(), request, scriptName)) { naDisplayf("%s [Script %s] [ScriptIdx %u] [Bank Idx %u] [Bank Name %s] [BankSlotIdx %u] [RefCount %d]" ,request,scriptName,scriptIndex,bankId,audWaveSlot::GetBankName(bankId),m_Scripts[scriptIndex].BankSlotIndex,m_BankSlots[bankIndex].ReferenceCount); if (m_BankSlots[bankIndex].BankSlot) { naDisplayf("Script bankSlot: [Id %u] [Bankslot: %s] [WaveSlot: %s]",m_BankSlots[bankIndex].BankId,m_BankSlots[bankIndex].BankSlot->GetLoadedBankName(),m_BankSlots[bankIndex].BankSlot->GetSlotName()); } else { naDisplayf("Script bankSlot: Null"); } const audWaveSlot* waveSlot = audWaveSlot::FindLoadedBankWaveSlot(m_BankSlots[bankIndex].BankId); if(waveSlot) { naDisplayf("WaveSlot: [Id %u] [Bankslot: %s] [WaveSlot: %s]",waveSlot->GetLoadedBankId(),waveSlot->GetCachedLoadedBankName(),waveSlot->GetSlotName()); } else { naDisplayf("WaveSlot: Null"); } } } void audScriptAudioEntity::LogNetworkScriptBankRequest(const char *request,const char* scriptName,const u32 bankId, const u32 bankIndex) { if(audVerifyf(bankIndex < m_BankSlots.GetMaxCount(), "Bank index %u exceeds slot count %u, Request %s, Script %s", bankIndex, m_BankSlots.GetMaxCount(), request, scriptName)) { naDisplayf("%s [Script %s] [Bank Idx %u] [Bank Name %s]" ,request,scriptName,bankId,audWaveSlot::GetBankName(bankId)); if (m_BankSlots[bankIndex].BankSlot) { naDisplayf("Script bankSlot: [Id %u] [Bankslot: %s] [WaveSlot: %s]",m_BankSlots[bankIndex].BankId,m_BankSlots[bankIndex].BankSlot->GetLoadedBankName(),m_BankSlots[bankIndex].BankSlot->GetSlotName()); } else { naDisplayf("Script bankSlot: Null"); } const audWaveSlot* waveSlot = audWaveSlot::FindLoadedBankWaveSlot(m_BankSlots[bankIndex].BankId); if(waveSlot) { naDisplayf("WaveSlot: [Id %u] [Bankslot: %s] [WaveSlot: %s]",waveSlot->GetLoadedBankId(),waveSlot->GetCachedLoadedBankName(),waveSlot->GetSlotName()); } else { naDisplayf("WaveSlot: Null"); } } } void audScriptAudioEntity::DrawDebug() { if(sm_DrawReplayScriptSpeechSlots) { char tempString[64]; static bank_float lineInc = 0.015f; f32 lineBase = 0.1f; #if GTA_REPLAY for(int i=0; iPrepare(sm_ReplayScriptedSpeechSlot[i].WaveSlot, true, naWaveLoadPriority::Speech); } bool played = false; played = g_ScriptAudioEntity.HasAlreadyPlayed(sm_ReplayScriptedSpeechSlot[i].VoiceNameHash, sm_ReplayScriptedSpeechSlot[i].ContextNameHash, sm_ReplayScriptedSpeechSlot[i].VariationNumber); sprintf(tempString, "context %u voice %u Speech %d Waveslot %d State %d Played %d", sm_ReplayScriptedSpeechSlot[i].ContextNameHash, sm_ReplayScriptedSpeechSlot[i].VoiceNameHash, sm_ReplayScriptedSpeechSlot[i].ScriptedSpeechSound ? 1 : 0, index, state, played); grcDebugDraw::Text(Vector2(0.1f, lineBase), Color32(255,255,255), tempString ); lineBase += lineInc; } #endif // GTA_REPLAY lineBase += lineInc; lineBase += lineInc; for(int i=0; iBankSlotIndex & BIT(i)) { GtaThread* pThread = static_cast(scrThread::GetThread(script->ScriptThreadId)); scriptName = pThread ? pThread->GetScriptName() : "none"; } } } #endif bool isReleased = m_BankSlots[i].ReferenceCount == 0; char buf[128]; Color32 color = Color_green; if(isReleased) { color = Color_blue; formatf(state,"FREE:"); } else if( m_BankSlots[i].State == AUD_SCRIPT_BANK_LOADING ) { color = Color_yellow; formatf(state,"LOADING..."); } if (m_BankSlots[i].HintLoaded) { color = Color_LightBlue; formatf(state,"HINTED:"); } formatf(buf, "%s [Script %s] [Script Bank %s] [Bank Idx %u] [Script bank slot %d]",state,scriptName,m_BankSlots[i].BankSlot->GetLoadedBankName(), m_BankSlots[i].BankSlot->GetLoadedBankId(), i); grcDebugDraw::Text(Vector2(0.05f, offset), color,buf,true,1.0f,1.0f,1); offset += 0.02f; } } } if(sm_DrawScriptStream && m_StreamSound) { GtaThread* streamThread = static_cast(scrThread::GetThread(m_Scripts[sm_ScriptInChargeOfTheStream].ScriptThreadId)); const char *streamScriptName = streamThread ? streamThread->GetScriptName() : "none"; formatf(buf, "Script: %s, Stream : %s, script idx : %d, type %d",streamScriptName,m_StreamSound->GetName(),sm_ScriptInChargeOfTheStream, m_StreamType); grcDebugDraw::Text(Vector2(0.05f, offset), Color32(255,255,255),buf,true,1.0f,1.0f,1); offset += 0.02f; } //Script sounds log if(sm_LogScriptTriggeredSounds && m_FileHandle == INVALID_FILE_HANDLE) { m_FileHandle = CFileMgr::OpenFileForWriting("common:/ScriptTriggeredSoundsLog.csv"); if(m_FileHandle != INVALID_FILE_HANDLE) { const char *header = "Time,Name,Sound,Sound idx,script idx\r\n"; CFileMgr::Write(m_FileHandle, header, istrlen(header)); CFileMgr::CloseFile(m_FileHandle); m_FileHandle = CFileMgr::OpenFileForAppending("common:/ScriptTriggeredSoundsLog.csv"); } } for(u32 soundIdx = 0; soundIdx < AUD_MAX_SCRIPT_SOUNDS; soundIdx++) { if(m_ScriptSounds[soundIdx].Sound) { const char * scriptName = "none"; audScript * script = GetScript(m_ScriptSounds[soundIdx].ScriptIndex); if(script) { GtaThread* pThread = static_cast(scrThread::GetThread(script->ScriptThreadId)); scriptName = pThread ? pThread->GetScriptName() : "none"; } if(sm_DrawScriptTriggeredSounds) { audScriptSound scriptSound = m_ScriptSounds[soundIdx]; if(scriptSound.SoundSetHash) { formatf(buf, "Script: %s, Soundset: %s, Field: %s, Sound: %s, Sound Idx: %d, Script Idx: %d",scriptName, m_ScriptSounds[soundIdx].SoundSetTriggeredName, m_ScriptSounds[soundIdx].SoundTriggeredName, m_ScriptSounds[soundIdx].Sound->GetName(),soundIdx, scriptSound.ScriptIndex); } else { formatf(buf, "Script: %s, Sound:%s, Sound Idx: %d, Script Idx: %d",scriptName, m_ScriptSounds[soundIdx].Sound->GetName(), soundIdx, scriptSound.ScriptIndex); } grcDebugDraw::Text(Vector2(0.05f, offset), Color32(255,255,255),buf,true,1.0f,1.0f,1); offset += 0.02f; } if(sm_InWorldDrawScriptTriggeredSounds) { bool found = true; if(sm_FilterScriptTriggeredSounds) { found = false; naDisplayf("sound name :%s text :%s ",m_ScriptSounds[soundIdx].Sound->GetName(),sm_ScriptTriggerOverride); if(atStringHash(m_ScriptSounds[soundIdx].Sound->GetName()) == soundHash) { found = true; } if (soundIdx == (u32)strtol(sm_ScriptTriggerOverride,NULL,0)) { found = true; } } if(found) { audScriptSound scriptSound = m_ScriptSounds[soundIdx]; formatf(buf, "Script: %s, SoundTriggeredName : %s, Soundset: %s , Sound: %s sound idx : %d, script idx : %d",scriptName, m_ScriptSounds[soundIdx].SoundTriggeredName, m_ScriptSounds[soundIdx].SoundSetTriggeredName,m_ScriptSounds[soundIdx].Sound->GetName(),soundIdx, scriptSound.ScriptIndex); grcDebugDraw::Text(m_ScriptSounds[soundIdx].Sound->GetRequestedPosition(), Color32(255, 255, 255, 255),buf,true,1); } } if(m_FileHandle != INVALID_FILE_HANDLE && m_ScriptSounds[soundIdx].Sound) { audScriptSound scriptSound = m_ScriptSounds[soundIdx]; formatf(buf, "%f,%s,%s,%s,%s,%d,%d\r\n",fwTimer::GetTimeInMilliseconds(),scriptName ,m_ScriptSounds[soundIdx].SoundTriggeredName, m_ScriptSounds[soundIdx].SoundSetTriggeredName,m_ScriptSounds[soundIdx].Sound->GetName(),soundIdx, scriptSound.ScriptIndex); CFileMgr::Write(m_FileHandle, buf, istrlen(buf)); } } } if(!sm_LogScriptTriggeredSounds && m_FileHandle != INVALID_FILE_HANDLE) { CFileMgr::CloseFile(m_FileHandle); m_FileHandle = INVALID_FILE_HANDLE; } if(g_DisplayScriptedLineInfo && sm_ScriptedLineDebugInfo[0] != '\0') { grcDebugDraw::Text(Vector2(0.05f, 0.8f), Color32(255,255,255),sm_ScriptedLineDebugInfo,true,1.0f,1.0f,1); } if(g_DisplayScriptSoundIdInfo) { f32 yCoord = 0.02f - g_ScriptSoundIdInfoScroll; grcDebugDraw::Text(Vector2(0.05f, yCoord), Color_white, "Slot", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.1f, yCoord), Color_white, "Owner", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.25f, yCoord), Color_white, "Sound", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.4f, yCoord), Color_white, "Sound State", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.55f, yCoord), Color_white, "Age", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.7f, yCoord), Color_white, "Frame Allocated", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.85f, yCoord), Color_white, "Script Has Ref", true, 1.f, 1.f, -1); yCoord += 0.04f; const char* soundStates[AUD_SOUND_STATE_MAX + 1] = {"Dormant","Preparing","Playing","Waiting to be Deleted","Max"}; for (u32 scriptSoundIndex = 0; scriptSoundIndex < AUD_MAX_SCRIPT_SOUNDS; scriptSoundIndex++) { // This is the logic used in FindFreeSoundId, so invert it to find all used slots if (!(m_ScriptSounds[scriptSoundIndex].ScriptIndex == -1 && m_ScriptSounds[scriptSoundIndex].NetworkId == audScriptSound::INVALID_NETWORK_ID)) { formatf(buf, "%d", scriptSoundIndex); grcDebugDraw::Text(Vector2(0.05f, yCoord), Color_white, buf, true, 1.f, 1.f, -1); formatf(buf, "%s", m_ScriptSounds[scriptSoundIndex].ScriptName); grcDebugDraw::Text(Vector2(0.1f, yCoord), Color_white, buf, true, 1.f, 1.f, -1); formatf(buf, m_ScriptSounds[scriptSoundIndex].SoundRef.IsValid() ? g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectName(m_ScriptSounds[scriptSoundIndex].SoundRef) : "Invalid"); grcDebugDraw::Text(Vector2(0.25f, yCoord), Color_white, buf, true, 1.f, 1.f, -1); formatf(buf, "%s", m_ScriptSounds[scriptSoundIndex].Sound? soundStates[m_ScriptSounds[scriptSoundIndex].Sound->GetPlayState()] : "No Sound"); grcDebugDraw::Text(Vector2(0.4f, yCoord), Color_white, buf, true, 1.f, 1.f, -1); formatf(buf, "%.02f", (fwTimer::GetTimeInMilliseconds() - m_ScriptSounds[scriptSoundIndex].TimeAllocated) * 0.001f); grcDebugDraw::Text(Vector2(0.55f, yCoord), Color_white, buf, true, 1.f, 1.f, -1); formatf(buf, "%u", m_ScriptSounds[scriptSoundIndex].FrameAllocated); grcDebugDraw::Text(Vector2(0.7f, yCoord), Color_white, buf, true, 1.f, 1.f, -1); formatf(buf, "%s", m_ScriptSounds[scriptSoundIndex].ScriptHasReference? "true" : "false"); grcDebugDraw::Text(Vector2(0.85f, yCoord), Color_white, buf, true, 1.f, 1.f, -1); } yCoord += 0.02f; } } if(g_DisplayScriptBankUsage) { f32 yCoord = 0.02f - g_ScriptSoundIdInfoScroll; grcDebugDraw::Text(Vector2(0.05f, yCoord), Color_white, "Slot", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.1f, yCoord), Color_white, "Bank", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.3f, yCoord), Color_white, "State", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.4f, yCoord), Color_white, "Ref Count", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.5f, yCoord), Color_white, "Network Script A", true, 1.f, 1.f, -1); grcDebugDraw::Text(Vector2(0.7f, yCoord), Color_white, "Network Script B", true, 1.f, 1.f, -1); yCoord += 0.04f; for (s32 i=0; iGetSlotName(), m_ScriptedSpeechSlot[i][currentPreloadSet].WaveSlot->GetLoadedWaveNameHash(), m_ScriptedSpeechSlot[i][currentPreloadSet].Context, m_ScriptedSpeechSlot[i][currentPreloadSet].VoiceName ); grcDebugDraw::Text(Vector2(xPos, yPos), Color_white, str); yPos += yInc; if (m_ScriptedSpeechSlot[i][currentPreloadSet].ScriptedSpeechSound) { sprintf(str, "SoundInfo %d %d Waveslot: %p, WaveNameHash: %u BankId: %u", currentPreloadSet, i, m_ScriptedSpeechSlot[i][currentPreloadSet].ScriptedSpeechSound->GetWaveSlot()->GetSlotName(), m_ScriptedSpeechSlot[i][currentPreloadSet].ScriptedSpeechSound->GetWaveNameHash(), m_ScriptedSpeechSlot[i][currentPreloadSet].ScriptedSpeechSound->GetBankId()); grcDebugDraw::Text(Vector2(xPos, yPos), Color_white, str); } yPos += yInc; } } } } } void audScriptAudioEntity::DumpScriptSoundIdUsage() { char str[1024]; FileHandle fileHandle = CFileMgr::OpenFileForWriting("common:/ScriptSoundIdUsage.csv"); if(fileHandle != INVALID_FILE_HANDLE) { const char *header = "Slot,Owner,Sound,State,Age,Frame Allocated,Script Has Ref\r\n"; CFileMgr::Write(fileHandle, header, istrlen(header)); const char* soundStates[AUD_SOUND_STATE_MAX + 1] = {"Dormant","Preparing","Playing","Waiting to be Deleted","Max"}; for (u32 scriptSoundIndex = 0; scriptSoundIndex < AUD_MAX_SCRIPT_SOUNDS; scriptSoundIndex++) { // This is the logic used in FindFreeSoundId, so invert it to find all used slots if (!(m_ScriptSounds[scriptSoundIndex].ScriptIndex == -1 && m_ScriptSounds[scriptSoundIndex].NetworkId == audScriptSound::INVALID_NETWORK_ID)) { formatf(str, "%d,%s,%s,%s,%.02f,%u,%s\r\n", scriptSoundIndex, m_ScriptSounds[scriptSoundIndex].ScriptName, m_ScriptSounds[scriptSoundIndex].SoundRef.IsValid() ? g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectName(m_ScriptSounds[scriptSoundIndex].SoundRef) : "Invalid", m_ScriptSounds[scriptSoundIndex].Sound? soundStates[m_ScriptSounds[scriptSoundIndex].Sound->GetPlayState()] : "No Sound", (fwTimer::GetTimeInMilliseconds() - m_ScriptSounds[scriptSoundIndex].TimeAllocated) * 0.001f, m_ScriptSounds[scriptSoundIndex].FrameAllocated, m_ScriptSounds[scriptSoundIndex].ScriptHasReference? "true" : "false" ); CFileMgr::Write(fileHandle, str, istrlen(str)); } } audDisplayf("Successfully dumped script sound usage to common:/ScriptSoundIdUsage.csv"); CFileMgr::CloseFile(fileHandle); } else { audErrorf("Failed to open common:/ScriptSoundIdUsage.csv"); } } void MissionCompleteCB() { g_ScriptAudioEntity.TriggerMissionComplete("FRANKLIN_BIG_01"); } void TriggerAmbientSpeechCB() { g_ScriptAudioEntity.SayAmbientSpeechFromPosition(g_AmbientSpeechDebugContext, atStringHash(g_AmbientSpeechDebugVoice), Vector3(g_AmbientSpeechDebugPosX, g_AmbientSpeechDebugPosY, g_AmbientSpeechDebugPosZ), g_AmbientSpeechDebugParams); } void audScriptAudioEntity::ForceLowInterrupt() { if(m_Ped[g_InterruptSpeakerNum]) { RegisterScriptRequestedInterrupt(m_Ped[g_InterruptSpeakerNum],"", "",false, true); } } void audScriptAudioEntity::ForceMediumInterrupt() { if(m_Ped[g_InterruptSpeakerNum]) { RegisterScriptRequestedInterrupt(m_Ped[g_InterruptSpeakerNum],"", "",false, true); } } void audScriptAudioEntity::ForceHighInterrupt() { if(m_Ped[g_InterruptSpeakerNum]) { RegisterScriptRequestedInterrupt(m_Ped[g_InterruptSpeakerNum],"", "",false, true); } } void audScriptAudioEntity::DebugEnableFlag(const char *flagName) { // Note - using the string version here only to test the map; code interaction with flags // should use the flag ids directly. const audScriptAudioFlags::FlagId flagId = audScriptAudioFlags::FindFlagId(flagName); g_ScriptAudioEntity.SetFlagState(flagId, true); } void audScriptAudioEntity::DebugDisableFlag(const char *flagName) { // Note - using the string version here only to test the map; code interaction with flags // should use the flag ids directly. const audScriptAudioFlags::FlagId flagId = audScriptAudioFlags::FindFlagId(flagName); g_ScriptAudioEntity.SetFlagState(flagId, false); } void audScriptAudioEntity::AddWidgets(bkBank &bank) { bank.PushGroup("audScriptAudioEntity",false); bank.AddToggle("Display Script Sound ID Usage", &g_DisplayScriptSoundIdInfo); bank.AddToggle("Display Script Bank Usage", &g_DisplayScriptBankUsage); bank.AddSlider("Sound ID Usage Display Scroll", &g_ScriptSoundIdInfoScroll, 0, 1.5f, 0.1f); bank.AddButton("Dump Script Sound Id Usage (common:/ScriptSoundIdUsage.csv)", datCallback(MFA(audScriptAudioEntity::DumpScriptSoundIdUsage), &g_ScriptAudioEntity)); bank.AddButton("Log Script Bank Usage", datCallback(MFA(audScriptAudioEntity::LogScriptBanks), &g_ScriptAudioEntity)); bank.AddButton("TriggerMissionComplete", CFA(MissionCompleteCB)); bank.AddToggle("Debug Replay Scripted Speech", &sm_DrawReplayScriptSpeechSlots); bank.AddButton("Debug Scripted Conv NOW", datCallback(CFA(DebugScriptedConversationStatic))); bank.AddToggle("Debug scripted convs", &g_DebugScriptedConvs); bank.AddToggle("g_DisplayScriptedLineInfo", &g_DisplayScriptedLineInfo); bank.AddSlider("g_MinRagdollTimeBeforeConvPause", &g_MinRagdollTimeBeforeConvPause, 0, 5000, 10); bank.AddSlider("g_MinNotRagdollTimeBeforeConvRestart", &g_MinNotRagdollTimeBeforeConvRestart, 0, 5000, 10); bank.AddToggle("g_AnimTriggersControllingConversation", &g_AnimTriggersControllingConversation); bank.AddToggle("g_FakeConvAnimTrigger", &g_FakeConvAnimTrigger); bank.AddToggle("g_DuckForConvAfterPreload", &g_DuckForConvAfterPreload); bank.AddToggle("g_ConversationDebugSpew", &g_ConversationDebugSpew); bank.AddToggle("Force Mobile Interference", &g_ForceMobileInterference); bank.AddToggle("g_AdjustOverlapForLipsyncPredelay", &g_AdjustOverlapForLipsyncPredelay); bank.AddToggle("g_AdjustTriggerTimeForZeroPlaytime", &g_AdjustTriggerTimeForZeroPlaytime); bank.AddToggle("g_AdjustTriggerForZeroEvenIfMarkedOverlap", &g_AdjustTriggerForZeroEvenIfMarkedOverlap); bank.AddSlider("g_AdditionalScriptedSpeechPause", &g_AdditionalScriptedSpeechPause, 0, 1000, 10); bank.AddToggle("g_TrackMissingDialogue", &g_TrackMissingDialogue); bank.AddToggle("g_TrackMissingDialogueMP", &g_TrackMissingDialogueMP); bank.AddSlider("g_MinLowPainBikeReaction", &g_MinLowPainBikeReaction, 0.0f, 100.0f, 0.01f); bank.AddToggle("g_EnableAircraftWarningSpeech", &g_EnableAircraftWarningSpeech); bank.PushGroup("Conversation Interrupts",true); bank.AddButton("Force Low Interrupt", datCallback(MFA(audScriptAudioEntity::ForceLowInterrupt), &g_ScriptAudioEntity)); bank.AddButton("Force Medium Interrupt", datCallback(MFA(audScriptAudioEntity::ForceMediumInterrupt), &g_ScriptAudioEntity)); bank.AddButton("Force High Interrupt", datCallback(MFA(audScriptAudioEntity::ForceHighInterrupt), &g_ScriptAudioEntity)); bank.AddSlider("Player Speaker Number", &g_InterruptSpeakerNum, 0, 8, 1); bank.AddToggle("g_DisableInterrupts", &g_DisableInterrupts); bank.AddToggle("g_ForceScriptedInterrupts", &g_ForceScriptedInterrupts); bank.AddToggle("g_DisableAirborneInterrupts", &g_DisableAirborneInterrupts); bank.AddToggle("g_AllowMuliWantedInterrupts", &g_AllowMuliWantedInterrupts); bank.AddSlider("g_MinTimeCarInAirForInterrupt", &g_MinTimeCarInAirForInterrupt, 0, 20000, 10); bank.AddSlider("g_MinTimeCarOnGroundForInterrupt", &g_MinTimeCarOnGroundForInterrupt, 0, 20000, 10); bank.AddSlider("g_MinTimeCarOnGroundToRestart", &g_MinTimeCarOnGroundToRestart, 0, 20000, 10); bank.AddSlider("g_MinAirBorneTimeToKillConversation", &g_MinAirBorneTimeToKillConversation, 0, 20000, 10); bank.AddSlider("g_MaxAirBorneTimeToApplyRestartOffset", &g_MaxAirBorneTimeToApplyRestartOffset, 0, 20000, 10); bank.AddSlider("g_MinCarRollForInterrupt", &g_MinCarRollForInterrupt, -1.0f, 0.0f, 0.01f); bank.AddSlider("g_MinLowReactionDamage", &g_MinLowReactionDamage, 0.0f, 200.0f, 1.0f); bank.AddSlider("g_MinMediumReactionDamage", &g_MinMediumReactionDamage, 0.0f, 200.0f, 1.0f); bank.AddSlider("g_MinHighReactionDamage", &g_MinHighReactionDamage, 0.0f, 200.0f, 1.0f); bank.AddSlider("g_TimeToPauseConvOnCollision", &g_TimeToPauseConvOnCollision, 0, 10000, 50); bank.AddSlider("g_MinTimeBetweenCollisionReactions", &g_MinTimeBetweenCollisionReactions, 0, 10000, 50); bank.AddSlider("g_MinTimeBetweenCollisionTriggers", &g_MinTimeBetweenCollisionTriggers, 0, 10000, 50); bank.AddSlider("g_TimeToWaitForInterruptToPrepare", &g_TimeToWaitForInterruptToPrepare, 0, 5000, 50); bank.AddToggle("g_ForceNonPlayerInterrupt", &g_ForceNonPlayerInterrupt); bank.AddText("Low Interrupt Context", g_szLowInterruptContext, sizeof(g_szLowInterruptContext)); bank.AddText("Medium Interrupt Context", g_szMediumInterruptContext, sizeof(g_szLowInterruptContext)); bank.AddText("High Interrupt Context", g_szHighInterruptContext, sizeof(g_szLowInterruptContext)); bank.PopGroup(); bank.PushGroup("Ambient speech", true); bank.AddButton("TriggerMissionComplete", CFA(TriggerAmbientSpeechCB)); bank.AddText("g_AmbientSpeechDebugContext", &g_AmbientSpeechDebugContext[0], sizeof(g_AmbientSpeechDebugContext)); bank.AddText("g_AmbientSpeechDebugVoice", &g_AmbientSpeechDebugVoice[0], sizeof(g_AmbientSpeechDebugVoice)); bank.AddText("g_AmbientSpeechDebugParams", &g_AmbientSpeechDebugParams[0], sizeof(g_AmbientSpeechDebugParams)); bank.AddSlider("g_AmbientSpeechDebugPosX", &g_AmbientSpeechDebugPosX, -10000.0f, 10000.0f, 0.1f); bank.AddSlider("g_AmbientSpeechDebugPosY", &g_AmbientSpeechDebugPosY, -10000.0f, 10000.0f, 0.1f); bank.AddSlider("g_AmbientSpeechDebugPosZ", &g_AmbientSpeechDebugPosZ, -1000.0f, 1000.0f, 0.1f); bank.PopGroup(); bank.PushGroup("Debug script sounds",true); bank.AddToggle("Screen debug playing script-triggered sounds", &sm_DrawScriptTriggeredSounds); bank.AddToggle("In world debug playing script-triggered sounds", &sm_InWorldDrawScriptTriggeredSounds); bank.AddToggle("Screen debug playing script-stream sound", &sm_DrawScriptStream); bank.AddToggle("Log script-triggered sounds playing.", &sm_LogScriptTriggeredSounds); bank.AddToggle("Show script banks loaded.", &sm_DrawScriptBanksLoaded); bank.AddToggle("Filter script-triggered sounds", &sm_FilterScriptTriggeredSounds); bank.AddText("By sound id or sound name", sm_ScriptTriggerOverride, 64, false); bank.PopGroup(); bank.PushGroup("Script audio flags", true); for(s32 i = 0; i < audScriptAudioFlags::kNumScriptAudioFlags; i++) { char buttonName[128]; const audScriptAudioFlags::FlagId flagId = (audScriptAudioFlags::FlagId)i; formatf(buttonName, "Enable %s", audScriptAudioFlags::GetName(flagId)); bank.AddButton(buttonName, datCallback(CFA1(audScriptAudioEntity::DebugEnableFlag), (void*)audScriptAudioFlags::GetName(flagId))); formatf(buttonName, "Disable %s", audScriptAudioFlags::GetName(flagId)); bank.AddButton(buttonName, datCallback(CFA1(audScriptAudioEntity::DebugDisableFlag), (void*)audScriptAudioFlags::GetName(flagId))); } bank.PushGroup("Enable counts"); for(s32 i = 0; i < audScriptAudioFlags::kNumScriptAudioFlags; i++) { bank.AddText(audScriptAudioFlags::GetName((audScriptAudioFlags::FlagId)i), &g_ScriptAudioEntity.m_FlagState[i], true); } bank.PopGroup(); bank.PopGroup(); bank.PopGroup(); } #endif /****************************************************** * audScript *****************************************************/ audScene* audScript::GetScene(u32 sceneHash) { audScriptScene * scriptScene = scenes; while(scriptScene) { if(scriptScene->m_scene && scriptScene->m_scene->IsInstanceOf(sceneHash)) { return scriptScene->m_scene; } scriptScene = scriptScene->m_next; } return NULL; } bool audScript::StartScene(u32 sceneHash) { MixerScene * sceneSettings = DYNAMICMIXMGR.GetObject(sceneHash); audScene * existingScene = GetScene(sceneHash); if(existingScene) { return true; } audScriptScene * sScene = scenes; audScriptScene * prevSS = NULL; while(sScene) { if(!sScene->m_scene) { //Found a slot that used to contain a scene but is vacant so use it break; } prevSS = sScene; sScene = sScene->m_next; } if(!sScene) //We've reached the end of the list so add a new slot on { sScene = rage_new audScriptScene(); } if(prevSS) { prevSS->m_next = sScene; } else { //Must be at the head scenes = sScene; } //Tell the dynamic mixer to kick off this scene and pass through m_scene //to pick up the reference bool ready = true; if(sceneSettings) { DYNAMICMIXER.StartScene(sceneSettings, &sScene->m_scene, this); #if GTA_REPLAY if(sScene && sScene->m_scene) { sScene->m_scene->SetHash(sceneHash); if(CReplayMgr::ShouldRecord()) { if(sceneHash == ATSTRINGHASH("DEATH_SCENE", 0x5A583A13) || sceneHash == ATSTRINGHASH("QUIET_SCENE", 0x85BA09E8)) { CPed* pPlayerPed = FindPlayerPed(); bool isPlayerDead = pPlayerPed && pPlayerPed->ShouldBeDead(); bool isPlayerArrested = pPlayerPed && pPlayerPed->GetIsArrested(); if(isPlayerDead || isPlayerArrested) { return ready; } } CReplayMgr::RecordPersistantFx( CPacketDynamicMixerScene(sceneHash, false, 0), CTrackedEventInfo(sScene->m_scene), NULL, true); } } #endif //GTA_REPLAY } return ready; } void audScript::StopScene(u32 sceneHash) { audScene* scene = GetScene(sceneHash); if(scene) { scene->Stop(); } } void audScript::StopScenes() { while(scenes) { if(scenes->m_scene) { if(scenes->m_scene->IsInstanceOf(g_ScriptAudioEntity.GetPoliceScannerSceneHash())) { g_ScriptAudioEntity.ResetPoliceScannerVariables(); } scenes->m_scene->Stop(); } audScriptScene * next = scenes->m_next; delete scenes; scenes = next; } } void audScript::SetSceneVariable(u32 sceneHash, u32 variableHash, f32 value) { audScene* scene = GetScene(sceneHash); if(scene) { scene->SetVariableValue(variableHash, value); } } bool audScript::SceneHasFinishedFade(u32 sceneHash) { audScene * scene = GetScene(sceneHash); if(scene) { return scene->HasFinishedFade(); } return true; } //Functions for manipulating script-scoped variables const variableNameValuePair * audScript::GetVariableAddress(u32 nameHash) const { //Loop through our own variable block to see if it's one of ours for(s32 i=0; i < g_MaxAudScriptVariables; i++) { if(m_Variables[i].hash == nameHash) { //We own the variable it's asking about, so return its address return &(m_Variables[i]); } else if(m_Variables[i].hash == 0) { return NULL; } } return NULL; } //const variableNameValuePair * audScript::GetVariableAddress(const char * name) //{ // return GetVariableAddress(atStringHash(name)); //} // void audScript::SetVariableValue(u32 nameHash, f32 value) { //Look through our variable block to see if it exists already for(s32 i=0; i < g_MaxAudScriptVariables; i++) { if(m_Variables[i].hash == nameHash) { //the variable exists, so set it m_Variables[i].value = value; return; } else if (m_Variables[i].hash == 0) { //we've got the first empty one, so set this one to be the new variable //and bail out m_Variables[i].hash = nameHash; m_Variables[i]. value = value; return; } } naAssertf(0, "An audScript has run out of variables; consider using less variables or upping the max number of variables"); } void audScript::SetVariableValue(const char * name, float value) { SetVariableValue(atStringHash(name), value); } void audScript::Shutdown() { ReleaseScriptWeaponAudio(); StopScenes(); g_AmbientAudioEntity.CancelScriptPedDensityChanges(ScriptThreadId); g_AmbientAudioEntity.CancelScriptWallaSoundSet(ScriptThreadId); for(s32 ambientZoneLoop = 0; ambientZoneLoop < m_ScriptAmbientZoneChanges.GetCount(); ambientZoneLoop++) { g_AmbientAudioEntity.ClearZoneNonPersistentStatus(m_ScriptAmbientZoneChanges[ambientZoneLoop], true); } m_ScriptAmbientZoneChanges.clear(); for(int i=0; iSetPedIsAngry(false); } m_PedsSetToAngry.clear(); for(int i=0; iSetScriptPriority(AUD_VEHICLE_SCRIPT_PRIORITY_NONE, ScriptThreadId); } } m_PriorityVehicles.clear(); audSpeechAudioEntity::RemoveContextBlocks(ScriptThreadId); s32 scriptIndex = g_ScriptAudioEntity.GetIndexFromScriptThreadId(ScriptThreadId); scenes = NULL; if(scriptIndex == audScriptAudioEntity::sm_ScriptInChargeOfTheStream) { g_ScriptAudioEntity.StopStream(); } if(scriptIndex == g_SpeechManager.GetTennisScriptId()) { g_SpeechManager.UnrequestTennisBanks(); } if(m_FlagResetState.AreAnySet()) { for(s32 i = 0; i < audScriptAudioFlags::kNumScriptAudioFlags; i++) { if(m_FlagResetState.IsSet(i)) { g_ScriptAudioEntity.ResetFlagState((audScriptAudioFlags::FlagId)i); } } } if(m_SlowMoMode) { audWarningf("Automatically deactivating slow-mo mode %u for script", m_SlowMoMode); audNorthAudioEngine::DeactivateSlowMoMode(m_SlowMoMode); m_SlowMoMode = 0; } if(m_ScriptVehicleHash != 0u) { audVehicleAudioEntity::PreloadScriptVehicleAudio(0u, scriptIndex); m_ScriptVehicleHash = 0u; } // Ditch any pending music events owned by this script. If this shows up in a profile then we'll need // to flag internally whether this is necessary for each script instance; there should only ever be a few // active events, so it will hopefully be cheap enough to ignore. g_InteractiveMusicManager.GetEventManager().CancelAllEvents(ScriptThreadId); // We're done - clear out the slot ScriptThreadId = THREAD_INVALID; BankSlotIndex = 0; BankOverNetwork = 0; PlayerBits = AUD_NET_ALL_PLAYERS; m_ScriptisShuttingDown = false; } extern audScannerManager g_AudioScannerManager; void audScriptAudioEntity::OnFlagStateChange(audScriptAudioFlags::FlagId flagId, const bool state) { #if __BANK audDisplayf("Script audio flag %s now %s (from %s)", audScriptAudioFlags::GetName(flagId), state ? "enabled" : "disabled", CTheScripts::GetCurrentScriptNameAndProgramCounter()); #endif // If you want to kick off some custom logic when a script audio flag changes state, add a handler here. switch(flagId) { case audScriptAudioFlags::RadioOverlapDisabled: audRadioStation::DisableOverlappedTracks(state); break; case audScriptAudioFlags::PoliceScannerDisabled: g_AudioScannerManager.DisableScannerFlagChanged(state); default: // Not all flags will need to state changes - ie where code is happy to poll IsFlagSet() break; } } void audScriptAudioEntity::SetFlagState(const audScriptAudioFlags::FlagId flagId, const bool state) { const bool prevState = IsFlagSet(flagId); if(state) { m_FlagState[flagId]++; } else { if(audVerifyf(prevState, "script trying to disable an audio flag (%d / %s) that is not currently set (from %s)", flagId, audScriptAudioFlags::GetName(flagId), CTheScripts::GetCurrentScriptNameAndProgramCounter())) { m_FlagState[flagId]--; } } const bool newState = IsFlagSet(flagId); if(newState != prevState) { Assert(state == newState); OnFlagStateChange(flagId, state); } } void audScript::SetAudioFlag(const char *flagName, const bool state) { const audScriptAudioFlags::FlagId flagId = audScriptAudioFlags::FindFlagId(flagName); if(audScriptAudioFlags::IsValid(flagId)) { return SetAudioFlag(flagId, state); } } void audScript::SetAudioFlag(const audScriptAudioFlags::FlagId flagId, const bool state) { const bool prevState = m_FlagResetState.IsSet(flagId); // Silently ignore multiple requests for the same state if(state != prevState) { g_ScriptAudioEntity.SetFlagState(flagId, state); m_FlagResetState.Set(flagId, state); } } void audScript::ActivateSlowMoMode(const char *mode) { const u32 newMode = atStringHash(mode); if(newMode == m_SlowMoMode) { return; } else if(!audVerifyf(m_SlowMoMode == 0, "Script activating a slow-mo mode %s without first deactivating %u", mode, m_SlowMoMode)) { audNorthAudioEngine::DeactivateSlowMoMode(m_SlowMoMode); } m_SlowMoMode = newMode; audNorthAudioEngine::ActivateSlowMoMode(newMode); } void audScript::DeactivateSlowMoMode(const char *mode) { const u32 newMode = atStringHash(mode); if(newMode == m_SlowMoMode) { audNorthAudioEngine::DeactivateSlowMoMode(newMode); m_SlowMoMode = 0; } else { audWarningf("Script deactivating slow mo mode %s, but have set %u", mode, m_SlowMoMode); audNorthAudioEngine::DeactivateSlowMoMode(newMode); } } bool audScript::RequestScriptWeaponAudio(const char * itemName) { const CItemInfo * info = CWeaponInfoManager::GetInfo(atStringHash(itemName)); if(info) { m_ScriptWeaponAudioHash = info->GetAudioHash(); return g_WeaponAudioEntity.AddScriptWeapon(info->GetAudioHash()); } naAssertf(info, "SCRIPT: Couldn't find weapon %s in REQUEST_WEAPON_AUDIO", itemName); return true; //Can't find the weapon but return true to no block the script } void audScript::ReleaseScriptWeaponAudio() { if(m_ScriptWeaponAudioHash) { g_WeaponAudioEntity.ReleaseScriptWeapon(); m_ScriptWeaponAudioHash = 0; } } bool audScript::RequestScriptVehicleAudio(const u32 vehicleModelName) { if(audVehicleAudioEntity::PreloadScriptVehicleAudio(vehicleModelName, g_ScriptAudioEntity.GetIndexFromScriptThreadId(ScriptThreadId))) { m_ScriptVehicleHash = vehicleModelName; return true; } else { return false; } } audScript::~audScript() { Shutdown(); }