TravelEventSystem.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. /// <summary>
  5. /// Main travel event system that manages when and where events occur during travel
  6. /// Integrates with the existing TeamTravelSystem
  7. /// </summary>
  8. public class TravelEventSystem : MonoBehaviour
  9. {
  10. [Header("Event System Settings")]
  11. public bool enableEvents = true;
  12. [Range(0f, 1f)]
  13. public float baseEventChance = 0.1f; // 10% chance per tile check (reduced from 15%)
  14. [Range(1, 10)]
  15. public int tilesPerEventCheck = 4; // Check for events every 4 tiles (increased from 3)
  16. [Header("Event Collections")]
  17. public List<TravelEvent> availableEvents = new List<TravelEvent>();
  18. [Header("Debug")]
  19. public bool showDebugLogs = true;
  20. public bool forceNextEvent = false; // For testing
  21. // Event tracking
  22. private TravelEventHistory globalEventHistory;
  23. private int tilesTraveledSinceLastCheck = 0;
  24. private TravelEventContext currentContext;
  25. private Vector2Int lastTeamPosition = Vector2Int.zero;
  26. private bool hasInitializedPosition = false;
  27. // Component references
  28. private TeamTravelSystem travelSystem;
  29. private MapMaker2 mapMaker;
  30. private SimpleTeamPlacement teamPlacement;
  31. private TravelEventUI eventUI;
  32. private MonoBehaviour markerVisualizer; // Use MonoBehaviour to avoid compilation issues
  33. // Enhanced system will be linked after compilation
  34. private object enhancedEventSystem;
  35. void Start()
  36. {
  37. // Find required components
  38. travelSystem = FindFirstObjectByType<TeamTravelSystem>();
  39. mapMaker = FindFirstObjectByType<MapMaker2>();
  40. teamPlacement = FindFirstObjectByType<SimpleTeamPlacement>();
  41. eventUI = FindFirstObjectByType<TravelEventUI>();
  42. // Find EventMarkerVisualizer using reflection to avoid compilation order issues
  43. var markerVisualizerComponents = FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None);
  44. markerVisualizer = System.Array.Find(markerVisualizerComponents,
  45. comp => comp.GetType().Name == "EventMarkerVisualizer");
  46. // Enhanced system will be found after compilation
  47. // enhancedEventSystem = FindFirstObjectByType<EnhancedTravelEventSystem>();
  48. globalEventHistory = new TravelEventHistory();
  49. if (showDebugLogs)
  50. {
  51. Debug.Log($"🎲 Travel Event System initialized with {availableEvents.Count} events");
  52. if (eventUI == null)
  53. Debug.LogWarning("⚠️ TravelEventUI not found. Event messages will only appear in console.");
  54. LogEventDistribution();
  55. }
  56. // Load default events if none are assigned
  57. if (availableEvents.Count == 0)
  58. {
  59. LoadDefaultEvents();
  60. }
  61. }
  62. void Update()
  63. {
  64. if (!enableEvents || travelSystem == null) return;
  65. // Track actual tile movement instead of frame-based checks
  66. Vector2Int currentTeamPosition = GetCurrentTeamPosition();
  67. if (!hasInitializedPosition)
  68. {
  69. lastTeamPosition = currentTeamPosition;
  70. hasInitializedPosition = true;
  71. return;
  72. }
  73. // Only check for events when the team actually moves to a new tile
  74. if (currentTeamPosition != lastTeamPosition)
  75. {
  76. tilesTraveledSinceLastCheck++;
  77. if (showDebugLogs)
  78. {
  79. Debug.Log($"🚶 Team moved from {lastTeamPosition} to {currentTeamPosition} (tiles since last check: {tilesTraveledSinceLastCheck})");
  80. }
  81. // Check for event marker triggers first
  82. CheckForEventMarkerTriggers(currentTeamPosition);
  83. lastTeamPosition = currentTeamPosition;
  84. // Check for events every N tiles traveled
  85. if (tilesTraveledSinceLastCheck >= tilesPerEventCheck)
  86. {
  87. if (showDebugLogs)
  88. {
  89. Debug.Log($"🎲 Checking for travel event after {tilesTraveledSinceLastCheck} tiles traveled");
  90. }
  91. CheckForTravelEvent();
  92. tilesTraveledSinceLastCheck = 0;
  93. }
  94. }
  95. }
  96. /// <summary>
  97. /// Main method to check if a travel event should occur
  98. /// </summary>
  99. public void CheckForTravelEvent()
  100. {
  101. if (!enableEvents) return;
  102. // Get current travel context
  103. currentContext = CreateCurrentContext();
  104. if (currentContext == null) return;
  105. // Calculate if an event should occur
  106. float eventRoll = Random.value;
  107. float eventThreshold = CalculateEventThreshold(currentContext);
  108. // Force event for testing
  109. if (forceNextEvent)
  110. {
  111. forceNextEvent = false;
  112. eventRoll = 0f; // Guarantee event
  113. eventThreshold = 1f;
  114. }
  115. if (eventRoll <= eventThreshold)
  116. {
  117. TriggerRandomEvent(currentContext);
  118. }
  119. }
  120. /// <summary>
  121. /// Force a specific event to occur (for testing or story purposes)
  122. /// </summary>
  123. public void TriggerSpecificEvent(TravelEvent eventToTrigger)
  124. {
  125. if (eventToTrigger == null) return;
  126. currentContext = CreateCurrentContext();
  127. if (currentContext == null) return;
  128. ExecuteEvent(eventToTrigger, currentContext);
  129. }
  130. /// <summary>
  131. /// Check if team has triggered any event markers
  132. /// </summary>
  133. private void CheckForEventMarkerTriggers(Vector2Int teamPosition)
  134. {
  135. if (markerVisualizer == null) return;
  136. // Use reflection to call CheckForMarkerTrigger method
  137. var method = markerVisualizer.GetType().GetMethod("CheckForMarkerTrigger");
  138. if (method != null)
  139. {
  140. var triggeredEvent = method.Invoke(markerVisualizer, new object[] { teamPosition }) as TravelEvent;
  141. if (triggeredEvent != null)
  142. {
  143. // Create context and execute the triggered event
  144. currentContext = CreateCurrentContext();
  145. if (currentContext != null)
  146. {
  147. ExecuteEvent(triggeredEvent, currentContext);
  148. }
  149. }
  150. }
  151. }
  152. /// <summary>
  153. /// Trigger a random event based on current context
  154. /// </summary>
  155. private void TriggerRandomEvent(TravelEventContext context)
  156. {
  157. var possibleEvents = GetPossibleEvents(context);
  158. if (possibleEvents.Count == 0)
  159. {
  160. if (showDebugLogs)
  161. Debug.Log("🎲 No valid events found for current context");
  162. return;
  163. }
  164. // Separate events by trigger type
  165. var automaticEvents = possibleEvents.Where(e => e.triggerType == EventTriggerType.Automatic).ToList();
  166. var spottableEvents = possibleEvents.Where(e => e.triggerType == EventTriggerType.Spottable).ToList();
  167. // Try to trigger automatic event first
  168. if (automaticEvents.Count > 0)
  169. {
  170. TravelEvent selectedEvent = SelectWeightedEvent(automaticEvents, context);
  171. if (selectedEvent != null)
  172. {
  173. ExecuteEvent(selectedEvent, context);
  174. return;
  175. }
  176. }
  177. // Try to spawn spottable event markers
  178. if (spottableEvents.Count > 0 && markerVisualizer != null)
  179. {
  180. TravelEvent selectedSpottableEvent = SelectWeightedEvent(spottableEvents, context);
  181. if (selectedSpottableEvent != null)
  182. {
  183. // Use reflection to call TrySpawnEventMarker method
  184. var spawnMethod = markerVisualizer.GetType().GetMethod("TrySpawnEventMarker");
  185. if (spawnMethod != null)
  186. {
  187. var markerSpawned = (bool)spawnMethod.Invoke(markerVisualizer, new object[] { selectedSpottableEvent });
  188. if (markerSpawned && showDebugLogs)
  189. {
  190. Debug.Log($"🔴 Spawned spottable event marker: {selectedSpottableEvent.eventName}");
  191. }
  192. }
  193. }
  194. }
  195. }
  196. /// <summary>
  197. /// Execute a travel event and handle its results
  198. /// </summary>
  199. private void ExecuteEvent(TravelEvent travelEvent, TravelEventContext context)
  200. {
  201. if (showDebugLogs)
  202. {
  203. Debug.Log($"🎭 Executing event: {travelEvent.eventName} at {context.currentPosition}");
  204. }
  205. // Execute the event
  206. EventResult result = travelEvent.ExecuteEvent(context);
  207. if (!result.eventOccurred)
  208. {
  209. if (showDebugLogs)
  210. Debug.Log($"🎭 Event {travelEvent.eventName} chose not to occur");
  211. return;
  212. }
  213. // Record the event
  214. globalEventHistory.RecordEvent(travelEvent, context);
  215. // Display event message (unless suppressed by popup system)
  216. if (!result.suppressMessage)
  217. {
  218. if (eventUI != null)
  219. {
  220. eventUI.ShowEventResult(result);
  221. }
  222. else
  223. {
  224. ShowEventMessage(result.resultMessage);
  225. }
  226. }
  227. // Handle the event result
  228. HandleEventResult(result, context);
  229. }
  230. /// <summary>
  231. /// Handle the results of a travel event
  232. /// </summary>
  233. private void HandleEventResult(EventResult result, TravelEventContext context)
  234. {
  235. // Apply resource changes
  236. ApplyResourceChanges(result);
  237. // Handle special event types
  238. if (result.shouldStopTravel)
  239. {
  240. if (travelSystem != null)
  241. {
  242. // Stop current travel using the existing cancellation system
  243. travelSystem.CancelJourney();
  244. Debug.Log("🛑 Travel stopped due to event");
  245. }
  246. }
  247. if (result.startBattle)
  248. {
  249. HandleBattleEvent(result.battleData, context);
  250. }
  251. if (result.openTrading)
  252. {
  253. HandleTradingEvent(result.tradingData, context);
  254. }
  255. }
  256. /// <summary>
  257. /// Create travel event context from current game state
  258. /// </summary>
  259. private TravelEventContext CreateCurrentContext()
  260. {
  261. if (teamPlacement == null || mapMaker == null || travelSystem == null)
  262. return null;
  263. Vector2Int currentPos = teamPlacement.GetCurrentTeamPosition();
  264. var mapData = mapMaker.GetMapData();
  265. var currentTile = mapData.GetTile(currentPos.x, currentPos.y);
  266. var currentRoute = travelSystem.GetCurrentRoute();
  267. var context = new TravelEventContext(currentPos, currentTile, currentRoute)
  268. {
  269. dayOfJourney = 1, // This could be tracked elsewhere
  270. timeOfDay = 12f, // This could be from a day/night system
  271. teamGold = 100, // This should come from actual team resources
  272. teamFood = 50,
  273. eventHistory = globalEventHistory
  274. };
  275. // Set travel context
  276. if (currentRoute != null)
  277. {
  278. context.destination = travelSystem.GetPlannedDestination() ?? currentPos;
  279. context.isOnMainRoad = currentTile.featureType == FeatureType.Road;
  280. }
  281. return context;
  282. }
  283. /// <summary>
  284. /// Calculate the threshold for events to occur based on context
  285. /// </summary>
  286. private float CalculateEventThreshold(TravelEventContext context)
  287. {
  288. float threshold = baseEventChance;
  289. // Modify based on terrain (some areas are more eventful)
  290. switch (context.currentTile.terrainType)
  291. {
  292. case TerrainType.Forest:
  293. threshold *= 1.3f; // More events in forests
  294. break;
  295. case TerrainType.Mountain:
  296. threshold *= 1.4f; // Even more in mountains
  297. break;
  298. case TerrainType.Plains:
  299. threshold *= 0.8f; // Fewer in plains
  300. break;
  301. }
  302. // Modify based on features
  303. switch (context.currentTile.featureType)
  304. {
  305. case FeatureType.Road:
  306. threshold *= 0.7f; // Fewer events on roads (safer travel)
  307. break;
  308. case FeatureType.Town:
  309. case FeatureType.Village:
  310. threshold *= 0.5f; // Much fewer in settlements
  311. break;
  312. }
  313. return Mathf.Clamp01(threshold);
  314. }
  315. /// <summary>
  316. /// Get all events that can occur in the current context
  317. /// </summary>
  318. private List<TravelEvent> GetPossibleEvents(TravelEventContext context)
  319. {
  320. return availableEvents.Where(e => e != null && e.CanOccur(context)).ToList();
  321. }
  322. /// <summary>
  323. /// Select an event based on weighted probabilities
  324. /// </summary>
  325. private TravelEvent SelectWeightedEvent(List<TravelEvent> events, TravelEventContext context)
  326. {
  327. if (events.Count == 0) return null;
  328. if (events.Count == 1) return events[0];
  329. // Calculate weights
  330. var weights = events.Select(e => e.GetEventChance(context.currentTile)).ToList();
  331. float totalWeight = weights.Sum();
  332. if (totalWeight <= 0) return null;
  333. // Select based on weight
  334. float randomValue = Random.value * totalWeight;
  335. float currentWeight = 0f;
  336. for (int i = 0; i < events.Count; i++)
  337. {
  338. currentWeight += weights[i];
  339. if (randomValue <= currentWeight)
  340. {
  341. return events[i];
  342. }
  343. }
  344. return events.Last(); // Fallback
  345. }
  346. /// <summary>
  347. /// Apply resource changes from event results
  348. /// </summary>
  349. private void ApplyResourceChanges(EventResult result)
  350. {
  351. if (result.goldChange != 0)
  352. {
  353. // Apply gold change to team resources
  354. // This would integrate with your resource system
  355. Debug.Log($"💰 Gold changed by {result.goldChange}");
  356. }
  357. if (result.healthChange != 0)
  358. {
  359. // Apply health change to team
  360. // This would integrate with your character health system
  361. Debug.Log($"❤️ Health changed by {result.healthChange}");
  362. }
  363. if (result.foodChange != 0)
  364. {
  365. // Apply food change
  366. Debug.Log($"🍖 Food changed by {result.foodChange}");
  367. }
  368. }
  369. /// <summary>
  370. /// Handle battle events by setting up battle data
  371. /// </summary>
  372. private void HandleBattleEvent(BattleEventData battleData, TravelEventContext context)
  373. {
  374. if (battleData == null) return;
  375. // Try to use enhanced event system for player choice popup
  376. var enhancedSystem = FindFirstObjectByType(System.Type.GetType("EnhancedTravelEventSystem"));
  377. if (enhancedSystem != null)
  378. {
  379. Debug.Log("🎭 Using Enhanced Travel Event System for combat popup");
  380. // Use reflection to call the enhanced handler
  381. var method = enhancedSystem.GetType().GetMethod("HandleEnhancedBattleEvent");
  382. if (method != null)
  383. {
  384. string eventDescription = $"Your party encounters {battleData.enemyCount} {battleData.enemyType}!";
  385. method.Invoke(enhancedSystem, new object[] { battleData, context, eventDescription });
  386. return;
  387. }
  388. }
  389. // Fallback to original battle handling
  390. Debug.Log($"⚔️ Setting up battle: {battleData.enemyCount} {battleData.enemyType}(s)");
  391. // Enhanced battle setup using EnemyCharacterData if available
  392. if (battleData.enemyCharacterData != null)
  393. {
  394. Debug.Log($"🎯 Using enemy data: {battleData.enemyCharacterData.enemyName}");
  395. Debug.Log($"📊 Enemy stats: HP={battleData.enemyCharacterData.maxHealth}, " +
  396. $"AC={battleData.enemyCharacterData.armorClass}, " +
  397. $"Threat={battleData.enemyCharacterData.threatLevel}");
  398. // TODO: Enhanced battle setup with actual enemy data
  399. /*
  400. BattleSetupData.enemySelections.Clear();
  401. for (int i = 0; i < battleData.enemyCount; i++)
  402. {
  403. var enemyData = battleData.enemyCharacterData;
  404. BattleSetupData.enemySelections.Add(new CharacterSelection
  405. {
  406. characterName = $"{enemyData.enemyName}_{i+1}",
  407. weaponType = enemyData.preferredWeapon != null ? enemyData.preferredWeapon.weaponType.ToString() : "Sword",
  408. // Could also include:
  409. // health = enemyData.maxHealth,
  410. // armorClass = enemyData.armorClass,
  411. // threatLevel = enemyData.threatLevel
  412. });
  413. }
  414. */
  415. }
  416. else
  417. {
  418. // Fallback to string-based system
  419. Debug.Log("⚠️ No EnemyCharacterData found, using legacy string-based setup");
  420. /*
  421. BattleSetupData.enemySelections.Clear();
  422. for (int i = 0; i < battleData.enemyCount; i++)
  423. {
  424. BattleSetupData.enemySelections.Add(new CharacterSelection
  425. {
  426. characterName = $"{battleData.enemyType}_{i+1}",
  427. weaponType = "Sword" // Could be randomized
  428. });
  429. }
  430. */
  431. }
  432. }
  433. /// <summary>
  434. /// Handle trading events by opening shop interface
  435. /// </summary>
  436. private void HandleTradingEvent(TradingEventData tradingData, TravelEventContext context)
  437. {
  438. if (tradingData == null) return;
  439. Debug.Log($"🛒 Opening trading with {tradingData.merchantType}");
  440. // This would open your shop system
  441. // You could modify shop inventory based on merchantType and hasRareItems
  442. }
  443. /// <summary>
  444. /// Display event message to player
  445. /// </summary>
  446. private void ShowEventMessage(string message)
  447. {
  448. if (string.IsNullOrEmpty(message)) return;
  449. Debug.Log($"📜 EVENT: {message}");
  450. // Use UI system if available
  451. if (eventUI != null)
  452. {
  453. eventUI.ShowEventMessage(message);
  454. }
  455. }
  456. /// <summary>
  457. /// Load default events if none are assigned in inspector
  458. /// </summary>
  459. private void LoadDefaultEvents()
  460. {
  461. // Load default events from Resources folder
  462. TravelEvent[] defaultEvents = Resources.LoadAll<TravelEvent>("TravelEvents");
  463. availableEvents.AddRange(defaultEvents);
  464. if (showDebugLogs)
  465. {
  466. Debug.Log($"🔄 Loaded {defaultEvents.Length} default events from Resources");
  467. }
  468. }
  469. /// <summary>
  470. /// Debug method to show event distribution
  471. /// </summary>
  472. private void LogEventDistribution()
  473. {
  474. if (!showDebugLogs) return;
  475. var eventsByType = availableEvents.GroupBy(e => e.eventType);
  476. foreach (var group in eventsByType)
  477. {
  478. Debug.Log($"📊 {group.Key}: {group.Count()} events");
  479. }
  480. }
  481. /// <summary>
  482. /// Public method to trigger event check manually (for testing)
  483. /// </summary>
  484. [ContextMenu("Trigger Event Check")]
  485. public void TriggerEventCheck()
  486. {
  487. forceNextEvent = true;
  488. CheckForTravelEvent();
  489. }
  490. /// <summary>
  491. /// Reset event history (useful for testing)
  492. /// </summary>
  493. [ContextMenu("Reset Event History")]
  494. public void ResetEventHistory()
  495. {
  496. globalEventHistory = new TravelEventHistory();
  497. tilesTraveledSinceLastCheck = 0;
  498. Debug.Log("🔄 Event history reset");
  499. }
  500. /// <summary>
  501. /// Get the current team position in tile coordinates
  502. /// </summary>
  503. private Vector2Int GetCurrentTeamPosition()
  504. {
  505. if (teamPlacement == null)
  506. return Vector2Int.zero;
  507. var teamMarker = teamPlacement.GetTeamMarker();
  508. if (teamMarker == null)
  509. return Vector2Int.zero;
  510. // Convert world position to tile position
  511. Vector3 worldPos = teamMarker.transform.position;
  512. return new Vector2Int(
  513. Mathf.RoundToInt(worldPos.x),
  514. Mathf.RoundToInt(worldPos.z)
  515. );
  516. }
  517. }