CombatEventPopup.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. using UnityEngine;
  2. using UnityEngine.UIElements;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. /// <summary>
  6. /// Beautiful UI popup for combat event encounters
  7. /// Shows enemy details and provides options to attack or run away
  8. /// </summary>
  9. public class CombatEventPopup : MonoBehaviour
  10. {
  11. [Header("UI References")]
  12. public UIDocument uiDocument;
  13. public VisualTreeAsset popupTemplate;
  14. [Header("Popup Settings")]
  15. public float animationDuration = 0.3f;
  16. public float backgroundBlurIntensity = 0.5f;
  17. // Events
  18. public System.Action<bool> OnCombatDecision; // true = attack, false = run away
  19. // UI Elements
  20. private VisualElement popupContainer;
  21. private VisualElement backgroundOverlay;
  22. private VisualElement popupPanel;
  23. private Label eventTitleLabel;
  24. private Label eventDescriptionLabel;
  25. private VisualElement enemyListContainer;
  26. private Button attackButton;
  27. private Button runAwayButton;
  28. private VisualElement popupContent;
  29. // Current combat data
  30. private BattleEventData currentBattleData;
  31. private TravelEventContext currentContext;
  32. private bool isPopupActive = false;
  33. void Awake()
  34. {
  35. // Find UI Document if not assigned
  36. if (uiDocument == null)
  37. uiDocument = GetComponent<UIDocument>();
  38. if (uiDocument == null)
  39. {
  40. Debug.LogError("CombatEventPopup: No UIDocument found! Please assign one.");
  41. return;
  42. }
  43. SetupUI();
  44. }
  45. void SetupUI()
  46. {
  47. var root = uiDocument.rootVisualElement;
  48. // Create main popup container
  49. popupContainer = new VisualElement();
  50. popupContainer.name = "combat-event-popup";
  51. popupContainer.AddToClassList("combat-popup-container");
  52. popupContainer.style.display = DisplayStyle.None;
  53. popupContainer.style.position = Position.Absolute;
  54. popupContainer.style.top = 0;
  55. popupContainer.style.left = 0;
  56. popupContainer.style.right = 0;
  57. popupContainer.style.bottom = 0;
  58. popupContainer.style.justifyContent = Justify.Center;
  59. popupContainer.style.alignItems = Align.Center;
  60. // Background overlay
  61. backgroundOverlay = new VisualElement();
  62. backgroundOverlay.name = "background-overlay";
  63. backgroundOverlay.AddToClassList("combat-popup-overlay");
  64. backgroundOverlay.style.position = Position.Absolute;
  65. backgroundOverlay.style.top = 0;
  66. backgroundOverlay.style.left = 0;
  67. backgroundOverlay.style.right = 0;
  68. backgroundOverlay.style.bottom = 0;
  69. backgroundOverlay.style.backgroundColor = new Color(0, 0, 0, 0.7f);
  70. // Main popup panel
  71. popupPanel = new VisualElement();
  72. popupPanel.name = "popup-panel";
  73. popupPanel.AddToClassList("combat-popup-panel");
  74. popupPanel.style.backgroundColor = new Color(0.15f, 0.15f, 0.2f, 0.95f);
  75. popupPanel.style.borderLeftColor = new Color(0.8f, 0.2f, 0.2f, 1f);
  76. popupPanel.style.borderRightColor = new Color(0.8f, 0.2f, 0.2f, 1f);
  77. popupPanel.style.borderTopColor = new Color(0.8f, 0.2f, 0.2f, 1f);
  78. popupPanel.style.borderBottomColor = new Color(0.8f, 0.2f, 0.2f, 1f);
  79. popupPanel.style.borderLeftWidth = 3;
  80. popupPanel.style.borderRightWidth = 3;
  81. popupPanel.style.borderTopWidth = 3;
  82. popupPanel.style.borderBottomWidth = 3;
  83. popupPanel.style.borderTopLeftRadius = 10;
  84. popupPanel.style.borderTopRightRadius = 10;
  85. popupPanel.style.borderBottomLeftRadius = 10;
  86. popupPanel.style.borderBottomRightRadius = 10;
  87. popupPanel.style.minWidth = 400;
  88. popupPanel.style.maxWidth = 600;
  89. popupPanel.style.paddingTop = 20;
  90. popupPanel.style.paddingBottom = 20;
  91. popupPanel.style.paddingLeft = 20;
  92. popupPanel.style.paddingRight = 20;
  93. CreatePopupContent();
  94. popupContainer.Add(backgroundOverlay);
  95. popupContainer.Add(popupPanel);
  96. root.Add(popupContainer);
  97. // Setup event handlers
  98. backgroundOverlay.RegisterCallback<ClickEvent>(OnBackgroundClick);
  99. attackButton.clicked += OnAttackClicked;
  100. runAwayButton.clicked += OnRunAwayClicked;
  101. }
  102. void CreatePopupContent()
  103. {
  104. // Title
  105. eventTitleLabel = new Label("⚔️ COMBAT ENCOUNTER!");
  106. eventTitleLabel.AddToClassList("combat-title");
  107. eventTitleLabel.style.fontSize = 24;
  108. eventTitleLabel.style.color = new Color(1f, 0.3f, 0.3f, 1f);
  109. eventTitleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
  110. eventTitleLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
  111. eventTitleLabel.style.marginBottom = 15;
  112. popupPanel.Add(eventTitleLabel);
  113. // Description
  114. eventDescriptionLabel = new Label("Your party encounters hostile enemies!");
  115. eventDescriptionLabel.AddToClassList("combat-description");
  116. eventDescriptionLabel.style.fontSize = 16;
  117. eventDescriptionLabel.style.color = Color.white;
  118. eventDescriptionLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
  119. eventDescriptionLabel.style.whiteSpace = WhiteSpace.Normal;
  120. eventDescriptionLabel.style.marginBottom = 20;
  121. popupPanel.Add(eventDescriptionLabel);
  122. // Enemy list container
  123. var enemySection = new VisualElement();
  124. enemySection.AddToClassList("enemy-section");
  125. enemySection.style.marginBottom = 25;
  126. var enemyHeader = new Label("🛡️ ENEMIES");
  127. enemyHeader.AddToClassList("enemy-header");
  128. enemyHeader.style.fontSize = 18;
  129. enemyHeader.style.color = new Color(1f, 0.8f, 0.2f, 1f);
  130. enemyHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
  131. enemyHeader.style.unityTextAlign = TextAnchor.MiddleCenter;
  132. enemyHeader.style.marginBottom = 10;
  133. enemySection.Add(enemyHeader);
  134. enemyListContainer = new VisualElement();
  135. enemyListContainer.AddToClassList("enemy-list");
  136. enemyListContainer.style.backgroundColor = new Color(0.1f, 0.1f, 0.15f, 0.8f);
  137. enemyListContainer.style.borderTopLeftRadius = 5;
  138. enemyListContainer.style.borderTopRightRadius = 5;
  139. enemyListContainer.style.borderBottomLeftRadius = 5;
  140. enemyListContainer.style.borderBottomRightRadius = 5;
  141. enemyListContainer.style.paddingTop = 10;
  142. enemyListContainer.style.paddingBottom = 10;
  143. enemyListContainer.style.paddingLeft = 15;
  144. enemyListContainer.style.paddingRight = 15;
  145. enemySection.Add(enemyListContainer);
  146. popupPanel.Add(enemySection);
  147. // Button container
  148. var buttonContainer = new VisualElement();
  149. buttonContainer.AddToClassList("button-container");
  150. buttonContainer.style.flexDirection = FlexDirection.Row;
  151. buttonContainer.style.justifyContent = Justify.SpaceAround;
  152. buttonContainer.style.marginTop = 20;
  153. // Run Away button
  154. runAwayButton = new Button();
  155. runAwayButton.text = "🏃 RUN AWAY";
  156. runAwayButton.AddToClassList("run-button");
  157. runAwayButton.style.fontSize = 16;
  158. runAwayButton.style.backgroundColor = new Color(0.6f, 0.6f, 0.1f, 1f);
  159. runAwayButton.style.color = Color.white;
  160. runAwayButton.style.borderTopLeftRadius = 8;
  161. runAwayButton.style.borderTopRightRadius = 8;
  162. runAwayButton.style.borderBottomLeftRadius = 8;
  163. runAwayButton.style.borderBottomRightRadius = 8;
  164. runAwayButton.style.paddingTop = 12;
  165. runAwayButton.style.paddingBottom = 12;
  166. runAwayButton.style.paddingLeft = 20;
  167. runAwayButton.style.paddingRight = 20;
  168. runAwayButton.style.minWidth = 120;
  169. // Attack button
  170. attackButton = new Button();
  171. attackButton.text = "⚔️ ATTACK!";
  172. attackButton.AddToClassList("attack-button");
  173. attackButton.style.fontSize = 16;
  174. attackButton.style.backgroundColor = new Color(0.8f, 0.2f, 0.2f, 1f);
  175. attackButton.style.color = Color.white;
  176. attackButton.style.borderTopLeftRadius = 8;
  177. attackButton.style.borderTopRightRadius = 8;
  178. attackButton.style.borderBottomLeftRadius = 8;
  179. attackButton.style.borderBottomRightRadius = 8;
  180. attackButton.style.paddingTop = 12;
  181. attackButton.style.paddingBottom = 12;
  182. attackButton.style.paddingLeft = 20;
  183. attackButton.style.paddingRight = 20;
  184. attackButton.style.minWidth = 120;
  185. buttonContainer.Add(runAwayButton);
  186. buttonContainer.Add(attackButton);
  187. popupPanel.Add(buttonContainer);
  188. // Add hover effects
  189. SetupButtonHoverEffects();
  190. }
  191. void SetupButtonHoverEffects()
  192. {
  193. // Attack button hover effects
  194. attackButton.RegisterCallback<MouseEnterEvent>(evt =>
  195. {
  196. attackButton.style.backgroundColor = new Color(1f, 0.3f, 0.3f, 1f);
  197. attackButton.style.scale = new Vector2(1.05f, 1.05f);
  198. });
  199. attackButton.RegisterCallback<MouseLeaveEvent>(evt =>
  200. {
  201. attackButton.style.backgroundColor = new Color(0.8f, 0.2f, 0.2f, 1f);
  202. attackButton.style.scale = new Vector2(1f, 1f);
  203. });
  204. // Run away button hover effects
  205. runAwayButton.RegisterCallback<MouseEnterEvent>(evt =>
  206. {
  207. runAwayButton.style.backgroundColor = new Color(0.7f, 0.7f, 0.2f, 1f);
  208. runAwayButton.style.scale = new Vector2(1.05f, 1.05f);
  209. });
  210. runAwayButton.RegisterCallback<MouseLeaveEvent>(evt =>
  211. {
  212. runAwayButton.style.backgroundColor = new Color(0.6f, 0.6f, 0.1f, 1f);
  213. runAwayButton.style.scale = new Vector2(1f, 1f);
  214. });
  215. }
  216. /// <summary>
  217. /// Show the combat popup with enemy details
  218. /// </summary>
  219. public void ShowCombatEncounter(BattleEventData battleData, TravelEventContext context, string eventDescription)
  220. {
  221. if (isPopupActive || battleData == null) return;
  222. currentBattleData = battleData;
  223. currentContext = context;
  224. isPopupActive = true;
  225. // Update content
  226. eventDescriptionLabel.text = eventDescription;
  227. UpdateEnemyList(battleData);
  228. // Show popup with animation
  229. popupContainer.style.display = DisplayStyle.Flex;
  230. AnimatePopupIn();
  231. Debug.Log($"🎭 Combat popup shown: {battleData.enemyCount} enemies");
  232. }
  233. void UpdateEnemyList(BattleEventData battleData)
  234. {
  235. enemyListContainer.Clear();
  236. if (battleData.enemyCharacterData != null)
  237. {
  238. // Group enemies by type and count
  239. var enemyGroups = new Dictionary<string, int>();
  240. for (int i = 0; i < battleData.enemyCount; i++)
  241. {
  242. string enemyName = battleData.enemyCharacterData.enemyName;
  243. if (enemyGroups.ContainsKey(enemyName))
  244. enemyGroups[enemyName]++;
  245. else
  246. enemyGroups[enemyName] = 1;
  247. }
  248. // Create enemy display elements
  249. foreach (var group in enemyGroups)
  250. {
  251. var enemyElement = CreateEnemyElement(group.Key, group.Value, battleData.enemyCharacterData);
  252. enemyListContainer.Add(enemyElement);
  253. }
  254. }
  255. else
  256. {
  257. // Fallback for legacy string-based enemies
  258. var enemyElement = CreateFallbackEnemyElement(battleData.enemyType, battleData.enemyCount);
  259. enemyListContainer.Add(enemyElement);
  260. }
  261. }
  262. VisualElement CreateEnemyElement(string enemyName, int count, EnemyCharacterData enemyData)
  263. {
  264. var enemyContainer = new VisualElement();
  265. enemyContainer.style.flexDirection = FlexDirection.Row;
  266. enemyContainer.style.justifyContent = Justify.SpaceBetween;
  267. enemyContainer.style.alignItems = Align.Center;
  268. enemyContainer.style.marginBottom = 8;
  269. enemyContainer.style.paddingTop = 8;
  270. enemyContainer.style.paddingBottom = 8;
  271. enemyContainer.style.paddingLeft = 10;
  272. enemyContainer.style.paddingRight = 10;
  273. enemyContainer.style.backgroundColor = new Color(0.2f, 0.2f, 0.25f, 0.6f);
  274. enemyContainer.style.borderTopLeftRadius = 4;
  275. enemyContainer.style.borderTopRightRadius = 4;
  276. enemyContainer.style.borderBottomLeftRadius = 4;
  277. enemyContainer.style.borderBottomRightRadius = 4;
  278. // Enemy info section
  279. var enemyInfo = new VisualElement();
  280. enemyInfo.style.flexDirection = FlexDirection.Column;
  281. enemyInfo.style.flexGrow = 1;
  282. // Enemy name and count
  283. var nameLabel = new Label($"{count}x {enemyName}");
  284. nameLabel.style.fontSize = 16;
  285. nameLabel.style.color = new Color(1f, 0.9f, 0.7f, 1f);
  286. nameLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
  287. enemyInfo.Add(nameLabel);
  288. // Enemy stats
  289. var statsLabel = new Label($"HP: {enemyData.maxHealth} | AC: {enemyData.armorClass} | Threat: {enemyData.threatLevel}");
  290. statsLabel.style.fontSize = 12;
  291. statsLabel.style.color = new Color(0.8f, 0.8f, 0.8f, 1f);
  292. enemyInfo.Add(statsLabel);
  293. // Weapon info
  294. if (enemyData.preferredWeapon != null)
  295. {
  296. var weaponLabel = new Label($"⚔️ {enemyData.preferredWeapon.itemName}");
  297. weaponLabel.style.fontSize = 11;
  298. weaponLabel.style.color = new Color(0.7f, 0.7f, 1f, 1f);
  299. enemyInfo.Add(weaponLabel);
  300. }
  301. enemyContainer.Add(enemyInfo);
  302. // Threat level indicator
  303. var threatIndicator = new VisualElement();
  304. threatIndicator.style.width = 60;
  305. threatIndicator.style.height = 20;
  306. threatIndicator.style.borderTopLeftRadius = 10;
  307. threatIndicator.style.borderTopRightRadius = 10;
  308. threatIndicator.style.borderBottomLeftRadius = 10;
  309. threatIndicator.style.borderBottomRightRadius = 10;
  310. threatIndicator.style.justifyContent = Justify.Center;
  311. threatIndicator.style.alignItems = Align.Center;
  312. // Color based on threat level
  313. Color threatColor = enemyData.threatLevel switch
  314. {
  315. <= 2 => new Color(0.3f, 0.8f, 0.3f, 1f), // Green - Easy
  316. <= 4 => new Color(0.8f, 0.8f, 0.3f, 1f), // Yellow - Medium
  317. <= 6 => new Color(0.9f, 0.5f, 0.2f, 1f), // Orange - Hard
  318. _ => new Color(0.9f, 0.2f, 0.2f, 1f) // Red - Very Hard
  319. };
  320. threatIndicator.style.backgroundColor = threatColor;
  321. var threatLabel = new Label($"T{enemyData.threatLevel}");
  322. threatLabel.style.fontSize = 11;
  323. threatLabel.style.color = Color.white;
  324. threatLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
  325. threatIndicator.Add(threatLabel);
  326. enemyContainer.Add(threatIndicator);
  327. return enemyContainer;
  328. }
  329. VisualElement CreateFallbackEnemyElement(string enemyType, int count)
  330. {
  331. var enemyContainer = new VisualElement();
  332. enemyContainer.style.flexDirection = FlexDirection.Row;
  333. enemyContainer.style.justifyContent = Justify.SpaceBetween;
  334. enemyContainer.style.alignItems = Align.Center;
  335. enemyContainer.style.marginBottom = 8;
  336. enemyContainer.style.paddingTop = 8;
  337. enemyContainer.style.paddingBottom = 8;
  338. enemyContainer.style.paddingLeft = 10;
  339. enemyContainer.style.paddingRight = 10;
  340. enemyContainer.style.backgroundColor = new Color(0.2f, 0.2f, 0.25f, 0.6f);
  341. enemyContainer.style.borderTopLeftRadius = 4;
  342. enemyContainer.style.borderTopRightRadius = 4;
  343. enemyContainer.style.borderBottomLeftRadius = 4;
  344. enemyContainer.style.borderBottomRightRadius = 4;
  345. var nameLabel = new Label($"{count}x {enemyType}");
  346. nameLabel.style.fontSize = 16;
  347. nameLabel.style.color = new Color(1f, 0.9f, 0.7f, 1f);
  348. nameLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
  349. var unknownLabel = new Label("Unknown Threat");
  350. unknownLabel.style.fontSize = 12;
  351. unknownLabel.style.color = new Color(0.6f, 0.6f, 0.6f, 1f);
  352. enemyContainer.Add(nameLabel);
  353. enemyContainer.Add(unknownLabel);
  354. return enemyContainer;
  355. }
  356. void AnimatePopupIn()
  357. {
  358. // Simple scale animation
  359. popupPanel.style.scale = new Vector2(0.7f, 0.7f);
  360. popupPanel.style.opacity = 0f;
  361. // Use coroutine for smooth animation
  362. StartCoroutine(AnimateScale(0.7f, 1f, 0f, 1f, animationDuration));
  363. }
  364. void AnimatePopupOut(System.Action onComplete = null)
  365. {
  366. StartCoroutine(AnimateScale(1f, 0.7f, 1f, 0f, animationDuration, () =>
  367. {
  368. popupContainer.style.display = DisplayStyle.None;
  369. onComplete?.Invoke();
  370. }));
  371. }
  372. System.Collections.IEnumerator AnimateScale(float fromScale, float toScale, float fromOpacity, float toOpacity, float duration, System.Action onComplete = null)
  373. {
  374. float elapsed = 0f;
  375. while (elapsed < duration)
  376. {
  377. elapsed += Time.deltaTime;
  378. float t = elapsed / duration;
  379. // Smooth easing
  380. t = t * t * (3f - 2f * t); // Smoothstep
  381. float currentScale = Mathf.Lerp(fromScale, toScale, t);
  382. float currentOpacity = Mathf.Lerp(fromOpacity, toOpacity, t);
  383. popupPanel.style.scale = new Vector2(currentScale, currentScale);
  384. popupPanel.style.opacity = currentOpacity;
  385. yield return null;
  386. }
  387. popupPanel.style.scale = new Vector2(toScale, toScale);
  388. popupPanel.style.opacity = toOpacity;
  389. onComplete?.Invoke();
  390. }
  391. void OnBackgroundClick(ClickEvent evt)
  392. {
  393. // Optionally close popup when clicking background
  394. // For now, we'll require explicit choice
  395. }
  396. void OnAttackClicked()
  397. {
  398. if (!isPopupActive) return;
  399. Debug.Log("⚔️ Player chose to ATTACK!");
  400. HidePopup(() =>
  401. {
  402. OnCombatDecision?.Invoke(true); // true = attack
  403. });
  404. }
  405. void OnRunAwayClicked()
  406. {
  407. if (!isPopupActive) return;
  408. Debug.Log("🏃 Player chose to RUN AWAY!");
  409. HidePopup(() =>
  410. {
  411. OnCombatDecision?.Invoke(false); // false = run away
  412. });
  413. }
  414. void HidePopup(System.Action onComplete = null)
  415. {
  416. if (!isPopupActive) return;
  417. isPopupActive = false;
  418. AnimatePopupOut(onComplete);
  419. }
  420. /// <summary>
  421. /// Check if the popup is currently active
  422. /// </summary>
  423. public bool IsActive => isPopupActive;
  424. /// <summary>
  425. /// Force close the popup (for cleanup)
  426. /// </summary>
  427. public void ForceClose()
  428. {
  429. if (isPopupActive)
  430. {
  431. isPopupActive = false;
  432. popupContainer.style.display = DisplayStyle.None;
  433. }
  434. }
  435. }