Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/GameSession/CargoManager.cs
3 using FarseerPhysics;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using System.Xml.Linq;
11 using System.Collections;
12 using System.Collections.Immutable;
13 #if SERVER
15 #endif
16 
17 namespace Barotrauma
18 {
20  {
22  public Identifier ItemPrefabIdentifier { get; }
23 
24  public int Quantity { get; set; }
25  public bool? IsStoreComponentEnabled { get; set; }
26 
27  public readonly int BuyerCharacterInfoIdentifier;
28 
32  public bool DeliverImmediately { get; set; }
33 
34  public bool Delivered;
35 
36  public PurchasedItem(ItemPrefab itemPrefab, int quantity, int buyerCharacterInfoId)
37  {
38  ItemPrefabIdentifier = itemPrefab.Identifier;
39  Quantity = quantity;
41  BuyerCharacterInfoIdentifier = buyerCharacterInfoId;
42  }
43 
44 #if CLIENT
45  public PurchasedItem(ItemPrefab itemPrefab, int quantity)
46  : this(itemPrefab, quantity, buyer: null) { }
47 #endif
48  public PurchasedItem(ItemPrefab itemPrefab, int quantity, Client buyer)
49  : this(itemPrefab.Identifier, quantity, buyer) { }
50 
51  public PurchasedItem(Identifier itemPrefabId, int quantity, Client buyer)
52  {
53  ItemPrefabIdentifier = itemPrefabId;
54  Quantity = quantity;
57  }
58 
59  public override string ToString()
60  {
61  return $"{ItemPrefab.Name} ({Quantity})";
62  }
63  }
64 
65  class SoldItem
66  {
67  public ItemPrefab ItemPrefab { get; }
68  public ushort ID { get; private set; }
69  public bool Removed { get; set; }
70  public byte SellerID { get; }
71  public SellOrigin Origin { get; }
72 
73  public enum SellOrigin
74  {
75  Character,
76  Submarine
77  }
78 
79  public SoldItem(ItemPrefab itemPrefab, ushort id, bool removed, byte sellerId, SellOrigin origin)
80  {
81  ItemPrefab = itemPrefab;
82  ID = id;
83  Removed = removed;
84  SellerID = sellerId;
85  Origin = origin;
86  }
87 
88  public void SetItemId(ushort id)
89  {
90  if (ID != Entity.NullEntityID)
91  {
92  DebugConsole.LogError("Error setting SoldItem.ID: ID has already been set and should not be changed.");
93  return;
94  }
95  ID = id;
96  }
97  }
98 
99  partial class CargoManager
100  {
101  private class SoldEntity
102  {
103  public enum SellStatus
104  {
108  Confirmed,
112  Unconfirmed,
116  Local
117  }
118 
119  public Item Item { get; private set; }
120  public ItemPrefab ItemPrefab { get; }
121  public SellStatus Status { get; set; }
122 
123  public SoldEntity(Item item, SellStatus status)
124  {
125  Item = item;
126  ItemPrefab = item?.Prefab;
127  Status = status;
128  }
129 
130  public SoldEntity(ItemPrefab itemPrefab, SellStatus status)
131  {
132  ItemPrefab = itemPrefab;
133  Status = status;
134  }
135 
136  public void SetItem(Item item)
137  {
138  if (Item != null)
139  {
140  DebugConsole.LogError($"Trying to set SoldEntity.Item, but it's already set!\n{Environment.StackTrace.CleanupStackTrace()}");
141  return;
142  }
143  Item = item;
144  }
145  }
146 
147  public const int MaxQuantity = 100;
148 
149  public Dictionary<Identifier, List<PurchasedItem>> ItemsInBuyCrate { get; } = new Dictionary<Identifier, List<PurchasedItem>>();
150  public Dictionary<Identifier, List<PurchasedItem>> ItemsInSellCrate { get; } = new Dictionary<Identifier, List<PurchasedItem>>();
151  public Dictionary<Identifier, List<PurchasedItem>> ItemsInSellFromSubCrate { get; } = new Dictionary<Identifier, List<PurchasedItem>>();
152  public Dictionary<Identifier, List<PurchasedItem>> PurchasedItems { get; } = new Dictionary<Identifier, List<PurchasedItem>>();
153  public Dictionary<Identifier, List<SoldItem>> SoldItems { get; } = new Dictionary<Identifier, List<SoldItem>>();
154 
155  private readonly CampaignMode campaign;
156 
157  private Location Location => campaign?.Map?.CurrentLocation;
158 
159  public readonly NamedEvent<CargoManager> OnItemsInBuyCrateChanged = new NamedEvent<CargoManager>();
160  public readonly NamedEvent<CargoManager> OnItemsInSellCrateChanged = new NamedEvent<CargoManager>();
161  public readonly NamedEvent<CargoManager> OnItemsInSellFromSubCrateChanged = new NamedEvent<CargoManager>();
162  public readonly NamedEvent<CargoManager> OnPurchasedItemsChanged = new NamedEvent<CargoManager>();
163  public readonly NamedEvent<CargoManager> OnSoldItemsChanged = new NamedEvent<CargoManager>();
164 
165  public CargoManager(CampaignMode campaign)
166  {
167  this.campaign = campaign;
168  }
169 
170  public static bool HasUnlockedStoreItem(ItemPrefab prefab)
171  {
172  foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
173  {
174  if (character.HasStoreAccessForItem(prefab)) { return true; }
175  }
176 
177  return false;
178  }
179 
180  private List<T> GetItems<T>(Identifier identifier, Dictionary<Identifier, List<T>> items, bool create = false)
181  {
182  if (items.TryGetValue(identifier, out var storeSpecificItems) && storeSpecificItems != null)
183  {
184  return storeSpecificItems;
185  }
186  else if (create)
187  {
188  storeSpecificItems = new List<T>();
189  items.Add(identifier, storeSpecificItems);
190  return storeSpecificItems;
191  }
192  else
193  {
194  return new List<T>();
195  }
196  }
197 
198  public List<PurchasedItem> GetBuyCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInBuyCrate, create);
199 
200  public List<PurchasedItem> GetBuyCrateItems(Location.StoreInfo store, bool create = false) => GetBuyCrateItems(store?.Identifier ?? Identifier.Empty, create);
201 
202  public PurchasedItem GetBuyCrateItem(Identifier identifier, ItemPrefab prefab) => GetBuyCrateItems(identifier)?.FirstOrDefault(i => i.ItemPrefab == prefab);
203 
204  public PurchasedItem GetBuyCrateItem(Location.StoreInfo store, ItemPrefab prefab) => GetBuyCrateItem(store?.Identifier ?? Identifier.Empty, prefab);
205 
206  public List<PurchasedItem> GetSellCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInSellCrate, create);
207 
208  public List<PurchasedItem> GetSellCrateItems(Location.StoreInfo store, bool create = false) => GetSellCrateItems(store?.Identifier ?? Identifier.Empty, create);
209 
210  public PurchasedItem GetSellCrateItem(Identifier identifier, ItemPrefab prefab) => GetSellCrateItems(identifier)?.FirstOrDefault(i => i.ItemPrefab == prefab);
211 
212  public PurchasedItem GetSellCrateItem(Location.StoreInfo store, ItemPrefab prefab) => GetSellCrateItem(store?.Identifier ?? Identifier.Empty, prefab);
213 
214  public List<PurchasedItem> GetSubCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInSellFromSubCrate, create);
215 
216  public List<PurchasedItem> GetSubCrateItems(Location.StoreInfo store, bool create = false) => GetSubCrateItems(store?.Identifier ?? Identifier.Empty, create);
217 
218  public PurchasedItem GetSubCrateItem(Identifier identifier, ItemPrefab prefab) => GetSubCrateItems(identifier)?.FirstOrDefault(i => i.ItemPrefab == prefab);
219 
220  public PurchasedItem GetSubCrateItem(Location.StoreInfo store, ItemPrefab prefab) => GetSubCrateItem(store?.Identifier ?? Identifier.Empty, prefab);
221 
222  public List<PurchasedItem> GetPurchasedItems(Identifier identifier, bool create = false) => GetItems(identifier, PurchasedItems, create);
223 
224  public List<PurchasedItem> GetPurchasedItems(Location.StoreInfo store, bool create = false) => GetPurchasedItems(store?.Identifier ?? Identifier.Empty, create);
225 
226  public int GetPurchasedItemCount(Location.StoreInfo store, ItemPrefab prefab) =>
227  GetPurchasedItemCount(store?.Identifier ?? Identifier.Empty, prefab);
228 
229  public int GetPurchasedItemCount(Identifier identifier, ItemPrefab prefab) =>
230  GetPurchasedItems(identifier)?.Where(i => i.ItemPrefab == prefab).Sum(it => it.Quantity) ?? 0;
231 
232  public List<SoldItem> GetSoldItems(Identifier identifier, bool create = false) => GetItems(identifier, SoldItems, create);
233 
234  public List<SoldItem> GetSoldItems(Location.StoreInfo store, bool create = false) => GetSoldItems(store?.Identifier ?? Identifier.Empty, create);
235 
236  public void ClearItemsInBuyCrate()
237  {
238  ItemsInBuyCrate.Clear();
239  OnItemsInBuyCrateChanged?.Invoke(this);
240  }
241 
242  public void ClearItemsInSellCrate()
243  {
244  ItemsInSellCrate.Clear();
245  OnItemsInSellCrateChanged?.Invoke(this);
246  }
247 
249  {
250  ItemsInSellFromSubCrate.Clear();
251  OnItemsInSellFromSubCrateChanged?.Invoke(this);
252  }
253 
254  public void SetPurchasedItems(Dictionary<Identifier, List<PurchasedItem>> purchasedItems)
255  {
256  if (purchasedItems.Count == 0 && PurchasedItems.Count == 0) { return; }
257  PurchasedItems.Clear();
258  foreach (var entry in purchasedItems)
259  {
260  PurchasedItems.Add(entry.Key, entry.Value);
261  }
262  OnPurchasedItemsChanged?.Invoke(this);
263  }
264 
265  public void ModifyItemQuantityInBuyCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client = null)
266  {
267  if (GetBuyCrateItem(storeIdentifier, itemPrefab) is { } item)
268  {
269  item.Quantity += changeInQuantity;
270  if (item.Quantity < 1)
271  {
272  GetBuyCrateItems(storeIdentifier, create: true).Remove(item);
273  }
274  }
275  else if (changeInQuantity > 0)
276  {
277  GetBuyCrateItems(storeIdentifier, create: true).Add(new PurchasedItem(itemPrefab, changeInQuantity, client));
278  }
279  OnItemsInBuyCrateChanged?.Invoke(this);
280  }
281 
282  public void ModifyItemQuantityInSubSellCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client = null)
283  {
284  if (GetSubCrateItem(storeIdentifier, itemPrefab) is { } item)
285  {
286  item.Quantity += changeInQuantity;
287  if (item.Quantity < 1)
288  {
289  GetSubCrateItems(storeIdentifier)?.Remove(item);
290  }
291  }
292  else if (changeInQuantity > 0)
293  {
294  GetSubCrateItems(storeIdentifier, create: true).Add(new PurchasedItem(itemPrefab, changeInQuantity, client));
295  }
296  OnItemsInSellFromSubCrateChanged?.Invoke(this);
297  }
298 
299  public void PurchaseItems(Identifier storeIdentifier, List<PurchasedItem> itemsToPurchase, bool removeFromCrate, Client client = null)
300  {
301  var store = Location?.GetStore(storeIdentifier);
302  if (store == null) { return; }
303  var itemsPurchasedFromStore = GetPurchasedItems(storeIdentifier, create: true);
304  // Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction
305  var buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, itemsToPurchase.Select(i => i.ItemPrefab));
306  var itemsInStoreCrate = GetBuyCrateItems(storeIdentifier, create: true);
307  foreach (PurchasedItem item in itemsToPurchase)
308  {
309  if (item.Quantity <= 0) { continue; }
310  // Exchange money
311  int itemValue = item.Quantity * buyValues[item.ItemPrefab];
312  if (!campaign.TryPurchase(client, itemValue)) { continue; }
313 
314  // Add to the purchased items
315  var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab && pi.DeliverImmediately == item.DeliverImmediately);
316  if (purchasedItem != null)
317  {
318  purchasedItem.Quantity += item.Quantity;
319  }
320  else
321  {
322  purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity, client) { DeliverImmediately = item.DeliverImmediately };
323  itemsPurchasedFromStore.Add(purchasedItem);
324  }
325  purchasedItem.Delivered = item.DeliverImmediately;
327  {
328  GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
329  }
330  store.Balance += itemValue;
331  }
332  if (GameMain.NetworkMember is not { IsClient: true })
333  {
334  Character targetCharacter;
335 #if CLIENT
336  targetCharacter = Character.Controlled;
337  if (targetCharacter == null)
338  {
339  DebugConsole.ThrowError("Failed to deliver items directly to a character (not controlling a character).");
340  }
341 #else
342  targetCharacter = client?.Character;
343  if (targetCharacter == null)
344  {
345  DebugConsole.ThrowError($"Failed to deliver items directly to a character ({(client == null ? "client was null" : $"client {client.Name} is not controlling a character")}).");
346  }
347 #endif
348  if (targetCharacter == null)
349  {
350  DeliverItemsToSub(itemsToPurchase.Where(it => it.DeliverImmediately), Submarine.MainSub, this);
351  }
352  else
353  {
354  DeliverItemsToCharacter(itemsToPurchase.Where(it => it.DeliverImmediately), targetCharacter, this);
355  }
356 
357  }
358  if (removeFromCrate)
359  {
360  foreach (PurchasedItem item in itemsToPurchase)
361  {
362  // Remove from the shopping crate
363  if (itemsInStoreCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } crateItem)
364  {
365  crateItem.Quantity -= item.Quantity;
366  if (crateItem.Quantity < 1) { itemsInStoreCrate.Remove(crateItem); }
367  }
368  }
369  }
370  OnPurchasedItemsChanged?.Invoke(this);
371  }
372 
373  public Dictionary<ItemPrefab, int> GetBuyValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable<ItemPrefab> items)
374  {
375  var buyValues = new Dictionary<ItemPrefab, int>();
376  var store = Location?.GetStore(storeIdentifier);
377  if (store == null) { return buyValues; }
378  foreach (var item in items)
379  {
380  if (item == null) { continue; }
381  if (!buyValues.ContainsKey(item))
382  {
383  int buyValue = store?.GetAdjustedItemBuyPrice(item) ?? 0;
384  buyValues.Add(item, buyValue);
385  }
386  }
387  return buyValues;
388  }
389 
390  public Dictionary<ItemPrefab, int> GetSellValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable<ItemPrefab> items)
391  {
392  var sellValues = new Dictionary<ItemPrefab, int>();
393  var store = Location?.GetStore(storeIdentifier);
394  if (store == null) { return sellValues; }
395  foreach (var item in items)
396  {
397  if (item == null) { continue; }
398  if (!sellValues.ContainsKey(item))
399  {
400  int sellValue = store?.GetAdjustedItemSellPrice(item) ?? 0;
401  sellValues.Add(item, sellValue);
402  }
403  }
404  return sellValues;
405  }
406 
407  public void CreatePurchasedItems()
408  {
409  purchasedIDCards.Clear();
410  var items = new List<PurchasedItem>();
411  foreach (var storeSpecificItems in PurchasedItems)
412  {
413  items.AddRange(storeSpecificItems.Value.Where(it => !it.DeliverImmediately));
414  }
415  DeliverItemsToSub(items, Submarine.MainSub, this);
416  PurchasedItems.Clear();
417  OnPurchasedItemsChanged?.Invoke(this);
418  }
419 
420  private Dictionary<ItemPrefab, int> UndeterminedSoldEntities { get; } = new Dictionary<ItemPrefab, int>();
421 
422  public IEnumerable<Item> GetSellableItemsFromSub()
423  {
424  if (Submarine.MainSub == null) { return new List<Item>(); }
425  var confirmedSoldEntities = Enumerable.Empty<SoldEntity>();
426  UndeterminedSoldEntities.Clear();
427 #if CLIENT
428  confirmedSoldEntities = GetConfirmedSoldEntities();
429  foreach (var soldEntity in SoldEntities)
430  {
431  if (soldEntity.Item != null) { continue; }
432  if (UndeterminedSoldEntities.TryGetValue(soldEntity.ItemPrefab, out int count))
433  {
434  UndeterminedSoldEntities[soldEntity.ItemPrefab] = count + 1;
435  }
436  else
437  {
438  UndeterminedSoldEntities.Add(soldEntity.ItemPrefab, 1);
439  }
440  }
441 #endif
442  return FindAllSellableItems().Where(it => IsItemSellable(it, confirmedSoldEntities)).ToList();
443  }
444 
445  public static IReadOnlyCollection<Item> FindAllItemsOnPlayerAndSub(Character character)
446  {
447  List<Item> allItems = new();
448  if (character?.Inventory is { } inv)
449  {
450  allItems.AddRange(inv.FindAllItems(recursive: true));
451  }
452  allItems.AddRange(FindAllSellableItems());
453  return allItems;
454  }
455 
456  public static IEnumerable<Item> FindAllSellableItems()
457  {
458  if (Submarine.MainSub is null) { return Enumerable.Empty<Item>(); }
459 
460  return Submarine.MainSub.GetItems(true).FindAll(static item =>
461  {
462  if (item.GetRootInventoryOwner() is Character) { return false; }
463  if (!item.Components.All(static c => c is not Holdable { Attachable: true, Attached: true })) { return false; }
464  if (!item.Components.All(static c => c is not Wire w || w.Connections.All(static c => c is null))) { return false; }
465  if (!ItemAndAllContainersInteractable(item)) { return false; }
466  if (!AllContainersAllowSellingItems(item)) { return false; }
467  return true;
468  }).Distinct();
469 
470  static bool AllContainersAllowSellingItems(Item item)
471  {
472  do
473  {
474  item = item.Container;
475  if (item is null) { return true; }
476  if (item.HasTag(Tags.DontSellItems)) { return false; }
477  if (item.Components.Any(static c => c.DisallowSellingItemsFromContainer)) { return false; }
478  } while (item != null);
479  return true;
480  }
481 
482  static bool ItemAndAllContainersInteractable(Item item)
483  {
484  do
485  {
486  if (!item.IsPlayerTeamInteractable) { return false; }
487  item = item.Container;
488  } while (item != null);
489  return true;
490  }
491  }
492 
493  private bool IsItemSellable(Item item, IEnumerable<SoldEntity> confirmedItems)
494  {
495  if (item.Removed) { return false; }
496  if (!item.Prefab.CanBeSold) { return false; }
497  if (item.SpawnedInCurrentOutpost) { return false; }
498  if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
499  if (confirmedItems != null && confirmedItems.Any(ci => ci.Item == item)) { return false; }
500  if (UndeterminedSoldEntities.TryGetValue(item.Prefab, out int count))
501  {
502  int newCount = count - 1;
503  if (newCount > 0)
504  {
505  UndeterminedSoldEntities[item.Prefab] = newCount;
506  }
507  else
508  {
509  UndeterminedSoldEntities.Remove(item.Prefab);
510  }
511  return false;
512  }
513  if (item.OwnInventory?.Container is ItemContainer itemContainer)
514  {
515  var containedItems = item.ContainedItems;
516  if (containedItems.None()) { return true; }
517  // Allow selling the item if contained items are unsellable and set to be removed on deconstruct
518  if (itemContainer.RemoveContainedItemsOnDeconstruct && containedItems.All(it => !it.Prefab.CanBeSold)) { return true; }
519  if (confirmedItems != null)
520  {
521  // Otherwise there must be no contained items or the contained items must be confirmed as sold
522  if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) { return false; }
523  }
524  }
525  return true;
526  }
527 
528  public static IEnumerable<Hull> FindCargoRooms(IEnumerable<Submarine> subs) => subs.SelectMany(s => FindCargoRooms(s));
529 
530  public static IEnumerable<Hull> FindCargoRooms(Submarine sub) => WayPoint.WayPointList
531  .Where(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Cargo)
532  .Select(wp => wp.CurrentHull)
533  .Distinct();
534 
535  public static IEnumerable<Item> FilterCargoCrates(IEnumerable<Item> items, Func<Item, bool> conditional = null)
536  => items.Where(it => it.HasTag(Tags.Crate) && !it.NonInteractable && !it.NonPlayerTeamInteractable && !it.IsHidden && !it.Removed && (conditional == null || conditional(it)));
537 
538  public static IEnumerable<ItemContainer> FindReusableCargoContainers(IEnumerable<Submarine> subs, IEnumerable<Hull> cargoRooms = null) =>
539  FilterCargoCrates(Item.ItemList, it => subs.Contains(it.Submarine) && !it.HasTag(Tags.CargoMissionItem) && (cargoRooms == null || cargoRooms.Contains(it.CurrentHull)))
540  .Select(it => it.GetComponent<ItemContainer>())
541  .Where(c => c != null);
542 
543  public static ItemContainer GetOrCreateCargoContainerFor(ItemPrefab item, ISpatialEntity cargoRoomOrSpawnPoint, ref List<ItemContainer> availableContainers)
544  {
545  ItemContainer itemContainer = null;
546  if (!string.IsNullOrEmpty(item.CargoContainerIdentifier))
547  {
548  itemContainer = availableContainers.Find(ac =>
549  ac.Inventory.CanBePut(item) &&
550  (ac.Item.Prefab.Identifier == item.CargoContainerIdentifier ||
551  ac.Item.Prefab.Tags.Contains(item.CargoContainerIdentifier)));
552 
553  if (itemContainer == null)
554  {
555  ItemPrefab containerPrefab = ItemPrefab.Prefabs.Find(ep =>
556  ep.Identifier == item.CargoContainerIdentifier ||
557  (ep.Tags != null && ep.Tags.Contains(item.CargoContainerIdentifier)));
558 
559  if (containerPrefab == null)
560  {
561  DebugConsole.AddWarning($"CargoManager: could not find the item prefab for container {item.CargoContainerIdentifier}!");
562  return null;
563  }
564 
565  Vector2 containerPosition = cargoRoomOrSpawnPoint is Hull cargoRoom ? GetCargoPos(cargoRoom, containerPrefab) : cargoRoomOrSpawnPoint.Position;
566  Item containerItem = new Item(containerPrefab, containerPosition, cargoRoomOrSpawnPoint.Submarine);
567  itemContainer = containerItem.GetComponent<ItemContainer>();
568  if (itemContainer == null)
569  {
570  DebugConsole.AddWarning($"CargoManager: No ItemContainer component found in {containerItem.Prefab.Identifier}!");
571  return null;
572  }
573  if (!itemContainer.CanBeContained(item))
574  {
575  // Can't contain the item in the crate -> let's not create it.
576  containerItem.Remove();
577  return null;
578  }
579  availableContainers.Add(itemContainer);
580 #if SERVER
581  if (GameMain.Server != null)
582  {
583  Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item));
584  }
585 #endif
586  }
587  }
588  return itemContainer;
589  }
590 
591  public static void DeliverItemsToSub(IEnumerable<PurchasedItem> itemsToSpawn, Submarine sub, CargoManager cargoManager)
592  {
593  if (!itemsToSpawn.Any()) { return; }
594 
595  WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub);
596  if (wp == null)
597  {
598  DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!");
599  return;
600  }
601 
602  Hull cargoRoom = Hull.FindHull(wp.WorldPosition);
603  if (cargoRoom == null)
604  {
605  DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
606  return;
607  }
608 
609  if (sub == Submarine.MainSub && itemsToSpawn.Any(it => !it.Delivered && it.Quantity > 0))
610  {
611 #if CLIENT
612  new GUIMessageBox("",
613  TextManager.GetWithVariable("CargoSpawnNotification",
614  "[roomname]",
615  cargoRoom.DisplayName,
616  FormatCapitals.Yes),
617  Array.Empty<LocalizedString>(),
618  type: GUIMessageBox.Type.InGame,
619  iconStyle: "StoreShoppingCrateIcon");
620 #else
621  foreach (Client client in GameMain.Server.ConnectedClients)
622  {
623  ChatMessage msg = ChatMessage.Create("",
624  TextManager.ContainsTag(cargoRoom.RoomName) ? $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}" : $"CargoSpawnNotification~[roomname]={cargoRoom.RoomName}",
625  ChatMessageType.ServerMessageBoxInGame, null);
626  msg.IconStyle = "StoreShoppingCrateIcon";
627  GameMain.Server.SendDirectChatMessage(msg, client);
628  }
629 #endif
630  }
631  var connectedSubs = sub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player);
632  List<ItemContainer> availableContainers = FindReusableCargoContainers(connectedSubs, FindCargoRooms(connectedSubs)).ToList();
633  foreach (PurchasedItem pi in itemsToSpawn)
634  {
635  pi.Delivered = true;
636  Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab);
637  for (int i = 0; i < pi.Quantity; i++)
638  {
639  var item = new Item(pi.ItemPrefab, position, wp.Submarine);
640  var itemContainer = GetOrCreateCargoContainerFor(pi.ItemPrefab, cargoRoom, ref availableContainers);
641  itemContainer?.Inventory.TryPutItem(item, null);
642  ItemSpawned(pi, item, cargoManager);
643 #if SERVER
644  Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
645 #endif
646  (itemContainer?.Item ?? item).AssignCampaignInteractionType(CampaignMode.InteractionType.Cargo);
647  }
648  }
649  }
650 
651  public static void DeliverItemsToCharacter(IEnumerable<PurchasedItem> itemsToSpawn, Character character, CargoManager cargoManager)
652  {
653  if (!itemsToSpawn.Any()) { return; }
654 
655  foreach (PurchasedItem pi in itemsToSpawn)
656  {
657  pi.Delivered = true;
658  for (int i = 0; i < pi.Quantity; i++)
659  {
660  var item = new Item(pi.ItemPrefab, character.Position, character.Submarine);
661 #if SERVER
662  Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
663 #endif
664  if (item.GetComponent<Holdable>() is { Attached: true })
665  {
666  item.Drop(dropper: null);
667  }
668  if (!character.Inventory.TryPutItem(item, user: null, item.AllowedSlots))
669  {
670  foreach (Item containedItem in character.Inventory.AllItemsMod)
671  {
672  if (containedItem.OwnInventory != null &&
673  containedItem.OwnInventory.TryPutItem(item, user: null, item.AllowedSlots))
674  {
675  break;
676  }
677  }
678  }
679  ItemSpawned(pi, item, cargoManager);
680  }
681  }
682  }
683  private static void ItemSpawned(PurchasedItem purchased, Item item, CargoManager cargoManager)
684  {
685  var idCard = item.GetComponent<IdCard>();
686  if (cargoManager != null && idCard != null && purchased.BuyerCharacterInfoIdentifier != 0)
687  {
688  if (purchased.DeliverImmediately)
689  {
690  InitPurchasedIDCard(purchased, idCard);
691  }
692  else
693  {
694  cargoManager.purchasedIDCards.Add((purchased, idCard));
695  }
696  }
697 
698  Submarine sub = item.Submarine ?? item.RootContainer?.Submarine;
699  if (sub != null)
700  {
701  foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
702  {
703  wifiComponent.TeamID = sub.TeamID;
704  }
705  }
706  }
707 
708  private readonly List<(PurchasedItem purchaseInfo, IdCard idCard)> purchasedIDCards = new List<(PurchasedItem purchaseInfo, IdCard idCard)>();
709  public void InitPurchasedIDCards()
710  {
711  foreach ((PurchasedItem purchased, IdCard idCard) in purchasedIDCards)
712  {
713  InitPurchasedIDCard(purchased, idCard);
714  }
715  }
716 
717  private static void InitPurchasedIDCard(PurchasedItem purchased, IdCard idCard)
718  {
719  if (idCard != null && purchased.BuyerCharacterInfoIdentifier != 0)
720  {
721  var owner = Character.CharacterList.Find(c => c.Info?.GetIdentifier() == purchased.BuyerCharacterInfoIdentifier);
722  if (owner?.Info != null)
723  {
724  var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(new List<CharacterInfo>() { owner.Info }, Submarine.MainSub);
725  idCard.Initialize(mainSubSpawnPoints.FirstOrDefault(), owner);
726  }
727  }
728  }
729 
730  public static Vector2 GetCargoPos(Hull hull, ItemPrefab itemPrefab)
731  {
732  float floorPos = hull.Rect.Y - hull.Rect.Height;
733 
734  Vector2 position = new Vector2(
735  hull.Rect.Width > 40 ? Rand.Range(hull.Rect.X + 20f, hull.Rect.Right - 20f) : hull.Rect.Center.X,
736  floorPos);
737 
738  //check where the actual floor structure is in case the bottom of the hull extends below it
739  if (Submarine.PickBody(
740  ConvertUnits.ToSimUnits(new Vector2(position.X, hull.Rect.Y - hull.Rect.Height / 2)),
741  ConvertUnits.ToSimUnits(position),
742  collisionCategory: Physics.CollisionWall) != null)
743  {
744  float floorStructurePos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition.Y);
745  if (floorStructurePos > floorPos)
746  {
747  floorPos = floorStructurePos;
748  }
749  }
750 
751  position.Y = floorPos + itemPrefab.Size.Y / 2;
752 
753  return position;
754  }
755 
756  public void SavePurchasedItems(XElement parentElement)
757  {
758  var itemsElement = new XElement("cargo");
759  foreach (var storeSpecificItems in PurchasedItems)
760  {
761  foreach (var item in storeSpecificItems.Value)
762  {
763  if (item?.ItemPrefab == null) { continue; }
764  itemsElement.Add(new XElement("item",
765  new XAttribute("id", item.ItemPrefab.Identifier),
766  new XAttribute("qty", item.Quantity),
767  new XAttribute("storeid", storeSpecificItems.Key),
768  new XAttribute("deliverimmediately", item.DeliverImmediately),
769  new XAttribute("buyer", item.BuyerCharacterInfoIdentifier)));
770  }
771  }
772  parentElement.Add(itemsElement);
773  }
774 
775  public void LoadPurchasedItems(XElement element)
776  {
777  var purchasedItems = new Dictionary<Identifier, List<PurchasedItem>>();
778  if (element != null)
779  {
780  foreach (XElement itemElement in element.GetChildElements("item"))
781  {
782  string prefabId = itemElement.GetAttributeString("id", null);
783  if (string.IsNullOrWhiteSpace(prefabId)) { continue; }
784  if (!ItemPrefab.Prefabs.TryGet(prefabId.ToIdentifier(), out var prefab)) { continue; }
785  int qty = itemElement.GetAttributeInt("qty", 0);
786  Identifier storeId = itemElement.GetAttributeIdentifier("storeid", "merchant");
787  bool deliverImmediately = itemElement.GetAttributeBool("deliverimmediately", false);
788  int buyerId = itemElement.GetAttributeInt("buyer", 0);
789  if (!purchasedItems.TryGetValue(storeId, out var storeItems))
790  {
791  storeItems = new List<PurchasedItem>();
792  purchasedItems.Add(storeId, storeItems);
793  }
794  storeItems.Add(new PurchasedItem(prefab, qty, buyerId)
795  {
796  DeliverImmediately = deliverImmediately,
797  //must have already been delivered if we had opted for immediate delivery
798  Delivered = deliverImmediately
799  });
800  }
801  }
802  SetPurchasedItems(purchasedItems);
803  }
804  }
805 }
static IEnumerable< Item > FilterCargoCrates(IEnumerable< Item > items, Func< Item, bool > conditional=null)
Dictionary< Identifier, List< PurchasedItem > > PurchasedItems
List< PurchasedItem > GetSubCrateItems(Identifier identifier, bool create=false)
List< PurchasedItem > GetSellCrateItems(Identifier identifier, bool create=false)
List< PurchasedItem > GetSubCrateItems(Location.StoreInfo store, bool create=false)
void SetPurchasedItems(Dictionary< Identifier, List< PurchasedItem >> purchasedItems)
void PurchaseItems(Identifier storeIdentifier, List< PurchasedItem > itemsToPurchase, bool removeFromCrate, Client client=null)
int GetPurchasedItemCount(Identifier identifier, ItemPrefab prefab)
List< PurchasedItem > GetPurchasedItems(Identifier identifier, bool create=false)
static IEnumerable< Hull > FindCargoRooms(IEnumerable< Submarine > subs)
static IEnumerable< Hull > FindCargoRooms(Submarine sub)
PurchasedItem GetBuyCrateItem(Location.StoreInfo store, ItemPrefab prefab)
Dictionary< Identifier, List< PurchasedItem > > ItemsInSellFromSubCrate
void ModifyItemQuantityInSubSellCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client=null)
void ModifyItemQuantityInBuyCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client=null)
PurchasedItem GetSellCrateItem(Location.StoreInfo store, ItemPrefab prefab)
static ItemContainer GetOrCreateCargoContainerFor(ItemPrefab item, ISpatialEntity cargoRoomOrSpawnPoint, ref List< ItemContainer > availableContainers)
Dictionary< Identifier, List< PurchasedItem > > ItemsInBuyCrate
PurchasedItem GetSubCrateItem(Location.StoreInfo store, ItemPrefab prefab)
Dictionary< ItemPrefab, int > GetBuyValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable< ItemPrefab > items)
readonly NamedEvent< CargoManager > OnItemsInSellFromSubCrateChanged
static Vector2 GetCargoPos(Hull hull, ItemPrefab itemPrefab)
int GetPurchasedItemCount(Location.StoreInfo store, ItemPrefab prefab)
List< PurchasedItem > GetPurchasedItems(Location.StoreInfo store, bool create=false)
PurchasedItem GetBuyCrateItem(Identifier identifier, ItemPrefab prefab)
PurchasedItem GetSellCrateItem(Identifier identifier, ItemPrefab prefab)
List< SoldItem > GetSoldItems(Identifier identifier, bool create=false)
static void DeliverItemsToSub(IEnumerable< PurchasedItem > itemsToSpawn, Submarine sub, CargoManager cargoManager)
List< SoldItem > GetSoldItems(Location.StoreInfo store, bool create=false)
Dictionary< Identifier, List< PurchasedItem > > ItemsInSellCrate
Dictionary< Identifier, List< SoldItem > > SoldItems
List< PurchasedItem > GetBuyCrateItems(Location.StoreInfo store, bool create=false)
Dictionary< ItemPrefab, int > GetSellValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable< ItemPrefab > items)
static void DeliverItemsToCharacter(IEnumerable< PurchasedItem > itemsToSpawn, Character character, CargoManager cargoManager)
List< PurchasedItem > GetSellCrateItems(Location.StoreInfo store, bool create=false)
List< PurchasedItem > GetBuyCrateItems(Identifier identifier, bool create=false)
static IReadOnlyCollection< Item > FindAllItemsOnPlayerAndSub(Character character)
static IEnumerable< ItemContainer > FindReusableCargoContainers(IEnumerable< Submarine > subs, IEnumerable< Hull > cargoRooms=null)
PurchasedItem GetSubCrateItem(Identifier identifier, ItemPrefab prefab)
Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo=null, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, RagdollParams ragdollParams=null, bool spawnInitialItems=true)
int GetIdentifier()
Returns a presumably (not guaranteed) unique and persistent hash using the (current) Name,...
override bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
static EntitySpawner Spawner
Definition: Entity.cs:31
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
const ushort NullEntityID
Definition: Entity.cs:14
static bool IsSingleplayer
Definition: GameMain.cs:34
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static ImmutableHashSet< Character > GetSessionCrewCharacters(CharacterType type)
Returns a list of crew characters currently in the game with a given filter.
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
IEnumerable< Item > AllItemsMod
All items contained in the inventory. Allows modifying the contents of the inventory while being enum...
bool IsPlayerTeamInteractable
Checks both NonInteractable and NonPlayerTeamInteractable
static readonly List< Item > ItemList
override bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
static readonly PrefabCollection< ItemPrefab > Prefabs
bool CanBeSold
Any item with a Price element in the definition can be sold everywhere.
int GetAdjustedItemSellPrice(ItemPrefab item, PriceInfo priceInfo=null, bool considerRequestedGoods=true)
Definition: Location.cs:324
int GetAdjustedItemBuyPrice(ItemPrefab item, PriceInfo priceInfo=null, bool considerDailySpecials=true)
Definition: Location.cs:283
StoreInfo GetStore(Identifier identifier)
Definition: Location.cs:1299
static ChatMessage Create(string senderName, string text, ChatMessageType type, Entity sender, Client client=null, PlayerConnectionChangeType changeType=PlayerConnectionChangeType.None, Color? textColor=null)
readonly Identifier Identifier
Definition: Prefab.cs:34
PurchasedItem(Identifier itemPrefabId, int quantity, Client buyer)
bool DeliverImmediately
Should the items be given to the buyer immediately, as opposed to spawning them in the sub the next r...
PurchasedItem(ItemPrefab itemPrefab, int quantity, int buyerCharacterInfoId)
PurchasedItem(ItemPrefab itemPrefab, int quantity, Client buyer)
SoldItem(ItemPrefab itemPrefab, ushort id, bool removed, byte sellerId, SellOrigin origin)
List< Item > GetItems(bool alsoFromConnectedSubs)
IEnumerable< Submarine > GetConnectedSubs()
Returns a list of all submarines that are connected to this one via docking ports,...
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
static WayPoint GetRandom(SpawnType spawnType=SpawnType.Human, JobPrefab assignedJob=null, Submarine sub=null, bool useSyncedRand=false, string spawnPointTag=null, bool ignoreSubmarine=false)
static WayPoint[] SelectCrewSpawnPoints(List< CharacterInfo > crew, Submarine submarine)
CharacterType
Definition: Enums.cs:685