// Copyright Epic Games, Inc. All Rights Reserved. #include "IntegrationGameModeBase.h" #include "lpx-drvutil.hpp" #include "lpx-paths.hpp" #include "DebugPrint.h" #include "Tangible.h" #include "TangibleManager.h" #include "CommonTypes.h" #include "AnimQueue.h" #include #include using namespace DebugPrint; using namespace CommonTypes; AIntegrationGameModeBase::AIntegrationGameModeBase() { TangibleManager = NewObject(); PlayerId = 0; EngineSeconds = 0.0; //PrimaryActorTick.bCanEverTick = true; // Probably wrong //PrimaryActorTick.bTickEvenWhenPaused = true; // Probably wrong //PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong SetActorTickEnabled(true); SetActorTickInterval(0.0f); DebugPrintControl::EnableCollection(); ResetToInitialState(); OnWorldPreActorTickHandle = FWorldDelegates::OnWorldPreActorTick.AddUObject(this, &AIntegrationGameModeBase::OnWorldPreActorTick); OnWorldPostActorTickHandle = FWorldDelegates::OnWorldPostActorTick.AddUObject(this, &AIntegrationGameModeBase::OnWorldPostActorTick); } AIntegrationGameModeBase::~AIntegrationGameModeBase() { ResetToInitialState(); FWorldDelegates::OnWorldPreActorTick.Remove(OnWorldPreActorTickHandle); FWorldDelegates::OnWorldPostActorTick.Remove(OnWorldPostActorTickHandle); } // This method runs in the background thread, // at the moment we trigger it. // uint32 AIntegrationGameModeBase::Run() { FlxLockedWrapper lockedwrap(LockableWrapper); Sockets->Update(lockedwrap); lockedwrap->play_update(lockedwrap.Get(), EngineSeconds); Sockets->Update(lockedwrap); return 0; } void AIntegrationGameModeBase::ResetToInitialState() { Playing = false; if (TangibleManager != nullptr) { TangibleManager->ConditionalBeginDestroy(); TangibleManager = nullptr; } // Shut down the thread LuprexUpdateTask.Shutdown(); // Now that the thread's gone, we should be able to // just claim and hold the lock on the wrapper. FlxLockedWrapper w(LockableWrapper); // Release and close all sockets. if (Sockets != nullptr) { Sockets->ForceCloseEverything(w); Sockets.Reset(); } // Delete the engine. if (w->release != nullptr) { w->release(w.Get()); } // Clear the lua call assembly buffer. LuaCallBuffer.clear(); // Clear the PlayerID PlayerId = 0; // Clear the camera ray state. CameraRayTarget = nullptr; CameraRayChanged = false; // Reset the clocks. EngineSeconds = 0.0; NextRotateCube = 1.0; } void AIntegrationGameModeBase::UpdateConsoleOutput() { // Copy Luprex Stdout into the console. FlxLockedWrapper lockedwrap(LockableWrapper); if (Playing) { ConsoleOutput.Append(lockedwrap.FetchStdout()); } // Copy Debugging Prints into the console. TArray prints = DebugPrintControl::GetStored(); for (const FString& fs : prints) { ConsoleOutput.AppendLine(fs); } // If the Console text has changed, update the widget. if (ConsoleOutput.IsDirty()) { ConsoleSetOutput(ConsoleOutput.Get()); ConsoleOutput.ClearDirty(); } } #pragma optimize("", off) void AIntegrationGameModeBase::UpdateTangibles() { double radius = 1000.0; // Hardwired for now. using TanArray = UlxTangibleManager::TanArray; if (!Playing) return; TanArray alltans; { FlxLockedWrapper w(LockableWrapper); PlayerId = w.GetActor(); IdView nearids = w.GetNear(PlayerId, radius, radius, radius); TangibleManager->UpdateNearAccordingToLuprex(nearids); alltans = TangibleManager->GetAllTangibles(); IdArray allids = TangibleManager->GetIds(alltans); StringViewVec allqueues = w.GetAnimationQueues(allids); for (int i = 0; i < alltans.Num(); i++) { alltans[i]->UpdateAnimationQueue(allqueues[i]); } } // This is where we run the blueprint code that updates animation // states. Be aware that the blueprint code could call back // into the gamemode, which could in turn access the FlxLockedWrapper. // This is why we've released the FlxLockedWrapper before doing this. for (int i = 0; i < alltans.Num(); i++) { alltans[i]->MaybeExecuteAnimStateChanged(); } TangibleManager->RecalcNearAccordingToUnreal(PlayerId, radius); TangibleManager->DeleteFarawayTangibles(); } void AIntegrationGameModeBase::UpdatePossessedTangible() { bool updated = TangibleManager->SetPossessedTangible(PlayerId); if (updated) { UlxTangible *ptan = TangibleManager->GetPossessedTangible(); if (ptan != nullptr) { IlxTangibleInterface::Execute_BecomePossessed(ptan->GetActor()); } } } void AIntegrationGameModeBase::CastCameraRay() { // The range we're using is currently hardwired. double range = 1000.0; // If there's no possessed tangible, then we don't even try casting the ray. UlxTangible *possessed = TangibleManager->GetPossessedTangible(); if (possessed == nullptr) { CameraRayChanged = (CameraRayTarget != nullptr); CameraRayTarget = nullptr; return; } // Get the ray in question. // APlayerCameraManager *camManager = GetWorld()->GetFirstPlayerController()->PlayerCameraManager; // FVector TraceStart = camManager->GetCameraLocation(); // FVector TraceEnd = camManager->GetCameraRotation().Vector() * range; FVector TraceStart, TraceVector; APlayerController *ctrl = GetWorld()->GetFirstPlayerController(); ctrl->DeprojectScreenPositionToWorld(0.5, 0.5, TraceStart, TraceVector); FVector TraceEnd = TraceStart + (TraceVector * range); // FHitResult will hold all data returned by our line collision query FHitResult Hit; // You can use FCollisionQueryParams to further configure the query // Here we add ourselves to the ignored list so we won't block the trace FCollisionQueryParams QueryParams; QueryParams.AddIgnoredActor(possessed->GetActor()); // To run the query, you need a pointer to the current level, which you can get from an Actor with GetWorld() // UWorld()->LineTraceSingleByChannel runs a line trace and returns the first actor hit over the provided collision channel. GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, QueryParams); // You can use DrawDebug helpers and the log to help visualize and debug your trace queries. // DrawDebugLine(GetWorld(), TraceStart, TraceEnd, Hit.bBlockingHit ? FColor::Blue : FColor::Red, false, 5.0f, 0, 10.0f); // UE_LOG(LogTemp, Log, TEXT("Tracing line: %s to %s"), *TraceStart.ToCompactString(), *TraceEnd.ToCompactString()); // If the trace hit something, bBlockingHit will be true, // and its fields will be filled with detailed info about what was hit if (Hit.bBlockingHit && IsValid(Hit.GetActor())) { CameraRayChanged = (CameraRayTarget != Hit.GetActor()); CameraRayTarget = Hit.GetActor(); } else { CameraRayChanged = (CameraRayTarget != nullptr); CameraRayTarget = nullptr; } } void AIntegrationGameModeBase::LuaCallEnd(InvocationKind kind, int64 place_id) { std::string_view datapk = LuaCallBuffer.view(); FlxLockedWrapper w(LockableWrapper); if (place_id == 0) place_id = w.GetActor(); uint32_t retpklen; const char *retpk; w->play_call_function(w.Get(), kind, place_id, datapk.size(), datapk.data(), &retpklen, &retpk); LuaCallResult.open(std::string_view(retpk, retpklen)); } void AIntegrationGameModeBase::LuaCallEnd(InvocationKind kind) { LuaCallEnd(kind, int64(0)); } void AIntegrationGameModeBase::LuaCallEnd(InvocationKind kind, AActor *place) { if (place == nullptr) { LuaCallEnd(kind, int64(0)); } else { LuaCallEnd(kind, UlxTangible::GetActorTangible(place)->TangibleId); } } void AIntegrationGameModeBase::ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs) { if (fs == "\\invokeplayer") { DPrint(TEXT("Trying to invoke 'myfunction' in lua")); FlxStreamBuffer &sb = LuaCallBegin(); sb.write_string("engio"); sb.write_string("myfunction"); sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); sb.write_double(3.0); sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING); sb.write_string("Howdy"); sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR); sb.write_fvector(FVector(2,3,4)); LuaCallEnd(InvocationKind::LUA_INVOKE); } else { ConsoleOutput.AppendLine(TEXT("Unknown Command")); } } void AIntegrationGameModeBase::ConsoleSendInput(const FString& fs) { if (fs.IsEmpty()) { return; } FlxLockedWrapper w(LockableWrapper); if (w->engine != nullptr) { ConsoleOutput.AppendLine(FString("> ") + fs); // This is a bad way to do this. The problem is that if some // lua code contains '\\', we'll catch it instead of passing it // through. There's no simple solution, though. if (fs[0] == '\\') { ExecuteDebuggingCommand(w, fs); } else { FTCHARToUTF8 utf8fs(fs); std::string ufs(utf8fs.Get(), utf8fs.Length()); ufs = ufs + "\n"; w->play_recv_incoming(w.Get(), 0, ufs.size(), ufs.c_str()); } } } void AIntegrationGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) { if(Playing && (GetWorld() == InWorld) && (InLevelTick == LEVELTICK_All)) { LuprexUpdateTask.Wait(); EngineSeconds += deltaseconds; UpdateConsoleOutput(); UpdateTangibles(); UpdatePossessedTangible(); //CastCameraRay(); } } void AIntegrationGameModeBase::OnWorldPostActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) { if(Playing && (GetWorld() == InWorld) && (InLevelTick == LEVELTICK_All)) { LuprexUpdateTask.Trigger(); } } void AIntegrationGameModeBase::Tick(float deltaseconds) { Super::Tick(deltaseconds); } void AIntegrationGameModeBase::BeginPlay() { Super::BeginPlay(); // Make sure we're starting from a clean slate. // Note: this claims the wrapper lock, so don't claim // the lock before calling this. ResetToInitialState(); // Now we're just going to claim the wrapper // lock for the remainder. When we create the thread, // the thread will hang until we release this lock. FlxLockedWrapper w(LockableWrapper); // Sanity checks. Make sure everything is clean. checkf(!LuprexUpdateTask.IsRunning(), TEXT("There should be no thread here.")); checkf(w->engine == nullptr, TEXT("There should be no engine here.")); // Try to initialize the wrapper. w.InitWrapper(); // If we failed to initialize the wrapper, print an error message. if (w->play_initialize == nullptr) { DPrint("Luprex wrapper initialization failed"); } // If wrapper is initialized, try to initialize the luprex engine. if (w->play_initialize != nullptr) { drvutil::ostringstream srcpak; std::string srcpakerr = drvutil::package_lua_source(LUPREX_ROOT_PATH, &srcpak); if (!srcpakerr.empty()) { DPrint(srcpakerr.c_str()); } else { std::string_view srcpakv = srcpak.view(); char* argv[1]; argv[0] = const_cast("lpxclient"); w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), ""); if (w->error[0]) { DPrint(w->error); } if (w->engine != nullptr) { DPrint("Luprex initialize success"); Playing = true; } } } // If we successfully created a luprex engine, create a socket system and a worker thread. if (Playing) { Sockets.Reset(FlxSockets::Create(w)); std::string error = Sockets->GetError(); check(error.empty()); LuprexUpdateTask.Startup(this); } // Initialize the tangible manager. TangibleManager = NewObject(); TangibleManager->Init(GetWorld(), this); } void AIntegrationGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason) { ResetToInitialState(); } int64 AIntegrationGameModeBase::GetPlayerId() { return PlayerId; } AIntegrationGameModeBase *AIntegrationGameModeBase::GetFromWorld(UWorld *world) { AIntegrationGameModeBase *result = world->GetAuthGameMode(); if (result == nullptr) { UE_LOG(LogBlueprint, Fatal, TEXT("No IntegrationGameModeBase in this context")); } return result; } AIntegrationGameModeBase *AIntegrationGameModeBase::GetFromContext(UObject *context) { return GetFromWorld(context->GetWorld()); } // /** Gets the game mode that owns this component, this will always return null on the client */ // template // T* GetGameMode() const // { // // Note: Intentionally getting the game mode from the world instead of the game state as it can be null during game state initialization // static_assert(TPointerIsConvertibleFromTo::Value, "'T' template parameter to GetGameMode must be derived from AGameModeBase"); // const UWorld* World = GetWorld(); // return World ? World->GetAuthGameMode() : nullptr; // } // // in GameplayStatics // /** Returns the current GameModeBase or Null if it can't be retrieved, such as on the client */ // UFUNCTION(BlueprintPure, Category="Game", meta=(WorldContext="WorldContextObject")) // static ENGINE_API class AGameModeBase* GetGameMode(const UObject* WorldContextObject); // // Give the URL a chance to override it // if (AGameModeBase* GameModeBase = GetGameMode()) // { // EffectiveBotCount = UGameplayStatics::GetIntOption(GameModeBase->OptionsString, TEXT("NumBots"), EffectiveBotCount); // } // // In UWorld // /** // * Returns the current Game Mode instance cast to the template type. // * This can only return a valid pointer on the server and may be null if the cast fails. Will always return null on a client. // */ // template< class T > // T* GetAuthGameMode() const // { // return Cast(AuthorityGameMode); // }