Instantane de reference de la reprise de migration (2026-09-29). Contient GI.uasset tel que trouve (copie UE4 identique a 4.27, MD5 F4550DA5DC05FC005400CDCA2FC123C9). Ajouts de ce commit : .gitattributes (aucune conversion de fin de ligne), regles .gitignore CHG-001, Tools/ (Snapshot, Restore, AssetHeaders), empreintes L0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
323 lines
11 KiB
C++
323 lines
11 KiB
C++
// Portage UE 5.5 - voir LPFixDeprecatedNodesCommandlet.h
|
|
|
|
#include "LPFixDeprecatedNodesCommandlet.h"
|
|
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Components/SkinnedMeshComponent.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraph/EdGraphPin.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "K2Node_CallFunction.h"
|
|
#include "K2Node_VariableGet.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"
|
|
#include "UObject/UObjectGlobals.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogLPFixDeprecated, Log, All);
|
|
|
|
namespace
|
|
{
|
|
// --- R-03 ---
|
|
const FName SkeletalMeshPropertyName(TEXT("SkeletalMesh"));
|
|
const FName DeprecatedGetterName(TEXT("GetSkeletalMesh_DEPRECATED"));
|
|
const FName ReplacementGetterName(TEXT("GetSkeletalMeshAsset"));
|
|
|
|
// --- R-04 ---
|
|
const FName DeprecatedNavFunctionName(TEXT("K2_GetRandomPointInNavigableRadius"));
|
|
const FName ReplacementNavFunctionName(TEXT("GetRandomLocationInNavigableRadius"));
|
|
|
|
/** Decrit une broche et ses liaisons, pour le rapport. Copie du motif de LPFixEditableTextBoxFont. */
|
|
FString DescribeLinks(const UEdGraphPin* Pin)
|
|
{
|
|
if (!Pin)
|
|
{
|
|
return TEXT("(broche absente)");
|
|
}
|
|
if (Pin->LinkedTo.Num() == 0)
|
|
{
|
|
return FString::Printf(TEXT("non connectee (defaut: '%s')"), *Pin->DefaultValue);
|
|
}
|
|
|
|
TArray<FString> Targets;
|
|
for (const UEdGraphPin* Linked : Pin->LinkedTo)
|
|
{
|
|
Targets.Add(FString::Printf(TEXT("%s.%s"),
|
|
Linked->GetOwningNode() ? *Linked->GetOwningNode()->GetName() : TEXT("?"),
|
|
*Linked->PinName.ToString()));
|
|
}
|
|
return FString::Printf(TEXT("%d liaison(s) -> %s"), Pin->LinkedTo.Num(), *FString::Join(Targets, TEXT(", ")));
|
|
}
|
|
|
|
/** Vrai si la classe proprietaire du membre est USkinnedMeshComponent ou une de ses filles. */
|
|
bool IsSkinnedMeshOwner(const UClass* OwnerClass)
|
|
{
|
|
return OwnerClass && OwnerClass->IsChildOf(USkinnedMeshComponent::StaticClass());
|
|
}
|
|
}
|
|
|
|
ULPFixDeprecatedNodesCommandlet::ULPFixDeprecatedNodesCommandlet()
|
|
{
|
|
IsClient = false;
|
|
IsServer = false;
|
|
IsEditor = true;
|
|
LogToConsole = true;
|
|
}
|
|
|
|
int32 ULPFixDeprecatedNodesCommandlet::Main(const FString& Params)
|
|
{
|
|
const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase);
|
|
|
|
UFunction* ReplacementGetter =
|
|
USkeletalMeshComponent::StaticClass()->FindFunctionByName(ReplacementGetterName);
|
|
UFunction* ReplacementNavFunction =
|
|
ULPNavigationHelpers::StaticClass()->FindFunctionByName(ReplacementNavFunctionName);
|
|
|
|
if (!ReplacementGetter || !ReplacementNavFunction)
|
|
{
|
|
UE_LOG(LogLPFixDeprecated, Error,
|
|
TEXT("Fonctions de remplacement introuvables (GetSkeletalMeshAsset=%s, GetRandomLocationInNavigableRadius=%s)."),
|
|
ReplacementGetter ? TEXT("ok") : TEXT("NULL"),
|
|
ReplacementNavFunction ? TEXT("ok") : TEXT("NULL"));
|
|
return 1;
|
|
}
|
|
|
|
UE_LOG(LogLPFixDeprecated, Warning, TEXT("=== Noeuds deprecies : SkeletalMesh (R-03) + GetRandomPointInNavigableRadius (R-04)%s ==="),
|
|
bDryRun ? TEXT(" [DRY RUN]") : TEXT(""));
|
|
|
|
FAssetRegistryModule& AssetRegistryModule =
|
|
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
AssetRegistryModule.Get().SearchAllAssets(true);
|
|
|
|
TArray<FAssetData> BlueprintAssets;
|
|
// bSearchSubClasses = true : couvre aussi les UWidgetBlueprint (CharGen, Inventory, ...).
|
|
AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
|
|
|
|
int32 MeshNodesFound = 0;
|
|
int32 NavNodesFound = 0;
|
|
int32 MeshNodesFixed = 0;
|
|
int32 NavNodesFixed = 0;
|
|
int32 BlueprintsModified = 0;
|
|
int32 SaveFailures = 0;
|
|
int32 Unhandled = 0;
|
|
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
for (const FAssetData& AssetData : BlueprintAssets)
|
|
{
|
|
if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/")))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UBlueprint* Blueprint = Cast<UBlueprint>(AssetData.GetAsset());
|
|
if (!Blueprint)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<UEdGraph*> Graphs;
|
|
Blueprint->GetAllGraphs(Graphs);
|
|
|
|
int32 FixedHere = 0;
|
|
|
|
for (UEdGraph* Graph : Graphs)
|
|
{
|
|
if (!Graph)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Snapshot : on cree et on detruit des noeuds pendant l'iteration.
|
|
TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
|
|
for (UEdGraphNode* GraphNode : Snapshot)
|
|
{
|
|
// ---------------------------------------------------------------
|
|
// R-04 : reference de fonction a reaiguiller, rien d'autre a faire.
|
|
// Signatures et noms de broches identiques -> ReconstructNode conserve
|
|
// toutes les liaisons.
|
|
// ---------------------------------------------------------------
|
|
if (UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(GraphNode))
|
|
{
|
|
const FName MemberName = CallNode->FunctionReference.GetMemberName();
|
|
const UClass* MemberOwner = CallNode->FunctionReference.GetMemberParentClass();
|
|
|
|
if (MemberName == DeprecatedNavFunctionName)
|
|
{
|
|
++NavNodesFound;
|
|
UE_LOG(LogLPFixDeprecated, Warning, TEXT(" [R-04] %s :: %s -> %s"),
|
|
*Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName());
|
|
UE_LOG(LogLPFixDeprecated, Warning, TEXT(" RandomLocation : %s"),
|
|
*DescribeLinks(GraphNode->FindPin(TEXT("RandomLocation"))));
|
|
|
|
if (!bDryRun)
|
|
{
|
|
GraphNode->Modify();
|
|
CallNode->SetFromFunction(ReplacementNavFunction);
|
|
CallNode->ReconstructNode();
|
|
++NavNodesFixed;
|
|
++FixedHere;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// R-03, variante « appel explicite du getter deprecie ».
|
|
if (MemberName == DeprecatedGetterName && IsSkinnedMeshOwner(MemberOwner))
|
|
{
|
|
++MeshNodesFound;
|
|
UE_LOG(LogLPFixDeprecated, Warning, TEXT(" [R-03/appel] %s :: %s -> %s"),
|
|
*Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName());
|
|
|
|
if (!bDryRun)
|
|
{
|
|
GraphNode->Modify();
|
|
CallNode->SetFromFunction(ReplacementGetter);
|
|
CallNode->ReconstructNode();
|
|
++MeshNodesFixed;
|
|
++FixedHere;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// R-03 : acces direct a la propriete depreciee. Il faut remplacer le
|
|
// noeud, pas seulement sa reference : un K2Node_VariableGet ne peut pas
|
|
// devenir un K2Node_CallFunction.
|
|
// ---------------------------------------------------------------
|
|
UK2Node_VariableGet* VarGet = Cast<UK2Node_VariableGet>(GraphNode);
|
|
if (!VarGet || VarGet->VariableReference.GetMemberName() != SkeletalMeshPropertyName)
|
|
{
|
|
continue;
|
|
}
|
|
if (!IsSkinnedMeshOwner(VarGet->VariableReference.GetMemberParentClass()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UEdGraphPin* OldSelfPin = VarGet->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input);
|
|
UEdGraphPin* OldValuePin = VarGet->GetValuePin();
|
|
|
|
++MeshNodesFound;
|
|
UE_LOG(LogLPFixDeprecated, Warning, TEXT(" [R-03/propriete] %s :: %s -> %s"),
|
|
*Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName());
|
|
UE_LOG(LogLPFixDeprecated, Warning, TEXT(" Target : %s"), *DescribeLinks(OldSelfPin));
|
|
UE_LOG(LogLPFixDeprecated, Warning, TEXT(" Valeur : %s"), *DescribeLinks(OldValuePin));
|
|
|
|
if (bDryRun)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!OldValuePin)
|
|
{
|
|
++Unhandled;
|
|
UE_LOG(LogLPFixDeprecated, Error, TEXT(" broche de valeur introuvable, noeud laisse en l'etat."));
|
|
continue;
|
|
}
|
|
|
|
// Construction du noeud de remplacement au meme emplacement.
|
|
UK2Node_CallFunction* NewNode = NewObject<UK2Node_CallFunction>(Graph);
|
|
NewNode->SetFromFunction(ReplacementGetter);
|
|
NewNode->NodePosX = VarGet->NodePosX;
|
|
NewNode->NodePosY = VarGet->NodePosY;
|
|
NewNode->CreateNewGuid();
|
|
NewNode->PostPlacedNewNode();
|
|
NewNode->AllocateDefaultPins();
|
|
Graph->Modify();
|
|
Graph->AddNode(NewNode, false, false);
|
|
|
|
UEdGraphPin* NewSelfPin = NewNode->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input);
|
|
UEdGraphPin* NewValuePin = NewNode->GetReturnValuePin();
|
|
|
|
bool bMoved = true;
|
|
|
|
// GetSkeletalMeshAsset() n'existe que sur USkeletalMeshComponent alors que la
|
|
// propriete depreciee vit sur USkinnedMeshComponent. Si la cible du noeud est
|
|
// typee sur la classe de base, le deplacement echoue : on le signale au lieu de
|
|
// casser le graphe.
|
|
if (OldSelfPin && NewSelfPin)
|
|
{
|
|
const FPinConnectionResponse Response = Schema->MovePinLinks(*OldSelfPin, *NewSelfPin);
|
|
if (Response.Response == CONNECT_RESPONSE_DISALLOW)
|
|
{
|
|
bMoved = false;
|
|
UE_LOG(LogLPFixDeprecated, Error, TEXT(" Target incompatible (%s) : reprise manuelle necessaire."),
|
|
*Response.Message.ToString());
|
|
}
|
|
}
|
|
|
|
if (bMoved && NewValuePin)
|
|
{
|
|
const FPinConnectionResponse Response = Schema->MovePinLinks(*OldValuePin, *NewValuePin);
|
|
if (Response.Response == CONNECT_RESPONSE_DISALLOW)
|
|
{
|
|
bMoved = false;
|
|
UE_LOG(LogLPFixDeprecated, Error, TEXT(" Valeur incompatible (%s) : reprise manuelle necessaire."),
|
|
*Response.Message.ToString());
|
|
}
|
|
}
|
|
|
|
if (!bMoved)
|
|
{
|
|
// On remet tout en place : le noeud d'origine garde ses liaisons.
|
|
++Unhandled;
|
|
NewNode->DestroyNode();
|
|
continue;
|
|
}
|
|
|
|
VarGet->DestroyNode();
|
|
++MeshNodesFixed;
|
|
++FixedHere;
|
|
}
|
|
}
|
|
|
|
if (FixedHere > 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(LogLPFixDeprecated, 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(LogLPFixDeprecated, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere);
|
|
}
|
|
else
|
|
{
|
|
++SaveFailures;
|
|
UE_LOG(LogLPFixDeprecated, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogLPFixDeprecated, Warning,
|
|
TEXT("=== Termine : R-03 %d trouve(s) / %d corrige(s), R-04 %d trouve(s) / %d corrige(s), %d Blueprint(s) modifie(s), %d cas non traite(s), %d echec(s) de sauvegarde. ==="),
|
|
MeshNodesFound, MeshNodesFixed, NavNodesFound, NavNodesFixed, BlueprintsModified, Unhandled, SaveFailures);
|
|
|
|
return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0;
|
|
}
|