Skip to content

Commit

Permalink
Add ModModule validation
Browse files Browse the repository at this point in the history
  • Loading branch information
mircearoata committed Jul 18, 2023
1 parent fee7530 commit 401983a
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 2 deletions.
96 changes: 96 additions & 0 deletions Mods/SML/Source/SML/Private/Module/GameWorldModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,102 @@
#include "Command/ChatCommandLibrary.h"
#include "Registry/ModContentRegistry.h"
#include "Subsystem/SubsystemActorManager.h"
#include "Unlocks/FGUnlockRecipe.h"

EDataValidationResult ValidateRecipe(TSubclassOf<UFGRecipe> Recipe, TArray<FText>& ValidationErrors) {
if (Recipe == nullptr) {
ValidationErrors.Add(NSLOCTEXT("GameWorldModule", "NullRecipe", "Null recipe"));
return EDataValidationResult::Invalid;
}
EDataValidationResult ValidationResult = EDataValidationResult::Valid;
TArray<FItemAmount> AllReferencedItems;
AllReferencedItems.Append(UFGRecipe::GetIngredients(Recipe));
AllReferencedItems.Append(UFGRecipe::GetProducts(Recipe));

for (const FItemAmount& ItemAmount : AllReferencedItems) {
if (ItemAmount.ItemClass == nullptr) {
ValidationErrors.Add(NSLOCTEXT("GameWorldModule", "NullSchematicItem", "Null recipe item"));
ValidationResult = EDataValidationResult::Invalid;
}
}

return ValidationResult;
}

EDataValidationResult ValidateSchematic(TSubclassOf<UFGSchematic> Schematic, TArray<FText>& ValidationErrors) {
if (Schematic == nullptr) {
ValidationErrors.Add(NSLOCTEXT("GameWorldModule", "NullSchematic", "Null schematic"));
return EDataValidationResult::Invalid;
}
EDataValidationResult ValidationResult = EDataValidationResult::Valid;
TArray<TSubclassOf<UFGRecipe>> Recipes;
AModContentRegistry::ExtractRecipesFromSchematic(Schematic, Recipes);
for (TSubclassOf<UFGRecipe> Recipe : Recipes) {
TArray<FText> RecipeValidationErrors;
ValidationResult = FMath::Min(ValidationResult, ValidateRecipe(Recipe, RecipeValidationErrors));
for (const FText& Error : RecipeValidationErrors) {
ValidationErrors.Add(
FText::Format(
NSLOCTEXT("GameWorldModule", "RecipeValidationError", "Recipe {0}: {1}"),
FText::FromString(Recipe ? Recipe->GetPackage()->GetPathName() : TEXT("")),
Error));
}
}
return ValidationResult;
}

EDataValidationResult ValidateResearchTree(TSubclassOf<UFGResearchTree> ResearchTree, TArray<FText>& ValidationErrors) {
if (ResearchTree == nullptr) {
ValidationErrors.Add(NSLOCTEXT("GameWorldModule", "NullResearchTree", "Null research tree"));
return EDataValidationResult::Invalid;
}
EDataValidationResult ValidationResult = EDataValidationResult::Valid;
TArray<TSubclassOf<UFGSchematic>> Schematics;
AModContentRegistry::ExtractSchematicsFromResearchTree(ResearchTree, Schematics);
for (TSubclassOf<UFGSchematic> Schematic : Schematics) {
TArray<FText> SchematicValidationErrors;
ValidationResult = FMath::Min(ValidationResult, ValidateSchematic(Schematic, SchematicValidationErrors));
for (const FText& Error : SchematicValidationErrors) {
ValidationErrors.Add(
FText::Format(
NSLOCTEXT("GameWorldModule", "SchematicValidationError", "Schematic {0}: {1}"),
FText::FromString(Schematic ? Schematic->GetPackage()->GetPathName() : TEXT("")),
Error));
}
}
return ValidationResult;
}

EDataValidationResult UGameWorldModule::IsDataValid(TArray<FText>& ValidationErrors) {
EDataValidationResult ValidationResult = EDataValidationResult::Valid;

//Check that we do not have any null schematics, research trees, recipes or items
for (const TSubclassOf<UFGSchematic>& Schematic : mSchematics) {
TArray<FText> SchematicValidationErrors;
ValidationResult = FMath::Min(ValidationResult, ValidateSchematic(Schematic, SchematicValidationErrors));
for (const FText& Error : SchematicValidationErrors) {
ValidationErrors.Add(
FText::Format(
NSLOCTEXT("GameWorldModule", "SchematicValidationError", "Schematic {0}: {1}"),
FText::FromString(Schematic ? Schematic->GetPackage()->GetPathName() : TEXT("")),
Error));
}
}
for (const TSubclassOf<UFGResearchTree>& ResearchTree : mResearchTrees) {
TArray<FText> ResearchTreeValidationErrors;
ValidationResult = FMath::Min(ValidationResult, ValidateResearchTree(ResearchTree, ResearchTreeValidationErrors));
for (const FText& Error : ResearchTreeValidationErrors) {
ValidationErrors.Add(
FText::Format(
NSLOCTEXT("GameWorldModule", "ResearchTreeValidationError", "Research tree {0}: {1}"),
FText::FromString(ResearchTree ? ResearchTree->GetPackage()->GetPathName() : TEXT("")),
Error));
}
}

ValidationResult = FMath::Min(ValidationResult, Super::IsDataValid(ValidationErrors));
return ValidationResult;
}

void UGameWorldModule::DispatchLifecycleEvent(ELifecyclePhase Phase) {
//Register default content before calling blueprint event logic
Expand Down
54 changes: 54 additions & 0 deletions Mods/SML/Source/SML/Private/Module/ModModule.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
#include "Module/ModModule.h"

#include "Kismet/BlueprintAssetHelperLibrary.h"
#include "ModLoading/PluginModuleLoader.h"
#include "Module/GameInstanceModule.h"
#include "Module/GameWorldModule.h"
#include "Module/MenuWorldModule.h"

EDataValidationResult UModModule::IsDataValid(TArray<FText>& ValidationErrors) {
EDataValidationResult ValidationResult = EDataValidationResult::Valid;

//Check that we have exactly one module of this type marked as root
UClass* ModuleClass = nullptr;
if (IsA(UGameInstanceModule::StaticClass())) {
ModuleClass = UGameInstanceModule::StaticClass();
} else if (IsA(UGameWorldModule::StaticClass())) {
ModuleClass = UGameWorldModule::StaticClass();
} else if (IsA(UMenuWorldModule::StaticClass())) {
ModuleClass = UMenuWorldModule::StaticClass();
}

if (!ModuleClass) {
ValidationErrors.Add(FText::Format(NSLOCTEXT("ModModule", "RootModuleInvalidType", "Root module {0} has invalid type"), FText::FromString(GetName())));
ValidationResult = EDataValidationResult::Invalid;
} else {
const FString OwnerPluginName = UBlueprintAssetHelperLibrary::FindPluginNameByObjectPath(GetPathName());

//Discover modules by scanning classpath
const TArray<FDiscoveredModule> DiscoveredModules = FPluginModuleLoader::FindRootModulesOfType(ModuleClass);

TArray<FDiscoveredModule> ModulesInPlugin;
for (const FDiscoveredModule& Module : DiscoveredModules) {
if (Module.OwnerPluginName == OwnerPluginName) {
ModulesInPlugin.Add(Module);
}
}

if (ModulesInPlugin.Num() > 1) {
TArray<FText> ModuleNames;
for (const FDiscoveredModule& Module : ModulesInPlugin) {
ModuleNames.Add(FText::FromString(Module.ModuleClass->GetPackage()->GetPathName()));
}
ValidationErrors.Add(FText::Format(NSLOCTEXT("ModModule", "RootModuleMultiple", "Multiple root modules of the same type in the same mod: {0}"), FText::Join(INVTEXT(", "), ModuleNames)));
ValidationResult = EDataValidationResult::Invalid;
}

if (ModulesInPlugin.Num() == 0) {
ValidationErrors.Add(NSLOCTEXT("ModModule", "RootModuleMultiple", "No module of this type marked as root in this mod"));
ValidationResult = EDataValidationResult::Invalid;
}
}

ValidationResult = FMath::Min(ValidationResult, Super::IsDataValid(ValidationErrors));
return ValidationResult;
}

UModModule* UModModule::SpawnChildModule(FName ModuleName, TSoftClassPtr<UModModule> ModuleClass) {
//Make sure we are not attempting to spawn same module twice
checkf(!ChildModules.Contains(ModuleName), TEXT("Module already loaded: %s"), *ModuleName.ToString());
Expand Down
4 changes: 2 additions & 2 deletions Mods/SML/Source/SML/Private/Registry/ModContentRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static bool GIsRegisteringVanillaContent = false;
}


void ExtractRecipesFromSchematic(TSubclassOf<UFGSchematic> Schematic, TArray<TSubclassOf<UFGRecipe>>& OutRecipes) {
void AModContentRegistry::ExtractRecipesFromSchematic(TSubclassOf<UFGSchematic> Schematic, TArray<TSubclassOf<UFGRecipe>>& OutRecipes) {
const TArray<UFGUnlock*> Unlocks = UFGSchematic::GetUnlocks(Schematic);
for (UFGUnlock* Unlock : Unlocks) {
if (UFGUnlockRecipe* UnlockRecipe = Cast<UFGUnlockRecipe>(Unlock)) {
Expand All @@ -44,7 +44,7 @@ void ExtractRecipesFromSchematic(TSubclassOf<UFGSchematic> Schematic, TArray<TSu
}
}

void ExtractSchematicsFromResearchTree(TSubclassOf<UFGResearchTree> ResearchTree, TArray<TSubclassOf<UFGSchematic>>& OutSchematics) {
void AModContentRegistry::ExtractSchematicsFromResearchTree(TSubclassOf<UFGResearchTree> ResearchTree, TArray<TSubclassOf<UFGSchematic>>& OutSchematics) {

static FStructProperty* NodeDataStructProperty = NULL;
static FClassProperty* SchematicStructProperty = NULL;
Expand Down
3 changes: 3 additions & 0 deletions Mods/SML/Source/SML/Public/Module/GameWorldModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ UCLASS(Blueprintable)
class SML_API UGameWorldModule : public UWorldModule {
GENERATED_BODY()
public:
#if WITH_EDITOR
virtual EDataValidationResult IsDataValid(TArray<FText>& ValidationErrors) override;
#endif
/**
* List of schematics that will be automatically registered
* by the SML during the loading phase
Expand Down
4 changes: 4 additions & 0 deletions Mods/SML/Source/SML/Public/Module/ModModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ enum class ELifecyclePhase : uint8 {
UCLASS(Blueprintable)
class SML_API UModModule : public UObject {
GENERATED_BODY()
public:
#if WITH_EDITOR
virtual EDataValidationResult IsDataValid(TArray<FText>& ValidationErrors) override;
#endif
private:
/** Private field holding owner mod reference, accessible directly only by mod loader */
FName OwnerModReference;
Expand Down
3 changes: 3 additions & 0 deletions Mods/SML/Source/SML/Public/Registry/ModContentRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ class SML_API AModContentRegistry : public AModSubsystem {
/** Called when research tree is registered into the registry */
UPROPERTY(BlueprintAssignable)
FOnResearchTreeRegistered OnResearchTreeRegistered;

static void ExtractRecipesFromSchematic(TSubclassOf<UFGSchematic> Schematic, TArray<TSubclassOf<UFGRecipe>>& OutRecipes);
static void ExtractSchematicsFromResearchTree(TSubclassOf<UFGResearchTree> ResearchTree, TArray<TSubclassOf<UFGSchematic>>& OutSchematics);
protected:
/** Called early when subsystem is spawned */
virtual void Init() override;
Expand Down

0 comments on commit 401983a

Please sign in to comment.