// 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 expGainEffect; ANetworkCharacter::ANetworkCharacter() { expGainEffect = ConstructorHelpers::FClassFinder(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 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(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(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(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(world->GetFirstPlayerController()); if(IsValid(controller)) { APlayerStateBase* playerState = Cast(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(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& 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& skills) { ADefaultPlayerController* pc = Cast(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& skills) { // Server side call LearnSkills_Implementation(skills); // Client side call LearnSkills(skills); } void ANetworkCharacter::m_SpawnModifiers() { ARegenModifier* regenMod = Cast(m_modifierManager->AddModifier(ARegenModifier::StaticClass(), 0)); regenMod->regenPerTick = passiveHealthRegen; AManaRegenModifier* manaMod = Cast(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(); 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(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 ANetworkCharacter::GetModifiersOfClass(TSubclassOf modifierClass) { if (m_modifierManager) return m_modifierManager->GetModifiersOfClass(modifierClass); return TArray(); } 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(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() != this->IsA()) { ANetworkPlayer* const player = dealer->IsA() ? Cast(dealer) : Cast(this); if (IsValid(player)) { ADefaultPlayerState* const state = Cast(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(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(this); if (IsValid(thisPlayer)) { ADefaultPlayerState* state = Cast(thisPlayer->PlayerState); if (IsValid(state)) state->deaths++; ANetworkPlayer* const killerPlayer = Cast(killer); if (IsValid(killerPlayer)) { state = Cast(killerPlayer->PlayerState); if (IsValid(state)) state->kills++; } } if (experienceGainOnKill > 0 && !m_teamDamageDealt.empty()) { ADefaultGameState* gameState = Cast(GetWorld()->GetGameState()); TArray players = gameState->GetPlayers(); // 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(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(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; }