TravelEventSystem.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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.15f; // 15% chance per tile by default
  14. [Range(1, 10)]
  15. public int tilesPerEventCheck = 3; // Check for events every N tiles
  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. // Component references
  26. private TeamTravelSystem travelSystem;
  27. private MapMaker2 mapMaker;
  28. private SimpleTeamPlacement teamPlacement;
  29. private TravelEventUI eventUI;
  30. // Enhanced system will be linked after compilation
  31. private object enhancedEventSystem;
  32. void Start()
  33. {
  34. // Find required components
  35. travelSystem = FindFirstObjectByType<TeamTravelSystem>();
  36. mapMaker = FindFirstObjectByType<MapMaker2>();
  37. teamPlacement = FindFirstObjectByType<SimpleTeamPlacement>();
  38. eventUI = FindFirstObjectByType<TravelEventUI>();
  39. // Enhanced system will be found after compilation
  40. // enhancedEventSystem = FindFirstObjectByType<EnhancedTravelEventSystem>();
  41. globalEventHistory = new TravelEventHistory();
  42. if (showDebugLogs)
  43. {
  44. Debug.Log($"🎲 Travel Event System initialized with {availableEvents.Count} events");
  45. if (eventUI == null)
  46. Debug.LogWarning("⚠️ TravelEventUI not found. Event messages will only appear in console.");
  47. LogEventDistribution();
  48. }
  49. // Load default events if none are assigned
  50. if (availableEvents.Count == 0)
  51. {
  52. LoadDefaultEvents();
  53. }
  54. }
  55. void Update()
  56. {
  57. if (!enableEvents || travelSystem == null || !travelSystem.IsTraveling()) return;
  58. // Check if we should evaluate for events
  59. tilesTraveledSinceLastCheck++;
  60. if (tilesTraveledSinceLastCheck >= tilesPerEventCheck)
  61. {
  62. CheckForTravelEvent();
  63. tilesTraveledSinceLastCheck = 0;
  64. }
  65. }
  66. /// <summary>
  67. /// Main method to check if a travel event should occur
  68. /// </summary>
  69. public void CheckForTravelEvent()
  70. {
  71. if (!enableEvents) return;
  72. // Get current travel context
  73. currentContext = CreateCurrentContext();
  74. if (currentContext == null) return;
  75. // Calculate if an event should occur
  76. float eventRoll = Random.value;
  77. float eventThreshold = CalculateEventThreshold(currentContext);
  78. // Force event for testing
  79. if (forceNextEvent)
  80. {
  81. forceNextEvent = false;
  82. eventRoll = 0f; // Guarantee event
  83. eventThreshold = 1f;
  84. }
  85. if (eventRoll <= eventThreshold)
  86. {
  87. TriggerRandomEvent(currentContext);
  88. }
  89. }
  90. /// <summary>
  91. /// Force a specific event to occur (for testing or story purposes)
  92. /// </summary>
  93. public void TriggerSpecificEvent(TravelEvent eventToTrigger)
  94. {
  95. if (eventToTrigger == null) return;
  96. currentContext = CreateCurrentContext();
  97. if (currentContext == null) return;
  98. ExecuteEvent(eventToTrigger, currentContext);
  99. }
  100. /// <summary>
  101. /// Trigger a random event based on current context
  102. /// </summary>
  103. private void TriggerRandomEvent(TravelEventContext context)
  104. {
  105. var possibleEvents = GetPossibleEvents(context);
  106. if (possibleEvents.Count == 0)
  107. {
  108. if (showDebugLogs)
  109. Debug.Log("🎲 No valid events found for current context");
  110. return;
  111. }
  112. // Select event based on weighted chances
  113. TravelEvent selectedEvent = SelectWeightedEvent(possibleEvents, context);
  114. if (selectedEvent != null)
  115. {
  116. ExecuteEvent(selectedEvent, context);
  117. }
  118. }
  119. /// <summary>
  120. /// Execute a travel event and handle its results
  121. /// </summary>
  122. private void ExecuteEvent(TravelEvent travelEvent, TravelEventContext context)
  123. {
  124. if (showDebugLogs)
  125. {
  126. Debug.Log($"🎭 Executing event: {travelEvent.eventName} at {context.currentPosition}");
  127. }
  128. // Execute the event
  129. EventResult result = travelEvent.ExecuteEvent(context);
  130. if (!result.eventOccurred)
  131. {
  132. if (showDebugLogs)
  133. Debug.Log($"🎭 Event {travelEvent.eventName} chose not to occur");
  134. return;
  135. }
  136. // Record the event
  137. globalEventHistory.RecordEvent(travelEvent, context);
  138. // Display event message
  139. if (eventUI != null)
  140. {
  141. eventUI.ShowEventResult(result);
  142. }
  143. else
  144. {
  145. ShowEventMessage(result.resultMessage);
  146. }
  147. // Handle the event result
  148. HandleEventResult(result, context);
  149. }
  150. /// <summary>
  151. /// Handle the results of a travel event
  152. /// </summary>
  153. private void HandleEventResult(EventResult result, TravelEventContext context)
  154. {
  155. // Apply resource changes
  156. ApplyResourceChanges(result);
  157. // Handle special event types
  158. if (result.shouldStopTravel)
  159. {
  160. if (travelSystem != null)
  161. {
  162. // Stop current travel using the existing cancellation system
  163. travelSystem.CancelJourney();
  164. Debug.Log("🛑 Travel stopped due to event");
  165. }
  166. }
  167. if (result.startBattle)
  168. {
  169. HandleBattleEvent(result.battleData, context);
  170. }
  171. if (result.openTrading)
  172. {
  173. HandleTradingEvent(result.tradingData, context);
  174. }
  175. }
  176. /// <summary>
  177. /// Create travel event context from current game state
  178. /// </summary>
  179. private TravelEventContext CreateCurrentContext()
  180. {
  181. if (teamPlacement == null || mapMaker == null || travelSystem == null)
  182. return null;
  183. Vector2Int currentPos = teamPlacement.GetCurrentTeamPosition();
  184. var mapData = mapMaker.GetMapData();
  185. var currentTile = mapData.GetTile(currentPos.x, currentPos.y);
  186. var currentRoute = travelSystem.GetCurrentRoute();
  187. var context = new TravelEventContext(currentPos, currentTile, currentRoute)
  188. {
  189. dayOfJourney = 1, // This could be tracked elsewhere
  190. timeOfDay = 12f, // This could be from a day/night system
  191. teamGold = 100, // This should come from actual team resources
  192. teamFood = 50,
  193. eventHistory = globalEventHistory
  194. };
  195. // Set travel context
  196. if (currentRoute != null)
  197. {
  198. context.destination = travelSystem.GetPlannedDestination() ?? currentPos;
  199. context.isOnMainRoad = currentTile.featureType == FeatureType.Road;
  200. }
  201. return context;
  202. }
  203. /// <summary>
  204. /// Calculate the threshold for events to occur based on context
  205. /// </summary>
  206. private float CalculateEventThreshold(TravelEventContext context)
  207. {
  208. float threshold = baseEventChance;
  209. // Modify based on terrain (some areas are more eventful)
  210. switch (context.currentTile.terrainType)
  211. {
  212. case TerrainType.Forest:
  213. threshold *= 1.3f; // More events in forests
  214. break;
  215. case TerrainType.Mountain:
  216. threshold *= 1.4f; // Even more in mountains
  217. break;
  218. case TerrainType.Plains:
  219. threshold *= 0.8f; // Fewer in plains
  220. break;
  221. }
  222. // Modify based on features
  223. switch (context.currentTile.featureType)
  224. {
  225. case FeatureType.Road:
  226. threshold *= 0.7f; // Fewer events on roads (safer travel)
  227. break;
  228. case FeatureType.Town:
  229. case FeatureType.Village:
  230. threshold *= 0.5f; // Much fewer in settlements
  231. break;
  232. }
  233. return Mathf.Clamp01(threshold);
  234. }
  235. /// <summary>
  236. /// Get all events that can occur in the current context
  237. /// </summary>
  238. private List<TravelEvent> GetPossibleEvents(TravelEventContext context)
  239. {
  240. return availableEvents.Where(e => e != null && e.CanOccur(context)).ToList();
  241. }
  242. /// <summary>
  243. /// Select an event based on weighted probabilities
  244. /// </summary>
  245. private TravelEvent SelectWeightedEvent(List<TravelEvent> events, TravelEventContext context)
  246. {
  247. if (events.Count == 0) return null;
  248. if (events.Count == 1) return events[0];
  249. // Calculate weights
  250. var weights = events.Select(e => e.GetEventChance(context.currentTile)).ToList();
  251. float totalWeight = weights.Sum();
  252. if (totalWeight <= 0) return null;
  253. // Select based on weight
  254. float randomValue = Random.value * totalWeight;
  255. float currentWeight = 0f;
  256. for (int i = 0; i < events.Count; i++)
  257. {
  258. currentWeight += weights[i];
  259. if (randomValue <= currentWeight)
  260. {
  261. return events[i];
  262. }
  263. }
  264. return events.Last(); // Fallback
  265. }
  266. /// <summary>
  267. /// Apply resource changes from event results
  268. /// </summary>
  269. private void ApplyResourceChanges(EventResult result)
  270. {
  271. if (result.goldChange != 0)
  272. {
  273. // Apply gold change to team resources
  274. // This would integrate with your resource system
  275. Debug.Log($"💰 Gold changed by {result.goldChange}");
  276. }
  277. if (result.healthChange != 0)
  278. {
  279. // Apply health change to team
  280. // This would integrate with your character health system
  281. Debug.Log($"❤️ Health changed by {result.healthChange}");
  282. }
  283. if (result.foodChange != 0)
  284. {
  285. // Apply food change
  286. Debug.Log($"🍖 Food changed by {result.foodChange}");
  287. }
  288. }
  289. /// <summary>
  290. /// Handle battle events by setting up battle data
  291. /// </summary>
  292. private void HandleBattleEvent(BattleEventData battleData, TravelEventContext context)
  293. {
  294. if (battleData == null) return;
  295. // Try to use enhanced event system for player choice popup
  296. var enhancedSystem = FindFirstObjectByType(System.Type.GetType("EnhancedTravelEventSystem"));
  297. if (enhancedSystem != null)
  298. {
  299. Debug.Log("🎭 Using Enhanced Travel Event System for combat popup");
  300. // Use reflection to call the enhanced handler
  301. var method = enhancedSystem.GetType().GetMethod("HandleEnhancedBattleEvent");
  302. if (method != null)
  303. {
  304. string eventDescription = $"Your party encounters {battleData.enemyCount} {battleData.enemyType}!";
  305. method.Invoke(enhancedSystem, new object[] { battleData, context, eventDescription });
  306. return;
  307. }
  308. }
  309. // Fallback to original battle handling
  310. Debug.Log($"⚔️ Setting up battle: {battleData.enemyCount} {battleData.enemyType}(s)");
  311. // Enhanced battle setup using EnemyCharacterData if available
  312. if (battleData.enemyCharacterData != null)
  313. {
  314. Debug.Log($"🎯 Using enemy data: {battleData.enemyCharacterData.enemyName}");
  315. Debug.Log($"📊 Enemy stats: HP={battleData.enemyCharacterData.maxHealth}, " +
  316. $"AC={battleData.enemyCharacterData.armorClass}, " +
  317. $"Threat={battleData.enemyCharacterData.threatLevel}");
  318. // TODO: Enhanced battle setup with actual enemy data
  319. /*
  320. BattleSetupData.enemySelections.Clear();
  321. for (int i = 0; i < battleData.enemyCount; i++)
  322. {
  323. var enemyData = battleData.enemyCharacterData;
  324. BattleSetupData.enemySelections.Add(new CharacterSelection
  325. {
  326. characterName = $"{enemyData.enemyName}_{i+1}",
  327. weaponType = enemyData.preferredWeapon != null ? enemyData.preferredWeapon.weaponType.ToString() : "Sword",
  328. // Could also include:
  329. // health = enemyData.maxHealth,
  330. // armorClass = enemyData.armorClass,
  331. // threatLevel = enemyData.threatLevel
  332. });
  333. }
  334. */
  335. }
  336. else
  337. {
  338. // Fallback to string-based system
  339. Debug.Log("⚠️ No EnemyCharacterData found, using legacy string-based setup");
  340. /*
  341. BattleSetupData.enemySelections.Clear();
  342. for (int i = 0; i < battleData.enemyCount; i++)
  343. {
  344. BattleSetupData.enemySelections.Add(new CharacterSelection
  345. {
  346. characterName = $"{battleData.enemyType}_{i+1}",
  347. weaponType = "Sword" // Could be randomized
  348. });
  349. }
  350. */
  351. }
  352. }
  353. /// <summary>
  354. /// Handle trading events by opening shop interface
  355. /// </summary>
  356. private void HandleTradingEvent(TradingEventData tradingData, TravelEventContext context)
  357. {
  358. if (tradingData == null) return;
  359. Debug.Log($"🛒 Opening trading with {tradingData.merchantType}");
  360. // This would open your shop system
  361. // You could modify shop inventory based on merchantType and hasRareItems
  362. }
  363. /// <summary>
  364. /// Display event message to player
  365. /// </summary>
  366. private void ShowEventMessage(string message)
  367. {
  368. if (string.IsNullOrEmpty(message)) return;
  369. Debug.Log($"📜 EVENT: {message}");
  370. // Use UI system if available
  371. if (eventUI != null)
  372. {
  373. eventUI.ShowEventMessage(message);
  374. }
  375. }
  376. /// <summary>
  377. /// Load default events if none are assigned in inspector
  378. /// </summary>
  379. private void LoadDefaultEvents()
  380. {
  381. // Load default events from Resources folder
  382. TravelEvent[] defaultEvents = Resources.LoadAll<TravelEvent>("TravelEvents");
  383. availableEvents.AddRange(defaultEvents);
  384. if (showDebugLogs)
  385. {
  386. Debug.Log($"🔄 Loaded {defaultEvents.Length} default events from Resources");
  387. }
  388. }
  389. /// <summary>
  390. /// Debug method to show event distribution
  391. /// </summary>
  392. private void LogEventDistribution()
  393. {
  394. if (!showDebugLogs) return;
  395. var eventsByType = availableEvents.GroupBy(e => e.eventType);
  396. foreach (var group in eventsByType)
  397. {
  398. Debug.Log($"📊 {group.Key}: {group.Count()} events");
  399. }
  400. }
  401. /// <summary>
  402. /// Public method to trigger event check manually (for testing)
  403. /// </summary>
  404. [ContextMenu("Trigger Event Check")]
  405. public void TriggerEventCheck()
  406. {
  407. forceNextEvent = true;
  408. CheckForTravelEvent();
  409. }
  410. /// <summary>
  411. /// Reset event history (useful for testing)
  412. /// </summary>
  413. [ContextMenu("Reset Event History")]
  414. public void ResetEventHistory()
  415. {
  416. globalEventHistory = new TravelEventHistory();
  417. tilesTraveledSinceLastCheck = 0;
  418. Debug.Log("🔄 Event history reset");
  419. }
  420. }