194 lines
6.6 KiB
C#
194 lines
6.6 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using Unheimlich.Core;
|
|
using Unheimlich.Data.DetectiveBoard;
|
|
|
|
namespace Unheimlich.Managers
|
|
{
|
|
/// <summary>
|
|
/// Manages the detective board state and clue unlocking system.
|
|
/// Tracks unlocked clues, connections, certainty levels, and eliminated hypotheses.
|
|
/// Does NOT affect game progression - purely interpretive tool for the player.
|
|
/// </summary>
|
|
public class DetectiveBoardManager : Singleton<DetectiveBoardManager>
|
|
{
|
|
public enum CertaintyLevel
|
|
{
|
|
Uncertain,
|
|
Possible,
|
|
Confident,
|
|
Certain
|
|
}
|
|
|
|
public class ClueState
|
|
{
|
|
public ClueData Data;
|
|
public bool IsUnlocked;
|
|
public bool IsEliminated;
|
|
public Vector2 BoardPosition;
|
|
}
|
|
|
|
public class Connection
|
|
{
|
|
public string FromClueID;
|
|
public string ToClueID;
|
|
public CertaintyLevel Certainty;
|
|
public bool IsEliminated;
|
|
}
|
|
|
|
[Header("References")]
|
|
[SerializeField] private DetectiveBoardSettings _settings;
|
|
|
|
private Dictionary<string, ClueState> _clueStates = new Dictionary<string, ClueState>();
|
|
private List<Connection> _connections = new List<Connection>();
|
|
|
|
public DetectiveBoardSettings Settings => _settings;
|
|
public IReadOnlyDictionary<string, ClueState> ClueStates => _clueStates;
|
|
public IReadOnlyList<Connection> Connections => _connections;
|
|
|
|
/// <summary>
|
|
/// Unlocks a clue on the detective board. Can be triggered by any in-game event.
|
|
/// </summary>
|
|
public void UnlockClue(ClueData clueData)
|
|
{
|
|
if (clueData == null)
|
|
{
|
|
Debug.LogError("[DetectiveBoardManager] Attempted to unlock null clue!");
|
|
return;
|
|
}
|
|
|
|
if (!_clueStates.ContainsKey(clueData.ClueID))
|
|
{
|
|
_clueStates[clueData.ClueID] = new ClueState
|
|
{
|
|
Data = clueData,
|
|
IsUnlocked = true,
|
|
IsEliminated = false,
|
|
BoardPosition = clueData.InitialPosition
|
|
};
|
|
|
|
Debug.Log($"[DetectiveBoardManager] Unlocked clue: {clueData.Title}");
|
|
OnClueUnlocked?.Invoke(clueData);
|
|
}
|
|
else if (!_clueStates[clueData.ClueID].IsUnlocked)
|
|
{
|
|
_clueStates[clueData.ClueID].IsUnlocked = true;
|
|
Debug.Log($"[DetectiveBoardManager] Unlocked clue: {clueData.Title}");
|
|
OnClueUnlocked?.Invoke(clueData);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlocks a clue by its ID string.
|
|
/// </summary>
|
|
public void UnlockClueByID(string clueID)
|
|
{
|
|
if (_clueStates.TryGetValue(clueID, out ClueState state))
|
|
{
|
|
if (!state.IsUnlocked)
|
|
{
|
|
state.IsUnlocked = true;
|
|
Debug.Log($"[DetectiveBoardManager] Unlocked clue: {state.Data.Title}");
|
|
OnClueUnlocked?.Invoke(state.Data);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[DetectiveBoardManager] Clue ID '{clueID}' not found!");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a clue in the system without unlocking it.
|
|
/// </summary>
|
|
public void RegisterClue(ClueData clueData)
|
|
{
|
|
if (clueData == null || _clueStates.ContainsKey(clueData.ClueID))
|
|
return;
|
|
|
|
_clueStates[clueData.ClueID] = new ClueState
|
|
{
|
|
Data = clueData,
|
|
IsUnlocked = false,
|
|
IsEliminated = false,
|
|
BoardPosition = clueData.InitialPosition
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a connection between two clues.
|
|
/// </summary>
|
|
public void CreateConnection(string fromClueID, string toClueID, CertaintyLevel certainty = CertaintyLevel.Uncertain)
|
|
{
|
|
if (!_clueStates.ContainsKey(fromClueID) || !_clueStates.ContainsKey(toClueID))
|
|
{
|
|
Debug.LogWarning($"[DetectiveBoardManager] Cannot create connection - one or both clues not found!");
|
|
return;
|
|
}
|
|
|
|
Connection existing = _connections.Find(c =>
|
|
(c.FromClueID == fromClueID && c.ToClueID == toClueID) ||
|
|
(c.FromClueID == toClueID && c.ToClueID == fromClueID));
|
|
|
|
if (existing != null)
|
|
{
|
|
existing.Certainty = certainty;
|
|
}
|
|
else
|
|
{
|
|
_connections.Add(new Connection
|
|
{
|
|
FromClueID = fromClueID,
|
|
ToClueID = toClueID,
|
|
Certainty = certainty,
|
|
IsEliminated = false
|
|
});
|
|
|
|
OnConnectionCreated?.Invoke(fromClueID, toClueID);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a clue as eliminated (incorrect hypothesis).
|
|
/// </summary>
|
|
public void EliminateClue(string clueID)
|
|
{
|
|
if (_clueStates.TryGetValue(clueID, out ClueState state))
|
|
{
|
|
state.IsEliminated = true;
|
|
OnClueEliminated?.Invoke(clueID);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a connection as eliminated.
|
|
/// </summary>
|
|
public void EliminateConnection(string fromClueID, string toClueID)
|
|
{
|
|
Connection connection = _connections.Find(c =>
|
|
(c.FromClueID == fromClueID && c.ToClueID == toClueID) ||
|
|
(c.FromClueID == toClueID && c.ToClueID == fromClueID));
|
|
|
|
if (connection != null)
|
|
{
|
|
connection.IsEliminated = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates a clue's position on the board.
|
|
/// </summary>
|
|
public void UpdateCluePosition(string clueID, Vector2 newPosition)
|
|
{
|
|
if (_clueStates.TryGetValue(clueID, out ClueState state))
|
|
{
|
|
state.BoardPosition = newPosition;
|
|
}
|
|
}
|
|
|
|
public event System.Action<ClueData> OnClueUnlocked;
|
|
public event System.Action<string, string> OnConnectionCreated;
|
|
public event System.Action<string> OnClueEliminated;
|
|
}
|
|
}
|