#include "CustomBlueprintsEditor.h" #include "SpawnActorBaseNode.h" #include "Globals.h" #include "BlueprintEditorUtils.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" struct FK2Node_SpawnActorFromClassHelper { static FString WorldContextPinName; static FString ClassPinName; static FString SpawnTransformPinName; static FString TargetPinName; }; FString FK2Node_SpawnActorFromClassHelper::WorldContextPinName(TEXT("WorldContextObject")); FString FK2Node_SpawnActorFromClassHelper::ClassPinName(TEXT("Class")); FString FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName(TEXT("SpawnTransform")); FString FK2Node_SpawnActorFromClassHelper::TargetPinName(TEXT("Target")); #define FMT_TEXT(__x) FText::FromString(FString() + __x) UK2Node_SpawnActorBaseNode::UK2Node_SpawnActorBaseNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { NodeTooltip = FMT_TEXT("Oh Nooooos"); } void UK2Node_SpawnActorBaseNode::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_SpawnActorFromClassHelper::WorldContextPinName); } // Add blueprint pin UEdGraphPin* ClassPin = CreatePin(EGPD_Input, K2Schema->PC_Class, TEXT(""), GetActorClass(), false, false, FK2Node_SpawnActorFromClassHelper::ClassPinName); K2Schema->ConstructBasicPinTooltip(*ClassPin, FMT_TEXT("The " + GetObjectName() + " you want to spawn"), ClassPin->PinToolTip); // Transform pin if(SpawningFunctionHasTransform()) { UScriptStruct* TransformStruct = TBaseStructure::Get(); UEdGraphPin* TransformPin = CreatePin(EGPD_Input, K2Schema->PC_Struct, TEXT(""), TransformStruct, false, false, FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName); K2Schema->ConstructBasicPinTooltip(*TransformPin, FMT_TEXT("The transform to spawn the " + GetObjectName() + " with"), TransformPin->PinToolTip); } // Result pin UEdGraphPin* ResultPin = CreatePin(EGPD_Output, K2Schema->PC_Object, TEXT(""), GetActorClass(), false, false, K2Schema->PN_ReturnValue); K2Schema->ConstructBasicPinTooltip(*ResultPin, FMT_TEXT("The spawned " + GetObjectName()), ResultPin->PinToolTip); RegisterAdditionalPins(K2Schema); Super::AllocateDefaultPins(); } void UK2Node_SpawnActorBaseNode::CreatePinsForClass(UClass* InClass, TArray& OutClassPins) { 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, Pin->PinType); OutClassPins.Add(Pin); 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_SpawnActorBaseNode::GetClassToSpawn(const TArray* InPinsToSearch) const { UClass* UseSpawnClass = nullptr; const TArray* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins; UEdGraphPin* ClassPin = GetClassPin(PinsToSearch); if(ClassPin && ClassPin->DefaultObject != NULL && ClassPin->LinkedTo.Num() == 0) { UseSpawnClass = CastChecked(ClassPin->DefaultObject); } else if(ClassPin && (1 == ClassPin->LinkedTo.Num())) { auto SourcePin = ClassPin->LinkedTo[0]; UseSpawnClass = SourcePin ? Cast(SourcePin->PinType.PinSubCategoryObject.Get()) : nullptr; } return UseSpawnClass; } void UK2Node_SpawnActorBaseNode::ReallocatePinsDuringReconstruction(TArray& OldPins) { AllocateDefaultPins(); UClass* UseSpawnClass = GetClassToSpawn(&OldPins); if(UseSpawnClass != NULL) { // Reassign class pin UEdGraphPin* newClassPin = FindPinChecked(FK2Node_SpawnActorFromClassHelper::ClassPinName); newClassPin->DefaultObject = UseSpawnClass; TArray ClassPins; CreatePinsForClass(UseSpawnClass, ClassPins); } } void UK2Node_SpawnActorBaseNode::PostPlacedNewNode() { Super::PostPlacedNewNode(); UClass* UseSpawnClass = GetClassToSpawn(); if(UseSpawnClass != NULL) { TArray ClassPins; CreatePinsForClass(UseSpawnClass, ClassPins); } } bool UK2Node_SpawnActorBaseNode::IsSpawnVarPin(UEdGraphPin* Pin) { const UEdGraphSchema_K2* K2Schema = GetDefault(); UEdGraphPin* ParentPin = Pin->ParentPin; while(ParentPin) { if(ParentPin->PinName == FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName) { return false; } ParentPin = ParentPin->ParentPin; } return(Pin->PinName != K2Schema->PN_Execute && Pin->PinName != K2Schema->PN_Then && Pin->PinName != K2Schema->PN_ReturnValue && Pin->PinName != FK2Node_SpawnActorFromClassHelper::ClassPinName && Pin->PinName != FK2Node_SpawnActorFromClassHelper::WorldContextPinName && Pin->PinName != FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName && Pin->PinName != FK2Node_SpawnActorFromClassHelper::TargetPinName && !IsAdditionalPin(Pin)); } void UK2Node_SpawnActorBaseNode::OnClassPinChanged() { const UEdGraphSchema_K2* K2Schema = GetDefault(); // Remove all pins related to archetype variables TArray OldPins = Pins; TArray OldClassPins; for(int32 i = 0; i < OldPins.Num(); i++) { UEdGraphPin* OldPin = OldPins[i]; if(IsSpawnVarPin(OldPin)) { Pins.Remove(OldPin); OldClassPins.Add(OldPin); } } m_cachedNodeTitle.MarkDirty(); UClass* UseSpawnClass = GetClassToSpawn(); TArray NewClassPins; if(UseSpawnClass != NULL) { CreatePinsForClass(UseSpawnClass, NewClassPins); } UEdGraphPin* ResultPin = GetResultPin(); // Cache all the pin connections to the ResultPin, we will attempt to recreate them TArray ResultPinConnectionList = ResultPin->LinkedTo; // Because the archetype has changed, we break the output link as the output pin type will change ResultPin->BreakAllPinLinks(); // Recreate any pin links to the Result pin that are still valid for(UEdGraphPin* Connections : ResultPinConnectionList) { K2Schema->TryCreateConnection(ResultPin, Connections); } K2Schema->ConstructBasicPinTooltip(*ResultPin, FMT_TEXT("The spawned " + GetObjectName()), ResultPin->PinToolTip); // Rewire the old pins to the new pins so connections are maintained if possible RewireOldPinsToNewPins(OldClassPins, NewClassPins); // Destroy the old pins DestroyPinList(OldClassPins); // Refresh the UI for the graph so the pin changes show up UEdGraph* Graph = GetGraph(); Graph->NotifyGraphChanged(); // Mark dirty FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint()); } void UK2Node_SpawnActorBaseNode::PinConnectionListChanged(UEdGraphPin* ChangedPin) { if(ChangedPin && (ChangedPin->PinName == FK2Node_SpawnActorFromClassHelper::ClassPinName)) { OnClassPinChanged(); } } void UK2Node_SpawnActorBaseNode::PinDefaultValueChanged(UEdGraphPin* ChangedPin) { if(ChangedPin && (ChangedPin->PinName == FK2Node_SpawnActorFromClassHelper::ClassPinName)) { OnClassPinChanged(); } } FText UK2Node_SpawnActorBaseNode::GetTooltipText() const { return NodeTooltip; } UEdGraphPin* UK2Node_SpawnActorBaseNode::GetThenPin()const { const UEdGraphSchema_K2* K2Schema = GetDefault(); UEdGraphPin* Pin = FindPinChecked(K2Schema->PN_Then); check(Pin->Direction == EGPD_Output); return Pin; } UEdGraphPin* UK2Node_SpawnActorBaseNode::GetClassPin(const TArray* InPinsToSearch) 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_SpawnActorFromClassHelper::ClassPinName) { Pin = TestPin; break; } } check(Pin == NULL || Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UK2Node_SpawnActorBaseNode::GetSpawnTransformPin() const { UEdGraphPin* Pin = FindPinChecked(FK2Node_SpawnActorFromClassHelper::SpawnTransformPinName); check(Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UK2Node_SpawnActorBaseNode::GetWorldContextPin() const { UEdGraphPin* Pin = FindPin(FK2Node_SpawnActorFromClassHelper::WorldContextPinName); check(Pin == NULL || Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UK2Node_SpawnActorBaseNode::GetResultPin() const { const UEdGraphSchema_K2* K2Schema = GetDefault(); UEdGraphPin* Pin = FindPinChecked(K2Schema->PN_ReturnValue); check(Pin->Direction == EGPD_Output); return Pin; } FLinearColor UK2Node_SpawnActorBaseNode::GetNodeTitleColor() const { return Super::GetNodeTitleColor(); } FText UK2Node_SpawnActorBaseNode::GetNodeTitle(ENodeTitleType::Type TitleType) const { FText NodeTitle = FMT_TEXT("Spawn " + GetObjectName() + " from Class"); if(TitleType != ENodeTitleType::MenuTitle) { if(UEdGraphPin* ClassPin = FindPin(FK2Node_SpawnActorFromClassHelper::ClassPinName)) { if(ClassPin->LinkedTo.Num() > 0) { // Blueprint will be determined dynamically, so we don't have the name in this case NodeTitle = FMT_TEXT("Spawn " + GetObjectName()); } else if(ClassPin->DefaultObject == nullptr) { NodeTitle = FMT_TEXT("Spawn " + GetObjectName() + " NONE"); } else { if(m_cachedNodeTitle.IsOutOfDate(this)) { FText ClassName; if(UClass* PickedClass = Cast(ClassPin->DefaultObject)) { ClassName = PickedClass->GetDisplayNameText(); } FFormatNamedArguments Args; Args.Add(TEXT("ClassName"), ClassName); // FText::Format() is slow, so we cache this to save on performance m_cachedNodeTitle.SetCachedText(FText::Format(FMT_TEXT("Spawn {ClassName}"), Args), this); } NodeTitle = m_cachedNodeTitle; } } else { NodeTitle = FMT_TEXT(GetObjectName() + " NONE"); } } return NodeTitle; } bool UK2Node_SpawnActorBaseNode::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph); return Super::IsCompatibleWithGraph(TargetGraph) && (!Blueprint || FBlueprintEditorUtils::FindUserConstructionScript(Blueprint) != TargetGraph); } void UK2Node_SpawnActorBaseNode::GetNodeAttributes(TArray>& OutNodeAttributes) const { UClass* ClassToSpawn = GetClassToSpawn(); const FString ClassToSpawnStr = ClassToSpawn ? ClassToSpawn->GetName() : TEXT("InvalidClass"); OutNodeAttributes.Add(TKeyValuePair(TEXT("Type"), FString() + "Spawn" + GetObjectName() + "FromClass")); OutNodeAttributes.Add(TKeyValuePair(TEXT("Class"), GetClass()->GetName())); OutNodeAttributes.Add(TKeyValuePair(TEXT("Name"), GetName())); OutNodeAttributes.Add(TKeyValuePair(TEXT("ActorClass"), ClassToSpawnStr)); } void UK2Node_SpawnActorBaseNode::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_SpawnActorBaseNode::GetMenuCategory() const { return FMT_TEXT("Ability"); } FNodeHandlingFunctor* UK2Node_SpawnActorBaseNode::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const { return new FNodeHandlingFunctor(CompilerContext); } void UK2Node_SpawnActorBaseNode::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); if(!libClass) { LoadDependencies(); if(!libClass) { CompilerContext.MessageLog.Error(L"Dependencies not initialized yet"); return; } } FName BeginSpawningBlueprintFuncName = GetBeginSpawningFunction(); static FString ActorClassParamName = FString(TEXT("ActorClass")); static FString WorldContextParamName = FString(TEXT("WorldContextObject")); FName FinishSpawningFuncName = GetFinishSpawningFunction(); static FString ActorParamName = FString(TEXT("Actor")); static FString TransformParamName = FString(TEXT("SpawnTransform")); static FString CollisionHandlingOverrideParamName = FString(TEXT("CollisionHandlingOverride")); static FString OwnerParamName = FString(TEXT("Owner")); static FString ObjectParamName = FString(TEXT("Object")); static FString ValueParamName = FString(TEXT("Value")); static FString PropertyNameParamName = FString(TEXT("PropertyName")); UK2Node_SpawnActorBaseNode* SpawnNode = this; UEdGraphPin* SpawnNodeExec = SpawnNode->GetExecPin(); UEdGraphPin* SpawnWorldContextPin = SpawnNode->GetWorldContextPin(); UEdGraphPin* SpawnClassPin = SpawnNode->GetClassPin(); UEdGraphPin* SpawnNodeThen = SpawnNode->GetThenPin(); UEdGraphPin* SpawnNodeResult = SpawnNode->GetResultPin(); UClass* SpawnClass = (SpawnClassPin != NULL) ? Cast(SpawnClassPin->DefaultObject) : NULL; if((0 == SpawnClassPin->LinkedTo.Num()) && (NULL == SpawnClass)) { CompilerContext.MessageLog.Error(L"Spawn node @@ must have a class specified.", SpawnNode); // we break exec links so this is the only error we get, don't want the SpawnActor node being considered and giving 'unexpected node' type warnings SpawnNode->BreakAllNodeLinks(); return; } ////////////////////////////////////////////////////////////////////////// // create 'begin spawn' call node UK2Node_CallFunction* CallBeginSpawnNode = CompilerContext.SpawnIntermediateNode(SpawnNode, SourceGraph); CallBeginSpawnNode->FunctionReference.SetExternalMember(BeginSpawningBlueprintFuncName, libClass); CallBeginSpawnNode->AllocateDefaultPins(); UEdGraphPin* CallBeginExec = CallBeginSpawnNode->GetExecPin(); UEdGraphPin* CallBeginWorldContextPin = CallBeginSpawnNode->FindPin(WorldContextParamName); if(!CallBeginWorldContextPin) { CompilerContext.MessageLog.Error(L"World Context Pin not found"); return; } UEdGraphPin* CallBeginActorClassPin = CallBeginSpawnNode->FindPinChecked(ActorClassParamName); UEdGraphPin* CallBeginTransform = nullptr; UEdGraphPin* CallBeginResult = CallBeginSpawnNode->GetReturnValuePin(); // Move 'exec' connection from spawn node to 'begin spawn' CompilerContext.MovePinLinksToIntermediate(*SpawnNodeExec, *CallBeginExec); if(SpawnClassPin->LinkedTo.Num() > 0) { // Copy the 'blueprint' connection from the spawn node to 'begin spawn' CompilerContext.MovePinLinksToIntermediate(*SpawnClassPin, *CallBeginActorClassPin); } else { // Copy blueprint literal onto begin spawn call CallBeginActorClassPin->DefaultObject = SpawnClass; } // Copy the world context connection from the spawn node to 'begin spawn' if necessary if(SpawnWorldContextPin) { CompilerContext.MovePinLinksToIntermediate(*SpawnWorldContextPin, *CallBeginWorldContextPin); } // Copy the 'transform' connection from the spawn node to 'begin spawn' if(SpawningFunctionHasTransform()) { UEdGraphPin* SpawnNodeTransform = SpawnNode->GetSpawnTransformPin(); CallBeginTransform = CallBeginSpawnNode->FindPinChecked(TransformParamName); CompilerContext.MovePinLinksToIntermediate(*SpawnNodeTransform, *CallBeginTransform); } ConnectAdditionalPins(CompilerContext, nullptr, CallBeginSpawnNode); ////////////////////////////////////////////////////////////////////////// // create 'finish spawn' call node UK2Node_CallFunction* CallFinishSpawnNode = CompilerContext.SpawnIntermediateNode(SpawnNode, SourceGraph); CallFinishSpawnNode->FunctionReference.SetExternalMember(FinishSpawningFuncName, libClass); CallFinishSpawnNode->AllocateDefaultPins(); UEdGraphPin* CallFinishExec = CallFinishSpawnNode->GetExecPin(); UEdGraphPin* CallFinishThen = CallFinishSpawnNode->GetThenPin(); UEdGraphPin* CallFinishActor = CallFinishSpawnNode->FindPinChecked(ActorParamName); UEdGraphPin* CallFinishResult = CallFinishSpawnNode->GetReturnValuePin(); // Move 'then' connection from spawn node to 'finish spawn' CompilerContext.MovePinLinksToIntermediate(*SpawnNodeThen, *CallFinishThen); if(SpawningFunctionHasTransform()) { // Copy transform connection UEdGraphPin* CallFinishTransform = CallFinishSpawnNode->FindPinChecked(TransformParamName); CompilerContext.CopyPinLinksToIntermediate(*CallBeginTransform, *CallFinishTransform); } ConnectAdditionalPins(CompilerContext, nullptr, CallFinishSpawnNode); // Connect output actor from 'begin' to 'finish' CallBeginResult->MakeLinkTo(CallFinishActor); // Move result connection from spawn node to 'finish spawn' CallFinishResult->PinType = SpawnNodeResult->PinType; // Copy type so it uses the right actor subclass CompilerContext.MovePinLinksToIntermediate(*SpawnNodeResult, *CallFinishResult); ////////////////////////////////////////////////////////////////////////// // create 'set var' nodes // Get 'result' pin from 'begin spawn', this is the actual actor we want to set properties on UEdGraphPin* LastThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallBeginSpawnNode, SpawnNode, CallBeginResult, GetClassToSpawn()); // Make exec connection between 'then' on last node and 'finish' LastThen->MakeLinkTo(CallFinishExec); // Break any links to the expanded node SpawnNode->BreakAllNodeLinks(); } bool UK2Node_SpawnActorBaseNode::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; } UClass* USpawnGroupNode::GetActorClass() const { return abilityGroupClass; } UClass* USpawnTriggerNode::GetActorClass() const { return abilityTriggerClass; } UClass* USpawnModifierNode::GetActorClass() const { return modifierClass; } void USpawnModifierNode::RegisterAdditionalPins(const class UEdGraphSchema_K2* K2Schema) { UEdGraphPin* TargetPin = CreatePin(EGPD_Input, K2Schema->PC_Object, TEXT(""), characterClass, false, false, "Target"); } bool USpawnModifierNode::IsAdditionalPin(class UEdGraphPin* pin) { return pin->PinName == "Target"; } void USpawnModifierNode::ConnectAdditionalPins(class FKismetCompilerContext& CompilerContext, UK2Node* last, UK2Node* dst) { UEdGraphPin* dstPin = dst->FindPin("Target"); UEdGraphPin* srcPin = FindPin("Target"); if(dstPin && srcPin) CompilerContext.CopyPinLinksToIntermediate(*srcPin, *dstPin); } UClass* USpawnProjectileNode::GetActorClass() const { return projectileClass; }