// Portage UE 5.5 - voir LPFixWhatWhereCommandlet.h #include "LPFixWhatWhereCommandlet.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 "LPDataCompat.h" #include "Misc/PackageName.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" DEFINE_LOG_CATEGORY_STATIC(LogLPFixWhatWhere, Log, All); namespace { /** 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()); } /** ULPDataCompat::WhatWhereToArray si le noeud appelle la fonction WhatWhereToArray de GI (appel sur self), sinon nullptr. */ UFunction* ReplacementFor(const UK2Node_CallFunction* Node) { if (!Node || Node->FunctionReference.GetMemberName() != FName(TEXT("WhatWhereToArray"))) { return nullptr; } const UClass* Owner = Node->FunctionReference.GetMemberParentClass(Node->GetBlueprintClassFromNode()); if (!Owner || (!Owner->GetName().StartsWith(TEXT("GI_C")) && !Owner->GetName().StartsWith(TEXT("SKEL_GI_C")))) { return nullptr; } return ULPDataCompat::StaticClass()->FindFunctionByName(TEXT("WhatWhereToArray")); } /** Broches ignorees dans les comparaisons : cible implicite (self de l'appel d'origine, Target cache du remplacant). */ bool IsTargetPin(const UEdGraphPin* Pin) { return Pin && (Pin->PinName == UEdGraphSchema_K2::PN_Self || Pin->PinName == FName(TEXT("Target"))); } bool HasExecPins(const UEdGraphNode* Node) { for (const UEdGraphPin* Pin : Node->Pins) { if (Pin && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { return true; } } return false; } } ULPFixWhatWhereCommandlet::ULPFixWhatWhereCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixWhatWhereCommandlet::Main(const FString& Params) { const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); if (!ULPDataCompat::StaticClass()->FindFunctionByName(TEXT("WhatWhereToArray"))) { UE_LOG(LogLPFixWhatWhere, Error, TEXT("ULPDataCompat::WhatWhereToArray introuvable.")); return 1; } TMap PerFunction; UE_LOG(LogLPFixWhatWhere, Warning, TEXT("=== GI::WhatWhereToArray -> LPDataCompat::WhatWhereToArray%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 != FName(TEXT("/Game/BPs/GI"))) { 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); UFunction* NewFunction = ReplacementFor(OldNode); if (!NewFunction) { continue; } ++NodesFound; ++PerFunction.FindOrAdd(NewFunction->GetFName()); UE_LOG(LogLPFixWhatWhere, Warning, TEXT(" %s :: %s -> %s %s (%d, %d)"), *Blueprint->GetName(), *Graph->GetName(), *OldNode->GetName(), *NewFunction->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 && !IsTargetPin(Pin)) { Before.Add(Pin->PinName, PinState(Pin)); } } const UEdGraphPin* OldSelf = OldNode->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input); // Appel impur (broches d'execution) attendu des deux cotes ; seule une cible self reliee est refusee. if (!HasExecPins(OldNode) || (OldSelf && OldSelf->LinkedTo.Num() > 0)) { ++Unhandled; ++FailedHere; UE_LOG(LogLPFixWhatWhere, Error, TEXT(" cas non pris en charge (%s) : noeud laisse en l'etat."), !HasExecPins(OldNode) ? TEXT("noeud pur inattendu") : 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 || IsTargetPin(OldPin)) { continue; } UEdGraphPin* NewPin = NewNode->FindPin(OldPin->PinName, OldPin->Direction); if (!NewPin) { bOk = false; UE_LOG(LogLPFixWhatWhere, 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(LogLPFixWhatWhere, 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 || IsTargetPin(NewPin)) { continue; } const FString* Expected = Before.Find(NewPin->PinName); if (!Expected || *Expected != PinState(NewPin)) { bOk = false; UE_LOG(LogLPFixWhatWhere, Error, TEXT(" apres : %s %s (attendu %s)"), *NewPin->PinName.ToString(), *PinState(NewPin), Expected ? **Expected : TEXT("")); } ++Compared; } if (Compared != Before.Num()) { bOk = false; UE_LOG(LogLPFixWhatWhere, 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(LogLPFixWhatWhere, Error, TEXT(" ecart : Blueprint non sauvegarde.")); continue; } OldNode->DestroyNode(); ++NodesFixed; ++FixedHere; } } if (FailedHere > 0) { UE_LOG(LogLPFixWhatWhere, 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(LogLPFixWhatWhere, 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(LogLPFixWhatWhere, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere); } else { ++SaveFailures; UE_LOG(LogLPFixWhatWhere, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); } } for (const TPair& It : PerFunction) { UE_LOG(LogLPFixWhatWhere, Warning, TEXT(" %s : %d noeud(s)"), *It.Key.ToString(), It.Value); } UE_LOG(LogLPFixWhatWhere, 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; }