// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "BlueprintGraphPrivatePCH.h" #include "KismetCompiler.h" #include "Runtime/Engine/Classes/Kismet/GameplayStatics.h" #include "BlueprintNodeSpawner.h" #include "EditorCategoryUtils.h" #include "BlueprintActionDatabaseRegistrar.h" struct FK2Node_ConstructObjectFromClassHelper { static FString WorldContextPinName; static FString ClassPinName; }; FString FK2Node_ConstructObjectFromClassHelper::WorldContextPinName(TEXT("WorldContextObject")); FString FK2Node_ConstructObjectFromClassHelper::ClassPinName(TEXT("Class")); #define LOCTEXT_NAMESPACE "K2Node_ConstructObjectFromClass" UK2Node_ConstructObjectFromClass::UK2Node_ConstructObjectFromClass(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { NodeTooltip = LOCTEXT("NodeTooltip", "Attempts to spawn a new object"); } UClass* UK2Node_ConstructObjectFromClass::GetClassPinBaseClass() const { return UObject::StaticClass(); } void UK2Node_ConstructObjectFromClass::AllocateDefaultPins() { const UEdGraphSchema_K2* K2Schema = GetDefault(); // Add execution pins CreatePin(EGPD_Input, K2Schema->PC_Exec, TEXT(""), NULL, false, false, K2Schema->PN_Execute); CreatePin(EGPD_Output, K2Schema->PC_Exec, TEXT(""), NULL, false, false, K2Schema->PN_Then); // If required add the world context pin if ( GetBlueprint()->ParentClass->HasMetaData(FBlueprintMetadata::MD_ShowWorldContextPin) ) { CreatePin(EGPD_Input, K2Schema->PC_Object, TEXT(""), UObject::StaticClass(), false, false, FK2Node_ConstructObjectFromClassHelper::WorldContextPinName); } // Add blueprint pin UEdGraphPin* ClassPin = CreatePin(EGPD_Input, K2Schema->PC_Class, TEXT(""), GetClassPinBaseClass(), false, false, FK2Node_ConstructObjectFromClassHelper::ClassPinName); SetPinToolTip(*ClassPin, LOCTEXT("ClassPinDescription", "The object class you want to spawn")); // Result pin UEdGraphPin* ResultPin = CreatePin(EGPD_Output, K2Schema->PC_Object, TEXT(""), GetClassPinBaseClass(), false, false, K2Schema->PN_ReturnValue); SetPinToolTip(*ResultPin, LOCTEXT("ResultPinDescription", "The spawned object")); Super::AllocateDefaultPins(); } void UK2Node_ConstructObjectFromClass::SetPinToolTip(UEdGraphPin& MutatablePin, const FText& PinDescription) const { MutatablePin.PinToolTip = UEdGraphSchema_K2::TypeToText(MutatablePin.PinType).ToString(); UEdGraphSchema_K2 const* const K2Schema = Cast(GetSchema()); if (K2Schema != nullptr) { MutatablePin.PinToolTip += TEXT(" "); MutatablePin.PinToolTip += K2Schema->GetPinDisplayName(&MutatablePin).ToString(); } MutatablePin.PinToolTip += FString(TEXT("\n")) + PinDescription.ToString(); } void UK2Node_ConstructObjectFromClass::CreatePinsForClass(UClass* InClass) { check(InClass != NULL); const UEdGraphSchema_K2* K2Schema = GetDefault(); const UObject* const ClassDefaultObject = InClass->GetDefaultObject(false); for (TFieldIterator PropertyIt(InClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { UProperty* Property = *PropertyIt; UClass* PropertyClass = CastChecked(Property->GetOuter()); const bool bIsDelegate = Property->IsA(UMulticastDelegateProperty::StaticClass()); const bool bIsExposedToSpawn = UEdGraphSchema_K2::IsPropertyExposedOnSpawn(Property); const bool bIsSettableExternally = !Property->HasAnyPropertyFlags(CPF_DisableEditOnInstance); if( bIsExposedToSpawn && !Property->HasAnyPropertyFlags(CPF_Parm) && bIsSettableExternally && Property->HasAllPropertyFlags(CPF_BlueprintVisible) && !bIsDelegate && (NULL == FindPin(Property->GetName()) ) ) { UEdGraphPin* Pin = CreatePin(EGPD_Input, TEXT(""), TEXT(""), NULL, false, false, Property->GetName()); const bool bPinGood = (Pin != NULL) && K2Schema->ConvertPropertyToPinType(Property, /*out*/ Pin->PinType); if (ClassDefaultObject && Pin != NULL && K2Schema->PinDefaultValueIsEditable(*Pin)) { FString DefaultValueAsString; const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString); check( bDefaultValueSet ); K2Schema->TrySetDefaultValue(*Pin, DefaultValueAsString); } // Copy tooltip from the property. if (Pin != nullptr) { K2Schema->ConstructBasicPinTooltip(*Pin, Property->GetToolTipText(), Pin->PinToolTip); } } } // Change class of output pin UEdGraphPin* ResultPin = GetResultPin(); ResultPin->PinType.PinSubCategoryObject = InClass; } UClass* UK2Node_ConstructObjectFromClass::GetClassToSpawn(const TArray* InPinsToSearch /*=NULL*/) const { UClass* UseSpawnClass = NULL; const TArray* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins; UEdGraphPin* ClassPin = GetClassPin(PinsToSearch); if(ClassPin && ClassPin->DefaultObject != NULL && ClassPin->LinkedTo.Num() == 0) { UseSpawnClass = CastChecked(ClassPin->DefaultObject); } return UseSpawnClass; } void UK2Node_ConstructObjectFromClass::ReallocatePinsDuringReconstruction(TArray& OldPins) { AllocateDefaultPins(); UClass* UseSpawnClass = GetClassToSpawn(&OldPins); if( UseSpawnClass != NULL ) { CreatePinsForClass(UseSpawnClass); } } bool UK2Node_ConstructObjectFromClass::IsSpawnVarPin(UEdGraphPin* Pin) { const UEdGraphSchema_K2* K2Schema = GetDefault(); return( Pin->PinName != K2Schema->PN_Execute && Pin->PinName != K2Schema->PN_Then && Pin->PinName != K2Schema->PN_ReturnValue && Pin->PinName != FK2Node_ConstructObjectFromClassHelper::ClassPinName && Pin->PinName != FK2Node_ConstructObjectFromClassHelper::WorldContextPinName ); } void UK2Node_ConstructObjectFromClass::PinDefaultValueChanged(UEdGraphPin* ChangedPin) { if (ChangedPin->PinName == FK2Node_ConstructObjectFromClassHelper::ClassPinName) { const UEdGraphSchema_K2* K2Schema = GetDefault(); // Because the archetype has changed, we break the output link as the output pin type will change //UEdGraphPin* ResultPin = GetResultPin(); //ResultPin->BreakAllPinLinks(); // Remove all pins related to archetype variables TArray OldPins = Pins; for (int32 i = 0; i < OldPins.Num(); i++) { UEdGraphPin* OldPin = OldPins[i]; if (IsSpawnVarPin(OldPin)) { OldPin->BreakAllPinLinks(); Pins.Remove(OldPin); } } CachedNodeTitle.MarkDirty(); UClass* UseSpawnClass = GetClassToSpawn(); if (UseSpawnClass != NULL) { CreatePinsForClass(UseSpawnClass); } // Refresh the UI for the graph so the pin changes show up UEdGraph* Graph = GetGraph(); Graph->NotifyGraphChanged(); // Mark dirty FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint()); } } FText UK2Node_ConstructObjectFromClass::GetTooltipText() const { return NodeTooltip; } UEdGraphPin* UK2Node_ConstructObjectFromClass::GetThenPin()const { const UEdGraphSchema_K2* K2Schema = GetDefault(); UEdGraphPin* Pin = FindPinChecked(K2Schema->PN_Then); check(Pin->Direction == EGPD_Output); return Pin; } UEdGraphPin* UK2Node_ConstructObjectFromClass::GetClassPin(const TArray* InPinsToSearch /*= NULL*/) const { const TArray* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins; UEdGraphPin* Pin = NULL; for( auto PinIt = PinsToSearch->CreateConstIterator(); PinIt; ++PinIt ) { UEdGraphPin* TestPin = *PinIt; if( TestPin && TestPin->PinName == FK2Node_ConstructObjectFromClassHelper::ClassPinName ) { Pin = TestPin; break; } } check(Pin == NULL || Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UK2Node_ConstructObjectFromClass::GetWorldContextPin() const { UEdGraphPin* Pin = FindPin(FK2Node_ConstructObjectFromClassHelper::WorldContextPinName); check(Pin == NULL || Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UK2Node_ConstructObjectFromClass::GetResultPin() const { const UEdGraphSchema_K2* K2Schema = GetDefault(); UEdGraphPin* Pin = FindPinChecked(K2Schema->PN_ReturnValue); check(Pin->Direction == EGPD_Output); return Pin; } FLinearColor UK2Node_ConstructObjectFromClass::GetNodeTitleColor() const { return Super::GetNodeTitleColor(); } FText UK2Node_ConstructObjectFromClass::GetBaseNodeTitle() const { return NSLOCTEXT("K2Node", "ConstructObject_BaseTitle", "Construct Object from Class"); } FText UK2Node_ConstructObjectFromClass::GetNodeTitleFormat() const { return NSLOCTEXT("K2Node", "Construct", "Construct {ClassName}"); } FText UK2Node_ConstructObjectFromClass::GetNodeTitle(ENodeTitleType::Type TitleType) const { if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle) { return GetBaseNodeTitle(); } else if (UEdGraphPin* ClassPin = GetClassPin()) { if (ClassPin->LinkedTo.Num() > 0) { // Blueprint will be determined dynamically, so we don't have the name in this case return NSLOCTEXT("K2Node", "ConstructObject_Title_Unknown", "Construct"); } else if (ClassPin->DefaultObject == nullptr) { return NSLOCTEXT("K2Node", "ConstructObject_Title_NONE", "Construct NONE"); } else if (CachedNodeTitle.IsOutOfDate(this)) { FFormatNamedArguments Args; Args.Add(TEXT("ClassName"), FText::FromString(ClassPin->DefaultObject->GetName())); // FText::Format() is slow, so we cache this to save on performance CachedNodeTitle.SetCachedText(FText::Format(GetNodeTitleFormat(), Args), this); } } else { return NSLOCTEXT("K2Node", "ConstructObject_Title_NONE", "Construct NONE"); } return CachedNodeTitle; } bool UK2Node_ConstructObjectFromClass::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph); return Super::IsCompatibleWithGraph(TargetGraph) && (!Blueprint || FBlueprintEditorUtils::FindUserConstructionScript(Blueprint) != TargetGraph); } void UK2Node_ConstructObjectFromClass::GetNodeAttributes( TArray>& OutNodeAttributes ) const { UClass* ClassToSpawn = GetClassToSpawn(); const FString ClassToSpawnStr = ClassToSpawn ? ClassToSpawn->GetName() : TEXT( "InvalidClass" ); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Type" ), TEXT( "ConstructObjectFromClass" ) )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Class" ), GetClass()->GetName() )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Name" ), GetName() )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "ObjectClass" ), ClassToSpawnStr )); } void UK2Node_ConstructObjectFromClass::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // actions get registered under specific object-keys; the idea is that // actions might have to be updated (or deleted) if their object-key is // mutated (or removed)... here we use the node's class (so if the node // type disappears, then the action should go with it) UClass* ActionKey = GetClass(); // to keep from needlessly instantiating a UBlueprintNodeSpawner, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } FText UK2Node_ConstructObjectFromClass::GetMenuCategory() const { return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Gameplay); } FNodeHandlingFunctor* UK2Node_ConstructObjectFromClass::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const { return new FNodeHandlingFunctor(CompilerContext); } void UK2Node_ConstructObjectFromClass::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); } bool UK2Node_ConstructObjectFromClass::HasExternalDependencies(TArray* OptionalOutput) const { UClass* SourceClass = GetClassToSpawn(); const UBlueprint* SourceBlueprint = GetBlueprint(); const bool bResult = (SourceClass != NULL) && (SourceClass->ClassGeneratedBy != SourceBlueprint); if (bResult && OptionalOutput) { OptionalOutput->AddUnique(SourceClass); } const bool bSuperResult = Super::HasExternalDependencies(OptionalOutput); return bSuperResult || bResult; } #undef LOCTEXT_NAMESPACE