Unstable 1.2.1.0

This commit is contained in:
Markus Isberg
2023-11-10 17:45:19 +02:00
parent 2ea58c58a7
commit 8a2e2ea0ae
268 changed files with 4076 additions and 1843 deletions
@@ -34,8 +34,8 @@ namespace Barotrauma
{
if (!CheatsEnabled && IsCheat)
{
NewMessage("Client \"" + client.Name + "\" attempted to use the command \"" + names[0] + "\". Cheats must be enabled using \"enablecheats\" before the command can be used.", Color.Red);
GameMain.Server.SendConsoleMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", client, Color.Red);
NewMessage("Client \"" + client.Name + "\" attempted to use the command \"" + Names[0] + "\". Cheats must be enabled using \"enablecheats\" before the command can be used.", Color.Red);
GameMain.Server.SendConsoleMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + Names[0] + "\".", client, Color.Red);
#if USE_STEAM
NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red);
@@ -317,7 +317,7 @@ namespace Barotrauma
private static void AssignOnClientRequestExecute(string names, Action<Client, Vector2, string[]> onClientRequestExecute)
{
var matchingCommand = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0);
var matchingCommand = commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any());
if (matchingCommand == null)
{
throw new Exception("AssignOnClientRequestExecute failed. Command matching the name(s) \"" + names + "\" not found.");
@@ -654,8 +654,10 @@ namespace Barotrauma
ShowQuestionPrompt("Console command permissions to grant to \"" + client.Name + "\"? You may enter multiple commands separated with a space, or \"all\" to allow using any console command.", (commandsStr) =>
{
string[] splitCommands = commandsStr.Split(' ');
bool giveAll = splitCommands.Length > 0 && splitCommands[0].Equals("all", StringComparison.OrdinalIgnoreCase);
Identifier[] splitCommands = commandsStr.Split(' ')
.Select(s => s.Trim())
.ToIdentifiers().ToArray();
bool giveAll = splitCommands.Length > 0 && splitCommands[0] == "all";
List<Command> grantedCommands = new List<Command>();
if (giveAll)
@@ -664,13 +666,12 @@ namespace Barotrauma
}
else
{
for (int i = 0; i < splitCommands.Length; i++)
foreach (Identifier command in splitCommands)
{
splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant();
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i]));
Command matchingCommand = commands.Find(c => c.Names.Contains(command));
if (matchingCommand == null)
{
ThrowError("Could not find the command \"" + splitCommands[i] + "\"!");
ThrowError("Could not find the command \"" + command + "\"!");
}
else
{
@@ -688,7 +689,7 @@ namespace Barotrauma
}
else if (grantedCommands.Count > 0)
{
NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White);
NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.Names[0])) + ".", Color.White);
}
}, args, 1);
@@ -717,22 +718,23 @@ namespace Barotrauma
ShowQuestionPrompt("Console command permissions to revoke from \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) =>
{
string[] splitCommands = commandsStr.Split(' ');
Identifier[] splitCommands = commandsStr.Split(' ')
.Select(s => s.Trim())
.ToIdentifiers().ToArray();
List<Command> revokedCommands = new List<Command>();
bool revokeAll = splitCommands.Length > 0 && splitCommands[0].Equals("all", StringComparison.OrdinalIgnoreCase);
bool revokeAll = splitCommands.Length > 0 && splitCommands[0] == "all";
if (revokeAll)
{
revokedCommands.AddRange(commands);
}
else
{
for (int i = 0; i < splitCommands.Length; i++)
foreach (Identifier command in splitCommands)
{
splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant();
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i]));
Command matchingCommand = commands.Find(c => c.Names.Contains(command));
if (matchingCommand == null)
{
ThrowError("Could not find the command \"" + splitCommands[i] + "\"!");
ThrowError("Could not find the command \"" + command + "\"!");
}
else
{
@@ -749,7 +751,7 @@ namespace Barotrauma
}
else if (revokedCommands.Any())
{
NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White);
NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.Names[0])) + ".", Color.White);
}
}, args, 1);
});
@@ -793,7 +795,7 @@ namespace Barotrauma
NewMessage("Permitted console commands:", Color.White);
foreach (Command permittedCommand in client.PermittedConsoleCommands)
{
NewMessage(" - " + permittedCommand.names[0], Color.White);
NewMessage(" - " + permittedCommand.Names[0], Color.White);
}
}
}
@@ -1156,6 +1158,23 @@ namespace Barotrauma
}
);
commands.Add(new Command("debugjobassignment", "debugjobassignment: Shows information about how jobs were assigned for the most recent round.", (string[] args) =>
{
if (GameMain.Server == null) { return; }
foreach (var debugMsg in GameMain.Server.JobAssignmentDebugLog)
{
NewMessage(debugMsg, Color.Cyan);
}
}));
AssignOnClientRequestExecute("debugjobassignment", (Client client, Vector2 cursorWorldPos, string[] args) =>
{
if (GameMain.Server == null) { return; }
foreach (var debugMsg in GameMain.Server.JobAssignmentDebugLog)
{
GameMain.Server.SendConsoleMessage(debugMsg, client);
}
});
commands.Add(new Command("setpassword|setserverpassword|password", "setpassword [password]: Changes the password of the server that's being hosted.", (string[] args) =>
{
if (GameMain.Server == null) { return; }
@@ -1432,7 +1451,6 @@ namespace Barotrauma
GameMain.Server.PrintSenderTransters();
}));
commands.Add(new Command("forcelocationtypechange", "", (string[] args) =>
{
if (GameMain.Server == null || GameMain.GameSession?.Campaign == null) { return; }
@@ -1568,6 +1586,19 @@ namespace Barotrauma
GameMain.Server.SendChatMessage(ToolBox.RandomSeed(msgLength), ChatMessageType.Default);
}
}));
commands.Add(new Command("multiclienttestmode", "Makes the server assign campaign characters based on the name of the client and the character, as opposed to just checking the account ID or address. Useful for testing the campaign with multiple clients running locally.", (string[] args) =>
{
CharacterCampaignData.RequireClientNameMatch = !CharacterCampaignData.RequireClientNameMatch;
if (CharacterCampaignData.RequireClientNameMatch)
{
NewMessage("Enabled RequireClientNameMatch (clients' names must match their campaign character)");
}
else
{
NewMessage("Disabled RequireClientNameMatch");
}
}));
#endif
AssignOnClientRequestExecute(
@@ -1751,17 +1782,32 @@ namespace Barotrauma
{
Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
}
else
else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase))
{
Submarine.MainSub.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
}
else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase))
{
Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub);
if (Level.Loaded?.EndOutpost == null)
{
NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red);
return;
}
var outpostDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Level.Loaded.EndOutpost);
if (submarineDockingPort != null && outpostDockingPort != null)
{
submarineDockingPort.Dock(outpostDockingPort);
}
}
}
);
AssignOnClientRequestExecute("togglecampaignteleport",
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
if (!(GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign))
if (GameMain.GameSession?.Campaign is not MultiPlayerCampaign mpCampaign)
{
GameMain.Server.SendConsoleMessage("No campaign active.", client, Color.Red);
return;
@@ -2171,21 +2217,21 @@ namespace Barotrauma
}
List<Command> grantedCommands = new List<Command>();
string[] splitCommands = args.Skip(1).ToArray();
bool giveAll = splitCommands.Length > 0 && splitCommands[0].Equals("all", StringComparison.OrdinalIgnoreCase);
Identifier[] splitCommands = args.Skip(1)
.Select(s => s.Trim()).ToIdentifiers().ToArray();
bool giveAll = splitCommands.Length > 0 && splitCommands[0] == "all";
if (giveAll)
{
grantedCommands.AddRange(commands);
}
else
{
for (int i = 0; i < splitCommands.Length; i++)
foreach (Identifier command in splitCommands)
{
splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant();
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i]));
Command matchingCommand = commands.Find(c => c.Names.Contains(command));
if (matchingCommand == null)
{
GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient, Color.Red);
GameMain.Server.SendConsoleMessage("Could not find the command \"" + command + "\"!", senderClient, Color.Red);
}
else
{
@@ -2204,7 +2250,7 @@ namespace Barotrauma
}
else if (grantedCommands.Count > 0)
{
GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", senderClient);
GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.Names[0])) + ".", senderClient);
}
}
);
@@ -2227,21 +2273,21 @@ namespace Barotrauma
return;
}
List<Command> revokedCommands = new List<Command>();
string[] splitCommands = args.Skip(1).ToArray();
bool revokeAll = splitCommands.Length > 0 && splitCommands[0].Equals("all", StringComparison.OrdinalIgnoreCase);
Identifier[] splitCommands = args.Skip(1)
.Select(s => s.Trim()).ToIdentifiers().ToArray();
bool revokeAll = splitCommands.Length > 0 && splitCommands[0] == "all";
if (revokeAll)
{
revokedCommands.AddRange(commands);
}
else
{
for (int i = 0; i < splitCommands.Length; i++)
foreach (Identifier command in splitCommands)
{
splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant();
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i]));
Command matchingCommand = commands.Find(c => c.Names.Contains(command));
if (matchingCommand == null)
{
GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient, Color.Red);
GameMain.Server.SendConsoleMessage("Could not find the command \"" + command + "\"!", senderClient, Color.Red);
}
else
{
@@ -2256,14 +2302,14 @@ namespace Barotrauma
client.RemovePermission(ClientPermissions.ConsoleCommands);
}
GameMain.Server.UpdateClientPermissions(client);
GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient);
GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.Names[0])) + ".", senderClient);
if (revokeAll)
{
GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use console commands.", senderClient);
}
else if (revokedCommands.Count > 0)
{
GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient);
GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.Names[0])) + ".", senderClient);
}
}
);
@@ -2308,7 +2354,7 @@ namespace Barotrauma
GameMain.Server.SendConsoleMessage("Permitted console commands:", senderClient);
foreach (Command permittedCommand in client.PermittedConsoleCommands)
{
GameMain.Server.SendConsoleMessage(" - " + permittedCommand.names[0], senderClient);
GameMain.Server.SendConsoleMessage(" - " + permittedCommand.Names[0], senderClient);
}
}
}
@@ -2585,10 +2631,10 @@ namespace Barotrauma
}
string[] splitCommand = ToolBox.SplitCommand(command);
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant()));
Command matchingCommand = commands.Find(c => c.Names.Contains(splitCommand[0].ToIdentifier()));
if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand) && client.Connection != GameMain.Server.OwnerConnection)
{
GameMain.Server.SendConsoleMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client, Color.Red);
GameMain.Server.SendConsoleMessage("You are not permitted to use the command\"" + matchingCommand.Names[0] + "\"!", client, Color.Red);
GameServer.Log(GameServer.ClientLogName(client) + " attempted to execute the console command \"" + command + "\" without a permission to use the command.", ServerLog.MessageType.ConsoleUsage);
return;
}
@@ -2612,14 +2658,14 @@ namespace Barotrauma
}
catch (Exception e)
{
ThrowError("Executing the command \"" + matchingCommand.names[0] + "\" by request from \"" + GameServer.ClientLogName(client) + "\" failed.", e);
ThrowError("Executing the command \"" + matchingCommand.Names[0] + "\" by request from \"" + GameServer.ClientLogName(client) + "\" failed.", e);
}
}
static partial void ShowHelpMessage(Command command)
{
NewMessage(command.names[0], Color.Cyan);
NewMessage(command.help, Color.Gray);
NewMessage(command.Names[0].Value, Color.Cyan);
NewMessage(command.Help, Color.Gray);
}
}
}
@@ -27,7 +27,8 @@ partial class EventLogAction : EventAction
}
else
{
DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.");
DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.",
ParentEvent.Prefab.ContentPackage);
}
}
if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, targetClients) && ShowInServerLog)
@@ -118,26 +118,13 @@ namespace Barotrauma
private void CheckContentPackage()
{
//TODO: reimplement using only core package?
/*foreach (ContentPackage contentPackage in Config.AllEnabledPackages)
if (Version < VanillaContent.GameVersion)
{
var exePaths = contentPackage.GetFilesOfType(ContentType.ServerExecutable);
if (exePaths.Count() > 0 && AppDomain.CurrentDomain.FriendlyName != exePaths.First())
{
DebugConsole.NewMessage(AppDomain.CurrentDomain.FriendlyName);
DebugConsole.ShowQuestionPrompt(TextManager.GetWithVariables("IncorrectExe", new string[2] { "[selectedpackage]", "[exename]" }, new string[2] { contentPackage.Name, exePaths.First() }),
(option) =>
{
if (option.ToLower() == "y" || option.ToLower() == "yes")
{
string fullPath = Path.GetFullPath(exePaths.First());
ToolBox.OpenFileWithShell(fullPath);
ShouldRun = false;
}
});
break;
}
}*/
DebugConsole.ThrowError(
TextManager.GetWithVariables("versionmismatchwarning",
("[gameversion]", Version.ToString()),
("[contentversion]", VanillaContent.GameVersion.ToString())));
}
}
public void StartServer()
@@ -2,27 +2,12 @@
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Networking;
using System.Text;
namespace Barotrauma
{
partial class CargoManager
{
public void SellBackPurchasedItems(Identifier storeIdentifier, List<PurchasedItem> itemsToSell, Client client)
{
// Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction
var buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, itemsToSell.Select(i => i.ItemPrefab));
var store = Location.GetStore(storeIdentifier);
if (store == null) { return; }
var storeSpecificItems = GetPurchasedItems(storeIdentifier);
foreach (var item in itemsToSell)
{
var itemValue = item.Quantity * buyValues[item.ItemPrefab];
store.Balance -= itemValue;
campaign.GetWallet(client).Give(itemValue);
storeSpecificItems?.Remove(item);
}
}
public void BuyBackSoldItems(Identifier storeIdentifier, List<SoldItem> itemsToBuy, Client client)
{
var store = Location.GetStore(storeIdentifier);
@@ -80,6 +65,21 @@ namespace Barotrauma
OnSoldItemsChanged?.Invoke(this);
}
public void LogNewItemPurchases(Identifier storeIdentifier, List<PurchasedItem> newItems, Client client)
{
StringBuilder sb = new StringBuilder();
int price = 0;
Dictionary<ItemPrefab, int> buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, newItems.Select(i => i.ItemPrefab));
foreach (PurchasedItem item in newItems)
{
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
sb.Append($"\n - {item.ItemPrefab.Name} x{item.Quantity}");
price += itemValue;
}
GameServer.Log($"{NetworkMember.ClientLogName(client, client?.Name ?? "Unknown")} purchased {newItems.Count} item(s) for {TextManager.FormatCurrency(price)}{sb.ToString()}", ServerLog.MessageType.Money);
}
public void ClearSoldItemsProjSpecific()
{
SoldItems.Clear();
@@ -1,5 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Networking;
namespace Barotrauma
{
@@ -27,6 +26,15 @@ namespace Barotrauma
AnyOneAllowedToManageCampaign(permissions);
}
public static bool AllowImmediateItemDelivery(Client client)
{
if (client == null || GameMain.Server == null) { return false; }
return
GameMain.Server.ServerSettings.AllowImmediateItemDelivery ||
client.HasPermission(ClientPermissions.ManageCampaign) ||
client.Connection == GameMain.Server.OwnerConnection;
}
public static bool AllowedToManageWallets(Client client)
{
return AllowedToManageCampaign(client, ClientPermissions.ManageMoney);
@@ -6,6 +6,14 @@ namespace Barotrauma
{
partial class CharacterCampaignData
{
#if DEBUG
/// <summary>
/// If enabled, client names must match the name of the character. Useful for testing the campaign with multiple clients running locally:
/// without this, the clients would all get assigned the same character due to all of them having the same AccountId or Address.
/// </summary>
public static bool RequireClientNameMatch = false;
#endif
public bool HasSpawned;
public bool HasItemData
@@ -76,7 +84,7 @@ namespace Barotrauma
{
case "character":
case "characterinfo":
CharacterInfo = new CharacterInfo(subElement);
CharacterInfo = new CharacterInfo(new ContentXElement(contentPackage: null, subElement));
break;
case "inventory":
itemData = subElement;
@@ -103,6 +111,12 @@ namespace Barotrauma
}
else
{
#if DEBUG
if (RequireClientNameMatch)
{
return ClientAddress == client.Connection.Endpoint.Address && client.Name == Name;
}
#endif
return ClientAddress == client.Connection.Endpoint.Address;
}
}
@@ -806,7 +806,7 @@ namespace Barotrauma
UInt16 itemToRemoveID = msg.ReadUInt16();
Identifier itemToInstallIdentifier = msg.ReadIdentifier();
ItemPrefab itemToInstall = itemToInstallIdentifier.IsEmpty ? null : ItemPrefab.Find(string.Empty, itemToInstallIdentifier);
if (!(Entity.FindEntityByID(itemToRemoveID) is Item itemToRemove)) { continue; }
if (Entity.FindEntityByID(itemToRemoveID) is not Item itemToRemove) { continue; }
purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
}
@@ -894,7 +894,7 @@ namespace Barotrauma
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.GetPurchasedItem(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);
}
@@ -905,9 +905,41 @@ namespace Barotrauma
{
prevPurchasedItems.Add(kvp.Key, new List<PurchasedItem>(kvp.Value));
}
foreach (var kvp in prevPurchasedItems)
foreach (var storeId in purchasedItems.Keys)
{
CargoManager.SellBackPurchasedItems(kvp.Key, kvp.Value, sender);
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())
{
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})")));
}
}
DebugConsole.Log($" New purchases:");
foreach (var purchasedItem in purchasedItems[storeId])
{
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)
@@ -916,17 +948,23 @@ namespace Barotrauma
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) is { } alreadyPurchased)
{
desiredQuantity -= alreadyPurchased.Quantity;
}
int availableQuantity = map.CurrentLocation.Stores[storeId].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0;
purchasedItem.Quantity = Math.Min(purchasedItem.Quantity, availableQuantity);
}
CargoManager.PurchaseItems(storeId, purchasedItemList, false, sender);
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.OnNewItemsPurchased(storeIdentifier, items, sender);
CargoManager.LogNewItemPurchases(storeIdentifier, items, sender);
continue;
}
@@ -941,7 +979,6 @@ namespace Barotrauma
newItems.Add(item);
continue;
}
if (matching.Quantity < item.Quantity)
{
newItems.Add(new PurchasedItem(item.ItemPrefab, item.Quantity - matching.Quantity, sender));
@@ -950,7 +987,7 @@ namespace Barotrauma
if (newItems.Any())
{
CargoManager.OnNewItemsPurchased(storeIdentifier, newItems, sender);
CargoManager.LogNewItemPurchases(storeIdentifier, newItems, sender);
}
}
@@ -1015,7 +1052,7 @@ namespace Barotrauma
UpgradeManager.PurchaseUpgrade(prefab, category, client: sender);
// unstable logging
int price = prefab.Price.GetBuyPrice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation, characterList);
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);
}
@@ -1,11 +1,11 @@
#nullable enable
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
@@ -138,7 +138,7 @@ namespace Barotrauma.Items.Components
return;
}
bool result = AddComponentInternal(id, prefab, resource.Prefab, data.Position, it =>
bool result = AddComponentInternal(id, prefab, resource.Prefab, data.Position, c.Character, it =>
{
CreateServerEvent(new CircuitBoxServerCreateComponentEvent(it.ID, resource.Prefab.UintIdentifier, id, data.Position));
});
@@ -304,7 +304,8 @@ namespace Barotrauma.Items.Components
private void ThrowError(string message, Client c)
{
DebugConsole.ThrowError(message);
DebugConsole.ThrowError(message,
contentPackage: item.Prefab.ContentPackage);
SendToClient(CircuitBoxOpcode.Error, new CircuitBoxErrorEvent(message), c);
}
@@ -110,7 +110,8 @@ namespace Barotrauma
(pickable.IsAttached && !pickable.PickingDone) ||
item.AllowedSlots.None())
{
DebugConsole.AddWarning($"Client {c.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})");
DebugConsole.AddWarning($"Client {c.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})",
item.Prefab.ContentPackage);
continue;
}
@@ -124,7 +124,7 @@ namespace Barotrauma
out NetworkFireSource[] newFireSources);
if (!c.HasPermission(ClientPermissions.ConsoleCommands) ||
!c.PermittedConsoleCommands.Any(command => command.names.Contains("fire") || command.names.Contains("editfire")))
!c.PermittedConsoleCommands.Any(command => command.Names.Contains("fire".ToIdentifier()) || command.Names.Contains("editfire".ToIdentifier())))
{
return;
}
@@ -138,7 +138,7 @@ namespace Barotrauma
var newFire = i < FireSources.Count ?
FireSources[i] :
new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true);
new FireSource(Submarine == null ? pos : pos + Submarine.Position, sourceCharacter: null, isNetworkMessage: true);
newFire.Position = pos;
newFire.Size = new Vector2(size, newFire.Size.Y);
@@ -80,58 +80,10 @@ namespace Barotrauma.Networking
{
c.LastSentChatMessages.RemoveRange(0, c.LastSentChatMessages.Count - 10);
}
float similarity = 0.0f;
for (int i = 0; i < c.LastSentChatMessages.Count; i++)
{
float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i);
if (string.IsNullOrEmpty(txt))
{
similarity += closeFactor;
}
else
{
int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]);
similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f);
}
}
//order/report messages can be sent a little faster than normal messages without triggering the spam filter
if (orderMsg != null)
{
similarity *= 0.25f;
}
bool isSpamExempt = RateLimiter.IsExempt(c);
if (similarity + c.ChatSpamSpeed > 5.0f && !isSpamExempt)
{
GameMain.Server.KarmaManager.OnSpamFilterTriggered(c);
c.ChatSpamCount++;
if (c.ChatSpamCount > 3)
{
//kick for spamming too much
GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked").Value);
}
else
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
}
return;
}
c.ChatSpamSpeed += similarity + 0.5f;
if (c.ChatSpamTimer > 0.0f && !isSpamExempt)
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
return;
}
//order/report messages can be sent a little faster than normal messages without triggering the spam filter;
float similarityMultiplier = orderMsg != null ? 0.25f : 1.0f;
HandleSpamFilter(c, txt, out bool flaggedAsSpam, similarityMultiplier);
if (flaggedAsSpam) { return; }
if (type == ChatMessageType.Order)
{
@@ -177,6 +129,65 @@ namespace Barotrauma.Networking
}
}
/// <summary>
/// Increase the client's chat spam speed and check whether the spam filter should kick in
/// </summary>
public static void HandleSpamFilter(Client c, string messageText, out bool flaggedAsSpam, float similarityMultiplier = 1.0f)
{
float similarity = 0.0f;
for (int i = 0; i < c.LastSentChatMessages.Count; i++)
{
float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i);
if (string.IsNullOrEmpty(messageText))
{
similarity += closeFactor;
}
else
{
int levenshteinDist = ToolBox.LevenshteinDistance(messageText, c.LastSentChatMessages[i]);
similarity += Math.Max((messageText.Length - levenshteinDist) / (float)messageText.Length * closeFactor, 0.0f);
}
}
similarity *= similarityMultiplier;
bool isSpamExempt = RateLimiter.IsExempt(c);
if (similarity + c.ChatSpamSpeed > 5.0f && !isSpamExempt)
{
GameMain.Server.KarmaManager.OnSpamFilterTriggered(c);
c.ChatSpamCount++;
if (c.ChatSpamCount > 3)
{
//kick for spamming too much
GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked").Value);
}
else
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
}
flaggedAsSpam = true;
return;
}
c.ChatSpamSpeed += similarity + 0.5f;
if (c.ChatSpamTimer > 0.0f && !isSpamExempt)
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
flaggedAsSpam = true;
return;
}
flaggedAsSpam = false;
}
public int EstimateLengthBytesServer(Client c)
{
int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE
@@ -369,6 +369,9 @@ namespace Barotrauma.Networking
if (!character.ClientDisconnected) { continue; }
Client owner = connectedClients.Find(c => (c.Character == null || c.Character == character) && character.IsClientOwner(c));
bool canOwnerTakeControl =
owner != null && owner.InGame && !owner.NeedsMidRoundSync &&
(!ServerSettings.AllowSpectating || !owner.SpectateOnly);
if (!character.IsDead)
{
character.KillDisconnectedTimer += deltaTime;
@@ -379,18 +382,19 @@ namespace Barotrauma.Networking
character.Kill(CauseOfDeathType.Disconnected, null);
continue;
}
if (owner != null && owner.InGame && !owner.NeedsMidRoundSync &&
(!ServerSettings.AllowSpectating || !owner.SpectateOnly))
if (canOwnerTakeControl)
{
SetClientCharacter(owner, character);
}
}
else if (owner != null &&
else if (canOwnerTakeControl &&
character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected &&
character.CharacterHealth.VitalityDisregardingDeath > 0)
{
//create network event immediately to ensure the character is revived client-side
//before the client gains control of it (normally status events are created periodically)
character.Revive(removeAfflictions: false, createNetworkEvent: true);
SetClientCharacter(owner, character);
character.Revive(removeAfflictions: false);
}
}
@@ -2534,7 +2538,7 @@ namespace Barotrauma.Networking
{
spawnList.Add(new PurchasedItem(kvp.Key, kvp.Value, buyer: null));
}
CargoManager.CreateItems(spawnList, sub, cargoManager: null);
CargoManager.DeliverItemsToSub(spawnList, sub, cargoManager: null);
}
}
@@ -2581,6 +2585,7 @@ namespace Barotrauma.Networking
msg.WriteBoolean(ServerSettings.AllowRespawn && missionAllowRespawn);
msg.WriteBoolean(ServerSettings.AllowDisguises);
msg.WriteBoolean(ServerSettings.AllowRewiring);
msg.WriteBoolean(ServerSettings.AllowImmediateItemDelivery);
msg.WriteBoolean(ServerSettings.AllowFriendlyFire);
msg.WriteBoolean(ServerSettings.LockAllDefaultWires);
msg.WriteBoolean(ServerSettings.AllowLinkingWifiToChat);
@@ -3706,8 +3711,12 @@ namespace Barotrauma.Networking
}
}
public readonly List<string> JobAssignmentDebugLog = new List<string>();
public void AssignJobs(List<Client> unassigned)
{
JobAssignmentDebugLog.Clear();
var jobList = JobPrefab.Prefabs.ToList();
unassigned = new List<Client>(unassigned);
unassigned = unassigned.OrderBy(sp => Rand.Int(int.MaxValue)).ToList();
@@ -3729,10 +3738,11 @@ namespace Barotrauma.Networking
//remove already assigned clients from unassigned
unassigned.RemoveAll(u => campaignAssigned.ContainsKey(u));
//add up to assigned client count
foreach (KeyValuePair<Client, Job> clientJob in campaignAssigned)
foreach ((Client client, Job job) in campaignAssigned)
{
assignedClientCount[clientJob.Value.Prefab]++;
clientJob.Key.AssignedJob = new JobVariant(clientJob.Value.Prefab, clientJob.Value.Variant);
assignedClientCount[job.Prefab]++;
client.AssignedJob = new JobVariant(job.Prefab, job.Variant);
JobAssignmentDebugLog.Add($"Client {client.Name} has an existing campaign character, keeping the job {job.Name}.");
}
}
@@ -3751,6 +3761,7 @@ namespace Barotrauma.Networking
{
if (unassigned[i].JobPreferences.Count == 0) { continue; }
if (!unassigned[i].JobPreferences.Any() || !unassigned[i].JobPreferences[0].Prefab.AllowAlways) { continue; }
JobAssignmentDebugLog.Add($"Client {unassigned[i].Name} has {unassigned[i].JobPreferences[0].Prefab.Name} as their first preference, assigning it because the job is always allowed.");
unassigned[i].AssignedJob = unassigned[i].JobPreferences[0];
unassigned.RemoveAt(i);
}
@@ -3769,6 +3780,7 @@ namespace Barotrauma.Networking
Client client = FindClientWithJobPreference(unassigned, jobPrefab, forceAssign: false);
if (client != null)
{
JobAssignmentDebugLog.Add($"At least {jobPrefab.MinNumber} {jobPrefab.Name} required. Assigning {client.Name} as a {jobPrefab.Name} (has the job in their preferences).");
AssignJob(client, jobPrefab);
}
}
@@ -3780,7 +3792,11 @@ namespace Barotrauma.Networking
{
if (unassigned.Count == 0) { break; }
if (jobPrefab.MinNumber < 1 || assignedClientCount[jobPrefab] >= jobPrefab.MinNumber) { continue; }
AssignJob(FindClientWithJobPreference(unassigned, jobPrefab, forceAssign: true), jobPrefab);
var client = FindClientWithJobPreference(unassigned, jobPrefab, forceAssign: true);
JobAssignmentDebugLog.Add(
$"At least {jobPrefab.MinNumber} {jobPrefab.Name} required. "+
$"A random client needs to be assigned because no one has the job in their preferences. Assigning {client.Name} as a {jobPrefab.Name}.");
AssignJob(client, jobPrefab);
}
}
@@ -3798,32 +3814,6 @@ namespace Barotrauma.Networking
}
}
List<WayPoint> availableSpawnPoints = WayPoint.WayPointList.FindAll(wp =>
wp.SpawnType == SpawnType.Human &&
wp.Submarine != null && wp.Submarine.TeamID == teamID);
/*bool canAssign = false;
do
{
canAssign = false;
foreach (WayPoint spawnPoint in unassignedSpawnPoints)
{
if (unassigned.Count == 0) { break; }
JobPrefab job = spawnPoint.AssignedJob ?? JobPrefab.List.Values.GetRandom();
if (assignedClientCount[job] >= job.MaxNumber) { continue; }
Client assignedClient = FindClientWithJobPreference(unassigned, job, true);
if (assignedClient != null)
{
assignedClient.AssignedJob = job;
assignedClientCount[job]++;
unassigned.Remove(assignedClient);
canAssign = true;
}
}
} while (unassigned.Count > 0 && canAssign);*/
// Attempt to give the clients a job they have in their job preferences.
// First evaluate all the primary preferences, then all the secondary etc.
for (int preferenceIndex = 0; preferenceIndex < 3; preferenceIndex++)
@@ -3834,12 +3824,17 @@ namespace Barotrauma.Networking
if (preferenceIndex >= client.JobPreferences.Count) { continue; }
var preferredJob = client.JobPreferences[preferenceIndex];
JobPrefab jobPrefab = preferredJob.Prefab;
if (assignedClientCount[jobPrefab] >= jobPrefab.MaxNumber || client.Karma < jobPrefab.MinKarma)
if (assignedClientCount[jobPrefab] >= jobPrefab.MaxNumber)
{
//can't assign this job if maximum number has reached or the clien't karma is too low
JobAssignmentDebugLog.Add($"{client.Name} has {jobPrefab.Name} as their {preferenceIndex + 1}. preference. Cannot assign, maximum number of the job has been reached.");
continue;
}
if (client.Karma < jobPrefab.MinKarma)
{
JobAssignmentDebugLog.Add($"{client.Name} has {jobPrefab.Name} as their {preferenceIndex + 1}. preference. Cannot assign, karma too low ({client.Karma} < {jobPrefab.MinKarma}).");
continue;
}
JobAssignmentDebugLog.Add($"{client.Name} has {jobPrefab.Name} as their {preferenceIndex + 1}. preference. Assigning {client.Name} as a {jobPrefab.Name}.");
client.AssignedJob = preferredJob;
assignedClientCount[jobPrefab]++;
unassigned.RemoveAt(i);
@@ -3855,7 +3850,9 @@ namespace Barotrauma.Networking
//all jobs taken, give a random job
if (remainingJobs.Count == 0)
{
DebugConsole.ThrowError("Failed to assign a suitable job for \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
string errorMsg = $"Failed to assign a suitable job for \"{c.Name}\" (all jobs already have the maximum numbers of players). Assigning a random job...";
DebugConsole.ThrowError(errorMsg);
JobAssignmentDebugLog.Add(errorMsg);
int jobIndex = Rand.Range(0, jobList.Count);
int skips = 0;
while (c.Karma < jobList[jobIndex].MinKarma)
@@ -3871,19 +3868,20 @@ namespace Barotrauma.Networking
assignedClientCount[c.AssignedJob.Prefab]++;
}
//if one of the client's preferences is still available, give them that job
else if (c.JobPreferences.Any(jp => remainingJobs.Contains(jp.Prefab)))
else if (c.JobPreferences.FirstOrDefault(jp => remainingJobs.Contains(jp.Prefab)) is { } remainingJob)
{
foreach (JobVariant preferredJob in c.JobPreferences)
{
c.AssignedJob = preferredJob;
assignedClientCount[preferredJob.Prefab]++;
break;
}
JobAssignmentDebugLog.Add(
$"{c.Name} has {remainingJob.Prefab.Name} as their {c.JobPreferences.IndexOf(remainingJob) + 1}. preference, and it is still available."+
$" Assigning {c.Name} as a {remainingJob.Prefab.Name}.");
c.AssignedJob = remainingJob;
assignedClientCount[remainingJob.Prefab]++;
}
else //none of the client's preferred jobs available, choose a random job
{
c.AssignedJob = new JobVariant(remainingJobs[Rand.Range(0, remainingJobs.Count)], 0);
assignedClientCount[c.AssignedJob.Prefab]++;
JobAssignmentDebugLog.Add(
$"No suitable jobs available for {c.Name} (karma {c.Karma}). Assigning a random job: {c.AssignedJob.Prefab.Name}.");
}
}
}
@@ -611,7 +611,7 @@ namespace Barotrauma.Networking
{
foreach (DebugConsole.Command command in clientPermission.PermittedCommands)
{
clientElement.Add(new XElement("command", new XAttribute("name", command.names[0])));
clientElement.Add(new XElement("command", new XAttribute("name", command.Names[0])));
}
}
doc.Root.Add(clientElement);
@@ -345,9 +345,15 @@ namespace Barotrauma
sender.SetVote(voteType, client);
if (client?.Character != null)
{
GameMain.Server.SendChatMessage(
TextManager.GetWithVariable("traitor.blamebutton.dialog", "[name]", client.Character.DisplayName).Value,
ChatMessageType.Radio, senderClient: sender, senderCharacter: sender.Character);
string msg = TextManager.GetWithVariable("traitor.blamebutton.dialog", "[name]", client.Character.DisplayName).Value;
ChatMessage.HandleSpamFilter(sender, msg, out bool flaggedAsSpam);
if (!flaggedAsSpam)
{
GameMain.Server.SendChatMessage(
msg,
ChatMessageType.Radio, senderClient: sender, senderCharacter: sender.Character);
sender.LastSentChatMessages.Add(msg);
}
}
}
break;
@@ -58,15 +58,20 @@ namespace Barotrauma.Steam
Steamworks.SteamServer.SetKey("message", server.ServerSettings.ServerMessageText);
Steamworks.SteamServer.SetKey("version", GameMain.Version.ToString());
Steamworks.SteamServer.SetKey("playercount", server.ConnectedClients.Count.ToString());
//a2s seems to break if too much data is added (seems to be related to MTU?)
//let's restrict the number of packages to 10, clients can use packagecount to tell when the list has been truncated
const int MaxPackagesToList = 10;
int index = 0;
foreach (var contentPackage in contentPackages)
foreach (var contentPackage in contentPackages.Take(MaxPackagesToList))
{
string ugcIdStr = contentPackage.UgcId.TryUnwrap(out var ugcId) ? ugcId.StringRepresentation : string.Empty;
Steamworks.SteamServer.SetKey(
$"contentpackage{index}",
contentPackage.Name+","+ contentPackage.Hash.StringRepresentation + "," + ugcIdStr);
$"contentpackage{index}",
contentPackage.Name + "," + contentPackage.Hash.StringRepresentation + "," + ugcIdStr);
index++;
}
Steamworks.SteamServer.SetKey("packagecount", contentPackages.Count().ToString());
Steamworks.SteamServer.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
Steamworks.SteamServer.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString());
Steamworks.SteamServer.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString());
@@ -79,6 +84,10 @@ namespace Barotrauma.Steam
Steamworks.SteamServer.SetKey("gamemode", server.ServerSettings.GameModeIdentifier.Value);
Steamworks.SteamServer.SetKey("playstyle", server.ServerSettings.PlayStyle.ToString());
Steamworks.SteamServer.SetKey("language", server.ServerSettings.Language.ToString());
if (GameMain.NetLobbyScreen?.SelectedSub != null)
{
Steamworks.SteamServer.SetKey("submarine", GameMain.NetLobbyScreen.SelectedSub.Name);
}
Steamworks.SteamServer.DedicatedServer = true;
@@ -224,7 +224,8 @@ namespace Barotrauma
var selectedTraitor = SelectRandomTraitor();
if (selectedTraitor == null)
{
DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{selectedPrefab.Identifier}\".");
DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{selectedPrefab.Identifier}\".",
contentPackage: selectedPrefab.ContentPackage);
return false;
}
CreateTraitorEvent(eventManager, selectedPrefab, selectedTraitor);
@@ -263,7 +264,8 @@ namespace Barotrauma
{
DebugConsole.ThrowError(
$"Error in traitor event {traitorEvent.Prefab.Identifier}. Not enough players to choose {amountToChoose} secondary traitors."+
$"Make sure the {nameof(traitorEvent.Prefab.MinPlayerCount)} of the event is high enough to support to desired amount of secondary traitors.");
$"Make sure the {nameof(traitorEvent.Prefab.MinPlayerCount)} of the event is high enough to support to desired amount of secondary traitors.",
contentPackage: traitorEvent.Prefab.ContentPackage);
amountToChoose = viableTraitors.Count;
}
@@ -352,7 +354,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Failed to create an instance of the traitor event prefab \"{selectedPrefab.Identifier}\"!");
DebugConsole.ThrowError($"Failed to create an instance of the traitor event prefab \"{selectedPrefab.Identifier}\"!",
contentPackage: selectedPrefab.ContentPackage);
}
}
@@ -365,7 +368,8 @@ namespace Barotrauma
var traitor = SelectRandomTraitor();
if (traitor == null)
{
DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{traitorEventPrefab.Identifier}\".");
DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{traitorEventPrefab.Identifier}\".",
contentPackage: traitorEventPrefab.ContentPackage);
return;
}
CreateTraitorEvent(eventManager, traitorEventPrefab, traitor);