Release 1.10.6.0 - Autumn Update 2025 Hotfix 1
This commit is contained in:
+198
-166
@@ -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)
|
||||
|
||||
@@ -64,15 +64,15 @@ namespace Barotrauma
|
||||
decalUpdateTimer = 0;
|
||||
decalUpdatePending = false;
|
||||
}
|
||||
if (pendingSectionUpdates.Count > 0 && backgroundSectionUpdateTimer > NetConfig.HullUpdateInterval)
|
||||
if (pendingSectorUpdates.Count > 0 && backgroundSectionUpdateTimer > NetConfig.HullUpdateInterval)
|
||||
{
|
||||
foreach (int pendingSectionUpdate in pendingSectionUpdates)
|
||||
foreach (int pendingSectorUpdate in pendingSectorUpdates)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectionUpdate));
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectorUpdate));
|
||||
}
|
||||
|
||||
backgroundSectionUpdateTimer = 0;
|
||||
pendingSectionUpdates.Clear();
|
||||
pendingSectorUpdates.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,8 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case EventType.BackgroundSections:
|
||||
bool addPendingSectorUpdate = false;
|
||||
|
||||
SharedBackgroundSectionRead(
|
||||
msg,
|
||||
bsnu =>
|
||||
@@ -185,17 +187,67 @@ namespace Barotrauma
|
||||
Color color = bsnu.Color;
|
||||
float colorStrength = bsnu.ColorStrength;
|
||||
|
||||
#warning TODO: verify the client is close enough to this hull to paint it, that the sprayer is functional and that the color matches
|
||||
if (!(c.Character is { AllowInput: true })) { return; }
|
||||
if (c.Character.HeldItems.All(it => it.GetComponent<Sprayer>() == null)) { return; }
|
||||
if (c.Character is not { AllowInput: true }) { return; }
|
||||
|
||||
//ideally the server would just run the painting logic the same way as clients instead of relying on the clients setting colors on the hull,
|
||||
//but that's non-trivial because the server doesn't know the client's exact cursor position, just the direction they're aiming at
|
||||
//and we want the painting to be precise, lag shouldn't cause the paint to end up in the wrong place, etc.
|
||||
//but now that clients set the colors themselves, we need to do some sanity checks:
|
||||
|
||||
var sprayer = c.Character.HeldItems
|
||||
.Select(it => it.GetComponent<Sprayer>())
|
||||
.FirstOrDefault(component => component != null);
|
||||
if (sprayer == null) { return; }
|
||||
|
||||
Item liquidItem = sprayer.LiquidContainer?.Inventory?.FirstOrDefault();
|
||||
if (liquidItem == null) { return; }
|
||||
|
||||
if (!sprayer.LiquidColors.TryGetValue(liquidItem.Prefab.Identifier, out Color paintColor)) { return; }
|
||||
|
||||
bool isCleaning = paintColor.A == 0;
|
||||
|
||||
var backgroundSectionPos = GetBackgroundSectionWorldPos(BackgroundSections[i]);
|
||||
//rough distance check to disallow painting from very far away
|
||||
//(slightly longer range than the normal range of the sprayer to give the client some leeway)
|
||||
if (Vector2.Distance(backgroundSectionPos, sprayer.Item.WorldPosition) > sprayer.Range * 1.1f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//if we get to this point (client can paint this section), let's sync the changes
|
||||
//the color change below may fail if the color is out of sync client-side, even if the client isn't doing anything malicious,
|
||||
//in which case we want to get the client back in sync
|
||||
addPendingSectorUpdate = true;
|
||||
|
||||
if (isCleaning)
|
||||
{
|
||||
//if we're cleaning, strength of the color must go down
|
||||
if (colorStrength >= BackgroundSections[i].ColorStrength) { return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 colorChange = color.ToVector3() - BackgroundSections[i].Color.ToVector3();
|
||||
Vector3 expectedColorChange = paintColor.ToVector3() - BackgroundSections[i].Color.ToVector3();
|
||||
|
||||
//color should be going towards the color of the paint, if it's not, don't allow changing it
|
||||
if (Math.Sign(colorChange.X) != Math.Sign(expectedColorChange.X) ||
|
||||
Math.Sign(colorChange.Y) != Math.Sign(expectedColorChange.Y) ||
|
||||
Math.Sign(colorChange.Z) != Math.Sign(expectedColorChange.Z))
|
||||
{
|
||||
return;
|
||||
}
|
||||
BackgroundSections[i].SetColor(color);
|
||||
}
|
||||
BackgroundSections[i].SetColorStrength(colorStrength);
|
||||
BackgroundSections[i].SetColor(color);
|
||||
},
|
||||
out int sectorToUpdate);
|
||||
RefreshAveragePaintedColor();
|
||||
//add to pending updates to notify other clients as well
|
||||
pendingSectionUpdates.Add(sectorToUpdate);
|
||||
|
||||
if (addPendingSectorUpdate)
|
||||
{
|
||||
RefreshAveragePaintedColor();
|
||||
//add to pending updates to notify other clients as well
|
||||
pendingSectorUpdates.Add(sectorToUpdate);
|
||||
}
|
||||
break;
|
||||
case EventType.Decal:
|
||||
byte decalIndex = msg.ReadByte();
|
||||
@@ -209,7 +261,7 @@ namespace Barotrauma
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Malformed incoming hull event: {eventType} is not a supported event type");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4384,9 +4384,22 @@ namespace Barotrauma.Networking
|
||||
moustacheIndex: netInfo.MoustacheIndex,
|
||||
faceAttachmentIndex: netInfo.FaceAttachmentIndex);
|
||||
|
||||
sender.CharacterInfo.Head.SkinColor = netInfo.SkinColor;
|
||||
sender.CharacterInfo.Head.HairColor = netInfo.HairColor;
|
||||
sender.CharacterInfo.Head.FacialHairColor = netInfo.FacialHairColor;
|
||||
sender.CharacterInfo.Head.SkinColor = validateColor(netInfo.SkinColor, "skin color", sender.CharacterInfo.SkinColors.Select(kvp => kvp.Color));
|
||||
sender.CharacterInfo.Head.HairColor = validateColor(netInfo.HairColor, "hair color", sender.CharacterInfo.HairColors.Select(kvp => kvp.Color));
|
||||
sender.CharacterInfo.Head.FacialHairColor = validateColor(netInfo.FacialHairColor, "facial hair color", sender.CharacterInfo.FacialHairColors.Select(kvp => kvp.Color));
|
||||
|
||||
Color validateColor(Color newColor, string colorName, IEnumerable<Color> supportedColors)
|
||||
{
|
||||
if (!supportedColors.Contains(newColor))
|
||||
{
|
||||
DebugConsole.AddWarning($"Client {sender.Name} attempted to set their {colorName} to an unsupported value ({newColor}).");
|
||||
return supportedColors.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
return newColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (netInfo.JobVariants.Length > 0)
|
||||
{
|
||||
|
||||
+21
-2
@@ -269,8 +269,27 @@ namespace Barotrauma.Networking
|
||||
return;
|
||||
}
|
||||
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
callbacks.OnMessageReceived.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn));
|
||||
try
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
callbacks.OnMessageReceived.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn));
|
||||
}
|
||||
|
||||
catch (NetStructReadException)
|
||||
{
|
||||
//kick the client if we fail to parse their message
|
||||
if (conn != OwnerConnection)
|
||||
{
|
||||
if (connectedClients.Find(c => c.Connection == conn) is { } connectedClient)
|
||||
{
|
||||
Disconnect(connectedClient.Connection, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LidgrenConnection? FindConnection(NetConnection ligdrenConn)
|
||||
|
||||
+26
-5
@@ -73,11 +73,31 @@ namespace Barotrauma.Networking
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var incBuf in ChildServerRelay.Read())
|
||||
foreach (byte[] incBuf in ChildServerRelay.Read())
|
||||
{
|
||||
IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, OwnerConnection);
|
||||
|
||||
HandleDataMessage(inc);
|
||||
P2PEndpoint? senderEndpoint = null;
|
||||
try
|
||||
{
|
||||
IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, OwnerConnection);
|
||||
HandleDataMessage(inc, out senderEndpoint);
|
||||
}
|
||||
catch (NetStructReadException)
|
||||
{
|
||||
//kick the client if we fail to parse their message
|
||||
if (senderEndpoint != null && senderEndpoint != ownerEndpoint)
|
||||
{
|
||||
if (pendingClients.Find(c => c.Connection.Endpoint == senderEndpoint) is { } pendingClient)
|
||||
{
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData));
|
||||
}
|
||||
if (connectedClients.Find(c => c.Connection.Endpoint == senderEndpoint) is { } connectedClient)
|
||||
{
|
||||
Disconnect(connectedClient.Connection, PeerDisconnectPacket.WithReason(DisconnectReason.MalformedData));
|
||||
}
|
||||
break;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +120,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDataMessage(IReadMessage inc)
|
||||
private void HandleDataMessage(IReadMessage inc, out P2PEndpoint? senderEndPoint)
|
||||
{
|
||||
senderEndPoint = null;
|
||||
if (!started) { return; }
|
||||
|
||||
var senderInfo = INetSerializableStruct.Read<P2POwnerToServerHeader>(inc);
|
||||
|
||||
Reference in New Issue
Block a user