using System; using System.Collections.Generic; using System.Linq; using BriarQueen.Data.Identifiers; using BriarQueen.Data.IO.Saves; using BriarQueen.Framework.Coordinators.Events; using BriarQueen.Framework.Events.Gameplay; using BriarQueen.Framework.Events.Save; using BriarQueen.Framework.Events.UI; using BriarQueen.Framework.Managers.Audio; using BriarQueen.Framework.Managers.IO; using BriarQueen.Framework.Managers.Player.Data; using BriarQueen.Framework.Managers.Player.Data.Codex; using BriarQueen.Framework.Managers.Player.Data.Inventory; using BriarQueen.Framework.Managers.Player.Data.Tools; using BriarQueen.Framework.Registries; using BriarQueen.Framework.Services.Settings; using BriarQueen.Framework.Services.Tutorials; using UnityEngine; using VContainer; namespace BriarQueen.Framework.Managers.Player { /// /// Owns player state (inventory + codex + tools) and keeps it in sync with save data. /// public class PlayerManager : IDisposable, IManager { private readonly CodexRegistry _codexRegistry; private readonly EventCoordinator _eventCoordinator; private readonly ItemRegistry _itemRegistry; private readonly SaveManager _saveManager; private readonly AudioManager _audioManager; private readonly TutorialService _tutorialService; private readonly SettingsService _settingsService; private Backpack _backpack; private Codex _codex; private Toolbelt _toolbelt; public bool Initialized { get; private set; } [Inject] public PlayerManager( SaveManager saveManager, ItemRegistry itemRegistry, CodexRegistry codexRegistry, EventCoordinator eventCoordinator, TutorialService tutorialService, AudioManager audioManager, SettingsService settingsService) { _saveManager = saveManager; _itemRegistry = itemRegistry; _codexRegistry = codexRegistry; _eventCoordinator = eventCoordinator; _tutorialService = tutorialService; _audioManager = audioManager; _settingsService = settingsService; } public void Initialize() { if (Initialized) return; _saveManager.OnSaveGameLoaded += LoadFromSave; _saveManager.OnSaveRequested += UpdateSaveGame; _backpack ??= new Backpack(); _codex ??= new Codex(); EnsureToolbelt(); if (_saveManager.IsGameLoaded && _saveManager.CurrentSave != null) LoadFromSave(_saveManager.CurrentSave); Initialized = true; } public void Dispose() { if (!Initialized) return; _saveManager.OnSaveGameLoaded -= LoadFromSave; _saveManager.OnSaveRequested -= UpdateSaveGame; _toolbelt?.Dispose(); _toolbelt = null; Initialized = false; } public IReadOnlyList GetInventoryItems() { return _backpack?.Items ?? Array.Empty(); } public IReadOnlyList GetAllCollectedItems() { var collectedItems = new List(); var saveCollected = _saveManager.CurrentSave?.CollectedItems; if (saveCollected == null) return collectedItems; foreach (var item in saveCollected) { var template = _itemRegistry.FindItemTemplateByID(item.UniqueIdentifier); if (template != null) collectedItems.Add(template); } return collectedItems; } public IReadOnlyList GetDiscoveredCodexEntries() { return _codex?.Entries ?? Array.Empty(); } public IEnumerable GetDiscoveredCodexEntriesByType(CodexType codexType) { return _codex?.GetEntriesByType(codexType) ?? Enumerable.Empty(); } public bool HasCodexEntry(string uniqueIdentifier) { return _codex != null && _codex.HasEntry(uniqueIdentifier); } public bool HasCodexEntry(CodexEntrySo entry) { return _codex != null && _codex.HasEntry(entry); } public IReadOnlyDictionary GetTools() { EnsureToolbelt(); return _toolbelt.UnlockedTools; } public bool HasAccessToTool(ToolID id) { return _toolbelt != null && _toolbelt.HasAccess(id); } #region Inventory / Selection public void CollectItem(string uniqueIdentifier) { var item = _itemRegistry.FindItemTemplateByID(uniqueIdentifier); if (item == null) return; CollectItem(item); } public void CollectItem(ItemDataSo item) { if (item == null) return; _backpack ??= new Backpack(); _backpack.AddItem(item); var save = _saveManager.CurrentSave; if (save != null) { save.CollectedItems ??= new List(); if (save.CollectedItems.All(x => x.UniqueIdentifier != item.UniqueID)) { save.CollectedItems.Add(new ItemSaveData { UniqueIdentifier = item.UniqueID }); } } _audioManager.Play(AudioNameIdentifiers.Get(SFXKey.ItemCollected)); _eventCoordinator.PublishImmediate(new RequestGameSaveEvent()); _eventCoordinator.Publish(new InventoryChangedEvent()); } public void RemoveItem(string uniqueIdentifier) { var item = _backpack?.Items.FirstOrDefault(x => x.UniqueID == uniqueIdentifier); if (item == null) return; RemoveItem(item); } public void RemoveItem(ItemDataSo item) { if (item == null || _backpack == null) return; _backpack.RemoveItem(item); _eventCoordinator.PublishImmediate(new RequestGameSaveEvent()); _eventCoordinator.Publish(new InventoryChangedEvent()); } #endregion #region Codex public void UnlockCodexEntry(string uniqueIdentifier) { var entry = _codexRegistry.FindEntryByID(uniqueIdentifier); if (entry == null) return; UnlockCodexEntry(entry); } public void UnlockCodexEntry(CodexEntrySo entry) { if (entry == null) return; _codex ??= new Codex(); if (_codex.HasEntry(entry)) return; _codex.AddEntry(entry); var save = _saveManager.CurrentSave; if (save != null) { save.DiscoveredCodexEntries ??= new List(); if (save.DiscoveredCodexEntries.All(x => x.UniqueIdentifier != entry.UniqueID)) { save.DiscoveredCodexEntries.Add(new CodexSaveData { UniqueIdentifier = entry.UniqueID }); } } _tutorialService.DisplayTutorial(TutorialPopupID.Codex); _eventCoordinator.PublishImmediate(new RequestGameSaveEvent()); _eventCoordinator.Publish(new CodexChangedEvent(entry.EntryType)); } #endregion #region Tools public bool CanUseTool(ToolID id) { if(_settingsService.Game.AutoUseTools) return HasAccessToTool(id); return GetEquippedTool() == id; } public ToolID GetEquippedTool() { EnsureToolbelt(); return _toolbelt.CurrentTool; } public void UnlockTool(ToolID id) { EnsureToolbelt(); if (_toolbelt.HasAccess(id)) return; _toolbelt.Unlock(id); Debug.Log($"Tool {id} has been unlocked"); var save = _saveManager.CurrentSave; if (save != null) { save.Tools ??= new Dictionary(); save.Tools[id] = true; } Debug.Log($"Tool {id} access after unlock = {_toolbelt.HasAccess(id)}"); _eventCoordinator.Publish(new ToolbeltChangedEvent(id, false)); _eventCoordinator.PublishImmediate(new RequestGameSaveEvent()); } public void LockTool(ToolID id) { EnsureToolbelt(); if (!_toolbelt.UnlockedTools.TryGetValue(id, out var unlocked) || !unlocked) return; _toolbelt.Lock(id); var save = _saveManager.CurrentSave; if (save != null) { save.Tools ??= new Dictionary(); save.Tools[id] = false; } _eventCoordinator.Publish(new ToolbeltChangedEvent(id, true)); _eventCoordinator.PublishImmediate(new RequestGameSaveEvent()); } private void LoadToolsFromSave(SaveGame save) { _toolbelt?.Dispose(); _toolbelt = new Toolbelt(_eventCoordinator, _tutorialService); _toolbelt.Initialize(); if (save.Tools == null) return; foreach (var kvp in save.Tools) { if (kvp.Value) _toolbelt.Unlock(kvp.Key); else _toolbelt.Lock(kvp.Key); } } private void EnsureToolbelt() { if (_toolbelt != null) return; _toolbelt = new Toolbelt(_eventCoordinator, _tutorialService); _toolbelt.Initialize(); } #endregion #region Saving / Loading private void LoadFromSave(SaveGame save) { if (save == null) return; LoadItemsFromSave(save); LoadCodexFromSave(save); LoadToolsFromSave(save); } private void LoadItemsFromSave(SaveGame save) { _backpack = new Backpack(); Debug.Log($"[PlayerManager] LoadItemsFromSave InventoryData count = {save.InventoryData?.Count ?? 0}"); if (save.InventoryData != null) { foreach (var item in save.InventoryData) { Debug.Log($"[PlayerManager] Loading InventoryData item '{item.UniqueIdentifier}'"); var itemData = _itemRegistry.FindItemTemplateByID(item.UniqueIdentifier); if (!itemData) { Debug.LogWarning($"[PlayerManager] Missing item template '{item.UniqueIdentifier}', skipping."); continue; } _backpack.AddItem(itemData); } } Debug.Log($"[PlayerManager] Backpack count after load = {_backpack.Items?.Count ?? 0}"); _eventCoordinator.Publish(new SelectedItemChangedEvent(null)); _eventCoordinator.Publish(new InventoryChangedEvent()); } private void LoadCodexFromSave(SaveGame save) { _codex = new Codex(); if (save.DiscoveredCodexEntries != null) { foreach (var codexEntry in save.DiscoveredCodexEntries) { var entryData = _codexRegistry.FindEntryByID(codexEntry.UniqueIdentifier); if (!entryData) { Debug.LogWarning($"[PlayerManager] Missing codex entry '{codexEntry.UniqueIdentifier}', skipping."); continue; } _codex.AddEntry(entryData); } } } private void UpdateSaveGame(SaveGame save) { if (save == null) return; Debug.Log($"[PlayerManager] UpdateSaveGame backpack count = {_backpack?.Items?.Count ?? 0}"); save.InventoryData = new List(); if (_backpack?.Items != null) { foreach (var item in _backpack.Items) { if (!item) continue; Debug.Log($"[PlayerManager] Writing InventoryData item '{item.UniqueID}'"); save.InventoryData.Add(new ItemSaveData { UniqueIdentifier = item.UniqueID }); } } Debug.Log($"[PlayerManager] Final InventoryData count = {save.InventoryData.Count}"); save.DiscoveredCodexEntries ??= new List(); save.DiscoveredCodexEntries.Clear(); if (_codex?.Entries != null) { foreach (var entry in _codex.Entries) { if (!entry) continue; save.DiscoveredCodexEntries.Add(new CodexSaveData { UniqueIdentifier = entry.UniqueID }); } } save.Tools ??= new Dictionary(); save.Tools.Clear(); if (_toolbelt?.UnlockedTools != null) { foreach (var kvp in _toolbelt.UnlockedTools) { save.Tools[kvp.Key] = kvp.Value; } } } #endregion } }