haxis/Source/UnrealProject/Creatures/NetworkCharacter.cpp

1232 lines
33 KiB
C++

// Project Lab - NHTV Igad
#include "UnrealProject.h"
#if PLATFORM_SPECIFIC_WIN == 0
#include "InputManager.hpp"
using namespace Input;
#endif
#include "NetworkCharacter.h"
#include "AbilityInfo.h"
#include "ItemBase.h"
#include "BaseSkillObject.h"
#include "IngameSkillTree.h"
#include "DefaultPlayerController.h"
#include "DefaultGameMode.h"
#include "NetworkPlayer.h"
#include "Modifier.h"
#include "AbilityEventGroup.h"
#include "PreCastAbilityEventGroup.h"
#include "NativeModifiers.h"
#include "DefaultGameState.h"
#include "DefaultPlayerState.h"
#include "Effect.h"
TSubclassOf<ATargetedEffect> expGainEffect;
ANetworkCharacter::ANetworkCharacter()
{
expGainEffect = ConstructorHelpers::FClassFinder<ATargetedEffect>(L"/Game/Assets/Art/Effects/FX_ExpParticle").Class;
// Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
// Network init
bReplicates = true;
bAlwaysRelevant = true;
bReplicateMovement = true;
// Configure character movement
GetCharacterMovement()->bOrientRotationToMovement = true; // Rotate character to moving direction
GetCharacterMovement()->RotationRate = FRotator(0.f, 640.f, 0.f);
GetCharacterMovement()->bConstrainToPlane = true;
GetCharacterMovement()->bSnapToPlaneAtStart = true;
baseMovementSpeed = GetCharacterMovement()->MaxWalkSpeed;
experienceGainOnKill = 50;
m_maxHealth = 1000;
m_maxMana = 100;
canBeStunned = true;
m_visible = true;
m_usesMana = true;
// Default to neutral
GetMesh()->bRenderCustomDepth = true;
GetMesh()->CustomDepthStencilValue = 0;
PrimaryActorTick.bCanEverTick = true;
m_lastPlayerDamage = nullptr;
healthAccumTimer = 1.0f;
damageAccumTimer = 0.1f;
teamDamageTimer = 5.0f;
m_healAccumValue = 0;
m_healAccumTimer = 0;
}
void ANetworkCharacter::BeginPlay()
{
Super::BeginPlay();
m_cooldownReduction = 0.0f;
m_channelStun = false;
m_channelGroup = nullptr;
m_castingAbility = nullptr;
m_initialised = true;
m_hitable = true;
m_silenceCount = 0;
m_silenced = false;
swingAnimationSequence = 0;
// Filter out NULL abilities
TArray<class UAbilityInfo*> actualAbilities;
for (int32 i = 0; i < abilities.Num(); i++)
{
if (!IsValid(abilities[i]))
continue;
actualAbilities.Add(abilities[i]);
}
abilities = actualAbilities;
for (int32 i = 0; i < abilities.Num(); i++)
{
GetAbilityState(abilities[i]);
}
RegisterHealthBar();
// Initialize the character modifiers on the server
if (Role == ROLE_Authority)
{
m_modifierManager = new ModifierManager(this);
m_SpawnModifiers();
for(UAbilityInfo* info : passives)
{
CastAbility(info);
}
m_health = m_maxHealth;
m_mana = m_maxMana;
}
}
void ANetworkCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
if (m_modifierManager)
{
delete m_modifierManager;
m_modifierManager = nullptr;
}
#if UE_INCLUDE_METRICS
// Record trivial player death
if(metricsHandle && EndPlayReason == EEndPlayReason::Type::Destroyed)
metricsHandle->OnPlayerDie(0);
#endif
}
void ANetworkCharacter::Destroyed()
{
UnregisterHealthBar();
Super::Destroyed();
}
void ANetworkCharacter::RegisterHealthBar()
{
// Notify UI of character spawn
ULocalPlayer* localPlayer = GetGameInstance()->GetFirstGamePlayer();
if(localPlayer)
{
ADefaultPlayerController* localPlayerController = Cast<ADefaultPlayerController>(localPlayer->PlayerController);
if(localPlayerController)
{
localPlayerController->OnCharacterCreated(this);
}
else
{
GWWARNING(L"No local player found to send OnCharacterCreated");
}
}
}
void ANetworkCharacter::UnregisterHealthBar()
{
// Notify UI of character destroy
UGameInstance* inst = GetGameInstance();
if(!inst)
return;
ULocalPlayer* player = inst->GetFirstGamePlayer();
if(player)
{
APlayerController* controller = player->PlayerController;
ADefaultPlayerController* localPlayer = Cast<ADefaultPlayerController>(controller);
if(localPlayer)
{
localPlayer->OnCharacterDestroyed(this);
}
else
{
GWWARNING(L"No local player found to send OnCharacterDestroyed");
}
}
}
void ANetworkCharacter::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
// Update the damage accumolation table
if (m_damageAccum.size() > 0)
{
for (auto iter = m_damageAccum.begin(); iter != m_damageAccum.end();)
{
// Controller no longer exists
if (!IsValid(iter->first))
{
iter = m_damageAccum.erase(iter);
continue;
}
DamagePeriod& value = iter->second;
value.timer -= DeltaSeconds;
// Time has expired
if (value.timer <= 0)
{
if (value.damage > 0)
iter->first->SpawnArcingCombatText(GetActorLocation(), FString::FromInt(value.damage), FLinearColor::White);
m_totalDamage = value.damage;
iter = m_damageAccum.erase(iter);
continue;
}
iter++;
}
}
// Update the team damage dealt table
if (m_teamDamageDealt.size() > 0)
{
for (auto iter = m_teamDamageDealt.begin(); iter != m_teamDamageDealt.end();)
{
DamagePeriod& value = iter->second;
value.timer -= DeltaSeconds;
// Time has expired, this team is no longer entitled for the experience
if (value.timer <= 0)
{
iter = m_teamDamageDealt.erase(iter);
continue;
}
iter++;
}
}
ADefaultPlayerController* const controller = Cast<ADefaultPlayerController>(GetController());
// Update the heal accumolation
if (IsValid(controller) && m_healAccumValue > 0)
{
m_healAccumTimer -= DeltaSeconds;
if (m_healAccumTimer <= 0)
{
m_healAccumTimer = 0;
controller->SpawnCombatText(GetActorLocation(), FString::FromInt(m_healAccumValue), FLinearColor::Green);
m_healAccumValue = 0;
}
}
if(GetMesh())
{
// Check if ally or player for silhouette material
// (0 = neutral, 1 = player, 2 = ally, 3 = enemy)
UWorld* const world = GetWorld();
const int32 current = GetMesh()->CustomDepthStencilValue;
int32 target = 0; // Default to neutral
if(controller == world->GetFirstPlayerController())
target = 1; // Self
else
{
// Get the local team
int32 localTeam = -1;
ADefaultPlayerController* controller = Cast<ADefaultPlayerController>(world->GetFirstPlayerController());
if(IsValid(controller))
{
APlayerStateBase* playerState = Cast<ADefaultPlayerState>(controller->PlayerState);
if(IsValid(playerState))
localTeam = playerState->GetTeam();
}
if(localTeam != -1)
{
if(GetTeam() == localTeam)
target = 2; // Ally
else if(GetTeam() != 0)
target = 3; // Enemy
}
}
if(target != current)
{
GetMesh()->CustomDepthStencilValue = target;
GetMesh()->MarkRenderStateDirty();
}
}
m_TickAbilities(DeltaSeconds);
#if UE_INCLUDE_METRICS
if (metricsHandle)
{
// Record player stats
metricsHandle->OnPlayerUpdate(GetActorLocation().X, GetActorLocation().Y);
ADefaultPlayerState* const state = Cast<ADefaultPlayerState>(PlayerState);
if(state)
metricsHandle->OnPlayerLevel(uint8(state->GetLevel()));
metricsHandle->OnPlayerMaxHealth(m_maxHealth);
metricsHandle->OnPlayerMaxMana(m_maxMana);
metricsHandle->OnPlayerHealth(m_health);
metricsHandle->OnPlayerMana(m_mana);
}
#endif
if (m_shouldBeDestroyed)
Destroy();
}
void ANetworkCharacter::OnDeath(UAbilityInfo* ability)
{
m_shouldBeDestroyed = true;
}
void ANetworkCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ANetworkCharacter, basicAttack);
DOREPLIFETIME(ANetworkCharacter, m_health);
DOREPLIFETIME(ANetworkCharacter, m_maxHealth);
DOREPLIFETIME(ANetworkCharacter, m_mana);
DOREPLIFETIME(ANetworkCharacter, m_maxMana);
DOREPLIFETIME(ANetworkCharacter, m_stunned);
DOREPLIFETIME(ANetworkCharacter, m_silenced);
DOREPLIFETIME(ANetworkCharacter, m_channelStun);
DOREPLIFETIME(ANetworkCharacter, m_allowChannelRotation);
DOREPLIFETIME(ANetworkCharacter, m_castingAbility);
DOREPLIFETIME(ANetworkCharacter, m_cooldownReduction);
DOREPLIFETIME(ANetworkCharacter, m_attackSpeed);
DOREPLIFETIME(ANetworkCharacter, m_attackDamageMultiplier);
DOREPLIFETIME(ANetworkCharacter, m_magicDamageMultiplier);
DOREPLIFETIME(ANetworkCharacter, m_blockedMana);
DOREPLIFETIME(ANetworkCharacter, m_damageMultiplier);
DOREPLIFETIME(ANetworkCharacter, m_ignoreArmor);
DOREPLIFETIME(ANetworkCharacter, m_armor);
DOREPLIFETIME(ANetworkCharacter, m_manaRegenMultiplier);
DOREPLIFETIME(ANetworkCharacter, m_visible);
DOREPLIFETIME(ANetworkCharacter, m_hitable);
DOREPLIFETIME(ANetworkCharacter, m_manaUsageMultiplier);
DOREPLIFETIME(ANetworkCharacter, m_positiveEffectMultiplier);
DOREPLIFETIME(ANetworkCharacter, m_negativeEffectMultiplier);
DOREPLIFETIME(ANetworkCharacter, m_castingMovementspeedMultiplier);
DOREPLIFETIME(ANetworkCharacter, swingAnimationSequence);
}
void ANetworkCharacter::TriggerSwingAnimation_Server()
{
check(Role == ROLE_Authority);
swingAnimationSequence++;
}
void ANetworkCharacter::LearnSkills_Implementation(const TArray<FIngameSkillTreeSkill>& skills)
{
ADefaultPlayerController* pc = Cast<ADefaultPlayerController>(GetController());
for(size_t i = 0; i < skills.Num(); i++)
{
const FIngameSkillTreeSkill& obj = skills[i];
UAbilityInfo* ability = obj.selectedEffect;
if(ability)
{
// Initialize ability power in the ability state
AAbilityState* aState = GetAbilityState(ability);
if(aState)
aState->power = obj.power;
if(ability->passive)
{
if(!passives.Contains(ability))
{
// Learn passive
passives.Add(obj.selectedEffect);
// Only Cast the actual passive ability on the server
if(Role == ROLE_Authority)
{
CastAbility(ability);
}
}
}
else
{
// Learn ability
if(!abilities.Contains(ability))
{
abilities.Add(ability);
}
}
}
}
}
void ANetworkCharacter::LearnSkills_Server(const TArray<FIngameSkillTreeSkill>& skills)
{
// Server side call
LearnSkills_Implementation(skills);
// Client side call
LearnSkills(skills);
}
void ANetworkCharacter::m_SpawnModifiers()
{
ARegenModifier* regenMod = Cast<ARegenModifier>(m_modifierManager->AddModifier(ARegenModifier::StaticClass(), 0));
regenMod->regenPerTick = passiveHealthRegen;
AManaRegenModifier* manaMod = Cast<AManaRegenModifier>(m_modifierManager->AddModifier(AManaRegenModifier::StaticClass(), 0));
manaMod->regenPerTick = passiveManaRegen;
}
bool ANetworkCharacter::m_AllowedToMove()
{
return !IsStunned();
}
bool ANetworkCharacter::m_AllowedToRotate()
{
return CanRotate();
}
void ANetworkCharacter::m_OnAbilityCastConfirm_Implementation(UAbilityInfo* ability, bool success, bool toggleState, float cooldown, float cooldownTime)
{
if (!ability)
{
GWWARNING(L"Out of range value received for OnAbilityCastConfirm");
return;
}
AAbilityState* state = GetAbilityState(ability);
//GWPRINT(L"Ability toggle " + ability->GetName() + L" toggles from " + state->toggleState + L" to " + toggleState);
state->toggleState = toggleState;
state->onCooldownTimer = cooldown;
state->currentCooldownDuration = cooldownTime;
state->cooldownRate = cooldown/cooldownTime;
}
bool ANetworkCharacter::m_CastAbility_Server_Validate(UAbilityInfo* ability)
{
if(ability == nullptr)
return false;
if(ability->events.Num() == 0)
{
GWERROR(L"No events in ability->events for ability " + ability->GetName());
return false;
}
// Validate AbilityInfo Array
for(int32 i = 0; i < ability->events.Num(); i++)
{
if(ability->events[i] == nullptr)
{
GWERROR(L"Null entry in ability->events[" + i + L"] for ability " + ability->GetName());
return false;
}
}
return true;
}
void ANetworkCharacter::m_CastAbility_Server_Implementation(UAbilityInfo* ability)
{
AAbilityState* state = GetAbilityState(ability);
// Check if this is a toggle ability, and is toggled off
bool toggleOffAbility = ability->IsHoldOrToggle() && state->toggleState == true;
// Check if we're not channeling (if this is not a toggle off ability)
if(IsPendingKill() || IsPendingKillPending())
return;
// Check if stunned or already casting an ability
// allow if this is a toggle ability and currently on
if(!toggleOffAbility && (IsStunned() || IsChanneling() ))
{
m_OnAbilityCastConfirm_Server(ability, false);
return;
}
// Check if silenced, block all non-basic attacks in this case
// also, don't block passives
if (ability->abilityType != EAbilityType::Basic && IsSilenced() && !ability->passive)
{
m_OnAbilityCastConfirm_Server(ability, false);
return;
}
// Check if ability is not on cooldown (if not toggling off)
if(state->onCooldownTimer > 0.1f && !toggleOffAbility)
{
m_OnAbilityCastConfirm_Server(ability, false);
return;
}
// Use mana state for current ability / character mana usage chained together with ability state (toggle off ability?)
bool currentUsesMana = m_usesMana && !toggleOffAbility;
UWorld* const world = GetWorld();
if (ability && world && (ability->mana * m_manaUsageMultiplier <= m_mana || !currentUsesMana))
{
if (currentUsesMana)
RemoveMana(ability->mana * m_manaUsageMultiplier);
// Handle cooldown setting
if(ability->IsHoldOrToggle() && state->toggleState == false)
{
// No cooldown when casting toggle abilities
state->onCooldownTimer = 0.0f;
state->cooldownRate = 0.0f;
}
else
{
// Go on cooldown if it is a toggle ability, or a regular ability
m_SetAbilityCooldown(ability, state);
}
if(ability->IsHoldOrToggle())
{
AAbilityEventGroup** activeAbility = m_activeAbilities.Find(ability);
bool isAbilityActive = activeAbility && *activeAbility;
// Check if ability is enabled
if(state->toggleState == false)
{
// Check if ability does not yet exist
if(!isAbilityActive)
{
state->toggleState = true;
// Activate toggle ability
m_InitAbilitySequence(ability, state);
}
else
{
m_OnAbilityCastConfirm_Server(ability, false);
}
}
else
{
// Deactivate ability
if(isAbilityActive)
{
state->toggleState = false;
(*activeAbility)->NextGroup();
*activeAbility = nullptr;
m_OnAbilityCastConfirm_Server(ability, true);
}
else
{
m_OnAbilityCastConfirm_Server(ability, false);
}
}
}
else
{
m_InitAbilitySequence(ability, state);
}
}
else
{
m_OnAbilityCastConfirm_Server(ability, false);
}
}
void ANetworkCharacter::m_InitAbilitySequence(class UAbilityInfo* ability, class AAbilityState* state)
{
AAbilityEventGroup* group = AAbilityEventGroup::SpawnSequence(this, ability, state, ability->events);
check(group);
if(group->channelEvent)
{
//GWPRINT(L"Begin channel ability " + group->GetName());
m_channelGroup = group;
m_castingAbility = ability;
m_allowChannelRotation = group->allowRotateWhileChannel;
if(m_castingMovementspeedMultiplier == 0.0f || ability->abilityType == EAbilityType::Basic)
m_channelStun = group->stunWhileChannel;
else
{
if (group->stunWhileChannel)
{
ASpeedModifier* sModifier = GetWorld()->SpawnActor<ASpeedModifier>();
sModifier->lifeTime = group->duration;
sModifier->speedMultiplier = m_castingMovementspeedMultiplier;
sModifier->target = this;
GetModifierManager()->AddModifier(sModifier);
}
}
}
else
{
m_allowChannelRotation = false;
m_channelGroup = nullptr;
}
group->onAbilityEventGroupEnded.AddDynamic(this, &ANetworkCharacter::m_OnAbilityEventGroupEnded);
m_activeAbilities.Add(ability, group);
m_OnAbilityCastConfirm_Server(ability, true);
}
void ANetworkCharacter::m_PreCast(UAbilityInfo* ability)
{
if(ability->mana * m_manaUsageMultiplier <= m_mana)
m_currentPreCast = APreCastAbilityEventGroup::InitPreCast(ability, this);
}
void ANetworkCharacter::m_TickAbilities(float DeltaSeconds)
{
for(auto it = m_abilityStates.CreateIterator(); it; ++it)
{
AAbilityState* state = it.Value();
if((state->onCooldownTimer -= (DeltaSeconds * (1.0f + m_cooldownReduction))) <= 0.0f)
{
state->onCooldownTimer = 0.0f;
}
state->cooldownRate = state->onCooldownTimer / it.Key()->cooldown;
}
if(m_modifierManager)
m_modifierManager->Tick(DeltaSeconds);
m_timeSinceDamage += DeltaSeconds;
}
void ANetworkCharacter::m_SetAbilityCooldown(class UAbilityInfo* ability, class AAbilityState* state)
{
if(ability->abilityType == EAbilityType::Basic)
{
// This is the character's basic attack
state->onCooldownTimer = state->currentCooldownDuration = 1.0f / m_attackSpeed;
state->cooldownRate = (state->currentCooldownDuration > 0.0f) ? (state->onCooldownTimer / state->currentCooldownDuration) : 0.0f;
}
else
{
// This is an ability
state->NativeSetCooldown();
}
}
void ANetworkCharacter::m_OnAbilityCastConfirm_Server(UAbilityInfo* ability, bool success)
{
AAbilityState* state = GetAbilityState(ability);
m_OnAbilityCastConfirm(ability, success, state->toggleState, state->onCooldownTimer, state->currentCooldownDuration);
#if UE_INCLUDE_METRICS
if (metricsHandle && success && !ability->passive)
{
// Record ability cast
if (!ability->IsHoldOrToggle() || state->toggleState)
{
std::wstring abilityName = std::wstring() + ability->name;
if (abilityName.size() > 512)
abilityName.resize(512);
Metrics::RegisterAbility(ability->GetStaticHash(), abilityName);
metricsHandle->OnPlayerCast(ability->GetStaticHash());
}
}
#endif
return;
}
void ANetworkCharacter::m_OnAbilityEventGroupEnded(class UAbilityInfo* ability, class AAbilityEventGroup* current, class AAbilityEventGroup* next)
{
check(Role == ROLE_Authority);
check(ability);
AAbilityEventGroup** group = m_activeAbilities.Find(ability);
check(group);
*group = next;
// Check end of channeling ability
if(m_channelGroup == current)
{
if(!next || !next->channelEvent)
{
//GWPRINT(L"End ability channel on ability " + m_castingAbility->GetName());
m_castingAbility = nullptr;
m_channelGroup = nullptr;
m_channelStun = false;
}
else
{
m_channelGroup = next;
m_channelStun = m_channelGroup->stunWhileChannel;
}
}
if(!next)
{
// Turn of toggle state if it was active
AAbilityState* state = GetAbilityState(ability);
if(state->toggleState == true)
{
state->toggleState = false;
m_SetAbilityCooldown(ability, state);
m_OnAbilityCastConfirm_Server(ability, true);
}
}
else
{
// Subscribe to next group ended event
next->onAbilityEventGroupEnded.AddDynamic(this, &ANetworkCharacter::m_OnAbilityEventGroupEnded);
}
//GWPRINT(L"Ability " + ability->GetName() + L" reached next state " + (next ? next->GetName() : FString(L"end")));
}
void ANetworkCharacter::CastAbility(int32 abilityIndex)
{
if (abilityIndex < 0 || abilityIndex >= abilities.Num())
{
GWERROR(L"Ability index out of range");
return;
}
UAbilityInfo* ability = abilities[abilityIndex];
if (!ability)
{
GWERROR(L"Null ability assigned and casted on character character[" + GetName() + L"] slot[" + abilityIndex + L"]");
return;
}
CastAbility(ability);
}
void ANetworkCharacter::CastAbility(UAbilityInfo* ability)
{
if (!ability)
{
GWERROR(L"Ability pointer invalid");
return;
}
if(IsPendingKill() || IsPendingKillPending())
return;
AAbilityState* state = GetAbilityState(ability);
if(state->onCooldownTimer <= 0.0f && ability->precastEvent == nullptr)
{
m_CastAbility_Server(ability);
}
else if(ability->precastEvent != nullptr && state->onCooldownTimer <= 0.0f)
{
m_PreCast(ability);
}
}
class UAbilityInfo* ANetworkCharacter::GetCastingAbility()
{
return m_castingAbility;
}
AAbilityState* ANetworkCharacter::GetAbilityState(UAbilityInfo* abilityInfo)
{
AAbilityState** state = m_abilityStates.Find(abilityInfo);
if (!state)
{
// Don't check anymore if this character is allowed to cast an ability
// TODO: check this only for players in CastAbility_Validate when the new character class hierarchy is implemented
//check(abilities.Contains(abilityInfo) || passives.Contains(abilityInfo));
if (abilityInfo == nullptr)
{
FERROR("abilityInfo == nullptr");
return nullptr;
}
if(!abilityInfo->abilityState)
{
GWERROR(L"Ability " + abilityInfo->GetName() + L" does not have an ability state class assigned");
abilityInfo->abilityState = AAbilityState::StaticClass();
}
AAbilityState* abs = GetWorld()->SpawnActor<AAbilityState>(abilityInfo->abilityState);
abs->onCooldownTimer = 0.0f;
abs->cooldownRate = 0.0f;
abs->power = 0.0f;
abs->info = abilityInfo;
return m_abilityStates.Add(abilityInfo, abs);
}
return *state;
}
bool ANetworkCharacter::GetAbilityToggleState(class UAbilityInfo* abilityInfo)
{
AAbilityState* state = GetAbilityState(abilityInfo);
if(state)
{
return state->toggleState;
}
return false;
}
void ANetworkCharacter::m_InteruptSpellcasting()
{
if (m_channelGroup)
{
m_channelGroup->Interrupt();
m_channelGroup = nullptr;
m_castingAbility = nullptr;
m_channelStun = false;
}
}
ModifierManager* ANetworkCharacter::GetModifierManager()
{
return m_modifierManager;
}
TArray<AModifier*> ANetworkCharacter::GetModifiersOfClass(TSubclassOf<class AModifier> modifierClass)
{
if (m_modifierManager)
return m_modifierManager->GetModifiersOfClass(modifierClass);
return TArray<AModifier*>();
}
void ANetworkCharacter::ResetModifiers()
{
m_blockedMana = 0;
m_attackDamageMultiplier = 1.0f;
m_magicDamageMultiplier = 1.0f;
m_damageMultiplier = 1.0f;
m_manaRegenMultiplier = 1.0f;
m_stunned = false;
m_stunned = false;
m_damageTakenMultiplier = 1.0f;
m_cooldownReduction = 0.0f;
m_ignoreArmor = 0.0f;
m_manaUsageMultiplier = 1.0f;
m_positiveEffectMultiplier = 1.0f;
m_negativeEffectMultiplier = 1.0f;
m_castingMovementspeedMultiplier = 0.0f;
m_armor = baseArmor;
GetCharacterMovement()->MaxWalkSpeed = baseMovementSpeed;
m_SetLevelStats();
}
void ANetworkCharacter::m_SetLevelStats()
{
}
void ANetworkCharacter::CheckStatsOverflow()
{
if (m_health > m_maxHealth || !m_initialised)
m_health = m_maxHealth;
if (m_mana > m_maxMana || !m_initialised)
m_mana = m_maxMana;
}
int32 ANetworkCharacter::DealDamage(UObject* WorldContextObject, int32 damage, float armorPercentageIgnore)
{
ADealDamageProxy* damageProxy = Cast<ADealDamageProxy>(WorldContextObject);
if(!damageProxy)
{
GWERROR(L"DealDamage can not be called from an object that does not inherit from ADealDamageProxy");
return 0;
}
int32 damageDealth = NativeDealDamage(damageProxy->character, damage, armorPercentageIgnore, damageProxy->abilityInfo);
return damageDealth;
}
int32 ANetworkCharacter::NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore, UAbilityInfo* ability)
{
// Ensure that this is only called on the server
check(Role == ROLE_Authority);
if(dealer == NULL || dealer->IsPendingKill())
{
FWARNING("character in dealdamage is null or pendingkill");
return 0;
}
// Scale the damage done or taken by creatures and players
if (dealer->IsA<ANetworkPlayer>() != this->IsA<ANetworkPlayer>())
{
ANetworkPlayer* const player = dealer->IsA<ANetworkPlayer>() ? Cast<ANetworkPlayer>(dealer) : Cast<ANetworkPlayer>(this);
if (IsValid(player))
{
ADefaultPlayerState* const state = Cast<ADefaultPlayerState>(player->PlayerState);
if (IsValid(state))
{
UCurveFloat* const curve = player == this ? player->creatureDamageDealtCurve : player->creatureDamageTakenCurve;
if (curve)
{
const float scalar = curve->GetFloatValue((float)state->GetLevel());
damage = int32((float)damage * (scalar < 0.0f ? 0.0f : scalar));
}
}
}
}
if(damage < 0)
{
GWERROR(L"Negative damage dealt by " + ability->GetName());
return 0;
}
const int32 damageCap = 1000;
if(damage > damageCap)
{
GWERROR(L"A lot of damage (>" + damageCap + L") dealt by " + ability->GetName() + ", is this intended?");
}
if(armorPercentageIgnore > 1.0f || armorPercentageIgnore < 0.0f)
{
GWERROR(L"Invalid armorPercentageIgnore given by " + ability->GetName() + "will use 0.0f");
armorPercentageIgnore = 0.0f;
// return 0;
}
float totalArmorPercentage = armorPercentageIgnore + dealer->m_ignoreArmor;
totalArmorPercentage = std::fmax(0.0f, std::fmin(totalArmorPercentage, 1.0f));
if(m_damageTakenMultiplier == 0.0f || dealer->m_damageMultiplier <= 0.0f)
return 0;
float divider = 1 + 0.01f * ((float)m_armor * (1 - totalArmorPercentage));
float damageToDeal;
// The damage to deal
damageToDeal = damage / divider;
damageToDeal *= dealer->m_damageMultiplier;
// Multiply by weakness aka damage taken multiplier
damageToDeal *= m_damageTakenMultiplier;
// Magic damage has a different multiplier
if (ability->abilityType == EAbilityType::Ability)
damageToDeal *= dealer->m_magicDamageMultiplier;
int32 iDamageToDeal = damageToDeal;
bool dealdamage = true;
for (AModifier* mod : m_modifierManager->m_modifiers)
{
if (!mod->OnDamage(iDamageToDeal, dealer))
{
dealdamage = false;
}
}
if (dealer->GetModifierManager() == nullptr)
return 0;
for (AModifier* mod : dealer->GetModifierManager()->m_modifiers)
{
if (!mod->OnDealDamage(iDamageToDeal, this))
{
dealdamage = false;
}
}
if(!dealdamage)
return 0;
#if UE_INCLUDE_METRICS
if (dealer->metricsHandle && IsValid(dealer))
{
// Record ability damage dealt
dealer->metricsHandle->OnPlayerDealDamage(ability->GetStaticHash(), iDamageToDeal);
}
#endif
if(iDamageToDeal > m_health)
iDamageToDeal = m_health;
RemoveHealth(iDamageToDeal);
m_lastPlayerDamage = dealer;
onDamageTaken.Broadcast(dealer, damage, ability);
// Update the entry for this team in the damage dealt table
const int32 team = dealer->GetTeam();
if (experienceGainOnKill > 0 && team < 5)
{
auto find = m_teamDamageDealt.find(team);
if (find != m_teamDamageDealt.end())
{
find->second.damage += iDamageToDeal;
find->second.timer = teamDamageTimer;
}
else
m_teamDamageDealt.emplace(team, DamagePeriod(iDamageToDeal, teamDamageTimer));
}
// Spawn combat text
ADefaultPlayerController* const controller = Cast<ADefaultPlayerController>(dealer->GetController());
if (IsValid(controller))
{
auto find = m_damageAccum.find(controller);
if (find == m_damageAccum.end())
m_damageAccum.emplace(controller, DamagePeriod(iDamageToDeal, damageAccumTimer));
else
find->second.damage += iDamageToDeal;
}
for (AModifier* mod : m_modifierManager->m_modifiers)
mod->AfterDamage(iDamageToDeal);
m_timeSinceDamage = 0;
if(m_health <= 0)
{
NativeOnKilled(dealer, ability);
OnDeath(ability);
}
return iDamageToDeal;
}
void ANetworkCharacter::NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability)
{
// Score counting
ANetworkPlayer* const thisPlayer = Cast<ANetworkPlayer>(this);
if (IsValid(thisPlayer))
{
ADefaultPlayerState* state = Cast<ADefaultPlayerState>(thisPlayer->PlayerState);
if (IsValid(state)) state->deaths++;
ANetworkPlayer* const killerPlayer = Cast<ANetworkPlayer>(killer);
if (IsValid(killerPlayer))
{
state = Cast<ADefaultPlayerState>(killerPlayer->PlayerState);
if (IsValid(state)) state->kills++;
}
}
if (experienceGainOnKill > 0 && !m_teamDamageDealt.empty())
{
ADefaultGameState* gameState = Cast<ADefaultGameState>(GetWorld()->GetGameState());
TArray<ADefaultPlayerState*> players = gameState->GetPlayers<ADefaultPlayerState>();
// Fetch highest team
int32 highestTeam = -1;
int32 highestDamage = 0;
for (auto i : m_teamDamageDealt)
{
if (i.second.damage > highestDamage)
{
highestDamage = i.second.damage;
highestTeam = i.first;
}
}
m_teamDamageDealt.clear();
// Apply experience
if (highestTeam > 0)
{
for (size_t i = 0; i < players.Num(); i++)
{
if (!players[i])
continue;
// Award the experience
if(players[i]->GetTeam() == highestTeam)
{
players[i]->GainExperience(experienceGainOnKill);
// Spawn EXP gain effect
FTransform t;
if(players[i]->character)
{
t.SetTranslation(players[i]->character->GetActorLocation());
ATargetedEffect* fx = GetWorld()->SpawnActorDeferred<ATargetedEffect>(expGainEffect, t);
fx->Init(0.0f, players[i]->character);
fx->target = GetActorLocation();
UGameplayStatics::FinishSpawningActor(fx, t);
}
}
}
}
}
// Clear the damage accum table
if (!m_damageAccum.empty())
{
for (auto iter = m_damageAccum.begin(); iter != m_damageAccum.end();)
{
// Controller no longer exists
if (IsValid(iter->first))
{
DamagePeriod& value = iter->second;
if (value.damage > 0)
iter->first->SpawnArcingCombatText(GetActorLocation(), FString::FromInt(value.damage), FLinearColor::White);
}
iter = m_damageAccum.erase(iter);
}
}
#if UE_INCLUDE_METRICS
if(metricsHandle && IsValid(killer))
{
// Record player death
if(killer->metricsHandle)
metricsHandle->OnPlayerDie(killer->metricsHandle->Id());
else
metricsHandle->OnPlayerDie(0);
}
#endif
onCharacterKilled.Broadcast(killer, ability);
}
void ANetworkCharacter::OnStandardAttack(ANetworkCharacter* targetCharacter)
{
if (m_modifierManager==nullptr)
{
FWARNING("m_modifierManager==nullptr");
return;
}
for (AModifier* mod : m_modifierManager->m_modifiers)
{
if(IsValid(mod))
mod->OnStandardAttack(targetCharacter);
}
}
void ANetworkCharacter::AddHealth(int32 health)
{
NativeAddHealth(health);
}
void ANetworkCharacter::NativeAddHealth(int32 health)
{
check(Role == ROLE_Authority); // server call only
check(health >= 0);
if (m_health < m_maxHealth)
{
// Spawn combat text
ADefaultPlayerController* const controller = Cast<ADefaultPlayerController>(GetController());
if (IsValid(controller))
{
m_healAccumValue += health;
if (m_healAccumTimer <= 0.0f) m_healAccumTimer += healthAccumTimer;
}
m_health += health;
if (m_health > m_maxHealth)
m_health = m_maxHealth;
}
}
void ANetworkCharacter::RemoveHealth(int32 health)
{
check(Role == ROLE_Authority); // Server only call
m_health -= health;
}
void ANetworkCharacter::AddMana(float mana)
{
check(Role == ROLE_Authority); // Server only call
m_mana += mana * m_manaRegenMultiplier;
if (m_mana > (m_maxMana-m_blockedMana))
m_mana = (m_maxMana - m_blockedMana);
}
void ANetworkCharacter::RemoveMana(float mana)
{
check(Role == ROLE_Authority); // Server only call
if(mana > m_mana)
{
mana = m_mana; // mana has to remain positive or 0
}
m_mana -= mana;
}
int32 ANetworkCharacter::GetMana() const
{
return m_mana;
}
int32 ANetworkCharacter::GetBlockedMana() const
{
return m_blockedMana;
}
int32 ANetworkCharacter::GetMaxMana() const
{
return m_maxMana;
}
int32 ANetworkCharacter::GetHealth() const
{
return m_health;
}
int32 ANetworkCharacter::GetMaxHealth() const
{
return m_maxHealth;
}
int32 ANetworkCharacter::GetArmor() const
{
return m_armor;
}
float ANetworkCharacter::GetDamageMultiplier() const
{
return m_damageMultiplier;
}
float ANetworkCharacter::GetCooldownReduction() const
{
return m_cooldownReduction;
}
float ANetworkCharacter::GetAttackSpeed() const
{
return m_attackSpeed;
}
float ANetworkCharacter::GetAttackDamage() const
{
return m_attackDamageMultiplier;
}
float ANetworkCharacter::GetMagicDamage() const
{
return m_magicDamageMultiplier;
}
float ANetworkCharacter::GetTotalDamage() const
{
return m_totalDamage;
}
bool ANetworkCharacter::GetHitable() const
{
return m_hitable;
}
void ANetworkCharacter::SetHitable(bool hittable)
{
m_hitable = hittable;
}
bool ANetworkCharacter::IsStunned() const
{
return m_stunned || m_channelStun;
}
bool ANetworkCharacter::CanRotate() const
{
if(m_stunned)
return false;
if(m_channelStun && !m_allowChannelRotation)
return false;
return true;
}
bool ANetworkCharacter::IsSilenced() const
{
return m_silenced;
}
bool ANetworkCharacter::IsChanneling() const
{
return m_castingAbility != nullptr;
}
bool ANetworkCharacter::IsRanged() const
{
return m_isRanged;
}
ANetworkCharacter* ANetworkCharacter::GetLastPlayerDamage() const
{
return IsValid(m_lastPlayerDamage) ? m_lastPlayerDamage : nullptr;
}
bool ANetworkCharacter::CanBeStunned() const
{
return canBeStunned;
}
float ANetworkCharacter::GetTimeSinceDamage() const
{
return m_timeSinceDamage;
}
bool ANetworkCharacter::IsVisible() const
{
return m_visible;
}