Release 1.10.6.0 - Autumn Update 2025 Hotfix 1

This commit is contained in:
Regalis11
2025-09-25 11:11:35 +03:00
parent bad999d5fc
commit b2d91cde7c
25 changed files with 435 additions and 226 deletions
@@ -933,207 +933,235 @@ namespace Barotrauma
CheckTooManyMissions(Map.CurrentLocation, sender);
}
var prevBuyCrateItems = new Dictionary<Identifier, List<PurchasedItem>>();
foreach (var kvp in CargoManager.ItemsInBuyCrate)
if (HasCampaignInteractionAvailable(sender, InteractionType.Store))
{
prevBuyCrateItems.Add(kvp.Key, new List<PurchasedItem>(kvp.Value));
}
foreach (var store in prevBuyCrateItems)
{
foreach (var item in store.Value.ToList())
var prevBuyCrateItems = new Dictionary<Identifier, List<PurchasedItem>>();
foreach (var kvp in CargoManager.ItemsInBuyCrate)
{
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender);
prevBuyCrateItems.Add(kvp.Key, new List<PurchasedItem>(kvp.Value));
}
}
foreach (var store in buyCrateItems)
{
foreach (var item in store.Value.ToList())
foreach (var store in prevBuyCrateItems)
{
if (map?.CurrentLocation?.Stores == null || !map.CurrentLocation.Stores.ContainsKey(store.Key)) { continue; }
int availableQuantity = map.CurrentLocation.Stores[store.Key].Stock.Find(s => s.ItemPrefab == item.ItemPrefab)?.Quantity ?? 0;
int alreadyPurchasedQuantity =
CargoManager.GetBuyCrateItem(store.Key, item.ItemPrefab)?.Quantity ?? 0 +
CargoManager.GetPurchasedItemCount(store.Key, item.ItemPrefab);
item.Quantity = MathHelper.Clamp(item.Quantity, 0, availableQuantity - alreadyPurchasedQuantity);
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender);
}
}
var prevPurchasedItems = new Dictionary<Identifier, List<PurchasedItem>>();
foreach (var kvp in CargoManager.PurchasedItems)
{
prevPurchasedItems.Add(kvp.Key, new List<PurchasedItem>(kvp.Value));
}
foreach (var storeId in purchasedItems.Keys)
{
DebugConsole.Log($"Purchased items ({storeId}):\n");
if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchased))
{
var delivered = alreadyPurchased.Where(it => it.Delivered);
var notDelivered = alreadyPurchased.Where(it => !it.Delivered);
if (delivered.Any())
foreach (var item in store.Value.ToList())
{
DebugConsole.Log($" Already delivered:\n" + string.Concat(delivered.Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})")));
}
if (notDelivered.Any())
{
DebugConsole.Log($" Already purchased:\n" + string.Concat(notDelivered.Where(it => !it.Delivered).Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})")));
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender);
}
}
DebugConsole.Log($" New purchases:");
foreach (var purchasedItem in purchasedItems[storeId])
foreach (var store in buyCrateItems)
{
if (purchasedItem.Delivered) { continue; }
int quantity = purchasedItem.Quantity;
if (alreadyPurchased != null)
foreach (var item in store.Value.ToList())
{
quantity -= alreadyPurchased.Where(it => it.DeliverImmediately == purchasedItem.DeliverImmediately && it.ItemPrefab == purchasedItem.ItemPrefab).Sum(it => it.Quantity);
}
if (quantity > 0)
{
DebugConsole.Log($" - {purchasedItem.ItemPrefab.Name} (x{quantity})");
if (map?.CurrentLocation?.Stores == null || !map.CurrentLocation.Stores.ContainsKey(store.Key)) { continue; }
int availableQuantity = map.CurrentLocation.Stores[store.Key].Stock.Find(s => s.ItemPrefab == item.ItemPrefab)?.Quantity ?? 0;
int alreadyPurchasedQuantity =
CargoManager.GetBuyCrateItem(store.Key, item.ItemPrefab)?.Quantity ?? 0 +
CargoManager.GetPurchasedItemCount(store.Key, item.ItemPrefab);
item.Quantity = MathHelper.Clamp(item.Quantity, 0, availableQuantity - alreadyPurchasedQuantity);
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender);
}
}
}
foreach (var storeId in soldItems.Keys)
{
DebugConsole.Log($"Sold items:\n" + string.Concat(soldItems[storeId].Select(it => $" - {it.ItemPrefab.Name}")));
}
foreach (var kvp in purchasedItems)
{
var storeId = kvp.Key;
var purchasedItemList = kvp.Value;
foreach (var purchasedItem in purchasedItemList)
var prevPurchasedItems = new Dictionary<Identifier, List<PurchasedItem>>();
foreach (var kvp in CargoManager.PurchasedItems)
{
int desiredQuantity = purchasedItem.Quantity;
if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchasedList) &&
alreadyPurchasedList.FirstOrDefault(p => p.ItemPrefab == purchasedItem.ItemPrefab && p.DeliverImmediately == purchasedItem.DeliverImmediately) is { } alreadyPurchased)
prevPurchasedItems.Add(kvp.Key, new List<PurchasedItem>(kvp.Value));
}
foreach (var storeId in purchasedItems.Keys)
{
DebugConsole.Log($"Purchased items ({storeId}):\n");
if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchased))
{
desiredQuantity -= alreadyPurchased.Quantity;
var delivered = alreadyPurchased.Where(it => it.Delivered);
var notDelivered = alreadyPurchased.Where(it => !it.Delivered);
if (delivered.Any())
{
DebugConsole.Log($" Already delivered:\n" + string.Concat(delivered.Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})")));
}
if (notDelivered.Any())
{
DebugConsole.Log($" Already purchased:\n" + string.Concat(notDelivered.Where(it => !it.Delivered).Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})")));
}
}
int availableQuantity = map.CurrentLocation.Stores[storeId].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0;
purchasedItem.Quantity = Math.Min(desiredQuantity, availableQuantity);
}
CargoManager.PurchaseItems(storeId, purchasedItemList, removeFromCrate: false, client: sender);
}
foreach (var (storeIdentifier, items) in CargoManager.PurchasedItems)
{
if (!prevPurchasedItems.ContainsKey(storeIdentifier))
{
CargoManager.LogNewItemPurchases(storeIdentifier, items, sender);
continue;
}
List<PurchasedItem> newItems = new List<PurchasedItem>();
List<PurchasedItem> prevItems = prevPurchasedItems[storeIdentifier];
foreach (PurchasedItem item in items)
{
PurchasedItem matching = prevItems.FirstOrDefault(ppi => ppi.ItemPrefab == item.ItemPrefab);
if (matching is null)
DebugConsole.Log($" New purchases:");
foreach (var purchasedItem in purchasedItems[storeId])
{
newItems.Add(item);
if (purchasedItem.Delivered) { continue; }
int quantity = purchasedItem.Quantity;
if (alreadyPurchased != null)
{
quantity -= alreadyPurchased.Where(it => it.DeliverImmediately == purchasedItem.DeliverImmediately && it.ItemPrefab == purchasedItem.ItemPrefab).Sum(it => it.Quantity);
}
if (quantity > 0)
{
DebugConsole.Log($" - {purchasedItem.ItemPrefab.Name} (x{quantity})");
}
}
}
foreach (var storeId in soldItems.Keys)
{
DebugConsole.Log($"Sold items:\n" + string.Concat(soldItems[storeId].Select(it => $" - {it.ItemPrefab.Name}")));
}
foreach (var kvp in purchasedItems)
{
var storeId = kvp.Key;
var purchasedItemList = kvp.Value;
foreach (var purchasedItem in purchasedItemList)
{
int desiredQuantity = purchasedItem.Quantity;
if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchasedList) &&
alreadyPurchasedList.FirstOrDefault(p => p.ItemPrefab == purchasedItem.ItemPrefab && p.DeliverImmediately == purchasedItem.DeliverImmediately) is { } alreadyPurchased)
{
desiredQuantity -= alreadyPurchased.Quantity;
}
int availableQuantity = map.CurrentLocation.Stores[storeId].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0;
purchasedItem.Quantity = Math.Min(desiredQuantity, availableQuantity);
}
CargoManager.PurchaseItems(storeId, purchasedItemList, removeFromCrate: false, client: sender);
}
foreach (var (storeIdentifier, items) in CargoManager.PurchasedItems)
{
if (!prevPurchasedItems.ContainsKey(storeIdentifier))
{
CargoManager.LogNewItemPurchases(storeIdentifier, items, sender);
continue;
}
if (matching.Quantity < item.Quantity)
List<PurchasedItem> newItems = new List<PurchasedItem>();
List<PurchasedItem> prevItems = prevPurchasedItems[storeIdentifier];
foreach (PurchasedItem item in items)
{
newItems.Add(new PurchasedItem(item.ItemPrefab, item.Quantity - matching.Quantity, sender));
PurchasedItem matching = prevItems.FirstOrDefault(ppi => ppi.ItemPrefab == item.ItemPrefab);
if (matching is null)
{
newItems.Add(item);
continue;
}
if (matching.Quantity < item.Quantity)
{
newItems.Add(new PurchasedItem(item.ItemPrefab, item.Quantity - matching.Quantity, sender));
}
}
if (newItems.Any())
{
CargoManager.LogNewItemPurchases(storeIdentifier, newItems, sender);
}
}
if (newItems.Any())
bool allowedToSellSubItems = AllowedToManageCampaign(sender, ClientPermissions.SellSubItems);
if (allowedToSellSubItems)
{
CargoManager.LogNewItemPurchases(storeIdentifier, newItems, sender);
}
}
bool allowedToSellSubItems = AllowedToManageCampaign(sender, ClientPermissions.SellSubItems);
if (allowedToSellSubItems)
{
var prevSubSellCrateItems = new Dictionary<Identifier, List<PurchasedItem>>(CargoManager.ItemsInSellFromSubCrate);
foreach (var store in prevSubSellCrateItems)
{
foreach (var item in store.Value.ToList())
var prevSubSellCrateItems = new Dictionary<Identifier, List<PurchasedItem>>(CargoManager.ItemsInSellFromSubCrate);
foreach (var store in prevSubSellCrateItems)
{
CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, -item.Quantity, sender);
foreach (var item in store.Value.ToList())
{
CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, -item.Quantity, sender);
}
}
foreach (var store in subSellCrateItems)
{
foreach (var item in store.Value.ToList())
{
CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, item.Quantity, sender);
}
}
}
foreach (var store in subSellCrateItems)
bool allowedToSellInventoryItems = AllowedToManageCampaign(sender, ClientPermissions.SellInventoryItems);
if (allowedToSellInventoryItems && allowedToSellSubItems)
{
foreach (var item in store.Value.ToList())
// for some reason CargoManager.SoldItem is never cleared by the server, I've added a check to SellItems that ignores all
// sold items that are removed so they should be discarded on the next message
var prevSoldItems = new Dictionary<Identifier, List<SoldItem>>(CargoManager.SoldItems);
foreach (var store in prevSoldItems)
{
CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, item.Quantity, sender);
CargoManager.BuyBackSoldItems(store.Key, store.Value.ToList(), sender);
}
foreach (var store in soldItems)
{
CargoManager.SellItems(store.Key, store.Value.ToList(), sender);
}
}
else if (allowedToSellInventoryItems || allowedToSellSubItems)
{
var prevSoldItems = new Dictionary<Identifier, List<SoldItem>>(CargoManager.SoldItems);
foreach (var store in prevSoldItems)
{
store.Value.RemoveAll(predicate);
CargoManager.BuyBackSoldItems(store.Key, store.Value.ToList(), sender);
}
foreach (var store in soldItems)
{
store.Value.RemoveAll(predicate);
}
foreach (var store in soldItems)
{
CargoManager.SellItems(store.Key, store.Value.ToList(), sender);
}
bool predicate(SoldItem i) => allowedToSellInventoryItems != (i.Origin == SoldItem.SellOrigin.Character);
}
}
else
{
GameServer.Log($"{sender.Name} attempted to buy or sell items without having access to a store NPC.", ServerLog.MessageType.Error);
}
if ((purchasedUpgrades.Any() || purchasedItemSwaps.Any()) &&
HasCampaignInteractionAvailable(sender, InteractionType.Upgrade))
{
var characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both);
foreach (var (prefab, category, _) in purchasedUpgrades)
{
UpgradeManager.TryPurchaseUpgrade(prefab, category, client: sender);
// unstable logging
int price = prefab.Price.GetBuyPrice(prefab, UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation, characterList);
int level = UpgradeManager.GetUpgradeLevel(prefab, category);
GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage);
}
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, client: sender);
}
else
{
UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, client: sender);
}
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
UpgradeManager.CancelItemSwap(item);
item.PendingItemSwap = null;
}
}
}
else
{
GameServer.Log($"{sender.Name} attempted to buy upgrades without having access to an NPC offering upgrades.", ServerLog.MessageType.Error);
}
}
bool allowedToSellInventoryItems = AllowedToManageCampaign(sender, ClientPermissions.SellInventoryItems);
if (allowedToSellInventoryItems && allowedToSellSubItems)
private bool HasCampaignInteractionAvailable(Client sender, InteractionType interactionType)
{
if (sender.Character == null || sender.Character.IsIncapacitated) { return false; }
if (GameMain.Server?.ServerSettings is { AllowRemoteCampaignInteractions: true }) { return true; }
foreach (var otherCharacter in Character.CharacterList)
{
// for some reason CargoManager.SoldItem is never cleared by the server, I've added a check to SellItems that ignores all
// sold items that are removed so they should be discarded on the next message
var prevSoldItems = new Dictionary<Identifier, List<SoldItem>>(CargoManager.SoldItems);
foreach (var store in prevSoldItems)
if (otherCharacter.CampaignInteractionType != interactionType) { continue; }
//larger-than default maximum distance to give legit clients some leeway if their position is a bit off
if (sender.Character.CanInteractWith(otherCharacter, maxDist: 250.0f))
{
CargoManager.BuyBackSoldItems(store.Key, store.Value.ToList(), sender);
}
foreach (var store in soldItems)
{
CargoManager.SellItems(store.Key, store.Value.ToList(), sender);
}
}
else if (allowedToSellInventoryItems || allowedToSellSubItems)
{
var prevSoldItems = new Dictionary<Identifier, List<SoldItem>>(CargoManager.SoldItems);
foreach (var store in prevSoldItems)
{
store.Value.RemoveAll(predicate);
CargoManager.BuyBackSoldItems(store.Key, store.Value.ToList(), sender);
}
foreach (var store in soldItems)
{
store.Value.RemoveAll(predicate);
}
foreach (var store in soldItems)
{
CargoManager.SellItems(store.Key, store.Value.ToList(), sender);
}
bool predicate(SoldItem i) => allowedToSellInventoryItems != (i.Origin == SoldItem.SellOrigin.Character);
}
var characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both);
foreach (var (prefab, category, _) in purchasedUpgrades)
{
UpgradeManager.TryPurchaseUpgrade(prefab, category, client: sender);
// unstable logging
int price = prefab.Price.GetBuyPrice(prefab, UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation, characterList);
int level = UpgradeManager.GetUpgradeLevel(prefab, category);
GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage);
}
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, client: sender);
}
else
{
UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, client: sender);
}
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
UpgradeManager.CancelItemSwap(item);
item.PendingItemSwap = null;
return true;
}
}
return false;
}
public void ServerReadMoney(IReadMessage msg, Client sender)
@@ -1265,7 +1293,7 @@ namespace Barotrauma
CharacterInfo firedCharacter = null;
(ushort id, string newName) appliedRename = (Entity.NullEntityID, string.Empty);
if (location != null)
if (location != null && HasCampaignInteractionAvailable(sender, InteractionType.Crew))
{
if (fireCharacter && AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
{
@@ -1369,6 +1397,10 @@ namespace Barotrauma
});
}
}
else
{
GameServer.Log($"{sender.Name} attempted to manage hires without having access to an appropriate NPC.", ServerLog.MessageType.Error);
}
// bounce back
if (renameCharacter && existingCrewMember)