using Barotrauma.Extensions; using Barotrauma.Items.Components; using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using Barotrauma.Networking; using System.Collections; using System.Collections.Immutable; #if SERVER using Barotrauma.Networking; #endif namespace Barotrauma { class PurchasedItem { public ItemPrefab ItemPrefab => ItemPrefab.Prefabs[ItemPrefabIdentifier]; public Identifier ItemPrefabIdentifier { get; } public int Quantity { get; set; } public bool? IsStoreComponentEnabled { get; set; } public readonly int BuyerCharacterInfoIdentifier; /// /// Should the items be given to the buyer immediately, as opposed to spawning them in the sub the next round? /// public bool DeliverImmediately { get; set; } public bool Delivered; public PurchasedItem(ItemPrefab itemPrefab, int quantity, int buyerCharacterInfoId) { ItemPrefabIdentifier = itemPrefab.Identifier; Quantity = quantity; IsStoreComponentEnabled = null; BuyerCharacterInfoIdentifier = buyerCharacterInfoId; } #if CLIENT public PurchasedItem(ItemPrefab itemPrefab, int quantity) : this(itemPrefab, quantity, buyer: null) { } #endif public PurchasedItem(ItemPrefab itemPrefab, int quantity, Client buyer) : this(itemPrefab.Identifier, quantity, buyer) { } public PurchasedItem(Identifier itemPrefabId, int quantity, Client buyer) { ItemPrefabIdentifier = itemPrefabId; Quantity = quantity; IsStoreComponentEnabled = null; BuyerCharacterInfoIdentifier = buyer?.Character?.Info?.GetIdentifier() ?? Character.Controlled?.Info?.GetIdentifier() ?? 0; } public override string ToString() { return $"{ItemPrefab.Name} ({Quantity})"; } } class SoldItem { public ItemPrefab ItemPrefab { get; } public ushort ID { get; private set; } public bool Removed { get; set; } public byte SellerID { get; } public SellOrigin Origin { get; } public enum SellOrigin { Character, Submarine } public SoldItem(ItemPrefab itemPrefab, ushort id, bool removed, byte sellerId, SellOrigin origin) { ItemPrefab = itemPrefab; ID = id; Removed = removed; SellerID = sellerId; Origin = origin; } public void SetItemId(ushort id) { if (ID != Entity.NullEntityID) { DebugConsole.LogError("Error setting SoldItem.ID: ID has already been set and should not be changed."); return; } ID = id; } } partial class CargoManager { private class SoldEntity { public enum SellStatus { /// /// Entity sold in SP. Or, entity sold by client and confirmed by server in MP. /// Confirmed, /// /// Entity sold by client in MP. Client has received at least one update from server after selling, but this entity wasn't yet confirmed. /// Unconfirmed, /// /// Entity sold by client in MP. Client hasn't yet received an update from server after selling. /// Local } public Item Item { get; private set; } public ItemPrefab ItemPrefab { get; } public SellStatus Status { get; set; } public SoldEntity(Item item, SellStatus status) { Item = item; ItemPrefab = item?.Prefab; Status = status; } public SoldEntity(ItemPrefab itemPrefab, SellStatus status) { ItemPrefab = itemPrefab; Status = status; } public void SetItem(Item item) { if (Item != null) { DebugConsole.LogError($"Trying to set SoldEntity.Item, but it's already set!\n{Environment.StackTrace.CleanupStackTrace()}"); return; } Item = item; } } public const int MaxQuantity = 100; public Dictionary> ItemsInBuyCrate { get; } = new Dictionary>(); public Dictionary> ItemsInSellCrate { get; } = new Dictionary>(); public Dictionary> ItemsInSellFromSubCrate { get; } = new Dictionary>(); public Dictionary> PurchasedItems { get; } = new Dictionary>(); public Dictionary> SoldItems { get; } = new Dictionary>(); private readonly CampaignMode campaign; private Location Location => campaign?.Map?.CurrentLocation; public readonly NamedEvent OnItemsInBuyCrateChanged = new NamedEvent(); public readonly NamedEvent OnItemsInSellCrateChanged = new NamedEvent(); public readonly NamedEvent OnItemsInSellFromSubCrateChanged = new NamedEvent(); public readonly NamedEvent OnPurchasedItemsChanged = new NamedEvent(); public readonly NamedEvent OnSoldItemsChanged = new NamedEvent(); public CargoManager(CampaignMode campaign) { this.campaign = campaign; } public static bool HasUnlockedStoreItem(ItemPrefab prefab) { foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both)) { if (character.HasStoreAccessForItem(prefab)) { return true; } } return false; } private List GetItems(Identifier identifier, Dictionary> items, bool create = false) { if (items.TryGetValue(identifier, out var storeSpecificItems) && storeSpecificItems != null) { return storeSpecificItems; } else if (create) { storeSpecificItems = new List(); items.Add(identifier, storeSpecificItems); return storeSpecificItems; } else { return new List(); } } public List GetBuyCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInBuyCrate, create); public List GetBuyCrateItems(Location.StoreInfo store, bool create = false) => GetBuyCrateItems(store?.Identifier ?? Identifier.Empty, create); public PurchasedItem GetBuyCrateItem(Identifier identifier, ItemPrefab prefab) => GetBuyCrateItems(identifier)?.FirstOrDefault(i => i.ItemPrefab == prefab); public PurchasedItem GetBuyCrateItem(Location.StoreInfo store, ItemPrefab prefab) => GetBuyCrateItem(store?.Identifier ?? Identifier.Empty, prefab); public List GetSellCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInSellCrate, create); public List GetSellCrateItems(Location.StoreInfo store, bool create = false) => GetSellCrateItems(store?.Identifier ?? Identifier.Empty, create); public PurchasedItem GetSellCrateItem(Identifier identifier, ItemPrefab prefab) => GetSellCrateItems(identifier)?.FirstOrDefault(i => i.ItemPrefab == prefab); public PurchasedItem GetSellCrateItem(Location.StoreInfo store, ItemPrefab prefab) => GetSellCrateItem(store?.Identifier ?? Identifier.Empty, prefab); public List GetSubCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInSellFromSubCrate, create); public List GetSubCrateItems(Location.StoreInfo store, bool create = false) => GetSubCrateItems(store?.Identifier ?? Identifier.Empty, create); public PurchasedItem GetSubCrateItem(Identifier identifier, ItemPrefab prefab) => GetSubCrateItems(identifier)?.FirstOrDefault(i => i.ItemPrefab == prefab); public PurchasedItem GetSubCrateItem(Location.StoreInfo store, ItemPrefab prefab) => GetSubCrateItem(store?.Identifier ?? Identifier.Empty, prefab); public List GetPurchasedItems(Identifier identifier, bool create = false) => GetItems(identifier, PurchasedItems, create); public List GetPurchasedItems(Location.StoreInfo store, bool create = false) => GetPurchasedItems(store?.Identifier ?? Identifier.Empty, create); public int GetPurchasedItemCount(Location.StoreInfo store, ItemPrefab prefab) => GetPurchasedItemCount(store?.Identifier ?? Identifier.Empty, prefab); public int GetPurchasedItemCount(Identifier identifier, ItemPrefab prefab) => GetPurchasedItems(identifier)?.Where(i => i.ItemPrefab == prefab).Sum(it => it.Quantity) ?? 0; public List GetSoldItems(Identifier identifier, bool create = false) => GetItems(identifier, SoldItems, create); public List GetSoldItems(Location.StoreInfo store, bool create = false) => GetSoldItems(store?.Identifier ?? Identifier.Empty, create); public void ClearItemsInBuyCrate() { ItemsInBuyCrate.Clear(); OnItemsInBuyCrateChanged?.Invoke(this); } public void ClearItemsInSellCrate() { ItemsInSellCrate.Clear(); OnItemsInSellCrateChanged?.Invoke(this); } public void ClearItemsInSellFromSubCrate() { ItemsInSellFromSubCrate.Clear(); OnItemsInSellFromSubCrateChanged?.Invoke(this); } public void SetPurchasedItems(Dictionary> purchasedItems) { if (purchasedItems.Count == 0 && PurchasedItems.Count == 0) { return; } PurchasedItems.Clear(); foreach (var entry in purchasedItems) { PurchasedItems.Add(entry.Key, entry.Value); } OnPurchasedItemsChanged?.Invoke(this); } public void ModifyItemQuantityInBuyCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client = null) { if (GetBuyCrateItem(storeIdentifier, itemPrefab) is { } item) { item.Quantity += changeInQuantity; if (item.Quantity < 1) { GetBuyCrateItems(storeIdentifier, create: true).Remove(item); } } else if (changeInQuantity > 0) { GetBuyCrateItems(storeIdentifier, create: true).Add(new PurchasedItem(itemPrefab, changeInQuantity, client)); } OnItemsInBuyCrateChanged?.Invoke(this); } public void ModifyItemQuantityInSubSellCrate(Identifier storeIdentifier, ItemPrefab itemPrefab, int changeInQuantity, Client client = null) { if (GetSubCrateItem(storeIdentifier, itemPrefab) is { } item) { item.Quantity += changeInQuantity; if (item.Quantity < 1) { GetSubCrateItems(storeIdentifier)?.Remove(item); } } else if (changeInQuantity > 0) { GetSubCrateItems(storeIdentifier, create: true).Add(new PurchasedItem(itemPrefab, changeInQuantity, client)); } OnItemsInSellFromSubCrateChanged?.Invoke(this); } public void PurchaseItems(Identifier storeIdentifier, List itemsToPurchase, bool removeFromCrate, Client client = null) { var store = Location?.GetStore(storeIdentifier); if (store == null) { return; } var itemsPurchasedFromStore = GetPurchasedItems(storeIdentifier, create: true); // Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction var buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, itemsToPurchase.Select(i => i.ItemPrefab)); var itemsInStoreCrate = GetBuyCrateItems(storeIdentifier, create: true); foreach (PurchasedItem item in itemsToPurchase) { if (item.Quantity <= 0) { continue; } // Exchange money int itemValue = item.Quantity * buyValues[item.ItemPrefab]; if (!campaign.TryPurchase(client, itemValue)) { continue; } // Add to the purchased items var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab && pi.DeliverImmediately == item.DeliverImmediately); if (purchasedItem != null) { purchasedItem.Quantity += item.Quantity; } else { purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity, client) { DeliverImmediately = item.DeliverImmediately }; itemsPurchasedFromStore.Add(purchasedItem); } purchasedItem.Delivered = item.DeliverImmediately; if (GameMain.IsSingleplayer) { GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); } store.Balance += itemValue; } if (GameMain.NetworkMember is not { IsClient: true }) { Character targetCharacter; #if CLIENT targetCharacter = Character.Controlled; if (targetCharacter == null) { DebugConsole.ThrowError("Failed to deliver items directly to a character (not controlling a character)."); } #else targetCharacter = client?.Character; if (targetCharacter == null) { DebugConsole.ThrowError($"Failed to deliver items directly to a character ({(client == null ? "client was null" : $"client {client.Name} is not controlling a character")})."); } #endif if (targetCharacter == null) { DeliverItemsToSub(itemsToPurchase.Where(it => it.DeliverImmediately), Submarine.MainSub, this); } else { DeliverItemsToCharacter(itemsToPurchase.Where(it => it.DeliverImmediately), targetCharacter, this); } } if (removeFromCrate) { foreach (PurchasedItem item in itemsToPurchase) { // Remove from the shopping crate if (itemsInStoreCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } crateItem) { crateItem.Quantity -= item.Quantity; if (crateItem.Quantity < 1) { itemsInStoreCrate.Remove(crateItem); } } } } OnPurchasedItemsChanged?.Invoke(this); } public Dictionary GetBuyValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable items) { var buyValues = new Dictionary(); var store = Location?.GetStore(storeIdentifier); if (store == null) { return buyValues; } foreach (var item in items) { if (item == null) { continue; } if (!buyValues.ContainsKey(item)) { int buyValue = store?.GetAdjustedItemBuyPrice(item) ?? 0; buyValues.Add(item, buyValue); } } return buyValues; } public Dictionary GetSellValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable items) { var sellValues = new Dictionary(); var store = Location?.GetStore(storeIdentifier); if (store == null) { return sellValues; } foreach (var item in items) { if (item == null) { continue; } if (!sellValues.ContainsKey(item)) { int sellValue = store?.GetAdjustedItemSellPrice(item) ?? 0; sellValues.Add(item, sellValue); } } return sellValues; } public void CreatePurchasedItems() { purchasedIDCards.Clear(); var items = new List(); foreach (var storeSpecificItems in PurchasedItems) { items.AddRange(storeSpecificItems.Value.Where(it => !it.DeliverImmediately)); } DeliverItemsToSub(items, Submarine.MainSub, this); PurchasedItems.Clear(); OnPurchasedItemsChanged?.Invoke(this); } private Dictionary UndeterminedSoldEntities { get; } = new Dictionary(); public IEnumerable GetSellableItemsFromSub() { if (Submarine.MainSub == null) { return new List(); } var confirmedSoldEntities = Enumerable.Empty(); UndeterminedSoldEntities.Clear(); #if CLIENT confirmedSoldEntities = GetConfirmedSoldEntities(); foreach (var soldEntity in SoldEntities) { if (soldEntity.Item != null) { continue; } if (UndeterminedSoldEntities.TryGetValue(soldEntity.ItemPrefab, out int count)) { UndeterminedSoldEntities[soldEntity.ItemPrefab] = count + 1; } else { UndeterminedSoldEntities.Add(soldEntity.ItemPrefab, 1); } } #endif return FindAllSellableItems().Where(it => IsItemSellable(it, confirmedSoldEntities)).ToList(); } public static IReadOnlyCollection FindAllItemsOnPlayerAndSub(Character character) { List allItems = new(); if (character?.Inventory is { } inv) { allItems.AddRange(inv.FindAllItems(recursive: true)); } allItems.AddRange(FindAllSellableItems()); return allItems; } public static IEnumerable FindAllSellableItems() { if (Submarine.MainSub is null) { return Enumerable.Empty(); } return Submarine.MainSub.GetItems(true).FindAll(static item => { if (item.GetRootInventoryOwner() is Character) { return false; } if (!item.Components.All(static c => c is not Holdable { Attachable: true, Attached: true })) { return false; } if (!item.Components.All(static c => c is not Wire w || w.Connections.All(static c => c is null))) { return false; } if (!ItemAndAllContainersInteractable(item)) { return false; } if (!AllContainersAllowSellingItems(item)) { return false; } return true; }).Distinct(); static bool AllContainersAllowSellingItems(Item item) { do { item = item.Container; if (item is null) { return true; } if (item.HasTag(Tags.DontSellItems)) { return false; } if (item.Components.Any(static c => c.DisallowSellingItemsFromContainer)) { return false; } } while (item != null); return true; } static bool ItemAndAllContainersInteractable(Item item) { do { if (!item.IsPlayerTeamInteractable) { return false; } item = item.Container; } while (item != null); return true; } } private bool IsItemSellable(Item item, IEnumerable confirmedItems) { if (item.Removed) { return false; } if (!item.Prefab.CanBeSold) { return false; } if (item.SpawnedInCurrentOutpost) { return false; } if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; } if (confirmedItems != null && confirmedItems.Any(ci => ci.Item == item)) { return false; } if (UndeterminedSoldEntities.TryGetValue(item.Prefab, out int count)) { int newCount = count - 1; if (newCount > 0) { UndeterminedSoldEntities[item.Prefab] = newCount; } else { UndeterminedSoldEntities.Remove(item.Prefab); } return false; } if (item.OwnInventory?.Container is ItemContainer itemContainer) { var containedItems = item.ContainedItems; if (containedItems.None()) { return true; } // Allow selling the item if contained items are unsellable and set to be removed on deconstruct if (itemContainer.RemoveContainedItemsOnDeconstruct && containedItems.All(it => !it.Prefab.CanBeSold)) { return true; } if (confirmedItems != null) { // Otherwise there must be no contained items or the contained items must be confirmed as sold if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) { return false; } } } return true; } public static IEnumerable FindCargoRooms(IEnumerable subs) => subs.SelectMany(s => FindCargoRooms(s)); public static IEnumerable FindCargoRooms(Submarine sub) => WayPoint.WayPointList .Where(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Cargo) .Select(wp => wp.CurrentHull) .Distinct(); public static IEnumerable FilterCargoCrates(IEnumerable items, Func conditional = null) => items.Where(it => it.HasTag(Tags.Crate) && !it.NonInteractable && !it.NonPlayerTeamInteractable && !it.IsHidden && !it.Removed && (conditional == null || conditional(it))); public static IEnumerable FindReusableCargoContainers(IEnumerable subs, IEnumerable cargoRooms = null) => FilterCargoCrates(Item.ItemList, it => subs.Contains(it.Submarine) && !it.HasTag(Tags.CargoMissionItem) && (cargoRooms == null || cargoRooms.Contains(it.CurrentHull))) .Select(it => it.GetComponent()) .Where(c => c != null); public static ItemContainer GetOrCreateCargoContainerFor(ItemPrefab item, ISpatialEntity cargoRoomOrSpawnPoint, ref List availableContainers) { ItemContainer itemContainer = null; if (!string.IsNullOrEmpty(item.CargoContainerIdentifier)) { itemContainer = availableContainers.Find(ac => ac.Inventory.CanBePut(item) && (ac.Item.Prefab.Identifier == item.CargoContainerIdentifier || ac.Item.Prefab.Tags.Contains(item.CargoContainerIdentifier))); if (itemContainer == null) { ItemPrefab containerPrefab = ItemPrefab.Prefabs.Find(ep => ep.Identifier == item.CargoContainerIdentifier || (ep.Tags != null && ep.Tags.Contains(item.CargoContainerIdentifier))); if (containerPrefab == null) { DebugConsole.AddWarning($"CargoManager: could not find the item prefab for container {item.CargoContainerIdentifier}!"); return null; } Vector2 containerPosition = cargoRoomOrSpawnPoint is Hull cargoRoom ? GetCargoPos(cargoRoom, containerPrefab) : cargoRoomOrSpawnPoint.Position; Item containerItem = new Item(containerPrefab, containerPosition, cargoRoomOrSpawnPoint.Submarine); itemContainer = containerItem.GetComponent(); if (itemContainer == null) { DebugConsole.AddWarning($"CargoManager: No ItemContainer component found in {containerItem.Prefab.Identifier}!"); return null; } if (!itemContainer.CanBeContained(item)) { // Can't contain the item in the crate -> let's not create it. containerItem.Remove(); return null; } availableContainers.Add(itemContainer); #if SERVER if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item)); } #endif } } return itemContainer; } public static void DeliverItemsToSub(IEnumerable itemsToSpawn, Submarine sub, CargoManager cargoManager) { if (!itemsToSpawn.Any()) { return; } WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub); if (wp == null) { DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); return; } Hull cargoRoom = Hull.FindHull(wp.WorldPosition); if (cargoRoom == null) { DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); return; } if (sub == Submarine.MainSub && itemsToSpawn.Any(it => !it.Delivered && it.Quantity > 0)) { #if CLIENT new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, FormatCapitals.Yes), Array.Empty(), type: GUIMessageBox.Type.InGame, iconStyle: "StoreShoppingCrateIcon"); #else foreach (Client client in GameMain.Server.ConnectedClients) { ChatMessage msg = ChatMessage.Create("", TextManager.ContainsTag(cargoRoom.RoomName) ? $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}" : $"CargoSpawnNotification~[roomname]={cargoRoom.RoomName}", ChatMessageType.ServerMessageBoxInGame, null); msg.IconStyle = "StoreShoppingCrateIcon"; GameMain.Server.SendDirectChatMessage(msg, client); } #endif } var connectedSubs = sub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player); List availableContainers = FindReusableCargoContainers(connectedSubs, FindCargoRooms(connectedSubs)).ToList(); foreach (PurchasedItem pi in itemsToSpawn) { pi.Delivered = true; Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab); for (int i = 0; i < pi.Quantity; i++) { var item = new Item(pi.ItemPrefab, position, wp.Submarine); var itemContainer = GetOrCreateCargoContainerFor(pi.ItemPrefab, cargoRoom, ref availableContainers); itemContainer?.Inventory.TryPutItem(item, null); ItemSpawned(pi, item, cargoManager); #if SERVER Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item)); #endif (itemContainer?.Item ?? item).AssignCampaignInteractionType(CampaignMode.InteractionType.Cargo); } } } public static void DeliverItemsToCharacter(IEnumerable itemsToSpawn, Character character, CargoManager cargoManager) { if (!itemsToSpawn.Any()) { return; } foreach (PurchasedItem pi in itemsToSpawn) { pi.Delivered = true; for (int i = 0; i < pi.Quantity; i++) { var item = new Item(pi.ItemPrefab, character.Position, character.Submarine); #if SERVER Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item)); #endif if (item.GetComponent() is { Attached: true }) { item.Drop(dropper: null); } if (!character.Inventory.TryPutItem(item, user: null, item.AllowedSlots)) { foreach (Item containedItem in character.Inventory.AllItemsMod) { if (containedItem.OwnInventory != null && containedItem.OwnInventory.TryPutItem(item, user: null, item.AllowedSlots)) { break; } } } ItemSpawned(pi, item, cargoManager); } } } private static void ItemSpawned(PurchasedItem purchased, Item item, CargoManager cargoManager) { var idCard = item.GetComponent(); if (cargoManager != null && idCard != null && purchased.BuyerCharacterInfoIdentifier != 0) { if (purchased.DeliverImmediately) { InitPurchasedIDCard(purchased, idCard); } else { cargoManager.purchasedIDCards.Add((purchased, idCard)); } } Submarine sub = item.Submarine ?? item.RootContainer?.Submarine; if (sub != null) { foreach (WifiComponent wifiComponent in item.GetComponents()) { wifiComponent.TeamID = sub.TeamID; } } } private readonly List<(PurchasedItem purchaseInfo, IdCard idCard)> purchasedIDCards = new List<(PurchasedItem purchaseInfo, IdCard idCard)>(); public void InitPurchasedIDCards() { foreach ((PurchasedItem purchased, IdCard idCard) in purchasedIDCards) { InitPurchasedIDCard(purchased, idCard); } } private static void InitPurchasedIDCard(PurchasedItem purchased, IdCard idCard) { if (idCard != null && purchased.BuyerCharacterInfoIdentifier != 0) { var owner = Character.CharacterList.Find(c => c.Info?.GetIdentifier() == purchased.BuyerCharacterInfoIdentifier); if (owner?.Info != null) { var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(new List() { owner.Info }, Submarine.MainSub); idCard.Initialize(mainSubSpawnPoints.FirstOrDefault(), owner); } } } public static Vector2 GetCargoPos(Hull hull, ItemPrefab itemPrefab) { float floorPos = hull.Rect.Y - hull.Rect.Height; Vector2 position = new Vector2( hull.Rect.Width > 40 ? Rand.Range(hull.Rect.X + 20f, hull.Rect.Right - 20f) : hull.Rect.Center.X, floorPos); //check where the actual floor structure is in case the bottom of the hull extends below it if (Submarine.PickBody( ConvertUnits.ToSimUnits(new Vector2(position.X, hull.Rect.Y - hull.Rect.Height / 2)), ConvertUnits.ToSimUnits(position), collisionCategory: Physics.CollisionWall) != null) { float floorStructurePos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition.Y); if (floorStructurePos > floorPos) { floorPos = floorStructurePos; } } position.Y = floorPos + itemPrefab.Size.Y / 2; return position; } public void SavePurchasedItems(XElement parentElement) { var itemsElement = new XElement("cargo"); foreach (var storeSpecificItems in PurchasedItems) { foreach (var item in storeSpecificItems.Value) { if (item?.ItemPrefab == null) { continue; } itemsElement.Add(new XElement("item", new XAttribute("id", item.ItemPrefab.Identifier), new XAttribute("qty", item.Quantity), new XAttribute("storeid", storeSpecificItems.Key), new XAttribute("deliverimmediately", item.DeliverImmediately), new XAttribute("buyer", item.BuyerCharacterInfoIdentifier))); } } parentElement.Add(itemsElement); } public void LoadPurchasedItems(XElement element) { var purchasedItems = new Dictionary>(); if (element != null) { foreach (XElement itemElement in element.GetChildElements("item")) { string prefabId = itemElement.GetAttributeString("id", null); if (string.IsNullOrWhiteSpace(prefabId)) { continue; } if (!ItemPrefab.Prefabs.TryGet(prefabId.ToIdentifier(), out var prefab)) { continue; } int qty = itemElement.GetAttributeInt("qty", 0); Identifier storeId = itemElement.GetAttributeIdentifier("storeid", "merchant"); bool deliverImmediately = itemElement.GetAttributeBool("deliverimmediately", false); int buyerId = itemElement.GetAttributeInt("buyer", 0); if (!purchasedItems.TryGetValue(storeId, out var storeItems)) { storeItems = new List(); purchasedItems.Add(storeId, storeItems); } storeItems.Add(new PurchasedItem(prefab, qty, buyerId) { DeliverImmediately = deliverImmediately, //must have already been delivered if we had opted for immediate delivery Delivered = deliverImmediately }); } } SetPurchasedItems(purchasedItems); } } }