using System; using System.Collections.Generic; using System.IO; using UnityEngine; namespace BrewMonster.Scripts { public static class DebugCmdHistoryStore { private const string ResourcesJsonPathNoExt = "DebugCmdHistory"; private const string PlayerPrefsJsonKey = "DebugCmdHistory_v1"; private const string PlayerPrefsInitedKey = "DebugCmdHistory_inited_v1"; private const int MaxEntries = 200; [Serializable] public class Entry { public int header; public int param; public bool hasParam; public string describe; public long lastUsedUtcTicks; public string KeyString => hasParam ? $"{header}:{param}" : $"{header}:"; } [Serializable] private class EntryListWrapper { public List items = new List(); } public static List Load() { #if UNITY_EDITOR return LoadFromEditorJsonFile(); #else // Runtime: first-run seed from Resources into PlayerPrefs, then keep using PlayerPrefs. if (!IsInited()) { var seed = LoadFromResources(); SaveToPlayerPrefs(seed); SetInited(); return seed; } return LoadFromPlayerPrefs(); #endif } public static void Save(List entries) { #if UNITY_EDITOR SaveToEditorJsonFile(entries); #else SaveToPlayerPrefs(entries); #endif } public static bool Upsert(List entries, int header, int param, bool hasParam, string describe) { if (entries == null) throw new ArgumentNullException(nameof(entries)); describe = (describe ?? string.Empty).Trim(); int idx = FindIndex(entries, header, param, hasParam); if (idx >= 0) { var e = entries[idx]; bool changed = false; // Overwrite describe whenever it differs (supports updating from empty -> non-empty). if (!string.Equals((e.describe ?? string.Empty).Trim(), describe, StringComparison.Ordinal)) { e.describe = describe; changed = true; } e.lastUsedUtcTicks = DateTime.UtcNow.Ticks; if (idx != 0) { entries.RemoveAt(idx); entries.Insert(0, e); changed = true; } else if (!changed) { // still considered "used"; keep order } return changed; } else { var e = new Entry { header = header, param = param, hasParam = hasParam, describe = describe, lastUsedUtcTicks = DateTime.UtcNow.Ticks }; entries.Insert(0, e); if (entries.Count > MaxEntries) entries.RemoveRange(MaxEntries, entries.Count - MaxEntries); return true; } } public static void Clear() { #if UNITY_EDITOR try { var path = GetEditorJsonAbsolutePath(); if (File.Exists(path)) File.Delete(path); } catch (Exception ex) { Debug.LogWarning($"[DebugCmdHistoryStore] Clear (editor json) failed: {ex.Message}"); } #else PlayerPrefs.DeleteKey(PlayerPrefsJsonKey); PlayerPrefs.DeleteKey(PlayerPrefsInitedKey); PlayerPrefs.Save(); #endif } public static bool Remove(List entries, int header, int param, bool hasParam) { if (entries == null) throw new ArgumentNullException(nameof(entries)); int idx = FindIndex(entries, header, param, hasParam); if (idx < 0) return false; entries.RemoveAt(idx); return true; } private static string GetEditorJsonAbsolutePath() { // Keep it under Assets/Resources so it can be included as a TextAsset for runtime seeding. return Path.Combine(Application.dataPath, "Resources", $"{ResourcesJsonPathNoExt}.json"); } private static List LoadFromEditorJsonFile() { try { var path = GetEditorJsonAbsolutePath(); if (!File.Exists(path)) return LoadFromResources(); // fallback if file not created yet var json = File.ReadAllText(path); if (string.IsNullOrWhiteSpace(json)) return new List(); var wrapper = JsonUtility.FromJson(json); if (wrapper == null || wrapper.items == null) return new List(); wrapper.items.RemoveAll(e => e == null); return wrapper.items; } catch { return new List(); } } private static void SaveToEditorJsonFile(List entries) { try { var wrapper = new EntryListWrapper { items = entries ?? new List() }; string json = JsonUtility.ToJson(wrapper, prettyPrint: true); var path = GetEditorJsonAbsolutePath(); var dir = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir); File.WriteAllText(path, json); #if UNITY_EDITOR UnityEditor.AssetDatabase.Refresh(); #endif } catch (Exception ex) { Debug.LogWarning($"[DebugCmdHistoryStore] Save (editor json) failed: {ex.Message}"); } } private static List LoadFromResources() { try { var asset = Resources.Load(ResourcesJsonPathNoExt); if (asset == null || string.IsNullOrWhiteSpace(asset.text)) return new List(); var wrapper = JsonUtility.FromJson(asset.text); if (wrapper == null || wrapper.items == null) return new List(); wrapper.items.RemoveAll(e => e == null); return wrapper.items; } catch { return new List(); } } private static List LoadFromPlayerPrefs() { string json = PlayerPrefs.GetString(PlayerPrefsJsonKey, string.Empty); if (string.IsNullOrWhiteSpace(json)) return new List(); try { var wrapper = JsonUtility.FromJson(json); if (wrapper == null || wrapper.items == null) return new List(); wrapper.items.RemoveAll(e => e == null); return wrapper.items; } catch { return new List(); } } private static void SaveToPlayerPrefs(List entries) { try { var wrapper = new EntryListWrapper { items = entries ?? new List() }; string json = JsonUtility.ToJson(wrapper); PlayerPrefs.SetString(PlayerPrefsJsonKey, json); PlayerPrefs.Save(); } catch (Exception ex) { Debug.LogWarning($"[DebugCmdHistoryStore] Save failed: {ex.Message}"); } } private static bool IsInited() => PlayerPrefs.GetInt(PlayerPrefsInitedKey, 0) == 1; private static void SetInited() { PlayerPrefs.SetInt(PlayerPrefsInitedKey, 1); PlayerPrefs.Save(); } private static int FindIndex(List entries, int header, int param, bool hasParam) { for (int i = 0; i < entries.Count; i++) { var e = entries[i]; if (e == null) continue; if (e.header != header) continue; if (e.hasParam != hasParam) continue; if (hasParam && e.param != param) continue; return i; } return -1; } } }