// 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()); } // Stop trapping log errors to the debugger. BreakToDebuggerLogVerbosityDevice.Reset(); // Clear the lua call assembly buffer. LuaCallBuffer.clear(); // Clear the PlayerID PlayerId = 0; // Clear the look-at state; PreviousLookAt.Init(); CurrentLookAt.Init(); // 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() { UlxTangible *ptan = TangibleManager->GetPossessedTangible(); UlxTangible *tan = TangibleManager->GetTangible(PlayerId); APlayerController *ctrl = GetWorld()->GetFirstPlayerController(); APawn *pawn = nullptr; if (tan != nullptr) pawn = Cast(tan->GetActor()); if (pawn == nullptr) { if (ptan != nullptr) { TangibleManager->SetPossessedTangible(nullptr); ctrl->Possess(nullptr); } } else { if (ptan != tan) { TangibleManager->SetPossessedTangible(tan); ctrl->Possess(pawn); } } } 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(); UpdateLookAt(); } } 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); // If somebody generates a log message that's severe enough, break to debugger. BreakToDebuggerLogVerbosityDevice.Reset( new FlxDebugBlueprintErrorsOutputDevice(BreakToDebuggerLogVerbosity)); } 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()); } void AIntegrationGameModeBase::UpdateLookAt() { // Rotate the variables. PreviousLookAt = CurrentLookAt; CurrentLookAt.Init(); // Make sure the world is fully configured before we attempt to cast rays. UlxTangible *possessed = TangibleManager->GetPossessedTangible(); if (possessed == nullptr) return; APlayerController *pc = GetWorld()->GetFirstPlayerController(); if (pc == nullptr) return; APawn *pawn = pc->GetPawn(); if (pawn == nullptr) return; if (possessed->GetActor() != pawn) return; APlayerCameraManager *cam = pc->PlayerCameraManager; if (cam == nullptr) return; CalculateLookAt(pawn, pc, cam); }