| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- using UnityEngine;
- using System.Collections.Generic;
- using System.Linq;
- /// <summary>
- /// Manages spottable event markers that appear as red dots within the team's perception range
- /// </summary>
- 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<EventMarker> activeMarkers = new List<EventMarker>();
- private List<Vector2Int> occupiedPositions = new List<Vector2Int>();
-
- 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<TeamPerceptionVisualizer>();
- teamPlacement = FindFirstObjectByType<SimpleTeamPlacement>();
- eventSystem = FindFirstObjectByType<TravelEventSystem>();
- mapMaker = FindFirstObjectByType<MapMaker2>();
-
- 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();
- }
- }
-
- /// <summary>
- /// Attempts to spawn a spottable event marker within perception range
- /// </summary>
- /// <param name="travelEvent">The event to create a marker for</param>
- /// <returns>True if marker was successfully spawned</returns>
- 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);
- 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;
- }
-
- /// <summary>
- /// Checks if the team is close enough to trigger an event marker
- /// </summary>
- /// <param name="teamPosition">Current team position</param>
- /// <returns>The event to trigger, or null if none</returns>
- 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;
- }
-
- /// <summary>
- /// Removes all active event markers
- /// </summary>
- 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<TeamCharacter> 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<TeamCharacter> GetCurrentTeamMembers()
- {
- // Use the same logic as TeamPerceptionVisualizer
- var teamSelectScript = FindFirstObjectByType<MainTeamSelectScript>();
- var gameStateManager = FindFirstObjectByType<GameStateManager>();
-
- // 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<TeamCharacter>();
- 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<TeamCharacter>();
- 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)
- {
- 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<Collider>();
- if (collider != null)
- {
- DestroyImmediate(collider);
- }
- }
-
- // Position the marker
- Vector3 worldPosition = GetWorldPosition(position);
- markerObject.transform.position = worldPosition;
-
- // Apply material and color
- ApplyMarkerMaterial(markerObject);
-
- // 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<Renderer>();
- 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;
- }
-
- /// <summary>
- /// Gets the number of currently active event markers
- /// </summary>
- public int GetActiveMarkerCount()
- {
- return activeMarkers.Count(m => m.isActive);
- }
-
- /// <summary>
- /// Debug method to show all active markers
- /// </summary>
- [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)");
- }
- }
- }
- }
- /// <summary>
- /// Data structure for tracking active event markers
- /// </summary>
- [System.Serializable]
- public class EventMarker
- {
- public GameObject gameObject;
- public TravelEvent travelEvent;
- public Vector2Int position;
- public float spawnTime;
- public bool isActive;
- }
|