@@ -920,39 +920,76 @@ void CharacterService::OnDialogueEvent(const DialogueEvent& acEvent) noexcept
920920 if (!m_transport.IsConnected ())
921921 return ;
922922
923+ const bool isLeader = World::Get ().GetPartyService ().IsLeader (); // Helps distinguish in 2-party logs
923924 auto view = m_world.view <FormIdComponent>(entt::exclude<ObjectComponent>);
924- auto entityIt = std::find_if (view.begin (), view.end (), [view, formId = acEvent.ActorID ](auto entity) { return view.get <FormIdComponent>(entity).Id == formId; });
925+ auto entityIt = std::find_if (view.begin (), view.end (), [view, formId = acEvent.ActorID ](auto entity) {
926+ return view.get <FormIdComponent>(entity).Id == formId;
927+ });
925928
926929 if (entityIt == view.end ())
927930 {
928- spdlog::error ( __FUNCTION__ " : failed to find speaking Actor's FormIdComponent, formId {:X}" , acEvent.ActorID );
931+ spdlog::error (__FUNCTION__ " : failed to find speaking Actor's FormIdComponent, formId {:X}, isLeader {} " , acEvent.ActorID , isLeader );
929932 return ;
930933 }
931934
932935 auto serverIdRes = Utils::GetServerId (*entityIt);
933936 if (!serverIdRes)
934937 {
935- spdlog::warn (__FUNCTION__ " : server id not found for formId {:X}" , acEvent.ActorID );
938+ spdlog::warn (__FUNCTION__ " : server id not found for formId {:X}, isLeader {} " , acEvent.ActorID , isLeader );
936939 return ;
937940 }
938-
941+
939942 Actor* pActor = Cast<Actor>(TESForm::GetById (acEvent.ActorID ));
940- auto isRemote = pActor->GetExtension ()->IsRemote ();
941- auto isRemoteInScene = isRemote && pActor->GetCurrentScene () && pActor->GetCurrentScene ()->isPlaying ;
942- const bool isLeader = m_world.Get ().GetPartyService ().IsLeader (); // Helps distinguish in 2-party
943- if (isRemoteInScene) // If we're forwarding Remote dialog, it should be in-Scene Dialogue Actions, or Task Dialogue.
944- spdlog::warn (__FUNCTION__ " : remote actor is speaking in-scene formId {:X} serverId {:X} isRemote {} isLeader {}, name {}" , acEvent.ActorID , serverIdRes.value (), isRemote, isLeader, pActor->baseForm ->GetName ());
943+ if (!pActor)
944+ return ;
945945
946- DialogueRequest request{};
947- request.ServerId = serverIdRes.value ();
948- request.SoundFilename = acEvent.VoiceFile ;
946+ bool isLocal = pActor->GetExtension ()->IsLocal ();
947+ bool isInScene = pActor->IsInScene ();
948+ auto sceneId = isInScene ? pActor->GetCurrentScene ()->formID : 0 ;
949+ bool isTaskDialogue = pActor->IsTalking () && pActor->IsInDialogueWithPlayer ();
949950
950- m_transport.Send (request);
951+ // There's no good way to distinguish the goodbye line
952+ // from the first line of the scene, so disable this.
953+ bool repairEdgeCase = false ;
954+ #if 0
955+ // Fix the edge case, "goodbye line" of TaskDialogue
956+ static Actor* plastDialogueActor = nullptr;
957+ static bool lastIsInDialogueWithPlayer = false;
958+ bool repairEdgeCase = (pActor == plastDialogueActor) && lastIsInDialogueWithPlayer;
959+
960+ if (repairEdgeCase)
961+ {
962+ plastDialogueActor = nullptr;
963+ lastIsInDialogueWithPlayer = false;
964+ }
965+
966+ else if (isTaskDialogue)
967+ {
968+ plastDialogueActor = pActor;
969+ lastIsInDialogueWithPlayer = true;
970+ }
971+ #endif
972+
973+ const bool willSync = isTaskDialogue || repairEdgeCase || isLocal && !isInScene;
974+
975+ spdlog::warn (__FUNCTION__ " : isLocal {}, isInScene {}, isTaskDialogue {} repaireEdgeCase {}, willSync {}, scene {:X}, Actor "
976+ " {:X}, serverId {:X}, isLeader {}, name {}, soundFile {}" ,
977+ isLocal, isInScene, isTaskDialogue, repairEdgeCase, willSync, sceneId, pActor->formID , serverIdRes.value (),
978+ isLeader, pActor->baseForm ->GetName (), acEvent.VoiceFile );
979+
980+ if (willSync)
981+ {
982+ DialogueRequest request{};
983+ request.ServerId = serverIdRes.value ();
984+ request.SoundFilename = acEvent.VoiceFile ;
985+
986+ m_transport.Send (request);
987+ }
951988}
952989
953990void CharacterService::OnNotifyDialogue (const NotifyDialogue& acMessage) noexcept
954991{
955- const bool isLeader = m_world. Get ().GetPartyService ().IsLeader ();
992+ const bool isLeader = World:: Get ().GetPartyService ().IsLeader (); // Helps distinguish in 2-party logs
956993 auto view = m_world.view <FormIdComponent>(entt::exclude<ObjectComponent>);
957994 auto viewIt = std::find_if (
958995 view.begin (), view.end (),
@@ -972,60 +1009,22 @@ void CharacterService::OnNotifyDialogue(const NotifyDialogue& acMessage) noexcep
9721009 if (!pActor)
9731010 return ;
9741011
975- // If we receive remote dialog for a Local actor, it was sent because
976- // the remote version is in a cutscene and we should play it. Similarly if
977- // receive dialog for a Remote actor, it's most often from the Leader and we
978- // also should play it.
979- //
980- // Unless the actor is speaking! If actively talking, the remote dialog interrupts
981- // our lines. That's a problem for Scene Dialog Actions, they often control Scene
982- // timing, aborting them breaks the scene.
983- //
984- // FIXME: there is no known perfect test for "is talking," Papyrus IsTalking() only works
985- // on TaskDialog() (Dialog speak / response trees). It doesn't work on Scene dialog (Dialog
986- // Actions). The closest test is if a Scene is playing.
987- //
988- // The compromise is: dialog is rejected if the NPC is playing a scene.
989- //
990- // Most quests trigger scenes with Quest Stage changes. That means everyone is playing
991- // the scene, everyone gets their own dialog, and the scene timing stays correct. TaskDialog
992- // dialog won't play to others that don't participate in the dialog say&respond. Can't fix it.
993- //
994- // There are evil scenes that report "IsPlaying" when they are waiting to be triggered, and don't
995- // fire quest stages to make everyone play the scene. Companions C02 (e.g., Brotherhood /
996- // Induction scene). Whoever triggers gets dialog. Others would also have to trigger to hear
997- // the dialog. It's the best we can do.
998- //
999- const auto pScene = pActor->GetCurrentScene ();
1000- // const bool isPlaying = pScene && pScene->isPlaying;
1001- const bool isTalking = pActor->IsTalking (); // TaskDialogue?
1002- const bool isSpeakingInScene = pActor->IsSpeakingInScene ();
1003- const auto pName = (pActor->baseForm && pActor->baseForm ->GetName ()) ? pActor->baseForm ->GetName () : " " ;
1004-
1005- if (!isTalking && isSpeakingInScene)
1006- spdlog::warn (__FUNCTION__ " : aborting dialog sync while speaking in scene {:X}, Actor {:X}, serverId {:X}, "
1007- " isLeader {}, isTalking {}, isSpeakingInScene {}, name {}" ,
1008- pScene->formID , pActor->formID , acMessage.ServerId , isLeader, isTalking, isSpeakingInScene, pName);
1009- else
1010- {
1011- spdlog::warn (__FUNCTION__ " : syncing dialog should NOT be speaking in scene Actor {:X}, serverId {:X}, "
1012- " isLeader {}, isTalking {}, isSpeakingInScene {}, name {}" ,
1013- pActor->formID , acMessage.ServerId , isLeader, isTalking, isSpeakingInScene, pName);
10141012
1015- // if (pActor->IsTalking()) // To break out of dialogues.
1016- pActor->StopCurrentDialogue (true );
1017- pActor->SpeakSound (acMessage.SoundFilename .c_str ());
1018- }
1013+ spdlog::warn (__FUNCTION__ " : playing dialogue Actor {:X}, serverId {:X}, isLeader {}, name {}, soundFile {}" ,
1014+ pActor->formID , acMessage.ServerId , isLeader, pActor->baseForm ->GetName (), acMessage.SoundFilename );
1015+
1016+ pActor->StopCurrentDialogue (true );
1017+ pActor->SpeakSound (acMessage.SoundFilename .c_str ());
10191018}
10201019
10211020void CharacterService::OnSubtitleEvent (const SubtitleEvent& acEvent) noexcept
10221021{
10231022 if (!m_transport.IsConnected ())
10241023 return ;
10251024
1025+ const bool isLeader = World::Get ().GetPartyService ().IsLeader (); // Helps distinguish in 2-party logs
10261026 auto view = m_world.view <FormIdComponent>(entt::exclude<ObjectComponent>);
10271027 auto entityIt = std::find_if (view.begin (), view.end (), [view, formId = acEvent.SpeakerID ](auto entity) { return view.get <FormIdComponent>(entity).Id == formId; });
1028- const bool isLeader = m_world.Get ().GetPartyService ().IsLeader ();
10291028
10301029 if (entityIt == view.end ())
10311030 {
@@ -1036,16 +1035,52 @@ void CharacterService::OnSubtitleEvent(const SubtitleEvent& acEvent) noexcept
10361035 auto serverIdRes = Utils::GetServerId (*entityIt);
10371036 if (!serverIdRes)
10381037 {
1039- spdlog::warn (__FUNCTION__ " : server id not found for formId {:X}" , acEvent.SpeakerID );
1038+ spdlog::warn (__FUNCTION__ " : server id not found for formId {:X}, isLeader {} " , acEvent.SpeakerID , isLeader );
10401039 return ;
10411040 }
10421041
1043- SubtitleRequest request{};
1044- request.ServerId = serverIdRes.value ();
1045- request.Text = acEvent.Text ;
1046- request.TopicFormId = acEvent.TopicFormID ;
1042+ Actor* pActor = Cast<Actor>(TESForm::GetById (acEvent.SpeakerID ));
1043+ if (!pActor)
1044+ return ;
1045+
1046+ bool isLocal = pActor->GetExtension ()->IsLocal ();
1047+ bool isInScene = pActor->IsInScene ();
1048+ auto sceneId = isInScene ? pActor->GetCurrentScene ()->formID : 0 ;
1049+ bool isTaskDialogue = pActor->IsTalking () && pActor->IsInDialogueWithPlayer ();
10471050
1048- m_transport.Send (request);
1051+ // Fix the edge case, "goodbye line" of TaskDialogue
1052+ static Actor* plastDialogueActor = nullptr ;
1053+ static bool lastIsInDialogueWithPlayer = false ;
1054+ bool repairEdgeCase = (pActor == plastDialogueActor) && lastIsInDialogueWithPlayer;
1055+
1056+ if (repairEdgeCase)
1057+ {
1058+ plastDialogueActor = nullptr ;
1059+ lastIsInDialogueWithPlayer = false ;
1060+ }
1061+
1062+ else if (isTaskDialogue)
1063+ {
1064+ plastDialogueActor = pActor;
1065+ lastIsInDialogueWithPlayer = true ;
1066+ }
1067+
1068+ const bool willSync = isTaskDialogue || repairEdgeCase || isLocal && !isInScene;
1069+
1070+ spdlog::warn (
1071+ __FUNCTION__ " : isLocal {}, isInScene {}, isTaskDialogue {} repaireEdgeCase {}, willSync {}, scene {:X}, Actor "
1072+ " {:X}, serverId {:X}, isLeader {}, name {}, subtitle {}" ,
1073+ isLocal, isInScene, isTaskDialogue, repairEdgeCase, willSync, sceneId, pActor->formID , serverIdRes.value (),
1074+ isLeader, pActor->baseForm ->GetName (), acEvent.Text );
1075+
1076+ if (willSync)
1077+ {
1078+ SubtitleRequest request{};
1079+ request.ServerId = serverIdRes.value ();
1080+ request.Text = acEvent.Text ;
1081+ request.TopicFormId = acEvent.TopicFormID ;
1082+ m_transport.Send (request);
1083+ }
10491084}
10501085
10511086void CharacterService::OnNotifySubtitle (const NotifySubtitle& acMessage) noexcept
@@ -1074,19 +1109,8 @@ void CharacterService::OnNotifySubtitle(const NotifySubtitle& acMessage) noexcep
10741109 TESTopicInfo* pInfo = nullptr ;
10751110 pInfo = Cast<TESTopicInfo>(TESForm::GetById (acMessage.TopicFormId ));
10761111
1077- // If we receive remote subtitles for a Local actor, it was sent because
1078- // the remote version is in a cutscene and we should play it.
1079- const auto pScene = pActor->GetCurrentScene ();
1080- const bool isSpeakingInScene = pActor->IsSpeakingInScene ();
1081- // const bool isPlaying = pScene && pScene->isPlaying;
1082- const auto pName = (pActor->baseForm && pActor->baseForm ->GetName ()) ? pActor->baseForm ->GetName () : " " ;
1083- if (isSpeakingInScene)
1084- spdlog::warn (__FUNCTION__ " : subtitle sync while speaking in (likely same) scene {:X}, Actor {:X}, serverId {:X}, "
1085- " isLeader {}, name {}, message: {}" ,
1086- pScene->formID , pActor->formID , acMessage.ServerId , isLeader, pName, acMessage.Text .c_str ());
1087- else
1088- spdlog::warn (__FUNCTION__ " : showing subtitle Actor {:X}, serverId {:X}, isLeader {}, name {}, message: {}" ,
1089- pActor->formID , acMessage.ServerId , isLeader, pName, acMessage.Text .c_str ());
1112+ spdlog::warn (__FUNCTION__ " : showing subtitle Actor {:X}, serverId {:X}, isLeader {}, name {}, message: {}" ,
1113+ pActor->formID , acMessage.ServerId , isLeader, pActor->baseForm ->GetName (), acMessage.Text );
10901114
10911115 SubtitleManager::Get ()->ShowSubtitle (pActor, acMessage.Text .c_str (), pInfo);
10921116}
0 commit comments