203 lines
6.6 KiB
C++
203 lines
6.6 KiB
C++
|
|
// Portage UE 5.5 - voir LPModInfoSubsystem.h
|
||
|
|
|
||
|
|
#include "LPModInfoSubsystem.h"
|
||
|
|
|
||
|
|
#include "Engine/Engine.h"
|
||
|
|
#include "Engine/GameInstance.h"
|
||
|
|
#include "Engine/GameViewportClient.h"
|
||
|
|
#include "GeneralProjectSettings.h"
|
||
|
|
#include "HAL/FileManager.h"
|
||
|
|
#include "HAL/PlatformTime.h"
|
||
|
|
#include "LifePlay.h"
|
||
|
|
#include "Misc/FileHelper.h"
|
||
|
|
#include "Misc/Paths.h"
|
||
|
|
#include "Styling/CoreStyle.h"
|
||
|
|
#include "UObject/UnrealType.h"
|
||
|
|
#include "Widgets/Input/SButton.h"
|
||
|
|
#include "Widgets/Layout/SBorder.h"
|
||
|
|
#include "Widgets/Layout/SBox.h"
|
||
|
|
#include "Widgets/SBoxPanel.h"
|
||
|
|
#include "Widgets/SOverlay.h"
|
||
|
|
#include "Widgets/Text/STextBlock.h"
|
||
|
|
|
||
|
|
namespace
|
||
|
|
{
|
||
|
|
constexpr double CheckInterval = 2.0;
|
||
|
|
|
||
|
|
FString FieldText(const UStruct* Struct, const void* Data, const TCHAR* AuthoredName)
|
||
|
|
{
|
||
|
|
for (TFieldIterator<FProperty> It(Struct); It; ++It)
|
||
|
|
{
|
||
|
|
if (It->GetAuthoredName() == AuthoredName)
|
||
|
|
{
|
||
|
|
FString Out;
|
||
|
|
It->ExportTextItem_InContainer(Out, Data, nullptr, nullptr, PPF_None);
|
||
|
|
return Out.TrimQuotes();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return FString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPModInfoSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||
|
|
{
|
||
|
|
Super::Initialize(Collection);
|
||
|
|
GameVersion = GetDefault<UGeneralProjectSettings>()->ProjectVersion;
|
||
|
|
|
||
|
|
// Modules/<id>/<id>.lpmod (meme regle que le jeu : un fichier par dossier de module, a sa racine).
|
||
|
|
const FString ModulesDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir() / TEXT("Modules"));
|
||
|
|
TArray<FString> Dirs;
|
||
|
|
IFileManager::Get().FindFiles(Dirs, *(ModulesDir / TEXT("*")), false, true);
|
||
|
|
int32 WithGameVersion = 0;
|
||
|
|
for (const FString& Dir : Dirs)
|
||
|
|
{
|
||
|
|
FString Text;
|
||
|
|
if (!FFileHelper::LoadFileToString(Text, *(ModulesDir / Dir / Dir + TEXT(".lpmod"))))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
TMap<FString, FString>& Keys = Metadata.Add(Dir);
|
||
|
|
TArray<FString> Lines;
|
||
|
|
Text.ParseIntoArrayLines(Lines, true);
|
||
|
|
for (const FString& Line : Lines)
|
||
|
|
{
|
||
|
|
FString Key, Value;
|
||
|
|
if (!Line.TrimStart().StartsWith(TEXT("//")) && Line.Split(TEXT(":"), &Key, &Value))
|
||
|
|
{
|
||
|
|
Keys.Add(Key.TrimStartAndEnd().ToUpper(), Value.TrimStartAndEnd());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
WithGameVersion += Keys.Contains(TEXT("MODULE_GAME_VERSION")) ? 1 : 0;
|
||
|
|
}
|
||
|
|
UE_LOG(LogLifePlay, Display, TEXT("Modules : %d .lpmod lu(s), %d avec MODULE_GAME_VERSION (jeu %s)"), Metadata.Num(), WithGameVersion, *GameVersion);
|
||
|
|
if (WithGameVersion > 0)
|
||
|
|
{
|
||
|
|
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPModInfoSubsystem::Tick));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPModInfoSubsystem::Deinitialize()
|
||
|
|
{
|
||
|
|
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
|
||
|
|
CloseWarning();
|
||
|
|
Super::Deinitialize();
|
||
|
|
}
|
||
|
|
|
||
|
|
FString ULPModInfoSubsystem::GetModuleMetadata(const FString& Module, const FString& Key) const
|
||
|
|
{
|
||
|
|
for (const TPair<FString, TMap<FString, FString>>& Pair : Metadata)
|
||
|
|
{
|
||
|
|
if (Pair.Key.Equals(Module, ESearchCase::IgnoreCase))
|
||
|
|
{
|
||
|
|
const FString* Value = Pair.Value.Find(Key.ToUpper());
|
||
|
|
return Value ? *Value : FString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return FString();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ULPModInfoSubsystem::IsModuleEnabled(const FString& Module) const
|
||
|
|
{
|
||
|
|
// Meme regle que GI.IsModEnabled : un module absent de GI.ModEnableds est inactif.
|
||
|
|
const UObject* GI = GetGameInstance();
|
||
|
|
const FArrayProperty* Prop = GI ? CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(TEXT("ModEnableds"))) : nullptr;
|
||
|
|
const FStructProperty* Inner = Prop ? CastField<FStructProperty>(Prop->Inner) : nullptr;
|
||
|
|
if (!Inner)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr<void>(GI));
|
||
|
|
for (int32 i = 0; i < Helper.Num(); ++i)
|
||
|
|
{
|
||
|
|
if (FieldText(Inner->Struct, Helper.GetRawPtr(i), TEXT("UniqueID")).Equals(Module, ESearchCase::IgnoreCase))
|
||
|
|
{
|
||
|
|
return FieldText(Inner->Struct, Helper.GetRawPtr(i), TEXT("IsEnabled")).Equals(TEXT("True"), ESearchCase::IgnoreCase);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ULPModInfoSubsystem::Tick(float DeltaTime)
|
||
|
|
{
|
||
|
|
const double Now = FPlatformTime::Seconds();
|
||
|
|
if (Now < NextCheck || !GEngine || !GEngine->GameViewport || Popup.IsValid())
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
NextCheck = Now + CheckInterval;
|
||
|
|
TArray<FString> Lines;
|
||
|
|
for (const TPair<FString, TMap<FString, FString>>& Pair : Metadata)
|
||
|
|
{
|
||
|
|
const FString* Built = Pair.Value.Find(TEXT("MODULE_GAME_VERSION"));
|
||
|
|
if (!Built || Built->IsEmpty() || *Built == GameVersion || Warned.Contains(Pair.Key) || !IsModuleEnabled(Pair.Key))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
Warned.Add(Pair.Key);
|
||
|
|
const FString* Name = Pair.Value.Find(TEXT("MODULE_NAME"));
|
||
|
|
Lines.Add(FString::Printf(TEXT("- %s (%s) : built for LifePlay %s"), Name ? **Name : *Pair.Key, *Pair.Key, **Built));
|
||
|
|
UE_LOG(LogLifePlay, Warning, TEXT("Modules : %s construit pour LifePlay %s, jeu %s"), *Pair.Key, **Built, *GameVersion);
|
||
|
|
}
|
||
|
|
if (Lines.Num() > 0)
|
||
|
|
{
|
||
|
|
ShowWarning(Lines);
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPModInfoSubsystem::ShowWarning(const TArray<FString>& Lines)
|
||
|
|
{
|
||
|
|
const FString Body = FString::Printf(TEXT("This game is LifePlay %s. The following enabled mod(s) were built for another version:\n\n%s\n\n")
|
||
|
|
TEXT("Their 3D assets may fail to load (missing hair, clothes...) or look wrong. Update the mod, or ask its author for a ")
|
||
|
|
TEXT("version built for LifePlay %s. You can disable it in the Mod Manager."), *GameVersion, *FString::Join(Lines, TEXT("\n")), *GameVersion);
|
||
|
|
|
||
|
|
Popup = SNew(SOverlay)
|
||
|
|
+ SOverlay::Slot().HAlign(HAlign_Center).VAlign(VAlign_Center)
|
||
|
|
[
|
||
|
|
SNew(SBox).WidthOverride(760.f)
|
||
|
|
[
|
||
|
|
SNew(SBorder)
|
||
|
|
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
|
||
|
|
.BorderBackgroundColor(FLinearColor(0.02f, 0.02f, 0.03f, 0.94f))
|
||
|
|
.Padding(24.f)
|
||
|
|
[
|
||
|
|
SNew(SVerticalBox)
|
||
|
|
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 0.f, 0.f, 12.f)
|
||
|
|
[
|
||
|
|
SNew(STextBlock)
|
||
|
|
.Text(FText::FromString(TEXT("Mod version warning")))
|
||
|
|
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 22))
|
||
|
|
.ColorAndOpacity(FLinearColor(1.f, 0.75f, 0.2f))
|
||
|
|
]
|
||
|
|
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 0.f, 0.f, 18.f)
|
||
|
|
[
|
||
|
|
SNew(STextBlock)
|
||
|
|
.Text(FText::FromString(Body))
|
||
|
|
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 14))
|
||
|
|
.ColorAndOpacity(FLinearColor(0.92f, 0.92f, 0.92f))
|
||
|
|
.AutoWrapText(true)
|
||
|
|
]
|
||
|
|
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Right)
|
||
|
|
[
|
||
|
|
SNew(SButton)
|
||
|
|
.ContentPadding(FMargin(28.f, 6.f))
|
||
|
|
.OnClicked_Lambda([this]() { CloseWarning(); return FReply::Handled(); })
|
||
|
|
[
|
||
|
|
SNew(STextBlock).Text(FText::FromString(TEXT("OK"))).Font(FCoreStyle::GetDefaultFontStyle("Bold", 16))
|
||
|
|
]
|
||
|
|
]
|
||
|
|
]
|
||
|
|
]
|
||
|
|
];
|
||
|
|
GEngine->GameViewport->AddViewportWidgetContent(Popup.ToSharedRef(), 1000);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPModInfoSubsystem::CloseWarning()
|
||
|
|
{
|
||
|
|
if (Popup.IsValid() && GEngine && GEngine->GameViewport)
|
||
|
|
{
|
||
|
|
GEngine->GameViewport->RemoveViewportWidgetContent(Popup.ToSharedRef());
|
||
|
|
}
|
||
|
|
Popup.Reset();
|
||
|
|
}
|