Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs
2025-09-17 13:44:21 +03:00

841 lines
37 KiB
C#

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;
/// <summary>
/// Should the items be given to the buyer immediately, as opposed to spawning them in the sub the next round?
/// </summary>
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
{
/// <summary>
/// Entity sold in SP. Or, entity sold by client and confirmed by server in MP.
/// </summary>
Confirmed,
/// <summary>
/// Entity sold by client in MP. Client has received at least one update from server after selling, but this entity wasn't yet confirmed.
/// </summary>
Unconfirmed,
/// <summary>
/// Entity sold by client in MP. Client hasn't yet received an update from server after selling.
/// </summary>
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<Identifier, List<PurchasedItem>> ItemsInBuyCrate { get; } = new Dictionary<Identifier, List<PurchasedItem>>();
public Dictionary<Identifier, List<PurchasedItem>> ItemsInSellCrate { get; } = new Dictionary<Identifier, List<PurchasedItem>>();
public Dictionary<Identifier, List<PurchasedItem>> ItemsInSellFromSubCrate { get; } = new Dictionary<Identifier, List<PurchasedItem>>();
public Dictionary<Identifier, List<PurchasedItem>> PurchasedItems { get; } = new Dictionary<Identifier, List<PurchasedItem>>();
public Dictionary<Identifier, List<SoldItem>> SoldItems { get; } = new Dictionary<Identifier, List<SoldItem>>();
private readonly CampaignMode campaign;
private Location Location => campaign?.Map?.CurrentLocation;
public readonly NamedEvent<CargoManager> OnItemsInBuyCrateChanged = new NamedEvent<CargoManager>();
public readonly NamedEvent<CargoManager> OnItemsInSellCrateChanged = new NamedEvent<CargoManager>();
public readonly NamedEvent<CargoManager> OnItemsInSellFromSubCrateChanged = new NamedEvent<CargoManager>();
public readonly NamedEvent<CargoManager> OnPurchasedItemsChanged = new NamedEvent<CargoManager>();
public readonly NamedEvent<CargoManager> OnSoldItemsChanged = new NamedEvent<CargoManager>();
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<T> GetItems<T>(Identifier identifier, Dictionary<Identifier, List<T>> items, bool create = false)
{
if (items.TryGetValue(identifier, out var storeSpecificItems) && storeSpecificItems != null)
{
return storeSpecificItems;
}
else if (create)
{
storeSpecificItems = new List<T>();
items.Add(identifier, storeSpecificItems);
return storeSpecificItems;
}
else
{
return new List<T>();
}
}
public List<PurchasedItem> GetBuyCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInBuyCrate, create);
public List<PurchasedItem> 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<PurchasedItem> GetSellCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInSellCrate, create);
public List<PurchasedItem> 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<PurchasedItem> GetSubCrateItems(Identifier identifier, bool create = false) => GetItems(identifier, ItemsInSellFromSubCrate, create);
public List<PurchasedItem> 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<PurchasedItem> GetPurchasedItems(Identifier identifier, bool create = false) => GetItems(identifier, PurchasedItems, create);
public List<PurchasedItem> 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<SoldItem> GetSoldItems(Identifier identifier, bool create = false) => GetItems(identifier, SoldItems, create);
public List<SoldItem> 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<Identifier, List<PurchasedItem>> 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<PurchasedItem> 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);
//handle checking which items can be purchased and deducting money first
foreach (PurchasedItem item in itemsToPurchase.ToList())
{
if (item.Quantity <= 0) { continue; }
// Exchange money
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
if (!campaign.TryPurchase(client, itemValue))
{
itemsToPurchase.Remove(item);
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;
}
//actually spawn the items at this point
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<ItemPrefab, int> GetBuyValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable<ItemPrefab> items)
{
var buyValues = new Dictionary<ItemPrefab, int>();
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<ItemPrefab, int> GetSellValuesAtCurrentLocation(Identifier storeIdentifier, IEnumerable<ItemPrefab> items)
{
var sellValues = new Dictionary<ItemPrefab, int>();
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<PurchasedItem>();
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<ItemPrefab, int> UndeterminedSoldEntities { get; } = new Dictionary<ItemPrefab, int>();
public IEnumerable<Item> GetSellableItemsFromSub()
{
if (Submarine.MainSub == null) { return new List<Item>(); }
var confirmedSoldEntities = Enumerable.Empty<SoldEntity>();
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<Item> FindAllItemsOnPlayerAndSub(Character character)
{
List<Item> allItems = new();
if (character?.Inventory is { } inv)
{
allItems.AddRange(inv.FindAllItems(recursive: true));
}
allItems.AddRange(FindAllSellableItems());
return allItems;
}
public static IEnumerable<Item> FindAllSellableItems()
{
if (Submarine.MainSub is null) { return Enumerable.Empty<Item>(); }
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<SoldEntity> 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;
}
//can't sell items in hidden inventories
Item rootContainer = item.Container;
while (rootContainer != null)
{
if (rootContainer.OwnInventory?.Container is { } containerComponent)
{
if (!containerComponent.DrawInventory) { return false; }
if (!containerComponent.IsAccessible()) { return false; }
}
rootContainer = rootContainer.Container;
}
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<Hull> FindCargoRooms(IEnumerable<Submarine> subs) => subs.SelectMany(s => FindCargoRooms(s));
public static IEnumerable<Hull> FindCargoRooms(Submarine sub) => WayPoint.WayPointList
.Where(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Cargo)
.Select(wp => wp.CurrentHull)
.Distinct();
public static IEnumerable<Item> FilterCargoCrates(IEnumerable<Item> items, Func<Item, bool> conditional = null)
=> items.Where(it => it.HasTag(Tags.Crate) && !it.NonInteractable && !it.NonPlayerTeamInteractable && !it.IsHidden && !it.Removed && (conditional == null || conditional(it)));
public static IEnumerable<ItemContainer> FindReusableCargoContainers(IEnumerable<Submarine> subs, IEnumerable<Hull> cargoRooms = null) =>
FilterCargoCrates(Item.ItemList, it => subs.Contains(it.Submarine) && !it.HasTag(Tags.CargoMissionItem) && (cargoRooms == null || cargoRooms.Contains(it.CurrentHull)))
.Select(it => it.GetComponent<ItemContainer>())
.Where(c => c != null);
public static ItemContainer GetOrCreateCargoContainerFor(ItemPrefab item, ISpatialEntity cargoRoomOrSpawnPoint, ref List<ItemContainer> availableContainers)
{
ItemContainer itemContainer = null;
if (!string.IsNullOrEmpty(item.CargoContainerIdentifier))
{
itemContainer = availableContainers.Find(ac =>
ac.Inventory.CanProbablyBePut(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<ItemContainer>();
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<PurchasedItem> itemsToSpawn, Submarine sub, CargoManager cargoManager, bool showNotification = true)
{
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) && showNotification)
{
#if CLIENT
new GUIMessageBox("",
TextManager.GetWithVariable("CargoSpawnNotification",
"[roomname]",
cargoRoom.DisplayName,
FormatCapitals.Yes),
Array.Empty<LocalizedString>(),
type: GUIMessageBox.Type.InGame,
iconStyle: "StoreShoppingCrateIcon");
#else
foreach (Client client in GameMain.Server.ConnectedClients)
{
if (client.TeamID == CharacterTeamType.None || client.TeamID == sub.TeamID)
{
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<ItemContainer> 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, user: 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<PurchasedItem> 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<Holdable>() is { Attached: true })
{
item.Drop(dropper: null);
}
if (!character.Inventory.TryPutItem(item, user: null, item.AllowedSlots))
{
foreach (Item containedItem in character.Inventory.AllItemsMod)
{
//only put into containers that draw the inventory (not ones with a hidden inventory like circuit boxes!)
if (containedItem.OwnInventory?.Container is { DrawInventory: true } &&
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<IdCard>();
if (cargoManager != null && idCard != null && purchased.BuyerCharacterInfoIdentifier != 0)
{
if (purchased.DeliverImmediately)
{
InitPurchasedIDCard(purchased, idCard);
}
else
{
cargoManager.purchasedIDCards.Add((purchased, idCard));
}
}
ItemSpawned(item);
}
public static void ItemSpawned(Item item)
{
CharacterTeamType teamID = CharacterTeamType.Team1;
if (item.ParentInventory?.Owner is Character character)
{
teamID = character.TeamID;
}
else
{
Submarine sub = item.Submarine ?? item.RootContainer?.Submarine;
if (sub != null)
{
teamID = sub.TeamID;
}
}
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
{
wifiComponent.TeamID = 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<CharacterInfo>() { 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<Identifier, List<PurchasedItem>>();
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<PurchasedItem>();
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);
}
}
}