382 lines
13 KiB
C#
382 lines
13 KiB
C#
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unheimlich.Data.DetectiveBoard;
|
|
using Unheimlich.Managers;
|
|
using Unheimlich.Core;
|
|
|
|
namespace Unheimlich.UI.DetectiveBoard
|
|
{
|
|
public class DetectiveBoardCanvas : Singleton<DetectiveBoardCanvas>
|
|
{
|
|
[Header("References")]
|
|
[SerializeField] private Canvas _canvas;
|
|
[SerializeField] private RectTransform _boardArea;
|
|
[SerializeField] private GameObject _postItNodePrefab;
|
|
[SerializeField] private GameObject _connectionLinePrefab;
|
|
|
|
[Header("World Space Setup")]
|
|
[SerializeField] private Camera _targetCamera;
|
|
|
|
[Header("Connection Settings")]
|
|
[SerializeField] private Color _connectionColor = Color.white;
|
|
[SerializeField] private bool _allowFreeConnections = false;
|
|
|
|
[Header("Input")]
|
|
[SerializeField] private Key _connectModeKey = Key.LeftCtrl;
|
|
[SerializeField] private Key _eliminateKey = Key.X;
|
|
|
|
private Dictionary<string, PostItNode> _activeNodes = new Dictionary<string, PostItNode>();
|
|
private List<ConnectionLine> _activeConnections = new List<ConnectionLine>();
|
|
private PostItNode _selectedNode;
|
|
private bool _isConnectMode;
|
|
private DetectiveBoardSaveData _saveData;
|
|
|
|
public bool IsConnectMode => _isConnectMode;
|
|
|
|
/// <summary>
|
|
/// Called by DetectiveBoardInteraction when a node is left-clicked.
|
|
/// Handles connection creation in connect mode, or selection in normal mode.
|
|
/// </summary>
|
|
public void OnNodeClicked(PostItNode node)
|
|
{
|
|
if (_isConnectMode)
|
|
{
|
|
HandleConnectionModeClick(node);
|
|
}
|
|
else
|
|
{
|
|
_selectedNode = node;
|
|
UpdateAllNodeSelections();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by DetectiveBoardInteraction when a node is right-clicked.
|
|
/// Toggles the eliminated state of the node.
|
|
/// </summary>
|
|
public void OnNodeRightClicked(PostItNode node)
|
|
{
|
|
ToggleNodeEliminated(node);
|
|
}
|
|
|
|
protected override void OnAwake()
|
|
{
|
|
if (_canvas == null)
|
|
_canvas = GetComponent<Canvas>();
|
|
|
|
if (_canvas != null && _canvas.renderMode == RenderMode.WorldSpace)
|
|
{
|
|
if (_targetCamera == null)
|
|
_targetCamera = Camera.main;
|
|
|
|
_canvas.worldCamera = _targetCamera;
|
|
}
|
|
|
|
_saveData = new DetectiveBoardSaveData();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
TrySubscribeToManager();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
TrySubscribeToManager();
|
|
}
|
|
|
|
private void TrySubscribeToManager()
|
|
{
|
|
if (DetectiveBoardManager.Instance != null)
|
|
{
|
|
DetectiveBoardManager.Instance.OnClueUnlocked -= OnClueUnlocked;
|
|
DetectiveBoardManager.Instance.OnClueUnlocked += OnClueUnlocked;
|
|
|
|
foreach (var clue in DetectiveBoardManager.Instance.GetUnlockedClues())
|
|
{
|
|
if (!_activeNodes.ContainsKey(clue.ClueID))
|
|
{
|
|
AddClueNode(clue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (DetectiveBoardManager.Instance != null)
|
|
{
|
|
DetectiveBoardManager.Instance.OnClueUnlocked -= OnClueUnlocked;
|
|
}
|
|
}
|
|
|
|
private void OnClueUnlocked(ClueData clueData)
|
|
{
|
|
Debug.Log($"[DetectiveBoardCanvas] OnClueUnlocked received for: {clueData.ClueID}");
|
|
AddClueNode(clueData);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
HandleConnectModeInput();
|
|
HandleEliminateInput();
|
|
}
|
|
|
|
private void HandleConnectModeInput()
|
|
{
|
|
if (Keyboard.current == null)
|
|
return;
|
|
|
|
bool wasConnectMode = _isConnectMode;
|
|
_isConnectMode = Keyboard.current[_connectModeKey].isPressed;
|
|
|
|
if (wasConnectMode && !_isConnectMode)
|
|
{
|
|
_selectedNode = null;
|
|
UpdateAllNodeSelections();
|
|
}
|
|
}
|
|
|
|
private void HandleEliminateInput()
|
|
{
|
|
if (Keyboard.current == null)
|
|
return;
|
|
|
|
if (Keyboard.current[_eliminateKey].wasPressedThisFrame && _selectedNode != null)
|
|
{
|
|
ToggleNodeEliminated(_selectedNode);
|
|
}
|
|
}
|
|
|
|
public void AddClueNode(ClueData clueData)
|
|
{
|
|
if (_activeNodes.ContainsKey(clueData.ClueID))
|
|
return;
|
|
|
|
if (_postItNodePrefab == null)
|
|
{
|
|
Debug.LogError("[DetectiveBoardCanvas] _postItNodePrefab is null! Assign the prefab in the inspector.");
|
|
return;
|
|
}
|
|
|
|
if (_boardArea == null)
|
|
{
|
|
Debug.LogError("[DetectiveBoardCanvas] _boardArea is null! Assign NodesContainer in the inspector.");
|
|
return;
|
|
}
|
|
|
|
PostItNodeData nodeData = _saveData.GetNodeData(clueData.ClueID);
|
|
if (nodeData == null)
|
|
{
|
|
Vector2 canvasSize = _boardArea.rect.size;
|
|
Vector2 randomPos = new Vector2(
|
|
Random.Range(-canvasSize.x * 0.4f, canvasSize.x * 0.4f),
|
|
Random.Range(-canvasSize.y * 0.4f, canvasSize.y * 0.4f)
|
|
);
|
|
nodeData = new PostItNodeData(clueData.ClueID, randomPos);
|
|
_saveData.AddOrUpdateNode(nodeData);
|
|
}
|
|
|
|
GameObject nodeObj = Instantiate(_postItNodePrefab, _boardArea);
|
|
|
|
PostItNode node = nodeObj.GetComponent<PostItNode>();
|
|
|
|
if (node == null)
|
|
{
|
|
Debug.LogError("[DetectiveBoardCanvas] PostItNode component not found on prefab");
|
|
Destroy(nodeObj);
|
|
return;
|
|
}
|
|
|
|
node.Initialize(clueData, nodeData);
|
|
|
|
_activeNodes.Add(clueData.ClueID, node);
|
|
|
|
RestoreConnectionsForNode(node);
|
|
}
|
|
|
|
public void RemoveClueNode(string clueID)
|
|
{
|
|
if (!_activeNodes.TryGetValue(clueID, out PostItNode node))
|
|
return;
|
|
|
|
RemoveConnectionsForNode(node);
|
|
|
|
_activeNodes.Remove(clueID);
|
|
Destroy(node.gameObject);
|
|
}
|
|
|
|
private void HandleConnectionModeClick(PostItNode clickedNode)
|
|
{
|
|
if (_selectedNode == null)
|
|
{
|
|
_selectedNode = clickedNode;
|
|
UpdateAllNodeSelections();
|
|
}
|
|
else if (_selectedNode == clickedNode)
|
|
{
|
|
_selectedNode = null;
|
|
UpdateAllNodeSelections();
|
|
}
|
|
else
|
|
{
|
|
ToggleConnection(_selectedNode, clickedNode);
|
|
_selectedNode = null;
|
|
UpdateAllNodeSelections();
|
|
}
|
|
}
|
|
|
|
private void ToggleConnection(PostItNode nodeA, PostItNode nodeB)
|
|
{
|
|
if (!CanConnect(nodeA, nodeB))
|
|
return;
|
|
|
|
ConnectionLine existingConnection = _activeConnections.Find(c => c.ConnectsNodes(nodeA, nodeB));
|
|
|
|
if (existingConnection != null)
|
|
{
|
|
RemoveConnection(existingConnection);
|
|
}
|
|
else
|
|
{
|
|
CreateConnection(nodeA, nodeB);
|
|
}
|
|
}
|
|
|
|
private bool CanConnect(PostItNode nodeA, PostItNode nodeB)
|
|
{
|
|
if (nodeA == null || nodeB == null)
|
|
return false;
|
|
|
|
if (_allowFreeConnections)
|
|
return true;
|
|
|
|
if (nodeA.ClueData.PossibleConnections == null || nodeA.ClueData.PossibleConnections.Length == 0)
|
|
return true;
|
|
|
|
return nodeA.ClueData.PossibleConnections.Contains(nodeB.NodeID);
|
|
}
|
|
|
|
private void CreateConnection(PostItNode startNode, PostItNode endNode)
|
|
{
|
|
GameObject lineObj = Instantiate(_connectionLinePrefab, _boardArea);
|
|
lineObj.transform.SetAsFirstSibling();
|
|
|
|
ConnectionLine line = lineObj.GetComponent<ConnectionLine>();
|
|
if (line == null)
|
|
{
|
|
Debug.LogError("[DetectiveBoardCanvas] ConnectionLine component not found on prefab");
|
|
Destroy(lineObj);
|
|
return;
|
|
}
|
|
|
|
line.Initialize(startNode, endNode, _connectionColor);
|
|
_activeConnections.Add(line);
|
|
|
|
if (!startNode.NodeData.ConnectedNodeIDs.Contains(endNode.NodeID))
|
|
startNode.NodeData.ConnectedNodeIDs.Add(endNode.NodeID);
|
|
|
|
if (!endNode.NodeData.ConnectedNodeIDs.Contains(startNode.NodeID))
|
|
endNode.NodeData.ConnectedNodeIDs.Add(startNode.NodeID);
|
|
}
|
|
|
|
private void RemoveConnection(ConnectionLine connection)
|
|
{
|
|
if (connection.StartNode != null && connection.EndNode != null)
|
|
{
|
|
connection.StartNode.NodeData.ConnectedNodeIDs.Remove(connection.EndNode.NodeID);
|
|
connection.EndNode.NodeData.ConnectedNodeIDs.Remove(connection.StartNode.NodeID);
|
|
}
|
|
|
|
_activeConnections.Remove(connection);
|
|
Destroy(connection.gameObject);
|
|
}
|
|
|
|
private void RemoveConnectionsForNode(PostItNode node)
|
|
{
|
|
var connectionsToRemove = _activeConnections
|
|
.Where(c => c.StartNode == node || c.EndNode == node)
|
|
.ToList();
|
|
|
|
foreach (var connection in connectionsToRemove)
|
|
{
|
|
RemoveConnection(connection);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates all connection lines attached to the specified node.
|
|
/// Called during node dragging to keep connections visually updated.
|
|
/// </summary>
|
|
public void UpdateConnectionsForNode(PostItNode node)
|
|
{
|
|
foreach (var connection in _activeConnections)
|
|
{
|
|
if (connection.StartNode == node || connection.EndNode == node)
|
|
{
|
|
connection.UpdateLine();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RestoreConnectionsForNode(PostItNode node)
|
|
{
|
|
if (node.NodeData.ConnectedNodeIDs == null || node.NodeData.ConnectedNodeIDs.Count == 0)
|
|
return;
|
|
|
|
foreach (string connectedID in node.NodeData.ConnectedNodeIDs)
|
|
{
|
|
if (_activeNodes.TryGetValue(connectedID, out PostItNode otherNode))
|
|
{
|
|
bool connectionExists = _activeConnections.Any(c => c.ConnectsNodes(node, otherNode));
|
|
if (!connectionExists)
|
|
{
|
|
CreateConnection(node, otherNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ToggleNodeEliminated(PostItNode node)
|
|
{
|
|
node.SetEliminated(!node.IsEliminated);
|
|
}
|
|
|
|
private void UpdateAllNodeSelections()
|
|
{
|
|
foreach (var node in _activeNodes.Values)
|
|
{
|
|
node.SetSelected(node == _selectedNode);
|
|
}
|
|
}
|
|
|
|
public DetectiveBoardSaveData GetSaveData()
|
|
{
|
|
return _saveData;
|
|
}
|
|
|
|
public void LoadSaveData(DetectiveBoardSaveData saveData)
|
|
{
|
|
_saveData = saveData;
|
|
|
|
foreach (var node in _activeNodes.Values)
|
|
{
|
|
PostItNodeData nodeData = _saveData.GetNodeData(node.NodeID);
|
|
if (nodeData != null)
|
|
{
|
|
node.SetPosition(nodeData.Position);
|
|
node.SetEliminated(nodeData.IsEliminated);
|
|
}
|
|
}
|
|
|
|
_activeConnections.Clear();
|
|
foreach (var node in _activeNodes.Values)
|
|
{
|
|
RestoreConnectionsForNode(node);
|
|
}
|
|
}
|
|
}
|
|
}
|