diff --git a/Code/client/Events/ActorSpokeEvent.h b/Code/client/Events/DialogueEvent.h similarity index 68% rename from Code/client/Events/ActorSpokeEvent.h rename to Code/client/Events/DialogueEvent.h index 86f11f8aa..3047fd032 100644 --- a/Code/client/Events/ActorSpokeEvent.h +++ b/Code/client/Events/DialogueEvent.h @@ -3,9 +3,9 @@ /** * @brief Dispatched whenever an actor talks */ -struct ActorSpokeEvent +struct DialogueEvent { - ActorSpokeEvent(uint32_t aActorID, String aVoiceFile) + DialogueEvent(uint32_t aActorID, String aVoiceFile) : ActorID(aActorID), VoiceFile(aVoiceFile) {} diff --git a/Code/client/Games/References.cpp b/Code/client/Games/References.cpp index 76f6d6940..e828797f1 100644 --- a/Code/client/Games/References.cpp +++ b/Code/client/Games/References.cpp @@ -29,7 +29,7 @@ #include #include -#include +#include #include @@ -706,16 +706,21 @@ void TP_MAKE_THISCALL(HookInitFromPackage, void, TESPackage* apPackage, TESObjec return ThisCall(RealInitFromPackage, apThis, apPackage, apTarget, arActor); } -TP_THIS_FUNCTION(TSpeakSoundFunction, bool, Actor, char* apName, uint32_t* a3, uint32_t a4, uint32_t a5, uint32_t a6, uint64_t a7, uint64_t a8, uint64_t a9, bool a10, uint64_t a11, bool a12, bool a13, bool a14); +TP_THIS_FUNCTION(TSpeakSoundFunction, bool, Actor, const char* apName, uint32_t* a3, uint32_t a4, uint32_t a5, uint32_t a6, uint64_t a7, uint64_t a8, uint64_t a9, bool a10, uint64_t a11, bool a12, bool a13, bool a14); static TSpeakSoundFunction* RealSpeakSoundFunction = nullptr; -bool TP_MAKE_THISCALL(HookSpeakSoundFunction, Actor, char* apName, uint32_t* a3, uint32_t a4, uint32_t a5, uint32_t a6, uint64_t a7, uint64_t a8, uint64_t a9, bool a10, uint64_t a11, bool a12, bool a13, bool a14) +bool TP_MAKE_THISCALL(HookSpeakSoundFunction, Actor, const char* apName, uint32_t* a3, uint32_t a4, uint32_t a5, uint32_t a6, uint64_t a7, uint64_t a8, uint64_t a9, bool a10, uint64_t a11, bool a12, bool a13, bool a14) { - World::Get().GetRunner().Trigger(ActorSpokeEvent(apThis->formID, apName)); + spdlog::debug("a3: {:X}, a4: {}, a5: {}, a6: {}, a7: {}, a8: {:X}, a9: {:X}, a10: {}, a11: {:X}, a12: {}, a13: {}, a14: {}", + (uint64_t)a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); + + if (apThis->GetExtension()->IsLocal()) + World::Get().GetRunner().Trigger(DialogueEvent(apThis->formID, apName)); + return ThisCall(RealSpeakSoundFunction, apThis, apName, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); } -void Actor::SpeakSound(char* pFile) +void Actor::SpeakSound(const char* pFile) { uint32_t handle[3]{}; handle[0] = -1; diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 1472c2793..cef2195a4 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -228,7 +228,7 @@ struct Actor : TESObjectREFR void Respawn() noexcept; void PickUpObject(TESObjectREFR* apObject, int32_t aCount, bool aUnk1, float aUnk2) noexcept; void DropObject(TESBoundObject* apObject, ExtraDataList* apExtraData, int32_t aCount, NiPoint3* apLocation, NiPoint3* apRotation) noexcept; - void SpeakSound(char* pFile); + void SpeakSound(const char* pFile); enum ActorFlags { diff --git a/Code/client/Services/CharacterService.h b/Code/client/Services/CharacterService.h index 393483930..eb16590ab 100644 --- a/Code/client/Services/CharacterService.h +++ b/Code/client/Services/CharacterService.h @@ -33,6 +33,8 @@ struct NotifyRespawn; struct LeaveBeastFormEvent; struct AddExperienceEvent; struct NotifySyncExperience; +struct DialogueEvent; +struct NotifyDialogue; struct Actor; struct World; @@ -71,6 +73,8 @@ struct CharacterService void OnLeaveBeastForm(const LeaveBeastFormEvent& acEvent) const noexcept; void OnAddExperienceEvent(const AddExperienceEvent& acEvent) noexcept; void OnNotifySyncExperience(const NotifySyncExperience& acMessage) noexcept; + void OnDialogueEvent(const DialogueEvent& acEvent) noexcept; + void OnNotifyDialogue(const NotifyDialogue& acMessage) noexcept; private: @@ -118,4 +122,6 @@ struct CharacterService entt::scoped_connection m_leaveBeastFormConnection; entt::scoped_connection m_addExperienceEventConnection; entt::scoped_connection m_syncExperienceConnection; + entt::scoped_connection m_dialogueEventConnection; + entt::scoped_connection m_dialogueSyncConnection; }; diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index affb04e4a..51f75fc42 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include @@ -87,7 +87,7 @@ DebugService::DebugService(entt::dispatcher& aDispatcher, World& aWorld, Transpo { m_updateConnection = m_dispatcher.sink().connect<&DebugService::OnUpdate>(this); m_drawImGuiConnection = aImguiService.OnDraw.connect<&DebugService::OnDraw>(this); - m_actorSpokeConnection = m_dispatcher.sink().connect<&DebugService::OnActorSpokeEvent>(this); + m_actorSpokeConnection = m_dispatcher.sink().connect<&DebugService::OnActorSpokeEvent>(this); } void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept @@ -147,12 +147,12 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept s_f8Pressed = false; } -void DebugService::OnActorSpokeEvent(const ActorSpokeEvent& acEvent) noexcept +void DebugService::OnActorSpokeEvent(const DialogueEvent& acEvent) noexcept { m_spokenActorId = acEvent.ActorID; m_voiceFileName = acEvent.VoiceFile; - spdlog::warn("Actor spoke, id: {:X}, file: {}", acEvent.ActorID, acEvent.VoiceFile.c_str()); + spdlog::debug("Actor spoke, id: {:X}, file: {}", acEvent.ActorID, acEvent.VoiceFile.c_str()); } uint64_t DebugService::DisplayGraphDescriptorKey(BSAnimationGraphManager* pManager) noexcept diff --git a/Code/client/Services/DebugService.h b/Code/client/Services/DebugService.h index a33b4c05a..fdbf97997 100644 --- a/Code/client/Services/DebugService.h +++ b/Code/client/Services/DebugService.h @@ -6,7 +6,7 @@ struct World; struct ImguiService; struct UpdateEvent; -struct ActorSpokeEvent; +struct DialogueEvent; struct TransportService; struct BSAnimationGraphManager; @@ -22,7 +22,7 @@ struct DebugService TP_NOCOPYMOVE(DebugService); void OnUpdate(const UpdateEvent&) noexcept; - void OnActorSpokeEvent(const ActorSpokeEvent&) noexcept; + void OnActorSpokeEvent(const DialogueEvent&) noexcept; protected: diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index a00f689f0..aa94d50bd 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,8 @@ #include #include #include +#include +#include #include #include @@ -101,6 +104,9 @@ CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher, m_addExperienceEventConnection = m_dispatcher.sink().connect<&CharacterService::OnAddExperienceEvent>(this); m_syncExperienceConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifySyncExperience>(this); + + m_dialogueEventConnection = m_dispatcher.sink().connect<&CharacterService::OnDialogueEvent>(this); + m_dialogueSyncConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifyDialogue>(this); } void CharacterService::OnActorAdded(const ActorAddedEvent& acEvent) noexcept @@ -984,6 +990,58 @@ void CharacterService::OnNotifySyncExperience(const NotifySyncExperience& acMess pPlayer->AddSkillExperience(pPlayerEx->LastUsedCombatSkill, acMessage.Experience); } +void CharacterService::OnDialogueEvent(const DialogueEvent& acEvent) noexcept +{ + if (!m_transport.IsConnected()) + return; + + auto view = m_world.view(entt::exclude); + auto entityIt = std::find_if(view.begin(), view.end(), [view, formId = acEvent.ActorID](auto entity) { + return view.get(entity).Id == formId; + }); + + if (entityIt == view.end()) + return; + + auto serverIdRes = Utils::GetServerId(*entityIt); + if (!serverIdRes) + { + spdlog::error("{}: server id not found for form id {:X}", __FUNCTION__, acEvent.ActorID); + return; + } + + DialogueRequest request{}; + request.ServerId = serverIdRes.value(); + request.SoundFilename = acEvent.VoiceFile; + + m_transport.Send(request); +} + +void CharacterService::OnNotifyDialogue(const NotifyDialogue& acMessage) noexcept +{ + auto remoteView = m_world.view(); + const auto remoteIt = std::find_if(std::begin(remoteView), std::end(remoteView), [remoteView, Id = acMessage.ServerId](auto entity) + { + return remoteView.get(entity).Id == Id; + }); + + if (remoteIt == std::end(remoteView)) + { + spdlog::warn("Actor for dialogue with remote id {:X} not found.", acMessage.ServerId); + return; + } + + auto formIdComponent = remoteView.get(*remoteIt); + const TESForm* pForm = TESForm::GetById(formIdComponent.Id); + Actor* pActor = Cast(pForm); + + if (!pActor) + return; + + // TODO(cosideci): pActor->StopCurrentDialogue() or something + pActor->SpeakSound(acMessage.SoundFilename.c_str()); +} + void CharacterService::ProcessNewEntity(entt::entity aEntity) const noexcept { if (!m_transport.IsOnline()) diff --git a/Code/encoding/Messages/ClientMessageFactory.h b/Code/encoding/Messages/ClientMessageFactory.h index b3c6213ec..05f23652f 100644 --- a/Code/encoding/Messages/ClientMessageFactory.h +++ b/Code/encoding/Messages/ClientMessageFactory.h @@ -45,6 +45,7 @@ #include #include #include +#include using TiltedPhoques::UniquePtr; @@ -65,7 +66,7 @@ struct ClientMessageFactory RequestOwnershipClaim, RequestObjectInventoryChanges, SpellCastRequest, ProjectileLaunchRequest, InterruptCastRequest, AddTargetRequest, ScriptAnimationRequest, DrawWeaponRequest, MountRequest, NewPackageRequest, RequestRespawn, SyncExperienceRequest, RequestEquipmentChanges, SendChatMessageRequest, - TeleportCommandRequest, PlayerRespawnRequest>; + TeleportCommandRequest, PlayerRespawnRequest, DialogueRequest>; return s_visitor(std::forward(func)); } diff --git a/Code/encoding/Messages/DialogueRequest.cpp b/Code/encoding/Messages/DialogueRequest.cpp new file mode 100644 index 000000000..630ff5e40 --- /dev/null +++ b/Code/encoding/Messages/DialogueRequest.cpp @@ -0,0 +1,15 @@ +#include + +void DialogueRequest::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +{ + Serialization::WriteVarInt(aWriter, ServerId); + Serialization::WriteString(aWriter, SoundFilename); +} + +void DialogueRequest::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept +{ + ClientMessage::DeserializeRaw(aReader); + + ServerId = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + SoundFilename = Serialization::ReadString(aReader); +} diff --git a/Code/encoding/Messages/DialogueRequest.h b/Code/encoding/Messages/DialogueRequest.h new file mode 100644 index 000000000..99df1c8b6 --- /dev/null +++ b/Code/encoding/Messages/DialogueRequest.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Message.h" + +using TiltedPhoques::String; + +struct DialogueRequest final : ClientMessage +{ + static constexpr ClientOpcode Opcode = kDialogueRequest; + + DialogueRequest() : ClientMessage(Opcode) + { + } + + void SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept override; + void DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept override; + + bool operator==(const DialogueRequest& acRhs) const noexcept + { + return GetOpcode() == acRhs.GetOpcode() && + ServerId == acRhs.ServerId && + SoundFilename == acRhs.SoundFilename; + } + + uint32_t ServerId{}; + TiltedPhoques::String SoundFilename{}; +}; + diff --git a/Code/encoding/Messages/NotifyDialogue.cpp b/Code/encoding/Messages/NotifyDialogue.cpp new file mode 100644 index 000000000..4dda9aa45 --- /dev/null +++ b/Code/encoding/Messages/NotifyDialogue.cpp @@ -0,0 +1,15 @@ +#include + +void NotifyDialogue::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +{ + Serialization::WriteVarInt(aWriter, ServerId); + Serialization::WriteString(aWriter, SoundFilename); +} + +void NotifyDialogue::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept +{ + ServerMessage::DeserializeRaw(aReader); + + ServerId = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + SoundFilename = Serialization::ReadString(aReader); +} diff --git a/Code/encoding/Messages/NotifyDialogue.h b/Code/encoding/Messages/NotifyDialogue.h new file mode 100644 index 000000000..275cd22ad --- /dev/null +++ b/Code/encoding/Messages/NotifyDialogue.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Message.h" + +struct NotifyDialogue final : ServerMessage +{ + static constexpr ServerOpcode Opcode = kNotifyDialogue; + + NotifyDialogue() : ServerMessage(Opcode) + { + } + + void SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept override; + void DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept override; + + bool operator==(const NotifyDialogue& achRhs) const noexcept + { + return GetOpcode() == achRhs.GetOpcode() && + ServerId == achRhs.ServerId && + SoundFilename == achRhs.SoundFilename; + } + + uint32_t ServerId{}; + TiltedPhoques::String SoundFilename{}; +}; diff --git a/Code/encoding/Messages/ServerMessageFactory.h b/Code/encoding/Messages/ServerMessageFactory.h index 57f7368d3..426ad7576 100644 --- a/Code/encoding/Messages/ServerMessageFactory.h +++ b/Code/encoding/Messages/ServerMessageFactory.h @@ -43,6 +43,7 @@ #include #include #include +#include using TiltedPhoques::UniquePtr; @@ -62,7 +63,7 @@ struct ServerMessageFactory NotifyObjectInventoryChanges, NotifySpellCast, NotifyProjectileLaunch, NotifyInterruptCast, NotifyAddTarget, NotifyScriptAnimation, NotifyDrawWeapon, NotifyMount, NotifyNewPackage, NotifyRespawn, NotifySyncExperience, NotifyEquipmentChanges, NotifyChatMessageBroadcast, - TeleportCommandResponse, NotifyPlayerRespawn>; + TeleportCommandResponse, NotifyPlayerRespawn, NotifyDialogue>; return s_visitor(std::forward(func)); } diff --git a/Code/encoding/Opcodes.h b/Code/encoding/Opcodes.h index 4b0dc3bae..302cbeb07 100644 --- a/Code/encoding/Opcodes.h +++ b/Code/encoding/Opcodes.h @@ -43,6 +43,7 @@ enum ClientOpcode : unsigned char kSendChatMessageRequest, kTeleportCommandRequest, kPlayerRespawnRequest, + kDialogueRequest, kClientOpcodeMax }; @@ -87,5 +88,6 @@ enum ServerOpcode : unsigned char kTeleportCommandResponse, kStringCacheUpdate, kNotifyPlayerRespawn, + kNotifyDialogue, kServerOpcodeMax }; diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index 51ea94861..082c03275 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -36,6 +36,8 @@ #include #include #include +#include +#include CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher) noexcept : m_world(aWorld) @@ -56,6 +58,7 @@ CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher) , m_newPackageConnection(aDispatcher.sink>().connect<&CharacterService::OnNewPackageRequest>(this)) , m_requestRespawnConnection(aDispatcher.sink>().connect<&CharacterService::OnRequestRespawn>(this)) , m_syncExperienceConnection(aDispatcher.sink>().connect<&CharacterService::OnSyncExperienceRequest>(this)) + , m_dialogueConnection(aDispatcher.sink>().connect<&CharacterService::OnDialogueRequest>(this)) { } @@ -549,6 +552,18 @@ void CharacterService::OnSyncExperienceRequest(const PacketEventSendToParty(notify, partyComponent, acMessage.GetSender()); } +void CharacterService::OnDialogueRequest(const PacketEvent& acMessage) const noexcept +{ + auto& message = acMessage.Packet; + + NotifyDialogue notify{}; + notify.ServerId = message.ServerId; + notify.SoundFilename = message.SoundFilename; + + const entt::entity cEntity = static_cast(message.ServerId); + GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.pPlayer); +} + void CharacterService::CreateCharacter(const PacketEvent& acMessage) const noexcept { auto& message = acMessage.Packet; diff --git a/Code/server/Services/CharacterService.h b/Code/server/Services/CharacterService.h index 6dd1f11ba..50d53212f 100644 --- a/Code/server/Services/CharacterService.h +++ b/Code/server/Services/CharacterService.h @@ -22,6 +22,7 @@ struct MountRequest; struct NewPackageRequest; struct RequestRespawn; struct SyncExperienceRequest; +struct DialogueRequest; /** * @brief Manages player and actor state. @@ -54,6 +55,7 @@ struct CharacterService void OnNewPackageRequest(const PacketEvent& acMessage) const noexcept; void OnRequestRespawn(const PacketEvent& acMessage) const noexcept; void OnSyncExperienceRequest(const PacketEvent& acMessage) const noexcept; + void OnDialogueRequest(const PacketEvent& acMessage) const noexcept; void CreateCharacter(const PacketEvent& acMessage) const noexcept; @@ -81,4 +83,5 @@ struct CharacterService entt::scoped_connection m_newPackageConnection; entt::scoped_connection m_requestRespawnConnection; entt::scoped_connection m_syncExperienceConnection; + entt::scoped_connection m_dialogueConnection; };