588 lines
20 KiB
C++
588 lines
20 KiB
C++
#include "BlueprintMCPServer.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraph/EdGraphPin.h"
|
|
#include "K2Node_VariableGet.h"
|
|
#include "K2Node_VariableSet.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
#include "Serialization/JsonReader.h"
|
|
#include "Serialization/JsonWriter.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
|
|
// ============================================================
|
|
// HandleChangeVariableType — change a Blueprint member variable's type
|
|
// ============================================================
|
|
|
|
void FBlueprintMCPServer::HandleChangeVariableType(const FJsonObject* Json, FJsonObject* Result)
|
|
{
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
FString VariableName = Json->GetStringField(TEXT("variable"));
|
|
FString NewTypeName = Json->GetStringField(TEXT("newType"));
|
|
FString TypeCategory; // now optional
|
|
if (Json->HasField(TEXT("typeCategory")))
|
|
{
|
|
TypeCategory = Json->GetStringField(TEXT("typeCategory"));
|
|
}
|
|
|
|
if (BlueprintName.IsEmpty() || VariableName.IsEmpty() || NewTypeName.IsEmpty())
|
|
{
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variable, newType"));
|
|
}
|
|
|
|
// Load Blueprint
|
|
FString LoadError;
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
if (!BP)
|
|
{
|
|
return MakeErrorJson(Result, LoadError);
|
|
}
|
|
|
|
// Verify variable exists
|
|
bool bVarFound = false;
|
|
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
{
|
|
if (Var.VarName.ToString() == VariableName)
|
|
{
|
|
bVarFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bVarFound)
|
|
{
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName));
|
|
}
|
|
|
|
// Build the new pin type using shared resolver
|
|
FEdGraphPinType NewPinType;
|
|
FString ResolveInput = NewTypeName;
|
|
|
|
// If typeCategory is an object reference variant, use colon syntax for the resolver
|
|
if (TypeCategory == TEXT("object") || TypeCategory == TEXT("softobject") ||
|
|
TypeCategory == TEXT("class") || TypeCategory == TEXT("softclass") ||
|
|
TypeCategory == TEXT("interface"))
|
|
{
|
|
ResolveInput = TypeCategory + TEXT(":") + NewTypeName;
|
|
}
|
|
|
|
FString TypeError;
|
|
if (!ResolveTypeFromString(ResolveInput, NewPinType, TypeError))
|
|
{
|
|
return MakeErrorJson(Result, TypeError);
|
|
}
|
|
|
|
// Derive typeCategory from the resolved pin type for the response
|
|
if (TypeCategory.IsEmpty())
|
|
{
|
|
if (NewPinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
|
|
TypeCategory = TEXT("struct");
|
|
else if (NewPinType.PinCategory == UEdGraphSchema_K2::PC_Enum || NewPinType.PinCategory == UEdGraphSchema_K2::PC_Byte)
|
|
TypeCategory = TEXT("enum");
|
|
else if (NewPinType.PinCategory == UEdGraphSchema_K2::PC_Object)
|
|
TypeCategory = TEXT("object");
|
|
else if (NewPinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject)
|
|
TypeCategory = TEXT("softobject");
|
|
else if (NewPinType.PinCategory == UEdGraphSchema_K2::PC_Class)
|
|
TypeCategory = TEXT("class");
|
|
else if (NewPinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass)
|
|
TypeCategory = TEXT("softclass");
|
|
else if (NewPinType.PinCategory == UEdGraphSchema_K2::PC_Interface)
|
|
TypeCategory = TEXT("interface");
|
|
else
|
|
TypeCategory = NewPinType.PinCategory.ToString();
|
|
}
|
|
|
|
// Check for dry run
|
|
bool bDryRun = false;
|
|
if (Json->HasField(TEXT("dryRun")))
|
|
{
|
|
bDryRun = Json->GetBoolField(TEXT("dryRun"));
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: %s variable '%s' in '%s' to %s (%s)"),
|
|
bDryRun ? TEXT("[DRY RUN] Analyzing change of") : TEXT("Changing"),
|
|
*VariableName, *BlueprintName, *NewTypeName, *TypeCategory);
|
|
|
|
// Analyze affected nodes (get/set nodes for this variable)
|
|
TArray<TSharedPtr<FJsonValue>> AffectedNodes;
|
|
TArray<UEdGraph*> AllGraphs;
|
|
BP->GetAllGraphs(AllGraphs);
|
|
for (UEdGraph* Graph : AllGraphs)
|
|
{
|
|
if (!Graph) continue;
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
{
|
|
if (!Node) continue;
|
|
if (auto* VG = Cast<UK2Node_VariableGet>(Node))
|
|
{
|
|
if (VG->GetVarName().ToString() == VariableName)
|
|
{
|
|
TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>();
|
|
AffNode->SetStringField(TEXT("nodeId"), VG->NodeGuid.ToString());
|
|
AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableGet"));
|
|
AffNode->SetStringField(TEXT("graph"), Graph->GetName());
|
|
// Check which pins would be affected
|
|
TArray<TSharedPtr<FJsonValue>> AffPins;
|
|
for (UEdGraphPin* Pin : VG->Pins)
|
|
{
|
|
if (Pin && Pin->LinkedTo.Num() > 0 && Pin->Direction == EGPD_Output)
|
|
{
|
|
AffPins.Add(MakeShared<FJsonValueString>(
|
|
FString::Printf(TEXT("%s (connected to %d pin(s))"),
|
|
*Pin->PinName.ToString(), Pin->LinkedTo.Num())));
|
|
}
|
|
}
|
|
AffNode->SetArrayField(TEXT("affectedPins"), AffPins);
|
|
AffectedNodes.Add(MakeShared<FJsonValueObject>(AffNode));
|
|
}
|
|
}
|
|
else if (auto* VS = Cast<UK2Node_VariableSet>(Node))
|
|
{
|
|
if (VS->GetVarName().ToString() == VariableName)
|
|
{
|
|
TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>();
|
|
AffNode->SetStringField(TEXT("nodeId"), VS->NodeGuid.ToString());
|
|
AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableSet"));
|
|
AffNode->SetStringField(TEXT("graph"), Graph->GetName());
|
|
TArray<TSharedPtr<FJsonValue>> AffPins;
|
|
for (UEdGraphPin* Pin : VS->Pins)
|
|
{
|
|
if (Pin && Pin->LinkedTo.Num() > 0)
|
|
{
|
|
AffPins.Add(MakeShared<FJsonValueString>(
|
|
FString::Printf(TEXT("%s (connected to %d pin(s))"),
|
|
*Pin->PinName.ToString(), Pin->LinkedTo.Num())));
|
|
}
|
|
}
|
|
AffNode->SetArrayField(TEXT("affectedPins"), AffPins);
|
|
AffectedNodes.Add(MakeShared<FJsonValueObject>(AffNode));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bDryRun)
|
|
{
|
|
Result->SetBoolField(TEXT("dryRun"), true);
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
Result->SetStringField(TEXT("variable"), VariableName);
|
|
Result->SetStringField(TEXT("newType"), NewTypeName);
|
|
Result->SetStringField(TEXT("typeCategory"), TypeCategory);
|
|
Result->SetNumberField(TEXT("affectedNodeCount"), AffectedNodes.Num());
|
|
Result->SetArrayField(TEXT("affectedNodes"), AffectedNodes);
|
|
return;
|
|
}
|
|
|
|
// Directly modify the variable type in the description array.
|
|
for (FBPVariableDescription& Var : BP->NewVariables)
|
|
{
|
|
if (Var.VarName == FName(*VariableName))
|
|
{
|
|
Var.VarType = NewPinType;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Save
|
|
bool bSaved = SaveBlueprintPackage(BP);
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Variable type changed, save %s"),
|
|
bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
|
|
// Return updated variable state
|
|
TSharedRef<FJsonObject> UpdatedVar = MakeShared<FJsonObject>();
|
|
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
{
|
|
if (Var.VarName == FName(*VariableName))
|
|
{
|
|
UpdatedVar->SetStringField(TEXT("name"), Var.VarName.ToString());
|
|
UpdatedVar->SetStringField(TEXT("type"), Var.VarType.PinCategory.ToString());
|
|
if (Var.VarType.PinSubCategoryObject.IsValid())
|
|
UpdatedVar->SetStringField(TEXT("subtype"), Var.VarType.PinSubCategoryObject->GetName());
|
|
UpdatedVar->SetBoolField(TEXT("isArray"), Var.VarType.IsArray());
|
|
break;
|
|
}
|
|
}
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
Result->SetStringField(TEXT("variable"), VariableName);
|
|
Result->SetStringField(TEXT("newType"), NewTypeName);
|
|
Result->SetStringField(TEXT("typeCategory"), TypeCategory);
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
Result->SetObjectField(TEXT("updatedVariable"), UpdatedVar);
|
|
Result->SetArrayField(TEXT("affectedNodes"), AffectedNodes);
|
|
}
|
|
|
|
// ============================================================
|
|
// HandleAddVariable — add a new member variable to a Blueprint
|
|
// ============================================================
|
|
|
|
void FBlueprintMCPServer::HandleAddVariable(const FJsonObject* Json, FJsonObject* Result)
|
|
{
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
FString VariableName = Json->GetStringField(TEXT("variableName"));
|
|
FString VariableType = Json->GetStringField(TEXT("variableType"));
|
|
|
|
if (BlueprintName.IsEmpty() || VariableName.IsEmpty() || VariableType.IsEmpty())
|
|
{
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variableName, variableType"));
|
|
}
|
|
|
|
FString Category;
|
|
if (Json->HasField(TEXT("category")))
|
|
{
|
|
Category = Json->GetStringField(TEXT("category"));
|
|
}
|
|
|
|
bool bIsArray = false;
|
|
if (Json->HasField(TEXT("isArray")))
|
|
{
|
|
bIsArray = Json->GetBoolField(TEXT("isArray"));
|
|
}
|
|
|
|
FString DefaultValue;
|
|
if (Json->HasField(TEXT("defaultValue")))
|
|
{
|
|
DefaultValue = Json->GetStringField(TEXT("defaultValue"));
|
|
}
|
|
|
|
// Load Blueprint
|
|
FString LoadError;
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
if (!BP)
|
|
{
|
|
return MakeErrorJson(Result, LoadError);
|
|
}
|
|
|
|
// Check for duplicate variable name
|
|
FName VarFName(*VariableName);
|
|
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
{
|
|
if (Var.VarName == VarFName)
|
|
{
|
|
return MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Variable '%s' already exists in Blueprint '%s'"), *VariableName, *BlueprintName));
|
|
}
|
|
}
|
|
|
|
// Resolve the type using the shared helper
|
|
FEdGraphPinType PinType;
|
|
FString TypeError;
|
|
if (!ResolveTypeFromString(VariableType, PinType, TypeError))
|
|
{
|
|
return MakeErrorJson(Result, TypeError);
|
|
}
|
|
|
|
// Set container type for arrays
|
|
if (bIsArray)
|
|
{
|
|
PinType.ContainerType = EPinContainerType::Array;
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding variable '%s' (type=%s, array=%s) to Blueprint '%s'"),
|
|
*VariableName, *VariableType, bIsArray ? TEXT("true") : TEXT("false"), *BlueprintName);
|
|
|
|
// Add the variable using the editor utility function
|
|
bool bSuccess = FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, PinType, DefaultValue);
|
|
if (!bSuccess)
|
|
{
|
|
return MakeErrorJson(Result, FString::Printf(
|
|
TEXT("FBlueprintEditorUtils::AddMemberVariable failed for '%s'"), *VariableName));
|
|
}
|
|
|
|
// Set category if provided
|
|
if (!Category.IsEmpty())
|
|
{
|
|
FBlueprintEditorUtils::SetBlueprintVariableCategory(BP, VarFName, nullptr, FText::FromString(Category));
|
|
}
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
bool bSaved = SaveBlueprintPackage(BP);
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added variable '%s' to '%s' (saved: %s)"),
|
|
*VariableName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
Result->SetStringField(TEXT("variableName"), VariableName);
|
|
Result->SetStringField(TEXT("variableType"), VariableType);
|
|
if (!Category.IsEmpty())
|
|
{
|
|
Result->SetStringField(TEXT("category"), Category);
|
|
}
|
|
Result->SetBoolField(TEXT("isArray"), bIsArray);
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
}
|
|
|
|
// ============================================================
|
|
// HandleRemoveVariable — remove a member variable from a Blueprint
|
|
// ============================================================
|
|
|
|
void FBlueprintMCPServer::HandleRemoveVariable(const FJsonObject* Json, FJsonObject* Result)
|
|
{
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
FString VariableName = Json->GetStringField(TEXT("variableName"));
|
|
|
|
if (BlueprintName.IsEmpty() || VariableName.IsEmpty())
|
|
{
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variableName"));
|
|
}
|
|
|
|
// Load Blueprint
|
|
FString LoadError;
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
if (!BP)
|
|
{
|
|
return MakeErrorJson(Result, LoadError);
|
|
}
|
|
|
|
// Find variable by name (case-insensitive)
|
|
FName VarFName(*VariableName);
|
|
bool bVarFound = false;
|
|
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
{
|
|
if (Var.VarName.ToString().Equals(VariableName, ESearchCase::IgnoreCase))
|
|
{
|
|
VarFName = Var.VarName; // Use the exact name found
|
|
bVarFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bVarFound)
|
|
{
|
|
// Build available variables list for helpful error message
|
|
TArray<TSharedPtr<FJsonValue>> AvailVars;
|
|
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
{
|
|
AvailVars.Add(MakeShared<FJsonValueString>(Var.VarName.ToString()));
|
|
}
|
|
|
|
MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName));
|
|
Result->SetArrayField(TEXT("availableVariables"), AvailVars);
|
|
return;
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing variable '%s' from Blueprint '%s'"),
|
|
*VariableName, *BlueprintName);
|
|
|
|
// Use the editor utility to remove the variable (also cleans up Get/Set nodes)
|
|
FBlueprintEditorUtils::RemoveMemberVariable(BP, VarFName);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
bool bSaved = SaveBlueprintPackage(BP);
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed variable '%s' from '%s' (saved: %s)"),
|
|
*VariableName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
Result->SetStringField(TEXT("variableName"), VariableName);
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
}
|
|
|
|
// ============================================================
|
|
// HandleSetVariableMetadata — set variable properties (category, tooltip, replication, etc.)
|
|
// ============================================================
|
|
|
|
void FBlueprintMCPServer::HandleSetVariableMetadata(const FJsonObject* Json, FJsonObject* Result)
|
|
{
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
FString VariableName = Json->GetStringField(TEXT("variable"));
|
|
|
|
if (BlueprintName.IsEmpty() || VariableName.IsEmpty())
|
|
{
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variable"));
|
|
}
|
|
|
|
// Load Blueprint
|
|
FString LoadError;
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
if (!BP)
|
|
{
|
|
return MakeErrorJson(Result, LoadError);
|
|
}
|
|
|
|
// Find the variable
|
|
FName VarFName(*VariableName);
|
|
FBPVariableDescription* VarDesc = nullptr;
|
|
for (FBPVariableDescription& Var : BP->NewVariables)
|
|
{
|
|
if (Var.VarName == VarFName)
|
|
{
|
|
VarDesc = &Var;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!VarDesc)
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> AvailableVars;
|
|
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
{
|
|
AvailableVars.Add(MakeShared<FJsonValueString>(Var.VarName.ToString()));
|
|
}
|
|
MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName));
|
|
Result->SetArrayField(TEXT("availableVariables"), AvailableVars);
|
|
return;
|
|
}
|
|
|
|
TArray<TSharedPtr<FJsonValue>> Changes;
|
|
|
|
// Category
|
|
if (Json->HasField(TEXT("category")))
|
|
{
|
|
FString OldCategory = VarDesc->Category.ToString();
|
|
FString NewCategory = Json->GetStringField(TEXT("category"));
|
|
VarDesc->Category = FText::FromString(NewCategory);
|
|
FBlueprintEditorUtils::SetBlueprintVariableCategory(BP, VarFName, nullptr, FText::FromString(NewCategory));
|
|
|
|
TSharedRef<FJsonObject> Change = MakeShared<FJsonObject>();
|
|
Change->SetStringField(TEXT("field"), TEXT("category"));
|
|
Change->SetStringField(TEXT("oldValue"), OldCategory);
|
|
Change->SetStringField(TEXT("newValue"), NewCategory);
|
|
Changes.Add(MakeShared<FJsonValueObject>(Change));
|
|
}
|
|
|
|
// Tooltip
|
|
if (Json->HasField(TEXT("tooltip")))
|
|
{
|
|
FString OldTooltip;
|
|
FBlueprintEditorUtils::GetBlueprintVariableMetaData(BP, VarFName, nullptr, TEXT("tooltip"), OldTooltip);
|
|
FString NewTooltip = Json->GetStringField(TEXT("tooltip"));
|
|
FBlueprintEditorUtils::SetBlueprintVariableMetaData(BP, VarFName, nullptr, TEXT("tooltip"), NewTooltip);
|
|
|
|
TSharedRef<FJsonObject> Change = MakeShared<FJsonObject>();
|
|
Change->SetStringField(TEXT("field"), TEXT("tooltip"));
|
|
Change->SetStringField(TEXT("oldValue"), OldTooltip);
|
|
Change->SetStringField(TEXT("newValue"), NewTooltip);
|
|
Changes.Add(MakeShared<FJsonValueObject>(Change));
|
|
}
|
|
|
|
// Replication
|
|
if (Json->HasField(TEXT("replication")))
|
|
{
|
|
FString ReplicationStr = Json->GetStringField(TEXT("replication"));
|
|
uint64 OldFlags = VarDesc->PropertyFlags;
|
|
|
|
if (ReplicationStr == TEXT("none"))
|
|
{
|
|
VarDesc->PropertyFlags &= ~CPF_Net;
|
|
VarDesc->PropertyFlags &= ~CPF_RepNotify;
|
|
VarDesc->RepNotifyFunc = NAME_None;
|
|
}
|
|
else if (ReplicationStr == TEXT("replicated"))
|
|
{
|
|
VarDesc->PropertyFlags |= CPF_Net;
|
|
VarDesc->PropertyFlags &= ~CPF_RepNotify;
|
|
VarDesc->RepNotifyFunc = NAME_None;
|
|
}
|
|
else if (ReplicationStr == TEXT("repNotify"))
|
|
{
|
|
VarDesc->PropertyFlags |= CPF_Net | CPF_RepNotify;
|
|
// Auto-generate RepNotify function name
|
|
VarDesc->RepNotifyFunc = FName(*FString::Printf(TEXT("OnRep_%s"), *VariableName));
|
|
}
|
|
else
|
|
{
|
|
return MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Invalid replication value '%s'. Valid: none, replicated, repNotify"), *ReplicationStr));
|
|
}
|
|
|
|
TSharedRef<FJsonObject> Change = MakeShared<FJsonObject>();
|
|
Change->SetStringField(TEXT("field"), TEXT("replication"));
|
|
Change->SetStringField(TEXT("newValue"), ReplicationStr);
|
|
Changes.Add(MakeShared<FJsonValueObject>(Change));
|
|
}
|
|
|
|
// ExposeOnSpawn
|
|
if (Json->HasField(TEXT("exposeOnSpawn")))
|
|
{
|
|
bool bOld = (VarDesc->PropertyFlags & CPF_ExposeOnSpawn) != 0;
|
|
bool bNew = Json->GetBoolField(TEXT("exposeOnSpawn"));
|
|
if (bNew)
|
|
VarDesc->PropertyFlags |= CPF_ExposeOnSpawn;
|
|
else
|
|
VarDesc->PropertyFlags &= ~CPF_ExposeOnSpawn;
|
|
|
|
TSharedRef<FJsonObject> Change = MakeShared<FJsonObject>();
|
|
Change->SetStringField(TEXT("field"), TEXT("exposeOnSpawn"));
|
|
Change->SetStringField(TEXT("oldValue"), bOld ? TEXT("true") : TEXT("false"));
|
|
Change->SetStringField(TEXT("newValue"), bNew ? TEXT("true") : TEXT("false"));
|
|
Changes.Add(MakeShared<FJsonValueObject>(Change));
|
|
}
|
|
|
|
// isPrivate
|
|
if (Json->HasField(TEXT("isPrivate")))
|
|
{
|
|
bool bOld = (VarDesc->PropertyFlags & CPF_DisableEditOnInstance) != 0;
|
|
bool bNew = Json->GetBoolField(TEXT("isPrivate"));
|
|
// In UE5, "private" for Blueprint variables is represented via metadata
|
|
FBlueprintEditorUtils::SetBlueprintVariableMetaData(BP, VarFName, nullptr,
|
|
TEXT("BlueprintPrivate"), bNew ? TEXT("true") : TEXT("false"));
|
|
|
|
TSharedRef<FJsonObject> Change = MakeShared<FJsonObject>();
|
|
Change->SetStringField(TEXT("field"), TEXT("isPrivate"));
|
|
Change->SetStringField(TEXT("oldValue"), bOld ? TEXT("true") : TEXT("false"));
|
|
Change->SetStringField(TEXT("newValue"), bNew ? TEXT("true") : TEXT("false"));
|
|
Changes.Add(MakeShared<FJsonValueObject>(Change));
|
|
}
|
|
|
|
// Editability (EditAnywhere, EditDefaultsOnly, EditInstanceOnly)
|
|
if (Json->HasField(TEXT("editability")))
|
|
{
|
|
FString Editability = Json->GetStringField(TEXT("editability"));
|
|
|
|
// Clear all edit flags first
|
|
VarDesc->PropertyFlags &= ~(CPF_Edit | CPF_DisableEditOnInstance | CPF_DisableEditOnTemplate);
|
|
|
|
if (Editability == TEXT("editAnywhere"))
|
|
{
|
|
VarDesc->PropertyFlags |= CPF_Edit;
|
|
}
|
|
else if (Editability == TEXT("editDefaultsOnly"))
|
|
{
|
|
VarDesc->PropertyFlags |= CPF_Edit | CPF_DisableEditOnInstance;
|
|
}
|
|
else if (Editability == TEXT("editInstanceOnly"))
|
|
{
|
|
VarDesc->PropertyFlags |= CPF_Edit | CPF_DisableEditOnTemplate;
|
|
}
|
|
else if (Editability == TEXT("none"))
|
|
{
|
|
// All edit flags already cleared
|
|
}
|
|
else
|
|
{
|
|
return MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Invalid editability value '%s'. Valid: editAnywhere, editDefaultsOnly, editInstanceOnly, none"),
|
|
*Editability));
|
|
}
|
|
|
|
TSharedRef<FJsonObject> Change = MakeShared<FJsonObject>();
|
|
Change->SetStringField(TEXT("field"), TEXT("editability"));
|
|
Change->SetStringField(TEXT("newValue"), Editability);
|
|
Changes.Add(MakeShared<FJsonValueObject>(Change));
|
|
}
|
|
|
|
if (Changes.Num() == 0)
|
|
{
|
|
return MakeErrorJson(Result, TEXT("No metadata fields specified. Provide at least one of: category, tooltip, replication, exposeOnSpawn, isPrivate, editability"));
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SetVariableMetadata on '%s.%s' — %d field(s) changed"),
|
|
*BlueprintName, *VariableName, Changes.Num());
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
bool bSaved = SaveBlueprintPackage(BP);
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
Result->SetStringField(TEXT("variable"), VariableName);
|
|
Result->SetArrayField(TEXT("changes"), Changes);
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
}
|