DarkflameServer/dGame/dBehaviors/BasicAttackBehavior.cpp

287 lines
9.6 KiB
C++
Raw Normal View History

2022-08-06 03:01:59 +00:00
#include "BasicAttackBehavior.h"
#include "BehaviorBranchContext.h"
#include "Game.h"
#include "dLogger.h"
#include "EntityManager.h"
#include "DestroyableComponent.h"
#include "BehaviorContext.h"
#include "eBasicAttackSuccessTypes.h"
#include "DamageProfile.h"
void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
if (context->unmanaged) {
auto* entity = Game::entityManager->GetEntity(branch.target);
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) {
PlayFx(u"onhit", entity->GetObjectID());
destroyableComponent->Damage(this->m_MaxDamage, context->originator, context->skillID);
}
this->m_OnSuccess->Handle(context, bitStream, branch);
return;
}
bitStream->AlignReadToByteBoundary();
uint16_t allocatedBits{};
if (!bitStream->Read(allocatedBits) || allocatedBits == 0) {
Game::logger->LogDebug("BasicAttackBehavior", "No allocated bits");
return;
}
Game::logger->LogDebug("BasicAttackBehavior", "Number of allocated bits %i", allocatedBits);
const auto baseAddress = bitStream->GetReadOffset();
DoHandleBehavior(context, bitStream, branch);
bitStream->SetReadOffset(baseAddress + allocatedBits);
}
void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
auto* targetEntity = Game::entityManager->GetEntity(branch.target);
if (!targetEntity) {
Game::logger->Log("BasicAttackBehavior", "Target targetEntity %llu not found.", branch.target);
return;
}
auto* destroyableComponent = targetEntity->GetComponent<DestroyableComponent>();
if (!destroyableComponent) {
Game::logger->Log("BasicAttackBehavior", "No destroyable found on the obj/lot %llu/%i", branch.target, targetEntity->GetLOT());
return;
}
bool isBlocked{};
bool isImmune{};
bool isSuccess{};
if (!bitStream->Read(isBlocked)) {
Game::logger->Log("BasicAttackBehavior", "Unable to read isBlocked");
return;
}
if (isBlocked) {
destroyableComponent->SetAttacksToBlock(std::min(destroyableComponent->GetAttacksToBlock() - 1, 0U));
Game::entityManager->SerializeEntity(targetEntity);
this->m_OnFailBlocked->Handle(context, bitStream, branch);
return;
}
if (!bitStream->Read(isImmune)) {
Game::logger->Log("BasicAttackBehavior", "Unable to read isImmune");
return;
}
if (isImmune) {
this->m_OnFailImmune->Handle(context, bitStream, branch);
return;
}
if (!bitStream->Read(isSuccess)) {
Game::logger->Log("BasicAttackBehavior", "failed to read success from bitstream");
return;
}
if (isSuccess) {
uint32_t armorDamageDealt{};
if (!bitStream->Read(armorDamageDealt)) {
Game::logger->Log("BasicAttackBehavior", "Unable to read armorDamageDealt");
return;
}
uint32_t healthDamageDealt{};
if (!bitStream->Read(healthDamageDealt)) {
Game::logger->Log("BasicAttackBehavior", "Unable to read healthDamageDealt");
return;
}
uint32_t totalDamageDealt = armorDamageDealt + healthDamageDealt;
// A value that's too large may be a cheating attempt, so we set it to MIN
if (totalDamageDealt > this->m_MaxDamage) {
totalDamageDealt = this->m_MinDamage;
}
auto* source = EntityManager::Instance()->GetEntity(context->originator);
auto* damageProfile = DamageProfile::FindDamageProfile(context->skillID);
auto damageMap = destroyableComponent->ComputeDamage(totalDamageDealt, source, damageProfile);
// Apply a standard diviation of (+/-)20%
for (auto& damage : damageMap) {
damage.second = static_cast<uint32_t>(damage.second * (1.0f + (static_cast<float>(rand() % 40) / 100.0f) - 0.2f));
}
bool died{};
if (!bitStream->Read(died)) {
Game::logger->Log("BasicAttackBehavior", "Unable to read died");
return;
}
auto previousArmor = destroyableComponent->GetArmor();
auto previousHealth = destroyableComponent->GetHealth();
PlayFx(u"onhit", targetEntity->GetObjectID());
destroyableComponent->Damage(damageMap, context->originator, context->skillID);
//destroyableComponent->Damage(totalDamageDealt, context->originator, context->skillID);
}
uint8_t successState{};
if (!bitStream->Read(successState)) {
Game::logger->Log("BasicAttackBehavior", "Unable to read success state");
return;
}
switch (static_cast<eBasicAttackSuccessTypes>(successState)) {
case eBasicAttackSuccessTypes::SUCCESS:
this->m_OnSuccess->Handle(context, bitStream, branch);
break;
case eBasicAttackSuccessTypes::FAILARMOR:
this->m_OnFailArmor->Handle(context, bitStream, branch);
break;
default:
if (static_cast<eBasicAttackSuccessTypes>(successState) != eBasicAttackSuccessTypes::FAILIMMUNE) {
Game::logger->Log("BasicAttackBehavior", "Unknown success state (%i)!", successState);
return;
}
this->m_OnFailImmune->Handle(context, bitStream, branch);
break;
}
}
void BasicAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
bitStream->AlignWriteToByteBoundary();
const auto allocatedAddress = bitStream->GetWriteOffset();
bitStream->Write<uint16_t>(0);
const auto startAddress = bitStream->GetWriteOffset();
DoBehaviorCalculation(context, bitStream, branch);
const auto endAddress = bitStream->GetWriteOffset();
const uint16_t allocate = endAddress - startAddress;
bitStream->SetWriteOffset(allocatedAddress);
bitStream->Write(allocate);
bitStream->SetWriteOffset(startAddress + allocate);
}
void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
auto* targetEntity = Game::entityManager->GetEntity(branch.target);
if (!targetEntity) {
Game::logger->Log("BasicAttackBehavior", "Target entity %llu is null!", branch.target);
return;
}
auto* destroyableComponent = targetEntity->GetComponent<DestroyableComponent>();
if (!destroyableComponent || !destroyableComponent->GetParent()) {
Game::logger->Log("BasicAttackBehavior", "No destroyable component on %llu", branch.target);
return;
}
const bool isBlocking = destroyableComponent->GetAttacksToBlock() > 0;
bitStream->Write(isBlocking);
if (isBlocking) {
destroyableComponent->SetAttacksToBlock(destroyableComponent->GetAttacksToBlock() - 1);
Game::entityManager->SerializeEntity(targetEntity);
this->m_OnFailBlocked->Calculate(context, bitStream, branch);
return;
}
const bool isImmune = destroyableComponent->IsImmune();
bitStream->Write(isImmune);
if (isImmune) {
this->m_OnFailImmune->Calculate(context, bitStream, branch);
return;
}
bool isSuccess = false;
const uint32_t previousHealth = destroyableComponent->GetHealth();
const uint32_t previousArmor = destroyableComponent->GetArmor();
auto damage = this->m_MinDamage;
auto* source = EntityManager::Instance()->GetEntity(context->originator);
auto* damageProfile = DamageProfile::FindDamageProfile(context->skillID);
if (damageProfile != nullptr) {
Game::logger->Log("BasicAttackBehavior", "Found damage profile for skill %i, originator %i", context->skillID, source->GetLOT());
}
else {
Game::logger->Log("BasicAttackBehavior", "No damage profile found for skill %i, originator %i", context->skillID, source->GetLOT());
}
auto damageMap = destroyableComponent->ComputeDamage(damage, source, damageProfile);
// Apply a standard diviation of (+/-)20%
for (auto& damage : damageMap) {
damage.second = static_cast<uint32_t>(damage.second * (1.0f + (static_cast<float>(rand() % 40) / 100.0f) - 0.2f));
}
PlayFx(u"onhit", targetEntity->GetObjectID(), 1);
destroyableComponent->Damage(damageMap, context->originator, context->skillID, false);
context->ScheduleUpdate(branch.target);
const uint32_t armorDamageDealt = previousArmor - destroyableComponent->GetArmor();
const uint32_t healthDamageDealt = previousHealth - destroyableComponent->GetHealth();
isSuccess = armorDamageDealt > 0 || healthDamageDealt > 0 || (armorDamageDealt + healthDamageDealt) > 0;
bitStream->Write(isSuccess);
eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE;
if (isSuccess) {
if (healthDamageDealt >= 1) {
successState = eBasicAttackSuccessTypes::SUCCESS;
} else if (armorDamageDealt >= 1) {
successState = this->m_OnFailArmor->m_templateId == BehaviorTemplates::BEHAVIOR_EMPTY ? eBasicAttackSuccessTypes::FAILIMMUNE : eBasicAttackSuccessTypes::FAILARMOR;
}
bitStream->Write(armorDamageDealt);
bitStream->Write(healthDamageDealt);
bitStream->Write(targetEntity->GetIsDead());
}
bitStream->Write(successState);
switch (static_cast<eBasicAttackSuccessTypes>(successState)) {
case eBasicAttackSuccessTypes::SUCCESS:
this->m_OnSuccess->Calculate(context, bitStream, branch);
break;
case eBasicAttackSuccessTypes::FAILARMOR:
this->m_OnFailArmor->Calculate(context, bitStream, branch);
break;
default:
if (static_cast<eBasicAttackSuccessTypes>(successState) != eBasicAttackSuccessTypes::FAILIMMUNE) {
Game::logger->Log("BasicAttackBehavior", "Unknown success state (%i)!", successState);
break;
}
this->m_OnFailImmune->Calculate(context, bitStream, branch);
break;
}
}
void BasicAttackBehavior::Load() {
this->m_MinDamage = GetInt("min damage");
if (this->m_MinDamage == 0) this->m_MinDamage = 1;
this->m_MaxDamage = GetInt("max damage");
if (this->m_MaxDamage == 0) this->m_MaxDamage = 1;
// The client sets the minimum damage to maximum, so we'll do the same. These are usually the same value anyways.
if (this->m_MinDamage < this->m_MaxDamage) this->m_MinDamage = this->m_MaxDamage;
this->m_OnSuccess = GetAction("on_success");
this->m_OnFailArmor = GetAction("on_fail_armor");
this->m_OnFailImmune = GetAction("on_fail_immune");
this->m_OnFailBlocked = GetAction("on_fail_blocked");
}