Files
integration/Plugins/UEWingman/Source/UEWingman/Private/WingVariables.cpp
2026-04-09 04:22:28 -04:00

842 lines
24 KiB
C++

#include "WingVariables.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "WingProperty.h"
#include "WingTokenizer.h"
#include "EdGraphSchema_K2.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_FunctionResult.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_EditablePinBase.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
// Flag names used for blueprint variables.
static const FName Flag_InstanceEditable(TEXT("InstanceEditable"));
static const FName Flag_BlueprintReadOnly(TEXT("BlueprintReadOnly"));
static const FName Flag_ExposeOnSpawn(TEXT("ExposeOnSpawn"));
static const FName Flag_Private(TEXT("Private"));
static const FName Flag_ExposeToCinematics(TEXT("ExposeToCinematics"));
static TSet<FName> Flags_None = { };
static TSet<FName> Flags_BlueprintVariables = { Flag_InstanceEditable, Flag_BlueprintReadOnly, Flag_ExposeOnSpawn, Flag_Private, Flag_ExposeToCinematics };
void WingVariableList::Print(WingOut Out)
{
if (Variables.IsEmpty()) return;
Out.Print(ListName);
Out.PrintChar(':');
Out.PrintChar('\n');
for (const Var& V : Variables)
{
FString TypeStr = UWingTypes::TypeToText(V.Type);
FString NameStr = WingUtils::ExternalizeID(V.Name);
// Build flags string.
FString FlagsStr;
if (V.Flags.Num() > 0)
{
TArray<FName> Sorted = V.Flags.Array();
Sorted.Sort(FNameLexicalLess());
for (const FName& Flag : Sorted)
{
if (!FlagsStr.IsEmpty()) FlagsStr += TEXT(", ");
FlagsStr += Flag.ToString();
}
}
// Print: type name (flags) = defaultvalue
Out.Printf(TEXT(" %s %s"), *TypeStr, *NameStr);
if (!FlagsStr.IsEmpty())
Out.Printf(TEXT(" (%s)"), *FlagsStr);
if (!V.DefaultValue.IsEmpty())
Out.Printf(TEXT(" = %s"), *V.DefaultValue);
Out.Print(TEXT("\n"));
}
}
void WingVariableList::PrintCompact(WingOut Out)
{
bool First = true;
for (const Var& V : Variables)
{
if (!First) Out.PrintChar(',');
First = false;
Out.Printf(TEXT("%s %s"), *UWingTypes::TypeToText(V.Type), *WingUtils::ExternalizeID(V.Name));
}
}
void WingVariableList::ClearLinks()
{
for (Var &V : Variables)
{
V.BPVar = nullptr;
V.Pin = nullptr;
}
}
bool WingVariableList::CheckSanity(const TSet<FName> &GoodFlags, bool Allow, WingOut Errors)
{
if ((!Allow) && (!Variables.IsEmpty()))
{
Errors.Printf(TEXT("In this context, %s must be empty."), ListName);
return false;
}
for (const Var &Variable : Variables)
{
FString VarName = WingUtils::ExternalizeID(Variable.Name);
FString TypeText = UWingTypes::TypeToText(Variable.Type);
if (TypeText.IsEmpty())
{
Errors.Printf(TEXT("Type of variable %s is not valid for unknown reasons\n"), *VarName);
return false;
}
if (!UWingTypes::IsBlueprintType(Variable.Type))
{
Errors.Printf(TEXT("Type is not a valid BlueprintType: %s %s\n"), *TypeText, *VarName);
return false;
}
for (FName Flag : Variable.Flags)
{
if (!GoodFlags.Contains(Flag))
{
Errors.Printf(TEXT("Flag %s is not valid here. Valid flags are:"), *Flag.ToString());
for (FName Rel : GoodFlags) Errors.Printf(TEXT(" %s\n"), *Rel.ToString());
return false;
}
}
}
return true;
}
bool WingVariableList::ParseString(const FString &Input, WingOut Errors)
{
Variables.Empty();
TArray<FString> Lines;
Input.ParseIntoArrayLines(Lines);
for (const FString& Line : Lines)
{
WingTokenizer Tok(Line);
if (Tok.NextType() == 0) continue;
Var V;
V.DefaultSpecified = false;
if (!ParseOneVariable(Tok, V, Errors)) return false;
Variables.Add(MoveTemp(V));
}
return true;
}
bool WingVariableList::ParseNamesString(const FString &Input, WingOut Errors)
{
Variables.Empty();
WingTokenizer Tok(Input);
while (Tok.TokenIs(Tok.Identifier))
{
FName Name = Tok.NextName();
Var V;
V.Name = Name;
Variables.Add(V);
V.DefaultSpecified = false;
Tok.Advance();
if (Tok.TokenIs(',')) Tok.Advance();
}
if (!Tok.TokenIs(0))
{
Tok.SaveCursor(NAME_None);
Errors.Printf(TEXT("Unexpected token %s in variable list"),
*FString(Tok.GetRange(NAME_None, 1)));
return false;
}
return true;
}
bool WingVariableList::ParseOneVariable(WingTokenizer &Tok, Var &V, WingOut Errors)
{
// Parse type.
UWingTypes::Requirements Req;
Req.BlueprintType = true;
Req.Blueprintable = false;
Req.AllowContainer = true;
if (!UWingTypes::TextToType(Tok, V.Type, Req, false, Errors))
return false;
// Parse name.
if (Tok.NextType() != Tok.Identifier)
{
Errors.Print(TEXT("ERROR: Expected variable name after type\n"));
return false;
}
V.Name = Tok.NextName();
Tok.Advance();
// Parse optional flags: (flag1, flag2, ...)
if (Tok.TokenIs('('))
{
if (!ParseVariableFlags(Tok, V.Flags, Errors)) return false;
}
// Parse optional default value: = rest-of-line
if (Tok.NextType() == Tok.RestOfLine)
{
V.DefaultSpecified = true;
V.DefaultValue = FString(Tok.NextRest().TrimStartAndEnd());
Tok.Advance();
}
// Should be at end of line.
if (Tok.NextType() != 0)
{
Tok.SaveCursor(NAME_None);
Errors.Printf(TEXT("ERROR: Unexpected token after variable declaration: '%s'\n"),
*FString(Tok.GetRange(NAME_None, 1)));
return false;
}
return true;
}
bool WingVariableList::ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out, WingOut Errors)
{
Tok.Advance(); // Step over open-paren
while (Tok.TokenIs(Tok.Identifier))
{
Out.Add(Tok.NextName());
Tok.Advance();
// Commas are optional.
if (Tok.TokenIs(',')) Tok.Advance();
}
if (!Tok.TokenIs(')'))
{
Tok.SaveCursor(NAME_None);
Errors.Printf(TEXT("ERROR: flag list contains invalid token '%s'\n"),
*FString(Tok.GetRange(NAME_None, 1)));
return false;
}
Tok.Advance(); // Step over close-paren
return true;
}
void WingVariables::Empty()
{
BlueprintVariables.Empty();
LocalVariables.Empty();
InputVariables.Empty();
OutputVariables.Empty();
}
bool WingVariables::IsEmpty()
{
return (BlueprintVariables.IsEmpty() &&
LocalVariables.IsEmpty() &&
InputVariables.IsEmpty() &&
OutputVariables.IsEmpty());
}
void WingVariables::ClearLinks()
{
BlueprintVariables.ClearLinks();
LocalVariables.ClearLinks();
InputVariables.ClearLinks();
OutputVariables.ClearLinks();
}
void WingVariables::Print(WingOut Out)
{
BlueprintVariables.Print(Out);
LocalVariables.Print(Out);
InputVariables.Print(Out);
OutputVariables.Print(Out);
}
void WingVariables::Load(WingOut Errors)
{
Empty();
if (Blueprint) return LoadBlueprint();
if (Graph) return LoadGraph();
if (CustomEvent) return LoadCustomEvent();
ErrorNoBackingStore(Errors);
}
void WingVariables::LoadBlueprint()
{
UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint.Get());
for (FBPVariableDescription& Desc : Blueprint->NewVariables)
{
// Skip event dispatchers.
if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue;
// Load up the variable.
Var V = LoadBlueprintVariableDescription(Desc, CDO);
BlueprintVariables.Variables.Add(MoveTemp(V));
}
}
void WingVariables::LoadGraph()
{
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
FBlueprintEditorUtils::GetEntryAndResultNodes(Graph.Get(), EntryNode, ResultNode);
if (EntryNode.IsValid()) LoadEditablePinBase(EntryNode.Get(), InputVariables);
if (ResultNode.IsValid()) LoadEditablePinBase(ResultNode.Get(), OutputVariables);
LoadLocalVariables(EntryNode.Get());
}
void WingVariables::LoadLocalVariables(UK2Node_EditablePinBase *Node)
{
UK2Node_FunctionEntry *Func = Cast<UK2Node_FunctionEntry>(Node);
if (Func == nullptr) return;
for (FBPVariableDescription& Desc : Func->LocalVariables)
{
Var V = LoadLocalVariableDescription(Desc);
LocalVariables.Variables.Add(MoveTemp(V));
}
}
WingVariables::Var WingVariables::LoadBlueprintVariableDescription(FBPVariableDescription &Desc, UObject *CDO)
{
Var Result;
Result.Name = Desc.VarName;
Result.Type = Desc.VarType;
if (!(Desc.PropertyFlags & CPF_DisableEditOnInstance))
Result.Flags.Add(Flag_InstanceEditable);
if (Desc.PropertyFlags & CPF_BlueprintReadOnly)
Result.Flags.Add(Flag_BlueprintReadOnly);
if (Desc.PropertyFlags & CPF_Interp)
Result.Flags.Add(Flag_ExposeToCinematics);
if (Desc.HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn))
Result.Flags.Add(Flag_ExposeOnSpawn);
if (Desc.HasMetaData(FBlueprintMetadata::MD_Private))
Result.Flags.Add(Flag_Private);
// Read default value from CDO if available.
if (CDO)
{
FProperty* Prop = CDO->GetClass()->FindPropertyByName(Desc.VarName);
if (Prop)
{
Result.DefaultValue = FWingProperty(Prop, CDO, false).GetText();
Result.DefaultSpecified = true;
}
}
return Result;
}
WingVariables::Var WingVariables::LoadLocalVariableDescription(FBPVariableDescription &Desc)
{
Var Result;
Result.Name = Desc.VarName;
Result.Type = Desc.VarType;
Result.DefaultValue = Desc.DefaultValue;
Result.DefaultSpecified = true;
return Result;
}
void WingVariables::LoadEditablePinBase(UK2Node_EditablePinBase* Node, WingVariableList &List)
{
if (Node == nullptr) return;
for (const TSharedPtr<FUserPinInfo>& PinInfo : Node->UserDefinedPins)
{
Var V;
V.Name = PinInfo->PinName;
V.Type = PinInfo->PinType;
V.DefaultValue = PinInfo->PinDefaultValue;
V.DefaultSpecified = true;
List.Variables.Add(MoveTemp(V));
}
}
void WingVariables::LoadCustomEvent()
{
LoadEditablePinBase(CustomEvent.Get(), InputVariables);
}
bool WingVariables::Check(WingOut Errors)
{
if (Blueprint) return CheckBlueprint(Errors);
if (Graph) return CheckGraph(Errors);
if (CustomEvent) return CheckCustomEvent(Errors);
return ErrorNoBackingStore(Errors);
}
bool WingVariables::CheckBlueprint(WingOut Errors)
{
bool OK = true;
if (!BlueprintVariables.CheckSanity(Flags_BlueprintVariables, true, Errors)) OK = false;
if (!LocalVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
if (!InputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
if (!OutputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
return OK;
}
bool WingVariables::CheckGraph(WingOut Errors)
{
EGraphType T = Graph->GetSchema()->GetGraphType(Graph.Get());
bool AllowLocal = (T == EGraphType::GT_Function);
bool AllowInput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro);
bool AllowOutput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro);
if ((!AllowLocal) && (!AllowInput) && (!AllowOutput))
{
Errors.Printf(TEXT("This graph type has no variables."));
return false;
}
bool OK = true;
if (!BlueprintVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
if (!LocalVariables.CheckSanity(Flags_None, AllowLocal, Errors)) OK = false;
if (!InputVariables.CheckSanity(Flags_None, AllowInput, Errors)) OK = false;
if (!OutputVariables.CheckSanity(Flags_None, AllowOutput, Errors)) OK = false;
return OK;
}
bool WingVariables::CheckCustomEvent(WingOut Errors)
{
bool OK = true;
if (!BlueprintVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
if (!LocalVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
if (!InputVariables.CheckSanity(Flags_None, true, Errors)) OK = false;
if (!OutputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
return OK;
}
bool WingVariables::Modify(WingOut Errors)
{
if (Blueprint) return ModifyBlueprint(Errors);
if (Graph) return ModifyGraph(Errors);
if (CustomEvent) return ModifyCustomEvent(Errors);
return ErrorNoBackingStore(Errors);
}
bool WingVariables::ModifyBlueprint(WingOut Errors)
{
if (!CheckBlueprint(Errors)) return false;
if (!LinkBlueprintVariables(Errors)) return false;
for (Var &V : BlueprintVariables.Variables)
{
V.BPVar->VarType = V.Type;
ModifyBlueprintVariableFlags(V);
}
if (!ModifyBlueprintDefaults(Errors)) return false;
return true;
}
bool WingVariables::LinkBlueprintVariables(WingOut Errors)
{
ClearLinks();
for (Var &V : BlueprintVariables.Variables)
{
V.BPVar = WingUtils::FindOneWithInternalID(
V.Name, Blueprint->NewVariables, TEXT("non-inherited variable"), Errors);
if (V.BPVar == nullptr) return false;
}
return true;
}
void WingVariables::ModifyBlueprintVariableFlags(Var &Input)
{
FBPVariableDescription &Out = *Input.BPVar;
// InstanceEditable: absence means CPF_DisableEditOnInstance is set
if (Input.Flags.Contains(Flag_InstanceEditable))
Out.PropertyFlags &= ~CPF_DisableEditOnInstance;
else
Out.PropertyFlags |= CPF_DisableEditOnInstance;
if (Input.Flags.Contains(Flag_BlueprintReadOnly))
Out.PropertyFlags |= CPF_BlueprintReadOnly;
else
Out.PropertyFlags &= ~CPF_BlueprintReadOnly;
if (Input.Flags.Contains(Flag_ExposeToCinematics))
Out.PropertyFlags |= CPF_Interp;
else
Out.PropertyFlags &= ~CPF_Interp;
if (Input.Flags.Contains(Flag_ExposeOnSpawn))
Out.SetMetaData(FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true"));
else
Out.RemoveMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);
if (Input.Flags.Contains(Flag_Private))
Out.SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true"));
else
Out.RemoveMetaData(FBlueprintMetadata::MD_Private);
}
bool WingVariables::ModifyBlueprintDefaults(WingOut Errors)
{
bool AnySpecified = false;
for (Var &Input : BlueprintVariables.Variables)
if (Input.DefaultSpecified) AnySpecified = true;
if (!AnySpecified) return true;
FKismetEditorUtilities::CompileBlueprint(Blueprint.Get());
UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint.Get());
if (!CDO)
{
Errors.Printf(TEXT("Blueprint didn't compile, cannot store default values"));
return false;
}
for (Var &Input : BlueprintVariables.Variables)
{
if (Input.DefaultSpecified)
{
FProperty* Prop = CDO->GetClass()->FindPropertyByName(Input.Name);
if (!Prop)
{
Errors.Printf(TEXT("Variable exists in blueprint, but not the generated class, which is weird: %s."),
*WingTokenizer::ExternalizeID(Input.Name));
return false;
}
if (!FWingProperty(Prop, CDO, true).SetText(Input.DefaultValue, Errors)) return false;
}
}
return true;
}
bool WingVariables::ModifyGraph(WingOut Errors)
{
if (!CheckGraph(Errors)) return false;
ClearLinks();
UK2Node_EditablePinBase *InputNode, *OutputNode;
UK2Node_FunctionEntry *LocalNode;
bool CreateOutputNode = !OutputVariables.Variables.IsEmpty();
if (!GetGraphNodes(InputNode, OutputNode, LocalNode, CreateOutputNode, Errors)) return false;
for (Var &V : LocalVariables.Variables)
{
FBPVariableDescription *Desc =
WingUtils::FindOneWithInternalID(V.Name, LocalNode->LocalVariables, TEXT("local variable"), Errors);
if (Desc == nullptr) return false;
Desc->VarType = V.Type;
if (V.DefaultSpecified) Desc->DefaultValue = V.DefaultValue;
}
if (!ModifyEditablePinBase(InputVariables, InputNode, Errors)) return false;
if (OutputNode)
if (!ModifyEditablePinBase(OutputVariables, OutputNode, Errors)) return false;
if (InputNode) InputNode->ReconstructNode();
if (OutputNode) OutputNode->ReconstructNode();
return true;
}
bool WingVariables::ModifyCustomEvent(WingOut Errors)
{
if (!CheckCustomEvent(Errors)) return false;
ClearLinks();
if (!ModifyEditablePinBase(InputVariables, CustomEvent.Get(), Errors)) return false;
CustomEvent->ReconstructNode();
return true;
}
bool WingVariables::ModifyEditablePinBase(WingVariableList &List, UK2Node_EditablePinBase *Node, WingOut Errors)
{
for (Var &V : List.Variables)
{
TSharedPtr<FUserPinInfo> Found =
WingUtils::FindOneWithInternalID(V.Name, Node->UserDefinedPins, List.ListName, Errors);
if (!Found) return false;
Found->PinType = V.Type;
if (V.DefaultSpecified) Found->PinDefaultValue = V.DefaultValue;
}
return true;
}
bool WingVariables::GetGraphNodes(
UK2Node_EditablePinBase *&InputNode,
UK2Node_EditablePinBase *&OutputNode,
UK2Node_FunctionEntry *&LocalNode,
bool CreateOutputNode,
WingOut Errors)
{
// In theory, none of these errors should trigger, because
// we call CheckSanity before calling this function. But
// you never know.
InputNode = nullptr;
OutputNode = nullptr;
LocalNode = nullptr;
TWeakObjectPtr<UK2Node_EditablePinBase> Inputs, Outputs;
FBlueprintEditorUtils::GetEntryAndResultNodes(Graph.Get(), Inputs, Outputs);
if (!Inputs.IsValid())
{
Errors.Printf(TEXT("ERROR: no function entry node for graph."));
return false;
}
if (!Outputs.IsValid() && CreateOutputNode)
{
Outputs = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(Inputs.Get());
if (!Outputs.IsValid())
{
Errors.Printf(TEXT("ERROR: couldn't create result node for graph."));
return false;
}
}
UK2Node_FunctionEntry *Locals = Cast<UK2Node_FunctionEntry>(Inputs.Get());
if (!Locals && (!LocalVariables.Variables.IsEmpty()))
{
Errors.Printf(TEXT("ERROR: function doesn't have a proper entry node for local variables"));
return false;
}
InputNode = Inputs.Get();
OutputNode = Outputs.Get();
LocalNode = Locals;
return true;
}
bool WingVariables::Create(WingOut Errors)
{
if (Blueprint) return CreateBlueprint(Errors);
if (Graph) return CreateGraph(Errors);
if (CustomEvent) return CreateCustomEvent(Errors);
return ErrorNoBackingStore(Errors);
}
bool WingVariables::CreateBlueprint(WingOut Errors)
{
if (!CheckBlueprint(Errors)) return false;
ClearLinks();
// Check for name collisions against existing variables, components, and the like.
TSet<FName> Names;
FBlueprintEditorUtils::GetClassVariableList(Blueprint.Get(), Names);
if (!WingUtils::FindNoDuplicateNames(Names, BlueprintVariables.Variables, TEXT("variable or component"), Errors)) return false;
// Create the variables.
for (const WingVariables::Var& V : BlueprintVariables.Variables)
{
if (!FBlueprintEditorUtils::AddMemberVariable(Blueprint.Get(), V.Name, V.Type))
{
Errors.Printf(TEXT("ERROR: Failed to add variable '%s'\n"),
*WingUtils::ExternalizeID(V.Name));
return false;
}
}
if (!LinkBlueprintVariables(Errors)) return false;
for (Var &V : BlueprintVariables.Variables) ModifyBlueprintVariableFlags(V);
if (!ModifyBlueprintDefaults(Errors)) return false;
return true;
}
bool WingVariables::CreateGraph(WingOut Errors)
{
if (!CheckGraph(Errors)) return false;
ClearLinks();
UK2Node_EditablePinBase *InputNode, *OutputNode;
UK2Node_FunctionEntry *LocalNode;
bool CreateOutputNode = (!OutputVariables.Variables.IsEmpty());
if (!GetGraphNodes(InputNode, OutputNode, LocalNode, CreateOutputNode, Errors)) return false;
// Check for name collisions against existing local variables.
const TCHAR *ctx = TEXT("local, function input, and function output variables");
TSet<FName> Names;
if (InputNode && !WingUtils::FindNoDuplicateNames(
Names, InputNode->UserDefinedPins, ctx, Errors)) return false;
if (OutputNode && !WingUtils::FindNoDuplicateNames(
Names, OutputNode->UserDefinedPins, ctx, Errors)) return false;
if (LocalNode && !WingUtils::FindNoDuplicateNames(
Names, LocalNode->LocalVariables, ctx, Errors)) return false;
if (!WingUtils::FindNoDuplicateNames(
Names, InputVariables.Variables, ctx, Errors)) return false;
if (!WingUtils::FindNoDuplicateNames(
Names, OutputVariables.Variables, ctx, Errors)) return false;
if (!WingUtils::FindNoDuplicateNames(
Names, LocalVariables.Variables, ctx, Errors)) return false;
// Create input pins on the input node.
for (const Var& V : InputVariables.Variables)
AddUserPinInfo(V, EGPD_Output, InputNode);
// Create output pins on the output node.
for (const Var& V : OutputVariables.Variables)
AddUserPinInfo(V, EGPD_Input, OutputNode);
// Create local variables via the proper API.
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph.Get());
for (const Var& V : LocalVariables.Variables)
{
if (!FBlueprintEditorUtils::AddLocalVariable(BP, Graph.Get(), V.Name, V.Type, V.DefaultValue))
{
Errors.Printf(TEXT("ERROR: Failed to create local variable '%s'\n"),
*WingUtils::ExternalizeID(V.Name));
return false;
}
}
if (InputNode) InputNode->ReconstructNode();
if (OutputNode) OutputNode->ReconstructNode();
return true;
}
bool WingVariables::CreateCustomEvent(WingOut Errors)
{
if (!CheckCustomEvent(Errors)) return false;
ClearLinks();
// Check for name collisions against existing pins.
TSet<FName> Names;
if (!WingUtils::FindNoDuplicateNames(
Names, CustomEvent->UserDefinedPins, TEXT("event parameter"), Errors)) return false;
if (!WingUtils::FindNoDuplicateNames(
Names, InputVariables.Variables, TEXT("event parameter"), Errors)) return false;
for (const Var& V : InputVariables.Variables)
AddUserPinInfo(V, EGPD_Output, CustomEvent.Get());
CustomEvent->ReconstructNode();
return true;
}
void WingVariables::AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node)
{
TSharedPtr<FUserPinInfo> Result = MakeShareable( new FUserPinInfo() );
Result->PinName = V.Name;
Result->PinType = V.Type;
Result->PinDefaultValue = V.DefaultValue;
Result->DesiredPinDirection = Dir;
Node->UserDefinedPins.Add(Result);
}
bool WingVariables::Remove(WingOut Errors)
{
if (Blueprint) return RemoveBlueprint(Errors);
if (Graph) return RemoveGraph(Errors);
if (CustomEvent) return RemoveCustomEvent(Errors);
return ErrorNoBackingStore(Errors);
}
bool WingVariables::RemoveBlueprint(WingOut Errors)
{
// Verify that all named variables exist.
TArray<FName> Names;
for (const Var& V : BlueprintVariables.Variables)
{
FBPVariableDescription* Found =
WingUtils::FindOneWithInternalID(V.Name, Blueprint->NewVariables, TEXT("non-inherited variable"), Errors);
if (!Found) return false;
Names.Add(V.Name);
}
// Remove them.
FBlueprintEditorUtils::BulkRemoveMemberVariables(Blueprint.Get(), Names);
return true;
}
bool WingVariables::RemoveGraph(WingOut Errors)
{
UK2Node_EditablePinBase *InputNode, *OutputNode;
UK2Node_FunctionEntry *LocalNode;
if (!GetGraphNodes(InputNode, OutputNode, LocalNode, false, Errors)) return false;
// Verify that all named variables exist before removing anything.
if (!OutputNode && (!OutputVariables.Variables.IsEmpty()))
{
Errors.Printf(TEXT("No output variables on this function to remove.\n"));
return false;
}
for (const Var& V : InputVariables.Variables)
{
TSharedPtr<FUserPinInfo> Found =
WingUtils::FindOneWithInternalID(V.Name, InputNode->UserDefinedPins, TEXT("input variable"), Errors);
if (!Found) return false;
}
for (const Var& V : OutputVariables.Variables)
{
TSharedPtr<FUserPinInfo> Found =
WingUtils::FindOneWithInternalID(V.Name, OutputNode->UserDefinedPins, TEXT("output variable"), Errors);
if (!Found) return false;
}
for (const Var& V : LocalVariables.Variables)
{
FBPVariableDescription* Found =
WingUtils::FindOneWithInternalID(V.Name, LocalNode->LocalVariables, TEXT("local variable"), Errors);
if (!Found) return false;
}
// Remove input and output pins.
for (const Var& V : InputVariables.Variables)
InputNode->RemoveUserDefinedPinByName(V.Name);
for (const Var& V : OutputVariables.Variables)
OutputNode->RemoveUserDefinedPinByName(V.Name);
// Remove local variables.
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph.Get());
for (const Var& V : LocalVariables.Variables)
{
LocalNode->LocalVariables.RemoveAll(
[&](const FBPVariableDescription& Desc) { return Desc.VarName == V.Name; });
FBlueprintEditorUtils::RemoveVariableNodes(BP, V.Name, true, Graph.Get());
}
if (InputNode) InputNode->ReconstructNode();
if (OutputNode) OutputNode->ReconstructNode();
return true;
}
bool WingVariables::RemoveCustomEvent(WingOut Errors)
{
// Verify that all named parameters exist before removing anything.
for (const Var& V : InputVariables.Variables)
{
TSharedPtr<FUserPinInfo> Found =
WingUtils::FindOneWithInternalID(V.Name, CustomEvent->UserDefinedPins, TEXT("event parameter"), Errors);
if (!Found) return false;
}
for (const Var& V : InputVariables.Variables)
CustomEvent->RemoveUserDefinedPinByName(V.Name);
CustomEvent->ReconstructNode();
return true;
}
bool WingVariables::SetBackingStore(UObject *Obj, WingOut Errors)
{
Blueprint.Reset();
Graph.Reset();
CustomEvent.Reset();
if (UBlueprint *BP = Cast<UBlueprint>(Obj))
{
Blueprint.Reset(BP);
return true;
}
if (UEdGraph *G = Cast<UEdGraph>(Obj))
{
Graph.Reset(G);
return true;
}
if (UK2Node_CustomEvent *E = Cast<UK2Node_CustomEvent>(Obj))
{
CustomEvent.Reset(E);
return true;
}
Errors.Printf(TEXT(
"ERROR: The variable editor can only edit blueprints, "
"graphs, and custom event nodes. Passed in: %s"),
*WingUtils::FormatName(Obj->GetClass()));
return false;
}
bool WingVariables::ErrorNoBackingStore(WingOut Errors)
{
Errors.Printf(TEXT(
"ERROR: The variable editor was not successfully "
"set up with an object to edit."));
return false;
}