Files
integration/Source/Integration/FormatMessage.cpp

629 lines
21 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#include "FormatMessage.h"
#include "Internationalization/TextFormatter.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "Containers/EnumAsByte.h"
#include "Containers/UnrealString.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphSchema_K2_Actions.h"
#include "EditorCategoryUtils.h"
#include "Engine/Blueprint.h"
#include "HAL/PlatformCrt.h"
#include "Internationalization/Internationalization.h"
#include "K2Node_CallFunction.h"
#include "K2Node_MakeArray.h"
#include "K2Node_MakeStruct.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/KismetTextLibrary.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/CompilerResultsLog.h"
#include "KismetCompiler.h"
#include "Math/Vector2D.h"
#include "Misc/AssertionMacros.h"
#include "Misc/CString.h"
#include "ScopedTransaction.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "UObject/Class.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Package.h"
#include "UObject/UnrealNames.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
#define LOCTEXT_NAMESPACE "FormatMessage"
// All argument pins will have Names that start with "A:"
static bool IsArgumentPin(const UEdGraphPin *Pin) {
TCHAR pname[FName::StringBufferSize];
Pin->PinName.ToString(pname);
return pname[0] == 'A' && pname[1] == ':';
}
static FName ArgumentNameAddPrefix(const FString &name) {
FString Prefixed = FString("A:") + name;
return FName(*Prefixed);
2024-11-11 19:20:13 -05:00
}
static FString ArgumentNameRemovePrefix(const FName &name) {
return name.ToString().Mid(2, FName::StringBufferSize);
2024-11-11 19:20:13 -05:00
}
2024-11-14 23:57:04 -05:00
static const FName VerbosityPinName(TEXT("Verbosity"));
static bool IsVerbosityPin(const UEdGraphPin *Pin) {
return (Pin->PinName == VerbosityPinName);
}
static const FName FormatPinName(TEXT("Format"));
static bool IsFormatPin(const UEdGraphPin *Pin) {
return (Pin->PinName == FormatPinName);
2024-11-11 19:20:13 -05:00
}
static const FName ResultPinName(TEXT("Result"));
static bool IsResultPin(const UEdGraphPin *Pin) {
return (Pin->PinName == ResultPinName);
}
void UK2Node_FormatMessage::AllocateDefaultPins()
{
Super::AllocateDefaultPins();
2024-11-11 19:20:13 -05:00
CreateCorrectPins();
}
void UK2Node_FormatMessage::CreateCorrectPins()
2024-11-11 19:20:13 -05:00
{
if (FindPin(UEdGraphSchema_K2::PN_Execute) == nullptr) {
2024-11-14 23:57:04 -05:00
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
2024-11-11 19:20:13 -05:00
}
if (FindPin(UEdGraphSchema_K2::PN_Then) == nullptr) {
2024-11-14 23:57:04 -05:00
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
}
if (FindPin(FormatPinName, EGPD_Input) == nullptr) {
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName);
P->DefaultValue = TEXT("Message");
2024-11-14 23:57:04 -05:00
}
// If this is a FormatMessage node, create a pin to output the result as text.
//
if (!IsFormatErrorMessage())
{
if (FindPin(ResultPinName, EGPD_Output) == nullptr) {
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Text, ResultPinName);
}
2024-11-14 23:57:04 -05:00
}
// If this is a FormatErrorMessage node, create pins that control the log verbosity
//
if (IsFormatErrorMessage())
{
if (FindPin(VerbosityPinName, EGPD_Input) == nullptr) {
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxLogVerbosity>(), VerbosityPinName);
P->DefaultValue = TEXT("Error");
P->AutogeneratedDefaultValue = P->DefaultValue;
}
2024-11-11 19:20:13 -05:00
}
// Transfer all Existing Argument pins to the Old Pins Map.
//
2024-11-11 19:20:13 -05:00
TMap<FString, UEdGraphPin *> OldPins;
for (auto It = Pins.CreateIterator(); It; ++It)
{
UEdGraphPin* CheckPin = *It;
if (IsArgumentPin(CheckPin))
{
OldPins.Add(ArgumentNameRemovePrefix(CheckPin->PinName), CheckPin);
2024-11-11 19:20:13 -05:00
It.RemoveCurrent();
}
}
2024-11-11 19:20:13 -05:00
// Create Argument pins in the correct order, reusing old pins where possible.
//
for (const FString& Name : PinNames)
{
UEdGraphPin **OldPin = OldPins.Find(Name);
2024-11-11 19:20:13 -05:00
if (OldPin != nullptr) {
Pins.Emplace(*OldPin);
OldPins.Remove(Name);
2024-11-11 19:20:13 -05:00
} else {
FName PrefixedName = ArgumentNameAddPrefix(Name);
2024-11-14 23:57:04 -05:00
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PrefixedName);
2024-11-11 19:20:13 -05:00
}
}
// Delete any unused pins.
//
2024-11-11 19:20:13 -05:00
for (auto &iter : OldPins)
{
iter.Value->Modify();
iter.Value->MarkAsGarbage();
}
2024-11-11 19:20:13 -05:00
OldPins.Empty();
}
2024-11-11 19:20:13 -05:00
void UK2Node_FormatMessage::SynchronizeArgumentPinType(UEdGraphPin* Pin)
{
2024-11-11 19:20:13 -05:00
if (IsArgumentPin(Pin))
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
bool bPinTypeChanged = false;
if (Pin->LinkedTo.Num() == 0)
{
static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType());
// Ensure wildcard
if (Pin->PinType != WildcardPinType)
{
Pin->PinType = WildcardPinType;
bPinTypeChanged = true;
}
}
else
{
UEdGraphPin* ArgumentSourcePin = Pin->LinkedTo[0];
// Take the type of the connected pin
if (Pin->PinType != ArgumentSourcePin->PinType)
{
Pin->PinType = ArgumentSourcePin->PinType;
bPinTypeChanged = true;
}
}
if (bPinTypeChanged)
{
// Let the graph know to refresh
GetGraph()->NotifyNodeChanged(this);
UBlueprint* Blueprint = GetBlueprint();
if (!Blueprint->bBeingCompiled)
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
}
FText UK2Node_FormatMessage::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (IsFormatErrorMessage())
{
return LOCTEXT("FormatErrorMessage_Title", "Format Log Message");
}
else
{
return LOCTEXT("FormatMessage_Title", "Format Message");
}
}
FText UK2Node_FormatMessage::GetPinDisplayName(const UEdGraphPin* Pin) const
{
// The exec pins don't need labels.
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
{
return FText::GetEmpty();
}
2024-11-14 23:57:04 -05:00
// Many pins can go unlabeled if they have default values.
if (IsFormatPin(Pin) || IsVerbosityPin(Pin))
2024-11-14 23:57:04 -05:00
{
if (Pin->LinkedTo.Num() == 0)
{
2024-11-14 23:57:04 -05:00
return FText::GetEmpty();
}
}
// For argument pins, we must strip off the Argument Pin Prefix.
if (IsArgumentPin(Pin)) {
return FText::FromString(ArgumentNameRemovePrefix(Pin->PinName));
}
// Otherwise, just return the Pin Name the normal way.
return FText::FromName(Pin->PinName);
}
void UK2Node_FormatMessage::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
const FName PropertyName = (PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None);
if (PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_FormatMessage, PinNames))
{
ReconstructNode();
}
Super::PostEditChangeProperty(PropertyChangedEvent);
GetGraph()->NotifyNodeChanged(this);
}
void UK2Node_FormatMessage::PinConnectionListChanged(UEdGraphPin* Pin)
{
Modify();
SynchronizeArgumentPinType(Pin);
}
void UK2Node_FormatMessage::PinDefaultValueChanged(UEdGraphPin* Pin)
{
2024-11-11 19:20:13 -05:00
if(IsFormatPin(Pin))
{
PinNames.Empty();
FText::GetFormatPatternParameters(FText::FromString(Pin->DefaultValue), PinNames);
2024-11-11 19:20:13 -05:00
CreateCorrectPins();
GetGraph()->NotifyNodeChanged(this);
}
}
void UK2Node_FormatMessage::PinTypeChanged(UEdGraphPin* Pin)
{
SynchronizeArgumentPinType(Pin);
Super::PinTypeChanged(Pin);
}
FText UK2Node_FormatMessage::GetTooltipText() const
{
return NodeTooltip;
}
void UK2Node_FormatMessage::PostReconstructNode()
{
Super::PostReconstructNode();
UEdGraph* OuterGraph = GetGraph();
if (!IsTemplate() && OuterGraph && OuterGraph->Schema) {
for (UEdGraphPin* CurrentPin : Pins)
{
// Potentially update an argument pin type
SynchronizeArgumentPinType(CurrentPin);
}
}
}
// Get a function that can convert the specified type into a FFormatArgumentData.
//
// For example:
// * if you pass in a pin of type 'String', it will return UlxFormatDataLibrary::FormatArgumentDataString
// * if you pass in a pin of type 'Vector', it will return UlxFormatDataLibrary::FormatArgumentDataVector
// and so forth.
//
UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphPinType& PinType, bool AllowWild)
{
// Special case. Wildcard Pins are unconnected pins.
//
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard && AllowWild)
{
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataBlank));
}
// Try to find a match in the UlxFormatDataLibrary.
//
for (auto It = TFieldIterator<UFunction>(UlxFormatDataLibrary::StaticClass()); It; ++It)
{
UFunction* Function = *It;
FProperty* ValueProperty = Function->FindPropertyByName(TEXT("AutoConvertedValue"));
FEdGraphPinType ValuePinType;
bool Convertible = Schema->ConvertPropertyToPinType(ValueProperty, ValuePinType);
if (!Convertible) continue;
if (!Schema->ArePinTypesEquivalent(PinType, ValuePinType)) continue;
return Function;
}
// A general handler for Enums. You can override this for specific enums by
// putting that particular enum into the UlxFormatDataLibrary.
//
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast<const UEnum>(PinType.PinSubCategoryObject)))
{
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataEnum));
}
// A case for subclasses of 'Object' which are not exactly 'Object'
//
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Object)
{
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataObject));
}
// We don't have a match.
//
return nullptr;
};
void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
/**
At the end of this, the UK2Node_FormatMessage will not be a part of the Blueprint, it merely handles connecting
the other nodes into the Blueprint.
*/
const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
// Create a "Make Array" node to compile the list of arguments into an array for the Format function being called
UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode<UK2Node_MakeArray>(this, SourceGraph);
MakeArrayNode->AllocateDefaultPins();
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this);
// Decide which formatting function we're going to call.
UFunction *FormatFunction;
if (IsFormatErrorMessage())
{
FormatFunction = UK2Node_FormatMessage::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UK2Node_FormatMessage, FormatLogMessageInternal));
}
else
{
FormatFunction = UKismetTextLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Format));
}
// This is the node that does all the Format work and outputs the message.
UK2Node_CallFunction* CallFormatFunction = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
CallFormatFunction->SetFromFunction(FormatFunction);
CallFormatFunction->AllocateDefaultPins();
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFormatFunction, this);
// Connect the output of the "Make Array" pin to the function's "InArgs" pin
UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin();
ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs")));
// This will set the "Make Array" node's type, only works if one pin is connected.
MakeArrayNode->PinConnectionListChanged(ArrayOut);
// For each argument, we will need to add in a "Make Struct" node.
for(int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx)
{
FString OriginalName = PinNames[ArgIdx];
UEdGraphPin* ArgumentPin = FindPin(ArgumentNameAddPrefix(OriginalName), EGPD_Input);
// Find a function that can convert the input into an FFormatArgumentData.
UFunction *Converter = ToFormatArgumentData(Schema, ArgumentPin->PinType, true);
if (Converter == nullptr)
{
CompilerContext.MessageLog.Error(TEXT("Cannot convert Pin to a Format Argument"));
continue;
}
// Add a node to call the converter.
UK2Node_CallFunction* ConvertNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
ConvertNode->SetFromFunction(Converter);
ConvertNode->AllocateDefaultPins();
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(ConvertNode, this);
UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("AutoConvertedValue"));
UEdGraphPin *NamePin = ConvertNode->FindPinChecked(TEXT("Name"));
UEdGraphPin *SubCategoryObjectPin = ConvertNode->FindPin(TEXT("PinSubCategoryObject"));
// Set a value for the 'Name' pin of the converter.
ConvertNode->GetSchema()->TrySetDefaultValue(*NamePin, OriginalName);
// Connect the Value pin of the converter, if any.
if (ValuePin != nullptr)
{
CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ValuePin);
}
// If the converter wants to know the PinSubCategoryObject, pass it in.
if (SubCategoryObjectPin != nullptr)
{
SubCategoryObjectPin->DefaultObject = Cast<UObject>(ArgumentPin->PinType.PinSubCategoryObject);
}
// The "Make Array" node already has one pin available, so don't create one for ArgIdx == 0
if(ArgIdx > 0)
{
MakeArrayNode->AddInputPin();
}
// Find the input pin on the "Make Array" node by index.
const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx);
UEdGraphPin* InputPin = MakeArrayNode->FindPinChecked(PinName);
// Find the output for the pin's "Make Struct" node and link it to the corresponding pin on the "Make Array" node.
ConvertNode->GetReturnValuePin()->MakeLinkTo(InputPin);
}
// Connect up other pins to the Formatting node.
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(FormatPinName), *CallFormatFunction->FindPinChecked(TEXT("InPattern")));
if (IsFormatErrorMessage())
{
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(VerbosityPinName), *CallFormatFunction->FindPinChecked(TEXT("Verbosity")));
}
else
{
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(ResultPinName), *CallFormatFunction->GetReturnValuePin());
}
// Link up the Exec pins.
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *CallFormatFunction->GetExecPin());
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *CallFormatFunction->GetThenPin());
BreakAllNodeLinks();
}
UK2Node::ERedirectType UK2Node_FormatMessage::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
{
ERedirectType RedirectType = ERedirectType_None;
// if the pin names do match
if (NewPin->PinName.ToString().Equals(OldPin->PinName.ToString(), ESearchCase::CaseSensitive))
{
// Make sure we're not dealing with a menu node
UEdGraph* OuterGraph = GetGraph();
if( OuterGraph && OuterGraph->Schema )
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
if( !K2Schema || K2Schema->IsSelfPin(*NewPin) || K2Schema->ArePinTypesCompatible(OldPin->PinType, NewPin->PinType) )
{
RedirectType = ERedirectType_Name;
}
else
{
RedirectType = ERedirectType_None;
}
}
}
else
{
// try looking for a redirect if it's a K2 node
if (UK2Node* Node = Cast<UK2Node>(NewPin->GetOwningNode()))
{
// if you don't have matching pin, now check if there is any redirect param set
TArray<FString> OldPinNames;
GetRedirectPinNames(*OldPin, OldPinNames);
FName NewPinName;
RedirectType = ShouldRedirectParam(OldPinNames, /*out*/ NewPinName, Node);
// make sure they match
if ((RedirectType != ERedirectType_None) && (!NewPin->PinName.ToString().Equals(NewPinName.ToString(), ESearchCase::CaseSensitive)))
{
RedirectType = ERedirectType_None;
}
}
}
return RedirectType;
}
bool UK2Node_FormatMessage::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
2024-11-14 23:57:04 -05:00
// The following pins cannot be connected. They are meant to be hardwired constants.
2024-11-11 19:20:13 -05:00
if (IsFormatPin(MyPin))
{
OutReason = LOCTEXT("Error_FormatStringMustBeHardwired", "Format string must be a hardwired constant.").ToString();
return true;
}
2024-11-11 19:20:13 -05:00
// Argument input pins may only be connected to Byte, Integer, Float, Text, and ETextGender pins...
if (IsArgumentPin(MyPin))
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
const FName& OtherPinCategory = OtherPin->PinType.PinCategory;
UFunction *Converter = ToFormatArgumentData(K2Schema, OtherPin->PinType, false);
if (Converter == nullptr)
{
OutReason = LOCTEXT("Error_InvalidArgumentType", "Data cannot be converted to text.").ToString();
return true;
}
}
return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason);
}
void UK2Node_FormatMessage::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_FormatMessage::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text);
}
UK2Node_FormatMessage::UK2Node_FormatMessage(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
NodeTooltip = LOCTEXT("NodeTooltip",
"Format a message, and output it as Text.\n"
"\n"
" \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
"\n"
);
}
UK2Node_FormatLogMessage::UK2Node_FormatLogMessage(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
NodeTooltip = LOCTEXT("NodeTooltip",
"Output an error, warning, or informational message to the log file.\n"
"\n"
" \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
"\n"
"It is often desirable to use this in conjunction with a separate utility that\n"
"pauses the execution of the blueprint whenever an error is logged."
);
}
2024-11-14 23:57:04 -05:00
void UK2Node_FormatMessage::FormatLogMessageInternal(UObject *Context, ElxLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs)
{
// For throttled verbosity levels, suppress repeated messages with the
// same format pattern. We key on the blueprint name + format pattern,
// and allow at most one message per second per key.
//
if (Verbosity == ElxLogVerbosity::ThrottledDisplay || Verbosity == ElxLogVerbosity::ThrottledLog)
{
static TMap<FString, double> LastLogTime;
double Now = FPlatformTime::Seconds();
FString Key = Context->GetClass()->GetName() + TEXT("::") + InPattern;
double &Last = LastLogTime.FindOrAdd(Key, 0.0);
if (Now - Last < 1.0)
{
return;
}
Last = Now;
}
// Generate the formatted string.
//
FText InPatternText(FText::FromString(InPattern));
FText Message = FTextFormatter::Format(MoveTemp(InPatternText), MoveTemp(InArgs), false, false);
FString MessageString = Message.ToString();
// Get the blueprint name.
//
// Normally, the log function expects you to pass in a filename, and a log
// category name. We use the blueprint name for both.
//
// Using the blueprint name as a log category name is not technically
// correct. However, there is no correct way to create log categories
// from inside of blueprints. Doing it this way at least produces a reasonable
// message inside the log. What doesn't work correctly is the log message
// suppression system. Ie, console commands like 'log <category> verbose'
// don't have any effect here. The design of the log message suppression
// system is such that there just is no reasonable way to hook into it from
// inside of blueprints.
//
FString BlueprintNameString = Context->GetClass()->GetName();
auto BlueprintNameAnsi = StringCast<ANSICHAR>(*BlueprintNameString);
FLogCategoryName BlueprintNameLogCategory(Context->GetClass()->GetFName());
// Output to Log
//
ELogVerbosity::Type VerbosityValue = UlxBlueprintErrorLibrary::ConvertElxLogVerbosity(Verbosity);
if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY)
{
FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString);
}
}
#undef LOCTEXT_NAMESPACE