QuestManager.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System;
  5. /// <summary>
  6. /// Central manager for quest system - handles active quests, progress tracking, and rewards
  7. /// </summary>
  8. public class QuestManager : MonoBehaviour
  9. {
  10. public static QuestManager Instance { get; private set; }
  11. [Header("Quest Configuration")]
  12. [Tooltip("Maximum number of active quests")]
  13. public int maxActiveQuests = 5;
  14. [Tooltip("Game time speed multiplier (how fast time passes)")]
  15. public float gameTimeSpeed = 1f;
  16. [Header("Active Quests")]
  17. [SerializeField] private List<ActiveQuest> activeQuests = new List<ActiveQuest>();
  18. [SerializeField] private List<ActiveQuest> completedQuests = new List<ActiveQuest>();
  19. [SerializeField] private List<ActiveQuest> failedQuests = new List<ActiveQuest>();
  20. [Header("Renown System")]
  21. [SerializeField] private int currentRenown = 0;
  22. [Header("Debug")]
  23. public bool enableDebugLogs = false;
  24. // Events
  25. public event Action<ActiveQuest> OnQuestAccepted;
  26. public event Action<ActiveQuest, List<QuestReward>> OnQuestCompleted;
  27. public event Action<ActiveQuest> OnQuestFailed;
  28. public event Action<ActiveQuest> OnQuestAbandoned;
  29. public event Action<ActiveQuest, QuestGoal> OnQuestGoalCompleted;
  30. public event Action<int> OnRenownChanged;
  31. private void Awake()
  32. {
  33. if (Instance == null)
  34. {
  35. Instance = this;
  36. DontDestroyOnLoad(gameObject);
  37. LoadQuestData();
  38. }
  39. else
  40. {
  41. Destroy(gameObject);
  42. }
  43. }
  44. private void Update()
  45. {
  46. UpdateActiveQuests();
  47. }
  48. private void UpdateActiveQuests()
  49. {
  50. if (activeQuests.Count == 0) return;
  51. float deltaTimeHours = Time.deltaTime * gameTimeSpeed;
  52. // Update each active quest
  53. for (int i = activeQuests.Count - 1; i >= 0; i--)
  54. {
  55. var quest = activeQuests[i];
  56. quest.UpdateQuest(deltaTimeHours);
  57. // Remove completed or failed quests from active list
  58. if (quest.status != QuestStatus.Active)
  59. {
  60. activeQuests.RemoveAt(i);
  61. }
  62. }
  63. }
  64. /// <summary>
  65. /// Accept a new quest
  66. /// </summary>
  67. public bool AcceptQuest(Quest questData)
  68. {
  69. if (questData == null)
  70. {
  71. LogDebug("Cannot accept null quest");
  72. return false;
  73. }
  74. if (activeQuests.Count >= maxActiveQuests)
  75. {
  76. LogDebug($"Cannot accept quest: Maximum active quests reached ({maxActiveQuests})");
  77. return false;
  78. }
  79. // Check if already have this quest active
  80. if (activeQuests.Any(q => q.questData == questData))
  81. {
  82. LogDebug($"Quest '{questData.questTitle}' is already active");
  83. return false;
  84. }
  85. // Check requirements
  86. if (!MeetsRequirements(questData))
  87. {
  88. LogDebug($"Does not meet requirements for quest '{questData.questTitle}'");
  89. return false;
  90. }
  91. var activeQuest = questData.CreateActiveQuest();
  92. activeQuests.Add(activeQuest);
  93. LogDebug($"✅ Accepted quest: {questData.questTitle}");
  94. OnQuestAccepted?.Invoke(activeQuest);
  95. SaveQuestData();
  96. return true;
  97. }
  98. /// <summary>
  99. /// Abandon an active quest
  100. /// </summary>
  101. public bool AbandonQuest(string questId)
  102. {
  103. var quest = activeQuests.FirstOrDefault(q => q.questId == questId);
  104. if (quest == null) return false;
  105. quest.status = QuestStatus.Abandoned;
  106. activeQuests.Remove(quest);
  107. LogDebug($"🚫 Abandoned quest: {quest.questData.questTitle}");
  108. OnQuestAbandoned?.Invoke(quest);
  109. SaveQuestData();
  110. return true;
  111. }
  112. /// <summary>
  113. /// Progress quest goals - called by game events
  114. /// </summary>
  115. public void ProgressQuest(QuestGoalType goalType, string targetName = "", int amount = 1)
  116. {
  117. foreach (var quest in activeQuests)
  118. {
  119. if (quest.ProgressGoal(goalType, targetName, amount))
  120. {
  121. // Check if any goals were just completed
  122. foreach (var goal in quest.activeGoals)
  123. {
  124. if (goal.IsCompleted && goal.currentProgress == goal.targetCount)
  125. {
  126. OnQuestGoalCompleted?.Invoke(quest, goal);
  127. }
  128. }
  129. }
  130. }
  131. SaveQuestData();
  132. }
  133. /// <summary>
  134. /// Try to complete quests at current position
  135. /// </summary>
  136. public List<ActiveQuest> TryCompleteQuestsAtPosition(Vector2Int position)
  137. {
  138. var completedQuests = new List<ActiveQuest>();
  139. foreach (var quest in activeQuests.ToList())
  140. {
  141. if (quest.TryCompleteQuest(position))
  142. {
  143. completedQuests.Add(quest);
  144. }
  145. }
  146. return completedQuests;
  147. }
  148. /// <summary>
  149. /// Called when a quest is completed
  150. /// </summary>
  151. public void CompleteQuest(ActiveQuest quest)
  152. {
  153. if (quest == null) return;
  154. // Move to completed list
  155. completedQuests.Add(quest);
  156. // Generate rewards list
  157. var rewards = GenerateQuestRewards(quest);
  158. // Award rewards
  159. AwardQuestRewards(quest);
  160. // Trigger event with rewards
  161. OnQuestCompleted?.Invoke(quest, rewards);
  162. SaveQuestData();
  163. }
  164. /// <summary>
  165. /// Called when a quest fails
  166. /// </summary>
  167. public void FailQuest(ActiveQuest quest)
  168. {
  169. if (quest == null) return;
  170. // Move to failed list
  171. failedQuests.Add(quest);
  172. // Apply penalties
  173. ApplyQuestPenalties(quest);
  174. // Trigger event
  175. OnQuestFailed?.Invoke(quest);
  176. SaveQuestData();
  177. }
  178. private List<QuestReward> GenerateQuestRewards(ActiveQuest quest)
  179. {
  180. var rewards = new List<QuestReward>();
  181. var questData = quest.questData;
  182. // Add gold reward
  183. if (questData.goldReward > 0)
  184. {
  185. rewards.Add(new QuestReward
  186. {
  187. type = QuestRewardType.Gold,
  188. amount = questData.goldReward
  189. });
  190. }
  191. // Add silver reward
  192. if (questData.silverReward > 0)
  193. {
  194. rewards.Add(new QuestReward
  195. {
  196. type = QuestRewardType.Silver,
  197. amount = questData.silverReward
  198. });
  199. }
  200. // Add copper reward
  201. if (questData.copperReward > 0)
  202. {
  203. rewards.Add(new QuestReward
  204. {
  205. type = QuestRewardType.Copper,
  206. amount = questData.copperReward
  207. });
  208. }
  209. // Add renown reward
  210. if (questData.renownReward > 0)
  211. {
  212. rewards.Add(new QuestReward
  213. {
  214. type = QuestRewardType.Renown,
  215. amount = questData.renownReward
  216. });
  217. }
  218. // Add item rewards
  219. foreach (var itemName in questData.itemRewards)
  220. {
  221. if (!string.IsNullOrEmpty(itemName))
  222. {
  223. rewards.Add(new QuestReward
  224. {
  225. type = QuestRewardType.Item,
  226. amount = 1,
  227. itemName = itemName
  228. });
  229. }
  230. }
  231. return rewards;
  232. }
  233. private void AwardQuestRewards(ActiveQuest quest)
  234. {
  235. var questData = quest.questData;
  236. // Award money to team
  237. var gameStateManager = GameStateManager.Instance;
  238. if (gameStateManager?.savedTeam != null)
  239. {
  240. foreach (var character in gameStateManager.savedTeam)
  241. {
  242. if (character != null)
  243. {
  244. character.gold += questData.goldReward;
  245. character.silver += questData.silverReward;
  246. character.copper += questData.copperReward;
  247. }
  248. }
  249. }
  250. // Award renown
  251. ChangeRenown(questData.renownReward);
  252. // Award items (TODO: implement item reward system)
  253. foreach (var itemName in questData.itemRewards)
  254. {
  255. LogDebug($"🎁 Reward item: {itemName} (TODO: implement item awards)");
  256. }
  257. LogDebug($"💰 Quest rewards: {questData.goldReward}g, {questData.silverReward}s, {questData.copperReward}c, {questData.renownReward} renown");
  258. }
  259. private void ApplyQuestPenalties(ActiveQuest quest)
  260. {
  261. var questData = quest.questData;
  262. // Apply renown penalty
  263. ChangeRenown(-questData.renownPenalty);
  264. LogDebug($"💔 Quest penalties: -{questData.renownPenalty} renown");
  265. }
  266. private void ChangeRenown(int amount)
  267. {
  268. currentRenown = Mathf.Max(0, currentRenown + amount);
  269. OnRenownChanged?.Invoke(currentRenown);
  270. // Save to GameStateManager
  271. if (GameStateManager.Instance != null)
  272. {
  273. // TODO: Add renown field to GameStateManager
  274. }
  275. }
  276. private bool MeetsRequirements(Quest questData)
  277. {
  278. // Check renown requirement
  279. if (currentRenown < questData.minimumRenown)
  280. return false;
  281. // Check prerequisite quests
  282. foreach (var prereq in questData.prerequisiteQuests)
  283. {
  284. if (!completedQuests.Any(q => q.questData.questTitle == prereq))
  285. return false;
  286. }
  287. return true;
  288. }
  289. /// <summary>
  290. /// Get all active quests
  291. /// </summary>
  292. public List<ActiveQuest> GetActiveQuests() => new List<ActiveQuest>(activeQuests);
  293. /// <summary>
  294. /// Get quest by ID
  295. /// </summary>
  296. public ActiveQuest GetQuestById(string questId) => activeQuests.FirstOrDefault(q => q.questId == questId);
  297. /// <summary>
  298. /// Get current renown level
  299. /// </summary>
  300. public int GetRenown() => currentRenown;
  301. /// <summary>
  302. /// Check if can accept more quests
  303. /// </summary>
  304. public bool CanAcceptMoreQuests() => activeQuests.Count < maxActiveQuests;
  305. /// <summary>
  306. /// Clear all quest data - useful for new games
  307. /// </summary>
  308. public void ClearAllQuests()
  309. {
  310. LogDebug("Clearing all quest data for new game");
  311. activeQuests.Clear();
  312. completedQuests.Clear();
  313. failedQuests.Clear();
  314. currentRenown = 0;
  315. // Clear saved data
  316. PlayerPrefs.DeleteKey("QuestManager_SaveData");
  317. PlayerPrefs.Save();
  318. // Trigger events for UI updates
  319. OnRenownChanged?.Invoke(currentRenown);
  320. LogDebug("All quest data cleared successfully");
  321. }
  322. private void SaveQuestData()
  323. {
  324. try
  325. {
  326. var saveData = new QuestSaveData
  327. {
  328. currentRenown = this.currentRenown,
  329. activeQuests = this.activeQuests.Select(q => q.ToSaveData()).ToList(),
  330. completedQuests = this.completedQuests.Select(q => q.ToSaveData()).ToList(),
  331. failedQuests = this.failedQuests.Select(q => q.ToSaveData()).ToList()
  332. };
  333. string json = JsonUtility.ToJson(saveData, true);
  334. PlayerPrefs.SetString("QuestManager_SaveData", json);
  335. PlayerPrefs.Save();
  336. LogDebug("Quest data saved successfully");
  337. }
  338. catch (System.Exception ex)
  339. {
  340. Debug.LogError($"Failed to save quest data: {ex.Message}");
  341. }
  342. }
  343. public void LoadQuestData()
  344. {
  345. try
  346. {
  347. // Check if this is a new game and clear quest data if so
  348. if (GameStateManager.Instance != null && GameStateManager.Instance.isNewGame)
  349. {
  350. LogDebug("New game detected - clearing all quest data");
  351. ClearAllQuests();
  352. return;
  353. }
  354. string json = PlayerPrefs.GetString("QuestManager_SaveData", "");
  355. if (string.IsNullOrEmpty(json))
  356. {
  357. LogDebug("No quest save data found, starting fresh");
  358. currentRenown = 0;
  359. return;
  360. }
  361. var saveData = JsonUtility.FromJson<QuestSaveData>(json);
  362. if (saveData != null)
  363. {
  364. currentRenown = saveData.currentRenown;
  365. // Load active quests
  366. activeQuests.Clear();
  367. foreach (var questSave in saveData.activeQuests)
  368. {
  369. var activeQuest = ActiveQuest.FromSaveData(questSave);
  370. if (activeQuest != null)
  371. {
  372. activeQuests.Add(activeQuest);
  373. }
  374. }
  375. // Load completed quests
  376. completedQuests.Clear();
  377. foreach (var questSave in saveData.completedQuests)
  378. {
  379. var activeQuest = ActiveQuest.FromSaveData(questSave);
  380. if (activeQuest != null)
  381. {
  382. completedQuests.Add(activeQuest);
  383. }
  384. }
  385. // Load failed quests
  386. failedQuests.Clear();
  387. foreach (var questSave in saveData.failedQuests)
  388. {
  389. var activeQuest = ActiveQuest.FromSaveData(questSave);
  390. if (activeQuest != null)
  391. {
  392. failedQuests.Add(activeQuest);
  393. }
  394. }
  395. LogDebug($"Loaded quest data: {activeQuests.Count} active, {completedQuests.Count} completed, {failedQuests.Count} failed, {currentRenown} renown");
  396. // Trigger renown update event
  397. OnRenownChanged?.Invoke(currentRenown);
  398. // Refresh quest markers on map
  399. StartCoroutine(RefreshMarkersAfterLoad());
  400. }
  401. }
  402. catch (System.Exception ex)
  403. {
  404. Debug.LogError($"Failed to load quest data: {ex.Message}");
  405. currentRenown = 0;
  406. }
  407. }
  408. private System.Collections.IEnumerator RefreshMarkersAfterLoad()
  409. {
  410. // Wait a frame to ensure all systems are initialized
  411. yield return null;
  412. var markerManager = FindFirstObjectByType<QuestMapMarkerManager>();
  413. if (markerManager != null)
  414. {
  415. markerManager.RefreshAllMarkers();
  416. }
  417. }
  418. private void LogDebug(string message)
  419. {
  420. if (enableDebugLogs)
  421. {
  422. Debug.Log($"[QuestManager] {message}");
  423. }
  424. }
  425. #region Context Menu Debug Methods
  426. [ContextMenu("Add Test Combat Quest")]
  427. private void AddTestCombatQuest()
  428. {
  429. var testQuest = CreateTestCombatQuest();
  430. AcceptQuest(testQuest);
  431. }
  432. [ContextMenu("Progress Test Quest")]
  433. private void ProgressTestQuest()
  434. {
  435. ProgressQuest(QuestGoalType.KillEnemies, "Bandit", 1);
  436. }
  437. [ContextMenu("Complete Quest at (50,50)")]
  438. private void CompleteQuestAtTestPosition()
  439. {
  440. TryCompleteQuestsAtPosition(new Vector2Int(50, 50));
  441. }
  442. private Quest CreateTestCombatQuest()
  443. {
  444. var quest = ScriptableObject.CreateInstance<Quest>();
  445. quest.questTitle = "Clear the Bandit Camp";
  446. quest.questDescription = "Bandits have been attacking merchants on the road. Clear out their camp and make the roads safe again.";
  447. quest.questType = QuestType.Combat;
  448. quest.difficulty = QuestDifficulty.Normal;
  449. quest.timeLimitDays = 3;
  450. quest.targetAreaName = "Bandit Camp";
  451. quest.targetMapPosition = new Vector2Int(50, 50);
  452. quest.goldReward = 100;
  453. quest.renownReward = 15;
  454. quest.renownPenalty = 10;
  455. quest.goals.Add(new QuestGoal
  456. {
  457. description = "Defeat 5 Bandits",
  458. goalType = QuestGoalType.KillEnemies,
  459. targetName = "Bandit",
  460. targetCount = 5
  461. });
  462. return quest;
  463. }
  464. #endregion
  465. }
  466. /// <summary>
  467. /// Save data structure for quest system
  468. /// </summary>
  469. [System.Serializable]
  470. public class QuestSaveData
  471. {
  472. public int currentRenown;
  473. public List<ActiveQuestSaveData> activeQuests = new List<ActiveQuestSaveData>();
  474. public List<ActiveQuestSaveData> completedQuests = new List<ActiveQuestSaveData>();
  475. public List<ActiveQuestSaveData> failedQuests = new List<ActiveQuestSaveData>();
  476. }
  477. /// <summary>
  478. /// Save data structure for individual active quests
  479. /// </summary>
  480. [System.Serializable]
  481. public class ActiveQuestSaveData
  482. {
  483. public string questId;
  484. public string questAssetName; // To find the original Quest asset
  485. public string questTitle; // Fallback identification
  486. public QuestStatus status;
  487. public float elapsedTimeHours;
  488. public List<QuestGoalSaveData> goals = new List<QuestGoalSaveData>();
  489. }
  490. /// <summary>
  491. /// Save data structure for quest goals
  492. /// </summary>
  493. [System.Serializable]
  494. public class QuestGoalSaveData
  495. {
  496. public string description;
  497. public QuestGoalType goalType;
  498. public string targetName;
  499. public int targetCount;
  500. public int currentProgress;
  501. public bool isHidden;
  502. public bool isOptional;
  503. }