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 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 Tokens; + TArray Switches; + TMap 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(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + + TArray 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 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(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 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 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 -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 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 \LifePlay.uproject -run=LPFixAssetRegistryNodes [-DryRun] +// UnrealEditor-Cmd.exe \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(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + + TArray 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(); + + 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 FixedHere = 0; + + for (UEdGraph* Graph : Graphs) + { + if (!Graph) + { + continue; + } + + TArray 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(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(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(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 -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 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(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + + TArray 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(); + + 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 FixedHere = 0; + + for (UEdGraph* Graph : Graphs) + { + if (!Graph) + { + continue; + } + + // Snapshot : on cree et on detruit des noeuds pendant l'iteration. + TArray 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(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(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(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 \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 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::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(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + + TArray 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(AssetData.GetAsset()); + if (!Blueprint) + { + continue; + } + + TArray Graphs; + Blueprint->GetAllGraphs(Graphs); + + int32 FixedHere = 0; + struct FPendingLink { UK2Node_BreakStruct* Break; UK2Node_MakeStruct* Make; }; + TArray PendingLinks; + TArray NodesToReconstruct; + + for (UEdGraph* Graph : Graphs) + { + if (!Graph) + { + continue; + } + + TArray Snapshot = Graph->Nodes; + for (UEdGraphNode* GraphNode : Snapshot) + { + UK2Node_MakeStruct* MakeNode = Cast(GraphNode); + UK2Node_BreakStruct* BreakNode = Cast(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(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()->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 -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(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; +} 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 \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(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(Component); + if (!Primitive) + { + return TEXT("(pas un composant primitif)"); + } + + const UEnum* CollisionEnum = StaticEnum(); + 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(Collision)) : TEXT("?"), + *Primitive->GetCollisionProfileName().ToString()); + } +} + +ULPFixZeroScaleActorsCommandlet::ULPFixZeroScaleActorsCommandlet() +{ + IsClient = false; + IsServer = false; + IsEditor = true; + LogToConsole = true; +} + +int32 ULPFixZeroScaleActorsCommandlet::Main(const FString& Params) +{ + TArray Tokens; + TArray Switches; + TMap 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(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + + TArray 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 Components; + Actor->GetComponents(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 -run=LPFixZeroScaleActors (rapport seul) +// UnrealEditor-Cmd.exe -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" }); } }