void Screen::DoContext(ContextMenu *pNewCtx, Element *pAtElement, int32_t iX, int32_t iY) { assert(pNewCtx); assert(pNewCtx != pContext); // close previous context menu AbortContext(false); // element offset if (pAtElement) pAtElement->ClientPos2ScreenPos(iX, iY); // usually open bottom right // check bottom bounds if (iY + pNewCtx->GetBounds().Hgt >= GetBounds().Hgt) { // bottom too narrow: open to top, if height is sufficient // otherwise, open to top from bottom screen pos if (iY < pNewCtx->GetBounds().Hgt) iY = GetBounds().Hgt; iY -= pNewCtx->GetBounds().Hgt; } // check right bounds likewise if (iX + pNewCtx->GetBounds().Wdt >= GetBounds().Wdt) { // bottom too narrow: open to top, if height is sufficient // otherwise, open to top from bottom screen pos if (iX < pNewCtx->GetBounds().Wdt) iX = GetBounds().Wdt; iX -= pNewCtx->GetBounds().Wdt; } // open new (pContext = pNewCtx)->Open(pAtElement, iX, iY); }
Screen::~Screen() { // dtor: Close context menu AbortContext(false); // clear singleton if (this == pScreen) pScreen = NULL; // GamePad if (pGamePadOpener) delete pGamePadOpener; }
// IEntityEventListener void CDialogActorContext::OnEntityEvent( IEntity *pEntity,SEntityEvent &event ) { switch (event.event) { case ENTITY_EVENT_RESET: case ENTITY_EVENT_DONE: AbortContext(true, CDialogSession::eAR_EntityDestroyed); break; default: break; } }
void Screen::ActivateDialog(Dialog *pDlg) { // no change? if (pActiveDlg == pDlg) return; // in single-mode: release any MouseOver/Drag of previous dlg if (IsExclusive()) Mouse.ReleaseElements(); // close any context menu AbortContext(false); // set as active dlg pActiveDlg = pDlg; // ensure it's last in the list, if it's not a specially ordered dlg if (!pDlg->GetZOrdering() && pDlg->GetNext()) MakeLastElement(pDlg); }
void Screen::CloseDialog(Dialog *pDlg, bool fFade) { // hide dlg if (!fFade) pDlg->fShow = false; // kill from active if (pActiveDlg == pDlg) { // release any MouseOver/Drag of previous dlg Mouse.ReleaseElements(); // close context menu: probably belonging to closed dlg anyway AbortContext(false); // set new active dlg pActiveDlg = GetTopDialog(); // do not set yet if it's fading if (pActiveDlg && pActiveDlg->IsFading()) pActiveDlg = NULL; } // redraw background; clip update Game.GraphicsSystem.InvalidateBg(); UpdateMouseFocus(); }
bool CDialogActorContext::Update(float dt) { if (IsAborted()) return true; // FIXME: this should never happen, as we should get a notification before. // temp leave in for tracking down a bug [AlexL: 23/11/2006] IEntity* pEntity = gEnv->pEntitySystem->GetEntity(m_entityID); if (pEntity == 0) { m_pIActor = 0; GameWarning("[DIALOG] CDialogActorContext::Update: %s Actor=%d (EntityId=%d) no longer existent.", m_pSession->GetDebugName(), m_actorID, m_entityID); AbortContext(true, CDialogSession::eAR_EntityDestroyed); return true; } CDialogSession::AlertnessInterruptMode alertnessInterruptMode = m_pSession->GetAlertnessInterruptMode(); if(alertnessInterruptMode != CDialogSession::None) { if(IAIObject* aiObject = pEntity->GetAI()) { if(IAIActorProxy* aiActorProxy = aiObject->GetProxy()) { if(aiActorProxy->GetAlertnessState() >= alertnessInterruptMode) { AbortContext(true, CDialogSession::eAR_AIAborted); return true; } } } } float now = m_pSession->GetCurTime(); if (!CheckActorFlags(CDialogSession::eDACF_NoActorDeadAbort) && m_pIActor && m_pIActor->IsDead()) { if (!IsAborted()) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Actor=%d (EntityId=%d) has died. Aborting.", m_pSession->GetDebugName(), m_actorID, m_entityID); AbortContext(true, CDialogSession::eAR_ActorDead); return true; } } if (m_bIsLocalPlayer) { // when the local player is involved in the conversation do some special checks if (DoLocalPlayerChecks(dt) == false) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Abort from LocalPlayer.", m_pSession->GetDebugName()); AbortContext(true, m_bIsAwareInRange == false ? CDialogSession::eAR_PlayerOutOfRange : CDialogSession::eAR_PlayerOutOfView); return true; } } if (m_bAbortFromAI) { m_bAbortFromAI = false; if (!IsAborted()) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Abort from AI.", m_pSession->GetDebugName()); AbortContext(true, CDialogSession::eAR_AIAborted); return true; } } if (SessionAllowsLookAt()) { DoStickyLookAt(); } int loop = 0; do { // DiaLOG::Log("[DIALOG] DiaLOG::eAlways"CDialogActorContext::Update: %s now=%f actorId=%d loop=%d", m_pSession->GetDebugName(), now, m_actorID, loop); bool bAdvance = false; switch (m_phase) { case eDAC_Idle: break; case eDAC_NewLine: { m_bHasScheduled = false; m_lookAtTimeOut = LOOKAT_TIMEOUT; m_animTimeOut = ANIM_TIMEOUT; m_soundTimeOut = SOUND_TIMEOUT; // wait one second until sound is timed out m_bAnimScheduled = false; m_bAnimStarted = false; m_bSoundScheduled = false; m_bSoundStarted = false; m_soundLength = 0.0f; if (m_pCurLine->m_flagResetLookAt) { m_stickyLookAtActorID = CDialogScript::NO_ACTOR_ID; m_lookAtActorID = CDialogScript::NO_ACTOR_ID; } // check if look-at sticky is set else if (m_pCurLine->m_lookatActor != CDialogScript::NO_ACTOR_ID) { if (m_pCurLine->m_flagLookAtSticky == false) { m_lookAtActorID = m_pCurLine->m_lookatActor; m_stickyLookAtActorID = CDialogScript::NO_ACTOR_ID; } else { m_stickyLookAtActorID = m_pCurLine->m_lookatActor; m_lookAtActorID = CDialogScript::NO_ACTOR_ID; } } bAdvance = true; // handle the first sticky look-at here if (SessionAllowsLookAt()) { DoStickyLookAt(); } } break; case eDAC_LookAt: { bool bWaitForLookAtFinish = true; bAdvance = true; if (SessionAllowsLookAt() == false) { break; } // Question: maybe do this although EP is requested if (bWaitForLookAtFinish && m_lookAtActorID != CDialogScript::NO_ACTOR_ID && m_pCurLine->m_flagAGEP == false) { IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); IEntity* pLookAtEntity = m_pSession->GetActorEntity(m_lookAtActorID); if (pActorEntity != 0) { m_lookAtTimeOut -= dt; bool bTargetReached; //bool bSuccess = DoLookAt(pActorEntity, pLookAtEntity, bTargetReached); DoLookAt(pActorEntity, pLookAtEntity, bTargetReached); //DiaLOG::Log(DiaLOG::eDebugA, "[DIALOG] CDialogActorContext::Update: %s now=%f actorID=%d phase=eDAC_LookAt %d", // m_pSession->GetDebugName(), now, m_actorID, bTargetReached); if (/* bSuccess == false || */ bTargetReached || m_lookAtTimeOut<=0.0f) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s now=%f actorID=%d phase=eDAC_LookAt %s", m_pSession->GetDebugName(), now, m_actorID, bTargetReached ? "Target Reached" : "Timed Out"); m_lookAtActorID = CDialogScript::NO_ACTOR_ID; } else bAdvance = false; } } } break; case eDAC_Anim: { bAdvance = true; if (SessionAllowsLookAt() && m_lookAtActorID != CDialogScript::NO_ACTOR_ID) // maybe: don't do this when EP is requested [&& m_pCurLine->m_flagAGEP == false] { IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); IEntity* pLookAtEntity = m_pSession->GetActorEntity(m_lookAtActorID); if (pActorEntity != 0) { m_lookAtTimeOut -= dt; bool bTargetReached; //bool bSuccess = DoLookAt(pActorEntity, pLookAtEntity, bTargetReached); DoLookAt(pActorEntity, pLookAtEntity, bTargetReached); if (/* bSuccess == false || */ bTargetReached || m_lookAtTimeOut<=0.0f) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s now=%f actorID=%d phase=eDAC_Anim %s", m_pSession->GetDebugName(), now, m_actorID, bTargetReached ? "Target Reached" : "Timed Out"); m_lookAtActorID = CDialogScript::NO_ACTOR_ID; } else bAdvance = false; } } const bool bHasAnim = !m_pCurLine->m_anim.empty(); if (SessionAllowsAnim() && bHasAnim) { bAdvance = false; if (!m_bAnimScheduled) { m_bSoundStopsAnim = false; // whenever there is a new animation, no need for the still playing sound // to stop the animation m_bAnimStarted = false; m_bAnimScheduled = true; // schedule animation IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); if (pActorEntity) DoAnimAction(pActorEntity, m_pCurLine->m_anim, m_pCurLine->m_flagAGSignal, m_pCurLine->m_flagAGEP); else bAdvance = true; } else { // we scheduled it already // wait until it starts or timeout m_animTimeOut-=dt; bAdvance = m_animTimeOut <= 0.0f || m_bAnimStarted; if (bAdvance) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_Anim %s", m_pSession->GetDebugName(), now, m_actorID, m_bAnimStarted ? "Anim Started" : "Anim Timed Out"); } } } } break; case eDAC_ScheduleSoundPlay: { bAdvance = true; const bool bHasSound = !m_pCurLine->m_sound.empty(); if (bHasSound) { if (m_bSoundScheduled == false) { IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); if (pActorEntity == 0) break; IEntityAudioProxy* pActorAudioProxy = m_pSession->GetEntityAudioProxy(pActorEntity); if (pActorAudioProxy == 0) break; REINST("play voice line"); // start a new sound //int sFlags = CDialogActorContext::SOUND_FLAGS; //if (CDialogSystem::sLoadSoundSynchronously != 0) // sFlags |= FLAG_SOUND_LOAD_SYNCHRONOUSLY; // //// if it's the local player make the sound relative to the entity //if (m_bIsLocalPlayer) // sFlags |= FLAG_SOUND_RELATIVE; //const char* soundKey = GetSoundKey(m_pCurLine->m_sound); //ISound* pSound = 0; //pSound = gEnv->pAudioSystem->CreateSound(soundKey, sFlags); //DiaLOG::Log(DiaLOG::eDebugA, "[DIALOG] CDialogActorContex::Update: %s Now=%f actorID=%d phase=eDAC_ScheduleSoundPlay: Starting '%s' [key=%s]", // m_pSession->GetDebugName(), now, m_actorID, m_pCurLine->m_sound.c_str(), soundKey); //if (!pSound) //{ // GameWarning("[DIALOG] CDialogActorContext::Update: %s ActorID=%d: Cannot play sound '%s'", // m_pSession->GetDebugName(), m_actorID, m_pCurLine->m_sound.c_str()); //} //if (pSound) //{ // if (m_bSoundStopsAnim) // { // m_bSoundStopsAnim = false; // if (m_pAGState != 0) // { // ResetAGState(); // DiaLOG::Log(DiaLOG::eDebugA, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_ScheduleSoundPlay: Stopping old animation", // m_pSession->GetDebugName(), now, m_actorID); // } // } // StopSound(); // apparently, if we dont explicitely stop the sound, in rare cases it never stops. Im pretty sure that is an engine/fmod bug. // // stopping it this way may lead to some subtitles staying for an extra couple seconds on screen (this comes from another engine bug/problem related to an extra fade out time added to cover the dummy NAX soudns) // m_soundID = pSound->GetId(); // m_bSoundStarted = false; // m_bSoundScheduled = true; // // tell the animation whether to stop on sound stop // m_bSoundStopsAnim = m_pCurLine->m_flagSoundStopsAnim; // DiaLOG::Log(DiaLOG::eDebugA, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_ScheduleSoundPlay: Reg as listener on 0x%p '%s'", // m_pSession->GetDebugName(), now, m_actorID, pSound, pSound->GetName()); // pSound->SetSemantic(eSoundSemantic_Dialog); // pSound->AddEventListener(this, "DialogActor"); // event listener will set m_soundLength // // sound proxy uses head pos on dialog sounds // const bool bResult = pActorAudioProxy->PlaySound(pSound, Vec3(ZERO), FORWARD_DIRECTION, 1.0f); // if (bResult) // { // // Fetch some localization info for the sound // ILocalizationManager* pLocMgr = gEnv->pSystem->GetLocalizationManager(); // if (pLocMgr && CDialogSystem::sWarnOnMissingLoc != 0) // { // SLocalizedInfoGame GameInfo; // const bool bFound = pLocMgr->GetLocalizedInfoByKey(soundKey, GameInfo); // if (!bFound) // { // GameWarning("[DIALOG] CDialogActorContext::Update: '%s' DialogScript '%s': Localized info for '%s' not found!", // m_pSession->GetDebugName(), m_pSession->GetScript()->GetID().c_str(), m_pCurLine->m_sound.c_str()); // } // } // bAdvance = false; // don't advance // } // else // { // GameWarning("[DIALOG] CDialogActorContext::Update: %s ActorID=%d: Cannot play sound '%s' [AudioProxy ignored playback]", // m_pSession->GetDebugName(), m_actorID, m_pCurLine->m_sound.c_str()); // pSound->Stop(ESoundStopMode_AtOnce); // m_soundID = INVALID_SOUNDID; // pSound->RemoveEventListener(this); // pSound = NULL; // } //} //else //{ // GameWarning("[DIALOG] CDialogActorContext::Update: %s ActorID=%d: Cannot play sound '%s' [AudioProxy returns invalid sound]", // m_pSession->GetDebugName(), m_actorID, m_pCurLine->m_sound.c_str()); //} } else { // sound has been scheduled // wait for sound start or timeout m_soundTimeOut-=dt; const bool bTimedOut = m_soundTimeOut <= 0.0f; bAdvance = bTimedOut || m_bSoundStarted; if (bAdvance) { if (bTimedOut) StopSound(true); // unregister from sound as listener and free resource DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_ScheduleSoundPlay %s", m_pSession->GetDebugName(), now, m_actorID, m_bSoundStarted ? "Sound Started" : "Sound Timed Out"); } } } } break; case eDAC_SoundFacial: { bAdvance = true; const bool bHasSound = !m_pCurLine->m_sound.empty(); const bool bHasFacial = !m_pCurLine->m_facial.empty() || m_pCurLine->m_flagResetFacial; if (bHasFacial) { IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); if (pActorEntity) DoFacialExpression(pActorEntity, m_pCurLine->m_facial, m_pCurLine->m_facialWeight, m_pCurLine->m_facialFadeTime); } float delay = m_pCurLine->m_delay; if (bHasSound && m_bSoundStarted) { if (m_soundLength < 0.0f) { m_soundLength = Random(2.0f,6.0f); DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_SoundFacial Faking SoundTime to %f", m_pSession->GetDebugName(), now, m_actorID, m_soundLength); } DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_SoundFacial Delay=%f SoundTime=%f", m_pSession->GetDebugName(), now , m_actorID, delay, m_soundLength); delay += m_soundLength; } // schedule END if (delay < 0.0f) delay = 0.0f; m_pSession->ScheduleNextLine(delay+0.05f); // ~1 frame at 20 fps. we now stop the current line sound before going for next line. Sometimes, this stop call is right before the // sound has actually stopped in engine side. When that happens, the engine is adding ~2 seconds extra to the sound duration (because fade problems related to NAX header) // and that looks bad if subtitles are enabled. So we add this small delay here to try to minimize that situation. // this is part of the workaround for some dialogs apparently never finishing in the engine side (rare). m_bHasScheduled = true; } break; case eDAC_EndLine: bAdvance = true; if (m_bHasScheduled == false) { m_bHasScheduled = true; m_pSession->ScheduleNextLine(m_pCurLine->m_delay); } break; } if (bAdvance) { AdvancePhase(); dt = 0.0f; ++loop; assert (loop <= eDAC_EndLine+1); if (loop > eDAC_EndLine+1) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Actor=%d InternalLoopCount=%d. Final Exit!", m_pSession->GetDebugName(), m_actorID, loop); } } else break; } while (true); return true; }
bool CDialogActorContext::Update(float dt) { if (IsAborted()) return true; // FIXME: this should never happen, as we should get a notification before. // temp leave in for tracking down a bug [AlexL: 23/11/2006] IEntity* pEntity = gEnv->pEntitySystem->GetEntity(m_entityID); if (pEntity == 0) { m_pIActor = 0; GameWarning("[DIALOG] CDialogActorContext::Update: %s Actor=%d (EntityId=%d) no longer existent.", m_pSession->GetDebugName(), m_actorID, m_entityID); AbortContext(true, CDialogSession::eAR_EntityDestroyed); return true; } CDialogSession::AlertnessInterruptMode alertnessInterruptMode = m_pSession->GetAlertnessInterruptMode(); if(alertnessInterruptMode != CDialogSession::None) { if(IAIObject* aiObject = pEntity->GetAI()) { if(IAIActorProxy* aiActorProxy = aiObject->GetProxy()) { if(aiActorProxy->GetAlertnessState() >= alertnessInterruptMode) { AbortContext(true, CDialogSession::eAR_AIAborted); return true; } } } } float now = m_pSession->GetCurTime(); if (!CheckActorFlags(CDialogSession::eDACF_NoActorDeadAbort) && m_pIActor && m_pIActor->IsDead()) { if (!IsAborted()) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Actor=%d (EntityId=%d) has died. Aborting.", m_pSession->GetDebugName(), m_actorID, m_entityID); AbortContext(true, CDialogSession::eAR_ActorDead); return true; } } if (m_bIsLocalPlayer) { // when the local player is involved in the conversation do some special checks if (DoLocalPlayerChecks(dt) == false) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Abort from LocalPlayer.", m_pSession->GetDebugName()); AbortContext(true, m_bIsAwareInRange == false ? CDialogSession::eAR_PlayerOutOfRange : CDialogSession::eAR_PlayerOutOfView); return true; } } if (m_bAbortFromAI) { m_bAbortFromAI = false; if (!IsAborted()) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Abort from AI.", m_pSession->GetDebugName()); AbortContext(true, CDialogSession::eAR_AIAborted); return true; } } if (SessionAllowsLookAt()) { DoStickyLookAt(); } int loop = 0; do { // DiaLOG::Log("[DIALOG] DiaLOG::eAlways"CDialogActorContext::Update: %s now=%f actorId=%d loop=%d", m_pSession->GetDebugName(), now, m_actorID, loop); bool bAdvance = false; switch (m_phase) { case eDAC_Idle: break; case eDAC_NewLine: { m_bHasScheduled = false; m_lookAtTimeOut = LOOKAT_TIMEOUT; m_animTimeOut = ANIM_TIMEOUT; m_soundTimeOut = SOUND_TIMEOUT; // wait one second until sound is timed out m_bAnimScheduled = false; m_bAnimStarted = false; m_bSoundScheduled = false; m_bSoundStarted = false; m_soundLength = 0.0f; if (m_pCurLine->m_flagResetLookAt) { m_stickyLookAtActorID = CDialogScript::NO_ACTOR_ID; m_lookAtActorID = CDialogScript::NO_ACTOR_ID; } // check if look-at sticky is set else if (m_pCurLine->m_lookatActor != CDialogScript::NO_ACTOR_ID) { if (m_pCurLine->m_flagLookAtSticky == false) { m_lookAtActorID = m_pCurLine->m_lookatActor; m_stickyLookAtActorID = CDialogScript::NO_ACTOR_ID; } else { m_stickyLookAtActorID = m_pCurLine->m_lookatActor; m_lookAtActorID = CDialogScript::NO_ACTOR_ID; } } bAdvance = true; // handle the first sticky look-at here if (SessionAllowsLookAt()) { DoStickyLookAt(); } #if 0 // test for immediate scheduling of next line while the current line is still playing // maybe we're going to use some special delay TAGS like FORCE_NEXT, WAIT if (m_pCurLine->m_delay <= -999.0f) { m_bHasScheduled = true; m_pSession->ScheduleNextLine(0.0f); } #endif } break; case eDAC_LookAt: { bool bWaitForLookAtFinish = true; bAdvance = true; if (SessionAllowsLookAt() == false) { break; } // Question: maybe do this although EP is requested if (bWaitForLookAtFinish && m_lookAtActorID != CDialogScript::NO_ACTOR_ID && m_pCurLine->m_flagAGEP == false) { IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); IEntity* pLookAtEntity = m_pSession->GetActorEntity(m_lookAtActorID); if (pActorEntity != 0) { m_lookAtTimeOut -= dt; bool bTargetReached; //bool bSuccess = DoLookAt(pActorEntity, pLookAtEntity, bTargetReached); DoLookAt(pActorEntity, pLookAtEntity, bTargetReached); //DiaLOG::Log(DiaLOG::eDebugA, "[DIALOG] CDialogActorContext::Update: %s now=%f actorID=%d phase=eDAC_LookAt %d", // m_pSession->GetDebugName(), now, m_actorID, bTargetReached); if (/* bSuccess == false || */ bTargetReached || m_lookAtTimeOut<=0.0f) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s now=%f actorID=%d phase=eDAC_LookAt %s", m_pSession->GetDebugName(), now, m_actorID, bTargetReached ? "Target Reached" : "Timed Out"); m_lookAtActorID = CDialogScript::NO_ACTOR_ID; } else bAdvance = false; } } } break; case eDAC_Anim: { bAdvance = true; if (SessionAllowsLookAt() && m_lookAtActorID != CDialogScript::NO_ACTOR_ID) // maybe: don't do this when EP is requested [&& m_pCurLine->m_flagAGEP == false] { IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); IEntity* pLookAtEntity = m_pSession->GetActorEntity(m_lookAtActorID); if (pActorEntity != 0) { m_lookAtTimeOut -= dt; bool bTargetReached; //bool bSuccess = DoLookAt(pActorEntity, pLookAtEntity, bTargetReached); DoLookAt(pActorEntity, pLookAtEntity, bTargetReached); if (/* bSuccess == false || */ bTargetReached || m_lookAtTimeOut<=0.0f) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s now=%f actorID=%d phase=eDAC_Anim %s", m_pSession->GetDebugName(), now, m_actorID, bTargetReached ? "Target Reached" : "Timed Out"); m_lookAtActorID = CDialogScript::NO_ACTOR_ID; } else bAdvance = false; } } const bool bHasAnim = !m_pCurLine->m_anim.empty(); if (SessionAllowsAnim() && bHasAnim) { bAdvance = false; if (!m_bAnimScheduled) { m_bSoundStopsAnim = false; // whenever there is a new animation, no need for the still playing sound // to stop the animation m_bAnimStarted = false; m_bAnimScheduled = true; // schedule animation IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); if (pActorEntity) DoAnimAction(pActorEntity, m_pCurLine->m_anim, m_pCurLine->m_flagAGSignal, m_pCurLine->m_flagAGEP); else bAdvance = true; } else { // we scheduled it already // wait until it starts or timeout m_animTimeOut-=dt; bAdvance = m_animTimeOut <= 0.0f || m_bAnimStarted; if (bAdvance) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_Anim %s", m_pSession->GetDebugName(), now, m_actorID, m_bAnimStarted ? "Anim Started" : "Anim Timed Out"); } } } } break; case eDAC_ScheduleSoundPlay: { bAdvance = true; const bool bHasSound = m_pCurLine->m_audioID != INVALID_AUDIO_CONTROL_ID; if (bHasSound) { if (m_bSoundScheduled == false) { IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); if (pActorEntity == 0) break; IEntityAudioProxy* pActorAudioProxy = m_pSession->GetEntityAudioProxy(pActorEntity); if (pActorAudioProxy == 0) break; if(m_SpeechAuxProxy == INVALID_AUDIO_PROXY_ID) { m_SpeechAuxProxy = pActorAudioProxy->CreateAuxAudioProxy(); pActorAudioProxy->AddAsListenerToAuxAudioProxy(m_SpeechAuxProxy, &CDialogActorContext::OnAudioTriggerFinished, eART_AUDIO_CALLBACK_MANAGER_REQUEST, eACMRT_REPORT_FINISHED_TRIGGER_INSTANCE); } UpdateAuxProxyPosition(); SAudioCallBackInfos callbackInfos(0, (void*)CDialogActorContext::GetClassIdentifier(), reinterpret_cast<void*>(static_cast<intptr_t>(m_ContextID)), eARF_PRIORITY_NORMAL | eARF_SYNC_FINISHED_CALLBACK); if(!pActorAudioProxy->ExecuteTrigger(m_pCurLine->m_audioID, eLSM_None, m_SpeechAuxProxy, callbackInfos)) { m_bSoundStarted = false; m_bHasScheduled = false; m_pSession->ScheduleNextLine(2.0f); } else { m_bSoundStarted = true; m_bHasScheduled = true; //todo: get the length of the sound to schedule the next line (set m_soundLength). Currently we schedule the next line as soon as the current one is finished, ignoring the specified "delay" parameter. its done in: DialogTriggerFinishedCallback const char* triggerName = gEnv->pAudioSystem->GetAudioControlName(eACT_TRIGGER, m_pCurLine->m_audioID); if (triggerName == NULL) triggerName = "dialog trigger"; DiaLOG::Log(DiaLOG::eDebugA, "[DIALOG] CDialogActorContex::Update: %s Now=%f actorID=%d phase=eDAC_ScheduleSoundPlay: Starting '%s'", m_pSession->GetDebugName(), now, m_actorID, triggerName); } } else { // sound has been scheduled // wait for sound start or timeout m_soundTimeOut-=dt; const bool bTimedOut = m_soundTimeOut <= 0.0f; bAdvance = bTimedOut || m_bSoundStarted; if (bAdvance) { if (bTimedOut) StopSound(true); // unregister from sound as listener and free resource DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_ScheduleSoundPlay %s", m_pSession->GetDebugName(), now, m_actorID, m_bSoundStarted ? "Sound Started" : "Sound Timed Out"); } } } } break; case eDAC_SoundFacial: { bAdvance = true; const bool bHasSound = m_pCurLine->m_audioID != INVALID_AUDIO_CONTROL_ID; const bool bHasFacial = !m_pCurLine->m_facial.empty() || m_pCurLine->m_flagResetFacial; if (bHasFacial) { IEntity* pActorEntity = m_pSession->GetActorEntity(m_actorID); if (pActorEntity) DoFacialExpression(pActorEntity, m_pCurLine->m_facial, m_pCurLine->m_facialWeight, m_pCurLine->m_facialFadeTime); } //hd-todo: re-add me, when we have a way to query the length of the audio-lines //float delay = m_pCurLine->m_delay; //if (bHasSound && m_bSoundStarted) //{ // if (m_soundLength <= 0.0f) // { // m_soundLength = cry_random(2.0f, 6.0f); // DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_SoundFacial Faking SoundTime to %f", // m_pSession->GetDebugName(), now, m_actorID, m_soundLength); // } // DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Now=%f actorID=%d phase=eDAC_SoundFacial Delay=%f SoundTime=%f", // m_pSession->GetDebugName(), now , m_actorID, delay, m_soundLength); // delay += m_soundLength; //} //// schedule END //if (delay < 0.0f) // delay = 0.0f; //m_pSession->ScheduleNextLine(delay+0.05f); // ~1 frame at 20 fps. we now stop the current line sound before going for next line. Sometimes, this stop call is right before the // // sound has actually stopped in engine side. When that happens, the engine is adding ~2 seconds extra to the sound duration (because fade problems related to NAX header) // // and that looks bad if subtitles are enabled. So we add this small delay here to try to minimize that situation. // // this is part of the workaround for some dialogs apparently never finishing in the engine side (rare). //m_bHasScheduled = true; } break; case eDAC_EndLine: bAdvance = true; if (m_bHasScheduled == false) { m_bHasScheduled = true; m_pSession->ScheduleNextLine(m_pCurLine->m_delay); } break; } if (bAdvance) { AdvancePhase(); dt = 0.0f; ++loop; assert (loop <= eDAC_EndLine+1); if (loop > eDAC_EndLine+1) { DiaLOG::Log(DiaLOG::eAlways, "[DIALOG] CDialogActorContext::Update: %s Actor=%d InternalLoopCount=%d. Final Exit!", m_pSession->GetDebugName(), m_actorID, loop); } } else { if (IsStillPlaying()) { UpdateAuxProxyPosition(); } break; } } while (true); return true; }
bool Screen::MouseInput(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam, Dialog *pForDlg, class C4Viewport *pForVP) { // help mode and button pressed: Abort help and discard button if (Game.MouseControl.IsHelp()) { switch (iButton) { case C4MC_Button_None: // just movement break; case C4MC_Button_LeftDown: case C4MC_Button_RightDown: // special for left/right down: Just ignore them, but don't stop help yet // help should be stopped on button-up, so these won't be processed iButton = C4MC_Button_None; break; default: // buttons stop help Game.MouseControl.AbortHelp(); iButton = C4MC_Button_None; break; } } // forward to mouse Mouse.Input(iButton, iX, iY, dwKeyParam); // dragging if (Mouse.pDragElement) { int32_t iX2=iX, iY2=iY; Mouse.pDragElement->ScreenPos2ClientPos(iX2, iY2); if (!Mouse.IsLDown()) { // stop dragging Mouse.pDragElement->StopDragging(Mouse, iX2, iY2, dwKeyParam); Mouse.pDragElement = NULL; } else { // continue dragging Mouse.pDragElement->DoDragging(Mouse, iX2, iY2, dwKeyParam); } } // backup previous MouseOver-element Mouse.pPrevMouseOverElement = Mouse.pMouseOverElement; Mouse.pMouseOverElement = NULL; bool fProcessed = false; // active context menu? if (!pForVP && pContext && pContext->CtxMouseInput(Mouse, iButton, iX, iY, dwKeyParam)) { // processed by context menu: OK! } // otherwise: active dlg and inside screen? (or direct forward to specific dlg/viewport dlg) else if (rcBounds.Contains(iX, iY) || pForDlg || pForVP) { // context menu open but mouse down command issued? close context then if (pContext && (iButton == C4MC_Button_LeftDown || iButton == C4MC_Button_RightDown)) AbortContext(true); // get client pos if (!pForDlg && !pForVP) { C4Rect &rcClientArea = GetClientRect(); iX -= rcClientArea.x; iY -= rcClientArea.y; } // exclusive mode: process active dialog only if (IsExclusive() && !pForDlg && !pForVP) { if (pActiveDlg && pActiveDlg->IsVisible() && !pActiveDlg->IsFading()) { // bounds check to dlg: only if not dragging C4Rect &rcDlgBounds = pActiveDlg->GetBounds(); if (Mouse.IsLDown() || rcDlgBounds.Contains(iX, iY)) // forward to active dialog pActiveDlg->MouseInput(Mouse, iButton, iX - rcDlgBounds.x, iY - rcDlgBounds.y, dwKeyParam); else Mouse.pMouseOverElement = NULL; } else // outside dialog: own handling (for screen context menu) Window::MouseInput(Mouse, iButton, iX, iY, dwKeyParam); } else { // non-exclusive mode: process all dialogs; make them active on left-click Dialog *pDlg; for (Element *pEl = pLast; pEl; pEl = pEl->GetPrev()) if (pDlg = pEl->GetDlg()) if (pDlg->IsShown()) { // if specified: process specified dlg only if (pForDlg && pDlg != pForDlg) continue; // if specified: process specified viewport only bool fIsExternalDrawDialog = pDlg->IsExternalDrawDialog(); C4Viewport *pVP = fIsExternalDrawDialog ? pDlg->GetViewport() : NULL; if (pForVP && pForVP != pVP) continue; // calc offset C4Rect &rcDlgBounds = pDlg->GetBounds(); int32_t iOffX=0, iOffY=0; // special handling for viewport dialogs if (fIsExternalDrawDialog) { // ignore external drawing dialogs without a viepwort assigned if (!pVP) continue; // always clip to viewport bounds C4Rect rcOut(pVP->GetOutputRect()); if (!rcOut.Contains(iX + rcBounds.x, iY + rcBounds.y)) continue; // viewport dialogs: Offset determined by viewport position iOffX = rcOut.x; iOffY = rcOut.y; } // hit test; or special: dragging possible outside active dialog if (rcDlgBounds.Contains(iX-iOffX, iY-iOffY) || (pDlg == pActiveDlg && Mouse.pDragElement)) { // Okay; do input pDlg->MouseInput(Mouse, iButton, iX - rcDlgBounds.x - iOffX, iY - rcDlgBounds.y - iOffY, dwKeyParam); // dlgs may destroy GUI if (!IsGUIValid()) return false; // CAUTION: pDlg may be invalid now! // set processed-flag manually fProcessed = true; // inactive dialogs get activated by clicks if (Mouse.IsLDown() && pDlg != pActiveDlg) // but not viewport dialogs! if (!pDlg->IsExternalDrawDialog()) ActivateDialog(pDlg); // one dlg only; break loop here break; } } } // check valid GUI; might be destroyed by mouse input if (!IsGUIValid()) return false; } // check if MouseOver has changed if (Mouse.pPrevMouseOverElement != Mouse.pMouseOverElement) { // send events if (Mouse.pPrevMouseOverElement) Mouse.pPrevMouseOverElement->MouseLeave(Mouse); if (Mouse.pMouseOverElement) Mouse.pMouseOverElement->MouseEnter(Mouse); } // return whether anything processed it return fProcessed || Mouse.pDragElement || (Mouse.pMouseOverElement && Mouse.pMouseOverElement!=this) || pContext; }