LifePlay-Revival/Docs/correctifs/P02_Source_LifePlayEditorTools.patch

1941 lines
70 KiB
Diff
Raw Normal View History

diff --git a/D:/Game/LifePlay-master 4.27/Source/LifePlayEditorTools/LPCheckMeshAssignmentCommandlet.cpp b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPCheckMeshAssignmentCommandlet.cpp
index 61d6490..7f6e486 100644
--- a/D:/Game/LifePlay-master 4.27/Source/LifePlayEditorTools/LPCheckMeshAssignmentCommandlet.cpp
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPCheckMeshAssignmentCommandlet.cpp
@@ -67,7 +67,7 @@ int32 ULPCheckMeshAssignmentCommandlet::Main(const FString& Params)
AssetRegistryModule.Get().SearchAllAssets(true);
TArray<FAssetData> BlueprintAssets;
- AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetFName(), BlueprintAssets, true);
+ AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
int32 Total = 0;
int32 Disconnected = 0;
@@ -108,22 +108,40 @@ int32 ULPCheckMeshAssignmentCommandlet::Main(const FString& Params)
Callee = FString::Printf(TEXT(" [%s]"), *Fn->FunctionReference.GetMemberName().ToString());
}
+ // Un noeud ne nous interesse que s'il porte au moins une broche orpheline.
+ bool bHasOrphan = false;
+ for (const UEdGraphPin* Pin : GraphNode->Pins)
+ {
+ if (Pin && Pin->bOrphanedPin) { bHasOrphan = true; break; }
+ }
+ if (!bHasOrphan)
+ {
+ continue;
+ }
+
+ UE_LOG(LogLPCheckMesh, Warning, TEXT(" %s :: %s -> %s%s"),
+ *Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), *Callee);
+
+ // On dumpe TOUTES les broches : l'orpheline porte l'ancienne valeur, et la
+ // broche de remplacement dit si la migration l'a reprise ou laissee vide.
for (UEdGraphPin* Pin : GraphNode->Pins)
{
- if (!Pin || !Pin->bOrphanedPin)
+ if (!Pin || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
{
continue;
}
- ++Total;
- if (Pin->LinkedTo.Num() > 0)
+ const bool bOrphan = Pin->bOrphanedPin;
+ if (bOrphan)
{
- ++Disconnected;
+ ++Total;
+ if (Pin->LinkedTo.Num() > 0) { ++Disconnected; }
}
- UE_LOG(LogLPCheckMesh, Warning, TEXT(" %s :: %s -> %s%s | broche '%s' %s"),
- *Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), *Callee,
- *Pin->PinName.ToString(), *DescribePin(Pin));
+ UE_LOG(LogLPCheckMesh, Warning, TEXT(" %-22s %-3s %s"),
+ *Pin->PinName.ToString(),
+ bOrphan ? TEXT("[X]") : TEXT(" "),
+ *DescribePin(Pin));
}
}
}
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPCheckStreetMapCommandlet.cpp b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPCheckStreetMapCommandlet.cpp
new file mode 100644
index 0000000..32fc3d5
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPCheckStreetMapCommandlet.cpp
@@ -0,0 +1,186 @@
+// Portage UE 5.5 - voir LPCheckStreetMapCommandlet.h
+
+#include "LPCheckStreetMapCommandlet.h"
+
+#include "AssetRegistry/AssetRegistryModule.h"
+#include "HAL/FileManager.h"
+#include "Misc/FileHelper.h"
+#include "Misc/Paths.h"
+#include "StreetMap.h"
+#include "UObject/UObjectGlobals.h"
+
+DEFINE_LOG_CATEGORY_STATIC(LogLPCheckStreetMap, Log, All);
+
+namespace
+{
+ /** Ce qu'on a trouve dans une ville. */
+ struct FCityReport
+ {
+ FString Name;
+ int32 Roads = 0;
+ int32 Nodes = 0;
+ int32 Buildings = 0;
+ int32 Waters = 0;
+ int32 Parks = 0;
+ int32 NamedBuildings = 0; // batiments avec un nom non vide
+ int32 WithAmenity = 0; // batiments avec un Amenity non vide
+ int32 DistinctAmenities = 0;
+ bool bLoaded = false;
+ };
+}
+
+ULPCheckStreetMapCommandlet::ULPCheckStreetMapCommandlet()
+{
+ IsClient = false;
+ IsServer = false;
+ IsEditor = true;
+ LogToConsole = true;
+}
+
+int32 ULPCheckStreetMapCommandlet::Main(const FString& Params)
+{
+ TArray<FString> Tokens;
+ TArray<FString> Switches;
+ TMap<FString, FString> ParamsMap;
+ ParseCommandLine(*Params, Tokens, Switches, ParamsMap);
+
+ const bool bVerbose = Switches.Contains(TEXT("Verbose"));
+ const bool bDumpAmenities = Switches.Contains(TEXT("DumpAmenities"));
+ const FString CsvPath = ParamsMap.FindRef(TEXT("CSV"));
+
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT("=== Verification des assets UStreetMap ==="));
+
+ FAssetRegistryModule& AssetRegistryModule =
+ FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
+ AssetRegistryModule.Get().SearchAllAssets(true);
+
+ TArray<FAssetData> Assets;
+ AssetRegistryModule.Get().GetAssetsByClass(UStreetMap::StaticClass()->GetClassPathName(), Assets, true);
+
+ // Ordre stable : indispensable pour diffing entre deux executions.
+ Assets.Sort([](const FAssetData& A, const FAssetData& B)
+ {
+ return A.AssetName.LexicalLess(B.AssetName);
+ });
+
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT("%d asset(s) UStreetMap trouve(s)."), Assets.Num());
+
+ TArray<FCityReport> Reports;
+ Reports.Reserve(Assets.Num());
+
+ int32 LoadFailures = 0;
+ int32 EmptyCities = 0;
+
+ for (const FAssetData& AssetData : Assets)
+ {
+ FCityReport Report;
+ Report.Name = AssetData.AssetName.ToString();
+
+ UStreetMap* StreetMap = Cast<UStreetMap>(AssetData.GetAsset());
+ if (StreetMap == nullptr)
+ {
+ ++LoadFailures;
+ UE_LOG(LogLPCheckStreetMap, Error, TEXT(" ECHEC de chargement : %s"), *Report.Name);
+ Reports.Add(Report);
+ continue;
+ }
+
+ Report.bLoaded = true;
+ Report.Roads = StreetMap->GetRoads().Num();
+ Report.Nodes = StreetMap->GetNodes().Num();
+ Report.Buildings = StreetMap->GetBuildings().Num();
+ Report.Waters = StreetMap->GetWaters().Num();
+ Report.Parks = StreetMap->GetParks().Num();
+
+ // Amenity est le mecanisme par lequel le jeu identifie bars, salles de sport et
+ // commerces : c'est la donnee dont la perte serait la plus grave.
+ TSet<FString> Amenities;
+ for (const FStreetMapBuilding& Building : StreetMap->GetBuildings())
+ {
+ if (!Building.BuildingName.IsEmpty())
+ {
+ ++Report.NamedBuildings;
+ }
+ if (!Building.Amenity.IsEmpty())
+ {
+ ++Report.WithAmenity;
+ Amenities.Add(Building.Amenity);
+ }
+ }
+ Report.DistinctAmenities = Amenities.Num();
+
+ // Les comptes seuls ne prouvent pas que la donnee est la bonne : on releve les
+ // valeurs pour pouvoir les croiser avec les types de lieux du jeu (*.lpworld).
+ if (bDumpAmenities && Amenities.Num() > 0)
+ {
+ TArray<FString> Sorted = Amenities.Array();
+ Sorted.Sort();
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" --- amenities de %s (%d) : %s"),
+ *Report.Name, Sorted.Num(), *FString::Join(Sorted, TEXT(", ")));
+ }
+
+ if (Report.Roads == 0 && Report.Buildings == 0)
+ {
+ ++EmptyCities;
+ }
+
+ if (bVerbose)
+ {
+ UE_LOG(LogLPCheckStreetMap, Warning,
+ TEXT(" %-24s routes=%-7d noeuds=%-7d batiments=%-7d eaux=%-4d parcs=%-4d nommes=%-6d amenity=%-6d (%d types)"),
+ *Report.Name, Report.Roads, Report.Nodes, Report.Buildings,
+ Report.Waters, Report.Parks, Report.NamedBuildings,
+ Report.WithAmenity, Report.DistinctAmenities);
+ }
+
+ Reports.Add(Report);
+
+ // Sans cela, charger les 280 villes fait exploser la memoire (CentralLondon pese
+ // 33 Mo de donnees a lui seul).
+ CollectGarbage(RF_NoFlags);
+ }
+
+ // --- Synthese
+ int64 TotalRoads = 0, TotalBuildings = 0, TotalWaters = 0, TotalParks = 0, TotalAmenity = 0;
+ for (const FCityReport& R : Reports)
+ {
+ TotalRoads += R.Roads;
+ TotalBuildings += R.Buildings;
+ TotalWaters += R.Waters;
+ TotalParks += R.Parks;
+ TotalAmenity += R.WithAmenity;
+ }
+
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT("=== Synthese ==="));
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" villes chargees : %d / %d"), Assets.Num() - LoadFailures, Assets.Num());
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" echecs de chargement : %d"), LoadFailures);
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" villes sans donnees : %d"), EmptyCities);
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" routes : %lld"), TotalRoads);
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" batiments : %lld"), TotalBuildings);
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" batiments avec amenity : %lld"), TotalAmenity);
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" plans d'eau : %lld"), TotalWaters);
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" parcs : %lld"), TotalParks);
+
+ if (!CsvPath.IsEmpty())
+ {
+ FString Csv = TEXT("Ville;Charge;Routes;Noeuds;Batiments;Eaux;Parcs;BatimentsNommes;AvecAmenity;TypesAmenity\n");
+ for (const FCityReport& R : Reports)
+ {
+ Csv += FString::Printf(TEXT("%s;%d;%d;%d;%d;%d;%d;%d;%d;%d\n"),
+ *R.Name, R.bLoaded ? 1 : 0, R.Roads, R.Nodes, R.Buildings,
+ R.Waters, R.Parks, R.NamedBuildings, R.WithAmenity, R.DistinctAmenities);
+ }
+
+ if (FFileHelper::SaveStringToFile(Csv, *CsvPath))
+ {
+ UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" CSV ecrit : %s"), *CsvPath);
+ }
+ else
+ {
+ UE_LOG(LogLPCheckStreetMap, Error, TEXT(" ECHEC d'ecriture du CSV : %s"), *CsvPath);
+ }
+ }
+
+ // Code de retour non nul si une ville n'a pas pu etre chargee : exploitable en CI.
+ return LoadFailures > 0 ? 1 : 0;
+}
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPCheckStreetMapCommandlet.h b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPCheckStreetMapCommandlet.h
new file mode 100644
index 0000000..2d54589
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPCheckStreetMapCommandlet.h
@@ -0,0 +1,27 @@
+// Portage UE 5.5 - verification du chargement des assets ville apres migration.
+//
+// Charge chaque UStreetMap du projet et compte ce qu'il contient reellement : routes,
+// noeuds, batiments, plans d'eau, parcs, et amenities non vides. Le but est de prouver
+// que la fusion du plugin StreetMap n'a rien perdu, sans avoir a ouvrir l'editeur et
+// inspecter 280 villes a la main.
+//
+// Sortie CSV optionnelle, pour comparer une execution 4.27 et une execution 5.5.
+//
+// UnrealEditor-Cmd.exe <projet> -run=LPCheckStreetMap [-CSV=chemin.csv] [-Verbose]
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Commandlets/Commandlet.h"
+#include "LPCheckStreetMapCommandlet.generated.h"
+
+UCLASS()
+class ULPCheckStreetMapCommandlet : public UCommandlet
+{
+ GENERATED_BODY()
+
+public:
+ ULPCheckStreetMapCommandlet();
+
+ virtual int32 Main(const FString& Params) override;
+};
diff --git a/D:/Game/LifePlay-master 4.27/Source/LifePlayEditorTools/LPFixAssetRegistryNodesCommandlet.cpp b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryNodesCommandlet.cpp
index da20732..fb40e85 100644
--- a/D:/Game/LifePlay-master 4.27/Source/LifePlayEditorTools/LPFixAssetRegistryNodesCommandlet.cpp
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryNodesCommandlet.cpp
@@ -14,6 +14,7 @@
#include "Misc/FeedbackContext.h"
#include "Misc/PackageName.h"
#include "UObject/Package.h"
+#include "UObject/SavePackage.h"
#include "UObject/UObjectGlobals.h"
DEFINE_LOG_CATEGORY_STATIC(LogLPFixNodes, Log, All);
@@ -80,7 +81,8 @@ int32 ULPFixAssetRegistryNodesCommandlet::Main(const FString& Params)
AssetRegistryModule.Get().SearchAllAssets(true);
TArray<FAssetData> BlueprintAssets;
- AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetFName(), BlueprintAssets, true);
+ // 5.1+ : GetAssetsByClass prend un FTopLevelAssetPath, plus un FName de classe.
+ AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
UE_LOG(LogLPFixNodes, Warning, TEXT("%d Blueprints a inspecter."), BlueprintAssets.Num());
@@ -190,8 +192,16 @@ int32 ULPFixAssetRegistryNodesCommandlet::Main(const FString& Params)
UE_LOG(LogLPFixNodes, Warning, TEXT(" -> sauvegarde vers %s"), *Filename);
+ // 5.x : les arguments passent par FSavePackageArgs (Conform n'est plus implemente).
+ FSavePackageArgs SaveArgs;
+ SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
+ SaveArgs.SaveFlags = SAVE_None;
+ SaveArgs.bForceByteSwapping = false;
+ SaveArgs.bWarnOfLongFilename = true;
// GWarn et non GError : en commandlet, GError rend toute erreur de sauvegarde fatale.
- if (UPackage::SavePackage(Package, nullptr, RF_Public | RF_Standalone, *Filename, GWarn, nullptr, false, true, SAVE_None))
+ SaveArgs.Error = GWarn;
+
+ if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs))
{
UE_LOG(LogLPFixNodes, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), NodesFixedHere);
}
diff --git a/D:/Game/LifePlay-master 4.27/Source/LifePlayEditorTools/LPFixAssetRegistryNodesCommandlet.h b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryNodesCommandlet.h
index 781d3a6..427776f 100644
--- a/D:/Game/LifePlay-master 4.27/Source/LifePlayEditorTools/LPFixAssetRegistryNodesCommandlet.h
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryNodesCommandlet.h
@@ -1,11 +1,11 @@
-// Portage UE 4.27 (2026.9 beta)
+// Portage UE 5.5 (2026.9 beta) - correctif introduit lors du portage 4.27, toujours actif.
//
// Commandlet de correction ponctuelle : remplace les noeuds Blueprint appelant
// IAssetRegistry::GetAssetByObjectPath (devenu impur cote Epic) par la version pure
// ULPAssetRegistryHelpers::GetAssetByObjectPath, puis resauvegarde les Blueprints.
//
// Usage :
-// UE4Editor-Cmd.exe <chemin>\LifePlay.uproject -run=LPFixAssetRegistryNodes [-DryRun]
+// UnrealEditor-Cmd.exe <chemin>\LifePlay.uproject -run=LPFixAssetRegistryNodes [-DryRun]
#pragma once
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryPinsCommandlet.cpp b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryPinsCommandlet.cpp
new file mode 100644
index 0000000..7aa6146
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryPinsCommandlet.cpp
@@ -0,0 +1,319 @@
+// Portage UE 5.5 - voir LPFixAssetRegistryPinsCommandlet.h
+
+#include "LPFixAssetRegistryPinsCommandlet.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_BreakStruct.h"
+#include "K2Node_CallFunction.h"
+#include "Kismet2/BlueprintEditorUtils.h"
+#include "Kismet2/KismetEditorUtilities.h"
+#include "LPAssetRegistryHelpers.h"
+#include "Misc/PackageName.h"
+#include "UObject/Package.h"
+#include "UObject/SavePackage.h"
+#include "UObject/UObjectGlobals.h"
+
+DEFINE_LOG_CATEGORY_STATIC(LogLPFixARPins, Log, All);
+
+namespace
+{
+ const FName DeprecatedClassNamePin(TEXT("ClassName"));
+ const FName DeprecatedObjectPathPin(TEXT("ObjectPath"));
+ const FName AssetDataPin(TEXT("AssetData"));
+ const FName AssetDataStructName(TEXT("AssetData"));
+
+ const FName GetAssetsByClassFunction(TEXT("GetAssetsByClass"));
+ const FName ReplacementEnumFunction(TEXT("GetAssetsByClassName"));
+ const FName ReplacementPathFunction(TEXT("GetAssetObjectPath"));
+
+ /** Broche orpheline portant ce nom, s'il y en a une. */
+ UEdGraphPin* FindOrphan(UEdGraphNode* Node, FName PinName)
+ {
+ for (UEdGraphPin* Pin : Node->Pins)
+ {
+ if (Pin && Pin->bOrphanedPin && Pin->PinName == PinName)
+ {
+ return Pin;
+ }
+ }
+ return nullptr;
+ }
+}
+
+ULPFixAssetRegistryPinsCommandlet::ULPFixAssetRegistryPinsCommandlet()
+{
+ IsClient = false;
+ IsServer = false;
+ IsEditor = true;
+ LogToConsole = true;
+}
+
+int32 ULPFixAssetRegistryPinsCommandlet::Main(const FString& Params)
+{
+ const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase);
+
+ UFunction* EnumFunction = ULPAssetRegistryHelpers::StaticClass()->FindFunctionByName(ReplacementEnumFunction);
+ UFunction* PathFunction = ULPAssetRegistryHelpers::StaticClass()->FindFunctionByName(ReplacementPathFunction);
+ if (!EnumFunction || !PathFunction)
+ {
+ UE_LOG(LogLPFixARPins, Error, TEXT("Fonctions de remplacement introuvables (%s=%s, %s=%s)."),
+ *ReplacementEnumFunction.ToString(), EnumFunction ? TEXT("ok") : TEXT("NULL"),
+ *ReplacementPathFunction.ToString(), PathFunction ? TEXT("ok") : TEXT("NULL"));
+ return 1;
+ }
+
+ UE_LOG(LogLPFixARPins, Warning, TEXT("=== Broches AssetRegistry perdues a la migration 5.1%s ==="),
+ bDryRun ? TEXT(" [DRY RUN]") : TEXT(""));
+
+ FAssetRegistryModule& AssetRegistryModule =
+ FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
+ AssetRegistryModule.Get().SearchAllAssets(true);
+
+ TArray<FAssetData> BlueprintAssets;
+ AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
+
+ int32 EnumFixed = 0;
+ int32 PathFixed = 0;
+ int32 Unhandled = 0;
+ int32 BlueprintsModified = 0;
+ int32 SaveFailures = 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;
+ }
+
+ TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
+ for (UEdGraphNode* GraphNode : Snapshot)
+ {
+ // -------------------------------------------------------------------
+ // 1. GetAssetsByClass : la valeur de classe est restee sur l'orpheline.
+ // On bascule sur l'enrobage FName, qui la reprend telle quelle.
+ // -------------------------------------------------------------------
+ if (UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(GraphNode))
+ {
+ if (CallNode->FunctionReference.GetMemberName() != GetAssetsByClassFunction)
+ {
+ continue;
+ }
+
+ UEdGraphPin* Orphan = FindOrphan(CallNode, DeprecatedClassNamePin);
+ if (!Orphan)
+ {
+ continue; // deja corrige, ou noeud sain
+ }
+
+ const FString ClassValue = Orphan->DefaultValue;
+ UE_LOG(LogLPFixARPins, Warning, TEXT(" [enum] %s :: %s -> %s | classe='%s'"),
+ *Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), *ClassValue);
+
+ if (ClassValue.IsEmpty())
+ {
+ ++Unhandled;
+ UE_LOG(LogLPFixARPins, Error, TEXT(" orpheline vide : rien a reporter, reprise manuelle."));
+ continue;
+ }
+ if (bDryRun)
+ {
+ continue;
+ }
+
+ CallNode->Modify();
+ // La broche orpheline doit disparaitre AVANT la reconstruction, sinon
+ // ReconstructNode la conserve (garde-fou d'UE contre la perte de donnees).
+ Orphan->BreakAllPinLinks();
+ Orphan->DefaultValue.Empty();
+
+ CallNode->SetFromFunction(EnumFunction);
+ CallNode->ReconstructNode();
+
+ // La fonction de remplacement a une broche ClassName de meme nom : on y
+ // remet la valeur recuperee.
+ if (UEdGraphPin* NewPin = CallNode->FindPin(DeprecatedClassNamePin, EGPD_Input))
+ {
+ NewPin->DefaultValue = ClassValue;
+ UE_LOG(LogLPFixARPins, Warning, TEXT(" -> GetAssetsByClassName(ClassName='%s')"), *ClassValue);
+ ++EnumFixed;
+ ++FixedHere;
+ }
+ else
+ {
+ ++Unhandled;
+ UE_LOG(LogLPFixARPins, Error, TEXT(" broche ClassName absente apres reconstruction."));
+ }
+ continue;
+ }
+
+ // -------------------------------------------------------------------
+ // 2. Break AssetData : la sortie ObjectPath n'existe plus. On insere un
+ // appel a GetAssetObjectPath alimente par la meme structure, et on lui
+ // transfere les liaisons restees sur l'orpheline.
+ // -------------------------------------------------------------------
+ UK2Node_BreakStruct* BreakNode = Cast<UK2Node_BreakStruct>(GraphNode);
+ if (!BreakNode || !BreakNode->StructType ||
+ BreakNode->StructType->GetFName() != AssetDataStructName)
+ {
+ continue;
+ }
+
+ UEdGraphPin* Orphan = FindOrphan(BreakNode, DeprecatedObjectPathPin);
+ if (!Orphan)
+ {
+ continue;
+ }
+
+ UEdGraphPin* StructInput = BreakNode->FindPin(AssetDataPin, EGPD_Input);
+ UE_LOG(LogLPFixARPins, Warning, TEXT(" [path] %s :: %s -> %s | %d consommateur(s)"),
+ *Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), Orphan->LinkedTo.Num());
+
+ if (Orphan->LinkedTo.Num() == 0)
+ {
+ // Personne ne la lit : la vider suffit a la faire disparaitre.
+ if (!bDryRun)
+ {
+ BreakNode->Modify();
+ Orphan->DefaultValue.Empty();
+ BreakNode->ReconstructNode();
+ ++PathFixed;
+ ++FixedHere;
+ }
+ continue;
+ }
+
+ if (!StructInput || StructInput->LinkedTo.Num() == 0)
+ {
+ ++Unhandled;
+ UE_LOG(LogLPFixARPins, Error, TEXT(" entree AssetData non connectee : reprise manuelle."));
+ continue;
+ }
+ if (bDryRun)
+ {
+ continue;
+ }
+
+ // Noeud de remplacement, place juste sous le Break.
+ UK2Node_CallFunction* PathNode = NewObject<UK2Node_CallFunction>(Graph);
+ PathNode->SetFromFunction(PathFunction);
+ PathNode->NodePosX = BreakNode->NodePosX;
+ PathNode->NodePosY = BreakNode->NodePosY + 120;
+ PathNode->CreateNewGuid();
+ PathNode->PostPlacedNewNode();
+ PathNode->AllocateDefaultPins();
+ Graph->Modify();
+ Graph->AddNode(PathNode, false, false);
+
+ UEdGraphPin* PathStructIn = PathNode->FindPin(AssetDataPin, EGPD_Input);
+ UEdGraphPin* PathReturn = PathNode->GetReturnValuePin();
+
+ bool bOk = PathStructIn && PathReturn;
+
+ // Meme source de structure que le Break.
+ if (bOk)
+ {
+ UEdGraphPin* Source = StructInput->LinkedTo[0];
+ bOk = Schema->TryCreateConnection(Source, PathStructIn);
+ if (!bOk)
+ {
+ UE_LOG(LogLPFixARPins, Error, TEXT(" source AssetData incompatible avec la nouvelle entree."));
+ }
+ }
+
+ // Transfert des consommateurs de l'orpheline vers la nouvelle sortie.
+ if (bOk)
+ {
+ const FPinConnectionResponse R = Schema->MovePinLinks(*Orphan, *PathReturn);
+ bOk = R.Response != CONNECT_RESPONSE_DISALLOW;
+ if (!bOk)
+ {
+ UE_LOG(LogLPFixARPins, Error, TEXT(" transfert impossible (%s)."), *R.Message.ToString());
+ }
+ }
+
+ if (!bOk)
+ {
+ ++Unhandled;
+ PathNode->DestroyNode();
+ continue;
+ }
+
+ BreakNode->Modify();
+ Orphan->BreakAllPinLinks();
+ Orphan->DefaultValue.Empty();
+ BreakNode->ReconstructNode();
+
+ UE_LOG(LogLPFixARPins, Warning, TEXT(" -> GetAssetObjectPath inserE"));
+ ++PathFixed;
+ ++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(LogLPFixARPins, 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(LogLPFixARPins, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere);
+ }
+ else
+ {
+ ++SaveFailures;
+ UE_LOG(LogLPFixARPins, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName());
+ }
+ }
+ }
+
+ UE_LOG(LogLPFixARPins, Warning,
+ TEXT("=== Termine : %d enumeration(s), %d chemin(s), %d Blueprint(s) modifie(s), %d cas non traite(s), %d echec(s). ==="),
+ EnumFixed, PathFixed, BlueprintsModified, Unhandled, SaveFailures);
+
+ return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0;
+}
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryPinsCommandlet.h b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryPinsCommandlet.h
new file mode 100644
index 0000000..95ef029
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixAssetRegistryPinsCommandlet.h
@@ -0,0 +1,50 @@
+// Portage UE 5.5 (2026.9 beta) - correctif du 2026-09-28.
+//
+// SYMPTOME : pendant un evenement, l'homme s'appelle toujours Tyrone et la femme girl ou
+// Meihui, tous nus et au meme visage. generatePerson() sans argument doit produire un
+// acteur completement aleatoire ; il retombait sur un personnage par defaut.
+//
+// CAUSE : UE 5.1 a remplace
+// IAssetRegistry::GetAssetsByClass(FName ClassName, ...)
+// par GetAssetsByClass(FTopLevelAssetPath ClassPathName, ...)
+// FAssetData::ObjectPath (FName)
+// par FAssetData::GetSoftObjectPath()
+// La montee de version a supprime les anciennes broches des noeuds existants SANS reporter
+// leur contenu sur les nouvelles. Releve par LPCheckMeshAssignment :
+//
+// [GetAssetsByClass] ClassPathName [TopLevelAssetPath] VIDE
+// ClassName [orpheline] defaut='SkeletalMesh'
+//
+// Les trois appels de GI interrogeaient donc l'AssetRegistry avec un chemin de classe vide
+// et ne renvoyaient rien. Plus aucune enumeration de corps, de vetements ni de coiffures :
+// le tirage aleatoire n'avait plus de population ou puiser. Le chargement par chemin
+// explicite (GetAssetByObjectPath) fonctionnait, d'ou les seuls personnages visibles : ceux
+// que les scenes nomment explicitement, comme Tyrone via generatePerson(bggw_tyrone).
+//
+// Aucune erreur de compilation ne le signalait : 103 Blueprints compilaient a zero
+// avertissement. Seul le drapeau bOrphanedPin pose par le moteur le revelait (5 en 5.5,
+// 0 en 4.27).
+//
+// CORRECTIF : reaiguiller vers ULPAssetRegistryHelpers, qui re-expose les deux API avec
+// leur ancienne signature. Meme patron que LPFixAssetRegistryNodes et
+// LPFixEditableTextBoxFont.
+//
+// Usage :
+// UnrealEditor-Cmd.exe <projet> -run=LPFixAssetRegistryPins [-DryRun]
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Commandlets/Commandlet.h"
+#include "LPFixAssetRegistryPinsCommandlet.generated.h"
+
+UCLASS()
+class ULPFixAssetRegistryPinsCommandlet : public UCommandlet
+{
+ GENERATED_BODY()
+
+public:
+ ULPFixAssetRegistryPinsCommandlet();
+
+ virtual int32 Main(const FString& Params) override;
+};
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixDeprecatedNodesCommandlet.cpp b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixDeprecatedNodesCommandlet.cpp
new file mode 100644
index 0000000..e54c698
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixDeprecatedNodesCommandlet.cpp
@@ -0,0 +1,323 @@
+// 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;
+}
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixDeprecatedNodesCommandlet.h b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixDeprecatedNodesCommandlet.h
new file mode 100644
index 0000000..0c6aba4
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixDeprecatedNodesCommandlet.h
@@ -0,0 +1,39 @@
+// Portage UE 5.5 (2026.9 beta) - remediations R-03 et R-04.
+//
+// Deux familles de noeuds Blueprint deprecies subsistent apres la migration ; elles
+// representent a elles seules la quasi-totalite des avertissements de cook.
+//
+// R-03 USkinnedMeshComponent::SkeletalMesh (propriete) et son BlueprintGetter
+// GetSkeletalMesh_DEPRECATED -> USkeletalMeshComponent::GetSkeletalMeshAsset()
+// Les deux rendent le meme type (USkeletalMesh*), le remplacement conserve donc les
+// liaisons aval. Le noeud d'origine peut etre un K2Node_VariableGet (acces direct a
+// la propriete) ou un K2Node_CallFunction (appel du getter) : les deux sont traites.
+//
+// R-04 UNavigationSystemV1::K2_GetRandomPointInNavigableRadius
+// -> ULPNavigationHelpers::GetRandomLocationInNavigableRadius
+// On NE pointe PAS vers K2_GetRandomLocationInNavigableRadius du moteur : celui-ci
+// est BlueprintCallable alors que les noeuds existants sont purs. Voir
+// LPNavigationHelpers.h.
+//
+// Usage :
+// UnrealEditor-Cmd.exe <chemin>\LifePlay.uproject -run=LPFixDeprecatedNodes [-DryRun]
+//
+// -DryRun n'ecrit rien : il liste les noeuds trouves, leur Blueprint, leur graphe et l'etat
+// de leurs broches. A lancer systematiquement avant la passe reelle.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Commandlets/Commandlet.h"
+#include "LPFixDeprecatedNodesCommandlet.generated.h"
+
+UCLASS()
+class ULPFixDeprecatedNodesCommandlet : public UCommandlet
+{
+ GENERATED_BODY()
+
+public:
+ ULPFixDeprecatedNodesCommandlet();
+
+ virtual int32 Main(const FString& Params) override;
+};
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixEditableTextBoxFontCommandlet.cpp b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixEditableTextBoxFontCommandlet.cpp
new file mode 100644
index 0000000..80058af
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixEditableTextBoxFontCommandlet.cpp
@@ -0,0 +1,255 @@
+// Portage UE 5.5 - voir LPFixEditableTextBoxFontCommandlet.h
+
+#include "LPFixEditableTextBoxFontCommandlet.h"
+
+#include "AssetRegistry/AssetRegistryModule.h"
+#include "EdGraph/EdGraph.h"
+#include "EdGraph/EdGraphPin.h"
+#include "EdGraphSchema_K2.h"
+#include "Engine/Blueprint.h"
+#include "K2Node_BreakStruct.h"
+#include "K2Node_MakeStruct.h"
+#include "Kismet2/BlueprintEditorUtils.h"
+#include "Kismet2/KismetEditorUtilities.h"
+#include "Misc/PackageName.h"
+#include "Styling/SlateTypes.h"
+#include "UObject/Package.h"
+#include "UObject/SavePackage.h"
+#include "UObject/UObjectGlobals.h"
+
+DEFINE_LOG_CATEGORY_STATIC(LogLPFixFont, Log, All);
+
+namespace
+{
+ const TCHAR* OrphanPinName = TEXT("Font");
+ const TCHAR* TextStylePinName = TEXT("TextStyle");
+
+ /** Decrit une broche et ses liaisons, pour le rapport. */
+ 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(", ")));
+ }
+
+}
+
+ULPFixEditableTextBoxFontCommandlet::ULPFixEditableTextBoxFontCommandlet()
+{
+ IsClient = false;
+ IsServer = false;
+ IsEditor = true;
+ LogToConsole = true;
+}
+
+int32 ULPFixEditableTextBoxFontCommandlet::Main(const FString& Params)
+{
+ const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase);
+
+ UScriptStruct* EditableStruct = TBaseStructure<FEditableTextBoxStyle>::Get();
+ if (!EditableStruct)
+ {
+ UE_LOG(LogLPFixFont, Error, TEXT("Structure FEditableTextBoxStyle introuvable."));
+ return 1;
+ }
+
+ UE_LOG(LogLPFixFont, Warning, TEXT("=== Rebranchement de la broche Font (EditableTextBoxStyle)%s ==="),
+ bDryRun ? TEXT(" [DRY RUN]") : TEXT(""));
+
+ FAssetRegistryModule& AssetRegistryModule =
+ FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
+ AssetRegistryModule.Get().SearchAllAssets(true);
+
+ TArray<FAssetData> BlueprintAssets;
+ AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
+
+ int32 NodesFound = 0;
+ int32 NodesFixed = 0;
+ int32 BlueprintsModified = 0;
+ int32 SaveFailures = 0;
+ int32 UnhandledLinks = 0;
+
+ 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;
+ struct FPendingLink { UK2Node_BreakStruct* Break; UK2Node_MakeStruct* Make; };
+ TArray<FPendingLink> PendingLinks;
+ TArray<UEdGraphNode*> NodesToReconstruct;
+
+ for (UEdGraph* Graph : Graphs)
+ {
+ if (!Graph)
+ {
+ continue;
+ }
+
+ TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
+ for (UEdGraphNode* GraphNode : Snapshot)
+ {
+ UK2Node_MakeStruct* MakeNode = Cast<UK2Node_MakeStruct>(GraphNode);
+ UK2Node_BreakStruct* BreakNode = Cast<UK2Node_BreakStruct>(GraphNode);
+
+ const bool bIsTarget =
+ (MakeNode && MakeNode->StructType == EditableStruct) ||
+ (BreakNode && BreakNode->StructType == EditableStruct);
+ if (!bIsTarget)
+ {
+ continue;
+ }
+
+ UEdGraphPin* FontPin = GraphNode->FindPin(OrphanPinName);
+ if (!FontPin)
+ {
+ continue; // deja corrige
+ }
+
+ ++NodesFound;
+ UE_LOG(LogLPFixFont, Warning, TEXT(" %s :: %s -> %s"),
+ *Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName());
+ UE_LOG(LogLPFixFont, Warning, TEXT(" broche Font : %s"), *DescribeLinks(FontPin));
+ UE_LOG(LogLPFixFont, Warning, TEXT(" broche TextStyle : %s"),
+ *DescribeLinks(GraphNode->FindPin(TextStylePinName)));
+
+ if (bDryRun)
+ {
+ continue;
+ }
+
+ // Topologie relevee sur CharGen et StatMenu :
+ // [Break EditableTextBoxStyle].Font -> [Make EditableTextBoxStyle].Font
+ // La police n'est pas construite, elle est recopiee d'un style vers un autre.
+ // Les deux extremites etant la meme structure et le champ ayant migre dans
+ // TextStyle, la correspondance fidele est TextStyle -> TextStyle : aucun
+ // noeud intermediaire n'est necessaire, et le reste du style suit.
+ if (MakeNode)
+ {
+ for (UEdGraphPin* Source : FontPin->LinkedTo)
+ {
+ UK2Node_BreakStruct* SourceBreak = Cast<UK2Node_BreakStruct>(Source->GetOwningNode());
+ if (SourceBreak && SourceBreak->StructType == EditableStruct)
+ {
+ PendingLinks.Add({ SourceBreak, MakeNode });
+ }
+ else
+ {
+ UE_LOG(LogLPFixFont, Error,
+ TEXT(" source Font inattendue (%s) : rebranchement a faire a la main."),
+ Source->GetOwningNode() ? *Source->GetOwningNode()->GetName() : TEXT("?"));
+ ++UnhandledLinks;
+ }
+ }
+ }
+
+ // ReconstructNode conserve une broche orpheline dans DEUX cas : si elle
+ // porte des liaisons, et si elle porte une valeur differente du defaut.
+ // C'est le garde-fou d'UE contre la perte de donnees. Il faut donc lever
+ // les deux, APRES avoir releve la topologie et AVANT de reconstruire,
+ // sans quoi la broche Font survit et l'erreur persiste.
+ FontPin->BreakAllPinLinks();
+ GetDefault<UEdGraphSchema_K2>()->ResetPinToAutogeneratedDefaultValue(FontPin, false);
+
+ NodesToReconstruct.AddUnique(GraphNode);
+
+ ++NodesFixed;
+ ++FixedHere;
+ }
+ }
+
+ // La reconstruction invalide les broches : on la fait en bloc, puis on relie.
+ for (UEdGraphNode* Node : NodesToReconstruct)
+ {
+ Node->ReconstructNode();
+ }
+
+ for (const FPendingLink& Link : PendingLinks)
+ {
+ UEdGraphPin* From = Link.Break->FindPin(TextStylePinName, EGPD_Output);
+ UEdGraphPin* To = Link.Make->FindPin(TextStylePinName, EGPD_Input);
+ if (From && To)
+ {
+ // Idempotent : une seconde execution ne doit pas doubler la liaison.
+ if (!From->LinkedTo.Contains(To))
+ {
+ From->MakeLinkTo(To);
+ }
+ UE_LOG(LogLPFixFont, Warning, TEXT(" %s.TextStyle -> %s.TextStyle"),
+ *Link.Break->GetName(), *Link.Make->GetName());
+ }
+ else
+ {
+ ++UnhandledLinks;
+ UE_LOG(LogLPFixFont, Error, TEXT(" TextStyle introuvable sur %s ou %s."),
+ *Link.Break->GetName(), *Link.Make->GetName());
+ }
+ }
+
+ 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(LogLPFixFont, 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(LogLPFixFont, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere);
+ }
+ else
+ {
+ ++SaveFailures;
+ UE_LOG(LogLPFixFont, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName());
+ }
+ }
+ }
+
+ UE_LOG(LogLPFixFont, Warning, TEXT("=== Termine : %d noeud(s) trouve(s), %d traite(s) dans %d Blueprint(s), %d liaison(s) non geree(s), %d echec(s) de sauvegarde. ==="),
+ NodesFound, NodesFixed, BlueprintsModified, UnhandledLinks, SaveFailures);
+
+ return (SaveFailures > 0 || UnhandledLinks > 0) ? 1 : 0;
+}
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixEditableTextBoxFontCommandlet.h b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixEditableTextBoxFontCommandlet.h
new file mode 100644
index 0000000..c8039a7
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixEditableTextBoxFontCommandlet.h
@@ -0,0 +1,32 @@
+// Portage UE 5.5 - rebranchement de la broche Font des noeuds Make/Break EditableTextBoxStyle.
+//
+// FEditableTextBoxStyle::Font est devenu Font_DEPRECATED en 5.1 : la police vit desormais
+// dans TextStyle (FTextBlockStyle). Les noeuds Make/Break existants gardent une broche
+// orpheline "Font" qui fait echouer la compilation, et donc le cook :
+//
+// La broche "Font" n'existe plus sur le noeud "Break Editable Text Box Style".
+//
+// La commandlet retablit le chemin via TextStyle :
+// Make : source Font -> [Make TextBlockStyle].Font -> [Make EditableTextBoxStyle].TextStyle
+// Break : [Break EditableTextBoxStyle].TextStyle -> [Break TextBlockStyle].Font -> consommateurs
+//
+// UnrealEditor-Cmd.exe <projet> -run=LPFixEditableTextBoxFont [-DryRun]
+//
+// Sans -DryRun, les Blueprints modifies sont recompiles et sauvegardes.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Commandlets/Commandlet.h"
+#include "LPFixEditableTextBoxFontCommandlet.generated.h"
+
+UCLASS()
+class ULPFixEditableTextBoxFontCommandlet : public UCommandlet
+{
+ GENERATED_BODY()
+
+public:
+ ULPFixEditableTextBoxFontCommandlet();
+
+ virtual int32 Main(const FString& Params) override;
+};
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixOrphanBoundEventsCommandlet.cpp b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixOrphanBoundEventsCommandlet.cpp
new file mode 100644
index 0000000..cbfb64c
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixOrphanBoundEventsCommandlet.cpp
@@ -0,0 +1,200 @@
+// 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<FObjectProperty>(Scope, ComponentPropertyName) != nullptr;
+ }
+
+ /** Compte les noeuds atteignables depuis la broche Exec de sortie de l'evenement. */
+ int32 CountDownstreamNodes(const UEdGraphNode* EventNode)
+ {
+ TSet<const UEdGraphNode*> Seen;
+ TArray<const UEdGraphNode*> 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<FAssetRegistryModule>(TEXT("AssetRegistry"));
+ AssetRegistryModule.Get().SearchAllAssets(true);
+
+ TArray<FAssetData> 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<UBlueprint>(AssetData.GetAsset());
+ if (!Blueprint)
+ {
+ continue;
+ }
+
+ TArray<UEdGraph*> Graphs;
+ Blueprint->GetAllGraphs(Graphs);
+
+ int32 RemovedHere = 0;
+
+ for (UEdGraph* Graph : Graphs)
+ {
+ if (!Graph)
+ {
+ continue;
+ }
+
+ TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
+ for (UEdGraphNode* GraphNode : Snapshot)
+ {
+ UK2Node_ComponentBoundEvent* BoundEvent = Cast<UK2Node_ComponentBoundEvent>(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;
+}
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixOrphanBoundEventsCommandlet.h b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixOrphanBoundEventsCommandlet.h
new file mode 100644
index 0000000..7bf014a
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixOrphanBoundEventsCommandlet.h
@@ -0,0 +1,31 @@
+// Portage UE 5.5 (2026.9 beta) - remediation R-05.
+//
+// Trois evenements « On Clicked » restent lies a des widgets qui n'existent plus dans l'arbre
+// de leur Blueprint (UI/TopRight::StatButtonL, UI/CharGen::ButtonPreviewErect et
+// ButtonEyelashColor). Le compilateur emet « n'a pas de composant correspondant valide ! » et
+// le delegue n'est jamais branche : le clic est deja mort a l'execution.
+//
+// Cette commandlet repere ces UK2Node_ComponentBoundEvent orphelins et les supprime. Les
+// noeuds situes en aval sont CONSERVES tels quels : ils deviennent simplement inatteignables,
+// ce qui n'a aucun effet a l'execution mais laisse la logique visible pour qui voudrait
+// recreer le widget manquant. Le rapport indique combien de noeuds passent dans ce cas.
+//
+// Usage :
+// UnrealEditor-Cmd.exe <chemin>\LifePlay.uproject -run=LPFixOrphanBoundEvents [-DryRun]
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Commandlets/Commandlet.h"
+#include "LPFixOrphanBoundEventsCommandlet.generated.h"
+
+UCLASS()
+class ULPFixOrphanBoundEventsCommandlet : public UCommandlet
+{
+ GENERATED_BODY()
+
+public:
+ ULPFixOrphanBoundEventsCommandlet();
+
+ virtual int32 Main(const FString& Params) override;
+};
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixZeroScaleActorsCommandlet.cpp b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixZeroScaleActorsCommandlet.cpp
new file mode 100644
index 0000000..c1d5e9c
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixZeroScaleActorsCommandlet.cpp
@@ -0,0 +1,223 @@
+// Portage UE 5.5 - voir LPFixZeroScaleActorsCommandlet.h
+
+#include "LPFixZeroScaleActorsCommandlet.h"
+
+#include "AssetRegistry/AssetRegistryModule.h"
+#include "Components/SceneComponent.h"
+#include "Components/PrimitiveComponent.h"
+#include "Components/StaticMeshComponent.h"
+#include "Engine/StaticMesh.h"
+#include "Engine/World.h"
+#include "GameFramework/Actor.h"
+#include "Misc/PackageName.h"
+#include "UObject/Package.h"
+#include "UObject/SavePackage.h"
+#include "UObject/UObjectGlobals.h"
+
+DEFINE_LOG_CATEGORY_STATIC(LogLPFixZeroScale, Log, All);
+
+namespace
+{
+ /** Seuil du moteur : FMath::IsNearlyZero sur chaque composante avant creation du body. */
+ constexpr double ZeroScaleTolerance = UE_KINDA_SMALL_NUMBER;
+
+ /** Decrit le maillage porte par un composant, pour pouvoir juger de la valeur a remettre. */
+ FString DescribeMesh(const USceneComponent* Component)
+ {
+ const UStaticMeshComponent* MeshComponent = Cast<UStaticMeshComponent>(Component);
+ if (!MeshComponent || !MeshComponent->GetStaticMesh())
+ {
+ return TEXT("(pas de maillage statique)");
+ }
+
+ const UStaticMesh* Mesh = MeshComponent->GetStaticMesh();
+ const FBoxSphereBounds Bounds = Mesh->GetBounds();
+ return FString::Printf(TEXT("%s demi-dimensions locales (X=%.2f Y=%.2f Z=%.2f)"),
+ *Mesh->GetPathName(),
+ Bounds.BoxExtent.X, Bounds.BoxExtent.Y, Bounds.BoxExtent.Z);
+ }
+
+ /**
+ * Visibilite et collision : ce sont elles qui dictent la valeur de remplacement.
+ * Un bloqueur invisible tolere une epaisseur quelconque ; un mur visible non.
+ */
+ FString DescribeRendering(const USceneComponent* Component)
+ {
+ const UPrimitiveComponent* Primitive = Cast<UPrimitiveComponent>(Component);
+ if (!Primitive)
+ {
+ return TEXT("(pas un composant primitif)");
+ }
+
+ const UEnum* CollisionEnum = StaticEnum<ECollisionEnabled::Type>();
+ const ECollisionEnabled::Type Collision = Primitive->GetCollisionEnabled();
+
+ return FString::Printf(TEXT("visible=%s cacheEnJeu=%s collision=%s profil=%s"),
+ Primitive->IsVisible() ? TEXT("oui") : TEXT("NON"),
+ Primitive->bHiddenInGame ? TEXT("oui") : TEXT("non"),
+ CollisionEnum ? *CollisionEnum->GetNameStringByValue(static_cast<int64>(Collision)) : TEXT("?"),
+ *Primitive->GetCollisionProfileName().ToString());
+ }
+}
+
+ULPFixZeroScaleActorsCommandlet::ULPFixZeroScaleActorsCommandlet()
+{
+ IsClient = false;
+ IsServer = false;
+ IsEditor = true;
+ LogToConsole = true;
+}
+
+int32 ULPFixZeroScaleActorsCommandlet::Main(const FString& Params)
+{
+ TArray<FString> Tokens;
+ TArray<FString> Switches;
+ TMap<FString, FString> ParamsMap;
+ ParseCommandLine(*Params, Tokens, Switches, ParamsMap);
+
+ const bool bFix = Switches.Contains(TEXT("Fix"));
+ double Replacement = 1.0;
+ if (const FString* ScaleText = ParamsMap.Find(TEXT("Scale")))
+ {
+ Replacement = FCString::Atod(**ScaleText);
+ }
+ // Garde-fou volontaire : PowerShell coupe « -Scale=0.1 » en deux arguments si on ne le
+ // guillemette pas, et la commandlet recevrait alors 0 sans rien dire. Voir l'en-tete.
+ if (bFix && FMath::IsNearlyZero(Replacement, ZeroScaleTolerance))
+ {
+ UE_LOG(LogLPFixZeroScale, Error,
+ TEXT("-Scale lu a %f : valeur nulle ou argument coupe par le shell. Guillemettez \"-Scale=0.1\"."),
+ Replacement);
+ return 1;
+ }
+
+ UE_LOG(LogLPFixZeroScale, Warning, TEXT("=== Composantes d'echelle nulles (R-06)%s ==="),
+ bFix ? *FString::Printf(TEXT(" [CORRECTION -> %.4f]"), Replacement) : TEXT(" [RAPPORT SEUL]"));
+
+ FAssetRegistryModule& AssetRegistryModule =
+ FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
+ AssetRegistryModule.Get().SearchAllAssets(true);
+
+ TArray<FAssetData> MapAssets;
+ AssetRegistryModule.Get().GetAssetsByClass(UWorld::StaticClass()->GetClassPathName(), MapAssets, true);
+
+ int32 ComponentsFound = 0;
+ int32 ComponentsFixed = 0;
+ int32 MapsModified = 0;
+ int32 SaveFailures = 0;
+
+ for (const FAssetData& AssetData : MapAssets)
+ {
+ const FString PackageName = AssetData.PackageName.ToString();
+ if (!PackageName.StartsWith(TEXT("/Game/")))
+ {
+ continue;
+ }
+
+ UPackage* Package = LoadPackage(nullptr, *PackageName, LOAD_None);
+ UWorld* World = Package ? UWorld::FindWorldInPackage(Package) : nullptr;
+ if (!World || !World->PersistentLevel)
+ {
+ UE_LOG(LogLPFixZeroScale, Warning, TEXT(" %s : monde illisible, ignore."), *PackageName);
+ continue;
+ }
+
+ int32 FixedHere = 0;
+
+ for (AActor* Actor : World->PersistentLevel->Actors)
+ {
+ if (!Actor)
+ {
+ continue;
+ }
+
+ TArray<USceneComponent*> Components;
+ Actor->GetComponents<USceneComponent>(Components);
+
+ for (USceneComponent* Component : Components)
+ {
+ if (!Component)
+ {
+ continue;
+ }
+
+ FVector Scale = Component->GetRelativeScale3D();
+ const bool bZeroX = FMath::IsNearlyZero(Scale.X, ZeroScaleTolerance);
+ const bool bZeroY = FMath::IsNearlyZero(Scale.Y, ZeroScaleTolerance);
+ const bool bZeroZ = FMath::IsNearlyZero(Scale.Z, ZeroScaleTolerance);
+ if (!bZeroX && !bZeroY && !bZeroZ)
+ {
+ continue;
+ }
+
+ ++ComponentsFound;
+ UE_LOG(LogLPFixZeroScale, Warning, TEXT(" %s : %s.%s"),
+ *PackageName, *Actor->GetName(), *Component->GetName());
+ UE_LOG(LogLPFixZeroScale, Warning, TEXT(" echelle : X=%.4f Y=%.4f Z=%.4f"),
+ Scale.X, Scale.Y, Scale.Z);
+ UE_LOG(LogLPFixZeroScale, Warning, TEXT(" maillage : %s"), *DescribeMesh(Component));
+ UE_LOG(LogLPFixZeroScale, Warning, TEXT(" rendu : %s"), *DescribeRendering(Component));
+
+ if (!bFix)
+ {
+ continue;
+ }
+
+ if (bZeroX) { Scale.X = Replacement; }
+ if (bZeroY) { Scale.Y = Replacement; }
+ if (bZeroZ) { Scale.Z = Replacement; }
+
+ Actor->Modify();
+ Component->Modify();
+ Component->SetRelativeScale3D(Scale);
+ Component->MarkRenderStateDirty();
+ // Recalcule le corps physique avec la nouvelle echelle.
+ Component->RecreatePhysicsState();
+
+ UE_LOG(LogLPFixZeroScale, Warning, TEXT(" -> X=%.4f Y=%.4f Z=%.4f"),
+ Scale.X, Scale.Y, Scale.Z);
+
+ ++ComponentsFixed;
+ ++FixedHere;
+ }
+ }
+
+ if (FixedHere > 0)
+ {
+ ++MapsModified;
+
+ FString Filename;
+ if (!FPackageName::TryConvertLongPackageNameToFilename(
+ PackageName, Filename, FPackageName::GetMapPackageExtension()))
+ {
+ ++SaveFailures;
+ UE_LOG(LogLPFixZeroScale, Error, TEXT(" chemin introuvable pour %s"), *PackageName);
+ continue;
+ }
+
+ Package->MarkPackageDirty();
+
+ FSavePackageArgs SaveArgs;
+ SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
+ SaveArgs.SaveFlags = SAVE_None;
+ SaveArgs.bWarnOfLongFilename = true;
+ SaveArgs.Error = GWarn;
+
+ if (UPackage::SavePackage(Package, World, *Filename, SaveArgs))
+ {
+ UE_LOG(LogLPFixZeroScale, Warning, TEXT(" -> %s sauvegarde (%d composant(s))."), *PackageName, FixedHere);
+ }
+ else
+ {
+ ++SaveFailures;
+ UE_LOG(LogLPFixZeroScale, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *PackageName);
+ }
+ }
+ }
+
+ UE_LOG(LogLPFixZeroScale, Warning,
+ TEXT("=== Termine : %d composant(s) a echelle nulle, %d corrige(s) dans %d carte(s), %d echec(s) de sauvegarde. ==="),
+ ComponentsFound, ComponentsFixed, MapsModified, SaveFailures);
+
+ return SaveFailures > 0 ? 1 : 0;
+}
diff --git a/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixZeroScaleActorsCommandlet.h b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixZeroScaleActorsCommandlet.h
new file mode 100644
index 0000000..975b6a8
--- /dev/null
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LPFixZeroScaleActorsCommandlet.h
@@ -0,0 +1,39 @@
+// Portage UE 5.5 (2026.9 beta) - remediation R-06.
+//
+// /Game/Scenes/CentralLondon:PersistentLevel.Wall.StaticMeshComponent0 porte une echelle
+// (X=0.000, Y=12.562, Z=5.750). Une composante nulle produit un corps physique degenere :
+// le moteur emet « has a component set to zero, which will result in a bad body instance »
+// a chaque cook, un MapCheck: Error, et l'inversion de la matrice de transformation declenche
+// un ensure « TMatrix::InverseFast() ... results in NaNs » en session d'edition.
+//
+// La commandlet parcourt les cartes de /Game/, signale toute composante d'echelle nulle avec
+// le maillage concerne et ses dimensions (pour pouvoir juger de la valeur de remplacement),
+// puis corrige si on le lui demande.
+//
+// Usage :
+// UnrealEditor-Cmd.exe <projet> -run=LPFixZeroScaleActors (rapport seul)
+// UnrealEditor-Cmd.exe <projet> -run=LPFixZeroScaleActors -Fix [-Scale=1.0]
+//
+// ATTENTION sous PowerShell : « -Scale=0.1 » non guillemete est coupe en « -Scale=0 » et
+// « .1 », la commandlet recevrait 0. Ecrire "-Scale=0.1". Le garde-fou sur la valeur nulle
+// est la pour attraper le cas.
+//
+// Le rapport est le mode par defaut : contrairement aux autres commandlets du projet, la
+// valeur de remplacement est un choix de niveau, pas une equivalence mecanique.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Commandlets/Commandlet.h"
+#include "LPFixZeroScaleActorsCommandlet.generated.h"
+
+UCLASS()
+class ULPFixZeroScaleActorsCommandlet : public UCommandlet
+{
+ GENERATED_BODY()
+
+public:
+ ULPFixZeroScaleActorsCommandlet();
+
+ virtual int32 Main(const FString& Params) override;
+};
diff --git a/D:/Game/LifePlay-master 4.27/Source/LifePlayEditorTools/LifePlayEditorTools.Build.cs b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LifePlayEditorTools.Build.cs
index 438d0c9..da7afda 100644
--- a/D:/Game/LifePlay-master 4.27/Source/LifePlayEditorTools/LifePlayEditorTools.Build.cs
+++ b/D:/Game/LifePlay-master 5.5/Source/LifePlayEditorTools/LifePlayEditorTools.Build.cs
@@ -21,7 +21,19 @@ public class LifePlayEditorTools : ModuleRules
"UnrealEd",
"BlueprintGraph",
"KismetCompiler",
- "AssetRegistry"
+ "AssetRegistry",
+ // Portage 5.5 : LPCheckStreetMapCommandlet lit les assets UStreetMap
+ "StreetMapRuntime",
+ // Portage 5.5 : LPFixEditableTextBoxFontCommandlet manipule
+ // FEditableTextBoxStyle et FTextBlockStyle
+ "SlateCore",
+ // Portage 5.5 (R-04) : LPFixDeprecatedNodesCommandlet reference
+ // UNavigationSystemV1 pour identifier les noeuds a reaiguiller
+ "NavigationSystem",
+ // Portage 5.5 (R-05) : LPFixOrphanBoundEventsCommandlet traite les
+ // UWidgetBlueprint au meme titre que les UBlueprint
+ "UMG",
+ "UMGEditor"
});
}
}