SimpleShopManager.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.UIElements;
  4. public class SimpleShopManager : MonoBehaviour
  5. {
  6. [Header("Shop Settings")]
  7. public string shopName = "General Store";
  8. [Header("Available Items (Simple Version)")]
  9. public List<SimpleShopItem> weapons = new List<SimpleShopItem>();
  10. public List<SimpleShopItem> armor = new List<SimpleShopItem>();
  11. public List<SimpleShopItem> miscItems = new List<SimpleShopItem>();
  12. private UIDocument uiDocument;
  13. private TeamCharacter currentCustomer;
  14. // Callback for when character data changes
  15. public System.Action<TeamCharacter> OnCharacterDataChanged;
  16. // UI Elements
  17. private VisualElement shopContainer;
  18. private TextField searchField;
  19. private DropdownField categoryFilter;
  20. private ScrollView itemList;
  21. private Label shopTitle;
  22. private Label playerMoney;
  23. private Button closeButton;
  24. // Current filter state
  25. private string currentCategory = "All";
  26. private string currentSearchTerm = "";
  27. void Awake()
  28. {
  29. // Initialize default items first
  30. InitializeDefaultItems();
  31. }
  32. void Start()
  33. {
  34. // Initialize UI in Start() to ensure UIDocument is ready
  35. uiDocument = GetComponent<UIDocument>();
  36. if (uiDocument == null)
  37. {
  38. Debug.LogError("SimpleShopManager requires a UIDocument component");
  39. return;
  40. }
  41. if (uiDocument.visualTreeAsset == null)
  42. {
  43. Debug.LogError("SimpleShopManager: UIDocument needs a Visual Tree Asset assigned! Please assign ShopUI.uxml");
  44. return;
  45. }
  46. InitializeUI();
  47. }
  48. void InitializeDefaultItems()
  49. {
  50. // Initialize with some default items if lists are empty
  51. if (weapons.Count == 0)
  52. {
  53. weapons.Add(new SimpleShopItem("Simple Sword", "A basic sword for beginners.", ShopItemType.Weapon, 10, 0, 0, "sword,melee,blade,basic",
  54. new ItemStats { damageBonus = 1 }));
  55. weapons.Add(new SimpleShopItem("Simple Bow", "A basic bow for shooting arrows.", ShopItemType.Weapon, 15, 0, 0, "bow,ranged,arrow,basic",
  56. new ItemStats { damageBonus = 1 }));
  57. weapons.Add(new SimpleShopItem("Iron Sword", "A well-crafted iron sword.", ShopItemType.Weapon, 25, 0, 0, "sword,melee,blade,iron",
  58. new ItemStats { damageBonus = 2 }));
  59. weapons.Add(new SimpleShopItem("Composite Bow", "A bow made of multiple materials.", ShopItemType.Weapon, 35, 0, 0, "bow,ranged,arrow,composite",
  60. new ItemStats { damageBonus = 2 }));
  61. }
  62. if (armor.Count == 0)
  63. {
  64. armor.Add(new SimpleShopItem("Leather Helmet", "Basic head protection.", ShopItemType.Armor, 5, 0, 0, "helmet,head,leather,light"));
  65. armor.Add(new SimpleShopItem("Leather Vest", "Simple chest protection.", ShopItemType.Armor, 12, 0, 0, "vest,chest,leather,light",
  66. new ItemStats { acBonus = 1 }));
  67. armor.Add(new SimpleShopItem("Iron Chainmail", "Chainmail made of iron rings.", ShopItemType.Armor, 30, 0, 0, "chainmail,chest,iron,medium",
  68. new ItemStats { acBonus = 2 }));
  69. }
  70. if (miscItems.Count == 0)
  71. {
  72. miscItems.Add(new SimpleShopItem("Health Potion", "Restores 15 health points.", ShopItemType.Miscellaneous, 3, 0, 0, "potion,health,healing,consumable",
  73. new ItemStats { healthBonus = 15 }));
  74. miscItems.Add(new SimpleShopItem("Hemp Rope", "50 feet of sturdy rope.", ShopItemType.Miscellaneous, 0, 8, 0, "rope,hemp,utility,tool"));
  75. miscItems.Add(new SimpleShopItem("Torch", "Provides light in dark places.", ShopItemType.Miscellaneous, 0, 2, 0, "torch,light,utility"));
  76. }
  77. }
  78. void InitializeUI()
  79. {
  80. var root = uiDocument.rootVisualElement;
  81. Debug.Log("SimpleShopManager: Initializing UI elements...");
  82. shopContainer = root.Q<VisualElement>("ShopContainer");
  83. searchField = root.Q<TextField>("SearchField");
  84. categoryFilter = root.Q<DropdownField>("CategoryFilter");
  85. itemList = root.Q<ScrollView>("ItemList");
  86. shopTitle = root.Q<Label>("ShopTitle");
  87. playerMoney = root.Q<Label>("PlayerMoney");
  88. closeButton = root.Q<Button>("CloseButton");
  89. // Debug which elements were found
  90. Debug.Log($"ShopContainer found: {shopContainer != null}");
  91. Debug.Log($"SearchField found: {searchField != null}");
  92. Debug.Log($"CategoryFilter found: {categoryFilter != null}");
  93. Debug.Log($"ItemList found: {itemList != null}");
  94. Debug.Log($"ShopTitle found: {shopTitle != null}");
  95. Debug.Log($"PlayerMoney found: {playerMoney != null}");
  96. Debug.Log($"CloseButton found: {closeButton != null}");
  97. // Setup category filter
  98. if (categoryFilter != null)
  99. {
  100. categoryFilter.choices = new List<string> { "All", "Weapons", "Armor", "Miscellaneous" };
  101. categoryFilter.value = "All";
  102. categoryFilter.RegisterValueChangedCallback(OnCategoryChanged);
  103. }
  104. // Setup search field
  105. if (searchField != null)
  106. {
  107. searchField.RegisterValueChangedCallback(OnSearchChanged);
  108. }
  109. // Setup close button
  110. if (closeButton != null)
  111. {
  112. closeButton.clicked += CloseShop;
  113. }
  114. // Hide shop initially
  115. if (shopContainer != null)
  116. {
  117. shopContainer.style.display = DisplayStyle.None;
  118. Debug.Log("SimpleShopManager: Shop initially hidden");
  119. }
  120. // Debug UI layering information
  121. DebugUILayering();
  122. }
  123. void DebugUILayering()
  124. {
  125. if (uiDocument != null)
  126. {
  127. var panelSettings = uiDocument.panelSettings;
  128. if (panelSettings != null)
  129. {
  130. Debug.Log($"Shop Panel Settings - sortingOrder: {panelSettings.sortingOrder}");
  131. }
  132. else
  133. {
  134. Debug.LogWarning("Shop UIDocument has no Panel Settings! This will cause layering issues.");
  135. }
  136. Debug.Log($"Shop UIDocument - sortingOrder: {uiDocument.sortingOrder}");
  137. }
  138. // Check for other UIDocuments in the scene for comparison
  139. var allUIDocuments = FindObjectsByType<UIDocument>(FindObjectsSortMode.None);
  140. Debug.Log($"Found {allUIDocuments.Length} UIDocuments in scene:");
  141. foreach (var doc in allUIDocuments)
  142. {
  143. var panelSort = doc.panelSettings?.sortingOrder ?? -999;
  144. Debug.Log($"UIDocument '{doc.gameObject.name}' - sortingOrder: {doc.sortingOrder}, PanelSettings sortingOrder: {panelSort}");
  145. }
  146. }
  147. [ContextMenu("Fix UI Layering")]
  148. public void FixUILayering()
  149. {
  150. if (uiDocument != null)
  151. {
  152. // Set the UIDocument sortingOrder to a higher value
  153. uiDocument.sortingOrder = 10;
  154. // If Panel Settings exist, also set their sortingOrder
  155. if (uiDocument.panelSettings != null)
  156. {
  157. uiDocument.panelSettings.sortingOrder = 10;
  158. Debug.Log("Updated Panel Settings sortingOrder to 10");
  159. }
  160. Debug.Log("Updated Shop UIDocument sortingOrder to 10 - Shop should now render on top!");
  161. }
  162. else
  163. {
  164. Debug.LogError("No UIDocument found on SimpleShopManager!");
  165. }
  166. }
  167. public void OpenShop(TeamCharacter customer)
  168. {
  169. if (customer == null)
  170. {
  171. Debug.LogError("Cannot open shop: customer is null");
  172. return;
  173. }
  174. Debug.Log($"SimpleShopManager: Opening shop for {customer.name}");
  175. if (uiDocument == null)
  176. {
  177. Debug.LogError("SimpleShopManager: UIDocument is null!");
  178. return;
  179. }
  180. if (shopContainer == null)
  181. {
  182. Debug.LogError("SimpleShopManager: shopContainer is null! Make sure ShopUI.uxml is assigned to the UIDocument.");
  183. return;
  184. }
  185. currentCustomer = customer;
  186. // Update UI
  187. if (shopTitle != null)
  188. {
  189. shopTitle.text = shopName;
  190. }
  191. UpdatePlayerMoney();
  192. RefreshItemList();
  193. // Show shop
  194. if (shopContainer != null)
  195. {
  196. shopContainer.style.display = DisplayStyle.Flex;
  197. Debug.Log("SimpleShopManager: Shop container visibility set to Flex");
  198. }
  199. Debug.Log($"Opened {shopName} for {customer.name}");
  200. }
  201. public void CloseShop()
  202. {
  203. if (shopContainer != null)
  204. {
  205. shopContainer.style.display = DisplayStyle.None;
  206. }
  207. currentCustomer = null;
  208. Debug.Log("Closed shop");
  209. }
  210. private void OnCategoryChanged(ChangeEvent<string> evt)
  211. {
  212. currentCategory = evt.newValue;
  213. RefreshItemList();
  214. }
  215. private void OnSearchChanged(ChangeEvent<string> evt)
  216. {
  217. currentSearchTerm = evt.newValue;
  218. RefreshItemList();
  219. }
  220. private void RefreshItemList()
  221. {
  222. if (itemList == null) return;
  223. itemList.Clear();
  224. var filteredItems = GetFilteredItems();
  225. foreach (var item in filteredItems)
  226. {
  227. var itemElement = CreateItemElement(item);
  228. itemList.Add(itemElement);
  229. }
  230. }
  231. private List<SimpleShopItem> GetFilteredItems()
  232. {
  233. var allItems = new List<SimpleShopItem>();
  234. // Add items based on category filter
  235. if (currentCategory == "All" || currentCategory == "Weapons")
  236. {
  237. allItems.AddRange(weapons);
  238. }
  239. if (currentCategory == "All" || currentCategory == "Armor")
  240. {
  241. allItems.AddRange(armor);
  242. }
  243. if (currentCategory == "All" || currentCategory == "Miscellaneous")
  244. {
  245. allItems.AddRange(miscItems);
  246. }
  247. // Apply search filter
  248. if (!string.IsNullOrEmpty(currentSearchTerm))
  249. {
  250. var searchTerm = currentSearchTerm.ToLower();
  251. allItems = allItems.FindAll(item =>
  252. item.name.ToLower().Contains(searchTerm) ||
  253. item.description.ToLower().Contains(searchTerm) ||
  254. item.searchTags.ToLower().Contains(searchTerm)
  255. );
  256. }
  257. return allItems;
  258. }
  259. private VisualElement CreateItemElement(SimpleShopItem item)
  260. {
  261. var container = new VisualElement();
  262. container.style.flexDirection = FlexDirection.Row;
  263. container.style.justifyContent = Justify.SpaceBetween;
  264. container.style.alignItems = Align.Center;
  265. container.style.paddingLeft = 10;
  266. container.style.paddingRight = 10;
  267. container.style.paddingTop = 10;
  268. container.style.paddingBottom = 10;
  269. container.style.marginBottom = 5;
  270. container.style.backgroundColor = new Color(0.95f, 0.95f, 0.95f);
  271. container.style.borderTopLeftRadius = 5;
  272. container.style.borderTopRightRadius = 5;
  273. container.style.borderBottomLeftRadius = 5;
  274. container.style.borderBottomRightRadius = 5;
  275. // Item info section
  276. var infoSection = new VisualElement();
  277. infoSection.style.flexGrow = 1;
  278. var nameLabel = new Label(item.name);
  279. nameLabel.style.fontSize = 16;
  280. nameLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
  281. nameLabel.style.color = Color.black;
  282. infoSection.Add(nameLabel);
  283. var descriptionLabel = new Label(item.description);
  284. descriptionLabel.style.fontSize = 12;
  285. descriptionLabel.style.color = new Color(0.3f, 0.3f, 0.3f);
  286. infoSection.Add(descriptionLabel);
  287. container.Add(infoSection);
  288. // Price and buy section
  289. var buySection = new VisualElement();
  290. buySection.style.flexDirection = FlexDirection.Row;
  291. buySection.style.alignItems = Align.Center;
  292. var priceLabel = new Label(item.GetCostString());
  293. priceLabel.style.fontSize = 14;
  294. priceLabel.style.color = new Color(0, 0.5f, 0);
  295. priceLabel.style.marginRight = 10;
  296. buySection.Add(priceLabel);
  297. var buyButton = new Button(() => PurchaseItem(item));
  298. buyButton.text = "Buy";
  299. buyButton.style.width = 60;
  300. buyButton.style.height = 25;
  301. // Check if player can afford the item
  302. bool canAfford = item.CanAfford(currentCustomer);
  303. buyButton.SetEnabled(canAfford);
  304. if (!canAfford)
  305. {
  306. buyButton.style.backgroundColor = new Color(0.5f, 0.5f, 0.5f);
  307. buyButton.text = "Can't Afford";
  308. }
  309. else
  310. {
  311. buyButton.style.backgroundColor = new Color(0, 0.6f, 0);
  312. buyButton.style.color = Color.white;
  313. }
  314. buySection.Add(buyButton);
  315. container.Add(buySection);
  316. return container;
  317. }
  318. private void PurchaseItem(SimpleShopItem item)
  319. {
  320. if (currentCustomer == null || item == null)
  321. {
  322. Debug.LogError("Cannot purchase item: missing customer or item");
  323. return;
  324. }
  325. if (!item.CanAfford(currentCustomer))
  326. {
  327. Debug.LogWarning($"Cannot afford {item.name}");
  328. return;
  329. }
  330. Debug.Log($"Before purchase: {currentCustomer.name} has {currentCustomer.gold}g {currentCustomer.silver}s {currentCustomer.copper}c");
  331. // Complete the purchase
  332. item.Purchase(currentCustomer);
  333. Debug.Log($"After purchase: {currentCustomer.name} has {currentCustomer.gold}g {currentCustomer.silver}s {currentCustomer.copper}c");
  334. // Add item to customer's inventory
  335. if (weapons.Contains(item))
  336. {
  337. currentCustomer.weapons.Add(item.name);
  338. Debug.Log($"Added {item.name} to weapons list. Total weapons: {currentCustomer.weapons.Count}");
  339. }
  340. else if (armor.Contains(item))
  341. {
  342. currentCustomer.armor.Add(item.name);
  343. Debug.Log($"Added {item.name} to armor list. Total armor: {currentCustomer.armor.Count}");
  344. }
  345. else if (miscItems.Contains(item))
  346. {
  347. currentCustomer.miscItems.Add(item.name);
  348. Debug.Log($"Added {item.name} to misc items list. Total misc items: {currentCustomer.miscItems.Count}");
  349. }
  350. // Recalculate all equipment bonuses from inventory (temporary solution)
  351. currentCustomer.RecalculateEquipmentBonuses();
  352. if (item.stats != null && !string.IsNullOrEmpty(item.GetStatsString()))
  353. {
  354. Debug.Log($"Item {item.name} provides bonuses: {item.GetStatsString()}");
  355. }
  356. UpdatePlayerMoney();
  357. RefreshItemList(); // Refresh to update affordability
  358. // Notify that character data has changed
  359. Debug.Log("Triggering OnCharacterDataChanged callback...");
  360. OnCharacterDataChanged?.Invoke(currentCustomer);
  361. Debug.Log($"Purchased {item.name} for {currentCustomer.name}");
  362. }
  363. private void ApplyItemStats(TeamCharacter character, ItemStats stats)
  364. {
  365. if (character == null || stats == null) return;
  366. // Apply equipment modifiers (these affect final stats calculations)
  367. character.strengthModifier += stats.strengthBonus;
  368. character.dexterityModifier += stats.dexterityBonus;
  369. character.constitutionModifier += stats.constitutionBonus;
  370. character.wisdomModifier += stats.wisdomBonus;
  371. character.acModifier += stats.acBonus; // Apply direct AC bonuses
  372. // Note: AC and HP are calculated properties using Final stats (base + modifiers),
  373. // so they will automatically update when the modifiers change.
  374. // Log what bonuses were applied
  375. if (stats.acBonus > 0)
  376. {
  377. Debug.Log($"Item provides +{stats.acBonus} AC bonus (applied to AC modifier)");
  378. }
  379. if (stats.damageBonus > 0)
  380. {
  381. Debug.Log($"Item provides +{stats.damageBonus} damage bonus (applied during combat)");
  382. }
  383. if (stats.healthBonus > 0)
  384. {
  385. Debug.Log($"Item provides +{stats.healthBonus} health bonus (applied through CON bonus)");
  386. }
  387. if (stats.movementBonus > 0)
  388. {
  389. Debug.Log($"Item provides +{stats.movementBonus} movement bonus");
  390. }
  391. if (stats.initiativeBonus != 0)
  392. {
  393. Debug.Log($"Item provides {stats.initiativeBonus:+0;-0;0} initiative bonus");
  394. }
  395. }
  396. private void UpdatePlayerMoney()
  397. {
  398. if (playerMoney != null && currentCustomer != null)
  399. {
  400. string moneyString = "";
  401. if (currentCustomer.gold > 0) moneyString += $"{currentCustomer.gold}g ";
  402. if (currentCustomer.silver > 0) moneyString += $"{currentCustomer.silver}s ";
  403. if (currentCustomer.copper > 0) moneyString += $"{currentCustomer.copper}c";
  404. playerMoney.text = moneyString.Trim();
  405. }
  406. }
  407. }
  408. [System.Serializable]
  409. public class SimpleShopItem
  410. {
  411. [Header("Basic Info")]
  412. public string name;
  413. public string description;
  414. public ShopItemType itemType;
  415. [Header("Price")]
  416. public int goldCost;
  417. public int silverCost;
  418. public int copperCost;
  419. [Header("Item Properties")]
  420. public ItemStats stats = new ItemStats();
  421. public string searchTags;
  422. public SimpleShopItem(string itemName, string itemDescription, ShopItemType type, int gold, int silver, int copper, string tags, ItemStats itemStats = null)
  423. {
  424. name = itemName;
  425. description = itemDescription;
  426. itemType = type;
  427. goldCost = gold;
  428. silverCost = silver;
  429. copperCost = copper;
  430. searchTags = tags;
  431. stats = itemStats ?? new ItemStats();
  432. }
  433. public bool CanAfford(TeamCharacter customer)
  434. {
  435. if (customer == null) return false;
  436. int totalCopperCost = goldCost * 100 + silverCost * 10 + copperCost;
  437. int totalCopperAvailable = customer.gold * 100 + customer.silver * 10 + customer.copper;
  438. return totalCopperAvailable >= totalCopperCost;
  439. }
  440. public void Purchase(TeamCharacter customer)
  441. {
  442. if (!CanAfford(customer))
  443. {
  444. Debug.LogWarning($"Cannot afford {name}");
  445. return;
  446. }
  447. // Convert everything to copper for easier calculation
  448. int totalCopperCost = goldCost * 100 + silverCost * 10 + copperCost;
  449. int totalCopperAvailable = customer.gold * 100 + customer.silver * 10 + customer.copper;
  450. int remainingCopper = totalCopperAvailable - totalCopperCost;
  451. // Convert back to gold, silver, copper
  452. customer.gold = remainingCopper / 100;
  453. remainingCopper %= 100;
  454. customer.silver = remainingCopper / 10;
  455. customer.copper = remainingCopper % 10;
  456. Debug.Log($"Purchased {name} for {goldCost}g {silverCost}s {copperCost}c");
  457. }
  458. public string GetCostString()
  459. {
  460. string cost = "";
  461. if (goldCost > 0) cost += $"{goldCost}g ";
  462. if (silverCost > 0) cost += $"{silverCost}s ";
  463. if (copperCost > 0) cost += $"{copperCost}c";
  464. return cost.Trim();
  465. }
  466. public string GetStatsString()
  467. {
  468. var statStrings = new List<string>();
  469. if (stats.damageBonus != 0) statStrings.Add($"Damage: +{stats.damageBonus}");
  470. if (stats.acBonus != 0) statStrings.Add($"AC: +{stats.acBonus}");
  471. if (stats.strengthBonus != 0) statStrings.Add($"STR: +{stats.strengthBonus}");
  472. if (stats.dexterityBonus != 0) statStrings.Add($"DEX: +{stats.dexterityBonus}");
  473. if (stats.constitutionBonus != 0) statStrings.Add($"CON: +{stats.constitutionBonus}");
  474. if (stats.wisdomBonus != 0) statStrings.Add($"WIS: +{stats.wisdomBonus}");
  475. if (stats.movementBonus != 0) statStrings.Add($"Move: +{stats.movementBonus}");
  476. if (stats.healthBonus != 0) statStrings.Add($"HP: +{stats.healthBonus}");
  477. return statStrings.Count > 0 ? string.Join(", ", statStrings) : "";
  478. }
  479. }
  480. [System.Serializable]
  481. public class ItemStats
  482. {
  483. [Header("Combat Stats")]
  484. public int damageBonus = 0;
  485. public int acBonus = 0;
  486. [Header("Attribute Bonuses")]
  487. public int strengthBonus = 0;
  488. public int dexterityBonus = 0;
  489. public int constitutionBonus = 0;
  490. public int wisdomBonus = 0;
  491. [Header("Other Bonuses")]
  492. public int movementBonus = 0;
  493. public int healthBonus = 0;
  494. public int initiativeBonus = 0;
  495. }
  496. [System.Serializable]
  497. public enum ShopItemType
  498. {
  499. Weapon,
  500. Armor,
  501. Miscellaneous
  502. }