SimpleShopManager.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  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.perceptionModifier += stats.perceptionBonus;
  372. character.acModifier += stats.acBonus; // Apply direct AC bonuses
  373. // Note: AC and HP are calculated properties using Final stats (base + modifiers),
  374. // so they will automatically update when the modifiers change.
  375. // Log what bonuses were applied
  376. if (stats.acBonus > 0)
  377. {
  378. Debug.Log($"Item provides +{stats.acBonus} AC bonus (applied to AC modifier)");
  379. }
  380. if (stats.damageBonus > 0)
  381. {
  382. Debug.Log($"Item provides +{stats.damageBonus} damage bonus (applied during combat)");
  383. }
  384. if (stats.healthBonus > 0)
  385. {
  386. Debug.Log($"Item provides +{stats.healthBonus} health bonus (applied through CON bonus)");
  387. }
  388. if (stats.movementBonus > 0)
  389. {
  390. Debug.Log($"Item provides +{stats.movementBonus} movement bonus");
  391. }
  392. if (stats.initiativeBonus != 0)
  393. {
  394. Debug.Log($"Item provides {stats.initiativeBonus:+0;-0;0} initiative bonus");
  395. }
  396. }
  397. private void UpdatePlayerMoney()
  398. {
  399. if (playerMoney != null && currentCustomer != null)
  400. {
  401. string moneyString = "";
  402. if (currentCustomer.gold > 0) moneyString += $"{currentCustomer.gold}g ";
  403. if (currentCustomer.silver > 0) moneyString += $"{currentCustomer.silver}s ";
  404. if (currentCustomer.copper > 0) moneyString += $"{currentCustomer.copper}c";
  405. playerMoney.text = moneyString.Trim();
  406. }
  407. }
  408. }
  409. [System.Serializable]
  410. public class SimpleShopItem
  411. {
  412. [Header("Basic Info")]
  413. public string name;
  414. public string description;
  415. public ShopItemType itemType;
  416. [Header("Price")]
  417. public int goldCost;
  418. public int silverCost;
  419. public int copperCost;
  420. [Header("Item Properties")]
  421. public ItemStats stats = new ItemStats();
  422. public string searchTags;
  423. public SimpleShopItem(string itemName, string itemDescription, ShopItemType type, int gold, int silver, int copper, string tags, ItemStats itemStats = null)
  424. {
  425. name = itemName;
  426. description = itemDescription;
  427. itemType = type;
  428. goldCost = gold;
  429. silverCost = silver;
  430. copperCost = copper;
  431. searchTags = tags;
  432. stats = itemStats ?? new ItemStats();
  433. }
  434. public bool CanAfford(TeamCharacter customer)
  435. {
  436. if (customer == null) return false;
  437. int totalCopperCost = goldCost * 100 + silverCost * 10 + copperCost;
  438. int totalCopperAvailable = customer.gold * 100 + customer.silver * 10 + customer.copper;
  439. return totalCopperAvailable >= totalCopperCost;
  440. }
  441. public void Purchase(TeamCharacter customer)
  442. {
  443. if (!CanAfford(customer))
  444. {
  445. Debug.LogWarning($"Cannot afford {name}");
  446. return;
  447. }
  448. // Convert everything to copper for easier calculation
  449. int totalCopperCost = goldCost * 100 + silverCost * 10 + copperCost;
  450. int totalCopperAvailable = customer.gold * 100 + customer.silver * 10 + customer.copper;
  451. int remainingCopper = totalCopperAvailable - totalCopperCost;
  452. // Convert back to gold, silver, copper
  453. customer.gold = remainingCopper / 100;
  454. remainingCopper %= 100;
  455. customer.silver = remainingCopper / 10;
  456. customer.copper = remainingCopper % 10;
  457. Debug.Log($"Purchased {name} for {goldCost}g {silverCost}s {copperCost}c");
  458. }
  459. public string GetCostString()
  460. {
  461. string cost = "";
  462. if (goldCost > 0) cost += $"{goldCost}g ";
  463. if (silverCost > 0) cost += $"{silverCost}s ";
  464. if (copperCost > 0) cost += $"{copperCost}c";
  465. return cost.Trim();
  466. }
  467. public string GetStatsString()
  468. {
  469. var statStrings = new List<string>();
  470. if (stats.damageBonus != 0) statStrings.Add($"Damage: +{stats.damageBonus}");
  471. if (stats.acBonus != 0) statStrings.Add($"AC: +{stats.acBonus}");
  472. if (stats.strengthBonus != 0) statStrings.Add($"STR: +{stats.strengthBonus}");
  473. if (stats.dexterityBonus != 0) statStrings.Add($"DEX: +{stats.dexterityBonus}");
  474. if (stats.constitutionBonus != 0) statStrings.Add($"CON: +{stats.constitutionBonus}");
  475. if (stats.wisdomBonus != 0) statStrings.Add($"WIS: +{stats.wisdomBonus}");
  476. if (stats.perceptionBonus != 0) statStrings.Add($"PER: +{stats.perceptionBonus}");
  477. if (stats.movementBonus != 0) statStrings.Add($"Move: +{stats.movementBonus}");
  478. if (stats.healthBonus != 0) statStrings.Add($"HP: +{stats.healthBonus}");
  479. return statStrings.Count > 0 ? string.Join(", ", statStrings) : "";
  480. }
  481. }
  482. [System.Serializable]
  483. public class ItemStats
  484. {
  485. [Header("Combat Stats")]
  486. public int damageBonus = 0;
  487. public int acBonus = 0;
  488. [Header("Attribute Bonuses")]
  489. public int strengthBonus = 0;
  490. public int dexterityBonus = 0;
  491. public int constitutionBonus = 0;
  492. public int wisdomBonus = 0;
  493. public int perceptionBonus = 0;
  494. [Header("Other Bonuses")]
  495. public int movementBonus = 0;
  496. public int healthBonus = 0;
  497. public int initiativeBonus = 0;
  498. }
  499. [System.Serializable]
  500. public enum ShopItemType
  501. {
  502. Weapon,
  503. Armor,
  504. Miscellaneous
  505. }