Refactoring

This commit is contained in:
Ivan Anikin
2026-05-21 14:14:32 +07:00
parent 1b4e01b431
commit 54f34fc25e
247 changed files with 933 additions and 8 deletions
@@ -11,6 +11,7 @@
#include "LLM/SystemEventDataAsset.h"
#include "Misc/Paths.h"
#include "Morph/MorphControllerComponent.h"
#include "Player/PlayerContextComponent.h"
#include "World/WorldContextActor.h"
#include "Misc/FileHelper.h"
#include "Serialization/JsonSerializer.h"
@@ -49,6 +50,14 @@ void UPersonaBrainComponent::BeginPlay()
MorphController = Owner->FindComponentByClass<UMorphControllerComponent>();
}
}
if (!PlayerContext && bAutoFindPlayerContext)
{
if (AActor* Owner = GetOwner())
{
PlayerContext = Owner->FindComponentByClass<UPlayerContextComponent>();
}
}
}
void UPersonaBrainComponent::SendMessage(const FString& Prompt)
@@ -297,6 +306,11 @@ void UPersonaBrainComponent::AppendSystemMessages(TArray<TSharedPtr<FJsonValue>>
AddSystemMessage(Messages, MorphController->BuildEmotionPresetsPrompt());
}
if (PlayerContext)
{
AddSystemMessage(Messages, PlayerContext->BuildPlayerContextPrompt());
}
if (!CurrentSystemEventPrompt.IsEmpty())
{
AddSystemMessage(Messages, FString::Printf(
@@ -0,0 +1,138 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Player/PlayerContextComponent.h"
UPlayerContextComponent::UPlayerContextComponent()
{
PrimaryComponentTick.bCanEverTick = false;
PromptDetails = FText::FromString(TEXT(
"This block describes the player's current context as simple key/value pairs. "
"Each key is a context name, and each value is what is true for the player right now."));
Context.Add(TEXT("where"), FString());
Context.Add(TEXT("interactingWith"), FString());
}
void UPlayerContextComponent::SetContext(const FString& Key, const FString& Value)
{
const FString NormalizedKey = NormalizeContextKey(Key);
if (NormalizedKey.IsEmpty())
{
return;
}
Context.Add(NormalizedKey, Value.TrimStartAndEnd());
}
bool UPlayerContextComponent::GetContext(const FString& Key, FString& OutValue) const
{
OutValue.Reset();
const FString NormalizedKey = NormalizeContextKey(Key);
if (NormalizedKey.IsEmpty())
{
return false;
}
if (const FString* FoundValue = Context.Find(NormalizedKey))
{
OutValue = *FoundValue;
return true;
}
return false;
}
bool UPlayerContextComponent::RemoveContext(const FString& Key)
{
const FString NormalizedKey = NormalizeContextKey(Key);
return !NormalizedKey.IsEmpty() && Context.Remove(NormalizedKey) > 0;
}
bool UPlayerContextComponent::HasContext() const
{
for (const TPair<FString, FString>& Entry : Context)
{
if (!Entry.Key.TrimStartAndEnd().IsEmpty() && !Entry.Value.TrimStartAndEnd().IsEmpty())
{
return true;
}
}
return false;
}
void UPlayerContextComponent::SetPlayerContext(const FString& NewWhere, const FString& NewInteractingWith)
{
SetContext(TEXT("where"), NewWhere);
SetContext(TEXT("interactingWith"), NewInteractingWith);
}
void UPlayerContextComponent::SetWhere(const FString& NewWhere)
{
SetContext(TEXT("where"), NewWhere);
}
void UPlayerContextComponent::SetInteractingWith(const FString& NewInteractingWith)
{
SetContext(TEXT("interactingWith"), NewInteractingWith);
}
void UPlayerContextComponent::ClearPlayerContext()
{
Context.Empty();
}
FString UPlayerContextComponent::BuildPlayerContextPrompt() const
{
if (!HasContext())
{
return FString();
}
TArray<FString> Lines;
Lines.Add(TEXT("[Player Context]"));
const FString Details = PromptDetails.ToString().TrimStartAndEnd();
if (!Details.IsEmpty())
{
Lines.Add(Details);
}
TArray<FString> Keys;
Context.GetKeys(Keys);
Keys.Sort();
for (const FString& Key : Keys)
{
const FString* Value = Context.Find(Key);
if (!Value)
{
continue;
}
const FString TrimmedValue = Value->TrimStartAndEnd();
if (TrimmedValue.IsEmpty())
{
continue;
}
Lines.Add(FString::Printf(TEXT("%s: \"%s\""), *Key, *EscapePromptValue(TrimmedValue)));
}
return FString::Join(Lines, TEXT("\n"));
}
FString UPlayerContextComponent::NormalizeContextKey(const FString& Key)
{
return Key.TrimStartAndEnd();
}
FString UPlayerContextComponent::EscapePromptValue(const FString& Value)
{
FString EscapedValue = Value.TrimStartAndEnd();
EscapedValue.ReplaceInline(TEXT("\\"), TEXT("\\\\"));
EscapedValue.ReplaceInline(TEXT("\""), TEXT("\\\""));
return EscapedValue;
}
@@ -10,6 +10,7 @@
class USystemEventDataAsset;
class UIdentityDataAsset;
class UMorphControllerComponent;
class UPlayerContextComponent;
class FJsonObject;
class FJsonValue;
@@ -91,6 +92,12 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller")
bool bAutoFindMorphController = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player Context")
TObjectPtr<UPlayerContextComponent> PlayerContext;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player Context")
bool bAutoFindPlayerContext = true;
UPROPERTY(BlueprintAssignable, Category = "LLM|Events")
FLLMResponseDelegate OnLLMResponse;
@@ -0,0 +1,53 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "PlayerContextComponent.generated.h"
UCLASS(ClassGroup = (FunnyGirl), meta = (BlueprintSpawnableComponent, DisplayName = "Player Context"))
class FUNNYGIRL_API UPlayerContextComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPlayerContextComponent();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player Context")
TMap<FString, FString> Context;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player Context|Prompt", meta = (MultiLine = "true"))
FText PromptDetails;
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Player Context")
void SetContext(const FString& Key, const FString& Value);
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Player Context")
bool GetContext(const FString& Key, FString& OutValue) const;
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Player Context")
bool RemoveContext(const FString& Key);
UFUNCTION(BlueprintPure, Category = "FunnyGirl|Player Context")
bool HasContext() const;
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Player Context")
void SetPlayerContext(const FString& NewWhere, const FString& NewInteractingWith);
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Player Context")
void SetWhere(const FString& NewWhere);
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Player Context")
void SetInteractingWith(const FString& NewInteractingWith);
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Player Context")
void ClearPlayerContext();
UFUNCTION(BlueprintPure, Category = "FunnyGirl|Player Context")
FString BuildPlayerContextPrompt() const;
private:
static FString NormalizeContextKey(const FString& Key);
static FString EscapePromptValue(const FString& Value);
};