BattleItemSelector.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. using UnityEngine;
  2. using UnityEngine.UIElements;
  3. using System.Collections.Generic;
  4. using System;
  5. /// <summary>
  6. /// UI Toolkit-based item selection UI for battle
  7. /// </summary>
  8. public class BattleItemSelector : MonoBehaviour
  9. {
  10. [Header("UI References")]
  11. public UIDocument uiDocument;
  12. [Header("Settings")]
  13. public int maxItemsToShow = 10;
  14. private Character currentCharacter;
  15. private VisualElement rootElement;
  16. private VisualElement container;
  17. private VisualElement itemList;
  18. private Label titleLabel;
  19. private Label characterLabel;
  20. private VisualElement noItemsContainer;
  21. private Button cancelButton;
  22. private Button closeButton;
  23. // Events
  24. public event Action<string, int> OnItemSelected;
  25. public event Action OnSelectionCancelled;
  26. void Awake()
  27. {
  28. if (uiDocument == null)
  29. uiDocument = GetComponent<UIDocument>();
  30. if (uiDocument == null)
  31. {
  32. // Create UIDocument if it doesn't exist
  33. uiDocument = gameObject.AddComponent<UIDocument>();
  34. }
  35. }
  36. void Start()
  37. {
  38. InitializeUI();
  39. SetVisible(false);
  40. }
  41. void Update()
  42. {
  43. if (container != null && container.style.display == DisplayStyle.Flex)
  44. {
  45. if (Input.GetKeyDown(KeyCode.Escape))
  46. {
  47. OnSelectionCancelled?.Invoke();
  48. HideSelection();
  49. }
  50. }
  51. }
  52. private void InitializeUI()
  53. {
  54. // Load UXML and USS
  55. var visualTreeAsset = Resources.Load<VisualTreeAsset>("UI/ItemSelectionUI");
  56. var styleSheet = Resources.Load<StyleSheet>("UI/ItemSelectionUI");
  57. // Try simple version if main version fails
  58. if (visualTreeAsset == null)
  59. {
  60. Debug.LogWarning("Main UXML failed, trying simple version...");
  61. visualTreeAsset = Resources.Load<VisualTreeAsset>("UI/ItemSelectionUI_Simple");
  62. }
  63. if (visualTreeAsset == null)
  64. {
  65. Debug.LogError("Could not load any ItemSelectionUI.uxml from Resources/UI/");
  66. CreateFallbackUI();
  67. return;
  68. }
  69. rootElement = uiDocument.rootVisualElement;
  70. rootElement.Clear();
  71. // Clone the visual tree
  72. var clonedTree = visualTreeAsset.CloneTree();
  73. rootElement.Add(clonedTree);
  74. // Apply styles
  75. if (styleSheet != null)
  76. {
  77. rootElement.styleSheets.Add(styleSheet);
  78. }
  79. // Get UI elements
  80. container = rootElement.Q<VisualElement>("ItemSelectionContainer");
  81. itemList = rootElement.Q<VisualElement>("ItemList");
  82. titleLabel = rootElement.Q<Label>("TitleLabel");
  83. characterLabel = rootElement.Q<Label>("CharacterLabel");
  84. noItemsContainer = rootElement.Q<VisualElement>("NoItemsContainer");
  85. cancelButton = rootElement.Q<Button>("CancelButton");
  86. closeButton = rootElement.Q<Button>("CloseButton");
  87. // Setup event handlers
  88. if (cancelButton != null)
  89. {
  90. cancelButton.clicked += () =>
  91. {
  92. OnSelectionCancelled?.Invoke();
  93. HideSelection();
  94. };
  95. }
  96. if (closeButton != null)
  97. {
  98. closeButton.clicked += () =>
  99. {
  100. OnSelectionCancelled?.Invoke();
  101. HideSelection();
  102. };
  103. }
  104. }
  105. private void CreateFallbackUI()
  106. {
  107. Debug.LogWarning("Creating fallback BattleItemSelector UI");
  108. rootElement = uiDocument.rootVisualElement;
  109. rootElement.Clear();
  110. // Create basic container
  111. container = new VisualElement();
  112. container.name = "ItemSelectionContainer";
  113. container.style.position = Position.Absolute;
  114. container.style.top = 0;
  115. container.style.left = 0;
  116. container.style.right = 0;
  117. container.style.bottom = 0;
  118. container.style.backgroundColor = new Color(0, 0, 0, 0.7f);
  119. container.style.alignItems = Align.Center;
  120. container.style.justifyContent = Justify.Center;
  121. // Create modal
  122. var modal = new VisualElement();
  123. modal.style.backgroundColor = new Color(0.18f, 0.18f, 0.18f, 1f);
  124. modal.style.borderTopWidth = modal.style.borderBottomWidth = modal.style.borderLeftWidth = modal.style.borderRightWidth = 2;
  125. modal.style.borderTopColor = modal.style.borderBottomColor = modal.style.borderLeftColor = modal.style.borderRightColor = Color.gray;
  126. modal.style.borderTopLeftRadius = modal.style.borderTopRightRadius = modal.style.borderBottomLeftRadius = modal.style.borderBottomRightRadius = 8;
  127. modal.style.width = 400;
  128. modal.style.height = 500;
  129. modal.style.paddingTop = modal.style.paddingBottom = modal.style.paddingLeft = modal.style.paddingRight = 15;
  130. // Title
  131. titleLabel = new Label("Select Item");
  132. titleLabel.style.fontSize = 18;
  133. titleLabel.style.color = Color.white;
  134. titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
  135. titleLabel.style.marginBottom = 10;
  136. titleLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
  137. // Character label
  138. characterLabel = new Label();
  139. characterLabel.style.fontSize = 16;
  140. characterLabel.style.color = new Color(1f, 0.86f, 0.39f);
  141. characterLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
  142. characterLabel.style.marginBottom = 15;
  143. characterLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
  144. // Item list
  145. var scrollView = new ScrollView();
  146. scrollView.style.flexGrow = 1;
  147. scrollView.style.marginBottom = 15;
  148. itemList = new VisualElement();
  149. scrollView.Add(itemList);
  150. // No items message
  151. noItemsContainer = new VisualElement();
  152. noItemsContainer.style.alignItems = Align.Center;
  153. noItemsContainer.style.justifyContent = Justify.Center;
  154. noItemsContainer.style.flexGrow = 1;
  155. noItemsContainer.style.display = DisplayStyle.None;
  156. var noItemsLabel = new Label("No usable items available");
  157. noItemsLabel.style.color = new Color(0.6f, 0.6f, 0.6f);
  158. noItemsContainer.Add(noItemsLabel);
  159. // Cancel button
  160. cancelButton = new Button(() =>
  161. {
  162. OnSelectionCancelled?.Invoke();
  163. HideSelection();
  164. });
  165. cancelButton.text = "Cancel";
  166. cancelButton.style.paddingTop = cancelButton.style.paddingBottom = 8;
  167. cancelButton.style.paddingLeft = cancelButton.style.paddingRight = 16;
  168. // Assemble UI
  169. modal.Add(titleLabel);
  170. modal.Add(characterLabel);
  171. modal.Add(scrollView);
  172. modal.Add(noItemsContainer);
  173. modal.Add(cancelButton);
  174. container.Add(modal);
  175. rootElement.Add(container);
  176. }
  177. public void ShowItemSelection(Character character)
  178. {
  179. if (itemList == null)
  180. {
  181. Debug.LogError("❌ itemList is null - reinitializing UI");
  182. InitializeUI();
  183. }
  184. currentCharacter = character;
  185. if (characterLabel != null)
  186. characterLabel.text = $"{character.CharacterName}";
  187. if (titleLabel != null)
  188. titleLabel.text = "Select Item";
  189. PopulateItemList();
  190. SetVisible(true);
  191. }
  192. public void HideSelection()
  193. {
  194. SetVisible(false);
  195. ClearItemList();
  196. currentCharacter = null;
  197. }
  198. private void SetVisible(bool visible)
  199. {
  200. if (container != null)
  201. {
  202. container.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
  203. }
  204. }
  205. private void PopulateItemList()
  206. {
  207. ClearItemList();
  208. var items = GetCharacterItems();
  209. if (items.Count == 0)
  210. {
  211. if (noItemsContainer != null)
  212. noItemsContainer.style.display = DisplayStyle.Flex;
  213. return;
  214. }
  215. if (noItemsContainer != null)
  216. noItemsContainer.style.display = DisplayStyle.None;
  217. for (int i = 0; i < items.Count && i < maxItemsToShow; i++)
  218. {
  219. CreateItemButton(items[i], i);
  220. }
  221. }
  222. private void CreateItemButton(ItemData item, int index)
  223. {
  224. // Safety check to prevent NullReferenceException
  225. if (itemList == null)
  226. {
  227. Debug.LogError("❌ itemList is null! UI initialization may have failed.");
  228. return;
  229. }
  230. var button = new Button();
  231. button.AddToClassList("item-button");
  232. // Item name with quantity
  233. string itemText = item.quantity > 1 ? $"{item.name} x{item.quantity}" : item.name;
  234. var nameLabel = new Label(itemText);
  235. nameLabel.AddToClassList("item-name");
  236. // Item description
  237. var descLabel = new Label(item.description);
  238. descLabel.AddToClassList("item-description");
  239. button.Add(nameLabel);
  240. button.Add(descLabel);
  241. // Only allow selection if item is usable and quantity > 0
  242. if (item.isUsable && item.quantity > 0)
  243. {
  244. button.clicked += () =>
  245. {
  246. OnItemSelected?.Invoke(item.name, index);
  247. HideSelection();
  248. };
  249. }
  250. else
  251. {
  252. button.SetEnabled(false);
  253. nameLabel.style.color = Color.gray;
  254. descLabel.style.color = Color.gray;
  255. }
  256. itemList.Add(button);
  257. }
  258. private void ClearItemList()
  259. {
  260. if (itemList != null)
  261. {
  262. itemList.Clear();
  263. }
  264. }
  265. private List<ItemData> GetCharacterItems()
  266. {
  267. if (currentCharacter == null)
  268. {
  269. Debug.LogWarning("📦 GetCharacterItems: currentCharacter is null");
  270. return new List<ItemData>();
  271. }
  272. var items = new List<ItemData>();
  273. // Method 1: Try to get items from Inventory component (ScriptableObject-based system)
  274. var inventory = currentCharacter.GetComponent<Inventory>();
  275. if (inventory != null)
  276. {
  277. // Get misc/consumable items from inventory
  278. foreach (var slot in inventory.Miscellaneous)
  279. {
  280. if (slot.item is MiscellaneousItem miscItem && miscItem.isConsumable)
  281. {
  282. items.Add(new ItemData
  283. {
  284. name = miscItem.itemName,
  285. description = miscItem.GetEffectDescription(),
  286. quantity = slot.quantity,
  287. isUsable = true
  288. });
  289. }
  290. }
  291. return items;
  292. }
  293. // Method 2: Try to get items from CombatDataTransfer session data
  294. if (CombatDataTransfer.HasValidSession())
  295. {
  296. var session = CombatDataTransfer.GetCurrentSession();
  297. var characterData = session.playerTeam.Find(p => p.characterName == currentCharacter.CharacterName ||
  298. currentCharacter.CharacterName.StartsWith(p.characterName));
  299. if (characterData != null && characterData.miscItems != null)
  300. {
  301. foreach (var itemName in characterData.miscItems)
  302. {
  303. // Try to find the actual Item asset to get proper description
  304. var itemAsset = FindItemAsset(itemName);
  305. if (itemAsset is MiscellaneousItem miscItem && miscItem.isConsumable)
  306. {
  307. items.Add(new ItemData
  308. {
  309. name = miscItem.itemName,
  310. description = miscItem.GetEffectDescription(),
  311. quantity = 1, // Default quantity for string-based inventory
  312. isUsable = true
  313. });
  314. }
  315. else
  316. {
  317. // Fallback for items without ScriptableObject assets
  318. string description = GetFallbackItemDescription(itemName);
  319. items.Add(new ItemData
  320. {
  321. name = itemName,
  322. description = description,
  323. quantity = 1,
  324. isUsable = true
  325. });
  326. }
  327. }
  328. return items;
  329. }
  330. }
  331. // Method 3: Fallback - try to access string-based inventory directly (if exposed)
  332. Debug.LogWarning($"📦 No inventory data found for {currentCharacter.CharacterName}. Using empty inventory.");
  333. return items; // Return empty list
  334. }
  335. /// <summary>
  336. /// Try to find an Item ScriptableObject asset by name
  337. /// </summary>
  338. private Item FindItemAsset(string itemName)
  339. {
  340. // Try to find in Resources folder first
  341. var item = Resources.Load<Item>($"Items/{itemName}");
  342. if (item != null) return item;
  343. // Try to find using Resources.LoadAll as fallback
  344. var allItems = Resources.LoadAll<Item>("Items");
  345. foreach (var itemAsset in allItems)
  346. {
  347. if (itemAsset.itemName == itemName)
  348. return itemAsset;
  349. }
  350. return null;
  351. }
  352. /// <summary>
  353. /// Get a fallback description for items that don't have ScriptableObject assets
  354. /// </summary>
  355. private string GetFallbackItemDescription(string itemName)
  356. {
  357. // Basic descriptions for common item names
  358. switch (itemName.ToLower())
  359. {
  360. case "health potion":
  361. case "healing potion":
  362. return "Restores health";
  363. case "mana potion":
  364. case "magic potion":
  365. return "Restores mana";
  366. case "antidote":
  367. return "Cures poison";
  368. case "strength elixir":
  369. return "Temporarily increases strength";
  370. case "rope":
  371. case "hemp rope":
  372. return "Useful utility item";
  373. case "torch":
  374. return "Provides light";
  375. default:
  376. return "A useful item";
  377. }
  378. }
  379. [System.Serializable]
  380. public class ItemData
  381. {
  382. public string name;
  383. public string description;
  384. public int quantity = 1;
  385. public bool isUsable = true;
  386. }
  387. void OnDestroy()
  388. {
  389. OnItemSelected = null;
  390. OnSelectionCancelled = null;
  391. }
  392. }