mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
When a client gets EchoStartSkill (or SyncSkill) from a caster it sees as dead, aimed at another object, it logs "msgEchoStartSkill msgCasterDead" and sends CasterDead (120: optional i64Caster, optional uiSkillHandle) through that target (LWOSkillComponent::msgEchoStartSkill 0x00d5dc90). 303 live packets, the target nearly always the attacked player, the caster a spawned enemy; the next messages were mostly Die and SetStunned for the player. DLU dropped it. The server now ends that skill on the caster: its behaviors with that handle are dropped (end entries run, pending timers and syncs discarded, its projectiles removed), so a hit an enemy scheduled before dying does not land later. It only does so when the server also sees the caster as dead, so a client can't cancel a living enemy's attack. What live did with the message is inferred from the name and when it was sent. Check in game: kill an enemy in the middle of a slow or charged attack (e.g. a Maelstrom horseman or a spider queen add): its attack does not hit after it has died, and other enemies' attacks still hit normally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
312 lines
13 KiB
C++
312 lines
13 KiB
C++
#include "SkillMessages.h"
|
|
|
|
#include "BehaviorSlot.h"
|
|
#include "BitStreamUtils.h"
|
|
#include "CDClientManager.h"
|
|
#include "CDSkillBehaviorTable.h"
|
|
#include "DestroyableComponent.h"
|
|
#include "dServer.h"
|
|
#include "eMissionTaskType.h"
|
|
#include "Entity.h"
|
|
#include "EntityManager.h"
|
|
#include "Game.h"
|
|
#include "InventoryComponent.h"
|
|
#include "MissionComponent.h"
|
|
#include "SkillComponent.h"
|
|
|
|
namespace GameMessages {
|
|
void AddSkill::Serialize(RakNet::BitStream& bitStream) const {
|
|
BitStreamUtils::WriteOptional(bitStream, AICombatWeight, 0);
|
|
bitStream.Write(bFromSkillSet);
|
|
BitStreamUtils::WriteOptional(bitStream, castType, 0);
|
|
BitStreamUtils::WriteOptional(bitStream, fTimeSecs, -1.0f);
|
|
BitStreamUtils::WriteOptional(bitStream, iTimesCanCast, -1);
|
|
bitStream.Write(skillID);
|
|
BitStreamUtils::WriteOptional(bitStream, slotID, BehaviorSlot::Invalid);
|
|
bitStream.Write(temporary);
|
|
}
|
|
|
|
bool AddSkill::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, AICombatWeight, 0));
|
|
VALIDATE_READ(bitStream.Read(bFromSkillSet));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, castType, 0));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, fTimeSecs, -1.0f));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iTimesCanCast, -1));
|
|
VALIDATE_READ(bitStream.Read(skillID));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, slotID, BehaviorSlot::Invalid));
|
|
VALIDATE_READ(bitStream.Read(temporary));
|
|
return true;
|
|
}
|
|
|
|
void RemoveSkill::Serialize(RakNet::BitStream& bitStream) const {
|
|
bitStream.Write(bFromSkillSet);
|
|
bitStream.Write(skillID);
|
|
}
|
|
|
|
bool RemoveSkill::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(bitStream.Read(bFromSkillSet));
|
|
VALIDATE_READ(bitStream.Read(skillID));
|
|
return true;
|
|
}
|
|
|
|
void UncastSkill::Serialize(RakNet::BitStream& bitStream) const {
|
|
bitStream.Write(skillID);
|
|
}
|
|
|
|
bool UncastSkill::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(bitStream.Read(skillID));
|
|
return true;
|
|
}
|
|
|
|
void CasterDead::Serialize(RakNet::BitStream& bitStream) const {
|
|
BitStreamUtils::WriteOptional(bitStream, i64Caster, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteOptional(bitStream, uiSkillHandle, 0u);
|
|
}
|
|
|
|
bool CasterDead::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64Caster, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, uiSkillHandle, 0u));
|
|
return true;
|
|
}
|
|
|
|
void CasterDead::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
|
// Only for a caster the server also sees as dead: a client can't cancel a living enemy's attack
|
|
auto* const caster = Game::entityManager->GetEntity(i64Caster);
|
|
if (!caster) return;
|
|
const auto* const destroyable = caster->GetComponent<DestroyableComponent>();
|
|
if (!destroyable || !destroyable->GetIsDead()) return;
|
|
auto* const skillComponent = caster->GetComponent<SkillComponent>();
|
|
if (skillComponent) skillComponent->EndSkill(uiSkillHandle);
|
|
}
|
|
|
|
void SelectSkill::Serialize(RakNet::BitStream& bitStream) const {
|
|
bitStream.Write(bFromSkillSet);
|
|
bitStream.Write(skillID);
|
|
}
|
|
|
|
bool SelectSkill::Deserialize(RakNet::BitStream& bitStream) {
|
|
// The old handler read nothing; keep accepting the message whatever its payload.
|
|
if (!bitStream.Read(bFromSkillSet) || !bitStream.Read(skillID)) {
|
|
bFromSkillSet = false;
|
|
skillID = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
|
|
void SelectSkill::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
|
auto var = entity.GetVar<bool>(u"dlu_first_time_load");
|
|
if (var) {
|
|
entity.SetVar<bool>(u"dlu_first_time_load", false);
|
|
InventoryComponent* inventoryComponent = entity.GetComponent<InventoryComponent>();
|
|
|
|
if (inventoryComponent) inventoryComponent->FixInvisibleItems();
|
|
}
|
|
}
|
|
|
|
void StartSkill::Serialize(RakNet::BitStream& bitStream) const {
|
|
bitStream.Write(bUsedMouse);
|
|
BitStreamUtils::WriteOptional(bitStream, consumableItemID, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteOptional(bitStream, fCasterLatency, 0.0f);
|
|
BitStreamUtils::WriteOptional(bitStream, iCastType, 0);
|
|
BitStreamUtils::WriteOptional(bitStream, lastClickedPosit, NiPoint3Constant::ZERO);
|
|
bitStream.Write(optionalOriginatorID);
|
|
BitStreamUtils::WriteOptional(bitStream, optionalTargetID, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteOptional(bitStream, originatorRot, QuatUtils::IDENTITY);
|
|
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, sBitStream);
|
|
bitStream.Write(skillID);
|
|
BitStreamUtils::WriteOptional(bitStream, uiSkillHandle, 0u);
|
|
}
|
|
|
|
bool StartSkill::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(bitStream.Read(bUsedMouse));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, consumableItemID, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, fCasterLatency, 0.0f));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iCastType, 0));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, lastClickedPosit, NiPoint3Constant::ZERO));
|
|
VALIDATE_READ(bitStream.Read(optionalOriginatorID));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, optionalTargetID, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, originatorRot, QuatUtils::IDENTITY));
|
|
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, sBitStream));
|
|
VALIDATE_READ(bitStream.Read(skillID));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, uiSkillHandle, 0u));
|
|
return true;
|
|
}
|
|
|
|
void StartSkill::Handle(Entity& entityRef, const SystemAddress& sysAddr) {
|
|
Entity* entity = &entityRef;
|
|
if (skillID == 1561 || skillID == 1562 || skillID == 1541) return;
|
|
|
|
MissionComponent* comp = entity->GetComponent<MissionComponent>();
|
|
if (comp) {
|
|
comp->Progress(eMissionTaskType::USE_SKILL, skillID);
|
|
}
|
|
|
|
CDSkillBehaviorTable* skillTable = CDClientManager::GetTable<CDSkillBehaviorTable>();
|
|
unsigned int behaviorId = skillTable->GetSkillByID(skillID).behaviorID;
|
|
|
|
bool success = false;
|
|
|
|
if (behaviorId > 0) {
|
|
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&sBitStream[0]), sBitStream.size(), false);
|
|
|
|
auto* const skillComponent = entity->GetComponent<SkillComponent>();
|
|
|
|
if (skillComponent) success = skillComponent->CastPlayerSkill(behaviorId, uiSkillHandle, bs, optionalTargetID, skillID);
|
|
|
|
if (success && entity->GetCharacter()) {
|
|
DestroyableComponent* destComp = entity->GetComponent<DestroyableComponent>();
|
|
destComp->SetImagination(destComp->GetImagination() - skillTable->GetSkillByID(skillID).imaginationcost);
|
|
}
|
|
}
|
|
|
|
if (Game::server->GetZoneID() == 1302) {
|
|
return;
|
|
}
|
|
|
|
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;
|
|
echoStartSkill.BroadcastExcept(entity->GetSystemAddress());
|
|
}
|
|
}
|
|
|
|
void EchoStartSkill::Serialize(RakNet::BitStream& bitStream) const {
|
|
bitStream.Write(bUsedMouse);
|
|
BitStreamUtils::WriteOptional(bitStream, fCasterLatency, 0.0f);
|
|
BitStreamUtils::WriteOptional(bitStream, iCastType, 0);
|
|
BitStreamUtils::WriteOptional(bitStream, lastClickedPosit, NiPoint3Constant::ZERO);
|
|
bitStream.Write(optionalOriginatorID);
|
|
BitStreamUtils::WriteOptional(bitStream, optionalTargetID, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteOptional(bitStream, originatorRot, QuatUtils::IDENTITY);
|
|
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, sBitStream);
|
|
bitStream.Write(skillID);
|
|
BitStreamUtils::WriteOptional(bitStream, uiSkillHandle, 0u);
|
|
}
|
|
|
|
bool EchoStartSkill::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(bitStream.Read(bUsedMouse));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, fCasterLatency, 0.0f));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iCastType, 0));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, lastClickedPosit, NiPoint3Constant::ZERO));
|
|
VALIDATE_READ(bitStream.Read(optionalOriginatorID));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, optionalTargetID, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, originatorRot, QuatUtils::IDENTITY));
|
|
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, sBitStream));
|
|
VALIDATE_READ(bitStream.Read(skillID));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, uiSkillHandle, 0u));
|
|
return true;
|
|
}
|
|
|
|
void SyncSkill::Serialize(RakNet::BitStream& bitStream) const {
|
|
bitStream.Write(bDone);
|
|
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, sBitStream);
|
|
bitStream.Write(uiBehaviorHandle);
|
|
bitStream.Write(uiSkillHandle);
|
|
}
|
|
|
|
bool SyncSkill::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(bitStream.Read(bDone));
|
|
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, sBitStream));
|
|
VALIDATE_READ(bitStream.Read(uiBehaviorHandle));
|
|
VALIDATE_READ(bitStream.Read(uiSkillHandle));
|
|
return true;
|
|
}
|
|
|
|
void SyncSkill::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
|
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&sBitStream[0]), sBitStream.size(), false);
|
|
|
|
auto* const skillComponent = entity.GetComponent<SkillComponent>();
|
|
|
|
if (skillComponent) skillComponent->SyncPlayerSkill(uiSkillHandle, uiBehaviorHandle, bs);
|
|
|
|
// Echo the sync to every other client
|
|
EchoSyncSkill echo;
|
|
echo.target = entity.GetObjectID();
|
|
echo.bDone = bDone;
|
|
echo.sBitStream = sBitStream;
|
|
echo.uiBehaviorHandle = uiBehaviorHandle;
|
|
echo.uiSkillHandle = uiSkillHandle;
|
|
echo.BroadcastExcept(sysAddr);
|
|
}
|
|
|
|
void EchoSyncSkill::Serialize(RakNet::BitStream& bitStream) const {
|
|
bitStream.Write(bDone);
|
|
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, sBitStream);
|
|
bitStream.Write(uiBehaviorHandle);
|
|
bitStream.Write(uiSkillHandle);
|
|
}
|
|
|
|
bool EchoSyncSkill::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(bitStream.Read(bDone));
|
|
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, sBitStream));
|
|
VALIDATE_READ(bitStream.Read(uiBehaviorHandle));
|
|
VALIDATE_READ(bitStream.Read(uiSkillHandle));
|
|
return true;
|
|
}
|
|
|
|
void RequestServerProjectileImpact::Serialize(RakNet::BitStream& bitStream) const {
|
|
BitStreamUtils::WriteOptional(bitStream, i64LocalID, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteOptional(bitStream, i64TargetID, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, sBitStream);
|
|
}
|
|
|
|
bool RequestServerProjectileImpact::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64LocalID, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64TargetID, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, sBitStream));
|
|
return true;
|
|
}
|
|
|
|
void RequestServerProjectileImpact::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
|
auto* skill_component = entity.GetComponent<SkillComponent>();
|
|
|
|
if (skill_component != nullptr) {
|
|
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&sBitStream[0]), sBitStream.size(), false);
|
|
|
|
skill_component->SyncPlayerProjectile(i64LocalID, bs, i64TargetID);
|
|
}
|
|
}
|
|
|
|
void DoClientProjectileImpact::Serialize(RakNet::BitStream& bitStream) const {
|
|
BitStreamUtils::WriteOptional(bitStream, i64OrgID, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteOptional(bitStream, i64OwnerID, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteOptional(bitStream, i64TargetID, LWOOBJID_EMPTY);
|
|
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, sBitStream);
|
|
}
|
|
|
|
bool DoClientProjectileImpact::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64OrgID, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64OwnerID, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64TargetID, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, sBitStream));
|
|
return true;
|
|
}
|
|
|
|
void UseSkillSet::Serialize(RakNet::BitStream& bitStream) const {
|
|
bitStream.Write(bRemove);
|
|
bitStream.Write(possessedId != LWOOBJID_EMPTY);
|
|
if (possessedId != LWOOBJID_EMPTY) bitStream.Write(possessedId);
|
|
bitStream.Write(setId != -1);
|
|
if (setId != -1) bitStream.Write(setId);
|
|
}
|
|
|
|
bool UseSkillSet::Deserialize(RakNet::BitStream& bitStream) {
|
|
VALIDATE_READ(bitStream.Read(bRemove));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, possessedId, LWOOBJID_EMPTY));
|
|
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, setId, -1));
|
|
return true;
|
|
}
|
|
}
|