stat definitions & registry

This commit is contained in:
Pedro Gomes 2025-09-24 22:26:24 +01:00
parent 3dcf585d78
commit 00ae50e0da
18 changed files with 510 additions and 11 deletions

View File

@ -1,13 +1,5 @@
fileFormatVersion: 2
guid: bfd03f272fe010b4ba558a3bc456ffeb
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 109565
@ -15,3 +7,11 @@ AssetOrigin:
packageVersion: R 1.5.0
assetPath: Assets/1-Packs/Effects/JMO Assets/Welcome Screen/CFXR_WelcomeScreen.uxml
uploadId: 756876
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData: dontshow
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@ -0,0 +1,113 @@
using UnityEngine;
[CreateAssetMenu(fileName = "New Stat Definition", menuName = "RiftMayhem/Stat Definition")]
public class StatDefinition : ScriptableObject
{
[Header("Identity & Display")]
[SerializeField] private string statKey = "";
[SerializeField] private string displayName = "";
[SerializeField] private string shortName = "";
[TextArea(2, 4)]
[SerializeField] private string description = "";
[SerializeField] private Sprite icon = null; // Nullable by default
[Header("Categorization")]
[SerializeField] private StatCategory category = StatCategory.Damage;
[SerializeField] private bool isPrimary = false;
[SerializeField] private bool showInUI = true;
[SerializeField] private bool showInTooltips = true;
[Header("Item Generation")]
[SerializeField] private bool canRollOnItems = true;
[SerializeField] private float defaultWeight = 1f;
[SerializeField] private bool canBeFlat = true;
[SerializeField] private bool canBePercent = true;
[Header("Value Settings")]
[SerializeField] private float defaultBaseValue = 0f;
[SerializeField] private float minValue = float.MinValue;
[SerializeField] private float maxValue = float.MaxValue;
[SerializeField] private bool roundToInteger = false;
// Public read-only properties
public string StatKey => statKey;
public string DisplayName => displayName;
public string ShortName => shortName;
public string Description => description;
public Sprite Icon => icon; // Can be null
public StatCategory Category => category;
public bool IsPrimary => isPrimary;
public bool ShowInUI => showInUI;
public bool ShowInTooltips => showInTooltips;
public bool CanRollOnItems => canRollOnItems;
public float DefaultWeight => defaultWeight;
public bool CanBeFlat => canBeFlat;
public bool CanBePercent => canBePercent;
public float DefaultBaseValue => defaultBaseValue;
public float MinValue => minValue;
public float MaxValue => maxValue;
public bool RoundToInteger => roundToInteger;
// Validation in the editor
private void OnValidate()
{
// Ensure statKey is not empty and follows naming conventions
if (string.IsNullOrEmpty(statKey))
{
statKey = name.Replace(" ", "").Replace("(", "").Replace(")", "");
}
// Ensure displayName defaults to a readable version of statKey if empty
if (string.IsNullOrEmpty(displayName) && !string.IsNullOrEmpty(statKey))
{
displayName = System.Text.RegularExpressions.Regex.Replace(statKey, "([a-z])([A-Z])", "$1 $2");
}
// Ensure shortName defaults to displayName if empty
if (string.IsNullOrEmpty(shortName) && !string.IsNullOrEmpty(displayName))
{
shortName = displayName.Length > 8 ? displayName.Substring(0, 8) : displayName;
}
// Ensure at least one value type is allowed
if (!canBeFlat && !canBePercent)
{
canBeFlat = true;
}
// Ensure min/max values make sense
if (minValue > maxValue)
{
maxValue = minValue;
}
// Ensure default weight is positive
if (defaultWeight < 0f)
{
defaultWeight = 0f;
}
}
// Utility method to check if an icon exists
public bool HasIcon => icon != null;
// Utility method to get formatted display name with fallback
public string GetDisplayName(bool useShort = false)
{
if (useShort && !string.IsNullOrEmpty(shortName))
return shortName;
return !string.IsNullOrEmpty(displayName) ? displayName : statKey;
}
}
public enum StatCategory
{
Attributes, // Cunning, Flow, Presence
Damage, // AttackDamage, SpellDamage
Offensive, // AttackSpeed, CritChance, CritDamage
Resource, // MaxHealth, HealthRegen, MaxMana, ManaRegen
Defensive, // Armor, MagicResistance, DodgeChance, BlockChance
Utility, // AreaEffectiveness, CooldownReduction, MovementSpeed
Misc // ReputationGainIncrease, GoldCostReduction
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8c1f476e72435e044b602534d90d6ed1

View File

@ -0,0 +1,188 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class StatRegistry : MonoBehaviour
{
[Header("Auto-Discovery Settings")]
[SerializeField] private string statDefinitionsResourcesPath = "StatDefinitions";
[SerializeField] private bool autoRefreshInEditor = true;
[Header("Loaded Stats (Read-Only)")]
[SerializeField] private StatDefinition[] allStats = new StatDefinition[0];
// Singleton instance
private static StatRegistry _instance;
public static StatRegistry Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<StatRegistry>();
if (_instance == null)
{
GameObject registryGO = new GameObject("StatRegistry");
_instance = registryGO.AddComponent<StatRegistry>();
DontDestroyOnLoad(registryGO);
}
}
return _instance;
}
}
// Dictionary for fast lookups
private Dictionary<string, StatDefinition> statLookup;
private bool isInitialized = false;
private void Awake()
{
// Singleton enforcement
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
Initialize();
}
[ContextMenu("Refresh Stat Definitions")]
public void RefreshStatDefinitions()
{
LoadAllStatDefinitions();
BuildLookupDictionary();
Debug.Log($"StatRegistry: Loaded {allStats.Length} stat definitions from Resources/{statDefinitionsResourcesPath}");
}
private void Initialize()
{
if (isInitialized) return;
RefreshStatDefinitions();
isInitialized = true;
}
private void LoadAllStatDefinitions()
{
// Load all StatDefinition assets from the specified Resources folder
StatDefinition[] loadedStats = Resources.LoadAll<StatDefinition>(statDefinitionsResourcesPath);
// Validate for duplicate keys
var duplicateKeys = loadedStats
.GroupBy(stat => stat.StatKey)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
if (duplicateKeys.Any())
{
Debug.LogError($"StatRegistry: Duplicate stat keys found: {string.Join(", ", duplicateKeys)}");
}
// Filter out any with empty keys
allStats = loadedStats
.Where(stat => !string.IsNullOrEmpty(stat.StatKey))
.ToArray();
// Sort alphabetically for consistent ordering
System.Array.Sort(allStats, (a, b) => string.Compare(a.StatKey, b.StatKey, System.StringComparison.OrdinalIgnoreCase));
}
private void BuildLookupDictionary()
{
statLookup = new Dictionary<string, StatDefinition>();
foreach (var stat in allStats)
{
if (!statLookup.ContainsKey(stat.StatKey))
{
statLookup[stat.StatKey] = stat;
}
else
{
Debug.LogWarning($"StatRegistry: Duplicate stat key '{stat.StatKey}' ignored for asset '{stat.name}'");
}
}
}
// Public API methods
public StatDefinition GetStat(string statKey)
{
if (!isInitialized) Initialize();
return statLookup.TryGetValue(statKey, out StatDefinition stat) ? stat : null;
}
public StatDefinition[] GetAllStats()
{
if (!isInitialized) Initialize();
return allStats;
}
public StatDefinition[] GetStatsByCategory(StatCategory category)
{
if (!isInitialized) Initialize();
return allStats.Where(stat => stat.Category == category).ToArray();
}
public StatDefinition[] GetItemRollableStats()
{
if (!isInitialized) Initialize();
return allStats.Where(stat => stat.CanRollOnItems).ToArray();
}
public StatDefinition[] GetPrimaryStats()
{
if (!isInitialized) Initialize();
return allStats.Where(stat => stat.IsPrimary).ToArray();
}
public StatDefinition[] GetUIVisibleStats()
{
if (!isInitialized) Initialize();
return allStats.Where(stat => stat.ShowInUI).ToArray();
}
public bool HasStat(string statKey)
{
if (!isInitialized) Initialize();
return statLookup.ContainsKey(statKey);
}
public int GetStatCount()
{
if (!isInitialized) Initialize();
return allStats.Length;
}
// Debug/Editor methods
public void LogAllStats()
{
if (!isInitialized) Initialize();
Debug.Log($"=== StatRegistry Contents ({allStats.Length} stats) ===");
foreach (var category in System.Enum.GetValues(typeof(StatCategory)).Cast<StatCategory>())
{
var statsInCategory = GetStatsByCategory(category);
if (statsInCategory.Length > 0)
{
Debug.Log($"{category}: {string.Join(", ", statsInCategory.Select(s => s.StatKey))}");
}
}
}
#if UNITY_EDITOR
private void OnValidate()
{
// Auto-refresh in editor when settings change
if (autoRefreshInEditor && Application.isPlaying)
{
RefreshStatDefinitions();
}
}
#endif
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 84452ce979e046640b0ae5b78389b2d8

View File

@ -36,6 +36,7 @@ Material:
disabledShaderPasses:
- MOTIONVECTORS
- DepthOnly
- SHADOWCASTER
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
@ -134,7 +135,7 @@ Material:
- _ZWrite: 0
m_Colors:
- _BaseColor: {r: 0, g: 1, b: 0.2067751, a: 0}
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _Color: {r: 0, g: 1, b: 0.20677507, a: 0}
- _EmissionColor: {r: 5.019608, g: 5.019608, b: 5.019608, a: 1}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
m_BuildTextureStacks: []

View File

@ -36,6 +36,7 @@ Material:
disabledShaderPasses:
- MOTIONVECTORS
- DepthOnly
- SHADOWCASTER
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
@ -134,7 +135,7 @@ Material:
- _ZWrite: 0
m_Colors:
- _BaseColor: {r: 0.59599996, g: 0.59599996, b: 0.59599996, a: 0.42745098}
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _Color: {r: 0.59599996, g: 0.59599996, b: 0.59599996, a: 0.42745098}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
m_BuildTextureStacks: []

View File

@ -15389,6 +15389,7 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 565157831923484480}
- {fileID: 7475116341162077651}
- {fileID: 7624448236951792051}
- {fileID: 2491171992559046387}
@ -15902,6 +15903,56 @@ MonoBehaviour:
OnCastingStateChanged:
m_PersistentCalls:
m_Calls: []
--- !u!1 &7479586115776268845
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 565157831923484480}
- component: {fileID: 5154157730029199972}
m_Layer: 0
m_Name: StatRegistry
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &565157831923484480
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7479586115776268845}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 7475116342638198534}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5154157730029199972
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7479586115776268845}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 84452ce979e046640b0ae5b78389b2d8, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::StatRegistry
statDefinitionsResourcesPath: StatDefinitions
autoRefreshInEditor: 0
allStats:
- {fileID: 11400000, guid: d52b20e3f02ba8946bf37a7470beafd0, type: 2}
- {fileID: 11400000, guid: 63eee8f1286035f4a80356bcfad289b6, type: 2}
- {fileID: 11400000, guid: 599541ff1aaa6c848a732f9a97e5f1c4, type: 2}
--- !u!1 &7508304584244435363
GameObject:
m_ObjectHideFlags: 0

View File

@ -78,7 +78,7 @@ Material:
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Rotation: 0.019999992
- _Rotation: 12.773154
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bc5a44d142e6aed4b889cc72dac39c74
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 86b61c6f3020eec45b6639e6bfe6380c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bf631f3447ed4604faec247ec7645f32
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8c1f476e72435e044b602534d90d6ed1, type: 3}
m_Name: Cunning
m_EditorClassIdentifier: Assembly-CSharp::StatDefinition
statKey: Cunning
displayName: Cunning
shortName: Cunning
description:
icon: {fileID: 21300000, guid: 57094bae48dc077458660a9ea1d13cd3, type: 3}
category: 0
isPrimary: 1
showInUI: 1
showInTooltips: 1
canRollOnItems: 0
defaultWeight: 0
canBeFlat: 1
canBePercent: 0
defaultBaseValue: 0
minValue: 0
maxValue: 3.4028235e+38
roundToInteger: 1

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d52b20e3f02ba8946bf37a7470beafd0
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8c1f476e72435e044b602534d90d6ed1, type: 3}
m_Name: Flow
m_EditorClassIdentifier: Assembly-CSharp::StatDefinition
statKey: Flow
displayName: Flow
shortName: Flow
description:
icon: {fileID: 21300000, guid: 592bf67c59a04224ca949687074cbcfb, type: 3}
category: 0
isPrimary: 1
showInUI: 1
showInTooltips: 1
canRollOnItems: 0
defaultWeight: 0
canBeFlat: 1
canBePercent: 0
defaultBaseValue: 0
minValue: 0
maxValue: 3.4028235e+38
roundToInteger: 1

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 63eee8f1286035f4a80356bcfad289b6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8c1f476e72435e044b602534d90d6ed1, type: 3}
m_Name: Presence
m_EditorClassIdentifier: Assembly-CSharp::StatDefinition
statKey: Presence
displayName: Presence
shortName: Presence
description:
icon: {fileID: 21300000, guid: c08e90ff788ae574d8bc69ab60952a43, type: 3}
category: 0
isPrimary: 1
showInUI: 1
showInTooltips: 1
canRollOnItems: 0
defaultWeight: 0
canBeFlat: 1
canBePercent: 0
defaultBaseValue: 0
minValue: 0
maxValue: 3.4028235e+38
roundToInteger: 1

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 599541ff1aaa6c848a732f9a97e5f1c4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant: