refactor: effects, audio, animation and UI text game messages as structs

Converts PlayAnimation, PlayNDAudioEmitter, PlayEmbeddedEffectOnAllClientsNearObject,
PlayFXEffect, StopFXEffect, BroadcastTextToChatbox, Play2DAmbientSound,
Stop2DAmbientSound, UIMessageServerToSingleClient, UIMessageServerToAllClients,
StartCelebrationEffect, DisplayMessageBox, DisplayChatBubble, ChangeIdleFlags,
SetNameBillboardState, ShowBillboardInteractIcon, PlayCinematic, EndCinematic,
SlashCommandTextFeedback, PlayEmote and SetEmoteLockState (21 old Send
functions, 23 with overloads) and the received MessageBoxRespond,
ChoiceBoxRespond, CinematicUpdate and PlayEmote to NetGameMsgs in
EffectsMessages.{h,cpp}. The high traffic messages get a constructor for their
required fields. UI messages own their AMF arguments. Every caller is switched,
the received messages are registered in GameMessageHandler's map, and the old
functions and switch cases are deleted. Handlers are copied verbatim.

No wire change and no change in recipients: functions that always broadcast
whatever address they were given are sent with Send(UNASSIGNED_SYSTEM_ADDRESS),
functions that only did SEND_PACKET use SendToClient. Quirks are kept and
documented on the structs (PlayAnimation's UTF-8 sized name, the always written
null terminator in BroadcastTextToChatbox, StartCelebrationEffect always
writing celebrationID, SetNameBillboardState having no payload).

Verified byte for byte against a frozen verbatim copy of the old functions over
an input grid, to one client and broadcast; received messages compared with the
old handlers' read sequences and every truncated payload rejected; hand
computed golden bytes; round trips; a deliberate width mutation made the tests
fail. The byte-equality helper gains a Broadcast mode for functions that
ignored their address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-26 19:06:00 -05:00
parent 5a9a3e380b
commit dae1925d1a
117 changed files with 2832 additions and 1058 deletions

View File

@@ -3,6 +3,7 @@ set(DGAME_DGAMEMESSAGES_SOURCES
"BuildingMessages.cpp"
"GameMessageDecoder.cpp"
"GameMessageHandler.cpp"
"EffectsMessages.cpp"
"GameMessages.cpp"
"MissionMessages.cpp"
"PetMessages.cpp"

View File

@@ -0,0 +1,489 @@
#include "EffectsMessages.h"
#include "AMFDeserialize.h"
#include "AmfSerialize.h"
#include "BitStreamUtils.h"
#include "CDClientManager.h"
#include "CDEmoteTable.h"
#include "Character.h"
#include "eMissionTaskType.h"
#include "eReplicaComponentType.h"
#include "Entity.h"
#include "EntityManager.h"
#include "Game.h"
#include "GeneralUtils.h"
#include "Logger.h"
#include "MissionComponent.h"
#include "NiPoint3.h"
#include "RacingControlComponent.h"
#include "ScriptedActivityComponent.h"
#include "User.h"
#include "UserManager.h"
#include <stdexcept>
namespace {
// Reads an AMF3 array written with Write<AMFBaseValue&>. Fails on anything that is not an array.
bool ReadAmfArray(RakNet::BitStream& bitStream, AMFArrayValue& args) {
try {
AMFDeserialize reader;
auto value = reader.Read(bitStream);
if (!value || value->GetValueType() != eAmf::Array) return false;
args = std::move(*static_cast<AMFArrayValue*>(value.get()));
return true;
} catch (const std::exception&) {
return false;
}
}
}
namespace GameMessages {
void PlayAnimation::Serialize(RakNet::BitStream& bitStream) const {
const auto animationIDLength = static_cast<uint32_t>(GeneralUtils::UTF16ToWTF8(animationID).size());
bitStream.Write(animationIDLength);
bitStream.Write(LUWString(animationID, animationIDLength));
bitStream.Write(bExpectAnimToExist);
bitStream.Write(bPlayImmediate);
bitStream.Write(bTriggerOnCompleteMsg);
BitStreamUtils::WriteOptional(bitStream, fPriority, 0.0f);
BitStreamUtils::WriteOptional(bitStream, fScale, 1.0f);
}
bool PlayAnimation::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, animationID));
VALIDATE_READ(bitStream.Read(bExpectAnimToExist));
VALIDATE_READ(bitStream.Read(bPlayImmediate));
VALIDATE_READ(bitStream.Read(bTriggerOnCompleteMsg));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, fPriority, 0.0f));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, fScale, 1.0f));
return true;
}
void PlayNDAudioEmitter::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional<LWOOBJID>(bitStream, NDAudioCallbackMessageData, 0);
BitStreamUtils::WriteOptional<uint32_t>(bitStream, NDAudioEmitterID, 0);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, NDAudioEventGUID);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, NDAudioMetaEventName);
bitStream.Write(result);
BitStreamUtils::WriteOptional(bitStream, targetObjectIDForNDAudioCallbackMessages, LWOOBJID_EMPTY);
}
bool PlayNDAudioEmitter::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional<LWOOBJID>(bitStream, NDAudioCallbackMessageData, 0));
VALIDATE_READ(BitStreamUtils::ReadOptional<uint32_t>(bitStream, NDAudioEmitterID, 0));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, NDAudioEventGUID));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, NDAudioMetaEventName));
VALIDATE_READ(bitStream.Read(result));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, targetObjectIDForNDAudioCallbackMessages, LWOOBJID_EMPTY));
return true;
}
void PlayEmbeddedEffectOnAllClientsNearObject::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, effectName);
bitStream.Write(fromObjectID);
bitStream.Write(radius);
}
bool PlayEmbeddedEffectOnAllClientsNearObject::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, effectName));
VALIDATE_READ(bitStream.Read(fromObjectID));
VALIDATE_READ(bitStream.Read(radius));
return true;
}
void PlayFXEffect::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, effectID, -1);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, effectType);
BitStreamUtils::WriteOptional(bitStream, scale, 1.0f);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, name);
BitStreamUtils::WriteOptional(bitStream, priority, 1.0f);
BitStreamUtils::WriteOptional(bitStream, secondary, LWOOBJID_EMPTY);
bitStream.Write(serialize);
}
bool PlayFXEffect::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, effectID, -1));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, effectType));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, scale, 1.0f));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, name));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, priority, 1.0f));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, secondary, LWOOBJID_EMPTY));
VALIDATE_READ(bitStream.Read(serialize));
return true;
}
void StopFXEffect::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(killImmediate);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, name);
}
bool StopFXEffect::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(killImmediate));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, name));
return true;
}
void BroadcastTextToChatbox::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, attrs);
bitStream.Write<uint16_t>(0x00); // null terminator, always written
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, wsText);
}
bool BroadcastTextToChatbox::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, attrs));
uint16_t nullTerminator{};
VALIDATE_READ(bitStream.Read(nullTerminator));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, wsText));
return true;
}
void Play2DAmbientSound::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, audioGUID);
bitStream.Write(result);
}
bool Play2DAmbientSound::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, audioGUID));
VALIDATE_READ(bitStream.Read(result));
return true;
}
void Stop2DAmbientSound::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(force);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, audioGUID);
bitStream.Write(result);
}
bool Stop2DAmbientSound::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(force));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, audioGUID));
VALIDATE_READ(bitStream.Read(result));
return true;
}
void UIMessageServerToSingleClient::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write<AMFBaseValue&>(const_cast<AMFArrayValue&>(args));
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, strMessageName);
}
bool UIMessageServerToSingleClient::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(ReadAmfArray(bitStream, args));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, strMessageName));
return true;
}
void UIMessageServerToAllClients::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write<AMFBaseValue&>(const_cast<AMFArrayValue&>(args));
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, strMessageName);
}
bool UIMessageServerToAllClients::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(ReadAmfArray(bitStream, args));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, strMessageName));
return true;
}
void StartCelebrationEffect::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, animation);
BitStreamUtils::WriteOptional<LOT>(bitStream, backgroundObject, 11164);
BitStreamUtils::WriteOptional<LOT>(bitStream, cameraPathLOT, 12458);
BitStreamUtils::WriteOptional(bitStream, celeLeadIn, 1.0f);
BitStreamUtils::WriteOptional(bitStream, celeLeadOut, 0.8f);
bitStream.Write1(); // DLU always sends celebrationID
bitStream.Write(celebrationID);
bitStream.Write(duration);
bitStream.Write(iconID);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, mainText);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, mixerProgram);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, musicCue);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, pathNodeName);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, soundGUID);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, subText);
}
bool StartCelebrationEffect::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, animation));
VALIDATE_READ(BitStreamUtils::ReadOptional<LOT>(bitStream, backgroundObject, 11164));
VALIDATE_READ(BitStreamUtils::ReadOptional<LOT>(bitStream, cameraPathLOT, 12458));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, celeLeadIn, 1.0f));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, celeLeadOut, 0.8f));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, celebrationID, -1));
VALIDATE_READ(bitStream.Read(duration));
VALIDATE_READ(bitStream.Read(iconID));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, mainText));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, mixerProgram));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, musicCue));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, pathNodeName));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, soundGUID));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, subText));
return true;
}
void DisplayMessageBox::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(bShow);
bitStream.Write(callbackClient);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, identifier);
bitStream.Write(imageID);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, text);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, userData);
}
bool DisplayMessageBox::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(bShow));
VALIDATE_READ(bitStream.Read(callbackClient));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, identifier));
VALIDATE_READ(bitStream.Read(imageID));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, text));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, userData));
return true;
}
void MessageBoxRespond::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(iButton);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, identifier);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, userData);
}
bool MessageBoxRespond::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(iButton));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, identifier));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, userData));
return true;
}
void MessageBoxRespond::Handle(Entity& entityRef, const SystemAddress& sysAddr) {
Entity* entity = &entityRef;
LOG("Button: %d; LOT: %u identifier: %s; userData: %s", iButton, entity->GetLOT(), GeneralUtils::UTF16ToWTF8(identifier).c_str(), GeneralUtils::UTF16ToWTF8(userData).c_str());
auto* user = UserManager::Instance()->GetUser(sysAddr);
if (user == nullptr) {
return;
}
auto* userEntity = user->GetLastUsedChar()->GetEntity();
if (userEntity == nullptr) {
return;
}
entity->OnMessageBoxResponse(userEntity, iButton, identifier, userData);
auto* scriptedActivityComponent = entity->GetComponent<ScriptedActivityComponent>();
if (scriptedActivityComponent != nullptr) {
scriptedActivityComponent->HandleMessageBoxResponse(userEntity, GeneralUtils::UTF16ToWTF8(identifier));
}
auto* racingControlComponent = entity->GetComponent<RacingControlComponent>();
if (racingControlComponent != nullptr) {
racingControlComponent->HandleMessageBoxResponse(userEntity, iButton, GeneralUtils::UTF16ToWTF8(identifier));
}
for (auto* shootingGallery : Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SHOOTING_GALLERY)) {
shootingGallery->OnMessageBoxResponse(userEntity, iButton, identifier, userData);
}
}
void ChoiceBoxRespond::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, buttonIdentifier);
bitStream.Write(iButton);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, identifier);
}
bool ChoiceBoxRespond::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, buttonIdentifier));
VALIDATE_READ(bitStream.Read(iButton));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, identifier));
return true;
}
void ChoiceBoxRespond::Handle(Entity& entityRef, const SystemAddress& sysAddr) {
Entity* entity = &entityRef;
LOG("Button: %d; LOT: %u buttonIdentifier: %s; userData: %s", iButton, entity->GetLOT(), GeneralUtils::UTF16ToWTF8(buttonIdentifier).c_str(), GeneralUtils::UTF16ToWTF8(identifier).c_str());
auto* user = UserManager::Instance()->GetUser(sysAddr);
if (user == nullptr) {
return;
}
auto* userEntity = user->GetLastUsedChar()->GetEntity();
if (userEntity == nullptr) {
return;
}
entity->OnChoiceBoxResponse(userEntity, iButton, buttonIdentifier, identifier);
}
void DisplayChatBubble::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, wsText);
}
bool DisplayChatBubble::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, wsText));
return true;
}
void ChangeIdleFlags::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, flagsOff, eAnimationFlags::IDLE_NONE);
BitStreamUtils::WriteOptional(bitStream, flagsOn, eAnimationFlags::IDLE_NONE);
}
bool ChangeIdleFlags::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, flagsOff, eAnimationFlags::IDLE_NONE));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, flagsOn, eAnimationFlags::IDLE_NONE));
return true;
}
void PlayCinematic::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(allowGhostUpdates);
bitStream.Write(bCloseMultiInteract);
bitStream.Write(bSendServerNotify);
bitStream.Write(bUseControlledObjectForAudioListener);
BitStreamUtils::WriteOptional(bitStream, endBehavior, eEndBehavior::RETURN);
bitStream.Write(hidePlayerDuringCine);
BitStreamUtils::WriteOptional(bitStream, leadIn, -1.0f);
bitStream.Write(leavePlayerLockedWhenFinished);
bitStream.Write(lockPlayer);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, pathName);
bitStream.Write(result);
bitStream.Write(skipIfSamePath);
bitStream.Write(startTimeAdvance);
}
bool PlayCinematic::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(allowGhostUpdates));
VALIDATE_READ(bitStream.Read(bCloseMultiInteract));
VALIDATE_READ(bitStream.Read(bSendServerNotify));
VALIDATE_READ(bitStream.Read(bUseControlledObjectForAudioListener));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, endBehavior, eEndBehavior::RETURN));
VALIDATE_READ(bitStream.Read(hidePlayerDuringCine));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, leadIn, -1.0f));
VALIDATE_READ(bitStream.Read(leavePlayerLockedWhenFinished));
VALIDATE_READ(bitStream.Read(lockPlayer));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, pathName));
VALIDATE_READ(bitStream.Read(result));
VALIDATE_READ(bitStream.Read(skipIfSamePath));
VALIDATE_READ(bitStream.Read(startTimeAdvance));
return true;
}
void EndCinematic::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, leadOut, -1.0f);
bitStream.Write(leavePlayerLocked);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, pathName);
}
bool EndCinematic::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, leadOut, -1.0f));
VALIDATE_READ(bitStream.Read(leavePlayerLocked));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, pathName));
return true;
}
void CinematicUpdate::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, event, eCinematicEvent::STARTED);
BitStreamUtils::WriteOptional(bitStream, overallTime, -1.0f);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, pathName);
BitStreamUtils::WriteOptional(bitStream, pathTime, -1.0f);
BitStreamUtils::WriteOptional(bitStream, waypoint, -1);
}
bool CinematicUpdate::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, event, eCinematicEvent::STARTED));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, overallTime, -1.0f));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, pathName));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, pathTime, -1.0f));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, waypoint, -1));
return true;
}
void CinematicUpdate::Handle(Entity& entity, const SystemAddress& sysAddr) {
std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT);
for (Entity* scriptEntity : scriptedActs) {
scriptEntity->OnCinematicUpdate(scriptEntity, &entity, event, pathName, pathTime, overallTime, waypoint);
}
}
void SlashCommandTextFeedback::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, text);
}
bool SlashCommandTextFeedback::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, text));
return true;
}
void PlayEmote::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(emoteID);
bitStream.Write(targetID);
}
bool PlayEmote::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(emoteID));
VALIDATE_READ(bitStream.Read(targetID));
return true;
}
void PlayEmote::Handle(Entity& entityRef, const SystemAddress& sysAddr) {
Entity* entity = &entityRef;
LOG_DEBUG("Emote (%i) (%llu)", emoteID, targetID);
//TODO: If targetID != 0, and we have one of the "perform emote" missions, complete them.
if (emoteID == 0) return;
std::string sAnimationName = "deaded"; //Default name in case we fail to get the emote
CDEmoteTableTable* emotes = CDClientManager::GetTable<CDEmoteTableTable>();
if (emotes) {
CDEmoteTable* emote = emotes->GetEmote(emoteID);
if (emote) sAnimationName = emote->animationName;
}
GameMessages::EmotePlayed msg;
msg.target = entity->GetObjectID();
msg.emoteID = emoteID;
msg.targetID = targetID; // The emote’s target entity or 0 if none
msg.Send(UNASSIGNED_SYSTEM_ADDRESS); // Broadcast to all clients
MissionComponent* missionComponent = entity->GetComponent<MissionComponent>();
if (!missionComponent) return;
if (targetID != LWOOBJID_EMPTY) {
auto* targetEntity = Game::entityManager->GetEntity(targetID);
LOG_DEBUG("Emote target found (%d)", targetEntity != nullptr);
if (targetEntity != nullptr) {
targetEntity->OnEmoteReceived(emoteID, entity);
missionComponent->Progress(eMissionTaskType::EMOTE, emoteID, targetID);
}
} else {
LOG_DEBUG("Target ID is empty, using backup");
const auto scriptedEntities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT);
const auto& referencePoint = entity->GetPosition();
for (auto* scripted : scriptedEntities) {
if (Vector3::DistanceSquared(scripted->GetPosition(), referencePoint) > 5.0f * 5.0f) continue;
scripted->OnEmoteReceived(emoteID, entity);
missionComponent->Progress(eMissionTaskType::EMOTE, emoteID, scripted->GetObjectID());
}
}
}
void SetEmoteLockState::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(bLock);
bitStream.Write(emoteID);
}
bool SetEmoteLockState::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(bLock));
VALIDATE_READ(bitStream.Read(emoteID));
return true;
}
}

View File

@@ -0,0 +1,327 @@
#ifndef EFFECTSMESSAGES_H
#define EFFECTSMESSAGES_H
#include "GameMessages.h"
#include "Amf3.h"
#include "eAnimationFlags.h"
#include "eCinematicEvent.h"
#include "eEndBehavior.h"
#include <string>
// Game messages for effects, audio, animations, emotes, cinematics and UI text (message boxes, chat bubbles,
// billboards, UI messages, slash command feedback).
// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order.
namespace GameMessages {
// Server -> client, broadcast.
struct PlayAnimation : public NetGameMsg {
PlayAnimation() : NetGameMsg(MessageType::Game::PLAY_ANIMATION) {}
PlayAnimation(const LWOOBJID _target, const std::u16string& _animationID) : PlayAnimation() {
target = _target;
animationID = _animationID;
}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
// Written as a u32 length followed by that many UTF-16 characters, where the length is the size of the
// name converted to UTF-8 (so a non-ASCII name is padded with null characters). This is what DLU has
// always sent.
std::u16string animationID{};
bool bExpectAnimToExist{ true };
bool bPlayImmediate{};
bool bTriggerOnCompleteMsg{};
float fPriority{ 0.0f }; // optional
float fScale{ 1.0f }; // optional
};
// Server -> client, broadcast.
struct PlayNDAudioEmitter : public NetGameMsg {
PlayNDAudioEmitter() : NetGameMsg(MessageType::Game::PLAY_ND_AUDIO_EMITTER) {}
PlayNDAudioEmitter(const LWOOBJID _target, const std::string& _NDAudioEventGUID) : PlayNDAudioEmitter() {
target = _target;
NDAudioEventGUID = _NDAudioEventGUID;
}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
LWOOBJID NDAudioCallbackMessageData{}; // optional
uint32_t NDAudioEmitterID{}; // optional
std::string NDAudioEventGUID{};
std::string NDAudioMetaEventName{};
bool result{};
LWOOBJID targetObjectIDForNDAudioCallbackMessages{ LWOOBJID_EMPTY }; // optional
};
// Server -> client, broadcast.
struct PlayEmbeddedEffectOnAllClientsNearObject : public NetGameMsg {
PlayEmbeddedEffectOnAllClientsNearObject() : NetGameMsg(MessageType::Game::PLAY_EMBEDDED_EFFECT_ON_ALL_CLIENTS_NEAR_OBJECT) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
std::u16string effectName{};
LWOOBJID fromObjectID{};
float radius{};
};
// Server -> client, broadcast.
struct PlayFXEffect : public NetGameMsg {
PlayFXEffect() : NetGameMsg(MessageType::Game::PLAY_FX_EFFECT) {}
PlayFXEffect(const LWOOBJID _target, const int32_t _effectID, const std::u16string& _effectType, const std::string& _name) : PlayFXEffect() {
target = _target;
effectID = _effectID;
effectType = _effectType;
name = _name;
}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
int32_t effectID{ -1 }; // optional
std::u16string effectType{};
float scale{ 1.0f }; // optional
std::string name{};
float priority{ 1.0f }; // optional
LWOOBJID secondary{ LWOOBJID_EMPTY }; // optional
bool serialize{ true };
};
// Server -> client, broadcast.
struct StopFXEffect : public NetGameMsg {
StopFXEffect() : NetGameMsg(MessageType::Game::STOP_FX_EFFECT) {}
StopFXEffect(const LWOOBJID _target, const bool _killImmediate, const std::string& _name) : StopFXEffect() {
target = _target;
killImmediate = _killImmediate;
name = _name;
}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
bool killImmediate{};
std::string name{};
};
// Server -> client, broadcast.
struct BroadcastTextToChatbox : public NetGameMsg {
BroadcastTextToChatbox() : NetGameMsg(MessageType::Game::BROADCAST_TEXT_TO_CHATBOX) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
// Name value text: u32 length, the characters, then a null character that DLU always writes (even when empty).
std::u16string attrs{};
std::u16string wsText{};
};
// Server -> client.
struct Play2DAmbientSound : public NetGameMsg {
Play2DAmbientSound() : NetGameMsg(MessageType::Game::PLAY2_D_AMBIENT_SOUND) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
std::string audioGUID{};
bool result{};
};
// Server -> client.
struct Stop2DAmbientSound : public NetGameMsg {
Stop2DAmbientSound() : NetGameMsg(MessageType::Game::STOP2_D_AMBIENT_SOUND) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
bool force{};
std::string audioGUID{};
bool result{};
};
// Server -> client.
struct UIMessageServerToSingleClient : public NetGameMsg {
UIMessageServerToSingleClient() : NetGameMsg(MessageType::Game::UI_MESSAGE_SERVER_TO_SINGLE_CLIENT) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
AMFArrayValue args{};
std::string strMessageName{};
};
// Server -> client. Broadcast, or sent to one client (DLU uses it to reach a client that may not have an
// entity yet; the target is then LWOOBJID_EMPTY).
struct UIMessageServerToAllClients : public NetGameMsg {
UIMessageServerToAllClients() : NetGameMsg(MessageType::Game::UI_MESSAGE_SERVER_TO_ALL_CLIENTS) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
AMFArrayValue args{};
std::string strMessageName{};
};
// Server -> client.
struct StartCelebrationEffect : public NetGameMsg {
StartCelebrationEffect() : NetGameMsg(MessageType::Game::START_CELEBRATION_EFFECT) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
std::u16string animation{};
LOT backgroundObject{ 11164 }; // optional
LOT cameraPathLOT{ 12458 }; // optional
float celeLeadIn{ 1.0f }; // optional
float celeLeadOut{ 0.8f }; // optional
int32_t celebrationID{ -1 }; // optional; DLU always writes it, even when it has the default value
float duration{};
uint32_t iconID{};
std::u16string mainText{};
std::string mixerProgram{};
std::string musicCue{};
std::string pathNodeName{};
std::string soundGUID{};
std::u16string subText{};
};
// Server -> client.
struct DisplayMessageBox : public NetGameMsg {
DisplayMessageBox() : NetGameMsg(MessageType::Game::DISPLAY_MESSAGE_BOX) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
bool bShow{};
LWOOBJID callbackClient{};
std::u16string identifier{};
int32_t imageID{};
std::u16string text{};
std::u16string userData{};
};
// Client -> server. Sent to the callbackClient of a DisplayMessageBox when the player presses a button.
struct MessageBoxRespond : public NetGameMsg {
MessageBoxRespond() : NetGameMsg(MessageType::Game::MESSAGE_BOX_RESPOND) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
int32_t iButton{};
std::u16string identifier{};
std::u16string userData{};
};
// Client -> server.
struct ChoiceBoxRespond : public NetGameMsg {
ChoiceBoxRespond() : NetGameMsg(MessageType::Game::CHOICE_BOX_RESPOND) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
std::u16string buttonIdentifier{};
int32_t iButton{};
std::u16string identifier{};
};
// Server -> client.
struct DisplayChatBubble : public NetGameMsg {
DisplayChatBubble() : NetGameMsg(MessageType::Game::DISPLAY_CHAT_BUBBLE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
std::u16string wsText{};
};
// Server -> client, broadcast.
struct ChangeIdleFlags : public NetGameMsg {
ChangeIdleFlags() : NetGameMsg(MessageType::Game::CHANGE_IDLE_FLAGS) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
eAnimationFlags flagsOff{ eAnimationFlags::IDLE_NONE }; // optional
eAnimationFlags flagsOn{ eAnimationFlags::IDLE_NONE }; // optional
};
// Server -> client. The client's message has two bools (overrideDefault, state), but the client never reads
// them, so DLU sends no payload; the message only turns the name billboard off.
struct SetNameBillboardState : public NetGameMsg {
SetNameBillboardState() : NetGameMsg(MessageType::Game::SET_NAME_BILLBOARD_STATE) {}
};
// Server -> client. No payload.
struct ShowBillboardInteractIcon : public NetGameMsg {
ShowBillboardInteractIcon() : NetGameMsg(MessageType::Game::SHOW_BILLBOARD_INTERACT_ICON) {}
};
// Server -> client.
struct PlayCinematic : public NetGameMsg {
PlayCinematic() : NetGameMsg(MessageType::Game::PLAY_CINEMATIC) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
bool allowGhostUpdates{ true };
bool bCloseMultiInteract{ true };
bool bSendServerNotify{};
bool bUseControlledObjectForAudioListener{};
eEndBehavior endBehavior{ eEndBehavior::RETURN }; // optional
bool hidePlayerDuringCine{};
float leadIn{ -1.0f }; // optional
bool leavePlayerLockedWhenFinished{};
bool lockPlayer{ true };
std::u16string pathName{};
bool result{};
bool skipIfSamePath{};
float startTimeAdvance{};
};
// Server -> client.
struct EndCinematic : public NetGameMsg {
EndCinematic() : NetGameMsg(MessageType::Game::END_CINEMATIC) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
float leadOut{ -1.0f }; // optional
bool leavePlayerLocked{};
std::u16string pathName{};
};
// Client -> server.
struct CinematicUpdate : public NetGameMsg {
CinematicUpdate() : NetGameMsg(MessageType::Game::CINEMATIC_UPDATE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
eCinematicEvent event{ eCinematicEvent::STARTED }; // optional
float overallTime{ -1.0f }; // optional
std::u16string pathName{};
float pathTime{ -1.0f }; // optional
int32_t waypoint{ -1 }; // optional
};
// Server -> client.
struct SlashCommandTextFeedback : public NetGameMsg {
SlashCommandTextFeedback() : NetGameMsg(MessageType::Game::SLASH_COMMAND_TEXT_FEEDBACK) {}
SlashCommandTextFeedback(const LWOOBJID _target, const std::u16string& _text) : SlashCommandTextFeedback() {
target = _target;
text = _text;
}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
std::u16string text{};
};
// Both directions: the client asks to play an emote (handled here), and the server tells clients an object
// plays one.
struct PlayEmote : public NetGameMsg {
PlayEmote() : NetGameMsg(MessageType::Game::PLAY_EMOTE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
int32_t emoteID{};
LWOOBJID targetID{};
};
// Server -> client.
struct SetEmoteLockState : public NetGameMsg {
SetEmoteLockState() : NetGameMsg(MessageType::Game::SET_EMOTE_LOCK_STATE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
bool bLock{};
int32_t emoteID{};
};
};
#endif // EFFECTSMESSAGES_H

View File

@@ -35,6 +35,7 @@
#include "BuildingMessages.h"
#include "RacingMessages.h"
#include "MissionMessages.h"
#include "EffectsMessages.h"
#include "PetMessages.h"
#include "PropertyMessages.h"
#include "eMissionTaskType.h"
@@ -76,6 +77,12 @@ namespace {
{ SET_FLAG, []() { return std::make_unique<SetFlag>(); } },
{ HAS_BEEN_COLLECTED, []() { return std::make_unique<HasBeenCollected>(); } },
// Effects, emotes, cinematics, UI
{ PLAY_EMOTE, []() { return std::make_unique<PlayEmote>(); } },
{ MESSAGE_BOX_RESPOND, []() { return std::make_unique<MessageBoxRespond>(); } },
{ CHOICE_BOX_RESPOND, []() { return std::make_unique<ChoiceBoxRespond>(); } },
{ CINEMATIC_UPDATE, []() { return std::make_unique<CinematicUpdate>(); } },
// Pets
{ PET_TAMING_TRY_BUILD, []() { return std::make_unique<PetTamingTryBuild>(); } },
{ NOTIFY_TAMING_BUILD_SUCCESS, []() { return std::make_unique<NotifyTamingBuildSuccess>(); } },
@@ -166,11 +173,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
switch (messageID) {
case MessageType::Game::PLAY_EMOTE: {
GameMessages::HandlePlayEmote(inStream, entity);
break;
}
case MessageType::Game::MOVE_ITEM_IN_INVENTORY: {
GameMessages::HandleMoveItemInInventory(inStream, entity);
break;
@@ -287,7 +289,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|| !entity->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
entity->AddCallbackTimer(0.5f, [entity, sysAddr]() {
if (!entity) return;
GameMessages::SendEndCinematic(entity->GetObjectID(), u"", sysAddr);
GameMessages::EndCinematic endCinematic;
endCinematic.target = entity->GetObjectID();
endCinematic.Send(sysAddr);
});
break;
}
@@ -520,14 +524,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
GameMessages::HandleClientTradeUpdate(inStream, entity, sysAddr);
break;
case MessageType::Game::MESSAGE_BOX_RESPOND:
GameMessages::HandleMessageBoxResponse(inStream, entity, sysAddr);
break;
case MessageType::Game::CHOICE_BOX_RESPOND:
GameMessages::HandleChoiceBoxRespond(inStream, entity, sysAddr);
break;
// Racing: most racing messages are registered in g_MessageHandlers
case MessageType::Game::ACKNOWLEDGE_POSSESSION:
GameMessages::HandleAcknowledgePossession(inStream, entity, sysAddr);
@@ -575,10 +571,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
GameMessages::HandlePlayerRailArrivedNotification(inStream, entity, sysAddr);
break;
case MessageType::Game::CINEMATIC_UPDATE:
GameMessages::HandleCinematicUpdate(inStream, entity, sysAddr);
break;
case MessageType::Game::MODIFY_PLAYER_ZONE_STATISTIC:
GameMessages::HandleModifyPlayerZoneStatistic(inStream, entity);
break;

View File

@@ -1,4 +1,5 @@
#include "GameMessages.h"
#include "EffectsMessages.h"
#include "DashboardNotify.h"
#include "PlayerReports.h"
#include "EconomyLedger.h"
@@ -162,43 +163,6 @@ void GameMessages::SendTeleport(const LWOOBJID& objectID, const NiPoint3& pos, c
SEND_PACKET;
}
void GameMessages::SendPlayAnimation(Entity* entity, const std::u16string& animationName, float fPriority, float fScale) {
if (!entity) {
LOG("Trying to play animation, but entity var is nullptr!");
return;
}
//Stolen from the old DLU codebase as the new one's autogenerated code doesn't work properly for animationIDs longer than 6 characters.
CBITSTREAM;
CMSGHEADER;
std::string sAnimationID = GeneralUtils::UTF16ToWTF8(animationName);
uint32_t animationIDLength = sAnimationID.size();
bool bExpectAnimToExist = true;
bool bPlayImmediate = false;
bool bTriggerOnCompleteMsg = false;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::PLAY_ANIMATION);
bitStream.Write(animationIDLength);
bitStream.Write(LUWString(animationName, animationIDLength));
bitStream.Write(bExpectAnimToExist);
bitStream.Write(bPlayImmediate);
bitStream.Write(bTriggerOnCompleteMsg);
bitStream.Write(fPriority != 0.0f);
if (fPriority != 0.0f) bitStream.Write(fPriority);
bitStream.Write(fScale != 1.0f);
if (fScale != 1.0f) bitStream.Write(fScale);
SEND_PACKET_BROADCAST;
}
void GameMessages::SendPlayerReady(Entity* entity, const SystemAddress& sysAddr) {
CBITSTREAM;
CMSGHEADER;
@@ -283,28 +247,6 @@ void GameMessages::SendPlayerSetCameraCyclingMode(const LWOOBJID& objectID, cons
SEND_PACKET;
}
void GameMessages::SendPlayNDAudioEmitter(Entity* entity, const SystemAddress& sysAddr, std::string audioGUID) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::PLAY_ND_AUDIO_EMITTER);
bitStream.Write0(); // callback message data {lwoobjid}
bitStream.Write0(); // audio emitterid {uint32_t}
uint32_t length = audioGUID.size();
bitStream.Write(length);
for (uint32_t k = 0; k < length; k++) {
bitStream.Write<char>(audioGUID[k]);
}
bitStream.Write<uint32_t>(0); // size of NDAudioMetaEventName (then print the string like the guid)
bitStream.Write0(); // result {bool}
bitStream.Write0(); // m_TargetObjectIDForNDAudioCallbackMessages {lwoobjid}
SEND_PACKET_BROADCAST;
}
void GameMessages::SendStartPathing(Entity* entity) {
CBITSTREAM;
CMSGHEADER;
@@ -491,153 +433,6 @@ void GameMessages::SendModifyLEGOScore(Entity* entity, const SystemAddress& sysA
SEND_PACKET;
}
void GameMessages::SendUIMessageServerToSingleClient(Entity* entity, const SystemAddress& sysAddr, const std::string& message, AMFBaseValue& args) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::UI_MESSAGE_SERVER_TO_SINGLE_CLIENT);
bitStream.Write<AMFBaseValue&>(args);
uint32_t strMessageNameLength = message.size();
bitStream.Write(strMessageNameLength);
for (uint32_t k = 0; k < strMessageNameLength; k++) {
bitStream.Write<char>(message[k]);
}
SEND_PACKET;
}
void GameMessages::SendUIMessageServerToSingleClient(const std::string& message, AMFBaseValue& args, const SystemAddress& sysAddr) {
CBITSTREAM;
CMSGHEADER;
LWOOBJID empty = 0;
bitStream.Write(empty);
bitStream.Write(MessageType::Game::UI_MESSAGE_SERVER_TO_ALL_CLIENTS); // This is intentional to allow the server to send a ui message to a client via their system address.
bitStream.Write<AMFBaseValue&>(args);
uint32_t strMessageNameLength = message.size();
bitStream.Write(strMessageNameLength);
for (uint32_t k = 0; k < strMessageNameLength; k++) {
bitStream.Write<char>(message[k]);
}
SEND_PACKET;
}
void GameMessages::SendUIMessageServerToAllClients(const std::string& message, AMFBaseValue& args) {
CBITSTREAM;
CMSGHEADER;
LWOOBJID empty = 0;
bitStream.Write(empty);
bitStream.Write(MessageType::Game::UI_MESSAGE_SERVER_TO_ALL_CLIENTS);
bitStream.Write<AMFBaseValue&>(args);
uint32_t strMessageNameLength = message.size();
bitStream.Write(strMessageNameLength);
for (uint32_t k = 0; k < strMessageNameLength; k++) {
bitStream.Write<char>(message[k]);
}
SEND_PACKET_BROADCAST;
}
void GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(Entity* entity, std::u16string effectName, const LWOOBJID& fromObjectID, float radius) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::PLAY_EMBEDDED_EFFECT_ON_ALL_CLIENTS_NEAR_OBJECT);
bitStream.Write<uint32_t>(effectName.length());
for (uint32_t k = 0; k < effectName.length(); k++) {
bitStream.Write<uint16_t>(effectName[k]);
}
bitStream.Write(fromObjectID);
bitStream.Write(radius);
SEND_PACKET_BROADCAST;
}
void GameMessages::SendPlayFXEffect(Entity* entity, int32_t effectID, const std::u16string& effectType, const std::string& name, LWOOBJID secondary, float priority, float scale, bool serialize) {
SendPlayFXEffect(entity->GetObjectID(), effectID, effectType, name, secondary, priority, scale, serialize);
}
void GameMessages::SendPlayFXEffect(const LWOOBJID& entity, int32_t effectID, const std::u16string& effectType, const std::string& name, LWOOBJID secondary, float priority, float scale, bool serialize) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity);
bitStream.Write(MessageType::Game::PLAY_FX_EFFECT);
bitStream.Write(effectID != -1);
if (effectID != -1) bitStream.Write(effectID);
bitStream.Write<uint32_t>(effectType.size());
for (const auto& k : effectType) {
bitStream.Write<uint16_t>(k);
}
bitStream.Write(scale != 1.0f);
if (scale != 1.0f) bitStream.Write(scale);
bitStream.Write<uint32_t>(name.size());
for (const auto& k : name) {
bitStream.Write(k);
}
bitStream.Write(priority != 1.0);
if (priority != 1.0) bitStream.Write(priority);
bitStream.Write(secondary != LWOOBJID_EMPTY);
if (secondary != LWOOBJID_EMPTY) bitStream.Write(secondary);
bitStream.Write(serialize);
SEND_PACKET_BROADCAST;
}
void GameMessages::SendStopFXEffect(Entity* entity, bool killImmediate, std::string name) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::STOP_FX_EFFECT);
bitStream.Write(killImmediate);
bitStream.Write<uint32_t>(name.size());
bitStream.Write(name.c_str(), name.size());
SEND_PACKET_BROADCAST;
}
void GameMessages::SendBroadcastTextToChatbox(Entity* entity, const SystemAddress& sysAddr, const std::u16string& attrs, const std::u16string& wsText) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::BROADCAST_TEXT_TO_CHATBOX);
bitStream.Write<uint32_t>(attrs.size());
for (uint32_t i = 0; i < attrs.size(); ++i) {
bitStream.Write<uint16_t>(attrs[i]);
}
bitStream.Write<uint16_t>(0x00); // Null Terminator
uint32_t wsTextLength = wsText.size();
bitStream.Write(wsTextLength);
for (uint32_t k = 0; k < wsTextLength; k++) {
bitStream.Write<uint16_t>(wsText[k]);
}
SEND_PACKET_BROADCAST;
}
void GameMessages::SendSetCurrency(Entity* entity, int64_t currency, int lootType, const LWOOBJID& sourceID, const LOT& sourceLOT, int sourceTradeID, bool overrideCurrent, eLootSourceType sourceType) {
CBITSTREAM;
CMSGHEADER;
@@ -792,19 +587,6 @@ void GameMessages::SendSetInventorySize(Entity* entity, int invType, int size) {
SEND_PACKET;
}
void GameMessages::SendSetEmoteLockState(Entity* entity, bool bLock, int emoteID) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::SET_EMOTE_LOCK_STATE);
bitStream.Write(bLock);
bitStream.Write(emoteID);
SystemAddress sysAddr = entity->GetSystemAddress();
SEND_PACKET;
}
void GameMessages::SendSetJetPackMode(Entity* entity, bool use, bool bypassChecks, bool doHover, int effectID, float airspeed, float maxAirspeed, float verticalVelocity, int warningEffectID) {
/* historical jamesster jetpack values
if (bIsJamessterPhysics) {
@@ -880,47 +662,6 @@ void GameMessages::SendResurrect(Entity* entity) {
SEND_PACKET_BROADCAST;
}
void GameMessages::SendStop2DAmbientSound(Entity* entity, bool force, std::string audioGUID, bool result) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::STOP2_D_AMBIENT_SOUND);
uint32_t audioGUIDSize = audioGUID.size();
bitStream.Write(force);
bitStream.Write(audioGUIDSize);
for (uint32_t k = 0; k < audioGUIDSize; k++) {
bitStream.Write<char>(audioGUID[k]);
}
bitStream.Write(result);
SystemAddress sysAddr = entity->GetSystemAddress();
SEND_PACKET;
}
void GameMessages::SendPlay2DAmbientSound(Entity* entity, std::string audioGUID, bool result) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::PLAY2_D_AMBIENT_SOUND);
uint32_t audioGUIDSize = audioGUID.size();
bitStream.Write(audioGUIDSize);
for (uint32_t k = 0; k < audioGUIDSize; k++) {
bitStream.Write<char>(audioGUID[k]);
}
bitStream.Write(result);
SystemAddress sysAddr = entity->GetSystemAddress();
SEND_PACKET;
}
void GameMessages::SendSetNetworkScriptVar(Entity* entity, const SystemAddress& sysAddr, std::string data) {
CBITSTREAM;
CMSGHEADER;
@@ -1424,33 +1165,6 @@ void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream& inStream,
entity->OnActivityStateChangeRequest(objectID, value1, value2, stringValue);
}
void GameMessages::SendStartCelebrationEffect(Entity* entity, const SystemAddress& sysAddr, int celebrationID) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::START_CELEBRATION_EFFECT);
bitStream.Write<uint32_t>(0); //animation
bitStream.Write0(); //No custom bg obj
bitStream.Write0(); //custom path
bitStream.Write0(); //leadin
bitStream.Write0(); //leadout
bitStream.Write1();
bitStream.Write(celebrationID);
bitStream.Write<float>(0.0f); //duration
bitStream.Write<uint32_t>(0); //icon
bitStream.Write<uint32_t>(0); //text
bitStream.Write<uint32_t>(0); //mixer
bitStream.Write<uint32_t>(0); //cue
bitStream.Write<uint32_t>(0); //pathnodename
bitStream.Write<uint32_t>(0); //soundguid
bitStream.Write<uint32_t>(0); //subtext
SEND_PACKET;
}
void GameMessages::SendSetRailMovement(const LWOOBJID& objectID, bool pathGoForward, std::u16string pathName,
uint32_t pathStart, const SystemAddress& sysAddr, int32_t railActivatorComponentID,
LWOOBJID railActivatorObjectID) {
@@ -1696,114 +1410,6 @@ void GameMessages::HandleSetConsumableItem(RakNet::BitStream& inStream, Entity*
inventory->SetConsumable(lot);
}
void GameMessages::SendPlayCinematic(LWOOBJID objectId, std::u16string pathName, const SystemAddress& sysAddr,
bool allowGhostUpdates, bool bCloseMultiInteract, bool bSendServerNotify, bool bUseControlledObjectForAudioListener,
eEndBehavior endBehavior, bool hidePlayerDuringCine, float leadIn, bool leavePlayerLockedWhenFinished,
bool lockPlayer, bool result, bool skipIfSamePath, float startTimeAdvance) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(MessageType::Game::PLAY_CINEMATIC);
bitStream.Write(allowGhostUpdates);
bitStream.Write(bCloseMultiInteract);
bitStream.Write(bSendServerNotify);
bitStream.Write(bUseControlledObjectForAudioListener);
bitStream.Write(endBehavior != eEndBehavior::RETURN);
if (endBehavior != eEndBehavior::RETURN) bitStream.Write(endBehavior);
bitStream.Write(hidePlayerDuringCine);
bitStream.Write(leadIn != -1);
if (leadIn != -1) bitStream.Write(leadIn);
bitStream.Write(leavePlayerLockedWhenFinished);
bitStream.Write(lockPlayer);
bitStream.Write<uint32_t>(pathName.size());
for (char16_t c : pathName) {
bitStream.Write(c);
}
bitStream.Write(result);
bitStream.Write(skipIfSamePath);
bitStream.Write(startTimeAdvance);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
SEND_PACKET;
}
void GameMessages::SendEndCinematic(LWOOBJID objectId, std::u16string pathName, const SystemAddress& sysAddr,
float leadOut, bool leavePlayerLocked) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(MessageType::Game::END_CINEMATIC);
bitStream.Write(leadOut != -1);
if (leadOut != -1) bitStream.Write(leadOut);
bitStream.Write(leavePlayerLocked);
bitStream.Write<uint32_t>(pathName.size());
for (uint32_t i = 0; i < pathName.size(); i++) {
bitStream.Write(pathName[i]);
}
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
SEND_PACKET;
}
void GameMessages::HandleCinematicUpdate(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
eCinematicEvent event;
if (!inStream.ReadBit()) {
event = eCinematicEvent::STARTED;
} else {
inStream.Read<eCinematicEvent>(event);
}
float_t overallTime;
if (!inStream.ReadBit()) {
overallTime = -1.0f;
} else {
inStream.Read<float_t>(overallTime);
}
uint32_t pathNameLength;
inStream.Read(pathNameLength);
std::u16string pathName;
for (size_t i = 0; i < pathNameLength; i++) {
char16_t character;
inStream.Read(character);
pathName.push_back(character);
}
float_t pathTime;
if (!inStream.ReadBit()) {
pathTime = -1.0f;
} else {
inStream.Read<float_t>(pathTime);
}
int32_t waypoint;
if (!inStream.ReadBit()) {
waypoint = -1;
} else {
inStream.Read<int32_t>(waypoint);
}
std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT);
for (Entity* scriptEntity : scriptedActs) {
scriptEntity->OnCinematicUpdate(scriptEntity, entity, event, pathName, pathTime, overallTime, waypoint);
}
}
void GameMessages::SendSetStunned(LWOOBJID objectId, eStateChangeType stateChangeType, const SystemAddress& sysAddr,
LWOOBJID originator, bool bCantAttack, bool bCantEquip,
bool bCantInteract, bool bCantJump, bool bCantMove, bool bCantTurn,
@@ -2296,20 +1902,6 @@ void GameMessages::SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive
SEND_PACKET;
}
void GameMessages::SendPlayEmote(LWOOBJID objectId, int32_t emoteID, LWOOBJID target, const SystemAddress& sysAddr) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(MessageType::Game::PLAY_EMOTE);
bitStream.Write(emoteID);
bitStream.Write(target);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
SEND_PACKET;
}
void GameMessages::SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId) {
CBITSTREAM;
CMSGHEADER;
@@ -2325,106 +1917,6 @@ void GameMessages::SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeI
SEND_PACKET_BROADCAST;
}
void GameMessages::HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
int32_t iButton;
uint32_t identifierLength;
std::u16string identifier;
uint32_t userDataLength;
std::u16string userData;
inStream.Read(iButton);
inStream.Read(identifierLength);
if (identifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < identifierLength; i++) {
char16_t character;
inStream.Read(character);
identifier.push_back(character);
}
inStream.Read(userDataLength);
if (userDataLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < userDataLength; i++) {
char16_t character;
inStream.Read(character);
userData.push_back(character);
}
LOG("Button: %d; LOT: %u identifier: %s; userData: %s", iButton, entity->GetLOT(), GeneralUtils::UTF16ToWTF8(identifier).c_str(), GeneralUtils::UTF16ToWTF8(userData).c_str());
auto* user = UserManager::Instance()->GetUser(sysAddr);
if (user == nullptr) {
return;
}
auto* userEntity = user->GetLastUsedChar()->GetEntity();
if (userEntity == nullptr) {
return;
}
entity->OnMessageBoxResponse(userEntity, iButton, identifier, userData);
auto* scriptedActivityComponent = entity->GetComponent<ScriptedActivityComponent>();
if (scriptedActivityComponent != nullptr) {
scriptedActivityComponent->HandleMessageBoxResponse(userEntity, GeneralUtils::UTF16ToWTF8(identifier));
}
auto* racingControlComponent = entity->GetComponent<RacingControlComponent>();
if (racingControlComponent != nullptr) {
racingControlComponent->HandleMessageBoxResponse(userEntity, iButton, GeneralUtils::UTF16ToWTF8(identifier));
}
for (auto* shootingGallery : Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SHOOTING_GALLERY)) {
shootingGallery->OnMessageBoxResponse(userEntity, iButton, identifier, userData);
}
}
void GameMessages::HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
int32_t iButton;
uint32_t buttonIdentifierLength;
std::u16string buttonIdentifier;
uint32_t identifierLength;
std::u16string identifier;
inStream.Read(buttonIdentifierLength);
if (buttonIdentifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < buttonIdentifierLength; i++) {
char16_t character;
inStream.Read(character);
buttonIdentifier.push_back(character);
}
inStream.Read(iButton);
inStream.Read(identifierLength);
if (identifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < identifierLength; i++) {
char16_t character;
inStream.Read(character);
identifier.push_back(character);
}
LOG("Button: %d; LOT: %u buttonIdentifier: %s; userData: %s", iButton, entity->GetLOT(), GeneralUtils::UTF16ToWTF8(buttonIdentifier).c_str(), GeneralUtils::UTF16ToWTF8(identifier).c_str());
auto* user = UserManager::Instance()->GetUser(sysAddr);
if (user == nullptr) {
return;
}
auto* userEntity = user->GetLastUsedChar()->GetEntity();
if (userEntity == nullptr) {
return;
}
entity->OnChoiceBoxResponse(userEntity, iButton, buttonIdentifier, identifier);
}
void GameMessages::SendDisplayZoneSummary(LWOOBJID objectId, const SystemAddress& sysAddr, bool isPropertyMap, bool isZoneStart, LWOOBJID sender) {
CBITSTREAM;
CMSGHEADER;
@@ -2458,68 +1950,6 @@ void GameMessages::SendNotifyNotEnoughInvSpace(LWOOBJID objectId, uint32_t freeS
SEND_PACKET;
}
void GameMessages::SendDisplayMessageBox(LWOOBJID objectId, bool bShow, LWOOBJID callbackClient, const std::u16string& identifier, int32_t imageID, const std::u16string& text, const std::u16string& userData, const SystemAddress& sysAddr) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(MessageType::Game::DISPLAY_MESSAGE_BOX);
bitStream.Write(bShow);
bitStream.Write(callbackClient);
bitStream.Write<uint32_t>(identifier.size());
for (const auto character : identifier) {
bitStream.Write(character);
}
bitStream.Write(imageID);
bitStream.Write<uint32_t>(text.size());
for (const auto character : text) {
bitStream.Write(character);
}
bitStream.Write<uint32_t>(userData.size());
for (const auto character : userData) {
bitStream.Write(character);
}
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
SEND_PACKET;
}
void GameMessages::SendDisplayChatBubble(LWOOBJID objectId, const std::u16string& text, const SystemAddress& sysAddr) {
// MessageType::Game::DISPLAY_CHAT_BUBBLE
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(MessageType::Game::DISPLAY_CHAT_BUBBLE);
bitStream.Write<uint32_t>(text.size());
for (const auto character : text) {
bitStream.Write(character);
}
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
SEND_PACKET;
}
void GameMessages::SendChangeIdleFlags(LWOOBJID objectId, eAnimationFlags flagsOn, eAnimationFlags flagsOff, const SystemAddress& sysAddr) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(MessageType::Game::CHANGE_IDLE_FLAGS);
bitStream.Write<bool>(flagsOff != eAnimationFlags::IDLE_NONE);
if (flagsOff != eAnimationFlags::IDLE_NONE) bitStream.Write(flagsOff);
bitStream.Write<bool>(flagsOn != eAnimationFlags::IDLE_NONE);
if (flagsOn != eAnimationFlags::IDLE_NONE) bitStream.Write(flagsOn);
SEND_PACKET_BROADCAST;
}
// Mounts
void GameMessages::SendSetMountInventoryID(Entity* entity, const LWOOBJID& objectID, const SystemAddress& sysAddr) {
@@ -3143,59 +2573,6 @@ void GameMessages::HandleQuickBuildCancel(RakNet::BitStream& inStream, Entity* e
quickBuildComponent->CancelQuickBuild(Game::entityManager->GetEntity(userID), eQuickBuildFailReason::CANCELED_EARLY);
}
void GameMessages::HandlePlayEmote(RakNet::BitStream& inStream, Entity* entity) {
int32_t emoteID;
LWOOBJID targetID;
inStream.Read(emoteID);
inStream.Read(targetID);
LOG_DEBUG("Emote (%i) (%llu)", emoteID, targetID);
//TODO: If targetID != 0, and we have one of the "perform emote" missions, complete them.
if (emoteID == 0) return;
std::string sAnimationName = "deaded"; //Default name in case we fail to get the emote
CDEmoteTableTable* emotes = CDClientManager::GetTable<CDEmoteTableTable>();
if (emotes) {
CDEmoteTable* emote = emotes->GetEmote(emoteID);
if (emote) sAnimationName = emote->animationName;
}
GameMessages::EmotePlayed msg;
msg.target = entity->GetObjectID();
msg.emoteID = emoteID;
msg.targetID = targetID; // The emote’s target entity or 0 if none
msg.Send(UNASSIGNED_SYSTEM_ADDRESS); // Broadcast to all clients
MissionComponent* missionComponent = entity->GetComponent<MissionComponent>();
if (!missionComponent) return;
if (targetID != LWOOBJID_EMPTY) {
auto* targetEntity = Game::entityManager->GetEntity(targetID);
LOG_DEBUG("Emote target found (%d)", targetEntity != nullptr);
if (targetEntity != nullptr) {
targetEntity->OnEmoteReceived(emoteID, entity);
missionComponent->Progress(eMissionTaskType::EMOTE, emoteID, targetID);
}
} else {
LOG_DEBUG("Target ID is empty, using backup");
const auto scriptedEntities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT);
const auto& referencePoint = entity->GetPosition();
for (auto* scripted : scriptedEntities) {
if (Vector3::DistanceSquared(scripted->GetPosition(), referencePoint) > 5.0f * 5.0f) continue;
scripted->OnEmoteReceived(emoteID, entity);
missionComponent->Progress(eMissionTaskType::EMOTE, emoteID, scripted->GetObjectID());
}
}
}
void GameMessages::HandleNotifyServerLevelProcessingComplete(RakNet::BitStream& inStream, Entity* entity) {
auto* levelComp = entity->GetComponent<LevelProgressionComponent>();
if (!levelComp) return;
@@ -3218,7 +2595,7 @@ void GameMessages::HandleNotifyServerLevelProcessingComplete(RakNet::BitStream&
}
//Play the level up effect:
GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true);
GameMessages::PlayFXEffect(entity->GetObjectID(), 7074, u"create", "7074").Send(UNASSIGNED_SYSTEM_ADDRESS);
//Send a notification in chat:
std::stringstream wss;
@@ -3232,7 +2609,11 @@ void GameMessages::HandleNotifyServerLevelProcessingComplete(RakNet::BitStream&
std::u16string attrs = GeneralUtils::UTF8ToUTF16(wss.str());
std::u16string wsText = u"UI_LEVEL_PROGRESSION_LEVELUP_MESSAGE";
GameMessages::SendBroadcastTextToChatbox(entity, UNASSIGNED_SYSTEM_ADDRESS, attrs, wsText);
GameMessages::BroadcastTextToChatbox chatboxText;
chatboxText.target = entity->GetObjectID();
chatboxText.attrs = attrs;
chatboxText.wsText = wsText;
chatboxText.Send(UNASSIGNED_SYSTEM_ADDRESS);
}
void GameMessages::HandlePickupCurrency(RakNet::BitStream& inStream, Entity* entity) {
@@ -3809,35 +3190,6 @@ void GameMessages::HandleZoneSummaryDismissed(RakNet::BitStream& inStream, Entit
entity->TriggerEvent(eTriggerEventType::ZONE_SUMMARY_DISMISSED, target);
};
void GameMessages::SendSetNamebillboardState(const SystemAddress& sysAddr, LWOOBJID objectId) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(MessageType::Game::SET_NAME_BILLBOARD_STATE);
// Technically these bits would be written, however the client does not
// contain a deserialize method to actually deserialize, so we are leaving it out.
// As such this GM only turns the billboard off.
// bitStream.Write(overrideDefault);
// bitStream.Write(state);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST
else SEND_PACKET
}
void GameMessages::SendShowBillboardInteractIcon(const SystemAddress& sysAddr, LWOOBJID objectId) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(objectId);
bitStream.Write(MessageType::Game::SHOW_BILLBOARD_INTERACT_ICON);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST
else SEND_PACKET
}
void GameMessages::HandleAddDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
uint32_t count = 1;
bool hasCount = false;
@@ -3937,18 +3289,6 @@ void GameMessages::HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Ent
characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY);
}
void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string text) {
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::SLASH_COMMAND_TEXT_FEEDBACK);
bitStream.Write<uint32_t>(text.size());
bitStream.Write(text);
auto sysAddr = entity->GetSystemAddress();
SEND_PACKET;
}
void GameMessages::HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
std::string action;
std::u16string groupName;

View File

@@ -127,7 +127,6 @@ namespace GameMessages {
void SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender);
void SendTeleport(const LWOOBJID& objectID, const NiPoint3& pos, const NiQuaternion& rot, const SystemAddress& sysAddr, bool bSetRotation = false);
void SendPlayAnimation(Entity* entity, const std::u16string& animationName, float fPriority = 0.0f, float fScale = 1.0f);
void SendPlayerReady(Entity* entity, const SystemAddress& sysAddr);
void SendPlayerAllowedRespawn(LWOOBJID entityID, bool doNotPromptRespawn, const SystemAddress& systemAddress);
void SendInvalidZoneTransferList(Entity* entity, const SystemAddress& sysAddr, const std::u16string& feedbackURL, const std::u16string& invalidMapTransferList, bool feedbackOnExit, bool feedbackOnInvalidTransfer);
@@ -135,8 +134,6 @@ namespace GameMessages {
void SendPlayerSetCameraCyclingMode(const LWOOBJID& objectID, const SystemAddress& sysAddr, bool bAllowCyclingWhileDeadOnly = true, eCyclingMode cyclingMode = eCyclingMode::ALLOW_CYCLE_TEAMMATES);
void SendPlayNDAudioEmitter(Entity* entity, const SystemAddress& sysAddr, std::string audioGUID);
void SendStartPathing(Entity* entity);
// special is for the FV tree platform, feature is complete if we just do that so meh
@@ -153,17 +150,7 @@ namespace GameMessages {
void SendChangeObjectWorldState(const LWOOBJID& objectID, eObjectWorldState state, const SystemAddress& sysAddr);
void SendModifyLEGOScore(Entity* entity, const SystemAddress& sysAddr, int64_t score, eLootSourceType sourceType);
void SendUIMessageServerToSingleClient(Entity* entity, const SystemAddress& sysAddr, const std::string& message, AMFBaseValue& args);
// Specify sysAddr if you need to send a flash message to a client who you dont know the objectID of.
void SendUIMessageServerToSingleClient(const std::string& message, AMFBaseValue& args, const SystemAddress& sysAddr);
void SendUIMessageServerToAllClients(const std::string& message, AMFBaseValue& args);
void SendPlayEmbeddedEffectOnAllClientsNearObject(Entity* entity, std::u16string effectName, const LWOOBJID& fromObjectID, float radius);
void SendPlayFXEffect(Entity* entity, int32_t effectID, const std::u16string& effectType, const std::string& name, LWOOBJID secondary, float priority = 1, float scale = 1, bool serialize = true);
void SendPlayFXEffect(const LWOOBJID& entity, int32_t effectID, const std::u16string& effectType, const std::string& name, LWOOBJID secondary = LWOOBJID_EMPTY, float priority = 1, float scale = 1, bool serialize = true);
void SendStopFXEffect(Entity* entity, bool killImmediate, std::string name);
void SendBroadcastTextToChatbox(Entity* entity, const SystemAddress& sysAddr, const std::u16string& attrs, const std::u16string& wsText);
void SendSetCurrency(Entity* entity, int64_t currency, int lootType, const LWOOBJID& sourceID, const LOT& sourceLOT, int sourceTradeID, bool overrideCurrent, eLootSourceType sourceType);
void SendQuickBuildNotifyState(Entity* entity, eQuickBuildState prevState, eQuickBuildState state, const LWOOBJID& playerID);
@@ -178,11 +165,8 @@ namespace GameMessages {
void SendSetGravityScale(const LWOOBJID& target, const float effectScale, const SystemAddress& sysAddr);
void SendSetEmoteLockState(Entity* entity, bool bLock, int emoteID);
void SendSetJetPackMode(Entity* entity, bool use, bool bypassChecks = false, bool doHover = false, int effectID = -1, float airspeed = 10, float maxAirspeed = 15, float verticalVelocity = 1, int warningEffectID = -1);
void SendResurrect(Entity* entity);
void SendStop2DAmbientSound(Entity* entity, bool force, std::string audioGUID, bool result = false);
void SendPlay2DAmbientSound(Entity* entity, std::string audioGUID, bool result = false);
void SendSetNetworkScriptVar(Entity* entity, const SystemAddress& sysAddr, std::string data);
void SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme);
@@ -203,8 +187,6 @@ namespace GameMessages {
void SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response);
void SendMatchUpdate(Entity* entity, const SystemAddress& sysAddr, std::string data, eMatchUpdate type);
void SendStartCelebrationEffect(Entity* entity, const SystemAddress& sysAddr, int celebrationID);
// https://lcdruniverse.org/lu_packets/lu_packets/world/gm/client/struct.SetResurrectRestoreValues.html
void SendSetResurrectRestoreValues(Entity* targetEntity, int32_t armorRestore, int32_t healthRestore, int32_t imaginationRestore);
@@ -260,15 +242,6 @@ namespace GameMessages {
void HandleSetConsumableItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void SendPlayCinematic(LWOOBJID objectId, std::u16string pathName, const SystemAddress& sysAddr,
bool allowGhostUpdates = true, bool bCloseMultiInteract = true, bool bSendServerNotify = false, bool bUseControlledObjectForAudioListener = false,
eEndBehavior endBehavior = eEndBehavior::RETURN, bool hidePlayerDuringCine = false, float leadIn = -1, bool leavePlayerLockedWhenFinished = false,
bool lockPlayer = true, bool result = false, bool skipIfSamePath = false, float startTimeAdvance = 0);
void SendEndCinematic(LWOOBJID objectID, std::u16string pathName, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS,
float leadOut = -1.0f, bool leavePlayerLocked = false);
void HandleCinematicUpdate(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void SendSetStunned(LWOOBJID objectId, eStateChangeType stateChangeType, const SystemAddress& sysAddr,
LWOOBJID originator = LWOOBJID_EMPTY, bool bCantAttack = false, bool bCantEquip = false,
bool bCantInteract = false, bool bCantJump = false, bool bCantMove = false, bool bCantTurn = false,
@@ -343,30 +316,11 @@ namespace GameMessages {
//Pets:
void SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, eUnequippableActiveType iType, LWOOBJID itemID, const SystemAddress& sysAddr);
void SendPlayEmote(LWOOBJID objectId, int32_t emoteID, LWOOBJID target, const SystemAddress& sysAddr);
void HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void SendDisplayZoneSummary(LWOOBJID objectId, const SystemAddress& sysAddr, bool isPropertyMap = false, bool isZoneStart = false, LWOOBJID sender = LWOOBJID_EMPTY);
//UI:
void SendNotifyNotEnoughInvSpace(LWOOBJID objectId, uint32_t freeSlotsNeeded, eInventoryType inventoryType, const SystemAddress& sysAddr);
void SendDisplayMessageBox(LWOOBJID objectId, bool bShow, LWOOBJID callbackClient, const std::u16string& identifier, int32_t imageID, const std::u16string& text, const std::u16string& userData, const SystemAddress& sysAddr);
void SendDisplayChatBubble(LWOOBJID objectId, const std::u16string& text, const SystemAddress& sysAddr);
/**
* @brief
*
* @param objectId ID of the entity to set idle flags
* @param FlagsOn Flag to turn on
* @param FlagsOff Flag to turn off
*/
void SendChangeIdleFlags(LWOOBJID objectId, eAnimationFlags FlagsOn, eAnimationFlags FlagsOff, const SystemAddress& sysAddr);
// Mounts
/**
* @brief Set the Inventory LWOOBJID of the mount
@@ -445,8 +399,6 @@ namespace GameMessages {
void HandleToggleGhostReferenceOverride(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleSetGhostReferencePosition(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void SendSetNamebillboardState(const SystemAddress& sysAddr, LWOOBJID objectId);
void SendShowBillboardInteractIcon(const SystemAddress& sysAddr, LWOOBJID objectId);
void HandleBuyFromVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleSellToVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleBuybackFromVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
@@ -456,7 +408,6 @@ namespace GameMessages {
void HandleFireEventServerSide(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleRequestPlatformResync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleQuickBuildCancel(RakNet::BitStream& inStream, Entity* entity);
void HandlePlayEmote(RakNet::BitStream& inStream, Entity* entity);
void HandleNotifyServerLevelProcessingComplete(RakNet::BitStream& inStream, Entity* entity);
void HandlePickupCurrency(RakNet::BitStream& inStream, Entity* entity);
void HandleRequestDie(RakNet::BitStream& inStream, Entity* entity);
@@ -501,8 +452,6 @@ namespace GameMessages {
void HandleConfirmDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity);
void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity);
void SendSlashCommandFeedbackText(Entity* entity, std::u16string text);
void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);

View File

@@ -15,6 +15,7 @@
#include "Mission.h"
#include "MissionComponent.h"
#include "MissionOfferComponent.h"
#include "EffectsMessages.h"
namespace GameMessages {
void OfferMission::Serialize(RakNet::BitStream& bitStream) const {
@@ -150,7 +151,9 @@ namespace GameMessages {
|| !player->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
player->AddCallbackTimer(0.5f, [player]() {
if (!player) return;
GameMessages::SendEndCinematic(player->GetObjectID(), u"", player->GetSystemAddress());
GameMessages::EndCinematic endCinematic;
endCinematic.target = player->GetObjectID();
endCinematic.Send(player->GetSystemAddress());
});
}