From 1fe94cfa7a26fe1aec7404eba0c57bdbc3cdd016 Mon Sep 17 00:00:00 2001 From: Adnan Mujkic Date: Sat, 19 Sep 2026 15:46:26 +0200 Subject: [PATCH] Fix for stat forge, setup a test character --- Config/DefaultGame.ini | 13 +++-- Config/DefaultGameplayTags.ini | 1 + Content/Character/BP_Test_Character.uasset | 3 ++ Content/GameData/Npc/DT_Test_Npc.uasset | 3 ++ .../GameData/Npc/DT_Test_Npc_Schedule.uasset | 3 ++ .../0/CP/WRAORZJOI3BZNKX9G5228I.uasset | 3 ++ .../Private/Data/StatForgeRegistry.cpp | 28 +++++++++++ .../StatForge/Public/Data/StatForgeRegistry.h | 47 +++++++++---------- Source/ProjectEleri/Data/StatDefinitions.cpp | 2 - Source/ProjectEleri/Data/StatDefinitions.h | 7 +++ .../GameObjects/BaseCharacter.cpp | 9 +++- 11 files changed, 83 insertions(+), 36 deletions(-) create mode 100644 Content/Character/BP_Test_Character.uasset create mode 100644 Content/GameData/Npc/DT_Test_Npc.uasset create mode 100644 Content/GameData/Npc/DT_Test_Npc_Schedule.uasset create mode 100644 Content/__ExternalActors__/Maps/Persistent_GameWorld/0/CP/WRAORZJOI3BZNKX9G5228I.uasset create mode 100644 Plugins/StatForge/Source/StatForge/Private/Data/StatForgeRegistry.cpp diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index 40563c8b..da121dae 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -170,7 +170,11 @@ bMoviesAreSkippable=False [/Script/ProjectEleri.EleriGameSettings] SpecialItemsMap=(((TagName="Items.Gold"), "ItemDataAsset:DA_Gold"),((TagName="Items.Crystals.Air"), "ItemDataAsset:DA_CrystalAir"),((TagName="Items.Crystals.Earth"), "ItemDataAsset:DA_CrystalEarth"),((TagName="Items.Crystals.Fire"), "ItemDataAsset:DA_CrystalFire"),((TagName="Items.Crystals.Water"), "ItemDataAsset:DA_CrystalWater")) +RemovableStaticMeshClass=/Script/Engine.BlueprintGeneratedClass'/Game/Prefabs/BP_RemovableStaticMeshActor.BP_RemovableStaticMeshActor_C' +ItemSeedCarouselClass=None CameraZoomLevels=(((TagName="Camera.BoomOffset.Default"), (X=-1200.000000,Y=0.000000,Z=50.000000)),((TagName="Camera.BoomOffset.Cave"), (X=-800.000000,Y=0.000000,Z=40.000000)),((TagName="Camera.BoomOffset.Flying"), (X=-1500.000000,Y=0.000000,Z=60.000000))) +ExpIncreaseEffectClass=/Script/Engine.BlueprintGeneratedClass'/Game/Blueprints/GameEffects/GSE_AddExp_Template.GSE_AddExp_Template_C' +MovementDisableEffectClass=/Script/Engine.BlueprintGeneratedClass'/Game/Blueprints/GameEffects/GSE_MovementDisabled.GSE_MovementDisabled_C' +ProfessionExpArray=100.000000 +ProfessionExpArray=500.000000 +ProfessionExpArray=1500.000000 @@ -191,15 +195,10 @@ CameraZoomLevels=(((TagName="Camera.BoomOffset.Default"), (X=-1200.000000,Y=0.00 +ProfessionExpArray=180000.000000 +ProfessionExpArray=208000.000000 +ProfessionExpArray=240000.000000 -ExpIncreaseEffectClass=/Script/Engine.BlueprintGeneratedClass'/Game/Blueprints/GameEffects/GSE_AddExp_Template.GSE_AddExp_Template_C' ++NpcDataAssets=/Game/GameData/Npc/DT_Test_Npc.DT_Test_Npc +NpcSchedules=(((TagName="NpcTags.Test"), "/Game/GameData/Npc/DT_Test_Npc_Schedule.DT_Test_Npc_Schedule")) GlobalSettingsDataSheet=/Game/GameData/CSV/GlobalKeyValueSettings.GlobalKeyValueSettings -ItemSeedCarouselClass=None -RemovableStaticMeshClass=/Script/Engine.BlueprintGeneratedClass'/Game/Prefabs/BP_RemovableStaticMeshActor.BP_RemovableStaticMeshActor_C' -MovementDisableEffectClass=/Script/Engine.BlueprintGeneratedClass'/Game/Blueprints/GameEffects/GSE_MovementDisabled.GSE_MovementDisabled_C' SpriteShadowMaterial=/Game/Materials/Sprite/MI_SpriteShadow_Inst.MI_SpriteShadow_Inst -; NpcSchedules maps an NpcTags.* gameplay tag onto the schedule asset that drives every NPC carrying -; it. Deliberately left empty until the assets are authored, the Project Settings page writes it. -; +NpcSchedules=((TagName="NpcTags.Example"), "/Game/GameData/Npc/NpcSchedules/DA_NpcSchedule_Example.DA_NpcSchedule_Example") [/Script/InteractionSystem.InteractionSettings] DefaultInteractionWidgetClass=/Script/UMG.WidgetBlueprintGeneratedClass'/Game/UI/InteractionUI/WBP_Eleri_Interaction.WBP_Eleri_Interaction_C' diff --git a/Config/DefaultGameplayTags.ini b/Config/DefaultGameplayTags.ini index f7b44405..c1292343 100644 --- a/Config/DefaultGameplayTags.ini +++ b/Config/DefaultGameplayTags.ini @@ -45,6 +45,7 @@ NetIndexFirstBitSegment=16 +GameplayTagList=(Tag="Items.Crystals.Water",DevComment="") +GameplayTagList=(Tag="Items.Gold",DevComment="") +GameplayTagList=(Tag="NpcTags",DevComment="Identity of a scheduled NPC. Every NPC carrying the same tag is driven by the same schedule asset in NpcSchedules.") ++GameplayTagList=(Tag="NpcTags.Test",DevComment="") +GameplayTagList=(Tag="SaveGameTags",DevComment="") +GameplayTagList=(Tag="SaveGameTags.Alchemy.TableLevel",DevComment="") +GameplayTagList=(Tag="SaveGameTags.Bazar.Table.InventorySpace",DevComment="") diff --git a/Content/Character/BP_Test_Character.uasset b/Content/Character/BP_Test_Character.uasset new file mode 100644 index 00000000..40cf6416 --- /dev/null +++ b/Content/Character/BP_Test_Character.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ac073c918db1f7159795cd18647e7a5b96713839cddf737fe9fad722b202a64 +size 38698 diff --git a/Content/GameData/Npc/DT_Test_Npc.uasset b/Content/GameData/Npc/DT_Test_Npc.uasset new file mode 100644 index 00000000..801889d7 --- /dev/null +++ b/Content/GameData/Npc/DT_Test_Npc.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67576591bcba2354a598fd315775f35fcf35c71536118a4ce697170cdaa180b1 +size 1814 diff --git a/Content/GameData/Npc/DT_Test_Npc_Schedule.uasset b/Content/GameData/Npc/DT_Test_Npc_Schedule.uasset new file mode 100644 index 00000000..8eec69fe --- /dev/null +++ b/Content/GameData/Npc/DT_Test_Npc_Schedule.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1a80339730aab9f0924994cba5188456afcccd8fccd823b97d78463926ec251 +size 2333 diff --git a/Content/__ExternalActors__/Maps/Persistent_GameWorld/0/CP/WRAORZJOI3BZNKX9G5228I.uasset b/Content/__ExternalActors__/Maps/Persistent_GameWorld/0/CP/WRAORZJOI3BZNKX9G5228I.uasset new file mode 100644 index 00000000..4a67d0d4 --- /dev/null +++ b/Content/__ExternalActors__/Maps/Persistent_GameWorld/0/CP/WRAORZJOI3BZNKX9G5228I.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b96ddc3f0b5fdeb33fe9524f962acdc6814dd62fb89ad259d3f81469309fb3ae +size 7189 diff --git a/Plugins/StatForge/Source/StatForge/Private/Data/StatForgeRegistry.cpp b/Plugins/StatForge/Source/StatForge/Private/Data/StatForgeRegistry.cpp new file mode 100644 index 00000000..880846ff --- /dev/null +++ b/Plugins/StatForge/Source/StatForge/Private/Data/StatForgeRegistry.cpp @@ -0,0 +1,28 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "Data/StatForgeRegistry.h" + +TArray& FStatForgeRegistry::Get() +{ + // Function-local static on purpose: it is created on first use, so stats declared during static + // initialization (STATFORGE_MAKE_STAT) can never touch an uninitialized registry. + static TArray Registry; + return Registry; +} + +void FStatForgeRegistry::RegisterStat(const FName& StatName) +{ + UE_LOG(LogTemp, Verbose, TEXT("StatForge: Registering %s"), *StatName.ToString()); + + const bool bAlreadyRegistered = Get().ContainsByPredicate([&](const FStatDef& Def) + { + return Def.StatName == StatName; + }); + + if (!bAlreadyRegistered) + { + FStatDef StatDef; + StatDef.StatName = StatName; + Get().Add(StatDef); + } +} diff --git a/Plugins/StatForge/Source/StatForge/Public/Data/StatForgeRegistry.h b/Plugins/StatForge/Source/StatForge/Public/Data/StatForgeRegistry.h index e77c5f3b..35a1234b 100644 --- a/Plugins/StatForge/Source/StatForge/Public/Data/StatForgeRegistry.h +++ b/Plugins/StatForge/Source/StatForge/Public/Data/StatForgeRegistry.h @@ -4,38 +4,35 @@ #include "CoreMinimal.h" #include "StatForgeDefs.h" +/** + * Single, module-external registry of every stat declared with STATFORGE_MAKE_STAT / STATFORGE_MAKE_STAT_NS. + * + * The storage lives in StatForgeRegistry.cpp rather than in an inline body here on purpose: the registry has + * to be ONE instance shared by all module binaries. A function-local static inside an inline header function + * gets hidden (private-extern) visibility on macOS (-fvisibility-inlines-hidden) and is therefore duplicated + * into every module that calls it, which made the game module register its stats into a copy that + * UStatForgeDeveloperSettings::MergeRegistryIntoStatMap() - living in the plugin binary - never read. + */ class STATFORGE_API FStatForgeRegistry { public: - - static TArray& Get() - { - static TArray Registry; - return Registry; - } - - static void RegisterStat(const FName& StatName) - { - UE_LOG(LogTemp, Warning, TEXT("StatForge: Registering %s"), *StatName.ToString()); - - const bool bAlreadyRegistered = Get().ContainsByPredicate([&](const FStatDef& Def) - { - return Def.StatName == StatName; - }); - - if (!bAlreadyRegistered) - { - FStatDef StatDef; - StatDef.StatName = StatName; - Get().Add(StatDef); - } - } + static TArray& Get(); + static void RegisterStat(const FName& StatName); }; +/** + * Linkage specifier for the FName symbols generated by the macros below. Define it before including this + * header (e.g. to PROJECTELERI_API) when the stats are declared in a module other than the one consuming + * them - otherwise those symbols are hidden on macOS and cross-module references fail to link there. + */ +#ifndef STATFORGE_STAT_API +#define STATFORGE_STAT_API +#endif + #define STATFORGE_DECLARE_STAT(StatName)\ namespace FStat\ {\ - extern const FName StatName;\ + extern STATFORGE_STAT_API const FName StatName;\ }\ #define STATFORGE_DECLARE_STAT_NS(Group, StatName)\ @@ -43,7 +40,7 @@ public: {\ namespace Group\ {\ - extern const FName StatName;\ + extern STATFORGE_STAT_API const FName StatName;\ }\ }\ diff --git a/Source/ProjectEleri/Data/StatDefinitions.cpp b/Source/ProjectEleri/Data/StatDefinitions.cpp index 9d8bfd53..739dc087 100644 --- a/Source/ProjectEleri/Data/StatDefinitions.cpp +++ b/Source/ProjectEleri/Data/StatDefinitions.cpp @@ -1,6 +1,4 @@  -#pragma once - #include "StatDefinitions.h" STATFORGE_MAKE_STAT(Health) diff --git a/Source/ProjectEleri/Data/StatDefinitions.h b/Source/ProjectEleri/Data/StatDefinitions.h index 444566db..adad79e9 100644 --- a/Source/ProjectEleri/Data/StatDefinitions.h +++ b/Source/ProjectEleri/Data/StatDefinitions.h @@ -1,6 +1,13 @@  #pragma once +#include "CoreMinimal.h" + +// These FNames are defined in this module but are meant to be usable from other modules/plugins. Without an +// export specifier they end up hidden on macOS, so any cross-module reference would break there while +// Windows kept working. PROJECTELERI_API is supplied by this module's generated Definitions header. +#define STATFORGE_STAT_API PROJECTELERI_API + #include "Data/StatForgeRegistry.h" // -Flat Stats- diff --git a/Source/ProjectEleri/GameObjects/BaseCharacter.cpp b/Source/ProjectEleri/GameObjects/BaseCharacter.cpp index 8741c339..78549927 100644 --- a/Source/ProjectEleri/GameObjects/BaseCharacter.cpp +++ b/Source/ProjectEleri/GameObjects/BaseCharacter.cpp @@ -42,9 +42,14 @@ ABaseCharacter::ABaseCharacter() // Resident wherever the player is, because a schedule is driven by the clock rather than by // proximity, and the whole point is that the NPC is already in the right place by the time the // player arrives. Being frozen in place by the schedule component while out of range is what - // makes that affordable. Assigned as a plain member because SetIsSpatiallyLoaded() only exists in - // editor builds and this has to hold in a packaged one too. + // makes that affordable. + // In UE 5.8 this flag (and SetIsSpatiallyLoaded with it) is editor-only data on AActor, which is + // why it can only be set here and why the guard is needed for a packaged build to compile at + // all. That is enough for the intent to survive cooking: the flag is not read by the packaged + // game, the cook bakes this value into the actor descriptor that World Partition streams from. +#if WITH_EDITORONLY_DATA bIsSpatiallyLoaded = false; +#endif WidgetComponent = CreateDefaultSubobject(TEXT("WidgetComponent")); WidgetComponent->SetupAttachment(RootComponent);