mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
fix(quickbuild): a finished quickbuild's EnableRebuild has no duration
Live sent duration 0.0 in all 744 successful EnableRebuild (cancels carry the time spent building, which DLU already matches); DLU sent the reset time. Tests get EntityManager::_addEntity/_removeEntity to make an entity built outside CreateEntity findable, and helpers that read game messages back out of captured packets. Check: finish a quickbuild; the build completes, the celebrate animation plays and the built object stays up for its usual time before resetting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -184,6 +184,10 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityManager::_addEntity(Entity* entity) {
|
||||||
|
m_Entities.insert_or_assign(entity->GetObjectID(), entity);
|
||||||
|
}
|
||||||
|
|
||||||
void EntityManager::DestroyEntity(const LWOOBJID& objectID) {
|
void EntityManager::DestroyEntity(const LWOOBJID& objectID) {
|
||||||
DestroyEntity(GetEntity(objectID));
|
DestroyEntity(GetEntity(objectID));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ public:
|
|||||||
const std::unordered_map<LWOOBJID, Entity*> GetAllEntities() const { return m_Entities; }
|
const std::unordered_map<LWOOBJID, Entity*> GetAllEntities() const { return m_Entities; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Tests only: makes an entity built without CreateEntity (and still owned by the caller) findable with GetEntity.
|
||||||
|
void _addEntity(Entity* entity);
|
||||||
|
void _removeEntity(LWOOBJID objectID) { m_Entities.erase(objectID); }
|
||||||
|
|
||||||
void ConstructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
|
void ConstructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
void DestructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
|
void DestructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
void SerializeEntity(Entity* entity);
|
void SerializeEntity(Entity* entity);
|
||||||
|
|||||||
@@ -473,7 +473,8 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) {
|
|||||||
enableRebuild.bFail = false;
|
enableRebuild.bFail = false;
|
||||||
enableRebuild.bSuccess = true;
|
enableRebuild.bSuccess = true;
|
||||||
enableRebuild.eFailReason = eQuickBuildFailReason::NOT_GIVEN;
|
enableRebuild.eFailReason = eQuickBuildFailReason::NOT_GIVEN;
|
||||||
enableRebuild.fDuration = m_ResetTime;
|
// Live sent no duration on success (744 of 744); cancels carry the time spent building.
|
||||||
|
enableRebuild.fDuration = 0.0f;
|
||||||
enableRebuild.user = user->GetObjectID();
|
enableRebuild.user = user->GetObjectID();
|
||||||
enableRebuild.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
enableRebuild.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
GameMessages::TerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()).Send(UNASSIGNED_SYSTEM_ADDRESS);
|
GameMessages::TerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()).Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ set(DCOMPONENTS_TESTS
|
|||||||
"CharacterSaveFieldsTests.cpp"
|
"CharacterSaveFieldsTests.cpp"
|
||||||
"CollectibleComponentTests.cpp"
|
"CollectibleComponentTests.cpp"
|
||||||
"ReplicaComponentOrderTests.cpp"
|
"ReplicaComponentOrderTests.cpp"
|
||||||
|
"QuickBuildCompleteTests.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the folder name and prepend it to the files above
|
# Get the folder name and prepend it to the files above
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#include "GameDependencies.h"
|
||||||
|
#include "dGameMessagesTests/GameMessageTestUtils.h"
|
||||||
|
|
||||||
|
#include "Character.h"
|
||||||
|
#include "CharacterComponent.h"
|
||||||
|
#include "CDClientDatabase.h"
|
||||||
|
#include "Entity.h"
|
||||||
|
#include "QuickBuildComponent.h"
|
||||||
|
#include "QuickBuildMessages.h"
|
||||||
|
#include "eQuickBuildState.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace GameMessageTestUtils;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// The quick build spawns its activator through the entity manager, which reads the CDClient: give it an empty one.
|
||||||
|
void ConnectEmptyCDClient() {
|
||||||
|
if (CDClientDatabase::isConnected) return;
|
||||||
|
CDClientDatabase::Connect(":memory:");
|
||||||
|
for (const auto* table : {
|
||||||
|
"ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER)",
|
||||||
|
"ObjectSkills (objectTemplate INTEGER, skillID INTEGER, castOnType INTEGER, AICombatWeight INTEGER)",
|
||||||
|
"SkillBehavior (skillID INTEGER, behaviorID INTEGER)",
|
||||||
|
"ItemSets (setID INTEGER, itemIDs TEXT)",
|
||||||
|
}) {
|
||||||
|
CDClientDatabase::ExecuteDML(std::string("CREATE TABLE ") + table + ";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// What a finished quickbuild sends, compared with live (744 successful builds in the live captures).
|
||||||
|
class QuickBuildCompleteTests : public GameDependenciesTest {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
SetUpDependencies();
|
||||||
|
ConnectEmptyCDClient();
|
||||||
|
auto playerInfo = info;
|
||||||
|
playerInfo.lot = 1;
|
||||||
|
player = std::make_unique<Entity>(0x1000000000000001LL, playerInfo);
|
||||||
|
Game::entityManager->_addEntity(player.get()); // the quickbuild looks its builder up there
|
||||||
|
character = std::make_unique<Character>(1, nullptr);
|
||||||
|
player->SetCharacter(character.get());
|
||||||
|
character->SetEntity(player.get());
|
||||||
|
player->AddComponent<CharacterComponent>(-1, character.get(), ClientAddress());
|
||||||
|
|
||||||
|
auto buildInfo = info;
|
||||||
|
buildInfo.lot = 4717;
|
||||||
|
build = std::make_unique<Entity>(0x0102030405060708LL, buildInfo);
|
||||||
|
quickBuild = build->AddComponent<QuickBuildComponent>(-1);
|
||||||
|
quickBuild->SetResetTime(20.0f);
|
||||||
|
quickBuild->SetCompleteTime(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts the build and runs updates until it completes (the first update only sets up the imagination drain).
|
||||||
|
std::vector<CapturedPacket> Build() {
|
||||||
|
quickBuild->OnUse(player.get());
|
||||||
|
return Capture([&] {
|
||||||
|
for (int i = 0; i < 3 && quickBuild->GetState() != eQuickBuildState::COMPLETED; i++) quickBuild->Update(0.1f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
quickBuild = nullptr;
|
||||||
|
build.reset();
|
||||||
|
Game::entityManager->_removeEntity(player->GetObjectID());
|
||||||
|
player->SetCharacter(nullptr);
|
||||||
|
player.reset();
|
||||||
|
character.reset();
|
||||||
|
TearDownDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Entity> player;
|
||||||
|
std::unique_ptr<Character> character;
|
||||||
|
std::unique_ptr<Entity> build;
|
||||||
|
QuickBuildComponent* quickBuild{};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(QuickBuildCompleteTests, SuccessHasNoDuration) {
|
||||||
|
const auto sent = Build();
|
||||||
|
ASSERT_EQ(quickBuild->GetState(), eQuickBuildState::COMPLETED);
|
||||||
|
const auto enables = SentGameMessages<GameMessages::EnableRebuild>(sent);
|
||||||
|
ASSERT_EQ(enables.size(), 1u);
|
||||||
|
EXPECT_TRUE(enables[0].bSuccess);
|
||||||
|
EXPECT_FALSE(enables[0].bFail);
|
||||||
|
EXPECT_EQ(enables[0].fDuration, 0.0f);
|
||||||
|
EXPECT_EQ(enables[0].user, player->GetObjectID());
|
||||||
|
}
|
||||||
@@ -121,6 +121,35 @@ namespace GameMessageTestUtils {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The game message ID of every captured game message, in send order; other packets are skipped.
|
||||||
|
inline std::vector<MessageType::Game> SentGameMessageIds(const std::vector<CapturedPacket>& packets) {
|
||||||
|
std::vector<MessageType::Game> ids;
|
||||||
|
for (const auto& packet : packets) {
|
||||||
|
RakNet::BitStream bitStream(const_cast<unsigned char*>(packet.bytes.data()), packet.bytes.size(), false);
|
||||||
|
LWOOBJID target{};
|
||||||
|
MessageType::Game msgId{};
|
||||||
|
if (GameMessages::NetGameMsg::ReadPacketHeader(bitStream, target, msgId)) ids.push_back(msgId);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every captured game message of type T, read back with its target, in send order.
|
||||||
|
template<typename T>
|
||||||
|
std::vector<T> SentGameMessages(const std::vector<CapturedPacket>& packets) {
|
||||||
|
std::vector<T> found;
|
||||||
|
for (const auto& packet : packets) {
|
||||||
|
RakNet::BitStream bitStream(const_cast<unsigned char*>(packet.bytes.data()), packet.bytes.size(), false);
|
||||||
|
T msg;
|
||||||
|
LWOOBJID target{};
|
||||||
|
MessageType::Game msgId{};
|
||||||
|
if (!GameMessages::NetGameMsg::ReadPacketHeader(bitStream, target, msgId) || msgId != msg.msgId) continue;
|
||||||
|
EXPECT_TRUE(msg.Deserialize(bitStream));
|
||||||
|
msg.target = target;
|
||||||
|
found.push_back(msg);
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
// Checks that every strict prefix of msg's serialized payload fails to deserialize.
|
// Checks that every strict prefix of msg's serialized payload fails to deserialize.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void ExpectTruncatedFails(const T& msg) {
|
void ExpectTruncatedFails(const T& msg) {
|
||||||
|
|||||||
Reference in New Issue
Block a user