using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using TMPro; using UnityEngine; using OctoCore.Inputs; #if UNITY_EDITOR using UnityEditor; #endif #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif namespace OctoCore { /// /// Global settings for the OctoCore framework. /// Configure save system, input mappings, localization, and UI settings. /// [CreateAssetMenu(fileName = "OctoSettings", menuName = "Octo/Settings Global", order = 0)] public class OctoSettings : ScriptableObject { /// /// Save system configuration. /// [System.Serializable] public class SaveSettings { /// /// All boolean save data keys in the project. /// public global::OctoCore.Save.Data.OctoDataBool[] Booleans; /// /// All integer save data keys in the project. /// public global::OctoCore.Save.Data.OctoDataInt[] Integers; /// /// All float save data keys in the project. /// public global::OctoCore.Save.Data.OctoDataFloat[] Floats; /// /// All string save data keys in the project. /// public global::OctoCore.Save.Data.OctoDataString[] Strings; /// /// All vector save data keys in the project. /// public global::OctoCore.Save.Data.OctoDataVector[] Vectors; } /// /// Input mapping configuration. /// [System.Serializable] public class MappingSettings { [System.Serializable] public class Icon { public bool UseName = true; public Sprite Sprite; } [System.Serializable] public class ActionData { [HideInInspector] public string Name; [HideInInspector] public string MapName; public OctoActions Action; #if ENABLE_INPUT_SYSTEM [Tooltip("Reference to the InputAction from the asset")] public InputActionReference Reference; #endif public Icon Icon; public ActionData(OctoActions action, string name, string mapName) { Action = action; Name = name; MapName = mapName; } } /// /// Reference Input Action Asset. /// #if ENABLE_INPUT_SYSTEM [Tooltip("Assign your Input Action Asset here")] public InputActionAsset InputActionAsset; #endif /// /// Generated Actions. /// [Tooltip("Actions (generated automatically)")] public List Actions = new List(); /// /// Get action map name for a specific action. /// public string GetActionMapName(OctoActions action) { foreach (var actionData in Actions) { if (actionData.Action == action) return actionData.MapName; } return null; } #if ENABLE_INPUT_SYSTEM && UNITY_EDITOR /// /// Get InputActionReference for a specific action. /// public InputActionReference GetReference(OctoActions action) { foreach (var actionData in Actions) { if (actionData.Action == action) return actionData.Reference; } return null; } #endif } /// /// Language configuration. /// [System.Serializable] public class LanguagesSettings { /// /// Font override settings for specific languages. /// [System.Serializable] public class Override { [HideInInspector] public string Name; /// /// The language this override applies to. /// public Localization.OctoLanguages Language; /// /// Custom font for this language. /// public TMP_FontAsset Font; /// /// Size multiplier for this language's text. /// public float SizeMultiplier = 1; } /// /// Default language to use when no preference is set. /// public Localization.OctoLanguages Default; /// /// Save data key for written language preference. /// public global::OctoCore.Save.Data.OctoDataString SaveWritten; /// /// Save data key for audio language preference. /// public global::OctoCore.Save.Data.OctoDataString SaveAudio; /// /// Font overrides for specific languages. /// public Override[] Overrides; } /// /// Configuration for UI scaling. /// [System.Serializable] public class UiSettings { [System.Serializable] public class Sprites { public Sprite DefaultButton; } /// /// Reference screen size for UI scaling calculations. /// public Vector2Int ReferenceSize = new Vector2Int(1920, 1080); /// /// Reference screen size for UI scaling calculations. /// public Sprites IconsBackground; } /// /// Scene management configuration. /// [System.Serializable] public class ScenesSettings { /// /// Fade transition settings. /// [System.Serializable] public class FadeTransitionSettings { /// /// Fade in duration in seconds. /// [Tooltip("Fade in duration")] public float FadeInDuration = 0.5f; /// /// Fade out duration in seconds. /// [Tooltip("Fade out duration")] public float FadeOutDuration = 0.5f; /// /// Fade color. /// [Tooltip("Fade color")] public Color FadeColor = Color.black; } /// /// All scene data in the project. /// [Tooltip("All scene data in the project")] public Navigation.Data.OctoSceneData[] SceneDatas; /// /// Use Addressables for scene loading. /// Requires OCTO_ADDRESSABLES scripting define symbol. /// [Tooltip("Use Addressables for scene loading (requires OCTO_ADDRESSABLES define)")] public bool UseAddressables = false; /// /// Global fade transition settings. /// [Tooltip("Global fade transition settings")] public FadeTransitionSettings FadeSettings = new FadeTransitionSettings(); } [System.Serializable] public class ApplicationSettings { [Tooltip("Target frame rate (-1 = unlimited)")] public int TargetFrameRate = 120; [Tooltip("0 = no vsync, 1 = every vblank, 2 = every second vblank")] public int VSyncCount = 0; } public ApplicationSettings Application; /// /// Save system settings. /// [OctoReadOnly] public SaveSettings Save; /// /// Input mapping settings. /// public MappingSettings Mapping; /// /// Language system settings. /// public LanguagesSettings Languages; /// /// Scene management settings. /// public ScenesSettings Scenes; /// /// UI settings. /// public UiSettings Ui; #region DATAS SET #if UNITY_EDITOR void OnValidate() { Init(); } /// /// Initializes and validates settings in the Unity Editor. /// public void Init() { // Update language override names foreach (LanguagesSettings.Override langOverride in Languages.Overrides) { langOverride.Name = langOverride.Language.ToString(); } } /// /// Scans the project for all OctoData assets and populates the Save settings. /// public void GetAllDatas() { List booleans = new(); List integers = new(); List floats = new(); List strings = new(); List vectors = new(); string[] guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(global::OctoCore.Save.Data.OctoData).Name); for (int i = 0; i < guids.Length; i++) { string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]); global::OctoCore.Save.Data.OctoData data = (global::OctoCore.Save.Data.OctoData)UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, typeof(global::OctoCore.Save.Data.OctoData)); if (data) { data.Id = data.name; UnityEditor.EditorUtility.SetDirty(data); if (data is global::OctoCore.Save.Data.OctoDataBool) booleans.Add((global::OctoCore.Save.Data.OctoDataBool)data); if (data is global::OctoCore.Save.Data.OctoDataInt) integers.Add((global::OctoCore.Save.Data.OctoDataInt)data); if (data is global::OctoCore.Save.Data.OctoDataFloat) floats.Add((global::OctoCore.Save.Data.OctoDataFloat)data); if (data is global::OctoCore.Save.Data.OctoDataString) strings.Add((global::OctoCore.Save.Data.OctoDataString)data); if (data is global::OctoCore.Save.Data.OctoDataVector) vectors.Add((global::OctoCore.Save.Data.OctoDataVector)data); } } Save.Booleans = booleans.ToArray(); Save.Integers = integers.ToArray(); Save.Floats = floats.ToArray(); Save.Strings = strings.ToArray(); Save.Vectors = vectors.ToArray(); UnityEditor.EditorUtility.SetDirty(this); UnityEditor.AssetDatabase.SaveAssets(); } /// /// Scans the project for all OctoSceneData assets and populates the Scenes settings. /// public void GetAllSceneDatas() { List sceneDatas = new(); string[] guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(Navigation.Data.OctoSceneData).Name); for (int i = 0; i < guids.Length; i++) { string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]); Navigation.Data.OctoSceneData sceneData = (Navigation.Data.OctoSceneData)UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, typeof(Navigation.Data.OctoSceneData)); if (sceneData) { sceneData.Id = sceneData.name; UnityEditor.EditorUtility.SetDirty(sceneData); sceneDatas.Add(sceneData); } } Scenes.SceneDatas = sceneDatas.ToArray(); UnityEditor.EditorUtility.SetDirty(this); UnityEditor.AssetDatabase.SaveAssets(); Debug.Log("[OctoSettings] Found " + sceneDatas.Count + " Scene Datas"); } #endif #if ENABLE_INPUT_SYSTEM && UNITY_EDITOR private static string GetActionsFilePath() { // Find OctoInputs.cs to locate the package string[] guids = AssetDatabase.FindAssets("OctoInputs t:script"); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); if (path.Contains("/Inputs/OctoInputs.cs")) { // Found it! Same directory string directory = Path.GetDirectoryName(path); return Path.Combine(directory, "OctoActions.cs").Replace("\\", "/"); } } // Fallback return "Assets/OctoCore/Generated/OctoActions.cs"; } private const string ActionsBackupFile = "Assets/OctoCore/Editor/OctoActions.json"; public void GenerateInputs() { Debug.Log("[OctoCore] GenerateInputs() called"); if (Mapping.InputActionAsset == null) { EditorUtility.DisplayDialog("Error", "Please assign an Input Actions asset first!", "OK"); return; } string actionsFile = GetActionsFilePath(); Debug.Log($"[OctoCore] Input Action Asset: {Mapping.InputActionAsset.name}"); Debug.Log($"[OctoCore] Target file: {actionsFile}"); // Ensure directory exists string directory = Path.GetDirectoryName(actionsFile); if (!Directory.Exists(directory)) { Debug.Log($"[OctoCore] Creating directory: {directory}"); Directory.CreateDirectory(directory); } Debug.Log("[OctoCore] Calling GenerateActionsEnum..."); // Generate enum file GenerateActionsEnum(Mapping.InputActionAsset, actionsFile); Debug.Log("[OctoCore] GenerateActionsEnum completed"); // Refresh to compile new enums AssetDatabase.Refresh(); EditorUtility.DisplayProgressBar("Generating Inputs", "Waiting for compilation...", 0.5f); // Wait a bit longer for compilation with multiple delays EditorApplication.delayCall += () => { EditorApplication.delayCall += () => { EditorApplication.delayCall += () => { EditorUtility.ClearProgressBar(); // Populate mappings after compilation PopulateMappings(); // Mark data as dirty to save changes EditorUtility.SetDirty(this); EditorUtility.DisplayDialog( "Success", $"Input enums generated successfully!\n\n" + $"Generated in: {actionsFile}\n" + $"Backup saved in: {ActionsBackupFile}\n" + $"Total Actions: {CountTotalActions(Mapping.InputActionAsset)}\n\n" + $"Note: Backup is automatically restored after package updates!", "OK" ); }; }; }; } public void PopulateMappings() { Mapping.Actions.Clear(); foreach (var map in Mapping.InputActionAsset.actionMaps) { string mapName = CleanName(map.name); foreach (var action in map.actions) { string actionName = CleanName(action.name); string fullName = $"{mapName}_{actionName}"; if (!System.Enum.TryParse(fullName, out var actionEnum)) { Debug.LogWarning($"[OctoSettings] Could not parse action: {fullName}"); continue; } // Create action data with map name var actionData = new MappingSettings.ActionData(actionEnum, action.name, map.name); // Find InputActionReference in the asset's sub-assets actionData.Reference = FindInputActionReferenceInAsset(Mapping.InputActionAsset, action); if (actionData.Reference == null) { Debug.LogWarning($"[OctoSettings] No InputActionReference found for {map.name}/{action.name}"); } Mapping.Actions.Add(actionData); } } EditorUtility.SetDirty(this); AssetDatabase.SaveAssets(); } private InputActionReference FindInputActionReferenceInAsset(InputActionAsset asset, InputAction action) { // Get the asset path string assetPath = AssetDatabase.GetAssetPath(asset); // Load all sub-assets (InputActionReference are stored as sub-assets) var allAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath); foreach (var subAsset in allAssets) { if (subAsset is InputActionReference reference) { if (reference.action == action) { return reference; } } } return null; } public int CountTotalActions(InputActionAsset asset) { int count = 0; foreach (var map in asset.actionMaps) { count += map.actions.Count; } return count; } private void GenerateActionsEnum(InputActionAsset inputActions, string outputFile) { Debug.Log($"[OctoCore] GenerateActionsEnum started for: {inputActions.name}"); StringBuilder sb = new StringBuilder(); List backupData = new List(); sb.AppendLine("// ═══════════════════════════════════════════════════════════════════════════"); sb.AppendLine("// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY"); sb.AppendLine("// ═══════════════════════════════════════════════════════════════════════════"); sb.AppendLine("// Generated by OctoSettings"); sb.AppendLine("// Backup saved in: Assets/OctoCore/Generated/OctoActions.json"); sb.AppendLine("// To regenerate: OctoSettings > Mapping > Click 'Generate Inputs'"); sb.AppendLine($"// Generated from: {inputActions.name}"); sb.AppendLine($"// Generated on: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"); sb.AppendLine("// ═══════════════════════════════════════════════════════════════════════════"); sb.AppendLine(); sb.AppendLine("namespace OctoCore.Inputs"); sb.AppendLine("{"); sb.AppendLine(" /// "); sb.AppendLine(" /// Actions from Unity Input Actions."); sb.AppendLine($" /// Generated from: {inputActions.name}"); sb.AppendLine(" /// Format: ActionMap_ActionName"); sb.AppendLine(" /// Values are stable hashes to prevent serialization issues when regenerating."); sb.AppendLine(" /// "); sb.AppendLine(" public enum OctoActions"); sb.AppendLine(" {"); bool first = true; foreach (var map in inputActions.actionMaps) { string mapName = CleanName(map.name); foreach (var action in map.actions) { string actionName = CleanName(action.name); string fullName = $"{mapName}_{actionName}"; // Generate stable hash from name int hash = GetStableHash(fullName); // Save to backup data backupData.Add(new ActionBackupData { Name = fullName, Hash = hash, MapName = map.name, ActionName = action.name }); if (!first) sb.AppendLine(","); sb.AppendLine($" /// {map.name} > {action.name}"); sb.Append($" {fullName} = {hash}"); first = false; } } sb.AppendLine(); sb.AppendLine(" }"); sb.AppendLine("}"); // Write enum file File.WriteAllText(outputFile, sb.ToString()); Debug.Log($"[OctoCore] OctoActions.cs written to: {outputFile}"); Debug.Log($"[OctoCore] File contains {backupData.Count} actions"); // Force Unity to see the change AssetDatabase.ImportAsset(outputFile, ImportAssetOptions.ForceUpdate); // Save backup JSON SaveActionsBackup(backupData); } [System.Serializable] private class ActionBackupData { public string Name; public int Hash; public string MapName; public string ActionName; } [System.Serializable] private class ActionsBackup { public string GeneratedFrom; public string GeneratedOn; public List Actions; } private void SaveActionsBackup(List actions) { // Ensure directory exists string directory = Path.GetDirectoryName(ActionsBackupFile); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var backup = new ActionsBackup { GeneratedFrom = Mapping.InputActionAsset.name, GeneratedOn = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), Actions = actions }; string json = JsonUtility.ToJson(backup, true); File.WriteAllText(ActionsBackupFile, json); Debug.Log($"[OctoCore] Actions backup saved to: {ActionsBackupFile}"); } /// /// Generate a stable hash from a string (same string always gives same hash). /// private int GetStableHash(string str) { unchecked { int hash1 = 5381; int hash2 = hash1; for (int i = 0; i < str.Length && str[i] != '\0'; i += 2) { hash1 = ((hash1 << 5) + hash1) ^ str[i]; if (i == str.Length - 1 || str[i + 1] == '\0') break; hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; } return hash1 + (hash2 * 1566083941); } } private string CleanName(string name) { name = name.Replace(" ", "_").Replace("-", "_"); name = new string(name.Where(c => char.IsLetterOrDigit(c) || c == '_').ToArray()); if (char.IsDigit(name[0])) name = "_" + name; return name; } #endif #endregion } }