TravelEventSystem.cs 17 KB

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