// Copyright Epic Games, Inc. All Rights Reserved. #include "LuprexGameModeBase.h" #include "lpx-drvutil.hpp" #include "lpx-paths.hpp" #include "ConsoleOutput.h" #include "Tangible.h" #include "TangibleManager.h" #include "LuaCall.h" #include "Blueprint/UserWidget.h" #include "Blueprint/WidgetBlueprintLibrary.h" #include "Kismet/GameplayStatics.h" #include "CommonTypes.h" #include "AnimQueue.h" #include #include using namespace CommonTypes; DEFINE_LOG_CATEGORY(LogLuprex); DEFINE_LOG_CATEGORY(LogLuprexIntegration); ALuprexGameModeBase::ALuprexGameModeBase() { TangibleManager = nullptr; AssetLookup = nullptr; PlayerId = 0; EngineSeconds = 0.0; MustCallLookAtChanged = false; //PrimaryActorTick.bCanEverTick = true; // Probably wrong //PrimaryActorTick.bTickEvenWhenPaused = true; // Probably wrong //PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong SetActorTickEnabled(true); SetActorTickInterval(0.0f); ResetToInitialState(); OnWorldPreActorTickHandle = FWorldDelegates::OnWorldPreActorTick.AddUObject(this, &ALuprexGameModeBase::OnWorldPreActorTick); OnWorldPostActorTickHandle = FWorldDelegates::OnWorldPostActorTick.AddUObject(this, &ALuprexGameModeBase::OnWorldPostActorTick); } ALuprexGameModeBase::~ALuprexGameModeBase() { ResetToInitialState(); FWorldDelegates::OnWorldPreActorTick.Remove(OnWorldPreActorTickHandle); FWorldDelegates::OnWorldPostActorTick.Remove(OnWorldPostActorTickHandle); } // This method runs in the background thread, // at the moment we trigger it. // uint32 ALuprexGameModeBase::Run() { FlxLockedWrapper lockedwrap(LockableWrapper); Sockets->Update(lockedwrap); lockedwrap->play_update(lockedwrap.Get(), EngineSeconds); Sockets->Update(lockedwrap); return 0; } void ALuprexGameModeBase::ResetToInitialState() { Playing = false; if (TangibleManager != nullptr) { TangibleManager->ConditionalBeginDestroy(); TangibleManager = nullptr; } if (AssetLookup != nullptr) { AssetLookup->ConditionalBeginDestroy(); AssetLookup = 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; CurrentLookAt.Init(); MustCallLookAtChanged = false; // Reset the clocks. EngineSeconds = 0.0; NextRotateCube = 1.0; } void ALuprexGameModeBase::UpdateConsoleOutput() { // Copy Luprex Stdout into the console. FlxLockedWrapper lockedwrap(LockableWrapper); if (Playing) { ConsoleOutput.Append(lockedwrap.FetchStdout()); } // If the Console text has changed, update the widget. if (ConsoleOutput.IsDirty()) { ConsoleSetOutput(ConsoleOutput.Get()); ConsoleOutput.ClearDirty(); } } #pragma optimize("", off) void ALuprexGameModeBase::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 ALuprexGameModeBase::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); } } } UlxLuaValues *ALuprexGameModeBase::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); if (kind == InvocationKind::LUA_PROBE) { UlxLuaValues *Result = NewObject(this); Result->Initialize(std::string_view(retpk, retpklen)); return Result; } else return nullptr; } UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(InvocationKind kind) { return LuaCallEnd(kind, int64(0)); } UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(InvocationKind kind, AActor *place) { if (place == nullptr) { return LuaCallEnd(kind, int64(0)); } else { UlxTangible *tan = UlxTangible::GetActorTangibleOrLog(place); if (tan == nullptr) { return NewObject(this); } else { return LuaCallEnd(kind, tan->TangibleId); } } } void ALuprexGameModeBase::ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs) { // Nothing here right now. } void ALuprexGameModeBase::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 ALuprexGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) { if(Playing && (GetWorld() == InWorld) && (InLevelTick == LEVELTICK_All)) { LuprexUpdateTask.Wait(); EngineSeconds += deltaseconds; UpdateConsoleOutput(); UpdateTangibles(); UpdatePossessedTangible(); UpdateLookAt(); } } void ALuprexGameModeBase::OnWorldPostActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) { if(Playing && (GetWorld() == InWorld) && (InLevelTick == LEVELTICK_All)) { LuprexUpdateTask.Trigger(); } } void ALuprexGameModeBase::Tick(float deltaseconds) { Super::Tick(deltaseconds); } void ALuprexGameModeBase::BeginPlay() { ResetToInitialState(); InitializeGlobalState(); Super::BeginPlay(); } void ALuprexGameModeBase::InitializeGlobalState() { 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) { UE_LOG(LogLuprexIntegration, Error, TEXT("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()) { FString FMessage((const UTF8CHAR *)(srcpakerr.c_str())); UE_LOG(LogLuprexIntegration, Error, TEXT("Trying to read Lua source: %s"), *FMessage); } 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]) { FString FMessage((const UTF8CHAR *)w->error); UE_LOG(LogLuprexIntegration, Error, TEXT("Calling Luprex play_initialize: %s"), *FMessage); } if (w->engine != nullptr) { UE_LOG(LogLuprexIntegration, Verbose, TEXT("Luprex initialization 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 asset lookup table. AssetLookup = NewObject(this); AssetLookup->RebuildIndex(); // Initialize the tangible manager. TangibleManager = NewObject(this); TangibleManager->Init(this); // If somebody generates a log message that's severe enough, break to debugger. BreakToDebuggerLogVerbosityDevice.Reset( new FlxDebugBlueprintErrorsOutputDevice(BreakToDebuggerLogVerbosity)); } void ALuprexGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason) { ResetToInitialState(); } int64 ALuprexGameModeBase::GetPlayerId() { return PlayerId; } ALuprexGameModeBase *ALuprexGameModeBase::FromContext(const UObject *context) { ALuprexGameModeBase *result = context->GetWorld()->GetAuthGameMode(); if (result == nullptr) { UE_LOG(LogBlueprint, Fatal, TEXT("Not currently using a Luprex Game Mode.")); } return result; } void ALuprexGameModeBase::SetLookAt(const UObject *Context, const FHitResult &HitResult) { ALuprexGameModeBase *Mode = FromContext(Context); if (Mode->CurrentLookAt.HitObjectHandle != HitResult.HitObjectHandle) { Mode->MustCallLookAtChanged = true; } Mode->CurrentLookAt = HitResult; } void ALuprexGameModeBase::SetLookAtChanged(const UObject *Context) { ALuprexGameModeBase *Mode = FromContext(Context); Mode->MustCallLookAtChanged = true; } FVector2D ALuprexGameModeBase::GetLookAtPixel(const UObject *Context) { ALuprexGameModeBase *Mode = FromContext(Context); APlayerController *pc = Context->GetWorld()->GetFirstPlayerController(); if (pc == nullptr) return FVector2D(); FVector2D ScreenPosition; if (!UGameplayStatics::ProjectWorldToScreen(pc, Mode->CurrentLookAt.Location, ScreenPosition, false)) { return FVector2D(); } return ScreenPosition; } void ALuprexGameModeBase::UpdateLookAt() { // 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(pc); if (MustCallLookAtChanged) { MustCallLookAtChanged = false; LookAtChanged(); } }