3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Globalization;
17 public int Total {
get;
private set; }
18 public int NonEmpty {
get;
private set; }
19 public bool AllNonEmpty => NonEmpty == Total;
21 public ItemQuantity(
int total,
bool areNonEmpty =
true)
24 NonEmpty = areNonEmpty ? total : 0;
27 public void Add(
int amount,
bool areNonEmpty)
30 if (areNonEmpty) { NonEmpty += amount; }
36 private readonly List<GUIButton> storeTabButtons =
new List<GUIButton>();
37 private readonly List<GUIButton> itemCategoryButtons =
new List<GUIButton>();
38 private readonly Dictionary<StoreTab, GUIListBox> tabLists =
new Dictionary<StoreTab, GUIListBox>();
39 private readonly Dictionary<StoreTab, SortingMethod> tabSortingMethods =
new Dictionary<StoreTab, SortingMethod>();
40 private readonly List<PurchasedItem> itemsToSell =
new List<PurchasedItem>();
41 private readonly List<PurchasedItem> itemsToSellFromSub =
new List<PurchasedItem>();
47 private bool suppressBuySell;
48 private int buyTotal, sellTotal, sellFromSubTotal;
55 private GUIListBox storeBuyList, storeSellList, storeSellFromSubList;
59 private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup, storeRequestedSubGoodGroup;
60 private Color storeSpecialColor;
62 private GUIListBox shoppingCrateBuyList, shoppingCrateSellList, shoppingCrateSellFromSubList;
63 private GUITextBlock relevantBalanceName, shoppingCrateTotal;
64 private GUIButton clearAllButton, confirmButton;
66 private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh, needsSellingFromSubRefresh, needsItemsToSellFromSubRefresh;
68 private Point resolutionWhenCreated;
70 private PlayerBalanceElement? playerBalanceElement;
72 private Dictionary<ItemPrefab, ItemQuantity> OwnedItems {
get; } =
new Dictionary<ItemPrefab, ItemQuantity>();
76 private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation;
77 private int Balance => campaignUI.Campaign.GetBalance();
79 private bool IsBuying => activeTab
switch
84 _ =>
throw new NotImplementedException()
86 private GUIListBox ActiveShoppingCrateList => activeTab
switch
88 StoreTab.Buy => shoppingCrateBuyList,
89 StoreTab.Sell => shoppingCrateSellList,
90 StoreTab.SellSub => shoppingCrateSellFromSubList,
91 _ =>
throw new NotImplementedException()
110 private enum SortingMethod
121 private bool hadBuyPermissions, hadSellInventoryPermissions, hadSellSubPermissions;
123 private bool HasBuyPermissions
125 get => HasPermissionToUseTab(
StoreTab.Buy);
126 set => hadBuyPermissions = value;
128 private bool HasSellInventoryPermissions
130 get => HasPermissionToUseTab(
StoreTab.Sell);
131 set => hadSellInventoryPermissions = value;
133 private bool HasSellSubPermissions
135 get => HasPermissionToUseTab(
StoreTab.SellSub);
136 set => hadSellSubPermissions = value;
139 private static bool HasPermissionToUseTab(
StoreTab tab)
144 StoreTab.Sell => CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems),
145 StoreTab.SellSub => CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems),
150 private void UpdatePermissions()
152 HasBuyPermissions = HasPermissionToUseTab(
StoreTab.Buy);
153 HasSellInventoryPermissions = HasPermissionToUseTab(
StoreTab.Sell);
154 HasSellSubPermissions = HasPermissionToUseTab(
StoreTab.SellSub);
157 private bool HasTabPermissions(
StoreTab tab)
162 StoreTab.Sell => HasSellInventoryPermissions,
163 StoreTab.SellSub => HasSellSubPermissions,
168 private bool HasActiveTabPermissions()
170 return HasTabPermissions(activeTab);
173 private bool HavePermissionsChanged(
StoreTab tab)
175 bool hadTabPermissions = tab
switch
178 StoreTab.Sell => hadSellInventoryPermissions,
179 StoreTab.SellSub => hadSellSubPermissions,
182 return hadTabPermissions != HasTabPermissions(tab);
189 this.campaignUI = campaignUI;
190 this.parentComponent = parentComponent;
193 Identifier refreshStoreId =
new Identifier(
"RefreshStore");
196 (locationChangeInfo) => UpdateLocation(locationChangeInfo.PrevLocation, locationChangeInfo.NewLocation));
205 needsItemsToSellRefresh =
true;
206 needsItemsToSellFromSubRefresh =
true;
215 if (CurrentLocation?.Stores !=
null)
217 if (!storeIdentifier.IsEmpty && CurrentLocation.
GetStore(storeIdentifier) is { } store)
220 if (storeNameBlock !=
null)
222 var storeName = TextManager.Get($
"storename.{store.Identifier}");
223 if (storeName.IsNullOrEmpty())
225 storeName = TextManager.Get(
"store");
229 ActiveStore.SetMerchantFaction(merchant.
Faction);
235 if (storeIdentifier.IsEmpty)
237 errorId =
"Store.SelectStore:IdentifierEmpty";
238 msg = $
"Error selecting store at {CurrentLocation}: identifier is empty.";
242 errorId =
"Store.SelectStore:StoreDoesntExist";
243 msg = $
"Error selecting store with identifier \"{storeIdentifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location.";
245 DebugConsole.LogError(msg);
246 GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg);
252 string errorId =
"", msg =
"";
253 if (campaignUI.Campaign.Map ==
null)
255 errorId =
"Store.SelectStore:MapNull";
256 msg = $
"Error selecting store with identifier \"{storeIdentifier}\": Map is null.";
258 else if (CurrentLocation ==
null)
260 errorId =
"Store.SelectStore:CurrentLocationNull";
261 msg = $
"Error selecting store with identifier \"{storeIdentifier}\": CurrentLocation is null.";
263 else if (CurrentLocation.
Stores ==
null)
265 errorId =
"Store.SelectStore:StoresNull";
266 msg = $
"Error selecting store with identifier \"{storeIdentifier}\": CurrentLocation.Stores is null.";
268 if (!msg.IsNullOrEmpty())
270 DebugConsole.LogError(msg);
271 GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg);
281 if (updateOwned) { UpdateOwnedItems(); }
282 RefreshBuying(updateOwned:
false);
283 RefreshSelling(updateOwned:
false);
284 RefreshSellingFromSub(updateOwned:
false);
285 SetConfirmButtonBehavior();
286 needsRefresh =
false;
289 private void RefreshBuying(
bool updateOwned =
true)
291 if (updateOwned) { UpdateOwnedItems(); }
292 RefreshShoppingCrateBuyList();
293 RefreshStoreBuyList();
294 bool hasPermissions = HasTabPermissions(
StoreTab.Buy);
295 storeBuyList.
Enabled = hasPermissions;
296 shoppingCrateBuyList.
Enabled = hasPermissions;
297 needsBuyingRefresh =
false;
300 private void RefreshSelling(
bool updateOwned =
true)
302 if (updateOwned) { UpdateOwnedItems(); }
303 RefreshShoppingCrateSellList();
304 RefreshStoreSellList();
305 bool hasPermissions = HasTabPermissions(
StoreTab.Sell);
306 storeSellList.
Enabled = hasPermissions;
307 shoppingCrateSellList.
Enabled = hasPermissions;
308 needsSellingRefresh =
false;
311 private void RefreshSellingFromSub(
bool updateOwned =
true,
bool updateItemsToSellFromSub =
true)
313 if (updateOwned) { UpdateOwnedItems(); }
315 RefreshShoppingCrateSellFromSubList();
316 RefreshStoreSellFromSubList();
317 bool hasPermissions = HasTabPermissions(
StoreTab.SellSub);
318 storeSellFromSubList.
Enabled = hasPermissions;
319 shoppingCrateSellFromSubList.
Enabled = hasPermissions;
320 needsSellingFromSubRefresh =
false;
323 private void CreateUI()
325 if (parentComponent.FindChild(c => c.UserData as
string ==
"glow") is GUIComponent glowChild)
327 parentComponent.RemoveChild(glowChild);
329 if (parentComponent.FindChild(c => c.UserData as
string ==
"container") is GUIComponent containerChild)
331 parentComponent.RemoveChild(containerChild);
334 new GUIFrame(
new RectTransform(
new Vector2(1.25f, 1.25f), parentComponent.RectTransform,
Anchor.Center), style:
"OuterGlow", color: Color.Black * 0.7f)
336 CanBeFocused =
false,
339 new GUIFrame(
new RectTransform(
new Vector2(0.95f), parentComponent.RectTransform, anchor:
Anchor.Center), style:
null)
341 CanBeFocused =
false,
342 UserData =
"container"
345 var panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560));
346 var storeContent =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform,
Anchor.BottomLeft)
348 MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height - HUDLayoutSettings.ButtonAreaTop.Bottom)
352 RelativeSpacing = 0.01f
356 var headerGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.95f / 14.0f), storeContent.RectTransform), isHorizontal:
true)
358 RelativeSpacing = 0.005f
360 var imageWidth = (float)headerGroup.Rect.Height / headerGroup.Rect.Width;
361 new GUIImage(
new RectTransform(
new Vector2(imageWidth, 1.0f), headerGroup.RectTransform),
"StoreTradingIcon");
362 storeNameBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f - imageWidth, 1.0f), headerGroup.RectTransform), TextManager.Get(
"store"), font: GUIStyle.LargeFont)
364 CanBeFocused =
false,
369 var balanceAndValueGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.75f / 14.0f), storeContent.RectTransform), isHorizontal:
true)
371 RelativeSpacing = 0.005f
374 var merchantBalanceContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform))
376 RelativeSpacing = 0.005f
378 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform),
379 TextManager.Get(
"campaignstore.storebalance"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft)
381 AutoScaleVertical =
true,
384 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform),
"",
385 color: Color.White, font: GUIStyle.SubHeadingFont)
387 AutoScaleVertical =
true,
389 TextGetter = () => GetMerchantBalanceText()
393 var reputationEffectContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform))
396 RelativeSpacing = 0.005f,
397 ToolTip = TextManager.Get(
"campaignstore.reputationtooltip")
399 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform),
400 TextManager.Get(
"reputationmodifier"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft)
402 AutoScaleVertical =
true,
403 CanBeFocused =
false,
406 reputationEffectBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform),
"", font: GUIStyle.SubHeadingFont)
408 AutoScaleVertical =
true,
409 CanBeFocused =
false,
413 if (ActiveStore is not
null)
415 Color textColor = GUIStyle.ColorReputationNeutral;
417 int reputationModifier = (int)MathF.Round((ActiveStore.GetReputationModifier(activeTab ==
StoreTab.Buy) - 1) * 100);
418 if (reputationModifier > 0)
420 textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh;
423 else if (reputationModifier < 0)
425 textColor = IsBuying ? GUIStyle.ColorReputationHigh : GUIStyle.ColorReputationLow;
427 reputationEffectBlock.
TextColor = textColor;
428 return $
"{sign}{reputationModifier}%";
438 var modeButtonFrame =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.4f / 14.0f), storeContent.RectTransform), style:
null);
439 var modeButtonContainer =
new GUILayoutGroup(
new RectTransform(Vector2.One, modeButtonFrame.RectTransform), isHorizontal:
true);
441 var tabs = Enum.GetValues(typeof(
StoreTab));
442 storeTabButtons.Clear();
443 tabSortingMethods.Clear();
446 LocalizedString text = tab
switch
448 StoreTab.SellSub => TextManager.Get(
"submarine"),
449 _ => TextManager.Get(
"campaignstoretab." + tab)
451 var tabButton =
new GUIButton(
new RectTransform(
new Vector2(1.0f / (tabs.Length + 1), 1.0f), modeButtonContainer.RectTransform),
452 text: text, style:
"GUITabButton")
455 OnClicked = (button, userData) =>
461 storeTabButtons.Add(tabButton);
462 tabSortingMethods.Add(tab, SortingMethod.AlphabeticalAsc);
465 var storeInventoryContainer =
new GUILayoutGroup(
467 new Vector2(0.9f, 0.95f),
468 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 11.9f / 14.0f), storeContent.RectTransform)).RectTransform,
472 RelativeSpacing = 0.015f,
477 categoryButtonContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.08f, 1.0f), storeInventoryContainer.RectTransform))
479 RelativeSpacing = 0.02f
485 itemCategories.RemoveAll(c => !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought));
486 itemCategoryButtons.Clear();
487 var categoryButton =
new GUIButton(
new RectTransform(
new Point(categoryButtonContainer.
Rect.Width, categoryButtonContainer.
Rect.Width), categoryButtonContainer.
RectTransform), style:
"CategoryButton.All")
489 ToolTip = TextManager.Get(
"MapEntityCategory.All"),
490 OnClicked = OnClickedCategoryButton
492 itemCategoryButtons.Add(categoryButton);
495 categoryButton =
new GUIButton(
new RectTransform(
new Point(categoryButtonContainer.
Rect.Width, categoryButtonContainer.
Rect.Width), categoryButtonContainer.
RectTransform),
496 style:
"CategoryButton." + category)
498 ToolTip = TextManager.Get(
"MapEntityCategory." + category),
500 OnClicked = OnClickedCategoryButton
502 itemCategoryButtons.Add(categoryButton);
504 bool OnClickedCategoryButton(GUIButton button,
object userData)
507 if (newCategory.HasValue) { searchBox.
Text =
""; }
508 if (newCategory != selectedItemCategory) { tabLists[activeTab].ScrollBar.BarScroll = 0f; }
509 FilterStoreItems(newCategory, searchBox.
Text);
512 foreach (var btn
in itemCategoryButtons)
514 btn.RectTransform.SizeChanged += () =>
516 if (btn.Frame.sprites ==
null) {
return; }
517 var sprite = btn.Frame.sprites[GUIComponent.ComponentState.None].First();
518 btn.RectTransform.NonScaledSize =
new Point(btn.Rect.Width, (
int)(btn.Rect.Width * ((
float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width)));
522 GUILayoutGroup sortFilterListContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.92f, 1.0f), storeInventoryContainer.RectTransform))
524 RelativeSpacing = 0.015f,
527 GUILayoutGroup sortFilterGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.08f), sortFilterListContainer.RectTransform), isHorizontal:
true)
529 RelativeSpacing = 0.015f,
533 GUILayoutGroup sortGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.4f, 1.0f), sortFilterGroup.RectTransform))
537 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), sortGroup.RectTransform), text: TextManager.Get(
"campaignstore.sortby"));
538 sortingDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(1.0f, 0.5f), sortGroup.RectTransform), text: TextManager.Get(
"campaignstore.sortby"), elementCount: 3)
540 OnSelected = (child, userData) =>
542 SortActiveTabItems((SortingMethod)userData);
546 var tag =
"sortingmethod.";
547 sortingDropDown.
AddItem(TextManager.Get(tag + SortingMethod.AlphabeticalAsc), userData: SortingMethod.AlphabeticalAsc);
548 sortingDropDown.
AddItem(TextManager.Get(tag + SortingMethod.PriceAsc), userData: SortingMethod.PriceAsc);
549 sortingDropDown.
AddItem(TextManager.Get(tag + SortingMethod.PriceDesc), userData: SortingMethod.PriceDesc);
551 GUILayoutGroup filterGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.6f, 1.0f), sortFilterGroup.RectTransform))
555 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), filterGroup.RectTransform), TextManager.Get(
"serverlog.filter"));
556 searchBox =
new GUITextBox(
new RectTransform(
new Vector2(1.0f, 0.5f), filterGroup.RectTransform), createClearButton:
true);
557 searchBox.
OnTextChanged += (textBox, text) => { FilterStoreItems(
null, text);
return true; };
559 var storeItemListContainer =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.92f), sortFilterListContainer.RectTransform), style:
null);
562 storeBuyList =
new GUIListBox(
new RectTransform(Vector2.One, storeItemListContainer.RectTransform))
564 AutoHideScrollBar =
false,
567 storeDailySpecialsGroup = CreateDealsGroup(storeBuyList, CurrentLocation?.DailySpecialsCount ?? 1);
568 tabLists.Add(
StoreTab.Buy, storeBuyList);
570 storeSellList =
new GUIListBox(
new RectTransform(Vector2.One, storeItemListContainer.RectTransform))
572 AutoHideScrollBar =
false,
575 storeRequestedGoodGroup = CreateDealsGroup(storeSellList, CurrentLocation?.RequestedGoodsCount ?? 1);
576 tabLists.Add(
StoreTab.Sell, storeSellList);
578 storeSellFromSubList =
new GUIListBox(
new RectTransform(Vector2.One, storeItemListContainer.RectTransform))
580 AutoHideScrollBar =
false,
583 storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList, CurrentLocation?.RequestedGoodsCount ?? 1);
584 tabLists.Add(
StoreTab.SellSub, storeSellFromSubList);
588 var shoppingCrateContent =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor:
Anchor.BottomRight)
590 MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height - HUDLayoutSettings.ButtonAreaTop.Bottom)
594 RelativeSpacing = 0.01f
598 headerGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.75f / 14.0f), shoppingCrateContent.RectTransform), isHorizontal:
true, childAnchor:
Anchor.TopRight)
600 RelativeSpacing = 0.005f
602 imageWidth = (float)headerGroup.Rect.Height / headerGroup.Rect.Width;
603 new GUIImage(
new RectTransform(
new Vector2(imageWidth, 1.0f), headerGroup.RectTransform),
"StoreShoppingCrateIcon");
604 new GUITextBlock(
new RectTransform(
new Vector2(1.0f - imageWidth, 1.0f), headerGroup.RectTransform), TextManager.Get(
"campaignstore.shoppingcrate"), font: GUIStyle.LargeFont, textAlignment: Alignment.Right)
606 CanBeFocused =
false,
611 playerBalanceElement = CampaignUI.AddBalanceElement(shoppingCrateContent,
new Vector2(1.0f, 0.75f / 14.0f));
614 var dividerFrame =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.6f / 14.0f), shoppingCrateContent.RectTransform), style:
null);
615 new GUIImage(
new RectTransform(Vector2.One, dividerFrame.RectTransform, anchor:
Anchor.BottomCenter),
"HorizontalLine");
617 var shoppingCrateInventoryContainer =
new GUILayoutGroup(
619 new Vector2(0.9f, 0.95f),
620 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 11.9f / 14.0f), shoppingCrateContent.RectTransform)).RectTransform,
623 RelativeSpacing = 0.015f,
626 var shoppingCrateListContainer =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.8f), shoppingCrateInventoryContainer.RectTransform), style:
null);
627 shoppingCrateBuyList =
new GUIListBox(
new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible =
false, KeepSpaceForScrollBar =
true };
628 shoppingCrateSellList =
new GUIListBox(
new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible =
false, KeepSpaceForScrollBar =
true };
629 shoppingCrateSellFromSubList =
new GUIListBox(
new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible =
false, KeepSpaceForScrollBar =
true };
631 var relevantBalanceContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal:
true)
635 relevantBalanceName =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1.0f), relevantBalanceContainer.RectTransform),
"", font: GUIStyle.Font)
639 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1.0f), relevantBalanceContainer.RectTransform),
"", textColor: Color.White, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right)
641 CanBeFocused =
false,
643 TextGetter = () => IsBuying ? CampaignUI.GetTotalBalance() : GetMerchantBalanceText()
646 var totalContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal:
true)
650 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1.0f), totalContainer.RectTransform), TextManager.Get(
"campaignstore.total"), font: GUIStyle.Font)
654 shoppingCrateTotal =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1.0f), totalContainer.RectTransform),
"", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right)
656 CanBeFocused =
false,
660 var buttonContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.1f), shoppingCrateInventoryContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.TopRight);
661 confirmButton =
new GUIButton(
new RectTransform(
new Vector2(0.35f, 1.0f), buttonContainer.RectTransform))
665 SetConfirmButtonBehavior();
666 clearAllButton =
new GUIButton(
new RectTransform(
new Vector2(0.35f, 1.0f), buttonContainer.RectTransform), TextManager.Get(
"campaignstore.clearall"))
669 Enabled = HasActiveTabPermissions(),
671 OnClicked = (button, userData) =>
673 if (!HasActiveTabPermissions()) {
return false; }
674 var itemsToRemove = activeTab
switch
679 _ =>
throw new NotImplementedException(),
681 itemsToRemove.ForEach(i => ClearFromShoppingCrate(i));
686 ChangeStoreTab(activeTab);
687 resolutionWhenCreated =
new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
690 private LocalizedString GetMerchantBalanceText() => TextManager.FormatCurrency(ActiveStore?.Balance ?? 0);
692 private GUILayoutGroup CreateDealsGroup(GUIListBox parentList,
int elementCount)
696 var elementHeight = (int)(GUI.yScale * 80);
697 var frame =
new GUIFrame(
new RectTransform(
new Point(parentList.Content.Rect.Width, elementCount * elementHeight + 3), parent: parentList.Content.RectTransform), style:
null)
701 var dealsGroup =
new GUILayoutGroup(
new RectTransform(Vector2.One, frame.RectTransform, anchor:
Anchor.Center), childAnchor:
Anchor.TopCenter);
702 var dealsHeader =
new GUILayoutGroup(
new RectTransform(
new Point((
int)(0.95f * parentList.Content.Rect.Width), elementHeight), parent: dealsGroup.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
706 var iconWidth = (0.9f * dealsHeader.Rect.Height) / dealsHeader.Rect.Width;
707 var dealsIcon =
new GUIImage(
new RectTransform(
new Vector2(iconWidth, 0.9f), dealsHeader.RectTransform),
"StoreDealIcon", scaleToFit:
true);
708 var text = TextManager.Get(parentList == storeBuyList ?
"campaignstore.dailyspecials" :
"campaignstore.requestedgoods");
709 var dealsText =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f - iconWidth, 0.9f), dealsHeader.RectTransform), text, font: GUIStyle.LargeFont);
710 storeSpecialColor = dealsIcon.Color;
711 dealsText.TextColor = storeSpecialColor;
712 var divider =
new GUIImage(
new RectTransform(
new Point(dealsGroup.Rect.Width, 3), dealsGroup.RectTransform),
"HorizontalLine")
716 frame.CanBeFocused = dealsGroup.CanBeFocused = dealsHeader.CanBeFocused = dealsIcon.CanBeFocused = dealsText.CanBeFocused = divider.CanBeFocused =
false;
720 private void UpdateLocation(Location prevLocation, Location newLocation)
722 if (prevLocation == newLocation) {
return; }
723 if (prevLocation?.Reputation !=
null)
725 prevLocation.Reputation.OnReputationValueChanged.Dispose();
727 if (ItemPrefab.Prefabs.Any(p => p.CanBeBoughtFrom(newLocation)))
729 selectedItemCategory =
null;
732 if (newLocation?.Reputation !=
null)
734 newLocation.Reputation.OnReputationValueChanged.RegisterOverwriteExisting(
"RefreshStore".ToIdentifier(), _ => { SetNeedsRefresh(); });
738 void SetNeedsRefresh()
744 private void UpdateCategoryButtons()
746 var tabItems = activeTab
switch
750 StoreTab.SellSub => itemsToSellFromSub,
752 } ?? Enumerable.Empty<PurchasedItem>();
753 foreach (var button
in itemCategoryButtons)
759 bool isButtonEnabled =
false;
760 foreach (var item
in tabItems)
762 if (item.ItemPrefab.Category.HasFlag(category))
764 isButtonEnabled =
true;
768 button.Enabled = isButtonEnabled;
772 private void ChangeStoreTab(
StoreTab tab)
775 foreach (GUIButton tabButton
in storeTabButtons)
777 tabButton.Selected = (
StoreTab)tabButton.UserData == activeTab;
779 sortingDropDown.SelectItem(tabSortingMethods[tab]);
780 relevantBalanceName.
Text = IsBuying ? TextManager.Get(
"campaignstore.balance") : TextManager.Get(
"campaignstore.storebalance");
781 UpdateCategoryButtons();
782 SetShoppingCrateTotalText();
783 SetClearAllButtonStatus();
784 SetConfirmButtonBehavior();
785 SetConfirmButtonStatus();
791 if (storeSellFromSubList !=
null)
793 storeSellFromSubList.
Visible =
false;
796 shoppingCrateSellList.
Visible =
false;
797 if (shoppingCrateSellFromSubList !=
null)
799 shoppingCrateSellFromSubList.
Visible =
false;
801 shoppingCrateBuyList.
Visible =
true;
805 if (storeSellFromSubList !=
null)
807 storeSellFromSubList.
Visible =
false;
810 shoppingCrateBuyList.
Visible =
false;
811 if (shoppingCrateSellFromSubList !=
null)
813 shoppingCrateSellFromSubList.
Visible =
false;
815 shoppingCrateSellList.
Visible =
true;
820 if (storeSellFromSubList !=
null)
822 storeSellFromSubList.
Visible =
true;
824 shoppingCrateBuyList.
Visible =
false;
825 shoppingCrateSellList.
Visible =
false;
826 if (shoppingCrateSellFromSubList !=
null)
828 shoppingCrateSellFromSubList.
Visible =
true;
836 selectedItemCategory = category;
837 var list = tabLists[activeTab];
838 filter = filter?.ToLower();
839 foreach (GUIComponent child
in list.Content.Children)
841 var item = child.UserData as PurchasedItem;
842 if (item?.ItemPrefab?.Name ==
null) {
continue; }
844 (IsBuying || item.Quantity > 0) &&
845 (!category.HasValue || item.ItemPrefab.Category.HasFlag(category.Value)) &&
846 (
string.IsNullOrEmpty(filter) || item.ItemPrefab.Name.Contains(filter, StringComparison.OrdinalIgnoreCase));
848 foreach (GUIButton btn
in itemCategoryButtons)
852 list.UpdateScrollBarSize();
855 private void FilterStoreItems()
859 FilterStoreItems(category, searchBox.
Text);
862 private static KeyValuePair<Identifier, float>? GetReputationRequirement(PriceInfo priceInfo)
864 return GameMain.GameSession?.Campaign is not
null
865 ? priceInfo.MinReputation.FirstOrNull()
869 private static KeyValuePair<Identifier, float>? GetTooLowReputation(PriceInfo priceInfo)
871 if (GameMain.GameSession?.Campaign is CampaignMode campaign)
873 foreach (var minRep
in priceInfo.MinReputation)
875 if (MathF.Round(campaign.GetReputation(minRep.Key)) < minRep.Value)
884 int prevDailySpecialCount, prevRequestedGoodsCount, prevSubRequestedGoodsCount;
886 private void RefreshStoreBuyList()
888 float prevBuyListScroll = storeBuyList.
BarScroll;
889 float prevShoppingCrateScroll = shoppingCrateBuyList.
BarScroll;
891 int dailySpecialCount = ActiveStore?.DailySpecials.Count(s => s.CanCharacterBuy()) ?? 0;
892 if ((ActiveStore ==
null && storeDailySpecialsGroup !=
null) || (storeDailySpecialsGroup !=
null) != ActiveStore.DailySpecials.Any() || dailySpecialCount != prevDailySpecialCount)
894 storeBuyList.
RemoveChild(storeDailySpecialsGroup?.Parent);
895 if (ActiveStore !=
null && (storeDailySpecialsGroup ==
null || dailySpecialCount != prevDailySpecialCount))
897 storeDailySpecialsGroup = CreateDealsGroup(storeBuyList, dailySpecialCount);
902 storeDailySpecialsGroup =
null;
905 prevDailySpecialCount = dailySpecialCount;
908 bool hasPermissions = HasTabPermissions(
StoreTab.Buy);
909 var existingItemFrames =
new HashSet<GUIComponent>();
910 if (ActiveStore !=
null)
912 foreach (PurchasedItem item
in ActiveStore.Stock)
914 CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
916 foreach (ItemPrefab itemPrefab
in ActiveStore.DailySpecials)
918 if (ActiveStore.Stock.Any(pi => pi.ItemPrefab == itemPrefab)) {
continue; }
919 CreateOrUpdateItemFrame(itemPrefab, 0);
923 void CreateOrUpdateItemFrame(ItemPrefab itemPrefab,
int quantity)
925 if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy())
927 bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab);
928 var itemFrame = isDailySpecial ?
929 storeDailySpecialsGroup.
FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) :
930 storeBuyList.
Content.
FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab);
933 if (CargoManager.
GetBuyCrateItem(ActiveStore, itemPrefab) is { } buyCrateItem)
935 quantity = Math.Max(quantity - buyCrateItem.Quantity, 0);
937 if (itemFrame ==
null)
939 var parentComponent = isDailySpecial ? storeDailySpecialsGroup : storeBuyList as GUIComponent;
940 itemFrame = CreateItemFrame(
new PurchasedItem(itemPrefab, quantity), parentComponent,
StoreTab.Buy, forceDisable: !hasPermissions);
944 (itemFrame.UserData as PurchasedItem).Quantity = quantity;
945 SetQuantityLabelText(
StoreTab.Buy, itemFrame);
946 SetOwnedText(itemFrame);
947 SetPriceGetters(itemFrame,
true);
950 SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0 && !GetTooLowReputation(priceInfo).HasValue);
951 existingItemFrames.Add(itemFrame);
955 var removedItemFrames = storeBuyList.
Content.
Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList();
956 if (storeDailySpecialsGroup !=
null)
958 removedItemFrames.AddRange(storeDailySpecialsGroup.
Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
960 removedItemFrames.ForEach(f => f.RectTransform.Parent =
null);
963 UpdateCategoryButtons();
968 storeBuyList.
BarScroll = prevBuyListScroll;
969 shoppingCrateBuyList.
BarScroll = prevShoppingCrateScroll;
972 private void RefreshStoreSellList()
974 float prevSellListScroll = storeSellList.
BarScroll;
975 float prevShoppingCrateScroll = shoppingCrateSellList.
BarScroll;
977 int requestedGoodsCount = ActiveStore?.RequestedGoods.Count ?? 0;
978 if ((ActiveStore ==
null && storeRequestedGoodGroup !=
null) || (storeRequestedGoodGroup !=
null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevRequestedGoodsCount)
980 storeSellList.
RemoveChild(storeRequestedGoodGroup?.Parent);
981 if (ActiveStore !=
null && (storeRequestedGoodGroup ==
null || requestedGoodsCount != prevRequestedGoodsCount))
983 storeRequestedGoodGroup = CreateDealsGroup(storeSellList, requestedGoodsCount);
988 storeRequestedGoodGroup =
null;
991 prevRequestedGoodsCount = requestedGoodsCount;
994 bool hasPermissions = HasTabPermissions(
StoreTab.Sell);
995 var existingItemFrames =
new HashSet<GUIComponent>();
996 if (ActiveStore !=
null)
998 foreach (PurchasedItem item
in itemsToSell)
1000 CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
1002 foreach (var requestedGood
in ActiveStore.RequestedGoods)
1004 if (itemsToSell.Any(pi => pi.ItemPrefab == requestedGood)) {
continue; }
1005 CreateOrUpdateItemFrame(requestedGood, 0);
1009 void CreateOrUpdateItemFrame(ItemPrefab itemPrefab,
int itemQuantity)
1011 PriceInfo priceInfo = itemPrefab.GetPriceInfo(ActiveStore);
1012 if (priceInfo ==
null) {
return; }
1013 var isRequestedGood = ActiveStore.RequestedGoods.Contains(itemPrefab);
1014 var itemFrame = isRequestedGood ?
1015 storeRequestedGoodGroup.
FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) :
1016 storeSellList.
Content.
FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab);
1017 if (CargoManager.
GetSellCrateItem(ActiveStore, itemPrefab) is { } sellCrateItem)
1019 itemQuantity = Math.Max(itemQuantity - sellCrateItem.Quantity, 0);
1021 if (itemFrame ==
null)
1023 var parentComponent = isRequestedGood ? storeRequestedGoodGroup : storeSellList as GUIComponent;
1024 itemFrame = CreateItemFrame(
new PurchasedItem(itemPrefab, itemQuantity), parentComponent,
StoreTab.Sell, forceDisable: !hasPermissions);
1028 (itemFrame.UserData as PurchasedItem).Quantity = itemQuantity;
1029 SetQuantityLabelText(
StoreTab.Sell, itemFrame);
1030 SetOwnedText(itemFrame);
1031 SetPriceGetters(itemFrame,
false);
1033 SetItemFrameStatus(itemFrame, hasPermissions && itemQuantity > 0);
1034 if (itemQuantity < 1 && !isRequestedGood)
1036 itemFrame.Visible =
false;
1038 existingItemFrames.Add(itemFrame);
1041 var removedItemFrames = storeSellList.
Content.
Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList();
1042 if (storeRequestedGoodGroup !=
null)
1044 removedItemFrames.AddRange(storeRequestedGoodGroup.
Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
1046 removedItemFrames.ForEach(f => f.RectTransform.Parent =
null);
1050 UpdateCategoryButtons();
1055 storeSellList.
BarScroll = prevSellListScroll;
1056 shoppingCrateSellList.
BarScroll = prevShoppingCrateScroll;
1059 private void RefreshStoreSellFromSubList()
1061 float prevSellListScroll = storeSellFromSubList.
BarScroll;
1062 float prevShoppingCrateScroll = shoppingCrateSellFromSubList.
BarScroll;
1064 int requestedGoodsCount = ActiveStore?.RequestedGoods.Count ?? 0;
1065 if ((ActiveStore ==
null && storeRequestedSubGoodGroup !=
null) || (storeRequestedSubGoodGroup !=
null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevSubRequestedGoodsCount)
1067 storeSellFromSubList.
RemoveChild(storeRequestedSubGoodGroup?.Parent);
1068 if (ActiveStore !=
null && (storeRequestedSubGoodGroup ==
null || requestedGoodsCount != prevSubRequestedGoodsCount))
1070 storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList, requestedGoodsCount);
1075 storeRequestedSubGoodGroup =
null;
1078 prevSubRequestedGoodsCount = requestedGoodsCount;
1081 bool hasPermissions = HasSellSubPermissions;
1082 var existingItemFrames =
new HashSet<GUIComponent>();
1083 if (ActiveStore !=
null)
1085 foreach (PurchasedItem item
in itemsToSellFromSub)
1087 CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
1089 foreach (var requestedGood
in ActiveStore.RequestedGoods)
1091 if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) {
continue; }
1092 CreateOrUpdateItemFrame(requestedGood, 0);
1096 void CreateOrUpdateItemFrame(ItemPrefab itemPrefab,
int itemQuantity)
1098 PriceInfo priceInfo = itemPrefab.GetPriceInfo(ActiveStore);
1099 if (priceInfo ==
null) {
return; }
1100 bool isRequestedGood = ActiveStore.RequestedGoods.Contains(itemPrefab);
1101 var itemFrame = isRequestedGood ?
1102 storeRequestedSubGoodGroup.
FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) :
1103 storeSellFromSubList.
Content.
FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab);
1104 if (CargoManager.
GetSubCrateItem(ActiveStore, itemPrefab) is { } subCrateItem)
1106 itemQuantity = Math.Max(itemQuantity - subCrateItem.Quantity, 0);
1108 if (itemFrame ==
null)
1110 var parentComponent = isRequestedGood ? storeRequestedSubGoodGroup : storeSellFromSubList as GUIComponent;
1111 itemFrame = CreateItemFrame(
new PurchasedItem(itemPrefab, itemQuantity), parentComponent,
StoreTab.SellSub, forceDisable: !hasPermissions);
1115 (itemFrame.UserData as PurchasedItem).Quantity = itemQuantity;
1116 SetQuantityLabelText(
StoreTab.SellSub, itemFrame);
1117 SetOwnedText(itemFrame);
1118 SetPriceGetters(itemFrame,
false);
1120 SetItemFrameStatus(itemFrame, hasPermissions && itemQuantity > 0);
1121 if (itemQuantity < 1 && !isRequestedGood)
1123 itemFrame.Visible =
false;
1125 existingItemFrames.Add(itemFrame);
1128 var removedItemFrames = storeSellFromSubList.
Content.
Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList();
1129 if (storeRequestedSubGoodGroup !=
null)
1131 removedItemFrames.AddRange(storeRequestedSubGoodGroup.
Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
1133 removedItemFrames.ForEach(f => f.RectTransform.Parent =
null);
1137 UpdateCategoryButtons();
1142 storeSellFromSubList.
BarScroll = prevSellListScroll;
1143 shoppingCrateSellFromSubList.
BarScroll = prevShoppingCrateScroll;
1146 private void SetPriceGetters(GUIComponent itemFrame,
bool buying)
1148 if (itemFrame ==
null || itemFrame.UserData is not PurchasedItem pi) {
return; }
1150 if (itemFrame.FindChild(
"undiscountedprice", recursive:
true) is GUITextBlock undiscountedPriceBlock)
1154 undiscountedPriceBlock.TextGetter = () => TextManager.FormatCurrency(
1155 ActiveStore?.GetAdjustedItemBuyPrice(pi.ItemPrefab, considerDailySpecials:
false) ?? 0);
1159 undiscountedPriceBlock.TextGetter = () => TextManager.FormatCurrency(
1160 ActiveStore?.GetAdjustedItemSellPrice(pi.ItemPrefab, considerRequestedGoods:
false) ?? 0);
1164 if (itemFrame.FindChild(
"price", recursive:
true) is GUITextBlock priceBlock)
1168 priceBlock.TextGetter = () => TextManager.FormatCurrency(ActiveStore?.GetAdjustedItemBuyPrice(pi.ItemPrefab) ?? 0);
1172 priceBlock.TextGetter = () => TextManager.FormatCurrency(ActiveStore?.GetAdjustedItemSellPrice(pi.ItemPrefab) ?? 0);
1179 itemsToSell.Clear();
1180 if (ActiveStore ==
null) {
return; }
1182 foreach (
Item playerItem
in playerItems)
1184 if (itemsToSell.FirstOrDefault(i => i.ItemPrefab == playerItem.
Prefab) is
PurchasedItem item)
1198 var playerItem = itemsToSell.Find(i => i.ItemPrefab == crateItem.
ItemPrefab);
1199 var playerItemQuantity = playerItem !=
null ? playerItem.Quantity : 0;
1200 if (crateItem.
Quantity > playerItemQuantity)
1205 needsItemsToSellRefresh =
false;
1210 itemsToSellFromSub.Clear();
1211 if (ActiveStore ==
null) {
return; }
1213 foreach (
Item subItem
in subItems)
1215 if (itemsToSellFromSub.FirstOrDefault(i => i.ItemPrefab == subItem.
Prefab) is
PurchasedItem item)
1229 var subItem = itemsToSellFromSub.Find(i => i.ItemPrefab == crateItem.
ItemPrefab);
1230 var subItemQuantity = subItem !=
null ? subItem.Quantity : 0;
1231 if (crateItem.
Quantity > subItemQuantity)
1236 sellableItemsFromSubUpdateTimer = 0.0f;
1237 needsItemsToSellFromSubRefresh =
false;
1240 private void RefreshShoppingCrateList(IEnumerable<PurchasedItem> items,
GUIListBox listBox,
StoreTab tab)
1242 bool hasPermissions = HasTabPermissions(tab);
1243 HashSet<GUIComponent> existingItemFrames =
new HashSet<GUIComponent>();
1245 if (ActiveStore !=
null)
1250 GUINumberInput numInput =
null;
1253 itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions);
1254 numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive:
true) as GUINumberInput;
1258 itemFrame.UserData = item;
1259 numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive:
true) as GUINumberInput;
1260 if (numInput !=
null)
1262 numInput.UserData = item;
1263 numInput.Enabled = hasPermissions;
1264 numInput.MaxValueInt = GetMaxAvailable(item.
ItemPrefab, tab);
1266 SetOwnedText(itemFrame);
1267 SetItemFrameStatus(itemFrame, hasPermissions);
1269 existingItemFrames.Add(itemFrame);
1271 suppressBuySell =
true;
1272 if (numInput !=
null)
1274 if (numInput.IntValue != item.
Quantity) { itemFrame.Flash(GUIStyle.Green); }
1277 suppressBuySell =
false;
1281 int price = tab
switch
1283 StoreTab.Buy => ActiveStore.GetAdjustedItemBuyPrice(item.
ItemPrefab, priceInfo: priceInfo),
1284 StoreTab.Sell => ActiveStore.GetAdjustedItemSellPrice(item.
ItemPrefab, priceInfo: priceInfo),
1285 StoreTab.SellSub => ActiveStore.GetAdjustedItemSellPrice(item.
ItemPrefab, priceInfo: priceInfo),
1286 _ =>
throw new NotImplementedException()
1288 totalPrice += item.
Quantity * price;
1290 catch (NotImplementedException e)
1292 DebugConsole.LogError($
"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
1297 var removedItemFrames = listBox.
Content.
Children.Except(existingItemFrames).ToList();
1300 SortItems(listBox, SortingMethod.CategoryAsc);
1305 buyTotal = totalPrice;
1308 sellTotal = totalPrice;
1311 sellFromSubTotal = totalPrice;
1314 if (activeTab == tab)
1316 SetShoppingCrateTotalText();
1318 SetClearAllButtonStatus();
1319 SetConfirmButtonStatus();
1322 private void RefreshShoppingCrateBuyList() => RefreshShoppingCrateList(CargoManager.
GetBuyCrateItems(ActiveStore), shoppingCrateBuyList,
StoreTab.Buy);
1324 private void RefreshShoppingCrateSellList() => RefreshShoppingCrateList(CargoManager.
GetSellCrateItems(ActiveStore), shoppingCrateSellList,
StoreTab.Sell);
1326 private void RefreshShoppingCrateSellFromSubList() => RefreshShoppingCrateList(CargoManager.
GetSubCrateItems(ActiveStore), shoppingCrateSellFromSubList,
StoreTab.SellSub);
1328 private void SortItems(GUIListBox list, SortingMethod sortingMethod)
1330 if (CurrentLocation ==
null || ActiveStore ==
null) {
return; }
1332 if (sortingMethod == SortingMethod.AlphabeticalAsc || sortingMethod == SortingMethod.AlphabeticalDesc)
1334 list.Content.RectTransform.SortChildren(CompareByName);
1335 if (GetSpecialsGroup() is GUILayoutGroup specialsGroup)
1337 specialsGroup.RectTransform.SortChildren(CompareByName);
1338 specialsGroup.Recalculate();
1341 int CompareByName(RectTransform x, RectTransform y)
1343 if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY)
1345 int reputationCompare = CompareByReputationRestriction(itemX, itemY);
1346 if (reputationCompare != 0) {
return reputationCompare; }
1347 int sortResult = itemX.ItemPrefab.Name != itemY.ItemPrefab.Name ?
1348 itemX.ItemPrefab.Name.CompareTo(itemY.ItemPrefab.Name) :
1349 itemX.ItemPrefab.Identifier.CompareTo(itemY.ItemPrefab.Identifier);
1350 if (sortingMethod == SortingMethod.AlphabeticalDesc) { sortResult *= -1; }
1355 return CompareByElement(x, y);
1359 else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc)
1361 SortItems(list, SortingMethod.AlphabeticalAsc);
1362 if (list != storeBuyList && list != shoppingCrateBuyList)
1364 list.Content.RectTransform.SortChildren(CompareBySellPrice);
1365 if (GetSpecialsGroup() is GUILayoutGroup specialsGroup)
1367 specialsGroup.RectTransform.SortChildren(CompareBySellPrice);
1368 specialsGroup.Recalculate();
1371 int CompareBySellPrice(RectTransform x, RectTransform y)
1373 if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY)
1375 int reputationCompare = CompareByReputationRestriction(itemX, itemY);
1376 if (reputationCompare != 0) {
return reputationCompare; }
1377 int sortResult = ActiveStore.GetAdjustedItemSellPrice(itemX.ItemPrefab).CompareTo(
1378 ActiveStore.GetAdjustedItemSellPrice(itemY.ItemPrefab));
1379 if (sortingMethod == SortingMethod.PriceDesc) { sortResult *= -1; }
1384 return CompareByElement(x, y);
1390 list.Content.RectTransform.SortChildren(CompareByBuyPrice);
1391 if (GetSpecialsGroup() is GUILayoutGroup specialsGroup)
1393 specialsGroup.RectTransform.SortChildren(CompareByBuyPrice);
1394 specialsGroup.Recalculate();
1397 int CompareByBuyPrice(RectTransform x, RectTransform y)
1399 if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY)
1401 int reputationCompare = CompareByReputationRestriction(itemX, itemY);
1402 if (reputationCompare != 0) {
return reputationCompare; }
1403 int sortResult = ActiveStore.GetAdjustedItemBuyPrice(itemX.ItemPrefab).CompareTo(
1404 ActiveStore.GetAdjustedItemBuyPrice(itemY.ItemPrefab));
1405 if (sortingMethod == SortingMethod.PriceDesc) { sortResult *= -1; }
1410 return CompareByElement(x, y);
1415 else if (sortingMethod == SortingMethod.CategoryAsc)
1417 SortItems(list, SortingMethod.AlphabeticalAsc);
1418 list.Content.RectTransform.SortChildren(CompareByCategory);
1419 if (GetSpecialsGroup() is GUILayoutGroup specialsGroup)
1421 specialsGroup.RectTransform.SortChildren(CompareByCategory);
1422 specialsGroup.Recalculate();
1425 int CompareByCategory(RectTransform x, RectTransform y)
1427 if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY)
1429 int reputationCompare = CompareByReputationRestriction(itemX, itemY);
1430 if (reputationCompare != 0) {
return reputationCompare; }
1431 return itemX.ItemPrefab.Category.CompareTo(itemY.ItemPrefab.Category);
1435 return CompareByElement(x, y);
1440 GUILayoutGroup GetSpecialsGroup()
1442 if (list == storeBuyList)
1444 return storeDailySpecialsGroup;
1446 else if (list == storeSellList)
1448 return storeRequestedGoodGroup;
1450 else if (list == storeSellFromSubList)
1452 return storeRequestedSubGoodGroup;
1460 int CompareByReputationRestriction(PurchasedItem item1, PurchasedItem item2)
1462 PriceInfo priceInfo1 = item1.ItemPrefab.GetPriceInfo(ActiveStore);
1463 PriceInfo priceInfo2 = item2.ItemPrefab.GetPriceInfo(ActiveStore);
1464 if (priceInfo1 !=
null && priceInfo2 !=
null)
1466 var requiredReputation1 = GetTooLowReputation(priceInfo1)?.Value ?? 0.0f;
1467 var requiredReputation2 = GetTooLowReputation(priceInfo2)?.Value ?? 0.0f;
1468 return requiredReputation1.CompareTo(requiredReputation2);
1473 static int CompareByElement(RectTransform x, RectTransform y)
1475 if (ShouldBeOnTop(x) || ShouldBeOnBottom(y))
1479 else if (ShouldBeOnBottom(x) || ShouldBeOnTop(y))
1488 static bool ShouldBeOnTop(RectTransform rt) =>
1489 rt.GUIComponent.UserData is
string id && (
id ==
"deals" ||
id ==
"header");
1491 static bool ShouldBeOnBottom(RectTransform rt) =>
1492 rt.GUIComponent.UserData is
string id &&
id ==
"divider";
1496 private void SortItems(
StoreTab tab, SortingMethod sortingMethod)
1498 tabSortingMethods[tab] = sortingMethod;
1499 SortItems(tabLists[tab], sortingMethod);
1502 private void SortItems(
StoreTab tab)
1504 SortItems(tab, tabSortingMethods[tab]);
1507 private void SortActiveTabItems(SortingMethod sortingMethod) => SortItems(activeTab, sortingMethod);
1509 private GUIComponent CreateItemFrame(PurchasedItem pi, GUIComponent parentComponent,
StoreTab containingTab,
bool forceDisable =
false)
1511 GUIListBox parentListBox = parentComponent as GUIListBox;
1513 RectTransform parent =
null;
1514 if (parentListBox !=
null)
1516 width = parentListBox.Content.Rect.Width;
1517 parent = parentListBox.Content.RectTransform;
1521 width = parentComponent.Rect.Width;
1522 parent = parentComponent.RectTransform;
1524 GUIFrame frame =
new GUIFrame(
new RectTransform(
new Point(width, (
int)(GUI.yScale * 80)), parent: parent), style:
"ListBoxElement")
1529 GUILayoutGroup mainGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f, 1.0f), frame.RectTransform,
Anchor.Center),
1530 isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
1532 RelativeSpacing = 0.01f,
1536 var nameAndIconRelativeWidth = 0.635f;
1537 var iconRelativeWidth = 0.0f;
1538 var priceAndButtonRelativeWidth = 1.0f - nameAndIconRelativeWidth;
1540 if ((pi.ItemPrefab.InventoryIcon ?? pi.ItemPrefab.Sprite) is { } itemIcon)
1542 iconRelativeWidth = (0.9f * mainGroup.Rect.Height) / mainGroup.Rect.Width;
1543 GUIImage img =
new GUIImage(
new RectTransform(
new Vector2(iconRelativeWidth, 0.9f), mainGroup.RectTransform), itemIcon, scaleToFit:
true)
1545 CanBeFocused =
false,
1546 Color = (itemIcon == pi.ItemPrefab.InventoryIcon ? pi.ItemPrefab.InventoryIconColor : pi.ItemPrefab.SpriteColor) * (forceDisable ? 0.5f : 1.0f),
1549 img.RectTransform.MaxSize = img.Rect.Size;
1552 GUIFrame nameAndQuantityFrame =
new GUIFrame(
new RectTransform(
new Vector2(nameAndIconRelativeWidth - iconRelativeWidth, 1.0f), mainGroup.RectTransform), style:
null)
1554 CanBeFocused =
false
1556 GUILayoutGroup nameAndQuantityGroup =
new GUILayoutGroup(
new RectTransform(Vector2.One, nameAndQuantityFrame.RectTransform))
1558 CanBeFocused =
false,
1561 bool isSellingRelatedList = containingTab !=
StoreTab.Buy;
1562 bool locationHasDealOnItem = isSellingRelatedList ?
1563 ActiveStore.RequestedGoods.Contains(pi.ItemPrefab) : ActiveStore.DailySpecials.Contains(pi.ItemPrefab);
1564 GUITextBlock nameBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.4f), nameAndQuantityGroup.RectTransform),
1565 pi.ItemPrefab.Name, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft)
1567 CanBeFocused =
false,
1568 Shadow = locationHasDealOnItem,
1569 TextColor = Color.White * (forceDisable ? 0.5f : 1.0f),
1573 if (locationHasDealOnItem)
1575 var relativeWidth = (0.9f * nameAndQuantityFrame.Rect.Height) / nameAndQuantityFrame.Rect.Width;
1576 var dealIcon =
new GUIImage(
1577 new RectTransform(
new Vector2(relativeWidth, 0.9f), nameAndQuantityFrame.RectTransform, anchor:
Anchor.CenterLeft)
1579 AbsoluteOffset =
new Point((
int)nameBlock.Padding.X, 0)
1581 "StoreDealIcon", scaleToFit:
true)
1583 CanBeFocused =
false
1585 dealIcon.SetAsFirstChild();
1587 bool isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
1588 parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup ||
1589 parentComponent == storeSellFromSubList || parentComponent == storeRequestedSubGoodGroup;
1590 GUILayoutGroup shoppingCrateAmountGroup =
null;
1591 GUINumberInput amountInput =
null;
1592 if (isParentOnLeftSideOfInterface)
1594 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.3f), nameAndQuantityGroup.RectTransform),
1595 CreateQuantityLabelText(containingTab, pi.Quantity), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft)
1597 CanBeFocused =
false,
1598 Shadow = locationHasDealOnItem,
1599 TextColor = Color.White * (forceDisable ? 0.5f : 1.0f),
1601 UserData =
"quantitylabel"
1606 var relativePadding = nameBlock.Padding.X / nameBlock.
Rect.Width;
1607 shoppingCrateAmountGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f - relativePadding, 0.6f), nameAndQuantityGroup.RectTransform) { RelativeOffset = new Vector2(relativePadding, 0) },
1610 RelativeSpacing = 0.02f
1612 amountInput =
new GUINumberInput(
new RectTransform(
new Vector2(0.4f, 1.0f), shoppingCrateAmountGroup.RectTransform),
NumberType.Int)
1615 MaxValueInt = GetMaxAvailable(pi.ItemPrefab, containingTab),
1617 IntValue = pi.Quantity
1619 amountInput.Enabled = !forceDisable;
1620 amountInput.TextBox.OnSelected += (sender, key) => { suppressBuySell =
true; };
1621 amountInput.TextBox.OnDeselected += (sender, key) => { suppressBuySell =
false; amountInput.OnValueChanged?.Invoke(amountInput); };
1622 amountInput.OnValueChanged += (numberInput) =>
1624 if (suppressBuySell) {
return; }
1625 PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem;
1626 if (!HasActiveTabPermissions())
1628 numberInput.IntValue = purchasedItem.Quantity;
1631 AddToShoppingCrate(purchasedItem, quantity: numberInput.IntValue - purchasedItem.Quantity);
1633 frame.HoverColor = frame.SelectedColor = Color.Transparent;
1637 var rectTransform = shoppingCrateAmountGroup ==
null ?
1638 new RectTransform(
new Vector2(1.0f, 0.3f), nameAndQuantityGroup.RectTransform) :
1639 new RectTransform(new Vector2(0.6f, 1.0f), shoppingCrateAmountGroup.RectTransform);
1640 var ownedLabel =
new GUITextBlock(rectTransform,
string.Empty, font: GUIStyle.Font, textAlignment: shoppingCrateAmountGroup ==
null ? Alignment.TopLeft : Alignment.CenterLeft)
1642 CanBeFocused =
false,
1643 Shadow = locationHasDealOnItem,
1644 TextColor = Color.White * (forceDisable ? 0.5f : 1.0f),
1648 SetOwnedText(frame, ownedLabel);
1649 shoppingCrateAmountGroup?.Recalculate();
1651 var buttonRelativeWidth = (0.9f * mainGroup.Rect.Height) / mainGroup.Rect.Width;
1653 var priceFrame =
new GUIFrame(
new RectTransform(
new Vector2(priceAndButtonRelativeWidth - buttonRelativeWidth, 1.0f), mainGroup.RectTransform), style:
null)
1655 CanBeFocused =
false
1657 var priceBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), priceFrame.RectTransform, anchor:
Anchor.Center),
1658 "0 MK", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right)
1660 CanBeFocused =
false,
1661 TextColor = locationHasDealOnItem ? storeSpecialColor : Color.White,
1664 priceBlock.Color *= (forceDisable ? 0.5f : 1.0f);
1665 priceBlock.CalculateHeightFromText();
1666 if (locationHasDealOnItem)
1668 var undiscounterPriceBlock =
new GUITextBlock(
1669 new RectTransform(
new Vector2(1.0f, 0.25f), priceFrame.RectTransform, anchor:
Anchor.Center)
1671 AbsoluteOffset = new Point(0, priceBlock.RectTransform.ScaledSize.Y)
1672 },
"", font: GUIStyle.SmallFont, textAlignment: Alignment.Center)
1674 CanBeFocused =
false,
1675 Strikethrough =
new GUITextBlock.StrikethroughSettings(color: priceBlock.TextColor, expand: 1),
1676 TextColor = priceBlock.TextColor,
1677 UserData =
"undiscountedprice"
1680 SetPriceGetters(frame, !isSellingRelatedList);
1682 if (isParentOnLeftSideOfInterface)
1684 new GUIButton(
new RectTransform(
new Vector2(buttonRelativeWidth, 0.9f), mainGroup.RectTransform), style:
"StoreAddToCrateButton")
1687 Enabled = !forceDisable && pi.Quantity > 0,
1689 UserData =
"addbutton",
1690 OnClicked = (button, userData) => AddToShoppingCrate(pi)
1695 new GUIButton(
new RectTransform(
new Vector2(buttonRelativeWidth, 0.9f), mainGroup.RectTransform), style:
"StoreRemoveFromCrateButton")
1698 Enabled = !forceDisable,
1700 UserData =
"removebutton",
1701 OnClicked = (button, userData) => ClearFromShoppingCrate(pi)
1705 if (parentListBox !=
null)
1707 parentListBox.RecalculateChildren();
1709 else if (parentComponent is GUILayoutGroup parentLayoutGroup)
1711 parentLayoutGroup.Recalculate();
1713 mainGroup.Recalculate();
1714 mainGroup.RectTransform.RecalculateChildren(
true,
true);
1715 amountInput?.LayoutGroup.Recalculate();
1716 nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
1717 mainGroup.RectTransform.Children.ForEach(c => c.IsFixedSize =
true);
1722 private void UpdateOwnedItems()
1726 if (ActiveStore ==
null) {
return; }
1729 if (
Submarine.MainSub?.GetItems(
true) is List<Item> subItems)
1731 foreach (var subItem
in subItems)
1733 if (!subItem.Components.All(c => c is not
Holdable h || !h.
Attachable || !h.Attached)) {
continue; }
1734 if (!subItem.Components.All(c => c is not
Wire w || w.
Connections.All(c => c ==
null))) {
continue; }
1735 if (!ItemAndAllContainersInteractable(subItem)) {
continue; }
1737 var rootInventoryOwner = subItem.GetRootInventoryOwner();
1738 if (rootInventoryOwner is Character) {
continue; }
1739 AddOwnedItem(subItem);
1744 foreach (var item
in Item.ItemList)
1746 if (item ==
null || item.Removed) {
continue; }
1747 var rootInventoryOwner = item.GetRootInventoryOwner();
1748 var ownedByCrewMember = GameMain.GameSession.CrewManager.GetCharacters().Any(c => c == rootInventoryOwner);
1749 if (!ownedByCrewMember) {
continue; }
1754 CargoManager?.
GetPurchasedItems(ActiveStore).Where(pi => !pi.DeliverImmediately).ForEach(pi => AddNonEmptyOwnedItems(pi));
1756 ownedItemsUpdateTimer = 0.0f;
1758 static bool ItemAndAllContainersInteractable(Item item)
1762 if (!item.IsPlayerTeamInteractable) {
return false; }
1763 item = item.Container;
1764 }
while (item !=
null);
1768 void AddOwnedItem(Item item)
1770 if (item?.Prefab.GetPriceInfo(ActiveStore) is not PriceInfo priceInfo) {
return; }
1771 bool isNonEmpty = !priceInfo.DisplayNonEmpty || item.ConditionPercentage > 5.0f;
1772 if (OwnedItems.TryGetValue(item.Prefab, out ItemQuantity itemQuantity))
1774 OwnedItems[item.Prefab].Add(1, isNonEmpty);
1778 OwnedItems.Add(item.Prefab,
new ItemQuantity(1, areNonEmpty: isNonEmpty));
1782 void AddNonEmptyOwnedItems(PurchasedItem purchasedItem)
1784 if (purchasedItem ==
null) {
return; }
1785 if (OwnedItems.TryGetValue(purchasedItem.ItemPrefab, out ItemQuantity itemQuantity))
1787 OwnedItems[purchasedItem.ItemPrefab].Add(purchasedItem.Quantity,
true);
1791 OwnedItems.Add(purchasedItem.ItemPrefab,
new ItemQuantity(purchasedItem.Quantity));
1796 private void SetItemFrameStatus(GUIComponent itemFrame,
bool enabled)
1798 if (itemFrame?.UserData is not PurchasedItem pi) {
return; }
1799 bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
1800 if (!refreshFrameStatus) {
return; }
1801 if (itemFrame.FindChild(
"icon", recursive:
true) is GUIImage icon)
1803 if (pi.ItemPrefab?.InventoryIcon !=
null)
1805 icon.Color = pi.ItemPrefab.InventoryIconColor * (enabled ? 1.0f : 0.5f);
1807 else if (pi.ItemPrefab?.Sprite !=
null)
1809 icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f);
1812 var color = Color.White * (enabled ? 1.0f : 0.5f);
1813 if (itemFrame.FindChild(
"name", recursive:
true) is GUITextBlock name)
1815 name.TextColor = color;
1817 if (itemFrame.FindChild(
"quantitylabel", recursive:
true) is GUITextBlock qty)
1819 qty.TextColor = color;
1821 else if (itemFrame.FindChild(c => c is GUINumberInput, recursive:
true) is GUINumberInput numberInput)
1823 numberInput.Enabled = enabled;
1825 if (itemFrame.FindChild(
"owned", recursive:
true) is GUITextBlock ownedBlock)
1827 ownedBlock.TextColor = color;
1829 bool isDiscounted =
false;
1830 if (itemFrame.FindChild(
"undiscountedprice", recursive:
true) is GUITextBlock undiscountedPriceBlock)
1832 undiscountedPriceBlock.TextColor = color;
1833 undiscountedPriceBlock.Strikethrough.Color = color;
1834 isDiscounted =
true;
1836 if (itemFrame.FindChild(
"price", recursive:
true) is GUITextBlock priceBlock)
1838 priceBlock.TextColor = isDiscounted ? storeSpecialColor * (enabled ? 1.0f : 0.5f) : color;
1840 if (itemFrame.FindChild(
"addbutton", recursive:
true) is GUIButton addButton)
1842 addButton.Enabled = enabled;
1844 else if (itemFrame.FindChild(
"removebutton", recursive:
true) is GUIButton removeButton)
1846 removeButton.Enabled = enabled;
1848 pi.IsStoreComponentEnabled = enabled;
1849 itemFrame.UserData = pi;
1852 private static void SetQuantityLabelText(
StoreTab mode, GUIComponent itemFrame)
1854 if (itemFrame?.FindChild(
"quantitylabel", recursive:
true) is GUITextBlock label)
1856 label.Text = CreateQuantityLabelText(mode, (itemFrame.UserData as PurchasedItem).Quantity);
1860 private static LocalizedString CreateQuantityLabelText(
StoreTab mode,
int quantity)
1864 string textTag = mode
switch
1866 StoreTab.Buy =>
"campaignstore.instock",
1867 StoreTab.Sell =>
"campaignstore.ownedinventory",
1868 StoreTab.SellSub =>
"campaignstore.ownedsub",
1869 _ =>
throw new NotImplementedException()
1871 return TextManager.GetWithVariable(textTag,
"[amount]", quantity.ToString());
1873 catch (NotImplementedException e)
1875 string errorMsg = $
"Error creating a store quantity label text: unknown store tab.\n{e.StackTrace.CleanupStackTrace()}";
1877 DebugConsole.LogError(errorMsg);
1879 DebugConsole.AddWarning(errorMsg);
1882 return string.Empty;
1885 private void SetOwnedText(GUIComponent itemComponent, GUITextBlock ownedLabel =
null)
1887 ownedLabel ??= itemComponent?.FindChild(
"owned", recursive:
true) as GUITextBlock;
1888 if (itemComponent ==
null && ownedLabel ==
null) {
return; }
1889 PurchasedItem purchasedItem = itemComponent?.UserData as PurchasedItem;
1890 ItemQuantity itemQuantity =
null;
1891 LocalizedString ownedLabelText =
string.Empty;
1892 if (purchasedItem !=
null && OwnedItems.TryGetValue(purchasedItem.ItemPrefab, out itemQuantity) && itemQuantity.Total > 0)
1894 if (itemQuantity.AllNonEmpty)
1896 ownedLabelText = TextManager.GetWithVariable(
"campaignstore.owned",
"[amount]", itemQuantity.Total.ToString());
1900 ownedLabelText = TextManager.GetWithVariables(
"campaignstore.ownedspecific",
1901 (
"[nonempty]", itemQuantity.NonEmpty.ToString()),
1902 (
"[total]", itemQuantity.Total.ToString()));
1905 if (itemComponent !=
null)
1907 LocalizedString toolTip =
string.Empty;
1908 if (purchasedItem.ItemPrefab !=
null)
1910 toolTip = purchasedItem.ItemPrefab.GetTooltip(
Character.Controlled);
1911 if (itemQuantity !=
null)
1913 if (itemQuantity.AllNonEmpty)
1915 toolTip += $
"\n\n{ownedLabelText}";
1919 toolTip += $
"\n\n{TextManager.GetWithVariable("campaignstore.ownednonempty
", "[amount]
", itemQuantity.NonEmpty.ToString())}";
1920 toolTip += $
"\n{TextManager.GetWithVariable("campaignstore.ownedtotal
", "[amount]
", itemQuantity.Total.ToString())}";
1924 PriceInfo priceInfo = purchasedItem.ItemPrefab.GetPriceInfo(ActiveStore);
1925 var campaign = GameMain.GameSession?.Campaign;
1926 if (priceInfo !=
null && campaign !=
null)
1928 var requiredReputation = GetReputationRequirement(priceInfo);
1929 if (requiredReputation !=
null)
1931 var repStr = TextManager.GetWithVariables(
1932 "campaignstore.reputationrequired",
1933 (
"[amount]", ((
int)requiredReputation.Value.Value).ToString()),
1934 (
"[faction]", TextManager.Get(
"faction." + requiredReputation.Value.Key).Value));
1935 Color color = MathF.Round(campaign.GetReputation(requiredReputation.Value.Key)) < requiredReputation.Value.Value ?
1936 GUIStyle.Orange : GUIStyle.Green;
1937 toolTip += $
"\n‖color:{color.ToStringHex()}‖{repStr}‖color:end‖";
1941 itemComponent.ToolTip = RichString.Rich(toolTip);
1943 if (ownedLabel !=
null)
1945 ownedLabel.Text = ownedLabelText;
1949 private int GetMaxAvailable(ItemPrefab itemPrefab,
StoreTab mode)
1951 List<PurchasedItem> list =
null;
1956 StoreTab.Buy => ActiveStore?.Stock,
1958 StoreTab.SellSub => itemsToSellFromSub,
1959 _ =>
throw new NotImplementedException()
1962 catch (NotImplementedException e)
1964 DebugConsole.LogError($
"Error getting item availability: Unknown store tab type. {e.StackTrace.CleanupStackTrace()}");
1966 if (list !=
null && list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item)
1970 return Math.Max(item.Quantity - CargoManager.
GetPurchasedItemCount(ActiveStore, item.ItemPrefab), 0);
1972 return item.Quantity;
1980 private bool ModifyBuyQuantity(PurchasedItem item,
int quantity)
1982 if (item?.ItemPrefab ==
null) {
return false; }
1983 if (!HasBuyPermissions) {
return false; }
1986 var crateItem = CargoManager.
GetBuyCrateItem(ActiveStore, item.ItemPrefab);
1987 if (crateItem !=
null && crateItem.Quantity >= CargoManager.
MaxQuantity) {
return false; }
1989 var totalQuantityToBuy = crateItem !=
null ? crateItem.
Quantity + quantity : quantity;
1990 if (totalQuantityToBuy > GetMaxAvailable(item.ItemPrefab,
StoreTab.Buy)) {
return false; }
1993 GameMain.Client?.SendCampaignState();
1997 private bool ModifySellQuantity(PurchasedItem item,
int quantity)
1999 if (item?.ItemPrefab ==
null) {
return false; }
2000 if (!HasSellInventoryPermissions) {
return false; }
2004 var itemToSell = CargoManager.
GetSellCrateItem(ActiveStore, item.ItemPrefab);
2005 var totalQuantityToSell = itemToSell !=
null ? itemToSell.
Quantity + quantity : quantity;
2006 if (totalQuantityToSell > GetMaxAvailable(item.ItemPrefab,
StoreTab.Sell)) {
return false; }
2012 private bool ModifySellFromSubQuantity(PurchasedItem item,
int quantity)
2014 if (item?.ItemPrefab ==
null) {
return false; }
2015 if (!HasSellSubPermissions) {
return false; }
2019 var itemToSell = CargoManager.
GetSubCrateItem(ActiveStore, item.ItemPrefab);
2020 var totalQuantityToSell = itemToSell !=
null ? itemToSell.
Quantity + quantity : quantity;
2021 if (totalQuantityToSell > GetMaxAvailable(item.ItemPrefab,
StoreTab.SellSub)) {
return false; }
2024 GameMain.Client?.SendCampaignState();
2028 private bool AddToShoppingCrate(PurchasedItem item,
int quantity = 1)
2030 if (item ==
null) {
return false; }
2033 return activeTab
switch
2035 StoreTab.Buy => ModifyBuyQuantity(item, quantity),
2036 StoreTab.Sell => ModifySellQuantity(item, quantity),
2037 StoreTab.SellSub => ModifySellFromSubQuantity(item, quantity),
2038 _ =>
throw new NotImplementedException()
2041 catch (NotImplementedException e)
2043 DebugConsole.LogError($
"Error adding an item to the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
2048 private bool ClearFromShoppingCrate(PurchasedItem item)
2050 if (item ==
null) {
return false; }
2053 return activeTab
switch
2055 StoreTab.Buy => ModifyBuyQuantity(item, -item.Quantity),
2056 StoreTab.Sell => ModifySellQuantity(item, -item.Quantity),
2057 StoreTab.SellSub => ModifySellFromSubQuantity(item, -item.Quantity),
2058 _ =>
throw new NotImplementedException(),
2061 catch (NotImplementedException e)
2063 DebugConsole.LogError($
"Error clearing the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
2068 private bool BuyItems()
2070 if (!HasBuyPermissions) {
return false; }
2071 var itemsToPurchase =
new List<PurchasedItem>(CargoManager.
GetBuyCrateItems(ActiveStore));
2072 var itemsToRemove =
new List<PurchasedItem>();
2074 foreach (var item
in itemsToPurchase)
2076 if (item is
null) {
continue; }
2078 if (item.ItemPrefab ==
null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo))
2080 itemsToRemove.Add(item);
2084 if (item.ItemPrefab.DefaultPrice.RequiresUnlock)
2088 itemsToRemove.Add(item);
2093 totalPrice += item.Quantity * ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo);
2095 itemsToRemove.ForEach(i => itemsToPurchase.Remove(i));
2096 if (itemsToPurchase.None() || Balance < totalPrice) {
return false; }
2098 if (CampaignMode.AllowImmediateItemDelivery())
2100 deliveryPrompt =
new GUIMessageBox(
2101 TextManager.Get(
"newsupplies"),
2102 TextManager.Get(
"suppliespurchased.deliverymethod"),
2103 new LocalizedString[]
2105 TextManager.Get(
"suppliespurchased.deliverymethod.deliverimmediately"),
2106 TextManager.Get(
"suppliespurchased.deliverymethod.delivertosub")
2108 deliveryPrompt.
Buttons[0].OnClicked = (btn, userdata) =>
2110 ConfirmPurchase(deliverImmediately:
true);
2111 deliveryPrompt?.
Close();
2114 deliveryPrompt.
Buttons[1].OnClicked = (btn, userdata) =>
2116 ConfirmPurchase(deliverImmediately:
false);
2117 deliveryPrompt?.
Close();
2123 ConfirmPurchase(deliverImmediately:
false);
2126 void ConfirmPurchase(
bool deliverImmediately)
2128 itemsToPurchase.ForEach(it => it.DeliverImmediately = deliverImmediately);
2129 CargoManager.
PurchaseItems(ActiveStore.Identifier, itemsToPurchase, removeFromCrate:
true);
2130 GameMain.Client?.SendCampaignState();
2131 if (!deliverImmediately)
2133 var dialog =
new GUIMessageBox(
2134 TextManager.Get(
"newsupplies"),
2135 TextManager.GetWithVariable(
"suppliespurchasedmessage",
"[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName));
2136 dialog.Buttons[0].OnClicked += dialog.Close;
2144 deliveryPrompt?.
Close();
2145 deliveryPrompt =
null;
2148 private bool SellItems()
2150 if (!HasActiveTabPermissions()) {
return false; }
2151 List<PurchasedItem> itemsToSell;
2154 itemsToSell = activeTab
switch
2158 _ =>
throw new NotImplementedException()
2161 catch (NotImplementedException e)
2163 DebugConsole.LogError($
"Error confirming the store transaction: Unknown store tab type. {e.StackTrace.CleanupStackTrace()}");
2166 var itemsToRemove =
new List<PurchasedItem>();
2168 foreach (PurchasedItem item
in itemsToSell)
2170 if (item?.ItemPrefab?.GetPriceInfo(ActiveStore) is PriceInfo priceInfo)
2172 totalValue += item.Quantity * ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo);
2176 itemsToRemove.Add(item);
2179 itemsToRemove.ForEach(i => itemsToSell.Remove(i));
2180 if (itemsToSell.None() || totalValue > ActiveStore.Balance) {
return false; }
2181 CargoManager.
SellItems(ActiveStore.Identifier, itemsToSell, activeTab);
2182 GameMain.Client?.SendCampaignState();
2186 private void SetShoppingCrateTotalText()
2188 if (ActiveStore ==
null)
2190 shoppingCrateTotal.
Text = TextManager.FormatCurrency(0);
2191 shoppingCrateTotal.
TextColor = Color.White;
2195 shoppingCrateTotal.
Text = TextManager.FormatCurrency(buyTotal);
2196 shoppingCrateTotal.
TextColor = Balance < buyTotal ? Color.Red : Color.White;
2200 int total = activeTab
switch
2203 StoreTab.SellSub => sellFromSubTotal,
2204 _ =>
throw new NotImplementedException(),
2206 shoppingCrateTotal.
Text = TextManager.FormatCurrency(total);
2207 shoppingCrateTotal.
TextColor = CurrentLocation !=
null && total > ActiveStore.Balance ? Color.Red : Color.White;
2211 private void SetConfirmButtonBehavior()
2213 if (ActiveStore ==
null)
2220 confirmButton.
Text = TextManager.Get(
"CampaignStore.Purchase");
2221 confirmButton.
OnClicked = (b, o) => BuyItems();
2226 confirmButton.
Text = TextManager.Get(
"CampaignStoreTab.Sell");
2229 var confirmDialog =
new GUIMessageBox(
2230 TextManager.Get(
"FireWarningHeader"),
2231 TextManager.Get(
"CampaignStore.SellWarningText"),
2232 new LocalizedString[] { TextManager.Get(
"Yes"), TextManager.Get(
"No") });
2233 confirmDialog.Buttons[0].ClickSound =
GUISoundType.ConfirmTransaction;
2234 confirmDialog.Buttons[0].OnClicked = (b, o) => SellItems();
2235 confirmDialog.Buttons[0].OnClicked += confirmDialog.Close;
2236 confirmDialog.Buttons[1].OnClicked = confirmDialog.Close;
2242 private void SetConfirmButtonStatus()
2245 ActiveStore !=
null &&
2246 HasActiveTabPermissions() &&
2250 StoreTab.Buy => Balance >= buyTotal,
2251 StoreTab.Sell => CurrentLocation !=
null && sellTotal <= ActiveStore.Balance,
2252 StoreTab.SellSub => CurrentLocation !=
null && sellFromSubTotal <= ActiveStore.Balance,
2255 confirmButton.
Visible = ActiveStore !=
null;
2258 private void SetClearAllButtonStatus()
2261 HasActiveTabPermissions() &&
2265 private int prevBalance;
2266 private float ownedItemsUpdateTimer = 0.0f, sellableItemsFromSubUpdateTimer = 0.0f;
2267 private const float timerUpdateInterval = 1.5f;
2268 private readonly Stopwatch updateStopwatch =
new Stopwatch();
2272 updateStopwatch.Restart();
2277 needsRefresh =
true;
2284 ownedItemsUpdateTimer += deltaTime;
2285 if (ownedItemsUpdateTimer >= timerUpdateInterval)
2287 bool checkForRefresh = !needsItemsToSellRefresh || !needsRefresh;
2288 var prevOwnedItems = checkForRefresh ?
new Dictionary<ItemPrefab, ItemQuantity>(OwnedItems) :
null;
2290 if (checkForRefresh)
2292 bool refresh = OwnedItems.Count != prevOwnedItems.Count ||
2293 OwnedItems.Values.Sum(v => v.Total) != prevOwnedItems.Values.Sum(v => v.Total) ||
2294 OwnedItems.Any(kvp => !prevOwnedItems.TryGetValue(kvp.Key, out ItemQuantity v) || kvp.Value.Total != v.Total) ||
2295 prevOwnedItems.Any(kvp => !OwnedItems.ContainsKey(kvp.Key));
2298 needsItemsToSellRefresh =
true;
2299 needsRefresh =
true;
2304 sellableItemsFromSubUpdateTimer += deltaTime;
2305 if (sellableItemsFromSubUpdateTimer >= timerUpdateInterval)
2307 bool checkForRefresh = !needsRefresh;
2308 var prevSubItems = checkForRefresh ?
new List<PurchasedItem>(itemsToSellFromSub) :
null;
2310 if (checkForRefresh)
2312 needsRefresh = itemsToSellFromSub.Count != prevSubItems.Count ||
2313 itemsToSellFromSub.Sum(i => i.Quantity) != prevSubItems.Sum(i => i.Quantity) ||
2314 itemsToSellFromSub.Any(i => prevSubItems.FirstOrDefault(prev => prev.ItemPrefab == i.ItemPrefab) is not
PurchasedItem prev || i.
Quantity != prev.Quantity) ||
2315 prevSubItems.Any(prev => itemsToSellFromSub.None(i => i.ItemPrefab == prev.ItemPrefab));
2322 int currBalance = Balance;
2323 if (prevBalance != currBalance)
2325 needsBuyingRefresh =
true;
2326 prevBalance = currBalance;
2329 if (ActiveStore !=
null)
2331 if (needsItemsToSellRefresh)
2335 if (needsItemsToSellFromSubRefresh)
2341 Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f);
2343 if (needsBuyingRefresh || HavePermissionsChanged(
StoreTab.Buy))
2345 RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f);
2347 if (needsSellingRefresh || HavePermissionsChanged(
StoreTab.Sell))
2349 RefreshSelling(updateOwned: ownedItemsUpdateTimer > 0.0f);
2351 if (needsSellingFromSubRefresh || HavePermissionsChanged(
StoreTab.SellSub))
2353 RefreshSellingFromSub(updateOwned: ownedItemsUpdateTimer > 0.0f, updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f);
2357 updateStopwatch.Stop();
readonly CargoManager CargoManager
static ? PlayerBalanceElement UpdateBalanceElement(PlayerBalanceElement? playerBalanceElement)
List< PurchasedItem > GetSubCrateItems(Identifier identifier, bool create=false)
List< PurchasedItem > GetSellCrateItems(Identifier identifier, bool create=false)
CargoManager(CampaignMode campaign)
void PurchaseItems(Identifier storeIdentifier, List< PurchasedItem > itemsToPurchase, bool removeFromCrate, Client client=null)
void ModifyItemQuantityInSellCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity)
List< PurchasedItem > GetPurchasedItems(Identifier identifier, bool create=false)
IEnumerable< Item > GetSellableItemsFromSub()
readonly NamedEvent< CargoManager > OnPurchasedItemsChanged
void ModifyItemQuantityInSubSellCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client=null)
void ModifyItemQuantityInBuyCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client=null)
void SellItems(Identifier storeIdentifier, List< PurchasedItem > itemsToSell, Store.StoreTab sellingMode)
readonly NamedEvent< CargoManager > OnItemsInSellFromSubCrateChanged
int GetPurchasedItemCount(Location.StoreInfo store, ItemPrefab prefab)
PurchasedItem GetBuyCrateItem(Identifier identifier, ItemPrefab prefab)
PurchasedItem GetSellCrateItem(Identifier identifier, ItemPrefab prefab)
readonly NamedEvent< CargoManager > OnSoldItemsChanged
IEnumerable< Item > GetSellableItems(Character character)
readonly NamedEvent< CargoManager > OnItemsInSellCrateChanged
static bool HasUnlockedStoreItem(ItemPrefab prefab)
List< PurchasedItem > GetBuyCrateItems(Identifier identifier, bool create=false)
readonly NamedEvent< CargoManager > OnItemsInBuyCrateChanged
PurchasedItem GetSubCrateItem(Identifier identifier, ItemPrefab prefab)
static Character? Controlled
Identifier MerchantIdentifier
virtual void RemoveChild(GUIComponent child)
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
RectTransform RectTransform
IEnumerable< GUIComponent > Children
GUIComponent AddItem(LocalizedString text, object userData=null, LocalizedString toolTip=null, Color? color=null, Color? textColor=null)
override void RemoveChild(GUIComponent child)
void RecalculateChildren()
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
void UpdateScrollBarSize()
List< GUIButton > Buttons
void SetRichText(LocalizedString richText)
OnTextChangedHandler OnTextChanged
Don't set the Text property on delegates that register to this event, because modifying the Text will...
static PerformanceCounter PerformanceCounter
static int GraphicsHeight
PriceInfo GetPriceInfo(Location.StoreInfo store)
StoreInfo GetStore(Identifier identifier)
Dictionary< Identifier, StoreInfo > Stores
readonly NamedEvent< LocationChangeInfo > OnLocationChanged
From -> To
readonly Identifier Identifier
readonly NamedEvent< Reputation > OnReputationValueChanged
void RefreshItemsToSellFromSub()
void Update(float deltaTime)
void Refresh(bool updateOwned=true)
void RefreshItemsToSell()
void SelectStore(Character merchant)
Store(CampaignUI campaignUI, GUIComponent parentComponent)
@ Character
Characters only