mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-11 07:13:44 +00:00
fix(skills): EchoStartSkill carries the cast, not the caster's input
Live sends used_mouse false, no caster latency, no clicked position and a zero originator rotation (present, all four components 0) in every echo: 78,841 of 78,841 in the live captures, player and server casts alike. DLU forwarded the casting client's values and wrote the caster's facing for server casts. The client copies the rotation into its local StartSkill (LWOSkillComponent::msgEchoStartSkill), which is why a real rotation made power-up pickups jitter; the per-call override that worked around that is no longer needed and is removed. Check: with two players, attack, use a thrown or ranged item and pick up power-ups; the other player sees the same animations and the caster no longer snaps or jitters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1532,7 +1532,7 @@ void Entity::PickupItem(const LWOOBJID& objectID) const {
|
||||
const auto skills = skillsTable->Query([&info](CDObjectSkills entry) {return (entry.objectTemplate == info.lot); });
|
||||
for (const auto& skill : skills) {
|
||||
const auto [skillComponent, missionComponent] = GetComponentsMut<SkillComponent, MissionComponent>();
|
||||
if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID(), skill.castOnType, NiQuaternion(0, 0, 0, 0));
|
||||
if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID(), skill.castOnType);
|
||||
|
||||
if (missionComponent != nullptr) {
|
||||
missionComponent->Progress(eMissionTaskType::POWERUP, skill.skillID);
|
||||
|
||||
@@ -304,7 +304,7 @@ void SkillComponent::RegisterCalculatedProjectile(const LWOOBJID projectileId, B
|
||||
this->m_managedProjectiles.push_back(entry);
|
||||
}
|
||||
|
||||
bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LWOOBJID optionalOriginatorID, const int32_t castType, const NiQuaternion rotationOverride) {
|
||||
bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LWOOBJID optionalOriginatorID, const int32_t castType) {
|
||||
uint32_t behaviorId = -1;
|
||||
// try to find it via the cache
|
||||
const auto& pair = m_skillBehaviorCache.find(skillId);
|
||||
@@ -324,7 +324,7 @@ bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LW
|
||||
return false;
|
||||
}
|
||||
|
||||
return CalculateBehavior(skillId, behaviorId, target, false, false, optionalOriginatorID, castType, rotationOverride).success;
|
||||
return CalculateBehavior(skillId, behaviorId, target, false, false, optionalOriginatorID, castType).success;
|
||||
}
|
||||
|
||||
|
||||
@@ -335,8 +335,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(
|
||||
const bool ignoreTarget,
|
||||
const bool clientInitalized,
|
||||
const LWOOBJID originatorOverride,
|
||||
const int32_t castType,
|
||||
const NiQuaternion rotationOverride) {
|
||||
const int32_t castType) {
|
||||
RakNet::BitStream bitStream{};
|
||||
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
@@ -375,15 +374,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(
|
||||
start.optionalOriginatorID = context->originator;
|
||||
start.optionalTargetID = target;
|
||||
|
||||
auto* originator = Game::entityManager->GetEntity(context->originator);
|
||||
|
||||
if (originator != nullptr) {
|
||||
start.originatorRot = originator->GetRotation();
|
||||
}
|
||||
|
||||
if (rotationOverride != QuatUtils::IDENTITY) {
|
||||
start.originatorRot = rotationOverride;
|
||||
}
|
||||
start.originatorRot = GameMessages::EchoStartSkill::LIVE_ORIGINATOR_ROT;
|
||||
//start.optionalTargetID = target;
|
||||
|
||||
start.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed());
|
||||
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
* @param optionalOriginatorID change the originator of the skill
|
||||
* @return if the case succeeded
|
||||
*/
|
||||
bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = QuatUtils::IDENTITY);
|
||||
bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY, const int32_t castType = 0);
|
||||
|
||||
/**
|
||||
* Initializes a server-side skill calculation.
|
||||
@@ -174,7 +174,7 @@ public:
|
||||
* @param originatorOverride an override for the originator of the skill calculation
|
||||
* @return the result of the skill calculation
|
||||
*/
|
||||
SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = QuatUtils::IDENTITY);
|
||||
SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY, const int32_t castType = 0);
|
||||
|
||||
/**
|
||||
* Register a server-side projectile.
|
||||
|
||||
@@ -166,22 +166,24 @@ namespace GameMessages {
|
||||
|
||||
if (success) {
|
||||
// Echo the cast to every other client
|
||||
EchoStartSkill echoStartSkill;
|
||||
echoStartSkill.target = entity->GetObjectID();
|
||||
echoStartSkill.bUsedMouse = bUsedMouse;
|
||||
echoStartSkill.fCasterLatency = fCasterLatency;
|
||||
echoStartSkill.iCastType = iCastType;
|
||||
echoStartSkill.lastClickedPosit = lastClickedPosit;
|
||||
echoStartSkill.optionalOriginatorID = optionalOriginatorID;
|
||||
echoStartSkill.optionalTargetID = optionalTargetID;
|
||||
echoStartSkill.originatorRot = originatorRot;
|
||||
echoStartSkill.sBitStream = sBitStream;
|
||||
echoStartSkill.skillID = skillID;
|
||||
echoStartSkill.uiSkillHandle = uiSkillHandle;
|
||||
auto echoStartSkill = MakeEcho(entity->GetObjectID());
|
||||
echoStartSkill.BroadcastExcept(entity->GetSystemAddress());
|
||||
}
|
||||
}
|
||||
|
||||
EchoStartSkill StartSkill::MakeEcho(const LWOOBJID caster) const {
|
||||
EchoStartSkill echo;
|
||||
echo.target = caster;
|
||||
echo.iCastType = iCastType;
|
||||
echo.optionalOriginatorID = optionalOriginatorID;
|
||||
echo.optionalTargetID = optionalTargetID;
|
||||
echo.originatorRot = EchoStartSkill::LIVE_ORIGINATOR_ROT;
|
||||
echo.sBitStream = sBitStream;
|
||||
echo.skillID = skillID;
|
||||
echo.uiSkillHandle = uiSkillHandle;
|
||||
return echo;
|
||||
}
|
||||
|
||||
void EchoStartSkill::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(bUsedMouse);
|
||||
BitStreamUtils::WriteOptional(bitStream, fCasterLatency, 0.0f);
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace GameMessages {
|
||||
int32_t skillID{};
|
||||
};
|
||||
|
||||
struct EchoStartSkill;
|
||||
|
||||
// Client -> server. The player cast a skill.
|
||||
struct StartSkill : public NetGameMsg {
|
||||
StartSkill() : NetGameMsg(MessageType::Game::START_SKILL) {}
|
||||
@@ -92,6 +94,11 @@ namespace GameMessages {
|
||||
std::string sBitStream{};
|
||||
TSkillID skillID{ 0 };
|
||||
uint32_t uiSkillHandle{ 0 }; // optional
|
||||
|
||||
// The echo other clients get for this cast. Live keeps the cast (originator, target, cast type, behavior
|
||||
// data, skill and handle) and drops the caster's input: used_mouse false, no latency, no clicked position
|
||||
// and a zero rotation (every one of the 78,841 live EchoStartSkill).
|
||||
EchoStartSkill MakeEcho(LWOOBJID caster) const;
|
||||
};
|
||||
|
||||
// Server -> clients. A skill cast echoed to the other clients so they play it too.
|
||||
@@ -100,6 +107,11 @@ namespace GameMessages {
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
// What live writes for originatorRot in every echo, client casts and server casts alike: present, all four
|
||||
// components 0. The client copies it into its local StartSkill (LWOSkillComponent::msgEchoStartSkill); a real
|
||||
// rotation turns the caster, which is the jitter DLU once worked around for power-ups.
|
||||
static constexpr NiQuaternion LIVE_ORIGINATOR_ROT{ 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
|
||||
bool bUsedMouse{ false };
|
||||
float fCasterLatency{ 0.0f }; // optional
|
||||
int32_t iCastType{ 0 }; // optional
|
||||
|
||||
@@ -311,3 +311,44 @@ TEST_F(SkillMessagesTests, GoldenBytes) {
|
||||
request.i64TargetID = 0x11;
|
||||
EXPECT_PACKET_EQ(FromHex("44 40 00 00 00 00 00 00 00 00 00 00 00", 98), Payload(request));
|
||||
}
|
||||
|
||||
// Live echoes keep the cast and drop the caster's input (all 78,841 EchoStartSkill in the live captures).
|
||||
TEST_F(SkillMessagesTests, EchoOfClientCastMatchesLive) {
|
||||
GameMessages::StartSkill cast;
|
||||
cast.bUsedMouse = true;
|
||||
cast.consumableItemID = 0x0102030405060700LL;
|
||||
cast.fCasterLatency = 0.25f;
|
||||
cast.iCastType = 1;
|
||||
cast.lastClickedPosit = NiPoint3(1.0f, 2.0f, 3.0f);
|
||||
cast.optionalOriginatorID = 0x1000000000000001LL;
|
||||
cast.optionalTargetID = 0x0102030405060708LL;
|
||||
cast.originatorRot = NiQuaternion(0.5f, 0.5f, -0.5f, 0.5f);
|
||||
cast.skillID = 381;
|
||||
cast.uiSkillHandle = 1;
|
||||
|
||||
const auto echo = cast.MakeEcho(0x1000000000000001LL);
|
||||
EXPECT_EQ(echo.target, 0x1000000000000001LL);
|
||||
EXPECT_FALSE(echo.bUsedMouse);
|
||||
EXPECT_EQ(echo.fCasterLatency, 0.0f);
|
||||
EXPECT_EQ(echo.lastClickedPosit, NiPoint3Constant::ZERO);
|
||||
EXPECT_EQ(echo.originatorRot, GameMessages::EchoStartSkill::LIVE_ORIGINATOR_ROT);
|
||||
EXPECT_EQ(echo.iCastType, 1);
|
||||
EXPECT_EQ(echo.optionalOriginatorID, cast.optionalOriginatorID);
|
||||
EXPECT_EQ(echo.optionalTargetID, cast.optionalTargetID);
|
||||
EXPECT_EQ(echo.skillID, 381);
|
||||
EXPECT_EQ(echo.uiSkillHandle, 1u);
|
||||
|
||||
// A live echo of skill 381 (cast type 1, handle 1, no behavior data) with the caster and target IDs replaced.
|
||||
EXPECT_PACKET_EQ(FromHex(
|
||||
"53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 76 00 20 20 00 00 00 10 00 00 00 00 00 01 08 40 38 30 28 20 18 10 0c "
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 f4 04 00 02 02 00 00 00", 535), StructPacket(echo));
|
||||
}
|
||||
|
||||
// Server casts echo a zero rotation too, never the caster's facing.
|
||||
TEST_F(SkillMessagesTests, LiveOriginatorRotationIsWrittenAsZero) {
|
||||
GameMessages::EchoStartSkill echo;
|
||||
echo.originatorRot = GameMessages::EchoStartSkill::LIVE_ORIGINATOR_ROT;
|
||||
const auto copy = RoundTrip(echo);
|
||||
EXPECT_EQ(copy.originatorRot, GameMessages::EchoStartSkill::LIVE_ORIGINATOR_ROT);
|
||||
EXPECT_NE(copy.originatorRot, QuatUtils::IDENTITY);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user