// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraBotCreationComponent.h" #include "LyraGameMode.h" #include "Engine/World.h" #include "GameFramework/PlayerState.h" #include "GameModes/LyraExperienceDefinition.h" #include "GameModes/LyraExperienceManagerComponent.h" #include "Development/LyraDeveloperSettings.h" #include "Player/LyraPlayerState.h" #include "GameFramework/PlayerController.h" #include "Character/LyraPawnExtensionComponent.h" #include "AIController.h" #include "Kismet/GameplayStatics.h" #include "Character/LyraHealthComponent.h" ULyraBotCreationComponent::ULyraBotCreationComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void ULyraBotCreationComponent::BeginPlay() { Super::BeginPlay(); // Listen for the experience load to complete AGameStateBase* GameState = GetGameStateChecked(); ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); check(ExperienceComponent); ExperienceComponent->CallOrRegister_OnExperienceLoaded_LowPriority(FOnLyraExperienceLoaded::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded)); } void ULyraBotCreationComponent::OnExperienceLoaded(const ULyraExperienceDefinition* Experience) { #if WITH_SERVER_CODE if (HasAuthority()) { ServerCreateBots(); } #endif } #if WITH_SERVER_CODE void ULyraBotCreationComponent::ServerCreateBots() { if (BotControllerClass == nullptr) { return; } RemainingBotNames = RandomBotNames; // Determine how many bots to spawn int32 EffectiveBotCount = NumBotsToCreate; // Give the developer settings a chance to override it if (GIsEditor) { const ULyraDeveloperSettings* DeveloperSettings = GetDefault(); if (DeveloperSettings->bOverrideBotCount) { EffectiveBotCount = DeveloperSettings->OverrideNumPlayerBotsToSpawn; } } // Give the URL a chance to override it if (AGameModeBase* GameModeBase = GetGameMode()) { EffectiveBotCount = UGameplayStatics::GetIntOption(GameModeBase->OptionsString, TEXT("NumBots"), EffectiveBotCount); } // Create them for (int32 Count = 0; Count < EffectiveBotCount; ++Count) { SpawnOneBot(); } } FString ULyraBotCreationComponent::CreateBotName(int32 PlayerIndex) { FString Result; if (RemainingBotNames.Num() > 0) { const int32 NameIndex = FMath::RandRange(0, RemainingBotNames.Num() - 1); Result = RemainingBotNames[NameIndex]; RemainingBotNames.RemoveAtSwap(NameIndex); } else { //@TODO: PlayerId is only being initialized for players right now PlayerIndex = FMath::RandRange(260, 260+100); Result = FString::Printf(TEXT("Tinplate %d"), PlayerIndex); } return Result; } void ULyraBotCreationComponent::SpawnOneBot() { FActorSpawnParameters SpawnInfo; SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; SpawnInfo.OverrideLevel = GetComponentLevel(); SpawnInfo.ObjectFlags |= RF_Transient; AAIController* NewController = GetWorld()->SpawnActor(BotControllerClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnInfo); if (NewController != nullptr) { ALyraGameMode* GameMode = GetGameMode(); check(GameMode); if (NewController->PlayerState != nullptr) { NewController->PlayerState->SetPlayerName(CreateBotName(NewController->PlayerState->GetPlayerId())); } GameMode->DispatchPostLogin(NewController); GameMode->RestartPlayer(NewController); if (NewController->GetPawn() != nullptr) { if (ULyraPawnExtensionComponent* PawnExtComponent = NewController->GetPawn()->FindComponentByClass()) { PawnExtComponent->CheckDefaultInitialization(); } } SpawnedBotList.Add(NewController); } } void ULyraBotCreationComponent::RemoveOneBot() { if (SpawnedBotList.Num() > 0) { // Right now this removes a random bot as they're all the same; could prefer to remove one // that's high skill or low skill or etc... depending on why you are removing one const int32 BotToRemoveIndex = FMath::RandRange(0, SpawnedBotList.Num() - 1); AAIController* BotToRemove = SpawnedBotList[BotToRemoveIndex]; SpawnedBotList.RemoveAtSwap(BotToRemoveIndex); if (BotToRemove) { // If we can find a health component, self-destruct it, otherwise just destroy the actor if (APawn* ControlledPawn = BotToRemove->GetPawn()) { if (ULyraHealthComponent* HealthComponent = ULyraHealthComponent::FindHealthComponent(ControlledPawn)) { // Note, right now this doesn't work quite as desired: as soon as the player state goes away when // the controller is destroyed, the abilities like the death animation will be interrupted immediately HealthComponent->DamageSelfDestruct(); } else { ControlledPawn->Destroy(); } } // Destroy the controller (will cause it to Logout, etc...) BotToRemove->Destroy(); } } } #endif