mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
fix(replica): send each object's real age as time_since_created_on_server
Live sent the milliseconds since the object was created on the server in every construction header (nonzero in 142,258 of 145,677 captured constructions; the same object constructed again later carries a larger value by the time elapsed; the local player's own construction on load is 0). DLU always wrote 0. The client stores it in the object's config (LWOGameObject::time_since_created_on_server). Entities now remember when they were created (steady clock) and write their age. Test: ReplicaConstructionTest.TimeSinceCreatedOnServerIsTheObjectsAge. Check in game: zone in and walk around several worlds; objects, NPCs, enemies and other players appear and animate as before (no objects popping, stuck or misplaced animations on platforms/spinners). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -997,7 +997,9 @@ void Entity::WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacke
|
||||
outBitStream.Write<uint8_t>(name.size());
|
||||
outBitStream.Write(name);
|
||||
|
||||
outBitStream.Write<uint32_t>(0); //Time since created on server
|
||||
// Live sent the object's age on this server in milliseconds: 0 for an object constructed as it is created
|
||||
// (a player's own construction on load), the time since load for zone objects.
|
||||
outBitStream.Write<uint32_t>(GetTimeSinceCreatedMs());
|
||||
|
||||
const auto& syncLDF = GetVar<std::vector<std::u16string>>(u"syncLDF");
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
@@ -218,6 +219,11 @@ public:
|
||||
std::unordered_map<eReplicaComponentType, Component*>& GetComponents() { return m_Components; } // TODO: Remove
|
||||
|
||||
void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType);
|
||||
|
||||
// Milliseconds since this object was created on this server (the construction's time_since_created_on_server).
|
||||
uint32_t GetTimeSinceCreatedMs() const {
|
||||
return static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_CreationTime).count());
|
||||
}
|
||||
void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType) const;
|
||||
void UpdateXMLDoc(tinyxml2::XMLDocument& doc);
|
||||
void Update(float deltaTime);
|
||||
@@ -433,6 +439,9 @@ private:
|
||||
|
||||
bool m_IsParentChildDirty = true;
|
||||
|
||||
// When this object was created on this server. Construction sends its age from this, as live did.
|
||||
std::chrono::steady_clock::time_point m_CreationTime = std::chrono::steady_clock::now();
|
||||
|
||||
bool m_IsSleeping = false;
|
||||
|
||||
/*
|
||||
|
||||
@@ -15,6 +15,7 @@ set(DCOMPONENTS_TESTS
|
||||
"ReplicaComponentOrderTests.cpp"
|
||||
"QuickBuildCompleteTests.cpp"
|
||||
"RocketLaunchTests.cpp"
|
||||
"ReplicaConstructionTests.cpp"
|
||||
)
|
||||
|
||||
# Get the folder name and prepend it to the files above
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Object construction data as live sent it. The expected bits are built field by field in the order the client reads
|
||||
// them (lu_packets replica structs), with the values of live constructions from the 2011/2012 captures.
|
||||
#include "GameDependencies.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "BitStream.h"
|
||||
#include "CDClientDatabase.h"
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDInventoryComponentTable.h"
|
||||
#include "CDItemComponentTable.h"
|
||||
#include "Character.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "ControllablePhysicsComponent.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "Entity.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "ItemComponent.h"
|
||||
#include "Item.h"
|
||||
#include "ModelComponent.h"
|
||||
#include "MovingPlatformComponent.h"
|
||||
#include "SimplePhysicsComponent.h"
|
||||
#include "User.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "eReplicaPacketType.h"
|
||||
|
||||
namespace {
|
||||
// Copies the bits of `stream` from bit `from` on into `tail`
|
||||
void Tail(RakNet::BitStream& stream, const uint32_t from, RakNet::BitStream& tail) {
|
||||
stream.SetReadOffset(from);
|
||||
const auto bits = stream.GetNumberOfBitsUsed() - from;
|
||||
std::vector<uint8_t> buffer(BITS_TO_BYTES(bits) + 1);
|
||||
stream.ReadBits(buffer.data(), bits, false);
|
||||
tail.WriteBits(buffer.data(), bits, false);
|
||||
}
|
||||
|
||||
// The stream's bits as '0'/'1' characters, so a failure shows where they differ
|
||||
std::string Bits(const RakNet::BitStream& stream) {
|
||||
std::string bits;
|
||||
for (uint32_t i = 0; i < stream.GetNumberOfBitsUsed(); ++i) bits += (stream.GetData()[i / 8] & (0x80 >> (i % 8))) ? '1' : '0';
|
||||
return bits;
|
||||
}
|
||||
|
||||
void ExpectSameBits(const RakNet::BitStream& actual, const RakNet::BitStream& expected) {
|
||||
EXPECT_EQ(Bits(actual), Bits(expected));
|
||||
}
|
||||
}
|
||||
|
||||
class ReplicaConstructionTest : public GameDependenciesTest {
|
||||
protected:
|
||||
void SetUp() override { SetUpDependencies(); }
|
||||
void TearDown() override { TearDownDependencies(); }
|
||||
};
|
||||
|
||||
// time_since_created_on_server: live sent each object's age on the server in milliseconds (0 for the local player's own
|
||||
// construction right after it was made, the zone's uptime for objects loaded with the zone). DLU sent 0.
|
||||
TEST_F(ReplicaConstructionTest, TimeSinceCreatedOnServerIsTheObjectsAge) {
|
||||
info.lot = 12266;
|
||||
Entity entity(288300744895979394, info);
|
||||
|
||||
const auto readTime = [&entity]() {
|
||||
RakNet::BitStream stream;
|
||||
entity.WriteBaseReplicaData(stream, eReplicaPacketType::CONSTRUCTION);
|
||||
LWOOBJID objectID{};
|
||||
LOT lot{};
|
||||
uint8_t nameLength{};
|
||||
uint32_t time{};
|
||||
EXPECT_TRUE(stream.Read(objectID));
|
||||
EXPECT_TRUE(stream.Read(lot));
|
||||
EXPECT_TRUE(stream.Read(nameLength));
|
||||
EXPECT_EQ(nameLength, 0);
|
||||
EXPECT_TRUE(stream.Read(time));
|
||||
EXPECT_EQ(objectID, 288300744895979394);
|
||||
EXPECT_EQ(lot, 12266);
|
||||
return time;
|
||||
};
|
||||
|
||||
EXPECT_LT(readTime(), 1000u);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
const auto later = readTime();
|
||||
EXPECT_GE(later, 30u);
|
||||
EXPECT_LT(later, 60000u);
|
||||
EXPECT_GE(entity.GetTimeSinceCreatedMs(), later);
|
||||
}
|
||||
Reference in New Issue
Block a user