Files
DarkflameServer/dGame/dGameMessages/EffectsMessages.h
Aaron Kimbrell dae1925d1a 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>
2026-09-28 22:30:51 -05:00

328 lines
12 KiB
C++

#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