@@ -58,6 +58,10 @@ TSharedPtr<FJsonObject> FEpicUnrealMCPDataAssetCommands::HandleCommand(
5858 {
5959 return HandleGetMassConfigTraits (Params);
6060 }
61+ if (CommandType == TEXT (" add_mass_config_trait" ))
62+ {
63+ return HandleAddMassConfigTrait (Params);
64+ }
6165
6266 return FEpicUnrealMCPCommonUtils::CreateErrorResponse (
6367 FString::Printf (TEXT (" Unknown data asset command: %s" ), *CommandType));
@@ -1047,3 +1051,156 @@ TSharedPtr<FJsonObject> FEpicUnrealMCPDataAssetCommands::HandleGetMassConfigTrai
10471051 Result->SetArrayField (TEXT (" traits" ), TraitsArray);
10481052 return Result;
10491053}
1054+
1055+ // ============================================================================
1056+ // add_mass_config_trait — Add a SINGLE trait to a Mass Entity Config without
1057+ // replacing existing traits. Safe for configs with complex replicator settings.
1058+ // ============================================================================
1059+
1060+ TSharedPtr<FJsonObject> FEpicUnrealMCPDataAssetCommands::HandleAddMassConfigTrait (
1061+ const TSharedPtr<FJsonObject>& Params)
1062+ {
1063+ FString AssetPath;
1064+ if (!Params->TryGetStringField (TEXT (" asset_path" ), AssetPath))
1065+ {
1066+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Missing 'asset_path'" ));
1067+ }
1068+
1069+ FString TraitClassName;
1070+ if (!Params->TryGetStringField (TEXT (" trait_class" ), TraitClassName))
1071+ {
1072+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Missing 'trait_class' (e.g. '/Script/Jiggify.MassBuildingHealthTrait')" ));
1073+ }
1074+
1075+ UObject* Asset = StaticLoadObject (UObject::StaticClass (), nullptr , *AssetPath);
1076+ if (!Asset)
1077+ {
1078+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (
1079+ FString::Printf (TEXT (" Asset not found: %s" ), *AssetPath));
1080+ }
1081+
1082+ // Resolve trait class
1083+ UClass* TraitClass = StaticLoadClass (UObject::StaticClass (), nullptr , *TraitClassName);
1084+ if (!TraitClass)
1085+ {
1086+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (
1087+ FString::Printf (TEXT (" Trait class not found: %s" ), *TraitClassName));
1088+ }
1089+
1090+ // Find Config struct property
1091+ FStructProperty* ConfigProp = nullptr ;
1092+ for (TFieldIterator<FStructProperty> It (Asset->GetClass ()); It; ++It)
1093+ {
1094+ if (It->GetName () == TEXT (" Config" ))
1095+ {
1096+ ConfigProp = *It;
1097+ break ;
1098+ }
1099+ }
1100+
1101+ if (!ConfigProp)
1102+ {
1103+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Asset has no 'Config' struct property" ));
1104+ }
1105+
1106+ void * ConfigPtr = ConfigProp->ContainerPtrToValuePtr <void >(Asset);
1107+
1108+ // Find Traits array
1109+ FArrayProperty* TraitsArrayProp = nullptr ;
1110+ for (TFieldIterator<FArrayProperty> It (ConfigProp->Struct ); It; ++It)
1111+ {
1112+ if (It->GetName () == TEXT (" Traits" ))
1113+ {
1114+ TraitsArrayProp = *It;
1115+ break ;
1116+ }
1117+ }
1118+
1119+ if (!TraitsArrayProp)
1120+ {
1121+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Config has no 'Traits' array" ));
1122+ }
1123+
1124+ FObjectProperty* InnerObjProp = CastField<FObjectProperty>(TraitsArrayProp->Inner );
1125+ if (!InnerObjProp)
1126+ {
1127+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Traits array inner type is not an object" ));
1128+ }
1129+
1130+ // Check if trait already exists
1131+ FScriptArrayHelper ArrayHelper (TraitsArrayProp, TraitsArrayProp->ContainerPtrToValuePtr <void >(ConfigPtr));
1132+ for (int32 i = 0 ; i < ArrayHelper.Num (); ++i)
1133+ {
1134+ UObject* Existing = InnerObjProp->GetObjectPropertyValue (ArrayHelper.GetRawPtr (i));
1135+ if (Existing && Existing->GetClass () == TraitClass)
1136+ {
1137+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (
1138+ FString::Printf (TEXT (" Trait '%s' already exists at index %d" ), *TraitClass->GetName (), i));
1139+ }
1140+ }
1141+
1142+ // Create new trait instance
1143+ UObject* NewTrait = NewObject<UObject>(Asset, TraitClass);
1144+ if (!NewTrait)
1145+ {
1146+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Failed to create trait instance" ));
1147+ }
1148+
1149+ // Set properties if provided
1150+ const TSharedPtr<FJsonObject>* PropertiesObj = nullptr ;
1151+ if (Params->TryGetObjectField (TEXT (" properties" ), PropertiesObj) && PropertiesObj)
1152+ {
1153+ for (auto & Pair : (*PropertiesObj)->Values )
1154+ {
1155+ FProperty* Prop = NewTrait->GetClass ()->FindPropertyByName (*Pair.Key );
1156+ if (Prop)
1157+ {
1158+ void * ValuePtr = Prop->ContainerPtrToValuePtr <void >(NewTrait);
1159+ FString ValueStr;
1160+ if (Pair.Value ->TryGetString (ValueStr))
1161+ {
1162+ Prop->ImportText_Direct (*ValueStr, ValuePtr, nullptr , PPF_None);
1163+ }
1164+ else if (Pair.Value ->Type == EJson::Number)
1165+ {
1166+ // Handle numeric values
1167+ double NumVal = Pair.Value ->AsNumber ();
1168+ if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Prop))
1169+ {
1170+ FloatProp->SetPropertyValue (ValuePtr, static_cast <float >(NumVal));
1171+ }
1172+ else if (FDoubleProperty* DoubleProp = CastField<FDoubleProperty>(Prop))
1173+ {
1174+ DoubleProp->SetPropertyValue (ValuePtr, NumVal);
1175+ }
1176+ else if (FIntProperty* IntProp = CastField<FIntProperty>(Prop))
1177+ {
1178+ IntProp->SetPropertyValue (ValuePtr, static_cast <int32>(NumVal));
1179+ }
1180+ }
1181+ else if (Pair.Value ->Type == EJson::Boolean)
1182+ {
1183+ if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop))
1184+ {
1185+ BoolProp->SetPropertyValue (ValuePtr, Pair.Value ->AsBool ());
1186+ }
1187+ }
1188+ }
1189+ }
1190+ }
1191+
1192+ // Add to array
1193+ int32 NewIndex = ArrayHelper.AddValue ();
1194+ InnerObjProp->SetObjectPropertyValue (ArrayHelper.GetRawPtr (NewIndex), NewTrait);
1195+
1196+ // Mark dirty
1197+ Asset->MarkPackageDirty ();
1198+
1199+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1200+ Result->SetBoolField (TEXT (" success" ), true );
1201+ Result->SetStringField (TEXT (" asset_path" ), AssetPath);
1202+ Result->SetStringField (TEXT (" trait_class" ), TraitClass->GetName ());
1203+ Result->SetNumberField (TEXT (" trait_index" ), NewIndex);
1204+ Result->SetNumberField (TEXT (" total_traits" ), ArrayHelper.Num ());
1205+ return Result;
1206+ }
0 commit comments