316 lines
9.6 KiB
C++
316 lines
9.6 KiB
C++
|
|
// 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<FString> Links(const UEdGraphPin* Pin)
|
||
|
|
{
|
||
|
|
TArray<FString> 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<FName, int32> PerFunction;
|
||
|
|
|
||
|
|
UE_LOG(LogLPFixWhatWhere, Warning, TEXT("=== GI::WhatWhereToArray -> LPDataCompat::WhatWhereToArray%s ==="),
|
||
|
|
bDryRun ? TEXT(" [DRY RUN]") : TEXT(""));
|
||
|
|
|
||
|
|
FAssetRegistryModule& AssetRegistryModule =
|
||
|
|
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
||
|
|
AssetRegistryModule.Get().SearchAllAssets(true);
|
||
|
|
|
||
|
|
TArray<FAssetData> BlueprintAssets;
|
||
|
|
// Sous-classes incluses : couvre les UWidgetBlueprint.
|
||
|
|
AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
|
||
|
|
|
||
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
||
|
|
|
||
|
|
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<UBlueprint>(AssetData.GetAsset());
|
||
|
|
if (!Blueprint)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
TArray<UEdGraph*> 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<UEdGraphNode*> Snapshot = Graph->Nodes;
|
||
|
|
for (UEdGraphNode* GraphNode : Snapshot)
|
||
|
|
{
|
||
|
|
UK2Node_CallFunction* OldNode = Cast<UK2Node_CallFunction>(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<FName, FString> 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<UK2Node_CallFunction>(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("<absent>"));
|
||
|
|
}
|
||
|
|
++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<FName, int32>& 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;
|
||
|
|
}
|