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; }