// Portage UE 5.5 - voir LPTitleModManagerCommandlet.h #include "LPTitleModManagerCommandlet.h" #include "Blueprint/WidgetBlueprintLibrary.h" #include "Blueprint/WidgetTree.h" #include "Components/Button.h" #include "Components/Image.h" #include "Components/PanelWidget.h" #include "Components/TextBlock.h" #include "Components/Widget.h" #include "Engine/Texture2D.h" #include "EdGraph/EdGraph.h" #include "EdGraphSchema_K2.h" #include "K2Node_CallFunction.h" #include "K2Node_ComponentBoundEvent.h" #include "K2Node_IfThenElse.h" #include "K2Node_Self.h" #include "K2Node_VariableGet.h" #include "K2Node_VariableSet.h" #include "LPModSettingsLibrary.h" #include "Kismet/GameplayStatics.h" #include "Kismet/KismetSystemLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "Misc/PackageName.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" #include "WidgetBlueprint.h" #include "WidgetBlueprintEditorUtils.h" DEFINE_LOG_CATEGORY_STATIC(LogLPTitleMods, Log, All); namespace { const FName TitleMenuName(TEXT("TitleMenu")); const FName TitleButtonName(TEXT("ModManagerTitleButton")); /** Noeud cree a cote d'un noeud de reference ; Init renseigne la fonction ou la variable avant la creation des broches. */ template T* MakeNode(UEdGraph* Graph, const UEdGraphNode* Anchor, int32 Column, int32 Row, TFunctionRef Init) { FGraphNodeCreator Creator(*Graph); T* Node = Creator.CreateNode(false); Init(Node); Node->NodePosX = Anchor->NodePosX + 300 * Column; Node->NodePosY = Anchor->NodePosY + 400 + 160 * Row; Creator.Finalize(); return Node; } struct FEditor { const UEdGraphSchema_K2* Schema = GetDefault(); int32 Errors = 0; void Link(UEdGraphPin* A, UEdGraphPin* B, const TCHAR* What) { if (!A || !B || !Schema->TryCreateConnection(A, B)) { UE_LOG(LogLPTitleMods, Error, TEXT(" liaison impossible : %s"), What); ++Errors; } } void Default(UEdGraphPin* Pin, const TCHAR* Value, const TCHAR* What) { if (!Pin) { UE_LOG(LogLPTitleMods, Error, TEXT(" broche introuvable : %s"), What); ++Errors; return; } Schema->TrySetDefaultValue(*Pin, Value); if (Pin->DefaultValue != Value) { UE_LOG(LogLPTitleMods, Error, TEXT(" %s : valeur relue '%s', attendue '%s'"), What, *Pin->DefaultValue, Value); ++Errors; } } UFunction* Function(UClass* Class, const TCHAR* Name) { UFunction* F = Class ? Class->FindFunctionByName(Name) : nullptr; if (!F) { UE_LOG(LogLPTitleMods, Error, TEXT(" fonction introuvable : %s::%s"), Class ? *Class->GetName() : TEXT("?"), Name); ++Errors; } return F; } UK2Node_CallFunction* Call(UEdGraph* Graph, const UEdGraphNode* Anchor, int32 Column, int32 Row, UFunction* F) { return MakeNode(Graph, Anchor, Column, Row, [F](UK2Node_CallFunction* N) { if (F) { N->SetFromFunction(F); } }); } UK2Node_VariableGet* GetSelf(UEdGraph* Graph, const UEdGraphNode* Anchor, int32 Column, int32 Row, FName Name) { return MakeNode(Graph, Anchor, Column, Row, [Name](UK2Node_VariableGet* N) { N->VariableReference.SetSelfMember(Name); }); } /** Evenement de clic d'un element, et le noeud qu'il execute aujourd'hui. */ UK2Node_ComponentBoundEvent* BoundEvent(UBlueprint* BP, FName Component, const TCHAR* FirstCall, UEdGraphNode*& OutFirst) { TArray Events; FBlueprintEditorUtils::GetAllNodesOfClass(BP, Events); for (UK2Node_ComponentBoundEvent* Event : Events) { UEdGraphPin* Then = Event->FindPin(UEdGraphSchema_K2::PN_Then); if (Event->ComponentPropertyName != Component || !Then || Then->LinkedTo.Num() != 1) { continue; } const UK2Node_CallFunction* Call = Cast(Then->LinkedTo[0]->GetOwningNode()); if (Call && Call->FunctionReference.GetMemberName() == FirstCall) { OutFirst = Then->LinkedTo[0]->GetOwningNode(); return Event; } } UE_LOG(LogLPTitleMods, Error, TEXT(" %s : evenement de %s suivi de %s introuvable"), *BP->GetName(), *Component.ToString(), FirstCall); ++Errors; return nullptr; } }; bool Save(UBlueprint* BP) { UPackage* Package = BP->GetOutermost(); Package->MarkPackageDirty(); const FString Filename = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension()); FSavePackageArgs SaveArgs; SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; SaveArgs.Error = GWarn; const bool bOk = UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs); UE_LOG(LogLPTitleMods, Warning, TEXT("%s : %s"), bOk ? TEXT("sauvegarde") : TEXT("ECHEC de sauvegarde"), *Filename); return bOk; } } ULPTitleModManagerCommandlet::ULPTitleModManagerCommandlet() { IsClient = false; IsEditor = true; IsServer = false; LogToConsole = true; } int32 ULPTitleModManagerCommandlet::Main(const FString& Params) { TArray Tokens, Switches; TMap P; ParseCommandLine(*Params, Tokens, Switches, P); const bool bDryRun = Switches.Contains(TEXT("DryRun")); UWidgetBlueprint* MainMenu = LoadObject(nullptr, TEXT("/Game/UI/MainMenu.MainMenu")); UWidgetBlueprint* ModManager = LoadObject(nullptr, TEXT("/Game/UI/ModManager.ModManager")); UClass* GIClass = LoadObject(nullptr, TEXT("/Game/BPs/GI.GI_C")); if (!MainMenu || !ModManager || !GIClass) { UE_LOG(LogLPTitleMods, Error, TEXT("MainMenu, ModManager ou GI introuvable")); return 1; } const bool bModManagerDone = FBlueprintEditorUtils::FindNewVariableIndex(ModManager, TitleMenuName) != INDEX_NONE; const bool bMainMenuDone = MainMenu->WidgetTree->FindWidget(TitleButtonName) != nullptr; if (bModManagerDone && bMainMenuDone) { UE_LOG(LogLPTitleMods, Warning, TEXT("Deja applique (ModManager.TitleMenu et MainMenu.%s existent) : rien a faire"), *TitleButtonName.ToString()); return 0; } FEditor E; // GI.ModManager doit etre type ModManager_C (cible de TitleMenu). const FObjectProperty* GIModManager = CastField(GIClass->FindPropertyByName(TEXT("ModManager"))); if (!GIModManager || !ModManager->GeneratedClass || !GIModManager->PropertyClass->IsChildOf(ModManager->GeneratedClass)) { UE_LOG(LogLPTitleMods, Error, TEXT("GI.ModManager absent ou de type %s (ModManager_C attendu)"), GIModManager ? *GIModManager->PropertyClass->GetName() : TEXT("?")); return 1; } // 1. ModManager.TitleMenu (reference au menu titre qui l'a ouvert), compile pour que MainMenu puisse l'affecter. if (!bModManagerDone) { FEdGraphPinType MenuType; MenuType.PinCategory = UEdGraphSchema_K2::PC_Object; MenuType.PinSubCategoryObject = MainMenu->GeneratedClass; if (!FBlueprintEditorUtils::AddMemberVariable(ModManager, TitleMenuName, MenuType)) { UE_LOG(LogLPTitleMods, Error, TEXT("Ajout de ModManager.TitleMenu impossible")); return 1; } FKismetEditorUtilities::CompileBlueprint(ModManager); UE_LOG(LogLPTitleMods, Warning, TEXT("ModManager.TitleMenu (%s) ajoutee"), *MainMenu->GeneratedClass->GetName()); } // 2. MainMenu : entree "Mod Manager" de l'ecran titre, copie de LoadButton (meme taille, meme style) placee juste // apres lui, visible seulement sur l'ecran titre (liaison OnlyVisibleForTitleScreen, comme NewGameButton...). // L'entree d'origine ModManagerButton (sous-entree du menu en partie) n'est pas modifiee. if (!bMainMenuDone) { UWidgetTree* Tree = MainMenu->WidgetTree; UWidget* Load = Tree->FindWidget(TEXT("LoadButton")); UPanelWidget* Options = Load ? Load->GetParent() : nullptr; const FDelegateEditorBinding* TitleOnly = MainMenu->Bindings.FindByPredicate([](const FDelegateEditorBinding& B) { return B.PropertyName == TEXT("Visibility") && B.FunctionName == TEXT("OnlyVisibleForTitleScreen"); }); const UTexture2D* ModIcon = LoadObject(nullptr, TEXT("/Game/Heavy/New/mod.mod")); if (!Options || !TitleOnly || !ModIcon) { UE_LOG(LogLPTitleMods, Error, TEXT("LoadButton, sa boite, une liaison OnlyVisibleForTitleScreen ou l'icone mod introuvable")); return 1; } // Copie du sous-arbre de LoadButton (export/import texte, comme le copier-coller de l'editeur). TArray Source; Source.Add(Load); UWidgetTree::GetChildWidgets(Load, Source); FString Text; FWidgetBlueprintEditorUtils::ExportWidgetsToText(Source, Text); TSet Imported; TMap SlotData; FWidgetBlueprintEditorUtils::ImportWidgetsFromText(MainMenu, Text, Imported, SlotData); UButton* Button = nullptr; for (UWidget* W : Imported) { if (!W->Slot && Cast(W)) { Button = Cast(W); } } if (!Button || Imported.Num() != Source.Num()) { UE_LOG(LogLPTitleMods, Error, TEXT("Copie de LoadButton : %d element(s) importe(s) pour %d"), Imported.Num(), Source.Num()); return 1; } Button->Rename(*TitleButtonName.ToString(), Tree); Button->SetDisplayLabel(TitleButtonName.ToString()); Button->bIsVariable = true; Button->SetToolTipText(FText::FromString(TEXT("Choose which mods (modules) are enabled, then start or load a game"))); UPanelSlot* Slot = Options->InsertChildAt(Options->GetChildIndex(Load) + 1, Button); // Proprietes d'emplacement (marges) : celles de LoadButton. TMap SlotProps; FWidgetBlueprintEditorUtils::ExportPropertiesToText(Load->Slot, SlotProps); FWidgetBlueprintEditorUtils::ImportPropertiesFromText(Slot, SlotProps); int32 Icons = 0, Texts = 0; for (UWidget* W : Imported) { if (UImage* Image = Cast(W)) { if (Image->GetBrush().GetResourceObject() && Image->GetBrush().GetResourceObject()->GetName() == TEXT("loadgame")) { Image->SetBrushResourceObject(const_cast(ModIcon)); ++Icons; } } else if (UTextBlock* Label = Cast(W)) { Label->SetText(FText::FromString(TEXT("Mod Manager"))); ++Texts; } } if (Icons != 1 || Texts != 1) { UE_LOG(LogLPTitleMods, Error, TEXT("Copie de LoadButton : %d icone(s) loadgame et %d texte(s) (1 et 1 attendus)"), Icons, Texts); return 1; } FDelegateEditorBinding Binding = *TitleOnly; Binding.ObjectName = TitleButtonName.ToString(); MainMenu->Bindings.Add(Binding); UE_LOG(LogLPTitleMods, Warning, TEXT("MainMenu.%s : copie de LoadButton (%d elements), apres Load, icone mod, visible sur l'ecran titre (%s)"), *TitleButtonName.ToString(), Imported.Num(), *Binding.FunctionName.ToString()); // La variable du bouton doit exister dans la classe generee pour y lier l'evenement de clic. FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(MainMenu); FKismetEditorUtilities::CompileBlueprint(MainMenu); const FObjectProperty* ButtonProp = CastField(MainMenu->GeneratedClass->FindPropertyByName(TitleButtonName)); const FMulticastDelegateProperty* OnClicked = FindFProperty(UButton::StaticClass(), TEXT("OnClicked")); UEdGraph* G = FBlueprintEditorUtils::FindEventGraph(MainMenu); if (!ButtonProp || !OnClicked || !G) { UE_LOG(LogLPTitleMods, Error, TEXT("Variable %s, OnClicked ou graphe d'evenements introuvable"), *TitleButtonName.ToString()); return 1; } // Ancre : l'evenement de clic de l'entree d'origine (les nouveaux noeuds sont poses en dessous). UEdGraphNode* Ignored = nullptr; const UK2Node_ComponentBoundEvent* Anchor = E.BoundEvent(MainMenu, TEXT("ModManagerButton"), TEXT("CloseSelf"), Ignored); if (!Anchor) { return 1; } auto* Click = MakeNode(G, Anchor, 0, 3, [ButtonProp, OnClicked](UK2Node_ComponentBoundEvent* N) { N->InitializeComponentBoundEventParams(ButtonProp, OnClicked); }); auto* Hide = E.Call(G, Anchor, 1, 3, E.Function(UWidget::StaticClass(), TEXT("SetVisibility"))); E.Default(Hide->FindPin(TEXT("InVisibility")), TEXT("Collapsed"), TEXT("SetVisibility.InVisibility")); auto* GI = E.GetSelf(G, Anchor, 1, 4, TEXT("GI")); auto* Open = E.Call(G, Anchor, 2, 3, E.Function(GIClass, TEXT("CreateModManagerWidget"))); E.Default(Open->FindPin(TEXT("GameStart")), TEXT("false"), TEXT("CreateModManagerWidget.GameStart")); auto* GetMM = MakeNode(G, Anchor, 2, 4, [GIClass](UK2Node_VariableGet* N) { N->VariableReference.SetExternalMember(TEXT("ModManager"), GIClass); }); auto* SetTitle = MakeNode(G, Anchor, 3, 3, [ModManager](UK2Node_VariableSet* N) { N->VariableReference.SetExternalMember(TitleMenuName, ModManager->GeneratedClass); }); auto* Self = MakeNode(G, Anchor, 3, 4, [](UK2Node_Self*) {}); E.Link(Click->FindPin(UEdGraphSchema_K2::PN_Then), Hide->GetExecPin(), TEXT("clic -> masquer le menu")); E.Link(Hide->GetThenPin(), Open->GetExecPin(), TEXT("-> CreateModManagerWidget")); E.Link(GI->GetValuePin(), Open->FindPin(UEdGraphSchema_K2::PN_Self), TEXT("GI -> CreateModManagerWidget")); E.Link(Open->GetThenPin(), SetTitle->GetExecPin(), TEXT("-> TitleMenu")); E.Link(GI->GetValuePin(), GetMM->FindPin(UEdGraphSchema_K2::PN_Self), TEXT("GI -> GI.ModManager")); E.Link(GetMM->GetValuePin(), SetTitle->FindPin(UEdGraphSchema_K2::PN_Self), TEXT("GI.ModManager -> cible de TitleMenu")); E.Link(Self->FindPin(UEdGraphSchema_K2::PN_Self), SetTitle->FindPin(TitleMenuName), TEXT("self -> TitleMenu")); UE_LOG(LogLPTitleMods, Warning, TEXT("MainMenu : clic %s -> menu masque, CreateModManagerWidget, TitleMenu = self (7 noeuds)"), *TitleButtonName.ToString()); } // 3. ModManager : fermeture. if (!bModManagerDone) { UEdGraphNode* CloseMM = nullptr; UK2Node_ComponentBoundEvent* Close = nullptr; TArray Events; FBlueprintEditorUtils::GetAllNodesOfClass(ModManager, Events); for (UK2Node_ComponentBoundEvent* Event : Events) { UEdGraphPin* Then = Event->FindPin(UEdGraphSchema_K2::PN_Then); if (Then && Then->LinkedTo.Num() == 1) { const UK2Node_CallFunction* Call = Cast(Then->LinkedTo[0]->GetOwningNode()); if (Call && Call->FunctionReference.GetMemberName() == TEXT("CloseModManager")) { Close = Event; CloseMM = Then->LinkedTo[0]->GetOwningNode(); } } } if (!Close) { UE_LOG(LogLPTitleMods, Error, TEXT("ModManager : evenement suivi de CloseModManager introuvable")); ++E.Errors; } else { UEdGraph* G = Close->GetGraph(); UEdGraphPin* Then = Close->FindPin(UEdGraphSchema_K2::PN_Then); Then->BreakAllPinLinks(); auto* Branch = MakeNode(G, Close, 1, 0, [](UK2Node_IfThenElse*) {}); auto* Menu = E.GetSelf(G, Close, 0, 1, TitleMenuName); auto* Valid = E.Call(G, Close, 0, 2, E.Function(UKismetSystemLibrary::StaticClass(), TEXT("IsValid"))); E.Link(Then, Branch->GetExecPin(), TEXT("fermeture -> branche")); E.Link(Menu->GetValuePin(), Valid->FindPin(TEXT("Object")), TEXT("TitleMenu -> IsValid")); E.Link(Valid->GetReturnValuePin(), Branch->GetConditionPin(), TEXT("IsValid -> condition")); E.Link(Branch->GetElsePin(), CloseMM->FindPin(UEdGraphSchema_K2::PN_Execute), TEXT("sinon -> CloseModManager")); auto* Show = E.Call(G, Close, 2, 0, E.Function(UWidget::StaticClass(), TEXT("SetVisibility"))); E.Default(Show->FindPin(TEXT("InVisibility")), TEXT("Visible"), TEXT("SetVisibility.InVisibility")); auto* PC = E.Call(G, Close, 2, 1, E.Function(UGameplayStatics::StaticClass(), TEXT("GetPlayerController"))); auto* Focus = E.Call(G, Close, 3, 0, E.Function(UWidgetBlueprintLibrary::StaticClass(), TEXT("SetInputMode_UIOnlyEx"))); auto* GI = E.GetSelf(G, Close, 3, 1, TEXT("GI")); auto* Clear = E.Call(G, Close, 4, 0, E.Function(GIClass, TEXT("ClearModManagerWidget"))); auto* Populate = E.Call(G, Close, 5, 0, E.Function(GIClass, TEXT("PopulateRaws"))); auto* SaveSelection = E.Call(G, Close, 6, 0, E.Function(ULPModSettingsLibrary::StaticClass(), TEXT("SaveModSelection"))); E.Link(Branch->GetThenPin(), Show->GetExecPin(), TEXT("titre -> reafficher le menu")); E.Link(Menu->GetValuePin(), Show->FindPin(UEdGraphSchema_K2::PN_Self), TEXT("TitleMenu -> SetVisibility")); E.Link(Show->GetThenPin(), Focus->GetExecPin(), TEXT("-> SetInputMode_UIOnlyEx")); E.Link(PC->GetReturnValuePin(), Focus->FindPin(TEXT("PlayerController")), TEXT("GetPlayerController -> PlayerController")); E.Link(Menu->GetValuePin(), Focus->FindPin(TEXT("InWidgetToFocus")), TEXT("TitleMenu -> InWidgetToFocus")); E.Link(Focus->GetThenPin(), Clear->GetExecPin(), TEXT("-> ClearModManagerWidget")); E.Link(Clear->GetThenPin(), Populate->GetExecPin(), TEXT("-> PopulateRaws")); E.Link(Populate->GetThenPin(), SaveSelection->GetExecPin(), TEXT("-> SaveModSelection")); E.Link(GI->GetValuePin(), SaveSelection->FindPin(TEXT("GameInstance")), TEXT("GI -> SaveModSelection")); for (UK2Node_CallFunction* N : { Clear, Populate }) { E.Link(GI->GetValuePin(), N->FindPin(UEdGraphSchema_K2::PN_Self), TEXT("GI -> appel")); } UE_LOG(LogLPTitleMods, Warning, TEXT("ModManager : fermeture -> branche TitleMenu valide (%d noeuds ajoutes)"), 10); } } if (E.Errors > 0) { UE_LOG(LogLPTitleMods, Error, TEXT("%d erreur(s) : rien n'est sauvegarde"), E.Errors); return 1; } FKismetEditorUtilities::CompileBlueprint(ModManager); FKismetEditorUtilities::CompileBlueprint(MainMenu); if (ModManager->Status == BS_Error || MainMenu->Status == BS_Error) { UE_LOG(LogLPTitleMods, Error, TEXT("Compilation en erreur (ModManager %d, MainMenu %d) : rien n'est sauvegarde"), int32(ModManager->Status), int32(MainMenu->Status)); return 1; } UE_LOG(LogLPTitleMods, Warning, TEXT("ModManager et MainMenu compiles sans erreur (etat ModManager %d, MainMenu %d)"), int32(ModManager->Status), int32(MainMenu->Status)); if (bDryRun) { UE_LOG(LogLPTitleMods, Warning, TEXT("Essai a blanc : rien n'est sauvegarde")); return 0; } bool bOk = true; if (!bModManagerDone) { bOk &= Save(ModManager); } if (!bMainMenuDone) { bOk &= Save(MainMenu); } return bOk ? 0 : 1; }