// Portage UE 5.5 - voir LPFixOrphanBoundEventsCommandlet.h #include "LPFixOrphanBoundEventsCommandlet.h" #include "AssetRegistry/AssetRegistryModule.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "K2Node_ComponentBoundEvent.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "Misc/PackageName.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" #include "UObject/UObjectGlobals.h" DEFINE_LOG_CATEGORY_STATIC(LogLPFixBoundEvents, Log, All); namespace { /** * Reproduit le test que le compilateur applique lui-meme : le composant referencE par * l'evenement doit exister sous forme de FObjectProperty sur la classe squelette du * Blueprint. UK2Node_ComponentBoundEvent::IsDelegateValid() est prive, d'ou la copie. */ bool ComponentStillExists(const UBlueprint* Blueprint, FName ComponentPropertyName) { if (!Blueprint || ComponentPropertyName.IsNone()) { return false; } const UClass* Scope = Blueprint->SkeletonGeneratedClass ? Blueprint->SkeletonGeneratedClass : Blueprint->GeneratedClass; return Scope && FindFProperty(Scope, ComponentPropertyName) != nullptr; } /** Compte les noeuds atteignables depuis la broche Exec de sortie de l'evenement. */ int32 CountDownstreamNodes(const UEdGraphNode* EventNode) { TSet Seen; TArray Pending; Pending.Add(EventNode); Seen.Add(EventNode); while (Pending.Num() > 0) { const UEdGraphNode* Current = Pending.Pop(); for (const UEdGraphPin* Pin : Current->Pins) { if (!Pin || Pin->Direction != EGPD_Output || Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) { continue; } for (const UEdGraphPin* Linked : Pin->LinkedTo) { const UEdGraphNode* Next = Linked ? Linked->GetOwningNode() : nullptr; if (Next && !Seen.Contains(Next)) { Seen.Add(Next); Pending.Add(Next); } } } } return Seen.Num() - 1; // on ne compte pas l'evenement lui-meme } } ULPFixOrphanBoundEventsCommandlet::ULPFixOrphanBoundEventsCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixOrphanBoundEventsCommandlet::Main(const FString& Params) { const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); UE_LOG(LogLPFixBoundEvents, Warning, TEXT("=== Evenements de composant orphelins (R-05)%s ==="), bDryRun ? TEXT(" [DRY RUN]") : TEXT("")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray BlueprintAssets; AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true); int32 NodesFound = 0; int32 NodesRemoved = 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 RemovedHere = 0; for (UEdGraph* Graph : Graphs) { if (!Graph) { continue; } TArray Snapshot = Graph->Nodes; for (UEdGraphNode* GraphNode : Snapshot) { UK2Node_ComponentBoundEvent* BoundEvent = Cast(GraphNode); if (!BoundEvent) { continue; } const FName ComponentName = BoundEvent->GetComponentPropertyName(); if (ComponentStillExists(Blueprint, ComponentName)) { continue; } ++NodesFound; const int32 Downstream = CountDownstreamNodes(BoundEvent); UE_LOG(LogLPFixBoundEvents, Warning, TEXT(" %s :: %s -> %s (composant '%s' introuvable, %d noeud(s) en aval)"), *Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), *ComponentName.ToString(), Downstream); if (bDryRun) { continue; } Graph->Modify(); BoundEvent->Modify(); BoundEvent->DestroyNode(); ++NodesRemoved; ++RemovedHere; } } if (RemovedHere > 0 && !bDryRun) { ++BlueprintsModified; FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); FKismetEditorUtilities::CompileBlueprint(Blueprint); UPackage* Package = Blueprint->GetOutermost(); FString Filename; if (!FPackageName::TryConvertLongPackageNameToFilename( Package->GetName(), Filename, FPackageName::GetAssetPackageExtension())) { ++SaveFailures; UE_LOG(LogLPFixBoundEvents, 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; SaveArgs.Error = GWarn; if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs)) { UE_LOG(LogLPFixBoundEvents, Warning, TEXT(" -> %s sauvegarde (%d evenement(s) retire(s))."), *Package->GetName(), RemovedHere); } else { ++SaveFailures; UE_LOG(LogLPFixBoundEvents, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); } } } UE_LOG(LogLPFixBoundEvents, Warning, TEXT("=== Termine : %d evenement(s) orphelin(s) trouve(s), %d retire(s) dans %d Blueprint(s), %d echec(s) de sauvegarde. ==="), NodesFound, NodesRemoved, BlueprintsModified, SaveFailures); return SaveFailures > 0 ? 1 : 0; }