fix(skill): a dead caster's sync ends the behavior without running it, as the client

LWOSkillComponent::MsgSyncSkill (0x00d454b0) checks whether the caster is
dead before running a synced behavior; if so it only removes the behavior
handle from the skill's pending ones. The server ran the sync anyway, so a
player's delayed hit still landed after they died.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-02 09:58:24 -05:00
parent c9929f1c1e
commit 24932b1282
2 changed files with 35 additions and 0 deletions

View File

@@ -22,6 +22,7 @@
#include "BaseCombatAIComponent.h"
#include "ScriptComponent.h"
#include "BuffComponent.h"
#include "DestroyableComponent.h"
#include "SkillMessages.h"
#include "CDClientManager.h"
#include "CDSkillBehaviorTable.h"
@@ -117,6 +118,20 @@ void SkillComponent::SyncPlayerSkill(const uint32_t skillUid, const uint32_t syn
return;
}
// A dead caster's sync only ends that behavior: LWOSkillComponent::MsgSyncSkill (0x00d454b0) drops it unrun
const auto* const destroyable = m_Parent->GetComponent<DestroyableComponent>();
if (destroyable && destroyable->GetIsDead()) {
for (auto it = index.first; it != index.second; ++it) {
auto& entries = it->second->syncEntries;
const auto entry = std::find_if(entries.begin(), entries.end(), [syncId](const auto& sync) { return sync.handle == syncId; });
if (entry == entries.end()) continue;
entries.erase(entry);
return;
}
LOG("Failed to find sync id (%i) for skill with uid (%i)!", syncId, skillUid);
return;
}
bool foundSyncId = false;
for (auto it = index.first; it != index.second && !foundSyncId; ++it) {
const auto& context = it->second;