using UnityEngine;
using System.Collections.Generic;
using System.Linq;
///
/// Manages spottable event markers that appear as red dots within the team's perception range
///
public class EventMarkerVisualizer : MonoBehaviour
{
[Header("Event Marker Settings")]
[SerializeField] private GameObject eventMarkerPrefab;
[SerializeField] private Material eventMarkerMaterial;
[SerializeField] private Color eventMarkerColor = Color.red;
[SerializeField] private float markerSize = 0.8f;
[SerializeField] private float markerHeight = 0.6f;
[Header("Spawn Settings")]
[SerializeField] private int maxActiveMarkers = 5;
[SerializeField] private float minDistanceFromTeam = 2f;
[SerializeField] private float maxDistanceFromTeam = 8f;
[SerializeField] private float markerLifetime = 30f; // Markers disappear after 30 seconds if not approached
[Header("Visual Effects")]
[SerializeField] private bool enablePulsing = true;
[SerializeField] private float pulseSpeed = 2f;
[SerializeField] private float pulseIntensity = 0.3f;
[Header("Debug")]
[SerializeField] private bool showDebugInfo = false;
// Component references
private TeamPerceptionVisualizer perceptionVisualizer;
private SimpleTeamPlacement teamPlacement;
private TravelEventSystem eventSystem;
private MapMaker2 mapMaker;
// Active markers tracking
private List activeMarkers = new List();
private List occupiedPositions = new List();
public static EventMarkerVisualizer Instance { get; private set; }
void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Debug.LogWarning("Multiple EventMarkerVisualizer instances found. Destroying this one.");
Destroy(gameObject);
return;
}
}
void Start()
{
// Find required components
perceptionVisualizer = FindFirstObjectByType();
teamPlacement = FindFirstObjectByType();
eventSystem = FindFirstObjectByType();
mapMaker = FindFirstObjectByType();
if (perceptionVisualizer == null)
{
Debug.LogError("EventMarkerVisualizer: TeamPerceptionVisualizer not found!");
}
if (teamPlacement == null)
{
Debug.LogError("EventMarkerVisualizer: SimpleTeamPlacement not found!");
}
}
void Update()
{
// Clean up expired markers
CleanupExpiredMarkers();
// Update marker visuals (pulsing effect)
if (enablePulsing)
{
UpdateMarkerPulsing();
}
}
///
/// Attempts to spawn a spottable event marker within perception range
///
/// The event to create a marker for
/// True if marker was successfully spawned
public bool TrySpawnEventMarker(TravelEvent travelEvent)
{
if (travelEvent == null || activeMarkers.Count >= maxActiveMarkers)
{
return false;
}
// Get team position and perception range
Vector2Int teamPosition = GetTeamPosition();
float perceptionRange = GetTeamPerceptionRange();
if (perceptionRange <= 0)
{
if (showDebugInfo)
Debug.Log("EventMarkerVisualizer: No perception range, cannot spawn marker");
return false;
}
// Find a valid position within perception range
Vector2Int markerPosition = FindValidMarkerPosition(teamPosition, perceptionRange);
if (markerPosition == Vector2Int.zero)
{
if (showDebugInfo)
Debug.Log("EventMarkerVisualizer: Could not find valid position for event marker");
return false;
}
// Create the marker
GameObject markerObject = CreateMarkerObject(markerPosition, travelEvent);
if (markerObject == null)
{
return false;
}
// Create event marker data
EventMarker marker = new EventMarker
{
gameObject = markerObject,
travelEvent = travelEvent,
position = markerPosition,
spawnTime = Time.time,
isActive = true
};
activeMarkers.Add(marker);
occupiedPositions.Add(markerPosition);
if (showDebugInfo)
{
Debug.Log($"EventMarkerVisualizer: Spawned marker for '{travelEvent.eventName}' at {markerPosition}");
}
return true;
}
///
/// Checks if the team is close enough to trigger an event marker
///
/// Current team position
/// The event to trigger, or null if none
public TravelEvent CheckForMarkerTrigger(Vector2Int teamPosition)
{
float triggerDistance = 1.5f; // Distance to trigger event
foreach (var marker in activeMarkers.ToList())
{
if (!marker.isActive) continue;
float distance = Vector2Int.Distance(teamPosition, marker.position);
if (distance <= triggerDistance)
{
// Remove the marker and return the event
RemoveMarker(marker);
if (showDebugInfo)
{
Debug.Log($"EventMarkerVisualizer: Team triggered event '{marker.travelEvent.eventName}' at {marker.position}");
}
return marker.travelEvent;
}
}
return null;
}
///
/// Removes all active event markers
///
public void ClearAllMarkers()
{
foreach (var marker in activeMarkers.ToList())
{
RemoveMarker(marker);
}
}
private Vector2Int GetTeamPosition()
{
if (teamPlacement != null)
{
return teamPlacement.GetCurrentTeamPosition();
}
return Vector2Int.zero;
}
private float GetTeamPerceptionRange()
{
if (perceptionVisualizer != null)
{
// Access the perception range from the visualizer
// We need to get the team's highest perception and apply the multiplier
return GetHighestTeamPerception() * 1.0f; // Use same multiplier as perception visualizer
}
return 0f;
}
private int GetHighestTeamPerception()
{
// This mirrors the logic from TeamPerceptionVisualizer
int maxPerception = 0;
List teamMembers = GetCurrentTeamMembers();
if (teamMembers == null || teamMembers.Count == 0)
return 0;
foreach (TeamCharacter character in teamMembers)
{
if (character != null)
{
int characterPerception = character.FinalPerception;
if (characterPerception > maxPerception)
{
maxPerception = characterPerception;
}
}
}
return maxPerception;
}
private List GetCurrentTeamMembers()
{
// Use the same logic as TeamPerceptionVisualizer
var teamSelectScript = FindFirstObjectByType();
var gameStateManager = FindFirstObjectByType();
// Try to get team from MainTeamSelectScript first
if (teamSelectScript != null)
{
var configuredCharacters = teamSelectScript.GetConfiguredCharacters();
if (configuredCharacters != null && configuredCharacters.Count > 0)
{
return configuredCharacters;
}
}
// Fall back to GameStateManager
if (gameStateManager != null && gameStateManager.savedTeam != null)
{
var savedTeamList = new List();
foreach (var character in gameStateManager.savedTeam)
{
if (character != null)
savedTeamList.Add(character);
}
if (savedTeamList.Count > 0)
{
return savedTeamList;
}
}
// Final fallback: Load from PlayerPrefs
var playerPrefTeam = new List();
for (int i = 0; i < 4; i++)
{
string prefix = $"Character{i}_";
if (PlayerPrefs.HasKey(prefix + "Exists") && PlayerPrefs.GetInt(prefix + "Exists") == 1)
{
var character = new TeamCharacter();
character.name = PlayerPrefs.GetString(prefix + "Name", "");
character.isMale = PlayerPrefs.GetInt(prefix + "IsMale", 1) == 1;
character.strength = PlayerPrefs.GetInt(prefix + "Strength", 10);
character.dexterity = PlayerPrefs.GetInt(prefix + "Dexterity", 10);
character.constitution = PlayerPrefs.GetInt(prefix + "Constitution", 10);
character.wisdom = PlayerPrefs.GetInt(prefix + "Wisdom", 10);
character.perception = PlayerPrefs.GetInt(prefix + "Perception", 10);
character.gold = PlayerPrefs.GetInt(prefix + "Gold", 25);
playerPrefTeam.Add(character);
}
}
return playerPrefTeam.Count > 0 ? playerPrefTeam : null;
}
private Vector2Int FindValidMarkerPosition(Vector2Int teamPosition, float perceptionRange)
{
int maxAttempts = 20;
for (int i = 0; i < maxAttempts; i++)
{
// Generate random position within perception range
float angle = Random.Range(0f, 360f) * Mathf.Deg2Rad;
float distance = Random.Range(minDistanceFromTeam, Mathf.Min(maxDistanceFromTeam, perceptionRange));
Vector2Int candidatePosition = new Vector2Int(
teamPosition.x + Mathf.RoundToInt(Mathf.Cos(angle) * distance),
teamPosition.y + Mathf.RoundToInt(Mathf.Sin(angle) * distance)
);
// Check if position is valid
if (IsValidMarkerPosition(candidatePosition))
{
return candidatePosition;
}
}
return Vector2Int.zero; // Failed to find valid position
}
private bool IsValidMarkerPosition(Vector2Int position)
{
// Check if position is already occupied
if (occupiedPositions.Contains(position))
{
return false;
}
// Check if position is valid on the map
if (mapMaker != null && mapMaker.GetMapData() != null)
{
return mapMaker.GetMapData().IsValidPosition(position.x, position.y);
}
return true; // Default to valid if no map check available
}
private GameObject CreateMarkerObject(Vector2Int position, TravelEvent travelEvent)
{
GameObject markerObject;
if (eventMarkerPrefab != null)
{
markerObject = Instantiate(eventMarkerPrefab);
}
else
{
// Create simple sphere marker
markerObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
markerObject.transform.localScale = Vector3.one * markerSize;
// Remove collider to avoid interference
var collider = markerObject.GetComponent();
if (collider != null)
{
DestroyImmediate(collider);
}
}
// Position the marker
Vector3 worldPosition = GetWorldPosition(position);
markerObject.transform.position = worldPosition;
// Apply material and color
ApplyMarkerMaterial(markerObject);
// Add tooltip component for hover information using reflection (compilation-safe)
var tooltipType = System.Type.GetType("EventMarkerTooltip");
if (tooltipType != null)
{
var tooltip = markerObject.AddComponent(tooltipType) as MonoBehaviour;
if (tooltip != null)
{
// Use reflection to call Initialize method
var initMethod = tooltipType.GetMethod("Initialize");
initMethod?.Invoke(tooltip, new object[] { travelEvent });
}
}
else if (showDebugInfo)
{
Debug.Log("EventMarkerTooltip component not found - tooltips will not be available");
}
// Set name and tag
markerObject.name = "EventMarker";
// Use default "Untagged" tag to avoid tag definition errors
markerObject.tag = "Untagged";
return markerObject;
}
private Vector3 GetWorldPosition(Vector2Int mapPosition)
{
// Convert map coordinates to world position
float tileSize = 1f;
if (mapMaker?.mapVisualizer != null)
{
tileSize = mapMaker.mapVisualizer.tileSize;
}
return new Vector3(
mapPosition.x * tileSize,
markerHeight,
mapPosition.y * tileSize
);
}
private void ApplyMarkerMaterial(GameObject markerObject)
{
Renderer renderer = markerObject.GetComponent();
if (renderer == null) return;
if (eventMarkerMaterial != null)
{
renderer.material = eventMarkerMaterial;
}
else
{
// Create a simple red material
Material material = new Material(Shader.Find("Universal Render Pipeline/Lit"));
if (material.shader == null)
{
material = new Material(Shader.Find("Standard"));
}
material.color = eventMarkerColor;
// Add emission for visibility
if (material.HasProperty("_EmissionColor"))
{
material.EnableKeyword("_EMISSION");
material.SetColor("_EmissionColor", eventMarkerColor * 0.5f);
}
renderer.material = material;
}
}
private void CleanupExpiredMarkers()
{
var markersToRemove = activeMarkers.Where(m =>
!m.isActive ||
m.gameObject == null ||
(Time.time - m.spawnTime) > markerLifetime
).ToList();
foreach (var marker in markersToRemove)
{
RemoveMarker(marker);
}
}
private void UpdateMarkerPulsing()
{
float pulseValue = (Mathf.Sin(Time.time * pulseSpeed) + 1f) * 0.5f; // 0 to 1
float scale = 1f + (pulseValue * pulseIntensity);
foreach (var marker in activeMarkers)
{
if (marker.isActive && marker.gameObject != null)
{
marker.gameObject.transform.localScale = Vector3.one * (markerSize * scale);
}
}
}
private void RemoveMarker(EventMarker marker)
{
if (marker.gameObject != null)
{
DestroyImmediate(marker.gameObject);
}
activeMarkers.Remove(marker);
occupiedPositions.Remove(marker.position);
marker.isActive = false;
}
///
/// Gets the number of currently active event markers
///
public int GetActiveMarkerCount()
{
return activeMarkers.Count(m => m.isActive);
}
///
/// Debug method to show all active markers
///
[ContextMenu("Show Active Markers")]
public void ShowActiveMarkers()
{
Debug.Log($"=== Active Event Markers ({activeMarkers.Count}) ===");
foreach (var marker in activeMarkers)
{
if (marker.isActive)
{
float age = Time.time - marker.spawnTime;
Debug.Log($"- {marker.travelEvent.eventName} at {marker.position} (age: {age:F1}s)");
}
}
}
}
///
/// Data structure for tracking active event markers
///
[System.Serializable]
public class EventMarker
{
public GameObject gameObject;
public TravelEvent travelEvent;
public Vector2Int position;
public float spawnTime;
public bool isActive;
}