// Portage UE 5.5 - voir LPFixNavRandomPointCommandlet.h #include "LPFixNavRandomPointCommandlet.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "K2Node_CallFunction.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "LPNavigationHelpers.h" #include "Misc/PackageName.h" #include "NavigationSystem.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" DEFINE_LOG_CATEGORY_STATIC(LogLPFixNavRandomPoint, Log, All); namespace { const FName OldFunctionName(TEXT("K2_GetRandomPointInNavigableRadius")); const FName NewFunctionName(TEXT("GetRandomLocationInNavigableRadius")); /** Extremites reliees a une broche, triees : "Noeud.Broche". */ TArray Links(const UEdGraphPin* Pin) { TArray Out; if (Pin) { for (const UEdGraphPin* L : Pin->LinkedTo) { Out.Add(FString::Printf(TEXT("%s.%s"), L && L->GetOwningNode() ? *L->GetOwningNode()->GetName() : TEXT("?"), L ? *L->PinName.ToString() : TEXT("?"))); } } Out.Sort(); return Out; } /** Etat comparable d'une broche : liaisons et valeur par defaut. */ FString PinState(const UEdGraphPin* Pin) { return FString::Printf(TEXT("[%s] defaut='%s' objet=%s texte='%s'"), *FString::Join(Links(Pin), TEXT(", ")), *Pin->DefaultValue, Pin->DefaultObject ? *Pin->DefaultObject->GetPathName() : TEXT("None"), *Pin->DefaultTextValue.ToString()); } bool IsDeprecatedCall(const UK2Node_CallFunction* Node) { if (!Node || Node->FunctionReference.GetMemberName() != OldFunctionName) { return false; } const UClass* Owner = Node->FunctionReference.GetMemberParentClass(); return Owner && Owner->IsChildOf(UNavigationSystemV1::StaticClass()); } bool HasExecPins(const UEdGraphNode* Node) { for (const UEdGraphPin* Pin : Node->Pins) { if (Pin && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { return true; } } return false; } } ULPFixNavRandomPointCommandlet::ULPFixNavRandomPointCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixNavRandomPointCommandlet::Main(const FString& Params) { const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); UFunction* NewFunction = ULPNavigationHelpers::StaticClass()->FindFunctionByName(NewFunctionName); if (!NewFunction) { UE_LOG(LogLPFixNavRandomPoint, Error, TEXT("ULPNavigationHelpers::GetRandomLocationInNavigableRadius introuvable.")); return 1; } UE_LOG(LogLPFixNavRandomPoint, Warning, TEXT("=== GetRandomPointInNavigableRadius (deprecie) -> LPNavigationHelpers%s ==="), bDryRun ? TEXT(" [DRY RUN]") : TEXT("")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray BlueprintAssets; // Sous-classes incluses : couvre les UWidgetBlueprint. AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true); const UEdGraphSchema_K2* Schema = GetDefault(); int32 NodesFound = 0; int32 NodesFixed = 0; int32 Unhandled = 0; int32 BlueprintsModified = 0; int32 SaveFailures = 0; for (const FAssetData& AssetData : BlueprintAssets) { if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/"))) { continue; } UBlueprint* Blueprint = Cast(AssetData.GetAsset()); if (!Blueprint) { continue; } TArray Graphs; Blueprint->GetAllGraphs(Graphs); int32 FixedHere = 0; int32 FailedHere = 0; for (UEdGraph* Graph : Graphs) { if (!Graph) { continue; } // Copie : on ajoute et on detruit des noeuds pendant le parcours. TArray Snapshot = Graph->Nodes; for (UEdGraphNode* GraphNode : Snapshot) { UK2Node_CallFunction* OldNode = Cast(GraphNode); if (!IsDeprecatedCall(OldNode)) { continue; } ++NodesFound; UE_LOG(LogLPFixNavRandomPoint, Warning, TEXT(" %s :: %s -> %s (%d, %d)"), *Blueprint->GetName(), *Graph->GetName(), *OldNode->GetName(), OldNode->NodePosX, OldNode->NodePosY); // Etat avant, par nom de broche (self exclu : cible implicite de la bibliotheque). TMap Before; for (const UEdGraphPin* Pin : OldNode->Pins) { if (Pin && Pin->PinName != UEdGraphSchema_K2::PN_Self) { Before.Add(Pin->PinName, PinState(Pin)); UE_LOG(LogLPFixNavRandomPoint, Warning, TEXT(" %s %s %s"), Pin->Direction == EGPD_Input ? TEXT("in ") : TEXT("out"), *Pin->PinName.ToString(), *PinState(Pin)); } } const UEdGraphPin* OldSelf = OldNode->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input); if (HasExecPins(OldNode) || (OldSelf && OldSelf->LinkedTo.Num() > 0)) { ++Unhandled; ++FailedHere; UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" cas non pris en charge (%s) : noeud laisse en l'etat."), HasExecPins(OldNode) ? TEXT("noeud impur") : TEXT("cible self reliee")); continue; } if (bDryRun) { continue; } UK2Node_CallFunction* NewNode = NewObject(Graph); NewNode->SetFromFunction(NewFunction); NewNode->NodePosX = OldNode->NodePosX; NewNode->NodePosY = OldNode->NodePosY; NewNode->CreateNewGuid(); NewNode->PostPlacedNewNode(); NewNode->AllocateDefaultPins(); Graph->Modify(); Graph->AddNode(NewNode, false, false); bool bOk = true; for (UEdGraphPin* OldPin : OldNode->Pins) { if (!OldPin || OldPin->PinName == UEdGraphSchema_K2::PN_Self) { continue; } UEdGraphPin* NewPin = NewNode->FindPin(OldPin->PinName, OldPin->Direction); if (!NewPin) { bOk = false; UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" broche %s sans equivalent."), *OldPin->PinName.ToString()); break; } NewPin->DefaultValue = OldPin->DefaultValue; NewPin->DefaultObject = OldPin->DefaultObject; NewPin->DefaultTextValue = OldPin->DefaultTextValue; if (OldPin->LinkedTo.Num() > 0 && Schema->MovePinLinks(*OldPin, *NewPin).Response == CONNECT_RESPONSE_DISALLOW) { bOk = false; UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" liaison refusee sur %s."), *OldPin->PinName.ToString()); break; } } // Meme ensemble de broches, memes liaisons (les noms des voisins ne changent pas), memes valeurs. if (bOk) { int32 Compared = 0; for (const UEdGraphPin* NewPin : NewNode->Pins) { if (!NewPin || NewPin->PinName == UEdGraphSchema_K2::PN_Self) { continue; } const FString* Expected = Before.Find(NewPin->PinName); if (!Expected || *Expected != PinState(NewPin)) { bOk = false; UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" apres : %s %s (attendu %s)"), *NewPin->PinName.ToString(), *PinState(NewPin), Expected ? **Expected : TEXT("")); } ++Compared; } if (Compared != Before.Num()) { bOk = false; UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" %d broche(s) apres, %d avant."), Compared, Before.Num()); } } if (!bOk) { // Le Blueprint ne sera pas sauvegarde : le paquet sur disque reste intact. ++Unhandled; ++FailedHere; UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" ecart : Blueprint non sauvegarde.")); continue; } OldNode->DestroyNode(); ++NodesFixed; ++FixedHere; } } if (FailedHere > 0) { UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" -> %s : %d noeud(s) en ecart, Blueprint NON sauvegarde."), *Blueprint->GetName(), FailedHere); continue; } if (FixedHere == 0 || bDryRun) { continue; } ++BlueprintsModified; FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); FKismetEditorUtilities::CompileBlueprint(Blueprint); UPackage* Package = Blueprint->GetOutermost(); FString Filename; if (!FPackageName::TryConvertLongPackageNameToFilename( Package->GetName(), Filename, FPackageName::GetAssetPackageExtension())) { ++SaveFailures; UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" chemin introuvable pour %s"), *Package->GetName()); continue; } ResetLoaders(Package); FSavePackageArgs SaveArgs; SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; SaveArgs.SaveFlags = SAVE_None; SaveArgs.bWarnOfLongFilename = true; // GWarn et non GError : en commandlet, GError rend toute erreur de save fatale. SaveArgs.Error = GWarn; if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs)) { UE_LOG(LogLPFixNavRandomPoint, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere); } else { ++SaveFailures; UE_LOG(LogLPFixNavRandomPoint, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); } } UE_LOG(LogLPFixNavRandomPoint, Warning, TEXT("=== Termine : %d noeud(s) trouve(s), %d remplace(s), %d Blueprint(s) modifie(s), %d non traite(s), %d echec(s) de sauvegarde. ==="), NodesFound, NodesFixed, BlueprintsModified, Unhandled, SaveFailures); return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0; }