Files
A-Fairytale-Gone-Bad-Briar-…/Assets/Scripts/UI/Menus/SelectSaveWindow.cs

355 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Services.Game;
using BriarQueen.UI.Menus.Components;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using VContainer;
namespace BriarQueen.UI.Menus
{
/// <summary>
/// Select Save Window:
/// - Shows EXACTLY 3 slots (filled first, then empty).
/// - Clicking a slot:
/// - Filled slot => loads that save, then StartGame
/// - Empty slot => opens NewSaveWindow (create + load), then StartGame
/// - Delete is still supported (per-slot delete button + confirm modal).
/// - Back closes this window (handled by MainMenuWindow).
/// </summary>
public class SelectSaveWindow : MonoBehaviour
{
private const int MAX_SLOTS = 3;
[Header("UI")]
[SerializeField]
private RectTransform _listContentParent;
[SerializeField]
private SaveSlotUI _saveSlotPrefab;
[Header("Buttons")]
[SerializeField]
private Button _backButton;
[Header("New Save Window")]
[SerializeField]
private NewSaveWindow _newSaveWindow;
[Header("Confirm Delete Window (optional but recommended)")]
[SerializeField]
private ConfirmDeleteWindow _confirmDeleteWindow;
private readonly List<SaveSlotUI> _instantiatedSlots = new();
private int _currentSelectionIndex;
private GameService _gameService;
private bool _isBusy;
private SaveManager _saveManager;
private void Awake()
{
if (_backButton != null) _backButton.onClick.AddListener(OnBackClicked);
if (_newSaveWindow != null)
{
_newSaveWindow.OnCloseWindow += HandleNewSaveClosed;
_newSaveWindow.OnSaveCreated += HandleSaveCreatedAndStartGame;
}
if (_confirmDeleteWindow != null)
{
_confirmDeleteWindow.OnConfirmDelete += HandleConfirmDelete;
_confirmDeleteWindow.OnCancel += HandleCancelDelete;
_confirmDeleteWindow.Close();
}
}
private void OnDestroy()
{
if (_backButton != null) _backButton.onClick.RemoveListener(OnBackClicked);
if (_newSaveWindow != null)
{
_newSaveWindow.OnCloseWindow -= HandleNewSaveClosed;
_newSaveWindow.OnSaveCreated -= HandleSaveCreatedAndStartGame;
}
if (_confirmDeleteWindow != null)
{
_confirmDeleteWindow.OnConfirmDelete -= HandleConfirmDelete;
_confirmDeleteWindow.OnCancel -= HandleCancelDelete;
}
ClearSlots();
}
public event Action OnCloseWindow;
[Inject]
public void Construct(SaveManager saveManager, GameService gameService)
{
_saveManager = saveManager;
_gameService = gameService;
}
/// <summary>Called by MainMenuWindow after enabling this GO.</summary>
public void Refresh()
{
if (_newSaveWindow != null)
_newSaveWindow.CloseImmediate();
if (_confirmDeleteWindow != null)
_confirmDeleteWindow.Close();
EnsureThreeSlotsExist();
RefreshSlotsData();
SetBusy(false);
}
private void OnBackClicked()
{
if (_isBusy)
return;
// Check if Confirm Delete or New Save Window is open.
OnCloseWindow?.Invoke();
}
private void HandleNewSaveClosed()
{
// NewSaveWindow was closed (cancel/back) => return control to the list.
SetBusy(false);
RestoreSelection();
RefreshSlotsData();
}
private void HandleSaveCreatedAndStartGame(string _)
{
// NewSaveWindow already created + loaded the save.
OnCloseWindow?.Invoke();
_gameService?.StartGame().Forget();
}
private void SetBusy(bool busy)
{
Debug.Log("[SelectSaveWindow] SetBusy: " + busy);
_isBusy = busy;
if (_backButton != null)
_backButton.interactable = !busy;
foreach (var slot in _instantiatedSlots)
if (slot != null)
slot.SetInteractable(!busy);
Debug.Log($"[SelectSaveWindow] Finished set busy: {busy}");
}
private void EnsureThreeSlotsExist()
{
if (_listContentParent == null || _saveSlotPrefab == null)
return;
if (_instantiatedSlots.Count == MAX_SLOTS)
return;
ClearSlots();
for (var i = 0; i < MAX_SLOTS; i++)
{
var slot = Instantiate(_saveSlotPrefab, _listContentParent);
_instantiatedSlots.Add(slot);
}
}
private void RefreshSlotsData()
{
// Always show 3 slots; if save system is missing, theyll all appear empty/disabled.
if (_saveManager == null)
{
for (var i = 0; i < _instantiatedSlots.Count; i++)
_instantiatedSlots[i]?.SetEmpty(OnEmptySlotClicked);
SelectBackButton();
return;
}
// Newest first, cap at 3
var saveFiles = _saveManager.GetAvailableSaves();
var infos = (saveFiles ?? new List<(string, DateTime)>())
.Select(sf => new SaveFileInfo(sf.FileName, sf.LastModified))
.OrderByDescending(i => i.LastModified)
.Take(MAX_SLOTS)
.ToArray();
for (var i = 0; i < MAX_SLOTS; i++)
{
var slot = _instantiatedSlots.ElementAtOrDefault(i);
if (slot == null) continue;
if (i < infos.Length)
slot.SetFilled(infos[i], OnFilledSlotClicked, OnSlotDeleteClicked);
else
slot.SetEmpty(OnEmptySlotClicked);
}
_currentSelectionIndex = Mathf.Clamp(_currentSelectionIndex, 0, MAX_SLOTS - 1);
SelectSlot(_currentSelectionIndex);
}
private void ClearSlots()
{
foreach (var slot in _instantiatedSlots)
if (slot != null)
Destroy(slot.gameObject);
_instantiatedSlots.Clear();
_currentSelectionIndex = 0;
}
private void OnFilledSlotClicked(SaveFileInfo saveInfo)
{
if (_isBusy) return;
if (_saveManager == null) return;
if (string.IsNullOrWhiteSpace(saveInfo.FileName)) return;
if (!_saveManager.DoesSaveExist(saveInfo.FileName))
{
RefreshSlotsData();
return;
}
LoadAndStartGame(saveInfo.FileName).Forget();
}
private void OnEmptySlotClicked()
{
Debug.Log("[SelectSaveWindow] Empty slot clicked.");
if (_isBusy)
return;
if (_newSaveWindow == null)
{
Debug.LogWarning("[SelectSaveWindow] NewSaveWindow reference not set.");
return;
}
SetBusy(true);
_newSaveWindow.Open();
}
private async UniTask LoadAndStartGame(string profileName)
{
SetBusy(true);
try
{
await _saveManager.LoadGameData(profileName);
OnCloseWindow?.Invoke();
_gameService?.StartGame().Forget();
}
catch (Exception ex)
{
Debug.LogError($"[SelectSaveWindow] Failed to load profile '{profileName}': {ex}");
SetBusy(false);
RefreshSlotsData();
RestoreSelection();
}
}
private void OnSlotDeleteClicked(SaveFileInfo saveInfo)
{
if (_isBusy) return;
if (_saveManager == null) return;
if (string.IsNullOrWhiteSpace(saveInfo.FileName)) return;
// No confirm window wired? Do a direct delete.
if (_confirmDeleteWindow == null)
{
TryDeleteAndRefresh(saveInfo.FileName);
return;
}
SetBusy(true);
_confirmDeleteWindow.Open(saveInfo);
}
private void HandleConfirmDelete(SaveFileInfo saveInfo)
{
try
{
TryDeleteAndRefresh(saveInfo.FileName);
}
finally
{
SetBusy(false);
RestoreSelection();
}
}
private void HandleCancelDelete()
{
SetBusy(false);
RestoreSelection();
}
private void TryDeleteAndRefresh(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
return;
if (!_saveManager.DoesSaveExist(fileName))
{
RefreshSlotsData();
return;
}
var deleted = _saveManager.Delete(fileName);
if (!deleted)
Debug.LogWarning($"[SelectSaveWindow] Failed to delete save '{fileName}'.");
RefreshSlotsData();
}
private void RestoreSelection()
{
_currentSelectionIndex = Mathf.Clamp(_currentSelectionIndex, 0, MAX_SLOTS - 1);
SelectSlot(_currentSelectionIndex);
}
private void SelectBackButton()
{
if (_backButton == null) return;
if (EventSystem.current != null)
EventSystem.current.SetSelectedGameObject(_backButton.gameObject);
else
_backButton.Select();
}
private void SelectSlot(int index)
{
if (_instantiatedSlots.Count == 0)
{
SelectBackButton();
return;
}
index = Mathf.Clamp(index, 0, _instantiatedSlots.Count - 1);
_currentSelectionIndex = index;
var go = _instantiatedSlots[index]?.GetSelectableGameObject();
if (go == null) return;
if (EventSystem.current != null)
EventSystem.current.SetSelectedGameObject(go);
}
}
}