4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
11 using System.Collections;
12 using System.Collections.Immutable;
46 : this(itemPrefab, quantity, buyer: null) { }
49 : this(itemPrefab.Identifier, quantity, buyer) { }
61 return $
"{ItemPrefab.Name} ({Quantity})";
68 public ushort
ID {
get;
private set; }
92 DebugConsole.LogError(
"Error setting SoldItem.ID: ID has already been set and should not be changed.");
101 private class SoldEntity
103 public enum SellStatus
119 public Item Item {
get;
private set; }
121 public SellStatus Status {
get;
set; }
123 public SoldEntity(
Item item, SellStatus status)
130 public SoldEntity(
ItemPrefab itemPrefab, SellStatus status)
136 public void SetItem(
Item item)
140 DebugConsole.LogError($
"Trying to set SoldEntity.Item, but it's already set!\n{Environment.StackTrace.CleanupStackTrace()}");
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>>();
167 this.campaign = campaign;
180 private List<T> GetItems<T>(Identifier identifier, Dictionary<Identifier, List<T>> items,
bool create =
false)
182 if (items.TryGetValue(identifier, out var storeSpecificItems) && storeSpecificItems !=
null)
184 return storeSpecificItems;
188 storeSpecificItems =
new List<T>();
189 items.Add(identifier, storeSpecificItems);
190 return storeSpecificItems;
194 return new List<T>();
230 GetPurchasedItems(identifier)?.Where(i => i.ItemPrefab == prefab).Sum(it => it.Quantity) ?? 0;
232 public List<SoldItem>
GetSoldItems(Identifier identifier,
bool create =
false) => GetItems(identifier,
SoldItems, create);
256 if (purchasedItems.Count == 0 &&
PurchasedItems.Count == 0) {
return; }
258 foreach (var entry
in purchasedItems)
269 item.Quantity += changeInQuantity;
270 if (item.Quantity < 1)
275 else if (changeInQuantity > 0)
286 item.Quantity += changeInQuantity;
287 if (item.Quantity < 1)
292 else if (changeInQuantity > 0)
299 public void PurchaseItems(Identifier storeIdentifier, List<PurchasedItem> itemsToPurchase,
bool removeFromCrate,
Client client =
null)
302 if (store ==
null) {
return; }
309 if (item.
Quantity <= 0) {
continue; }
312 if (!campaign.TryPurchase(client, itemValue)) {
continue; }
315 var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.
ItemPrefab && pi.DeliverImmediately == item.
DeliverImmediately);
316 if (purchasedItem !=
null)
318 purchasedItem.Quantity += item.
Quantity;
323 itemsPurchasedFromStore.Add(purchasedItem);
328 GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.
ItemPrefab.
Identifier.Value);
330 store.Balance += itemValue;
337 if (targetCharacter ==
null)
339 DebugConsole.ThrowError(
"Failed to deliver items directly to a character (not controlling a character).");
343 if (targetCharacter ==
null)
345 DebugConsole.ThrowError($
"Failed to deliver items directly to a character ({(client == null ? "client was
null" : $"client {client.Name} is not controlling a character
")}).");
348 if (targetCharacter ==
null)
363 if (itemsInStoreCrate.Find(pi => pi.ItemPrefab == item.
ItemPrefab) is { } crateItem)
365 crateItem.Quantity -= item.
Quantity;
366 if (crateItem.Quantity < 1) { itemsInStoreCrate.Remove(crateItem); }
375 var buyValues =
new Dictionary<ItemPrefab, int>();
377 if (store ==
null) {
return buyValues; }
378 foreach (var item
in items)
380 if (item ==
null) {
continue; }
381 if (!buyValues.ContainsKey(item))
384 buyValues.Add(item, buyValue);
392 var sellValues =
new Dictionary<ItemPrefab, int>();
394 if (store ==
null) {
return sellValues; }
395 foreach (var item
in items)
397 if (item ==
null) {
continue; }
398 if (!sellValues.ContainsKey(item))
401 sellValues.Add(item, sellValue);
409 purchasedIDCards.Clear();
410 var items =
new List<PurchasedItem>();
413 items.AddRange(storeSpecificItems.Value.Where(it => !it.DeliverImmediately));
420 private Dictionary<ItemPrefab, int> UndeterminedSoldEntities {
get; } =
new Dictionary<ItemPrefab, int>();
425 var confirmedSoldEntities = Enumerable.Empty<SoldEntity>();
426 UndeterminedSoldEntities.Clear();
428 confirmedSoldEntities = GetConfirmedSoldEntities();
429 foreach (var soldEntity
in SoldEntities)
431 if (soldEntity.Item !=
null) {
continue; }
432 if (UndeterminedSoldEntities.TryGetValue(soldEntity.ItemPrefab, out
int count))
434 UndeterminedSoldEntities[soldEntity.ItemPrefab] = count + 1;
438 UndeterminedSoldEntities.Add(soldEntity.ItemPrefab, 1);
447 List<Item> allItems =
new();
450 allItems.AddRange(inv.FindAllItems(recursive:
true));
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; }
470 static bool AllContainersAllowSellingItems(
Item item)
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);
482 static bool ItemAndAllContainersInteractable(
Item item)
488 }
while (item !=
null);
493 private bool IsItemSellable(
Item item, IEnumerable<SoldEntity> confirmedItems)
495 if (item.
Removed) {
return false; }
499 if (confirmedItems !=
null && confirmedItems.Any(ci => ci.Item == item)) {
return false; }
500 if (UndeterminedSoldEntities.TryGetValue(item.
Prefab, out
int count))
502 int newCount = count - 1;
505 UndeterminedSoldEntities[item.
Prefab] = newCount;
509 UndeterminedSoldEntities.Remove(item.
Prefab);
516 if (containedItems.None()) {
return true; }
518 if (itemContainer.RemoveContainedItemsOnDeconstruct && containedItems.All(it => !it.Prefab.CanBeSold)) {
return true; }
519 if (confirmedItems !=
null)
522 if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) {
return false; }
531 .Where(wp => wp.Submarine == sub && wp.SpawnType ==
SpawnType.Cargo)
532 .Select(wp => wp.CurrentHull)
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)));
539 FilterCargoCrates(
Item.
ItemList, it => subs.Contains(it.Submarine) && !it.HasTag(Tags.CargoMissionItem) && (cargoRooms ==
null || cargoRooms.Contains(it.CurrentHull)))
541 .Where(c => c !=
null);
548 itemContainer = availableContainers.Find(ac =>
549 ac.Inventory.CanBePut(item) &&
553 if (itemContainer ==
null)
559 if (containerPrefab ==
null)
561 DebugConsole.AddWarning($
"CargoManager: could not find the item prefab for container {item.CargoContainerIdentifier}!");
565 Vector2 containerPosition = cargoRoomOrSpawnPoint is
Hull cargoRoom ?
GetCargoPos(cargoRoom, containerPrefab) : cargoRoomOrSpawnPoint.
Position;
566 Item containerItem =
new Item(containerPrefab, containerPosition, cargoRoomOrSpawnPoint.
Submarine);
568 if (itemContainer ==
null)
570 DebugConsole.AddWarning($
"CargoManager: No ItemContainer component found in {containerItem.Prefab.Identifier}!");
579 availableContainers.Add(itemContainer);
588 return itemContainer;
593 if (!itemsToSpawn.Any()) {
return; }
598 DebugConsole.ThrowError(
"The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!");
603 if (cargoRoom ==
null)
605 DebugConsole.ThrowError(
"A waypoint marked as Cargo must be placed inside a room!");
609 if (sub ==
Submarine.
MainSub && itemsToSpawn.Any(it => !it.Delivered && it.Quantity > 0))
613 TextManager.GetWithVariable(
"CargoSpawnNotification",
619 iconStyle:
"StoreShoppingCrateIcon");
624 TextManager.ContainsTag(cargoRoom.
RoomName) ? $
"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}" : $
"CargoSpawnNotification~[roomname]={cargoRoom.RoomName}",
626 msg.
IconStyle =
"StoreShoppingCrateIcon";
627 GameMain.Server.SendDirectChatMessage(msg, client);
637 for (
int i = 0; i < pi.
Quantity; i++)
641 itemContainer?.Inventory.TryPutItem(item,
null);
642 ItemSpawned(pi, item, cargoManager);
653 if (!itemsToSpawn.Any()) {
return; }
658 for (
int i = 0; i < pi.
Quantity; i++)
664 if (item.GetComponent<
Holdable>() is { Attached: true })
666 item.Drop(dropper:
null);
679 ItemSpawned(pi, item, cargoManager);
685 var idCard = item.GetComponent<
IdCard>();
690 InitPurchasedIDCard(purchased, idCard);
694 cargoManager.purchasedIDCards.Add((purchased, idCard));
703 wifiComponent.
TeamID = sub.TeamID;
708 private readonly List<(PurchasedItem purchaseInfo,
IdCard idCard)> purchasedIDCards =
new List<(PurchasedItem purchaseInfo,
IdCard idCard)>();
713 InitPurchasedIDCard(purchased, idCard);
722 if (owner?.Info !=
null)
725 idCard.
Initialize(mainSubSpawnPoints.FirstOrDefault(), owner);
732 float floorPos = hull.
Rect.Y - hull.
Rect.Height;
734 Vector2 position =
new Vector2(
735 hull.
Rect.Width > 40 ? Rand.Range(hull.
Rect.X + 20f, hull.
Rect.Right - 20f) : hull.
Rect.Center.X,
740 ConvertUnits.ToSimUnits(
new Vector2(position.X, hull.
Rect.Y - hull.
Rect.Height / 2)),
741 ConvertUnits.ToSimUnits(position),
742 collisionCategory: Physics.CollisionWall) !=
null)
745 if (floorStructurePos > floorPos)
747 floorPos = floorStructurePos;
751 position.Y = floorPos + itemPrefab.
Size.Y / 2;
758 var itemsElement =
new XElement(
"cargo");
761 foreach (var item
in storeSpecificItems.Value)
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)));
772 parentElement.Add(itemsElement);
777 var purchasedItems =
new Dictionary<Identifier, List<PurchasedItem>>();
780 foreach (XElement itemElement
in element.GetChildElements(
"item"))
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))
791 storeItems =
new List<PurchasedItem>();
792 purchasedItems.Add(storeId, storeItems);
796 DeliverImmediately = deliverImmediately,
798 Delivered = deliverImmediately
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)
void ClearItemsInBuyCrate()
List< PurchasedItem > GetSellCrateItems(Identifier identifier, bool create=false)
CargoManager(CampaignMode campaign)
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)
void InitPurchasedIDCards()
static IEnumerable< Item > FindAllSellableItems()
PurchasedItem GetBuyCrateItem(Location.StoreInfo store, ItemPrefab prefab)
Dictionary< Identifier, List< PurchasedItem > > ItemsInSellFromSubCrate
void CreatePurchasedItems()
IEnumerable< Item > GetSellableItemsFromSub()
readonly NamedEvent< CargoManager > OnPurchasedItemsChanged
void ModifyItemQuantityInSubSellCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client=null)
void SavePurchasedItems(XElement parentElement)
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)
void ClearItemsInSellCrate()
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)
readonly NamedEvent< CargoManager > OnSoldItemsChanged
List< SoldItem > GetSoldItems(Location.StoreInfo store, bool create=false)
Dictionary< Identifier, List< PurchasedItem > > ItemsInSellCrate
void ClearItemsInSellFromSubCrate()
Dictionary< Identifier, List< SoldItem > > SoldItems
List< PurchasedItem > GetBuyCrateItems(Location.StoreInfo store, bool create=false)
void LoadPurchasedItems(XElement element)
Dictionary< ItemPrefab, int > GetSellValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable< ItemPrefab > items)
static void DeliverItemsToCharacter(IEnumerable< PurchasedItem > itemsToSpawn, Character character, CargoManager cargoManager)
readonly NamedEvent< CargoManager > OnItemsInSellCrateChanged
static bool HasUnlockedStoreItem(ItemPrefab prefab)
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)
readonly NamedEvent< CargoManager > OnItemsInBuyCrateChanged
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)
static readonly List< Character > CharacterList
bool HasStoreAccessForItem(ItemPrefab prefab)
CharacterInventory Inventory
static Character? Controlled
override Vector2 Position
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
virtual Vector2 WorldPosition
const ushort NullEntityID
static bool IsSingleplayer
static NetworkMember NetworkMember
static ImmutableHashSet< Character > GetSessionCrewCharacters(CharacterType type)
Returns a list of crew characters currently in the game with a given filter.
LocalizedString DisplayName
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
ItemInventory OwnInventory
bool?? SpawnedInCurrentOutpost
bool HasTag(Identifier tag)
IEnumerable< Item > ContainedItems
static readonly List< Item > ItemList
float ConditionPercentage
List< ItemComponent > Components
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.
string CargoContainerIdentifier
bool AllowSellingWhenBroken
void Initialize(WayPoint spawnPoint, Character character)
bool CanBeContained(Item item)
int GetAdjustedItemSellPrice(ItemPrefab item, PriceInfo priceInfo=null, bool considerRequestedGoods=true)
int GetAdjustedItemBuyPrice(ItemPrefab item, PriceInfo priceInfo=null, bool considerDailySpecials=true)
StoreInfo GetStore(Identifier identifier)
static ChatMessage Create(string senderName, string text, ChatMessageType type, Entity sender, Client client=null, PlayerConnectionChangeType changeType=PlayerConnectionChangeType.None, Color? textColor=null)
readonly Identifier Identifier
bool? IsStoreComponentEnabled
PurchasedItem(Identifier itemPrefabId, int quantity, Client buyer)
Identifier ItemPrefabIdentifier
PurchasedItem(ItemPrefab itemPrefab, int quantity)
readonly int BuyerCharacterInfoIdentifier
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)
override string ToString()
SoldItem(ItemPrefab itemPrefab, ushort id, bool removed, byte sellerId, SellOrigin origin)
void SetItemId(ushort id)
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 Vector2 LastPickedPosition
static List< WayPoint > WayPointList
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)