using UnityEngine;
///
/// Handles proximity-based interactions for the player.
/// Automatically triggers interactions when the player gets within range.
/// Scalable for buildings, gates, bridges, ledges, and other interactive objects.
///
public class PlayerInteractionController : MonoBehaviour
{
[Header("Interaction Settings")]
[SerializeField]
private float interactionRange = 1.5f; // "Arm's reach" distance
[SerializeField]
private float stoppingDistance = 0.5f; // How close to the entry point before triggering the interaction
[Header("Debug")]
[SerializeField]
private bool enableDebugLogs = false;
private NavMeshMovementController movementController;
private Building pendingBuildingInteraction;
private IInteractiveObject pendingInteraction;
private NPC pendingNPCInteraction;
void Awake()
{
movementController = GetComponent();
if (movementController == null)
Debug.LogWarning("PlayerInteractionController requires NavMeshMovementController on the same GameObject.", gameObject);
}
void Update()
{
// Check if we should trigger pending building interaction
if (pendingBuildingInteraction != null)
{
CheckBuildingInteractionProximity();
}
// Check for generic interactive objects in range
if (pendingInteraction != null)
{
CheckGenericInteractionProximity();
}
// Check for NPC interactions in range
if (pendingNPCInteraction != null)
{
CheckNPCInteractionProximity();
}
}
///
/// Set a building as the pending interaction target.
/// The player will move to the building's entry point and enter when close enough.
///
public void SetPendingBuildingInteraction(Building building)
{
if (building == null) return;
// Clear any previous interaction
ClearPendingInteraction();
pendingBuildingInteraction = building;
// Move to the building's entry point and look at the building
if (movementController != null && building.entryPoint != null)
{
movementController.MoveToAndLookAt(building.entryPoint.position, building.transform.position);
Log($"PlayerInteractionController: Moving to {building.buildingName}");
}
}
///
/// Set a generic interactive object as the pending interaction target.
/// Scalable for gates, bridges, ledges, etc.
///
public void SetPendingInteraction(IInteractiveObject interactiveObject, Vector3 targetPosition)
{
if (interactiveObject == null) return;
ClearPendingInteraction();
pendingInteraction = interactiveObject;
if (movementController != null)
{
movementController.MoveToAndLookAt(targetPosition, targetPosition);
Log("PlayerInteractionController: Moving to interactive object");
}
}
///
/// Set an NPC as the pending interaction target.
/// The player will move to the NPC and trigger interaction when close enough.
///
public void SetPendingNPCInteraction(NPC npc)
{
if (npc == null) return;
ClearPendingInteraction();
pendingNPCInteraction = npc;
if (movementController != null)
{
movementController.MoveToAndLookAt(npc.transform.position, npc.transform.position);
Log($"PlayerInteractionController: Moving to interact with {npc.npcName}");
}
}
///
/// Clear any pending interactions
///
public void ClearPendingInteraction()
{
pendingBuildingInteraction = null;
pendingInteraction = null;
pendingNPCInteraction = null;
}
private void CheckBuildingInteractionProximity()
{
if (pendingBuildingInteraction == null || pendingBuildingInteraction.entryPoint == null)
{
pendingBuildingInteraction = null;
return;
}
float distanceToEntry = Vector3.Distance(transform.position, pendingBuildingInteraction.entryPoint.position);
// Check if player is close enough to interact
if (distanceToEntry <= stoppingDistance)
{
// Check if building can be entered
if (pendingBuildingInteraction.canEnter)
{
// Check gate status
if (pendingBuildingInteraction.gate != null && pendingBuildingInteraction.gate.isLocked)
{
Log($"Cannot enter {pendingBuildingInteraction.buildingName}: Gate is locked");
pendingBuildingInteraction = null;
return;
}
// Check if we need to open the gate
if (pendingBuildingInteraction.gate != null && !pendingBuildingInteraction.gate.IsOpen())
{
pendingBuildingInteraction.gate.Open();
Log($"Opening gate for {pendingBuildingInteraction.buildingName}");
// Wait a moment for the gate to open before entering
return;
}
// Enter the building
pendingBuildingInteraction.Enter();
Log($"Entering {pendingBuildingInteraction.buildingName}");
pendingBuildingInteraction = null;
// Stop movement
if (movementController != null)
movementController.Stop();
}
else
{
Log($"Cannot enter {pendingBuildingInteraction.buildingName}: Building is not enterable");
pendingBuildingInteraction = null;
}
}
}
private void CheckGenericInteractionProximity()
{
if (pendingInteraction == null) return;
Vector3 interactionPoint = pendingInteraction.GetInteractionPoint();
float distanceToInteraction = Vector3.Distance(transform.position, interactionPoint);
if (distanceToInteraction <= interactionRange)
{
if (pendingInteraction.CanInteract())
{
pendingInteraction.Interact(gameObject);
Log("Triggered interaction");
pendingInteraction = null;
if (movementController != null)
movementController.Stop();
}
else
{
Log("Cannot interact: conditions not met");
pendingInteraction = null;
}
}
}
private void CheckNPCInteractionProximity()
{
if (pendingNPCInteraction == null) return;
float distanceToNPC = Vector3.Distance(transform.position, pendingNPCInteraction.transform.position);
// Check if player is close enough to the NPC
if (distanceToNPC <= interactionRange)
{
// Trigger the NPC interaction
pendingNPCInteraction.OnInteract(gameObject);
Log($"Triggered NPC interaction: {pendingNPCInteraction.npcName}");
// Clear the pending interaction
pendingNPCInteraction = null;
// Stop player movement
if (movementController != null)
movementController.Stop();
}
}
///
/// Get the current interaction range setting
///
public float GetInteractionRange() => interactionRange;
///
/// Set the interaction range (useful for difficulty settings or equipment modifiers)
///
public void SetInteractionRange(float range)
{
interactionRange = Mathf.Max(0.1f, range);
}
private void Log(string message)
{
if (enableDebugLogs)
{
Debug.Log(message);
}
}
}