Files
DarkflameServer/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp
Aaron Kimbrell b466c08ee2 Merge origin/main into experimental: behaviors, raw terrain scene lookups, property model fixes, player count
Main's model behaviors (every rotation, DoDamage, OnTimer, OnStartup, MoveBackToStart, moves along the model's
local axes), raw terrain reading with scene lookups and scene tools, property model and brick by brick fixes and the
chat server's player count fix, merged onto this branch's structures:

- game messages stay structs; the angular velocity messages join them and the handler sets a message's target
- strips own their speed again and the block names, moves and rotations stay in the shared action table
  (PropertyBehaviorActions, which the dashboard's behavior player reads; it now lists rotations too)
- the zone keeps reading its file through ZoneFile and takes the boundaries from it, then loads the raw terrain as main
  does; the old dNavigation terrain reader is gone
- the show all response stays a struct and logs a player count mismatch as main does
- the property management component tells models who sent a chat message

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 10:57:45 -06:00

312 lines
12 KiB
C++

#include "Action.h"
#include "Amf3.h"
#include "AMFDeserialize.h"
#include "ClientPackets.h"
#include "GameMessages.h"
#include "GameDependencies.h"
#include <gtest/gtest.h>
// Message includes
#include "AddActionMessage.h"
#include "AddStripMessage.h"
#include "AddMessage.h"
#include "MigrateActionsMessage.h"
#include "MoveToInventoryMessage.h"
#include "MergeStripsMessage.h"
#include "RearrangeStripMessage.h"
#include "RemoveActionsMessage.h"
#include "RemoveStripMessage.h"
#include "RenameMessage.h"
#include "SplitStripMessage.h"
#include "UpdateActionMessage.h"
#include "UpdateStripUiMessage.h"
class GameMessageTests: public GameDependenciesTest {
protected:
void SetUp() override {
SetUpDependencies();
}
void TearDown() override {
TearDownDependencies();
}
std::string ReadFromFile(std::string filename) {
std::ifstream file(filename, std::ios::binary);
std::string readFile;
while (file.good()) {
char readCharacter = file.get();
readFile.push_back(readCharacter);
}
return readFile;
}
std::unique_ptr<AMFArrayValue> ReadArrayFromBitStream(RakNet::BitStream& inStream) {
AMFDeserialize des;
AMFBaseValue* readArray = des.Read(inStream).release();
EXPECT_EQ(readArray->GetValueType(), eAmf::Array);
return std::unique_ptr<AMFArrayValue>{ static_cast<AMFArrayValue*>(readArray) };
}
};
/**
* @brief Tests that the serialization struct BlueprintLoadItemResponse is serialized correctly
*
*/
TEST_F(GameMessageTests, SendBlueprintLoadItemResponse) {
ClientPackets::BlueprintLoadItemResponse response;
response.success = true;
response.itemId = 515;
response.destItemId = 990;
response.Send(UNASSIGNED_SYSTEM_ADDRESS);
auto* bitStream = static_cast<dServerMock*>(Game::server)->GetMostRecentBitStream();
ASSERT_NE(bitStream, nullptr);
ASSERT_EQ(bitStream->GetNumberOfUnreadBits(), 200);
// First read in the packets' header
uint8_t rakNetPacketId{};
uint16_t remoteServiceType{};
uint32_t packetId{};
uint8_t always0{};
bitStream->Read(rakNetPacketId);
bitStream->Read(remoteServiceType);
bitStream->Read(packetId);
bitStream->Read(always0);
ASSERT_EQ(rakNetPacketId, 0x53);
ASSERT_EQ(remoteServiceType, 0x5);
ASSERT_EQ(packetId, 0x17);
ASSERT_EQ(always0, 0x00);
// Next read in packet data
uint8_t bSuccess{}; // unsigned bool
LWOOBJID previousId{};
LWOOBJID newId{};
bitStream->Read(bSuccess);
bitStream->Read(previousId);
bitStream->Read(newId);
ASSERT_EQ(bSuccess, static_cast<uint8_t>(true));
ASSERT_EQ(previousId, 515);
ASSERT_EQ(newId, 990);
ASSERT_EQ(bitStream->GetNumberOfUnreadBits(), 0);
}
/**
* @brief PlaceModelResponse is laid out as the client's PlaceModelResponse::Deserialize (0x00dc0170) reads it: every
* field optional, the rotation a w, x, y, z quaternion
*/
TEST_F(GameMessageTests, SendPlaceModelResponse) {
const NiQuaternion rotation = NiQuaternion(0.5f, 0.1f, 0.2f, 0.3f); // w, x, y, z
GameMessages::SendPlaceModelResponse(77, UNASSIGNED_SYSTEM_ADDRESS, NiPoint3(1.0f, 2.0f, 3.0f), LWOOBJID_EMPTY, 14, rotation);
auto* bitStream = static_cast<dServerMock*>(Game::server)->GetMostRecentBitStream();
ASSERT_NE(bitStream, nullptr);
bitStream->IgnoreBits(64); // packet header
LWOOBJID objectId{};
MessageType::Game messageId{};
bitStream->Read(objectId);
bitStream->Read(messageId);
EXPECT_EQ(objectId, 77);
EXPECT_EQ(messageId, MessageType::Game::PLACE_MODEL_RESPONSE);
bool hasPosition{}, hasPlaque{}, hasResponse{}, hasRotation{};
NiPoint3 position{};
int32_t response{};
float w{}, x{}, y{}, z{};
bitStream->Read(hasPosition);
bitStream->Read(position);
bitStream->Read(hasPlaque);
bitStream->Read(hasResponse);
bitStream->Read(response);
bitStream->Read(hasRotation);
bitStream->Read(w);
bitStream->Read(x);
bitStream->Read(y);
bitStream->Read(z);
EXPECT_TRUE(hasPosition);
EXPECT_EQ(position, NiPoint3(1.0f, 2.0f, 3.0f));
EXPECT_FALSE(hasPlaque);
EXPECT_TRUE(hasResponse);
EXPECT_EQ(response, 14);
EXPECT_TRUE(hasRotation);
EXPECT_FLOAT_EQ(w, rotation.w);
EXPECT_FLOAT_EQ(x, rotation.x);
EXPECT_FLOAT_EQ(y, rotation.y);
EXPECT_FLOAT_EQ(z, rotation.z);
EXPECT_EQ(bitStream->GetNumberOfUnreadBits(), 0);
}
TEST_F(GameMessageTests, ControlBehaviorAddStrip) {
auto data = ReadFromFile("addStrip");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
AddStripMessage addStrip(*arr);
ASSERT_FLOAT_EQ(addStrip.GetPosition().GetX(), 50.65);
ASSERT_FLOAT_EQ(addStrip.GetPosition().GetY(), 178.05);
ASSERT_EQ(addStrip.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(addStrip.GetActionContext().GetStateId()), 0);
ASSERT_EQ(addStrip.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
ASSERT_EQ(addStrip.GetActionsToAdd().front().GetType(), "DropImagination");
ASSERT_EQ(addStrip.GetActionsToAdd().front().GetValueParameterName(), "Amount");
ASSERT_EQ(addStrip.GetActionsToAdd().front().GetValueParameterString(), "");
ASSERT_FLOAT_EQ(addStrip.GetActionsToAdd().front().GetValueParameterDouble(), 1.0);
}
TEST_F(GameMessageTests, ControlBehaviorRemoveStrip) {
auto data = ReadFromFile("removeStrip");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
RemoveStripMessage removeStrip(*arr);
ASSERT_EQ(static_cast<int32_t>(removeStrip.GetActionContext().GetStripId()), 1);
ASSERT_EQ(static_cast<int32_t>(removeStrip.GetActionContext().GetStateId()), 0);
ASSERT_EQ(removeStrip.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorMergeStrips) {
auto data = ReadFromFile("mergeStrips");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
MergeStripsMessage mergeStrips(*arr);
ASSERT_EQ(mergeStrips.GetSourceActionContext().GetStripId(), 2);
ASSERT_EQ(mergeStrips.GetDestinationActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(mergeStrips.GetSourceActionContext().GetStateId()), 0);
ASSERT_EQ(static_cast<uint32_t>(mergeStrips.GetDestinationActionContext().GetStateId()), 0);
ASSERT_EQ(mergeStrips.GetDstActionIndex(), 0);
ASSERT_EQ(mergeStrips.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorSplitStrip) {
auto data = ReadFromFile("splitStrip");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
SplitStripMessage splitStrip(*arr);
ASSERT_EQ(splitStrip.GetBehaviorId(), -1);
ASSERT_FLOAT_EQ(splitStrip.GetPosition().GetX(), 275.65);
ASSERT_FLOAT_EQ(splitStrip.GetPosition().GetY(), 28.7);
ASSERT_EQ(splitStrip.GetSourceActionContext().GetStripId(), 0);
ASSERT_EQ(splitStrip.GetDestinationActionContext().GetStripId(), 2);
ASSERT_EQ(static_cast<uint32_t>(splitStrip.GetSourceActionContext().GetStateId()), 0);
ASSERT_EQ(static_cast<uint32_t>(splitStrip.GetDestinationActionContext().GetStateId()), 0);
ASSERT_EQ(splitStrip.GetSrcActionIndex(), 1);
}
TEST_F(GameMessageTests, ControlBehaviorUpdateStripUI) {
auto data = ReadFromFile("updateStripUI");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
UpdateStripUiMessage updateStripUi(*arr);
ASSERT_FLOAT_EQ(updateStripUi.GetPosition().GetX(), 116.65);
ASSERT_FLOAT_EQ(updateStripUi.GetPosition().GetY(), 35.35);
ASSERT_EQ(updateStripUi.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(updateStripUi.GetActionContext().GetStateId()), 0);
ASSERT_EQ(updateStripUi.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorAddAction) {
auto data = ReadFromFile("addAction");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
AddActionMessage addAction(*arr);
ASSERT_EQ(addAction.GetActionIndex(), 3);
ASSERT_EQ(addAction.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(addAction.GetActionContext().GetStateId()), 0);
ASSERT_EQ(addAction.GetAction().GetType(), "DoDamage");
ASSERT_EQ(addAction.GetAction().GetValueParameterName(), "");
ASSERT_EQ(addAction.GetAction().GetValueParameterString(), "");
ASSERT_EQ(addAction.GetAction().GetValueParameterDouble(), 0.0);
ASSERT_EQ(addAction.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorMigrateActions) {
auto data = ReadFromFile("migrateActions");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
MigrateActionsMessage migrateActions(*arr);
ASSERT_EQ(migrateActions.GetSrcActionIndex(), 1);
ASSERT_EQ(migrateActions.GetDstActionIndex(), 2);
ASSERT_EQ(migrateActions.GetSourceActionContext().GetStripId(), 1);
ASSERT_EQ(migrateActions.GetDestinationActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(migrateActions.GetSourceActionContext().GetStateId()), 0);
ASSERT_EQ(static_cast<uint32_t>(migrateActions.GetDestinationActionContext().GetStateId()), 0);
ASSERT_EQ(migrateActions.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorRearrangeStrip) {
auto data = ReadFromFile("rearrangeStrip");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
RearrangeStripMessage rearrangeStrip(*arr);
ASSERT_EQ(rearrangeStrip.GetSrcActionIndex(), 2);
ASSERT_EQ(rearrangeStrip.GetDstActionIndex(), 1);
ASSERT_EQ(rearrangeStrip.GetActionContext().GetStripId(), 0);
ASSERT_EQ(rearrangeStrip.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
ASSERT_EQ(static_cast<uint32_t>(rearrangeStrip.GetActionContext().GetStateId()), 0);
}
TEST_F(GameMessageTests, ControlBehaviorAdd) {
auto data = ReadFromFile("add");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
AddMessage add(*arr, LWOOBJID_EMPTY);
ASSERT_EQ(add.GetBehaviorId(), 10446);
ASSERT_EQ(add.GetBehaviorIndex(), 0);
}
TEST_F(GameMessageTests, ControlBehaviorRemoveActions) {
auto data = ReadFromFile("removeActions");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
RemoveActionsMessage removeActions(*arr);
ASSERT_EQ(removeActions.GetBehaviorId(), -1);
ASSERT_EQ(removeActions.GetActionIndex(), 1);
ASSERT_EQ(removeActions.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(removeActions.GetActionContext().GetStateId()), 0);
}
TEST_F(GameMessageTests, ControlBehaviorRename) {
auto data = ReadFromFile("rename");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
RenameMessage rename(*arr);
ASSERT_EQ(rename.GetName(), "test");
ASSERT_EQ(rename.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorUpdateAction) {
auto data = ReadFromFile("updateAction");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
UpdateActionMessage updateAction(*arr);
ASSERT_EQ(updateAction.GetAction().GetType(), "FlyDown");
ASSERT_EQ(updateAction.GetAction().GetValueParameterName(), "Distance");
ASSERT_EQ(updateAction.GetAction().GetValueParameterString(), "");
ASSERT_EQ(updateAction.GetAction().GetValueParameterDouble(), 50.0);
ASSERT_EQ(updateAction.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
ASSERT_EQ(updateAction.GetActionIndex(), 1);
ASSERT_EQ(updateAction.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(updateAction.GetActionContext().GetStateId()), 0);
}