/** * This is essentially a mix of K2Node_BaseAsyncTask::ExpandNode and K2Node_SpawnActorFromClass::ExpandNode. * Several things are going on here: * -Factory call to create proxy object (K2Node_BaseAsyncTask) * -Task return delegates are created and hooked up (K2Node_BaseAsyncTask) * -A BeginSpawn function is called on proxyu object (similiar to K2Node_SpawnActorFromClass) * -BeginSpawn can choose to spawn or not spawn an actor (and return it) * -If spawned: * -SetVars are run on the newly spawned object (set expose on spawn variables - K2Node_SpawnActorFromClass) * -FinishSpawn is called on the proxy object * * * Also, a K2Node_SpawnActorFromClass could not be used directly here, since we want the proxy object to implement its own * BeginSpawn/FinishSpawn function (custom game logic will often be performed in the native implementation). K2Node_SpawnActorFromClass also * requires a SpawnTransform be wired into it, and in most ability task cases, the spawn transform is implied or not necessary. * * */ void UK2Node_LatentAbilityCall::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { bool validatedActorSpawn = ValidateActorSpawning(CompilerContext, false); bool validatedActorArraySpawn = ValidateActorArraySpawning(CompilerContext, false); UEdGraphPin* ClassPin = GetClassPin(); if (ClassPin == nullptr) { // Nothing special about this task, just call super Super::ExpandNode(CompilerContext, SourceGraph); return; } UK2Node::ExpandNode(CompilerContext, SourceGraph); if (!validatedActorSpawn && !validatedActorArraySpawn) { ValidateActorSpawning(CompilerContext, true); ValidateActorArraySpawning(CompilerContext, true); } const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); check(SourceGraph && Schema); bool bIsErrorFree = true; // ------------------------------------------------------------------------------------------ // CREATE A CALL TO FACTORY THE PROXY OBJECT // ------------------------------------------------------------------------------------------ UK2Node_CallFunction* const CallCreateProxyObjectNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph); CallCreateProxyObjectNode->FunctionReference.SetExternalMember(ProxyFactoryFunctionName, ProxyFactoryClass); CallCreateProxyObjectNode->AllocateDefaultPins(); bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(Schema->PN_Execute), *CallCreateProxyObjectNode->FindPinChecked(Schema->PN_Execute)).CanSafeConnect(); for (auto CurrentPin : Pins) { if (FBaseAsyncTaskHelper::ValidDataPin(CurrentPin, EGPD_Input, Schema)) { UEdGraphPin* DestPin = CallCreateProxyObjectNode->FindPin(CurrentPin->PinName); // match function inputs, to pass data to function from CallFunction node // NEW: if no DestPin, assume it is a Class Spawn PRoperty - not an error if (DestPin) { bIsErrorFree &= CompilerContext.CopyPinLinksToIntermediate(*CurrentPin, *DestPin).CanSafeConnect(); } } } // ------------------------------------------------------------------------------------------ // GATHER OUTPUT PARAMETERS AND PAIR THEM WITH LOCAL VARIABLES // ------------------------------------------------------------------------------------------ TArray<FBaseAsyncTaskHelper::FOutputPinAndLocalVariable> VariableOutputs; for (auto CurrentPin : Pins) { if (FBaseAsyncTaskHelper::ValidDataPin(CurrentPin, EGPD_Output, Schema)) { const FEdGraphPinType& PinType = CurrentPin->PinType; UK2Node_TemporaryVariable* TempVarOutput = CompilerContext.SpawnInternalVariable( this, PinType.PinCategory, PinType.PinSubCategory, PinType.PinSubCategoryObject.Get(), PinType.bIsArray); bIsErrorFree &= TempVarOutput->GetVariablePin() && CompilerContext.MovePinLinksToIntermediate(*CurrentPin, *TempVarOutput->GetVariablePin()).CanSafeConnect(); VariableOutputs.Add(FBaseAsyncTaskHelper::FOutputPinAndLocalVariable(CurrentPin, TempVarOutput)); } } // ------------------------------------------------------------------------------------------ // FOR EACH DELEGATE DEFINE EVENT, CONNECT IT TO DELEGATE AND IMPLEMENT A CHAIN OF ASSIGMENTS // ------------------------------------------------------------------------------------------ UEdGraphPin* LastThenPin = CallCreateProxyObjectNode->FindPinChecked(Schema->PN_Then); UEdGraphPin* const ProxyObjectPin = CallCreateProxyObjectNode->GetReturnValuePin(); for (TFieldIterator<UMulticastDelegateProperty> PropertyIt(ProxyClass, EFieldIteratorFlags::ExcludeSuper); PropertyIt && bIsErrorFree; ++PropertyIt) { bIsErrorFree &= FBaseAsyncTaskHelper::HandleDelegateImplementation(*PropertyIt, VariableOutputs, ProxyObjectPin, LastThenPin, this, SourceGraph, CompilerContext); } if (CallCreateProxyObjectNode->FindPinChecked(Schema->PN_Then) == LastThenPin) { CompilerContext.MessageLog.Error(*LOCTEXT("MissingDelegateProperties", "BaseAsyncTask: Proxy has no delegates defined. @@").ToString(), this); return; } // ------------------------------------------------------------------------------------------ // NEW: CREATE A CALL TO THE PRESPAWN FUNCTION, IF IT RETURNS TRUE, THEN WE WILL SPAWN THE NEW ACTOR // ------------------------------------------------------------------------------------------ FName ProxyPrespawnFunctionName = validatedActorArraySpawn ? *FK2Node_LatentAbilityCallHelper::BeginSpawnArrayFuncName : *FK2Node_LatentAbilityCallHelper::BeginSpawnFuncName; UFunction* PreSpawnFunction = ProxyFactoryClass->FindFunctionByName(ProxyPrespawnFunctionName); FName ProxyPostpawnFunctionName = validatedActorArraySpawn ? *FK2Node_LatentAbilityCallHelper::FinishSpawnArrayFuncName : *FK2Node_LatentAbilityCallHelper::FinishSpawnFuncName; UFunction* PostSpawnFunction = ProxyFactoryClass->FindFunctionByName(ProxyPostpawnFunctionName); if (PreSpawnFunction == nullptr) { if (validatedActorArraySpawn) { CompilerContext.MessageLog.Error(*LOCTEXT("MissingBeginSpawningActorArrayFunction", "AbilityTask: Proxy is missing BeginSpawningActorArray native function. @@").ToString(), this); } else { CompilerContext.MessageLog.Error(*LOCTEXT("MissingBeginSpawningActorFunction", "AbilityTask: Proxy is missing BeginSpawningActor native function. @@").ToString(), this); } return; } if (PostSpawnFunction == nullptr) { if (validatedActorArraySpawn) { CompilerContext.MessageLog.Error(*LOCTEXT("MissingFinishSpawningActorArrayFunction", "AbilityTask: Proxy is missing FinishSpawningActorArray native function. @@").ToString(), this); } else { CompilerContext.MessageLog.Error(*LOCTEXT("MissingFinishSpawningActorFunction", "AbilityTask: Proxy is missing FinishSpawningActor native function. @@").ToString(), this); } return; } UK2Node_CallFunction* const CallPrespawnProxyObjectNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph); CallPrespawnProxyObjectNode->FunctionReference.SetExternalMember(ProxyPrespawnFunctionName, ProxyClass); CallPrespawnProxyObjectNode->AllocateDefaultPins(); // Hook up the self connection UEdGraphPin* PrespawnCallSelfPin = Schema->FindSelfPin(*CallPrespawnProxyObjectNode, EGPD_Input); check(PrespawnCallSelfPin); bIsErrorFree &= Schema->TryCreateConnection(ProxyObjectPin, PrespawnCallSelfPin); // Hook up input parameters to PreSpawn for (auto CurrentPin : Pins) { if (FBaseAsyncTaskHelper::ValidDataPin(CurrentPin, EGPD_Input, Schema)) { UEdGraphPin* DestPin = CallPrespawnProxyObjectNode->FindPin(CurrentPin->PinName); if (DestPin) { bIsErrorFree &= CompilerContext.CopyPinLinksToIntermediate(*CurrentPin, *DestPin).CanSafeConnect(); } } } // Hook the activate node up in the exec chain UEdGraphPin* PrespawnExecPin = CallPrespawnProxyObjectNode->FindPinChecked(Schema->PN_Execute); UEdGraphPin* PrespawnThenPin = CallPrespawnProxyObjectNode->FindPinChecked(Schema->PN_Then); UEdGraphPin* PrespawnReturnPin = CallPrespawnProxyObjectNode->FindPinChecked(Schema->PN_ReturnValue); UEdGraphPin* SpawnedActorReturnPin = CallPrespawnProxyObjectNode->FindPinChecked(FK2Node_LatentAbilityCallHelper::SpawnedActorPinName); bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, PrespawnExecPin); LastThenPin = PrespawnThenPin; // ------------------------------------------- // Branch based on return value of Prespawn // ------------------------------------------- UK2Node_IfThenElse* BranchNode = SourceGraph->CreateBlankNode<UK2Node_IfThenElse>(); BranchNode->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(BranchNode, this); // Link return value of prespawn with the branch condtional bIsErrorFree &= Schema->TryCreateConnection(PrespawnReturnPin, BranchNode->GetConditionPin()); // Link our Prespawn call to the branch node bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, BranchNode->GetExecPin()); UEdGraphPin* BranchElsePin = BranchNode->GetElsePin(); LastThenPin = BranchNode->GetThenPin(); UClass* ClassToSpawn = GetClassToSpawn(); if (validatedActorArraySpawn && ClassToSpawn) { //Branch for main loop control UK2Node_IfThenElse* Branch = CompilerContext.SpawnIntermediateNode<UK2Node_IfThenElse>(this, SourceGraph); Branch->AllocateDefaultPins(); //Create int Iterator UK2Node_TemporaryVariable* IteratorVar = CompilerContext.SpawnIntermediateNode<UK2Node_TemporaryVariable>(this, SourceGraph); IteratorVar->VariableType.PinCategory = Schema->PC_Int; IteratorVar->AllocateDefaultPins(); //Iterator assignment (initialization to zero) UK2Node_AssignmentStatement* IteratorInitialize = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(this, SourceGraph); IteratorInitialize->AllocateDefaultPins(); IteratorInitialize->GetValuePin()->DefaultValue = TEXT("0"); //Iterator assignment (incrementing) UK2Node_AssignmentStatement* IteratorAssign = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(this, SourceGraph); IteratorAssign->AllocateDefaultPins(); //Increment iterator command UK2Node_CallFunction* Increment = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph); Increment->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(TEXT("Add_IntInt"))); Increment->AllocateDefaultPins(); Increment->FindPinChecked(TEXT("B"))->DefaultValue = TEXT("1"); //Array length UK2Node_CallArrayFunction* ArrayLength = CompilerContext.SpawnIntermediateNode<UK2Node_CallArrayFunction>(this, SourceGraph); ArrayLength->SetFromFunction(UKismetArrayLibrary::StaticClass()->FindFunctionByName(TEXT("Array_Length"))); ArrayLength->AllocateDefaultPins(); //Array element retrieval UK2Node_CallArrayFunction* GetElement = CompilerContext.SpawnIntermediateNode<UK2Node_CallArrayFunction>(this, SourceGraph); GetElement->SetFromFunction(UKismetArrayLibrary::StaticClass()->FindFunctionByName(TEXT("Array_Get"))); GetElement->AllocateDefaultPins(); //Check node for iterator versus array length UK2Node_CallFunction* Condition = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph); Condition->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(TEXT("Less_IntInt"))); Condition->AllocateDefaultPins(); //Connections to set up the loop UEdGraphSchema_K2 const* const K2Schema = Cast<const UEdGraphSchema_K2>(Schema); bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, IteratorInitialize->GetExecPin()); bIsErrorFree &= Schema->TryCreateConnection(IteratorVar->GetVariablePin(), IteratorInitialize->GetVariablePin()); bIsErrorFree &= Schema->TryCreateConnection(IteratorInitialize->GetThenPin(), Branch->GetExecPin()); bIsErrorFree &= Schema->TryCreateConnection(SpawnedActorReturnPin, ArrayLength->GetTargetArrayPin()); bIsErrorFree &= Schema->TryCreateConnection(Condition->GetReturnValuePin(), Branch->GetConditionPin()); bIsErrorFree &= Schema->TryCreateConnection(IteratorVar->GetVariablePin(), Condition->FindPinChecked(TEXT("A"))); bIsErrorFree &= Schema->TryCreateConnection(ArrayLength->FindPin(K2Schema->PN_ReturnValue), Condition->FindPinChecked(TEXT("B"))); //Connections to establish loop iteration bIsErrorFree &= Schema->TryCreateConnection(IteratorVar->GetVariablePin(), Increment->FindPinChecked(TEXT("A"))); bIsErrorFree &= Schema->TryCreateConnection(IteratorVar->GetVariablePin(), IteratorAssign->GetVariablePin()); bIsErrorFree &= Schema->TryCreateConnection(Increment->GetReturnValuePin(), IteratorAssign->GetValuePin()); bIsErrorFree &= Schema->TryCreateConnection(IteratorAssign->GetThenPin(), Branch->GetExecPin()); //This is the inner loop LastThenPin = Branch->GetThenPin(); //Connect the loop branch to the spawn-assignment code block bIsErrorFree &= Schema->TryCreateConnection(SpawnedActorReturnPin, GetElement->GetTargetArrayPin()); bIsErrorFree &= Schema->TryCreateConnection(IteratorVar->GetVariablePin(), GetElement->FindPinChecked(K2Schema->PN_Index)); bIsErrorFree &= ConnectSpawnProperties(ClassToSpawn, Schema, CompilerContext, SourceGraph, LastThenPin, GetElement->FindPinChecked(K2Schema->PN_Item)); //Last argument is the array element bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, IteratorAssign->GetExecPin()); //Connect the spawn-assignment code block to the iterator increment //Finish by providing the proper path out LastThenPin = Branch->GetElsePin(); } // ------------------------------------------- // Set spawn variables // Borrowed heavily from FKismetCompilerUtilities::GenerateAssignmentNodes // ------------------------------------------- if (validatedActorSpawn && ClassToSpawn) { bIsErrorFree &= ConnectSpawnProperties(ClassToSpawn, Schema, CompilerContext, SourceGraph, LastThenPin, SpawnedActorReturnPin); } // ------------------------------------------- // Call FinishSpawning // ------------------------------------------- UK2Node_CallFunction* const CallPostSpawnnProxyObjectNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph); CallPostSpawnnProxyObjectNode->FunctionReference.SetExternalMember(ProxyPostpawnFunctionName, ProxyClass); CallPostSpawnnProxyObjectNode->AllocateDefaultPins(); // Hook up the self connection UEdGraphPin* PostspawnCallSelfPin = Schema->FindSelfPin(*CallPostSpawnnProxyObjectNode, EGPD_Input); check(PostspawnCallSelfPin); bIsErrorFree &= Schema->TryCreateConnection(ProxyObjectPin, PostspawnCallSelfPin); // Link our Postspawn call in bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, CallPostSpawnnProxyObjectNode->FindPinChecked(Schema->PN_Execute)); // Hook up any other input parameters to PostSpawn for (auto CurrentPin : Pins) { if (FBaseAsyncTaskHelper::ValidDataPin(CurrentPin, EGPD_Input, Schema)) { UEdGraphPin* DestPin = CallPostSpawnnProxyObjectNode->FindPin(CurrentPin->PinName); if (DestPin) { bIsErrorFree &= CompilerContext.CopyPinLinksToIntermediate(*CurrentPin, *DestPin).CanSafeConnect(); } } } UEdGraphPin* InSpawnedActorPin = CallPostSpawnnProxyObjectNode->FindPin(TEXT("SpawnedActor")); if (InSpawnedActorPin == nullptr) { CompilerContext.MessageLog.Error(*LOCTEXT("MissingSpawnedActorInputPin", "AbilityTask: Proxy is missing SpawnedActor input pin in FinishSpawningActor. @@").ToString(), this); return; } bIsErrorFree &= Schema->TryCreateConnection(SpawnedActorReturnPin, InSpawnedActorPin); LastThenPin = CallPostSpawnnProxyObjectNode->FindPinChecked(Schema->PN_Then); // Move the connections from the original node then pin to the last internal then pin bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(Schema->PN_Then), *LastThenPin).CanSafeConnect(); bIsErrorFree &= CompilerContext.CopyPinLinksToIntermediate(*LastThenPin, *BranchElsePin).CanSafeConnect(); if (!bIsErrorFree) { CompilerContext.MessageLog.Error(*LOCTEXT("InternalConnectionError", "BaseAsyncTask: Internal connection error. @@").ToString(), this); } // Make sure we caught everything BreakAllNodeLinks(); }
bool BuildLoop(UK2Node* Node, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); check(Node && SourceGraph && Schema); bool bResult = true; // Create int Loop Counter UK2Node_TemporaryVariable* LoopCounterNode = CompilerContext.SpawnIntermediateNode<UK2Node_TemporaryVariable>(Node, SourceGraph); LoopCounterNode->VariableType.PinCategory = Schema->PC_Int; LoopCounterNode->AllocateDefaultPins(); LoopCounterOutPin = LoopCounterNode->GetVariablePin(); check(LoopCounterOutPin); // Initialize loop counter UK2Node_AssignmentStatement* LoopCounterInitialize = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(Node, SourceGraph); LoopCounterInitialize->AllocateDefaultPins(); LoopCounterInitialize->GetValuePin()->DefaultValue = TEXT("0"); bResult &= Schema->TryCreateConnection(LoopCounterOutPin, LoopCounterInitialize->GetVariablePin()); StartLoopExecInPin = LoopCounterInitialize->GetExecPin(); check(StartLoopExecInPin); // Create int Array Index UK2Node_TemporaryVariable* ArrayIndexNode = CompilerContext.SpawnIntermediateNode<UK2Node_TemporaryVariable>(Node, SourceGraph); ArrayIndexNode->VariableType.PinCategory = Schema->PC_Int; ArrayIndexNode->AllocateDefaultPins(); ArrayIndexOutPin = ArrayIndexNode->GetVariablePin(); check(ArrayIndexOutPin); // Initialize array index UK2Node_AssignmentStatement* ArrayIndexInitialize = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(Node, SourceGraph); ArrayIndexInitialize->AllocateDefaultPins(); ArrayIndexInitialize->GetValuePin()->DefaultValue = TEXT("0"); bResult &= Schema->TryCreateConnection(ArrayIndexOutPin, ArrayIndexInitialize->GetVariablePin()); bResult &= Schema->TryCreateConnection(LoopCounterInitialize->GetThenPin(), ArrayIndexInitialize->GetExecPin()); // Do loop branch UK2Node_IfThenElse* Branch = CompilerContext.SpawnIntermediateNode<UK2Node_IfThenElse>(Node, SourceGraph); Branch->AllocateDefaultPins(); bResult &= Schema->TryCreateConnection(ArrayIndexInitialize->GetThenPin(), Branch->GetExecPin()); LoopCompleteOutExecPin = Branch->GetElsePin(); check(LoopCompleteOutExecPin); // Do loop condition UK2Node_CallFunction* Condition = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(Node, SourceGraph); Condition->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(TEXT("Less_IntInt"))); Condition->AllocateDefaultPins(); bResult &= Schema->TryCreateConnection(Condition->GetReturnValuePin(), Branch->GetConditionPin()); bResult &= Schema->TryCreateConnection(Condition->FindPinChecked(TEXT("A")), LoopCounterOutPin); LoopCounterLimitInPin = Condition->FindPinChecked(TEXT("B")); check(LoopCounterLimitInPin); // Array Index assigned UK2Node_AssignmentStatement* ArrayIndexAssign = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(Node, SourceGraph); ArrayIndexAssign->AllocateDefaultPins(); bResult &= Schema->TryCreateConnection(Branch->GetThenPin(), ArrayIndexAssign->GetExecPin()); bResult &= Schema->TryCreateConnection(ArrayIndexAssign->GetVariablePin(), ArrayIndexOutPin); bResult &= Schema->TryCreateConnection(ArrayIndexAssign->GetValuePin(), LoopCounterOutPin); // body sequence UK2Node_ExecutionSequence* Sequence = CompilerContext.SpawnIntermediateNode<UK2Node_ExecutionSequence>(Node, SourceGraph); Sequence->AllocateDefaultPins(); bResult &= Schema->TryCreateConnection(ArrayIndexAssign->GetThenPin(), Sequence->GetExecPin()); InsideLoopExecOutPin = Sequence->GetThenPinGivenIndex(0); check(InsideLoopExecOutPin); // Loop Counter increment UK2Node_CallFunction* Increment = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(Node, SourceGraph); Increment->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(TEXT("Add_IntInt"))); Increment->AllocateDefaultPins(); bResult &= Schema->TryCreateConnection(Increment->FindPinChecked(TEXT("A")), LoopCounterOutPin); Increment->FindPinChecked(TEXT("B"))->DefaultValue = TEXT("1"); // Loop Counter assigned UK2Node_AssignmentStatement* LoopCounterAssign = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(Node, SourceGraph); LoopCounterAssign->AllocateDefaultPins(); bResult &= Schema->TryCreateConnection(LoopCounterAssign->GetExecPin(), Sequence->GetThenPinGivenIndex(1)); bResult &= Schema->TryCreateConnection(LoopCounterAssign->GetVariablePin(), LoopCounterOutPin); bResult &= Schema->TryCreateConnection(LoopCounterAssign->GetValuePin(), Increment->GetReturnValuePin()); bResult &= Schema->TryCreateConnection(LoopCounterAssign->GetThenPin(), Branch->GetExecPin()); return bResult; }