161 lines
5.0 KiB
C#
161 lines
5.0 KiB
C#
// ==============================
|
|
// HintManager.cs (fallback to nearest lower stage)
|
|
// ==============================
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using BriarQueen.Framework.Coordinators.Events;
|
|
using BriarQueen.Framework.Events.Gameplay;
|
|
using BriarQueen.Framework.Events.Progression;
|
|
using BriarQueen.Framework.Managers.Hints.Data;
|
|
using BriarQueen.Framework.Managers.Levels.Data;
|
|
using Cysharp.Threading.Tasks;
|
|
using UnityEngine;
|
|
using VContainer;
|
|
|
|
namespace BriarQueen.Framework.Managers.Hints
|
|
{
|
|
public class HintManager : IDisposable, IManager
|
|
{
|
|
private readonly EventCoordinator _eventCoordinator;
|
|
|
|
private readonly Dictionary<int, BaseHint> _hints = new();
|
|
private CancellationTokenSource _activeHintCts;
|
|
|
|
private BaseLevel _currentLevel;
|
|
|
|
private CancellationTokenSource _levelCts;
|
|
|
|
public bool Initialized { get; private set; }
|
|
|
|
[Inject]
|
|
public HintManager(EventCoordinator eventCoordinator)
|
|
{
|
|
_eventCoordinator = eventCoordinator;
|
|
}
|
|
|
|
public IReadOnlyDictionary<int, BaseHint> Hints => _hints;
|
|
|
|
public void Dispose()
|
|
{
|
|
_eventCoordinator.Unsubscribe<LevelChangedEvent>(OnLevelChanged);
|
|
_eventCoordinator.Unsubscribe<RequestHintEvent>(OnHintRequested);
|
|
|
|
CancelAndDispose(ref _activeHintCts);
|
|
CancelAndDispose(ref _levelCts);
|
|
|
|
_currentLevel = null;
|
|
_hints.Clear();
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
_eventCoordinator.Subscribe<LevelChangedEvent>(OnLevelChanged);
|
|
_eventCoordinator.Subscribe<RequestHintEvent>(OnHintRequested);
|
|
|
|
Initialized = true;
|
|
}
|
|
|
|
private void OnLevelChanged(LevelChangedEvent evt)
|
|
{
|
|
CancelAndDispose(ref _activeHintCts);
|
|
CancelAndDispose(ref _levelCts);
|
|
_levelCts = new CancellationTokenSource();
|
|
|
|
_hints.Clear();
|
|
_currentLevel = evt?.Level;
|
|
|
|
if (_currentLevel?.Hints == null) return;
|
|
|
|
foreach (var kvp in _currentLevel.Hints) _hints[kvp.Key] = kvp.Value;
|
|
}
|
|
|
|
private void OnHintRequested(RequestHintEvent evt)
|
|
{
|
|
RequestHint().Forget();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Plays the hint for the current hint stage.
|
|
/// If no hint exists at that stage, it falls back to the nearest lower stage that has a hint.
|
|
/// </summary>
|
|
public async UniTask RequestHint()
|
|
{
|
|
if (_currentLevel == null || _hints.Count == 0) return;
|
|
|
|
var stage = Mathf.Max(0, _currentLevel.CurrentLevelHintStage);
|
|
|
|
if (!TryGetHintForStageOrFallback(stage, out var resolvedStage, out var hint) || hint == null)
|
|
// Optional: later show a “No hints available” toast
|
|
return;
|
|
|
|
// Cancel any in-progress hint playback (spam-proof).
|
|
CancelAndDispose(ref _activeHintCts);
|
|
_activeHintCts = new CancellationTokenSource();
|
|
|
|
var levelToken = _levelCts?.Token ?? CancellationToken.None;
|
|
using var linked = CancellationTokenSource.CreateLinkedTokenSource(levelToken, _activeHintCts.Token);
|
|
|
|
try
|
|
{
|
|
await hint.Activate().AttachExternalCancellation(linked.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// Fine: new request or level changed.
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning(
|
|
$"[HintManager] Hint activation threw at requested stage {stage} (resolved {resolvedStage}): {ex}");
|
|
}
|
|
}
|
|
|
|
private bool TryGetHintForStageOrFallback(int requestedStage, out int resolvedStage, out BaseHint hint)
|
|
{
|
|
// Exact stage
|
|
if (_hints.TryGetValue(requestedStage, out hint) && hint != null)
|
|
{
|
|
resolvedStage = requestedStage;
|
|
return true;
|
|
}
|
|
|
|
// ✅ Fallback: nearest lower stage that exists
|
|
// (Designers can skip stages; hint still works.)
|
|
var best = -1;
|
|
|
|
// If you prefer performance, you can cache/sort keys once per level.
|
|
foreach (var k in _hints.Keys)
|
|
if (k <= requestedStage && k > best)
|
|
best = k;
|
|
|
|
if (best >= 0 && _hints.TryGetValue(best, out hint) && hint != null)
|
|
{
|
|
resolvedStage = best;
|
|
return true;
|
|
}
|
|
|
|
resolvedStage = -1;
|
|
hint = null;
|
|
return false;
|
|
}
|
|
|
|
private static void CancelAndDispose(ref CancellationTokenSource cts)
|
|
{
|
|
if (cts == null) return;
|
|
|
|
try
|
|
{
|
|
cts.Cancel();
|
|
}
|
|
catch
|
|
{
|
|
/* ignore */
|
|
}
|
|
|
|
cts.Dispose();
|
|
cts = null;
|
|
}
|
|
}
|
|
} |