// 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(); // Reset the clocks. EngineSeconds = 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]); } // Maybe call 'BecomePossessed' on some tangible. UlxTangible *poss = TangibleManager->SetPossessedTangible(PlayerId); if (poss != nullptr) { IlxTangibleInterface::Execute_BecomePossessed(poss->GetActor()); } } // 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::InvokeEngioMove(const FString &action, const FVector &xyz, double facing) { // FTCHARToUTF8 utf8action(action); // std::string uaction(utf8action.Get(), utf8action.Length()); // FlxStreamBuffer sb; // sb.write_string("engio"); // sb.write_string("move"); // sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING); // sb.write_string(uaction); // sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR); // sb.write_fvector(xyz); // sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); // sb.write_double(facing); // std::string_view datapk = sb.view(); // FlxLockedWrapper w(LockableWrapper); // int64 player = w.GetActor(); // w->play_call_function_lua_call(w.Get(), player, datapk.size(), datapk.data()); // } void AIntegrationGameModeBase::LuaCallInvoke(AActor *place) { std::string_view datapk = LuaCallBuffer.view(); FlxLockedWrapper w(LockableWrapper); int64_t place_id = w.GetActor(); if (place != nullptr) { place_id = UlxTangible::GetActorTangible(place)->TangibleId; } w->play_call_function(w.Get(), InvocationKind::LUA_CALL, place_id, datapk.size(), datapk.data()); } void AIntegrationGameModeBase::ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs) { if (fs == "\\invokeplayer") { DPrint(TEXT("Trying to invoke 'myfunction' in lua")); FlxStreamBuffer sb; 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)); std::string_view datapk = sb.view(); int64 player = w.GetActor(); w->play_call_function(w.Get(), InvocationKind::LUA_CALL, player, datapk.size(), datapk.data()); } 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(); } } 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); UpdateConsoleOutput(); UpdateTangibles(); } 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); // }