// Fill out your copyright notice in the Description page of Project Settings. #include "LMWaveManager.h" #include "EngineUtils.h" #include "Components/CapsuleComponent.h" #include "Enemy/LMEnemy.h" // Sets default values ALMWaveManager::ALMWaveManager() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; } // Called when the game starts or when spawned void ALMWaveManager::BeginPlay() { Super::BeginPlay(); SpawnPositionsList.Empty(); // Nettoie la liste avant de remplir for (TActorIterator It(GetWorld()); It; ++It) { ALMSpawnPosition* SpawnPos = *It; if (SpawnPos) { SpawnPositionsList.Add(SpawnPos); } } StartWave(); } void ALMWaveManager::SpawnEnemy(ALMSpawnPosition* spawnPosition) { int RandomIndex = FMath::RandRange(0, AllEnemyType.Num()-1); while(EnemiesPerType[RandomIndex] <= 0 ) { RandomIndex = FMath::RandRange(0, AllEnemyType.Num()-1); } //Choisis un ennemis à spawn random TSubclassOf enemyToSpawn = AllEnemyType[RandomIndex]; //Spawn sur une position FActorSpawnParameters SpawnParameters; SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; ALMEnemy* tempEnemy = GetWorld()->SpawnActor(enemyToSpawn, spawnPosition->GetActorLocation(), FRotator::ZeroRotator); if (!tempEnemy) { //UE_LOG(LogTemp, Error, TEXT("Failed to spawn enemy!")); return; } spawnPosition->StarCooldown(); float tailleCapsule = tempEnemy->GetCapsuleComponent()->GetScaledCapsuleHalfHeight(); FVector newPosition = tempEnemy->GetActorLocation(); tempEnemy->SetActorLocation(FVector(newPosition.X,newPosition.Y,newPosition.Z + tailleCapsule)); //ajoute l'ennemis à la liste des ennemis vivants EnemyAliveList.Add(tempEnemy); //bind la method ennemydead à la death de l'ennemis tempEnemy->OnEnemyDeath.AddDynamic(this, &ALMWaveManager::EnemyDead); //UE_LOG(LogTemp, Error, TEXT("Succes to spawn and bind on death method")); EnemySpawned++; EnemiesPerType[RandomIndex]--; } void ALMWaveManager::EnemyDead(ALMEnemy* enemyToRemoveFromLife) { //remove l'ennemis de la liste des ennemis vivants enemyToRemoveFromLife->OnEnemyDeath.RemoveDynamic(this,&ALMWaveManager::EnemyDead); EnemyAliveList.Remove(enemyToRemoveFromLife); } bool ALMWaveManager::RemainsEnemyToSpawn() { bool StillToSpawn = TotalOfEnemyToStillSpawn() !=0; bool NoEnemyLeftToSpawn = EnemyAliveList.Num() < MaxEnemyInstantiate; return EnemyNumberInWave != EnemySpawned && NoEnemyLeftToSpawn && StillToSpawn; } void ALMWaveManager::CheckForSpawnerOK() { SpawnPositionsOK.Empty(); for (ALMSpawnPosition* spawnPosition : SpawnPositionsList) { if(spawnPosition->CanSpawn()) { SpawnPositionsOK.Add(spawnPosition); } } } int ALMWaveManager::TotalOfEnemyToStillSpawn() { int TotalEnemies = 0; for (int EnemyCount : EnemiesPerType) { TotalEnemies += EnemyCount; } return TotalEnemies; } void ALMWaveManager::GetRandomDataWaveRow() { TArray RowNames = WaveDatePreset->GetRowNames(); int RandomIndex = FMath::RandRange(0, RowNames.Num() - 1);; FName WaveName = RowNames[RandomIndex]; CurrentWaveName = WaveName.ToString(); FLMWaveStructure* InfoWaveRow = WaveDatePreset->FindRow(WaveName, ""); //Set le nombre d'ennemis dans la wave en fonction de la Row, de la courbe et du nombre de vague passée EnemyNumberInWave = FMath::CeilToInt((InfoWaveRow->EnemyCount)* CurveForScalingEnemyInWave->GetFloatValue(WaveNumber)); //Set le nombre d'ennemis max spawnés en fonction de la Row, de la courbe, et du nombre de vague passée MaxEnemyInstantiate = FMath::CeilToInt((InfoWaveRow->MaxEnemyCount) * CurveForScalingMaxEnemyInstantiate->GetFloatValue(WaveNumber)); AllEnemyType.Empty(); for (const FLMEnemyRatio& EnemyRatio : InfoWaveRow->WaveComposition) { // Ajoute chaque EnemyType à la liste AllEnemyType.Add(EnemyRatio.EnemyType); } EnemiesPerType.Empty(); // Récupère les ratios (NbEnemy) et calcule leur somme int32 TotalRatio = 0; for (const FLMEnemyRatio& EnemyRatio : InfoWaveRow->WaveComposition) { TotalRatio += EnemyRatio.NbEnemy; } // Sécurité : Si le total est 0, évite la division par zéro if (TotalRatio == 0) return; // Répartit les ennemis proportionnellement int32 RemainingEnemies = EnemyNumberInWave; for (const FLMEnemyRatio& EnemyRatio : InfoWaveRow->WaveComposition) { // Calcul proportionnel arrondi int32 Count = FMath::RoundToInt((float(EnemyRatio.NbEnemy) / TotalRatio) * EnemyNumberInWave); EnemiesPerType.Add(Count); RemainingEnemies -= Count; } // Ajuste les arrondis pour respecter exactement EnemyNumberInWave for (int32 i = 0; i < RemainingEnemies; ++i) { EnemiesPerType[i % EnemiesPerType.Num()]++; } } // Called every frame void ALMWaveManager::Tick(float DeltaTime) { Super::Tick(DeltaTime); if(!OnCooldown) { if(RemainsEnemyToSpawn()) { CheckForSpawnerOK(); if(SpawnPositionsOK.Num() > 0) { //Choose spawnpoint to spawn int32 RandomIndex = FMath::RandRange(0, SpawnPositionsOK.Num() - 1); SpawnEnemy(SpawnPositionsOK[RandomIndex]); } return; } if(TotalOfEnemyToStillSpawn() == 0 && EnemyAliveList.Num() == 0) { //lauch break time // Lance un breaktime avant de relancer une wave OnCooldown = true; GetWorld()->GetTimerManager().SetTimer(BreakTimer, this, &ALMWaveManager::StartWave, BreakTime, false); } } } void ALMWaveManager::StartWave() { UE_LOG(LogTemp, Warning, TEXT("Start a new wave")); GetRandomDataWaveRow(); EnemySpawned = 0; OnCooldown = false; WaveNumber++; }