mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
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:
@@ -3,6 +3,7 @@ SET(DGAMEMESSAGES_TESTS
|
||||
"BuildingMessagesTests.cpp"
|
||||
"GameMessageTests.cpp"
|
||||
"GameMsgSplitTests.cpp"
|
||||
"EffectsMessagesTests.cpp"
|
||||
"MissionMessagesTests.cpp"
|
||||
"PetMessagesTests.cpp"
|
||||
"PropertyMessagesTests.cpp"
|
||||
|
||||
571
tests/dGameTests/dGameMessagesTests/EffectsMessagesTests.cpp
Normal file
571
tests/dGameTests/dGameMessagesTests/EffectsMessagesTests.cpp
Normal file
@@ -0,0 +1,571 @@
|
||||
#include "EffectsMessages.h"
|
||||
#include "GameDependencies.h"
|
||||
#include "GameMessageTestUtils.h"
|
||||
#include "Legacy/EffectsMessagesLegacy.h"
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace GameMessageTestUtils;
|
||||
|
||||
namespace {
|
||||
const std::vector<int32_t> g_Ints = { 0, 1, -1, 1727, std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::min() };
|
||||
const std::vector<float> g_Floats = { 0.0f, 1.0f, -1.0f, 0.4f, 0.8f, 3.5f };
|
||||
const std::vector<std::u16string> g_WStrings = { u"", u"a", u"camshake-bridge", u"été ☃", std::u16string(300, u'x') };
|
||||
const std::vector<std::string> g_Strings = { "", "a", "{9a24f1fa-3177-4745-a2df-fbd996d6e1e3}", std::string(300, 'y') };
|
||||
const std::vector<LWOOBJID> g_Ids = { LWOOBJID_EMPTY, 0x1000000000000001LL, -1 };
|
||||
|
||||
PacketBytes Payload(const GameMessages::NetGameMsg& msg) {
|
||||
RakNet::BitStream bitStream;
|
||||
msg.Serialize(bitStream);
|
||||
return FromBitStream(bitStream);
|
||||
}
|
||||
|
||||
// Serializes msg, then reads it with the legacy read sequence; both must consume exactly the same bits.
|
||||
template<typename Result>
|
||||
Result ReadWithLegacy(const GameMessages::NetGameMsg& msg, const std::function<Result(RakNet::BitStream&)>& read) {
|
||||
RakNet::BitStream wire;
|
||||
msg.Serialize(wire);
|
||||
RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false);
|
||||
auto result = read(legacyStream);
|
||||
EXPECT_EQ(legacyStream.GetReadOffset(), wire.GetNumberOfBitsUsed());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Round trip for messages carrying AMF arrays: the associative part is an unordered map, so a re-serialized
|
||||
// copy may list the keys in another order. Checks every bit is consumed and returns the copy.
|
||||
template<typename T>
|
||||
T AmfRoundTrip(const T& msg) {
|
||||
RakNet::BitStream bitStream;
|
||||
msg.Serialize(bitStream);
|
||||
T copy;
|
||||
EXPECT_TRUE(copy.Deserialize(bitStream));
|
||||
EXPECT_EQ(bitStream.GetNumberOfUnreadBits(), 0);
|
||||
RakNet::BitStream again;
|
||||
copy.Serialize(again);
|
||||
EXPECT_EQ(again.GetNumberOfBitsUsed(), bitStream.GetNumberOfBitsUsed());
|
||||
return copy;
|
||||
}
|
||||
|
||||
// A few differently shaped AMF argument arrays (built fresh each time: AMFArrayValue is move only).
|
||||
AMFArrayValue MakeArgs(const int variant) {
|
||||
AMFArrayValue args;
|
||||
if (variant == 1) {
|
||||
args.Insert("visible", true);
|
||||
args.Insert("text", "hello");
|
||||
} else if (variant == 2) {
|
||||
args.Insert("state", "Lobby");
|
||||
args.Insert<int32_t>("amount", -5);
|
||||
args.Insert("ratio", 0.25);
|
||||
auto* nested = args.InsertArray("nested");
|
||||
nested->Push("one");
|
||||
nested->Push(false);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
class EffectsMessagesTests : public GameDependenciesTest {
|
||||
protected:
|
||||
void SetUp() override { SetUpDependencies(); }
|
||||
void TearDown() override { TearDownDependencies(); }
|
||||
};
|
||||
|
||||
TEST_F(EffectsMessagesTests, PlayAnimationMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto& animation : g_WStrings) {
|
||||
for (const auto priority : g_Floats) {
|
||||
for (const auto scale : g_Floats) {
|
||||
GameMessages::PlayAnimation msg(target, animation);
|
||||
msg.fPriority = priority;
|
||||
msg.fScale = scale;
|
||||
ExpectSameAsLegacy([&](const SystemAddress&) { LegacyGameMessages::SendPlayAnimation(&entity, animation, priority, scale); }, msg, SendMode::Broadcast);
|
||||
if (animation == GeneralUtils::ASCIIToUTF16(GeneralUtils::UTF16ToWTF8(animation))) {
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.animationID, animation);
|
||||
EXPECT_EQ(copy.fPriority, priority);
|
||||
EXPECT_EQ(copy.fScale, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, PlayNDAudioEmitterMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto& guid : g_Strings) {
|
||||
const GameMessages::PlayNDAudioEmitter msg(target, guid);
|
||||
// The legacy function always broadcast, whatever address it was given.
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendPlayNDAudioEmitter(&entity, a, guid); }, msg, SendMode::Broadcast);
|
||||
EXPECT_EQ(RoundTrip(msg).NDAudioEventGUID, guid);
|
||||
}
|
||||
}
|
||||
GameMessages::PlayNDAudioEmitter full;
|
||||
full.NDAudioCallbackMessageData = 5;
|
||||
full.NDAudioEmitterID = 7;
|
||||
full.NDAudioEventGUID = "guid";
|
||||
full.NDAudioMetaEventName = "meta";
|
||||
full.result = true;
|
||||
full.targetObjectIDForNDAudioCallbackMessages = 9;
|
||||
const auto copy = RoundTrip(full);
|
||||
EXPECT_EQ(copy.NDAudioCallbackMessageData, 5);
|
||||
EXPECT_EQ(copy.NDAudioEmitterID, 7);
|
||||
EXPECT_EQ(copy.NDAudioMetaEventName, "meta");
|
||||
EXPECT_EQ(copy.targetObjectIDForNDAudioCallbackMessages, 9);
|
||||
ExpectTruncatedFails(full);
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, PlayEmbeddedEffectMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto& effectName : g_WStrings) {
|
||||
for (const auto from : g_Ids) {
|
||||
for (const auto radius : g_Floats) {
|
||||
GameMessages::PlayEmbeddedEffectOnAllClientsNearObject msg;
|
||||
msg.target = target;
|
||||
msg.effectName = effectName;
|
||||
msg.fromObjectID = from;
|
||||
msg.radius = radius;
|
||||
ExpectSameAsLegacy([&](const SystemAddress&) { LegacyGameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(&entity, effectName, from, radius); }, msg, SendMode::Broadcast);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.effectName, effectName);
|
||||
EXPECT_EQ(copy.fromObjectID, from);
|
||||
EXPECT_EQ(copy.radius, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, PlayFXEffectMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto effectID : g_Ints) {
|
||||
for (const auto& effectType : g_WStrings) {
|
||||
for (const auto& name : g_Strings) {
|
||||
for (const auto secondary : g_Ids) {
|
||||
for (const auto priority : { 1.0f, 0.4f, 1.07f }) {
|
||||
for (const auto scale : { 1.0f, 2.0f }) {
|
||||
for (const bool serialize : { false, true }) {
|
||||
GameMessages::PlayFXEffect msg(target, effectID, effectType, name);
|
||||
msg.secondary = secondary;
|
||||
msg.priority = priority;
|
||||
msg.scale = scale;
|
||||
msg.serialize = serialize;
|
||||
ExpectSameAsLegacy([&](const SystemAddress&) { LegacyGameMessages::SendPlayFXEffect(target, effectID, effectType, name, secondary, priority, scale, serialize); }, msg, SendMode::Broadcast);
|
||||
// The Entity* overload wrote the same thing.
|
||||
ExpectSameAsLegacy([&](const SystemAddress&) { LegacyGameMessages::SendPlayFXEffect(&entity, effectID, effectType, name, secondary, priority, scale, serialize); }, msg, SendMode::Broadcast);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.effectID, effectID);
|
||||
EXPECT_EQ(copy.effectType, effectType);
|
||||
EXPECT_EQ(copy.name, name);
|
||||
EXPECT_EQ(copy.secondary, secondary);
|
||||
EXPECT_EQ(copy.priority, priority);
|
||||
EXPECT_EQ(copy.scale, scale);
|
||||
EXPECT_EQ(copy.serialize, serialize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, StopFXEffectMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const bool killImmediate : { false, true }) {
|
||||
for (const auto& name : g_Strings) {
|
||||
const GameMessages::StopFXEffect msg(target, killImmediate, name);
|
||||
ExpectSameAsLegacy([&](const SystemAddress&) { LegacyGameMessages::SendStopFXEffect(&entity, killImmediate, name); }, msg, SendMode::Broadcast);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.killImmediate, killImmediate);
|
||||
EXPECT_EQ(copy.name, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, BroadcastTextToChatboxMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto& attrs : g_WStrings) {
|
||||
for (const auto& text : g_WStrings) {
|
||||
GameMessages::BroadcastTextToChatbox msg;
|
||||
msg.target = target;
|
||||
msg.attrs = attrs;
|
||||
msg.wsText = text;
|
||||
// The legacy function always broadcast.
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendBroadcastTextToChatbox(&entity, a, attrs, text); }, msg, SendMode::Broadcast);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.attrs, attrs);
|
||||
EXPECT_EQ(copy.wsText, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, AmbientSoundsMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto& guid : g_Strings) {
|
||||
for (const bool result : { false, true }) {
|
||||
GameMessages::Play2DAmbientSound play;
|
||||
play.target = target;
|
||||
play.audioGUID = guid;
|
||||
play.result = result;
|
||||
// Sent to the entity's own address (the test entity has none, so both send nothing to a client).
|
||||
const auto legacyPlay = Capture([&] { LegacyGameMessages::SendPlay2DAmbientSound(&entity, guid, result); });
|
||||
const auto ourPlay = Capture([&] { play.SendToClient(entity.GetSystemAddress()); });
|
||||
ASSERT_EQ(legacyPlay.size(), 1);
|
||||
ASSERT_EQ(ourPlay.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacyPlay[0]), FromCapture(ourPlay[0]));
|
||||
EXPECT_EQ(legacyPlay[0].sysAddr, ourPlay[0].sysAddr);
|
||||
EXPECT_EQ(legacyPlay[0].broadcast, ourPlay[0].broadcast);
|
||||
const auto playCopy = RoundTrip(play);
|
||||
EXPECT_EQ(playCopy.audioGUID, guid);
|
||||
EXPECT_EQ(playCopy.result, result);
|
||||
|
||||
for (const bool force : { false, true }) {
|
||||
GameMessages::Stop2DAmbientSound stop;
|
||||
stop.target = target;
|
||||
stop.force = force;
|
||||
stop.audioGUID = guid;
|
||||
stop.result = result;
|
||||
const auto legacyStop = Capture([&] { LegacyGameMessages::SendStop2DAmbientSound(&entity, force, guid, result); });
|
||||
const auto ourStop = Capture([&] { stop.SendToClient(entity.GetSystemAddress()); });
|
||||
ASSERT_EQ(legacyStop.size(), 1);
|
||||
ASSERT_EQ(ourStop.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacyStop[0]), FromCapture(ourStop[0]));
|
||||
EXPECT_EQ(legacyStop[0].sysAddr, ourStop[0].sysAddr);
|
||||
const auto stopCopy = RoundTrip(stop);
|
||||
EXPECT_EQ(stopCopy.force, force);
|
||||
EXPECT_EQ(stopCopy.audioGUID, guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, UIMessagesMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const int variant : { 0, 1, 2 }) {
|
||||
for (const auto& name : g_Strings) {
|
||||
GameMessages::UIMessageServerToSingleClient single;
|
||||
single.target = target;
|
||||
single.args = MakeArgs(variant);
|
||||
single.strMessageName = name;
|
||||
auto legacyArgs = MakeArgs(variant);
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendUIMessageServerToSingleClient(&entity, a, name, legacyArgs); }, single, SendMode::SendToClient);
|
||||
const auto singleCopy = AmfRoundTrip(single);
|
||||
EXPECT_EQ(singleCopy.strMessageName, name);
|
||||
EXPECT_EQ(singleCopy.args.GetAssociative().size(), single.args.GetAssociative().size());
|
||||
|
||||
// The "single client by address" overload sent UIMessageServerToAllClients with an empty target.
|
||||
GameMessages::UIMessageServerToAllClients all;
|
||||
all.args = MakeArgs(variant);
|
||||
all.strMessageName = name;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendUIMessageServerToSingleClient(name, legacyArgs, a); }, all, SendMode::SendToClient);
|
||||
// And the broadcast one.
|
||||
ExpectSameAsLegacy([&](const SystemAddress&) { LegacyGameMessages::SendUIMessageServerToAllClients(name, legacyArgs); }, all, SendMode::Broadcast);
|
||||
const auto allCopy = AmfRoundTrip(all);
|
||||
EXPECT_EQ(allCopy.strMessageName, name);
|
||||
EXPECT_EQ(allCopy.args.GetAssociative().size(), all.args.GetAssociative().size());
|
||||
EXPECT_EQ(allCopy.args.GetDense().size(), all.args.GetDense().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, StartCelebrationEffectMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto celebrationID : g_Ints) {
|
||||
GameMessages::StartCelebrationEffect msg;
|
||||
msg.target = target;
|
||||
msg.celebrationID = celebrationID;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendStartCelebrationEffect(&entity, a, celebrationID); }, msg, SendMode::SendToClient);
|
||||
EXPECT_EQ(RoundTrip(msg).celebrationID, celebrationID);
|
||||
}
|
||||
}
|
||||
GameMessages::StartCelebrationEffect full;
|
||||
full.animation = u"anim";
|
||||
full.backgroundObject = 1;
|
||||
full.cameraPathLOT = 2;
|
||||
full.celeLeadIn = 3.0f;
|
||||
full.celeLeadOut = 4.0f;
|
||||
full.celebrationID = 22;
|
||||
full.duration = 5.0f;
|
||||
full.iconID = 6;
|
||||
full.mainText = u"main";
|
||||
full.mixerProgram = "mixer";
|
||||
full.musicCue = "cue";
|
||||
full.pathNodeName = "node";
|
||||
full.soundGUID = "sound";
|
||||
full.subText = u"sub";
|
||||
const auto copy = RoundTrip(full);
|
||||
EXPECT_EQ(copy.backgroundObject, 1);
|
||||
EXPECT_EQ(copy.cameraPathLOT, 2);
|
||||
EXPECT_EQ(copy.celeLeadOut, 4.0f);
|
||||
EXPECT_EQ(copy.subText, u"sub");
|
||||
ExpectTruncatedFails(full);
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, MessageBoxAndChatBubbleMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
for (const bool show : { false, true }) {
|
||||
for (const auto callback : g_Ids) {
|
||||
for (const auto& identifier : g_WStrings) {
|
||||
for (const auto imageID : g_Ints) {
|
||||
GameMessages::DisplayMessageBox msg;
|
||||
msg.target = target;
|
||||
msg.bShow = show;
|
||||
msg.callbackClient = callback;
|
||||
msg.identifier = identifier;
|
||||
msg.imageID = imageID;
|
||||
msg.text = identifier + u"!";
|
||||
msg.userData = u"data";
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendDisplayMessageBox(target, show, callback, identifier, imageID, identifier + u"!", u"data", a); }, msg);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.bShow, show);
|
||||
EXPECT_EQ(copy.callbackClient, callback);
|
||||
EXPECT_EQ(copy.identifier, identifier);
|
||||
EXPECT_EQ(copy.imageID, imageID);
|
||||
EXPECT_EQ(copy.text, identifier + u"!");
|
||||
EXPECT_EQ(copy.userData, u"data");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& text : g_WStrings) {
|
||||
GameMessages::DisplayChatBubble bubble;
|
||||
bubble.target = target;
|
||||
bubble.wsText = text;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendDisplayChatBubble(target, text, a); }, bubble);
|
||||
EXPECT_EQ(RoundTrip(bubble).wsText, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, ChangeIdleFlagsMatchesLegacy) {
|
||||
const auto flags = { eAnimationFlags::IDLE_NONE, eAnimationFlags::IDLE_COMBAT, static_cast<eAnimationFlags>(0xFFFFFFFF) };
|
||||
for (const auto target : g_Targets) {
|
||||
for (const auto on : flags) {
|
||||
for (const auto off : flags) {
|
||||
GameMessages::ChangeIdleFlags msg;
|
||||
msg.target = target;
|
||||
msg.flagsOn = on;
|
||||
msg.flagsOff = off;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendChangeIdleFlags(target, on, off, a); }, msg, SendMode::Broadcast);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.flagsOn, on);
|
||||
EXPECT_EQ(copy.flagsOff, off);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, BillboardsMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
GameMessages::SetNameBillboardState state;
|
||||
state.target = target;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendSetNamebillboardState(a, target); }, state);
|
||||
GameMessages::ShowBillboardInteractIcon icon;
|
||||
icon.target = target;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendShowBillboardInteractIcon(a, target); }, icon);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, CinematicsMatchLegacy) {
|
||||
const auto endBehaviors = { eEndBehavior::RETURN, eEndBehavior::WAIT };
|
||||
for (const auto target : g_Targets) {
|
||||
for (const auto& path : g_WStrings) {
|
||||
for (const auto leadIn : { -1.0f, 0.0f, 2.0f }) {
|
||||
for (const auto endBehavior : endBehaviors) {
|
||||
for (uint32_t bools = 0; bools < (1u << 10); bools += 37) {
|
||||
const auto bit = [&](int i) { return ((bools >> i) & 1) != 0; };
|
||||
GameMessages::PlayCinematic msg;
|
||||
msg.target = target;
|
||||
msg.pathName = path;
|
||||
msg.allowGhostUpdates = bit(0);
|
||||
msg.bCloseMultiInteract = bit(1);
|
||||
msg.bSendServerNotify = bit(2);
|
||||
msg.bUseControlledObjectForAudioListener = bit(3);
|
||||
msg.endBehavior = endBehavior;
|
||||
msg.hidePlayerDuringCine = bit(4);
|
||||
msg.leadIn = leadIn;
|
||||
msg.leavePlayerLockedWhenFinished = bit(5);
|
||||
msg.lockPlayer = bit(6);
|
||||
msg.result = bit(7);
|
||||
msg.skipIfSamePath = bit(8);
|
||||
msg.startTimeAdvance = bit(9) ? 1.5f : 0.0f;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) {
|
||||
LegacyGameMessages::SendPlayCinematic(target, path, a, bit(0), bit(1), bit(2), bit(3), endBehavior, bit(4), leadIn, bit(5), bit(6), bit(7), bit(8), bit(9) ? 1.5f : 0.0f);
|
||||
}, msg);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.pathName, path);
|
||||
EXPECT_EQ(copy.endBehavior, endBehavior);
|
||||
EXPECT_EQ(copy.leadIn, leadIn);
|
||||
EXPECT_EQ(copy.lockPlayer, bit(6));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto leadOut : { -1.0f, 1.0f }) {
|
||||
for (const bool locked : { false, true }) {
|
||||
GameMessages::EndCinematic end;
|
||||
end.target = target;
|
||||
end.pathName = path;
|
||||
end.leadOut = leadOut;
|
||||
end.leavePlayerLocked = locked;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendEndCinematic(target, path, a, leadOut, locked); }, end);
|
||||
const auto copy = RoundTrip(end);
|
||||
EXPECT_EQ(copy.leadOut, leadOut);
|
||||
EXPECT_EQ(copy.leavePlayerLocked, locked);
|
||||
EXPECT_EQ(copy.pathName, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, SlashCommandTextFeedbackMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto& text : g_WStrings) {
|
||||
const GameMessages::SlashCommandTextFeedback msg(target, text);
|
||||
const auto legacy = Capture([&] { LegacyGameMessages::SendSlashCommandFeedbackText(&entity, text); });
|
||||
const auto ours = Capture([&] { msg.SendToClient(entity.GetSystemAddress()); });
|
||||
ASSERT_EQ(legacy.size(), 1);
|
||||
ASSERT_EQ(ours.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy[0]), FromCapture(ours[0]));
|
||||
EXPECT_EQ(legacy[0].sysAddr, ours[0].sysAddr);
|
||||
EXPECT_EQ(RoundTrip(msg).text, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, EmotesMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto emoteID : g_Ints) {
|
||||
for (const auto emoteTarget : g_Ids) {
|
||||
GameMessages::PlayEmote msg;
|
||||
msg.target = target;
|
||||
msg.emoteID = emoteID;
|
||||
msg.targetID = emoteTarget;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendPlayEmote(target, emoteID, emoteTarget, a); }, msg);
|
||||
const auto legacyRead = ReadWithLegacy<LegacyGameMessages::LegacyPlayEmote>(msg, LegacyGameMessages::ReadPlayEmote);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.emoteID, legacyRead.emoteID);
|
||||
EXPECT_EQ(copy.targetID, legacyRead.targetID);
|
||||
ExpectTruncatedFails(msg);
|
||||
}
|
||||
for (const bool lock : { false, true }) {
|
||||
GameMessages::SetEmoteLockState lockState;
|
||||
lockState.target = target;
|
||||
lockState.bLock = lock;
|
||||
lockState.emoteID = emoteID;
|
||||
const auto legacy = Capture([&] { LegacyGameMessages::SendSetEmoteLockState(&entity, lock, emoteID); });
|
||||
const auto ours = Capture([&] { lockState.SendToClient(entity.GetSystemAddress()); });
|
||||
ASSERT_EQ(legacy.size(), 1);
|
||||
ASSERT_EQ(ours.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy[0]), FromCapture(ours[0]));
|
||||
const auto copy = RoundTrip(lockState);
|
||||
EXPECT_EQ(copy.bLock, lock);
|
||||
EXPECT_EQ(copy.emoteID, emoteID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EffectsMessagesTests, InboundReadsLikeLegacy) {
|
||||
for (const auto button : g_Ints) {
|
||||
for (const auto& identifier : g_WStrings) {
|
||||
GameMessages::MessageBoxRespond box;
|
||||
box.iButton = button;
|
||||
box.identifier = identifier;
|
||||
box.userData = u"user";
|
||||
const auto legacyBox = ReadWithLegacy<LegacyGameMessages::LegacyMessageBoxResponse>(box, LegacyGameMessages::ReadMessageBoxResponse);
|
||||
const auto boxCopy = RoundTrip(box);
|
||||
EXPECT_TRUE(legacyBox.ok);
|
||||
EXPECT_EQ(boxCopy.iButton, legacyBox.iButton);
|
||||
EXPECT_EQ(boxCopy.identifier, legacyBox.identifier);
|
||||
EXPECT_EQ(boxCopy.userData, legacyBox.userData);
|
||||
ExpectTruncatedFails(box);
|
||||
|
||||
GameMessages::ChoiceBoxRespond choice;
|
||||
choice.buttonIdentifier = u"btn";
|
||||
choice.iButton = button;
|
||||
choice.identifier = identifier;
|
||||
const auto legacyChoice = ReadWithLegacy<LegacyGameMessages::LegacyChoiceBoxRespond>(choice, LegacyGameMessages::ReadChoiceBoxRespond);
|
||||
const auto choiceCopy = RoundTrip(choice);
|
||||
EXPECT_TRUE(legacyChoice.ok);
|
||||
EXPECT_EQ(choiceCopy.buttonIdentifier, legacyChoice.buttonIdentifier);
|
||||
EXPECT_EQ(choiceCopy.iButton, legacyChoice.iButton);
|
||||
EXPECT_EQ(choiceCopy.identifier, legacyChoice.identifier);
|
||||
ExpectTruncatedFails(choice);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto event : { eCinematicEvent::STARTED, eCinematicEvent::WAYPOINT, eCinematicEvent::ENDED }) {
|
||||
for (const auto time : { -1.0f, 0.0f, 2.5f }) {
|
||||
for (const auto waypoint : { -1, 0, 3 }) {
|
||||
for (const auto& path : g_WStrings) {
|
||||
GameMessages::CinematicUpdate update;
|
||||
update.event = event;
|
||||
update.overallTime = time;
|
||||
update.pathName = path;
|
||||
update.pathTime = time * 2;
|
||||
update.waypoint = waypoint;
|
||||
const auto legacy = ReadWithLegacy<LegacyGameMessages::LegacyCinematicUpdate>(update, LegacyGameMessages::ReadCinematicUpdate);
|
||||
const auto copy = RoundTrip(update);
|
||||
EXPECT_EQ(copy.event, legacy.event);
|
||||
EXPECT_EQ(copy.overallTime, legacy.overallTime);
|
||||
EXPECT_EQ(copy.pathName, legacy.pathName);
|
||||
EXPECT_EQ(copy.pathTime, legacy.pathTime);
|
||||
EXPECT_EQ(copy.waypoint, legacy.waypoint);
|
||||
ExpectTruncatedFails(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A length above MAX_MESSAGE_LENGTH is rejected, as the old handler did.
|
||||
RakNet::BitStream tooLong;
|
||||
tooLong.Write<int32_t>(0);
|
||||
tooLong.Write<uint32_t>(MAX_MESSAGE_LENGTH + 1);
|
||||
GameMessages::MessageBoxRespond box;
|
||||
EXPECT_FALSE(box.Deserialize(tooLong));
|
||||
}
|
||||
|
||||
// Independent of the legacy code: hand computed payloads (RakNet writes MSB first).
|
||||
TEST_F(EffectsMessagesTests, GoldenBytes) {
|
||||
// effectID 7 (flag 1), "a" (u32 1, 0x61 0x00), scale default (0), "b" (u32 1, 0x62), priority default (0),
|
||||
// secondary default (0), serialize true (1).
|
||||
GameMessages::PlayFXEffect fx(0, 7, u"a", "b");
|
||||
EXPECT_PACKET_EQ(FromHex("83 80 00 00 00 80 00 00 30 80 00 40 00 00 18 88", 125), Payload(fx));
|
||||
|
||||
GameMessages::StopFXEffect stop(0, true, "ab");
|
||||
EXPECT_PACKET_EQ(FromHex("81 00 00 00 30 b1 00", 49), Payload(stop));
|
||||
|
||||
GameMessages::PlayAnimation animation(0, u"ab");
|
||||
// u32 2, "a\0b\0", then expect(1) immediate(0) complete(0) priority(0) scale(0)
|
||||
EXPECT_PACKET_EQ(FromHex("02 00 00 00 61 00 62 00 80", 69), Payload(animation));
|
||||
|
||||
GameMessages::ChangeIdleFlags idle;
|
||||
idle.flagsOn = static_cast<eAnimationFlags>(1);
|
||||
// off default (0), on flag (1) then 01 00 00 00
|
||||
EXPECT_PACKET_EQ(FromHex("40 40 00 00 00", 34), Payload(idle));
|
||||
|
||||
GameMessages::EndCinematic end;
|
||||
EXPECT_PACKET_EQ(FromHex("00 00 00 00 00", 34), Payload(end));
|
||||
}
|
||||
@@ -33,8 +33,10 @@ namespace GameMessageTestUtils {
|
||||
}
|
||||
|
||||
// How the struct is sent in a comparison: Send (UNASSIGNED broadcasts; legacy functions that did
|
||||
// "if (UNASSIGNED) SEND_PACKET_BROADCAST; SEND_PACKET;") or SendToClient (legacy functions with only SEND_PACKET).
|
||||
enum class SendMode { Send, SendToClient };
|
||||
// "if (UNASSIGNED) SEND_PACKET_BROADCAST; SEND_PACKET;"), SendToClient (legacy functions with only SEND_PACKET)
|
||||
// or Broadcast (legacy functions with only SEND_PACKET_BROADCAST, whatever address they were given: the struct is
|
||||
// sent with Send(UNASSIGNED_SYSTEM_ADDRESS)).
|
||||
enum class SendMode { Send, SendToClient, Broadcast };
|
||||
|
||||
// Sends the same message through the frozen legacy function and through the struct, to one client and to
|
||||
// UNASSIGNED_SYSTEM_ADDRESS, and requires identical bytes and the same effective destination.
|
||||
@@ -43,7 +45,11 @@ namespace GameMessageTestUtils {
|
||||
for (const auto& address : g_Addresses) {
|
||||
SCOPED_TRACE(address == UNASSIGNED_SYSTEM_ADDRESS ? "unassigned address" : "single client");
|
||||
const auto legacyPackets = Capture([&] { legacySend(address); });
|
||||
const auto newPackets = Capture([&] { mode == SendMode::Send ? msg.Send(address) : msg.SendToClient(address); });
|
||||
const auto newPackets = Capture([&] {
|
||||
if (mode == SendMode::Send) msg.Send(address);
|
||||
else if (mode == SendMode::SendToClient) msg.SendToClient(address);
|
||||
else msg.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
});
|
||||
|
||||
ASSERT_FALSE(legacyPackets.empty());
|
||||
ASSERT_EQ(newPackets.size(), 1);
|
||||
|
||||
@@ -0,0 +1,607 @@
|
||||
#ifndef EFFECTSMESSAGESLEGACY_H
|
||||
#define EFFECTSMESSAGESLEGACY_H
|
||||
|
||||
// FROZEN ORACLE - DO NOT EDIT.
|
||||
// Verbatim copies of the hand written GameMessages functions that EffectsMessages.h replaced
|
||||
// (dGame/dGameMessages/GameMessages.cpp, branched from origin/main 129199e4). Only the namespace changed.
|
||||
// The Read* functions are the read sequences of the replaced GameMessages::Handle* functions.
|
||||
|
||||
#include "Amf3.h"
|
||||
#include "AmfSerialize.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "dServer.h"
|
||||
#include "eAnimationFlags.h"
|
||||
#include "eCinematicEvent.h"
|
||||
#include "eEndBehavior.h"
|
||||
#include "Entity.h"
|
||||
#include "Game.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "Logger.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include "ServiceType.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace LegacyGameMessages {
|
||||
// Declared in GameMessages.h in the original; the Entity* overload forwards to it.
|
||||
inline void SendPlayFXEffect(const LWOOBJID& entity, int32_t effectID, const std::u16string& effectType, const std::string& name, LWOOBJID secondary, float priority, float scale, bool serialize);
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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);
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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
|
||||
}
|
||||
|
||||
inline void 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
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
inline void 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;
|
||||
}
|
||||
|
||||
struct LegacyMessageBoxResponse { bool ok{}; int32_t iButton{}; std::u16string identifier; std::u16string userData; };
|
||||
inline LegacyMessageBoxResponse ReadMessageBoxResponse(RakNet::BitStream& inStream) {
|
||||
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);
|
||||
}
|
||||
return { true, iButton, identifier, userData };
|
||||
}
|
||||
|
||||
struct LegacyChoiceBoxRespond { bool ok{}; int32_t iButton{}; std::u16string buttonIdentifier; std::u16string identifier; };
|
||||
inline LegacyChoiceBoxRespond ReadChoiceBoxRespond(RakNet::BitStream& inStream) {
|
||||
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);
|
||||
}
|
||||
return { true, iButton, buttonIdentifier, identifier };
|
||||
}
|
||||
|
||||
struct LegacyCinematicUpdate { eCinematicEvent event; float_t overallTime; std::u16string pathName; float_t pathTime; int32_t waypoint; };
|
||||
inline LegacyCinematicUpdate ReadCinematicUpdate(RakNet::BitStream& inStream) {
|
||||
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);
|
||||
}
|
||||
return { event, overallTime, pathName, pathTime, waypoint };
|
||||
}
|
||||
|
||||
struct LegacyPlayEmote { int32_t emoteID; LWOOBJID targetID; };
|
||||
inline LegacyPlayEmote ReadPlayEmote(RakNet::BitStream& inStream) {
|
||||
int32_t emoteID;
|
||||
LWOOBJID targetID;
|
||||
|
||||
inStream.Read(emoteID);
|
||||
inStream.Read(targetID);
|
||||
return { emoteID, targetID };
|
||||
}
|
||||
}
|
||||
|
||||
#endif // EFFECTSMESSAGESLEGACY_H
|
||||
Reference in New Issue
Block a user