From 44ded0225a193452d9256a1b665ea0de5d3747d7 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Wed, 30 Mar 2022 01:20:59 +0900 Subject: [PATCH] Unstable 0.17.5.0 --- .../ClientSource/Characters/Character.cs | 10 +- .../Transition/UgcTransition.cs | 225 ++++++++++++ .../ClientSource/DebugConsole.cs | 52 --- .../ClientSource/GUI/CrewManagement.cs | 6 +- .../BarotraumaClient/ClientSource/GUI/GUI.cs | 26 +- .../ClientSource/GUI/GUITextBox.cs | 5 +- .../ClientSource/GUI/Store.cs | 88 ++--- .../ClientSource/GUI/TabMenu.cs | 347 +++++++++++------- .../ClientSource/GUI/UpgradeStore.cs | 30 +- .../ClientSource/GUI/VotingInterface.cs | 160 +++++--- .../BarotraumaClient/ClientSource/GameMain.cs | 8 +- .../ClientSource/GameSession/CargoManager.cs | 2 + .../GameSession/GameModes/CampaignMode.cs | 24 +- .../GameModes/MultiPlayerCampaign.cs | 6 +- .../GameModes/SinglePlayerCampaign.cs | 4 +- .../ClientSource/GameSession/GameSession.cs | 1 + .../ClientSource/GameSession/RoundSummary.cs | 2 +- .../Items/Components/ItemComponent.cs | 14 +- .../Items/Components/Machines/Fabricator.cs | 47 ++- .../Items/Components/Machines/MiniMap.cs | 6 +- .../ClientSource/Map/Map/Map.cs | 2 +- .../ClientSource/Networking/GameClient.cs | 68 ++-- .../ClientSource/Networking/Voting.cs | 319 ++++++++-------- .../CampaignSetupUI/CampaignSetupUI.cs | 58 +++ .../MultiPlayerCampaignSetupUI.cs | 75 +--- .../SinglePlayerCampaignSetupUI.cs | 74 +--- .../ClientSource/Screens/CampaignUI.cs | 22 +- .../Screens/CharacterEditor/Wizard.cs | 11 +- .../ClientSource/Screens/EditorScreen.cs | 2 + .../ClientSource/Screens/MainMenuScreen.cs | 9 +- .../ClientSource/Screens/NetLobbyScreen.cs | 93 +---- .../ClientSource/Screens/ServerListScreen.cs | 30 +- .../ClientSource/Screens/SubEditorScreen.cs | 30 +- .../ClientSource/Settings/SettingsMenu.cs | 8 +- .../Steam/{ => WorkshopMenu}/BBCode.cs | 8 +- .../Immutable/ImmutableWorkshopMenu.cs | 41 +++ .../{ => WorkshopMenu/Mutable}/ItemList.cs | 2 +- .../Mutable/MutableWorkshopMenu.cs} | 18 +- .../{ => WorkshopMenu/Mutable}/PublishTab.cs | 2 +- .../Steam/{ => WorkshopMenu}/UiUtil.cs | 24 +- .../Steam/WorkshopMenu/WorkshopMenu.cs | 12 + .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../GameModes/CharacterCampaignData.cs | 2 +- .../GameModes/MultiPlayerCampaign.cs | 248 ++++++------- .../ServerSource/GameSession/MedicalClinic.cs | 2 +- .../ServerSource/Networking/GameServer.cs | 160 ++++---- .../ServerSource/Networking/ServerSettings.cs | 4 +- .../ServerSource/Networking/Voting.cs | 268 ++++++++++---- .../ServerSource/Screens/NetLobbyScreen.cs | 2 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Data/permissionpresets.xml | 2 +- .../SharedSource/Characters/Character.cs | 9 +- .../ContentPackageManager.cs | 20 +- .../ContentManagement/ContentXElement.cs | 18 + .../BarotraumaShared/SharedSource/Enums.cs | 11 +- .../SharedSource/Events/Missions/Mission.cs | 16 +- .../Events/Missions/MissionPrefab.cs | 3 +- .../SharedSource/GameSession/CargoManager.cs | 1 + .../GameSession/Data/Reputation.cs | 2 +- .../SharedSource/GameSession/Data/Wallet.cs | 15 +- .../GameSession/GameModes/CampaignMode.cs | 37 +- .../GameModes/MultiPlayerCampaign.cs | 4 +- .../SharedSource/GameSession/GameSession.cs | 62 +++- .../Items/Components/Machines/Controller.cs | 93 +++-- .../Items/Components/Machines/Fabricator.cs | 30 +- .../SharedSource/Items/Item.cs | 11 +- .../SharedSource/Items/ItemPrefab.cs | 15 +- .../SharedSource/Map/Levels/Level.cs | 8 +- .../Map/Levels/LevelGenerationParams.cs | 4 +- .../SharedSource/Map/Map/Location.cs | 12 +- .../SharedSource/Map/Map/LocationType.cs | 37 +- .../SharedSource/Map/Map/Map.cs | 41 ++- .../Map/Outposts/OutpostGenerator.cs | 5 + .../SharedSource/Map/SubmarineInfo.cs | 79 +--- .../Networking/ClientPermissions.cs | 7 +- .../SharedSource/Networking/NetworkMember.cs | 7 +- .../SharedSource/Networking/ServerSettings.cs | 85 +++-- .../SharedSource/Networking/Voting.cs | 26 +- .../SharedSource/Steam/Workshop.cs | 10 +- .../SharedSource/SteamAchievementManager.cs | 10 +- .../SharedSource/{Map => Utils}/Md5Hash.cs | 0 .../SharedSource/Utils/NamedEvent.cs | 6 + .../SharedSource/Utils/SaveUtil.cs | 58 ++- Barotrauma/BarotraumaShared/changelog.txt | 50 +++ 88 files changed, 2033 insertions(+), 1430 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/UgcTransition.cs rename Barotrauma/BarotraumaClient/ClientSource/Steam/{ => WorkshopMenu}/BBCode.cs (96%) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Immutable/ImmutableWorkshopMenu.cs rename Barotrauma/BarotraumaClient/ClientSource/Steam/{ => WorkshopMenu/Mutable}/ItemList.cs (99%) rename Barotrauma/BarotraumaClient/ClientSource/Steam/{WorkshopMenu.cs => WorkshopMenu/Mutable/MutableWorkshopMenu.cs} (97%) rename Barotrauma/BarotraumaClient/ClientSource/Steam/{ => WorkshopMenu/Mutable}/PublishTab.cs (99%) rename Barotrauma/BarotraumaClient/ClientSource/Steam/{ => WorkshopMenu}/UiUtil.cs (79%) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/WorkshopMenu.cs rename Barotrauma/BarotraumaShared/SharedSource/{Map => Utils}/Md5Hash.cs (100%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 4bd56bce4..0891e5008 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -1151,15 +1151,7 @@ namespace Barotrauma } } - partial void OnMoneyChanged(int prevAmount, int newAmount) - { - if (newAmount > prevAmount) - { - int increase = newAmount - prevAmount; - AddMessage("+" + TextManager.GetWithVariable("currencyformat", "[credits]", "[value]").Value, - GUIStyle.Yellow, playSound: this == Controlled, "money".ToIdentifier(), increase); - } - } + partial void OnMoneyChanged(int prevAmount, int newAmount) { } partial void OnTalentGiven(TalentPrefab talentPrefab) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/UgcTransition.cs b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/UgcTransition.cs new file mode 100644 index 000000000..026b93043 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/UgcTransition.cs @@ -0,0 +1,225 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Barotrauma.Extensions; +using Barotrauma.Steam; +using Microsoft.Xna.Framework; +using Directory = Barotrauma.IO.Directory; +using File = Barotrauma.IO.File; +using Path = Barotrauma.IO.Path; + +namespace Barotrauma.Transition +{ + /// + /// Class dedicated to transitioning away from the old, shitty + /// Mods + Submarines folders to the new LocalMods folder + /// + public static class UgcTransition + { + private const string readmeName = "LOCALMODS_README.txt"; + + public static void Prepare() + { + TaskPool.Add("UgcTransition.Prepare", DetermineItemsToTransition(), t => + { + if (!t.TryGetResult(out (OldSubs, OldMods) pair)) { return; } + var (subs, mods) = pair; + if (!subs.FilePaths.Any() && !mods.Mods.Any()) { return; } + + var msgBox = new GUIMessageBox(TextManager.Get("Ugc.TransferTitle"), "", relativeSize: (0.5f, 0.8f), + buttons: new LocalizedString[] { TextManager.Get("Ugc.TransferButton") }); + + var desc = new GUITextBlock(new RectTransform((1.0f, 0.24f), msgBox.Content.RectTransform), + text: TextManager.Get("Ugc.TransferDesc"), wrap: true, textAlignment: Alignment.CenterLeft); + + var modsList = new GUIListBox(new RectTransform((1.0f, 0.6f), msgBox.Content.RectTransform)) + { + HoverCursor = CursorState.Default + }; + Dictionary pathTickboxMap = new Dictionary(); + + void addHeader(LocalizedString str) + { + var itemFrame = new GUITextBlock(new RectTransform((1.0f, 0.08f), modsList.Content.RectTransform), + text: str, font: GUIStyle.SubHeadingFont) + { + CanBeFocused = false + }; + } + void addTickbox(string dir, string name, bool ticked) + { + var itemFrame = new GUIFrame(new RectTransform((1.0f, 0.07f), modsList.Content.RectTransform), + style: null) + { + CanBeFocused = false + }; + var tickbox = new GUITickBox(new RectTransform(Vector2.One, itemFrame.RectTransform), name) + { + Selected = ticked + }; + pathTickboxMap.Add(dir, tickbox); + } + + addHeader(TextManager.Get("WorkshopLabelSubmarines")); + foreach (var sub in subs.FilePaths) + { + var subName = Path.GetFileNameWithoutExtension(sub); + addTickbox(sub, subName, ticked: !ContentPackageManager.LocalPackages.Any(p => p.NameMatches(subName))); + } + + addHeader(""); + addHeader(TextManager.Get("SubscribedMods")); + foreach (var mod in mods.Mods) + { + addTickbox(mod.Dir, mod.Name, ticked: !ContentPackageManager.LocalPackages.Any(p => p.SteamWorkshopId != 0 && p.SteamWorkshopId == mod.Item?.Id)); + } + + GUIMessageBox? subMsgBox = null; + + void createSubMsgBox(LocalizedString str, bool closable) + { + subMsgBox?.Close(); + subMsgBox = new GUIMessageBox(headerText: "", text: str, + buttons: closable ? new[] { TextManager.Get("Close") } : Array.Empty()); + if (closable) + { + subMsgBox.Buttons[0].OnClicked = subMsgBox.Close; + } + } + + msgBox.Buttons[0].OnClicked = (b, o) => + { + TaskPool.Add("TransferMods", TransferMods(pathTickboxMap), t2 => + { + if (t2.Exception != null) + { + DebugConsole.ThrowError("There was an error transferring mods", t2.Exception.GetInnermost()); + } + ContentPackageManager.LocalPackages.Refresh(); + createSubMsgBox(TextManager.Get("Ugc.TransferComplete"), closable: true); + }); + msgBox.Close(); + createSubMsgBox(TextManager.Get("Ugc.Transferring"), closable: false); + return false; + }; + }); + } + + private struct OldSubs + { + public readonly IReadOnlyList FilePaths; + + public OldSubs(IReadOnlyList filePaths) + { + FilePaths = filePaths; + } + } + + private struct OldMods + { + public readonly IReadOnlyList<(string Dir, string Name, Steamworks.Ugc.Item? Item, DateTime InstallTime)> Mods; + + public OldMods(IReadOnlyList<(string Dir, string Name, Steamworks.Ugc.Item? Item, DateTime InstallTime)> mods) + { + Mods = mods; + } + } + + private const string oldSubsPath = "Submarines"; + private const string oldModsPath = "Mods"; + + private static async Task<(OldSubs Subs, OldMods Mods)> DetermineItemsToTransition() + { + string[] subs = Array.Empty(); + List<(string Dir, string Name, Steamworks.Ugc.Item? Item, DateTime InstallTime)> mods + = new List<(string Dir, string Name, Steamworks.Ugc.Item? Item, DateTime InstallTime)>(); + if (FolderShouldBeTransitioned(oldModsPath)) + { + subs = Directory.GetFiles(oldSubsPath, "*.sub", SearchOption.TopDirectoryOnly); + string[] allOldMods = Directory.GetDirectories(oldModsPath, "*", SearchOption.TopDirectoryOnly); + + var publishedItems = await SteamManager.Workshop.GetPublishedItems(); + foreach (var modDir in allOldMods) + { + var fileList = XMLExtensions.TryLoadXml(Path.Combine(modDir, ContentPackage.FileListFileName), out _); + if (fileList?.Root is null) { continue; } + + var oldId = fileList.Root.GetAttributeUInt64("steamworkshopid", 0); + var updateTime = File.GetLastWriteTime(modDir).ToUniversalTime(); + var oldName = fileList.Root.GetAttributeString("name", ""); + + var item = oldId != 0 ? publishedItems.FirstOrNull(it => it.Id == oldId) : null; + if (oldId == 0 || item.HasValue) + { + mods.Add((modDir, oldName, item, updateTime)); + } + } + } + + while (!(Screen.Selected is MainMenuScreen)) { await Task.Delay(500); } + + return (new OldSubs(subs), new OldMods(mods)); + } + + private static bool FolderShouldBeTransitioned(string folderName) + { + return Directory.Exists(folderName) + && !File.Exists(Path.Combine(folderName, readmeName)); + } + + private static async Task TransferMods(Dictionary pathTickboxMap) + { + //WriteReadme(oldSubsPath); //can't do this because the submarine discovery code is borked + WriteReadme(oldModsPath); + foreach (var (path, tickbox) in pathTickboxMap) + { + if (!tickbox.Selected) { continue; } + string dirName = Path.GetFileNameWithoutExtension(path); + string destPath = Path.Combine(ContentPackage.LocalModsDir, dirName); + + //find unique path to save in + for (int i = 0;;i++) + { + if (!Directory.Exists(destPath)) { break; } + destPath = Path.Combine(ContentPackage.LocalModsDir, $"{dirName}.{i}"); + } + + if (path.StartsWith(oldSubsPath, StringComparison.OrdinalIgnoreCase)) + { + //copying a sub: manually create filelist.xml + ModProject modProject = new ModProject + { + Name = dirName, + ModVersion = ContentPackage.DefaultModVersion + }; + modProject.AddFile(ModProject.File.FromPath(Path.Combine(ContentPath.ModDirStr, $"{dirName}.sub"))); + + Directory.CreateDirectory(destPath); + File.Copy(path, Path.Combine(destPath, $"{dirName}.sub")); + modProject.Save(Path.Combine(destPath, ContentPackage.FileListFileName)); + + await Task.Yield(); + } + else + { + //copying a mod: we have a neat method for that! + await SteamManager.Workshop.CopyDirectory(path, Path.GetFileName(path), path, destPath); + } + } + } + + private static void WriteReadme(string folderName) + { + if (!Directory.Exists(folderName)) { return; } + File.WriteAllText(path: Path.Combine(folderName, readmeName), + contents: "This folder is no longer used by Barotrauma;\n" + + "your mods and submarines should have been transferred\n" + + "to LocalMods. If they are not being found, delete this\n" + + "readme and relaunch the game.", encoding: Encoding.UTF8); + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index f653a1a83..57d4bc38f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -1101,58 +1101,6 @@ namespace Barotrauma } }, isCheat: true)); - commands.Add(new Command("save|savesub", "save [submarine name]: Save the currently loaded submarine using the specified name.", (string[] args) => - { - if (args.Length < 1) { return; } - - GameMain.SubEditorScreen.SetMode(SubEditorScreen.Mode.Default); - - string fileName = string.Join(" ", args); - if (fileName.Contains("../")) - { - ThrowError("Illegal symbols in filename (../)"); - return; - } - - if (Submarine.MainSub.TrySaveAs(Barotrauma.IO.Path.Combine(SubmarineInfo.SavePath, fileName + ".sub"))) - { - NewMessage("Sub saved", Color.Green); - } - })); - - commands.Add(new Command("load|loadsub", "load [submarine name]: Load a submarine.", (string[] args) => - { - if (args.Length == 0) { return; } - - if (GameMain.GameSession != null) - { - ThrowError("The loadsub command cannot be used when a round is running. You should probably be using spawnsub instead."); - return; - } - - string name = string.Join(" ", args); - SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => name.Equals(s.Name, StringComparison.OrdinalIgnoreCase)); - if (subInfo == null) - { - string path = Path.Combine(SubmarineInfo.SavePath, name); - if (!File.Exists(path)) - { - ThrowError($"Could not find a submarine with the name \"{name}\" or in the path {path}."); - return; - } - subInfo = new SubmarineInfo(path); - } - - Submarine.Load(subInfo, true); - }, - () => - { - return new string[][] - { - SubmarineInfo.SavedSubmarines.Select(s => s.Name).ToArray() - }; - })); - commands.Add(new Command("cleansub", "", (string[] args) => { for (int i = MapEntity.mapEntityList.Count - 1; i >= 0; i--) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs index 17f493e12..37935913a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs @@ -22,7 +22,7 @@ namespace Barotrauma private GUIButton clearAllButton; private List PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires; - private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(); + private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(ClientPermissions.ManageHires); private Point resolutionWhenCreated; @@ -433,7 +433,7 @@ namespace Barotrauma else if (!btn.Enabled) { btn.ToolTip = string.Empty; - btn.Enabled = true; + btn.Enabled = HasPermission; } }; @@ -632,7 +632,7 @@ namespace Barotrauma totalBlock.Text = TextManager.FormatCurrency(total); bool enoughMoney = campaign == null || campaign.Wallet.CanAfford(total); totalBlock.TextColor = enoughMoney ? Color.White : Color.Red; - validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any(); + validateHiresButton.Enabled = enoughMoney && HasPermission && pendingList.Content.RectTransform.Children.Any(); } public bool ValidateHires(List hires, bool createNetworkEvent = false) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index cba0182cf..88b835ede 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -316,26 +316,27 @@ namespace Barotrauma if (GameMain.ShowPerf) { + int x = 400; int y = 10; - DrawString(spriteBatch, new Vector2(300, y), + DrawString(spriteBatch, new Vector2(x, y), "Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString("0.00") + " ms" + " Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms", GUIStyle.Green, Color.Black * 0.8f, font: GUIStyle.SmallFont); y += 15; - GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: GUIStyle.Green); + GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(x, y, 170, 50), color: GUIStyle.Green); y += 50; - DrawString(spriteBatch, new Vector2(300, y), + DrawString(spriteBatch, new Vector2(x, y), "Update - Avg: " + GameMain.PerformanceCounter.UpdateTimeGraph.Average().ToString("0.00") + " ms" + " Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString("0.00") + " ms", Color.LightBlue, Color.Black * 0.8f, font: GUIStyle.SmallFont); y += 15; - GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: Color.LightBlue); + GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(x, y, 170, 50), color: Color.LightBlue); y += 50; foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers) { float elapsedMillisecs = GameMain.PerformanceCounter.GetAverageElapsedMillisecs(key); - DrawString(spriteBatch, new Vector2(300, y), + DrawString(spriteBatch, new Vector2(x, y), key + ": " + elapsedMillisecs.ToString("0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, elapsedMillisecs / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); y += 15; @@ -351,18 +352,19 @@ namespace Barotrauma if (Powered.Grids != null) { - DrawString(spriteBatch, new Vector2(300, y), "Grids: " + Powered.Grids.Count, Color.LightGreen, Color.Black * 0.5f, 0, GUIStyle.SmallFont); + DrawString(spriteBatch, new Vector2(x, y), "Grids: " + Powered.Grids.Count, Color.LightGreen, Color.Black * 0.5f, 0, GUIStyle.SmallFont); y += 15; } if (Settings.EnableDiagnostics) { - DrawString(spriteBatch, new Vector2(320, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 15), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 30), "AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 45), "NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 60), "ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 75), "SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); + x += 20; + DrawString(spriteBatch, new Vector2(x, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); + DrawString(spriteBatch, new Vector2(x, y + 15), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); + DrawString(spriteBatch, new Vector2(x, y + 30), "AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); + DrawString(spriteBatch, new Vector2(x, y + 45), "NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); + DrawString(spriteBatch, new Vector2(x, y + 60), "ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); + DrawString(spriteBatch, new Vector2(x, y + 75), "SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs index 7a096a1a6..4635c2150 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs @@ -534,7 +534,10 @@ namespace Barotrauma } } Vector2 finalBottomRight = characterPositions[endIndex]; - finalBottomRight += Font.MeasureChar(Text[endIndex]) * TextBlock.TextScale; + if (Text.Length > endIndex) + { + finalBottomRight += Font.MeasureChar(Text[endIndex]) * TextBlock.TextScale; + } drawRect(topLeft, finalBottomRight); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index 7d408efd1..88bf466e4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -113,71 +113,40 @@ namespace Barotrauma #region Permissions - private bool hadPermissions, hadBuyPermissions, hadSellInventoryPermissions, hadSellSubPermissions; + private bool hadBuyPermissions, hadSellInventoryPermissions, hadSellSubPermissions; - private bool HasPermissions - { - get => GetPermissions(); - set => hadPermissions = value; - } private bool HasBuyPermissions { - get => HasPermissions || GetPermissions(StoreTab.Buy); + get => HasPermissionToUseTab(StoreTab.Buy); set => hadBuyPermissions = value; } private bool HasSellInventoryPermissions { - get => HasPermissions || GetPermissions(StoreTab.Sell); + get => HasPermissionToUseTab(StoreTab.Sell); set => hadSellInventoryPermissions = value; } private bool HasSellSubPermissions { - get => HasPermissions || GetPermissions(StoreTab.SellSub); + get => HasPermissionToUseTab(StoreTab.SellSub); set => hadSellSubPermissions = value; } - private bool GetPermissions(StoreTab? tab = null) + private bool HasPermissionToUseTab(StoreTab tab) { - if (!tab.HasValue) + return tab switch { - return campaignUI.Campaign.AllowedToManageCampaign() || campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.CampaignStore); - } - else - { - return tab.Value switch - { - StoreTab.Buy => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.BuyItems), - StoreTab.Sell => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems), - StoreTab.SellSub => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems), - _ => false, - }; - } + StoreTab.Buy => true, + StoreTab.Sell => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems), + StoreTab.SellSub => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems), + _ => false, + }; } - private void UpdatePermissions(StoreTab? tab = null) + private void UpdatePermissions() { - HasPermissions = GetPermissions(); - if (!tab.HasValue) - { - HasBuyPermissions = GetPermissions(StoreTab.Buy); - HasSellInventoryPermissions = GetPermissions(StoreTab.Sell); - HasSellSubPermissions = GetPermissions(StoreTab.SellSub); - } - else - { - switch (tab.Value) - { - case StoreTab.Buy: - HasBuyPermissions = GetPermissions(tab.Value); - break; - case StoreTab.Sell: - HasSellInventoryPermissions = GetPermissions(tab.Value); - break; - case StoreTab.SellSub: - HasSellSubPermissions = GetPermissions(tab.Value); - break; - } - } + HasBuyPermissions = HasPermissionToUseTab(StoreTab.Buy); + HasSellInventoryPermissions = HasPermissionToUseTab(StoreTab.Sell); + HasSellSubPermissions = HasPermissionToUseTab(StoreTab.SellSub); } private bool HasTabPermissions(StoreTab tab) @@ -196,23 +165,16 @@ namespace Barotrauma return HasTabPermissions(activeTab); } - private bool HavePermissionsChanged(StoreTab? tab = null) + private bool HavePermissionsChanged(StoreTab tab) { - if (!tab.HasValue) + bool hadTabPermissions = tab switch { - return hadPermissions != HasPermissions; - } - else - { - bool hadTabPermissions = tab.Value switch - { - StoreTab.Buy => hadBuyPermissions, - StoreTab.Sell => hadSellInventoryPermissions, - StoreTab.SellSub => hadSellSubPermissions, - _ => false - }; - return hadTabPermissions != HasTabPermissions(tab.Value); - } + StoreTab.Buy => hadBuyPermissions, + StoreTab.Sell => hadSellInventoryPermissions, + StoreTab.SellSub => hadSellSubPermissions, + _ => false + }; + return hadTabPermissions != HasTabPermissions(tab); } #endregion @@ -2202,10 +2164,6 @@ namespace Barotrauma { RefreshItemsToSellFromSub(); } - if (needsRefresh || HavePermissionsChanged()) - { - Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f); - } if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy)) { RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index abd866462..da6b8d6d1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -43,6 +43,7 @@ namespace Barotrauma private GUIButton transferMenuButton; private float transferMenuOpenState; private bool transferMenuStateCompleted; + private readonly HashSet registeredEvents = new HashSet(); private class LinkedGUI { @@ -218,7 +219,7 @@ namespace Barotrauma public void AddToGUIUpdateList() { - infoFrame?.AddToGUIUpdateList(order: 1); + infoFrame?.AddToGUIUpdateList(); NetLobbyScreen.JobInfoFrame?.AddToGUIUpdateList(); } @@ -298,11 +299,13 @@ namespace Barotrauma } SetBalanceText(balanceText, campaignMode.Bank.Balance); - campaignMode.OnMoneyChanged.RegisterOverwriteExisting(nameof(CreateInfoFrame).ToIdentifier(), e => + Identifier eventIdentifier = nameof(CreateInfoFrame).ToIdentifier(); + campaignMode.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e => { - if (e.Wallet != campaignMode.Bank) { return; } + if (!e.Owner.IsNone()) { return; } SetBalanceText(balanceText, e.Wallet.Balance); }); + registeredEvents.Add(eventIdentifier); static void SetBalanceText(GUITextBlock text, int balance) { @@ -371,15 +374,16 @@ namespace Barotrauma } } - private const float jobColumnWidthPercentage = 0.138f; - private const float characterColumnWidthPercentage = 0.656f; - private const float pingColumnWidthPercentage = 0.206f; + private const float jobColumnWidthPercentage = 0.138f, + characterColumnWidthPercentage = 0.45f, + pingColumnWidthPercentage = 0.206f, + walletColumnWidthPercentage = 0.206f; - private int jobColumnWidth, characterColumnWidth, pingColumnWidth; + private int jobColumnWidth, characterColumnWidth, pingColumnWidth, walletColumnWidth; private void CreateCrewListFrame(GUIFrame crewFrame) { - crew = GameMain.GameSession?.CrewManager?.GetCharacters() ?? Array.Empty(); + crew = GameMain.GameSession?.CrewManager?.GetCharacters() ?? new List() { TestScreen.dummyCharacter}; teamIDs = crew.Select(c => c.TeamID).Distinct().ToList(); // Show own team first when there's more than one team @@ -553,20 +557,23 @@ namespace Barotrauma GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale"); GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale"); GUIButton pingButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("serverlistping"), style: "GUIButtonSmallFreeScale"); + GUIButton walletButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("crewwallet.wallet"), style: "GUIButtonSmallFreeScale"); sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width; jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f); characterButton.RectTransform.RelativeSize = new Vector2(characterColumnWidthPercentage * sizeMultiplier, 1f); pingButton.RectTransform.RelativeSize = new Vector2(pingColumnWidthPercentage * sizeMultiplier, 1f); + walletButton.RectTransform.RelativeSize = new Vector2(walletColumnWidthPercentage * sizeMultiplier, 1f); - jobButton.TextBlock.Font = characterButton.TextBlock.Font = pingButton.TextBlock.Font = GUIStyle.HotkeyFont; - jobButton.CanBeFocused = characterButton.CanBeFocused = pingButton.CanBeFocused = false; - jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = pingButton.ForceUpperCase = ForceUpperCase.Yes; + jobButton.TextBlock.Font = characterButton.TextBlock.Font = pingButton.TextBlock.Font = walletButton.TextBlock.Font = GUIStyle.HotkeyFont; + jobButton.CanBeFocused = characterButton.CanBeFocused = pingButton.CanBeFocused = walletButton.CanBeFocused = false; + jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = pingButton.ForceUpperCase = walletButton.ForceUpperCase = ForceUpperCase.Yes; jobColumnWidth = jobButton.Rect.Width; characterColumnWidth = characterButton.Rect.Width; pingColumnWidth = pingButton.Rect.Width; + walletColumnWidth = walletButton.Rect.Width; } private void CreateMultiPlayerList(bool refresh) @@ -651,6 +658,8 @@ namespace Barotrauma }; } } + + CreateWalletCrewFrame(character, paddedFrame); } private void CreateMultiPlayerClientElement(Client client) @@ -678,6 +687,10 @@ namespace Barotrauma }; CreateNameWithPermissionIcon(client, paddedFrame); + if (client.Character is { } character) + { + CreateWalletCrewFrame(character, paddedFrame); + } linkedGUIList.Add(new LinkedGUI(client, frame, false, new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center))); } @@ -714,6 +727,47 @@ namespace Barotrauma return 0; } + private void CreateWalletCrewFrame(Character character, GUILayoutGroup paddedFrame) + { + GUILayoutGroup walletLayout = new GUILayoutGroup(new RectTransform(new Point(walletColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.Center) + { + CanBeFocused = false + }; + + if (character.IsBot) { return; } + + GUILayoutGroup paddedLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1f), walletLayout.RectTransform, Anchor.Center), isHorizontal: true) + { + Stretch = true + }; + + GUIImage icon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "StoreTradingIcon", scaleToFit: true); + GUITextBlock walletBlock = new GUITextBlock(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), string.Empty, textAlignment: Alignment.Right, font: GUIStyle.SubHeadingFont); + SetWalletText(walletBlock, character.Wallet); + + if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign) + { + Identifier eventIdentifier = new Identifier($"{nameof(CreateWalletCrewFrame)}.{character.ID}"); + campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e => + { + if (!(e.Owner is Some { Value: var owner }) || owner != character) { return; } + SetWalletText(walletBlock, e.Wallet); + }); + registeredEvents.Add(eventIdentifier); + } + + static void SetWalletText(GUITextBlock block, Wallet wallet) + { + block.Text = TextManager.FormatCurrency(wallet.Balance); + block.ToolTip = string.Empty; + if (block.TextSize.X + block.Padding.X + block.Padding.Z > block.Rect.Width) + { + block.ToolTip = block.Text; + block.Text = TextManager.Get("crewwallet.balance.toomuchtoshow"); + } + } + } + private void CreateNameWithPermissionIcon(Client client, GUILayoutGroup paddedFrame) { GUITextBlock characterNameBlock; @@ -795,7 +849,7 @@ namespace Barotrauma GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter"); if (existingPreview != null) { infoFrameHolder.RemoveChild(existingPreview); } - GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), infoFrameHolder.RectTransform, Anchor.TopLeft, Pivot.TopRight) { RelativeOffset = new Vector2(-0.145f, 0) }) + GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.69f), infoFrameHolder.RectTransform, Anchor.TopRight, Pivot.TopLeft) { RelativeOffset = new Vector2(-0.061f, 0) }) { UserData = "SelectedCharacter" }; @@ -810,28 +864,29 @@ namespace Barotrauma { GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character))); GameMain.Client.SelectCrewCharacter(character, preview); - CreateWalletFrame(background, character); + if (!character.IsBot && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) { CreateWalletFrame(background, character, mpCampaign); } } } else if (client != null) { GUIComponent preview = CreateClientInfoFrame(background, client, GetPermissionIcon(client)); GameMain.Client?.SelectCrewClient(client, preview); - if (client.Character != null) + if (client.Character != null && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) { - CreateWalletFrame(background, client.Character); + CreateWalletFrame(background, client.Character, mpCampaign); } } return true; } - private void CreateWalletFrame(GUIComponent parent, Character character) + private void CreateWalletFrame(GUIComponent parent, Character character, MultiPlayerCampaign campaign) { + if (campaign is null) { throw new ArgumentNullException(nameof(campaign), "Tried to create a wallet frame when campaign was null"); } if (character is null) { throw new ArgumentNullException(nameof(character), "Tried to create a wallet frame for a null character");} isTransferMenuOpen = false; transferMenuOpenState = 1f; - ImmutableArray salaryCrew = Mission.GetSalaryEligibleCrew().Where(c => c != character).ToImmutableArray(); + ImmutableHashSet salaryCrew = GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != character).ToImmutableHashSet(); Wallet targetWallet = character.Wallet; @@ -905,8 +960,8 @@ namespace Barotrauma GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedTransferMenuLayout.RectTransform), childAnchor: Anchor.Center); GUILayoutGroup centerButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 1f), buttonLayout.RectTransform), isHorizontal: true); - GUIButton confirmButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), centerButtonLayout.RectTransform), TextManager.Get("confirm"), style: "GUIButtonFreeScale") { Enabled = false }; GUIButton resetButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), centerButtonLayout.RectTransform), TextManager.Get("reset"), style: "GUIButtonFreeScale") { Enabled = false }; + GUIButton confirmButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), centerButtonLayout.RectTransform), TextManager.Get("confirm"), style: "GUIButtonFreeScale") { Enabled = false }; // @formatter:on ImmutableArray layoutGroups = ImmutableArray.Create(transferMenuLayout, paddedTransferMenuLayout, mainLayout, leftLayout, rightLayout); MedicalClinicUI.EnsureTextDoesntOverflow(character.Name, leftName, leftLayout.Rect, layoutGroups); @@ -927,137 +982,146 @@ namespace Barotrauma ToggleTransferMenuIcon(transferMenuButton, open: isTransferMenuOpen); ToggleCenterButton(centerButton, isSending); - if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign) + + if (!(Character.Controlled is { } myCharacter)) { - if (!(Character.Controlled is { } myCharacter)) - { - salarySlider.Enabled = false; - transferAmountInput.Enabled = false; - centerButton.Enabled = false; - confirmButton.Enabled = false; - return; - } + salarySlider.Enabled = false; + transferAmountInput.Enabled = false; + centerButton.Enabled = false; + confirmButton.Enabled = false; + return; + } - bool hasPermissions = campaign.AllowedToManageCampaign(); - salarySlider.Enabled = hasPermissions; - Wallet otherWallet; + bool hasMoneyPermissions = campaign.AllowedToManageCampaign(ClientPermissions.ManageMoney); + salarySlider.Enabled = hasMoneyPermissions; + Wallet otherWallet; - switch (hasPermissions) - { - case true: - rightName.Text = TextManager.Get("crewwallet.bank"); - otherWallet = campaign.Bank; - break; - case false when character == myCharacter: - rightName.Text = TextManager.Get("crewwallet.bank"); - otherWallet = campaign.Bank; - isSending = true; - ToggleCenterButton(centerButton, isSending); - break; - default: - rightName.Text = myCharacter.Name; - otherWallet = campaign.PersonalWallet; - break; - } + switch (hasMoneyPermissions) + { + case true: + rightName.Text = TextManager.Get("crewwallet.bank"); + otherWallet = campaign.Bank; + break; + case false when character == myCharacter: + rightName.Text = TextManager.Get("crewwallet.bank"); + otherWallet = campaign.Bank; + isSending = true; + ToggleCenterButton(centerButton, isSending); + break; + default: + rightName.Text = myCharacter.Name; + otherWallet = campaign.PersonalWallet; + break; + } - MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups); - - if (!hasPermissions) + MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups); + updateButtonText(); + if (!hasMoneyPermissions) + { + if (character != Character.Controlled) { centerButton.Enabled = centerButton.CanBeFocused = false; - salarySlider.Enabled = salarySlider.CanBeFocused = false; } + salarySlider.Enabled = salarySlider.CanBeFocused = false; + } - leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); + leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); + UpdateAllInputs(); + + centerButton.OnClicked = (btn, o) => + { + isSending = !isSending; + updateButtonText(); + ToggleCenterButton(btn, isSending); UpdateAllInputs(); + return true; + }; - centerButton.OnClicked = (btn, o) => + void updateButtonText() + { + confirmButton.Text = TextManager.Get(hasMoneyPermissions || isSending ? "confirm" : "crewwallet.requestmoney"); + } + + transferAmountInput.OnValueChanged = input => + { + UpdateInputs(); + }; + + transferAmountInput.OnValueEntered = input => + { + UpdateAllInputs(); + }; + + Identifier eventIdentifier = nameof(CreateWalletFrame).ToIdentifier(); + campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e => + { + if (e.Wallet == targetWallet) { - isSending = !isSending; - ToggleCenterButton(btn, isSending); - UpdateAllInputs(); - return true; - }; - - transferAmountInput.OnValueChanged = input => - { - UpdateInputs(); - }; - - transferAmountInput.OnValueEntered = input => - { - UpdateAllInputs(); - }; - - campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(CreateWalletFrame).ToIdentifier(), e => - { - if (e.Wallet == targetWallet) - { - moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance); - salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f; - } - UpdateAllInputs(); - }); - - resetButton.OnClicked = (button, o) => - { - transferAmountInput.IntValue = 0; - UpdateAllInputs(); - return true; - }; - - confirmButton.OnClicked = (button, o) => - { - int amount = transferAmountInput.IntValue; - if (amount == 0) { return false; } - - Option target1 = Option.Some(character), - target2 = otherWallet == campaign.Bank ? Option.None() : Option.Some(myCharacter); - if (isSending) { (target1, target2) = (target2, target1); } - - SendTransaction(target1, target2, amount); - isTransferMenuOpen = false; - ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen); - return true; - }; - - void UpdateAllInputs() - { - UpdateInputs(); - UpdateMaxInput(); + moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance); + salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f; } + UpdateAllInputs(); + }); + registeredEvents.Add(eventIdentifier); - void UpdateInputs() - { - confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0; - if (transferAmountInput.IntValue == 0) - { - rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); - rightBalance.TextColor = GUIStyle.TextColorNormal; - leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance); - leftBalance.TextColor = GUIStyle.TextColorNormal; - } - else if (isSending) - { - rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue); - rightBalance.TextColor = GUIStyle.Blue; - leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue); - leftBalance.TextColor = GUIStyle.Red; - } - else - { - rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue); - rightBalance.TextColor = GUIStyle.Red; - leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue); - leftBalance.TextColor = GUIStyle.Blue; - } - } + resetButton.OnClicked = (button, o) => + { + transferAmountInput.IntValue = 0; + UpdateAllInputs(); + return true; + }; - void UpdateMaxInput() + confirmButton.OnClicked = (button, o) => + { + int amount = transferAmountInput.IntValue; + if (amount == 0) { return false; } + + Option target1 = Option.Some(character), + target2 = otherWallet == campaign.Bank ? Option.None() : Option.Some(myCharacter); + if (isSending) { (target1, target2) = (target2, target1); } + + SendTransaction(target1, target2, amount); + isTransferMenuOpen = false; + ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen); + return true; + }; + + void UpdateAllInputs() + { + UpdateInputs(); + UpdateMaxInput(); + } + + void UpdateInputs() + { + confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0; + if (transferAmountInput.IntValue == 0) { - transferAmountInput.MaxValueInt = isSending ? targetWallet.Balance : otherWallet.Balance; + rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); + rightBalance.TextColor = GUIStyle.TextColorNormal; + leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance); + leftBalance.TextColor = GUIStyle.TextColorNormal; } + else if (isSending) + { + rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue); + rightBalance.TextColor = GUIStyle.Blue; + leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue); + leftBalance.TextColor = GUIStyle.Red; + } + else + { + rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue); + rightBalance.TextColor = GUIStyle.Red; + leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue); + leftBalance.TextColor = GUIStyle.Blue; + } + } + + void UpdateMaxInput() + { + transferAmountInput.MaxValueInt = isSending ? targetWallet.Balance : otherWallet.Balance; } static void ToggleTransferMenuIcon(GUIButton btn, bool open) @@ -1173,7 +1237,7 @@ namespace Barotrauma private void CreateMultiPlayerLogContent(GUIFrame crewFrame) { - var logContainer = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), crewFrame.RectTransform, Anchor.TopRight, Pivot.TopLeft) { RelativeOffset = new Vector2(-0.061f, 0) }); + var logContainer = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), infoFrameHolder.RectTransform, Anchor.TopLeft, Pivot.TopRight) { RelativeOffset = new Vector2(-0.145f, 0) }); var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.900f, 0.900f), logContainer.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0f, 0.0475f) }, style: null); var content = new GUILayoutGroup(new RectTransform(Vector2.One, innerFrame.RectTransform)) { @@ -1188,22 +1252,22 @@ namespace Barotrauma Spacing = (int)(5 * GUI.Scale) }; - foreach (Pair pair in storedMessages) + foreach ((string message, PlayerConnectionChangeType type) in storedMessages) { - AddLineToLog(pair.First, pair.Second); + AddLineToLog(message, type); } logList.BarScroll = 1f; } - private static readonly List> storedMessages = new List>(); + private static readonly List<(string message, PlayerConnectionChangeType type)> storedMessages = new List<(string message, PlayerConnectionChangeType type)>(); public static void StorePlayerConnectionChangeMessage(ChatMessage message) { if (!GameMain.GameSession?.IsRunning ?? true) { return; } string msg = ChatMessage.GetTimeStamp() + message.TextWithSender; - storedMessages.Add(new Pair(msg, message.ChangeType)); + storedMessages.Add((msg, message.ChangeType)); if (GameSession.IsTabMenuOpen && SelectedTab == InfoFrameTab.Crew) { @@ -2026,5 +2090,14 @@ namespace Barotrauma if (character != Character.Controlled) { return; } UpdateTalentInfo(); } + + public void OnClose() + { + if (!(GameMain.GameSession?.Campaign is { } campaign)) { return; } + foreach (Identifier identifier in registeredEvents) + { + campaign.OnMoneyChanged.TryDeregister(identifier); + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index a6a0f3a54..cbdf71c9f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -73,6 +73,8 @@ namespace Barotrauma private Point screenResolution; + private bool needsRefresh = true; + /// /// While set to true any call to will cause the buy button to be disabled and to not update the prices. /// This is to prevent us from buying another upgrade before the server has given us the new prices and causing potential syncing issues. @@ -102,13 +104,18 @@ namespace Barotrauma CreateUI(upgradeFrame); if (Campaign == null) { return; } - Campaign.UpgradeManager.OnUpgradesChanged += RefreshAll; - Campaign.CargoManager.OnPurchasedItemsChanged += RefreshAll; - Campaign.CargoManager.OnSoldItemsChanged += RefreshAll; - Campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(UpgradeStore).ToIdentifier(), e => { RefreshAll(); } ); + Campaign.UpgradeManager.OnUpgradesChanged += RequestRefresh; + Campaign.CargoManager.OnPurchasedItemsChanged += RequestRefresh; + Campaign.CargoManager.OnSoldItemsChanged += RequestRefresh; + Campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(UpgradeStore).ToIdentifier(), e => { RequestRefresh(); } ); } - public void RefreshAll() + public void RequestRefresh() + { + needsRefresh = true; + } + + private void RefreshAll() { switch (selectedUpgradeTab) { @@ -131,6 +138,7 @@ namespace Barotrauma } break; } + needsRefresh = false; } private void RefreshUpgradeList() @@ -1295,7 +1303,9 @@ namespace Barotrauma { if (Campaign == null) { return; } - if (!parent.Children.Any() || Submarine.MainSub != null && Submarine.MainSub != drawnSubmarine || GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y) + if (!parent.Children.Any() || + Submarine.MainSub != null && Submarine.MainSub != drawnSubmarine || + GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y) { GameMain.GameSession?.SubmarineInfo?.CheckSubsLeftBehind(); drawnSubmarine = Submarine.MainSub; @@ -1313,6 +1323,10 @@ namespace Barotrauma // we also need this when we first load in so we know which category entries to disable since the CampaignUI is created before the submarine is loaded in. RefreshAll(); } + if (needsRefresh) + { + RefreshAll(); + } // accept an active confirmation popup if any if (PlayerInput.KeyHit(Keys.Enter) && GUIMessageBox.MessageBoxes.Any()) @@ -1588,7 +1602,7 @@ namespace Barotrauma if (button != null) { button.Enabled = currentLevel < prefab.MaxLevel; - if (WaitForServerUpdate || !campaign.AllowedToManageCampaign() || !campaign.Wallet.CanAfford(price)) + if (WaitForServerUpdate || !campaign.Wallet.CanAfford(price)) { button.Enabled = false; } @@ -1693,7 +1707,7 @@ namespace Barotrauma return frames.ToArray(); } - private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(); + private bool HasPermission => true; // just a shortcut to create new RectTransforms since all the new RectTransform and new Vector2 confuses my IDE (and me) private static RectTransform rectT(float x, float y, GUIComponent parentComponent, Anchor anchor = Anchor.TopLeft, ScaleBasis scaleBasis = ScaleBasis.Normal) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/VotingInterface.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/VotingInterface.cs index 4f5541602..a927e30a4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/VotingInterface.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/VotingInterface.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Barotrauma.Networking; using Microsoft.Xna.Framework; @@ -22,27 +23,50 @@ namespace Barotrauma private float votingTime = 100f; private float timer; private VoteType currentVoteType; - private Color submarineColor => GUIStyle.Orange; + private Color SubmarineColor => GUIStyle.Orange; private Point createdForResolution; - public VotingInterface(Client starter, SubmarineInfo info, VoteType type, float votingTime) + public static VotingInterface CreateSubmarineVotingInterface(Client starter, SubmarineInfo info, VoteType type, float votingTime) { - if (starter == null || info == null) return; - SetSubmarineVotingText(starter, info, type); - this.votingTime = votingTime; - getYesVotes = SubmarineYesVotes; - getNoVotes = SubmarineNoVotes; - getMaxVotes = SubmarineMaxVotes; - onVoteEnd = () => SendSubmarineVoteEndMessage(info, type); + if (starter == null || info == null) { return null; } - Initialize(starter, type); + var subVoting = new VotingInterface() + { + votingTime = votingTime, + getYesVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountYes(type) ?? 0, + getNoVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountNo(type) ?? 0, + getMaxVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountMax(type) ?? 0, + }; + subVoting.onVoteEnd = () => subVoting.SendSubmarineVoteEndMessage(info, type); + subVoting.SetSubmarineVotingText(starter, info, type); + subVoting.Initialize(starter, type); + return subVoting; } + public static VotingInterface CreateMoneyTransferVotingInterface(Client starter, Client from, Client to, int amount, float votingTime) + { + if (starter == null) { return null; } + if (from == null && to == null) { return null; } + + var transferVoting = new VotingInterface() + { + votingTime = votingTime, + getYesVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountYes(VoteType.TransferMoney) ?? 0, + getNoVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountNo(VoteType.TransferMoney) ?? 0, + getMaxVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountMax(VoteType.TransferMoney) ?? 0, + }; + transferVoting.onVoteEnd = () => transferVoting.SendMoneyTransferVoteEndMessage(from, to, amount); + transferVoting.SetMoneyTransferVotingText(starter, from, to, amount); + transferVoting.Initialize(starter, VoteType.TransferMoney); + return transferVoting; + } + + private void Initialize(Client starter, VoteType type) { currentVoteType = type; CreateVotingGUI(); - if (starter.ID == GameMain.Client.ID) SetGUIToVotedState(2); + if (starter.ID == GameMain.Client.ID) { SetGUIToVotedState(2); } VoteRunning = true; } @@ -50,7 +74,7 @@ namespace Barotrauma { createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); - if (frame != null) frame.Parent.RemoveChild(frame); + frame?.Parent.RemoveChild(frame); frame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.VotingArea, GameMain.Client.InGameHUD.RectTransform), style: ""); int padding = HUDLayoutSettings.Padding * 2; @@ -116,8 +140,8 @@ namespace Barotrauma public void Update(float deltaTime) { - if (!VoteRunning) return; - if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) CreateVotingGUI(); + if (!VoteRunning) { return; } + if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) { CreateVotingGUI(); } yesVotes = getYesVotes(); noVotes = getNoVotes(); maxVotes = getMaxVotes(); @@ -126,7 +150,6 @@ namespace Barotrauma votingTimer.BarSize = timer / votingTime; } - public void EndVote(bool passed, int yesVoteFinal, int noVoteFinal) { VoteRunning = false; @@ -143,19 +166,20 @@ namespace Barotrauma JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab; Color nameColor = prefab != null ? prefab.UIColor : Color.White; string characterRichString = $"‖color:{nameColor.R},{nameColor.G},{nameColor.B}‖{name}‖color:end‖"; - string submarineRichString = $"‖color:{submarineColor.R},{submarineColor.G},{submarineColor.B}‖{info.DisplayName}‖color:end‖"; + string submarineRichString = $"‖color:{SubmarineColor.R},{SubmarineColor.G},{SubmarineColor.B}‖{info.DisplayName}‖color:end‖"; + LocalizedString text = string.Empty; switch (type) { case VoteType.PurchaseAndSwitchSub: - votingOnText = TextManager.GetWithVariables("submarinepurchaseandswitchvote", + text = TextManager.GetWithVariables("submarinepurchaseandswitchvote", ("[playername]", characterRichString), ("[submarinename]", submarineRichString), ("[amount]", info.Price.ToString()), ("[currencyname]", TextManager.Get("credit").ToLower())); break; case VoteType.PurchaseSub: - votingOnText = TextManager.GetWithVariables("submarinepurchasevote", + text = TextManager.GetWithVariables("submarinepurchasevote", ("[playername]", characterRichString), ("[submarinename]", submarineRichString), ("[amount]", info.Price.ToString()), @@ -163,10 +187,9 @@ namespace Barotrauma break; case VoteType.SwitchSub: int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); - if (deliveryFee > 0) { - votingOnText = TextManager.GetWithVariables("submarineswitchfeevote", + text = TextManager.GetWithVariables("submarineswitchfeevote", ("[playername]", characterRichString), ("[submarinename]", submarineRichString), ("[locationname]", endLocation.Name), @@ -175,37 +198,22 @@ namespace Barotrauma } else { - votingOnText = TextManager.GetWithVariables("submarineswitchnofeevote", + text = TextManager.GetWithVariables("submarineswitchnofeevote", ("[playername]", characterRichString), ("[submarinename]", submarineRichString)); } break; } - votingOnText = RichString.Rich(votingOnText); - } - - private int SubmarineYesVotes() - { - return GameMain.NetworkMember.SubmarineVoteYesCount; - } - - private int SubmarineNoVotes() - { - return GameMain.NetworkMember.SubmarineVoteNoCount; - } - - private int SubmarineMaxVotes() - { - return GameMain.NetworkMember.SubmarineVoteMax; + votingOnText = RichString.Rich(text); } private void SendSubmarineVoteEndMessage(SubmarineInfo info, VoteType type) { - GameMain.NetworkMember.AddChatMessage(GetSubmarineVoteResultMessage(info, type, yesVotes.ToString(), noVotes.ToString(), votePassed).Value, ChatMessageType.Server); + GameMain.NetworkMember.AddChatMessage(GetSubmarineVoteResultMessage(info, type, yesVotes, noVotes, votePassed).Value, ChatMessageType.Server); } - public static LocalizedString GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, string yesVoteString, string noVoteString, bool votePassed) + private LocalizedString GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, int yesVoteCount, int noVoteCount, bool votePassed) { LocalizedString result = string.Empty; @@ -214,18 +222,18 @@ namespace Barotrauma case VoteType.PurchaseAndSwitchSub: result = TextManager.GetWithVariables(votePassed ? "submarinepurchaseandswitchvotepassed" : "submarinepurchaseandswitchvotefailed", ("[submarinename]", info.DisplayName), - ("[amount]", info.Price.ToString()), + ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", info.Price)), ("[currencyname]", TextManager.Get("credit").ToLower()), - ("[yesvotecount]", yesVoteString), - ("[novotecount]" , noVoteString)); + ("[yesvotecount]", yesVoteCount.ToString()), + ("[novotecount]" , noVoteCount.ToString())); break; case VoteType.PurchaseSub: result = TextManager.GetWithVariables(votePassed ? "submarinepurchasevotepassed" : "submarinepurchasevotefailed", ("[submarinename]", info.DisplayName), - ("[amount]", info.Price.ToString()), + ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", info.Price)), ("[currencyname]", TextManager.Get("credit").ToLower()), - ("[yesvotecount]", yesVoteString), - ("[novotecount]", noVoteString)); + ("[yesvotecount]", yesVoteCount.ToString()), + ("[novotecount]", noVoteCount.ToString())); break; case VoteType.SwitchSub: int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); @@ -235,17 +243,17 @@ namespace Barotrauma result = TextManager.GetWithVariables(votePassed ? "submarineswitchfeevotepassed" : "submarineswitchfeevotefailed", ("[submarinename]", info.DisplayName), ("[locationname]", endLocation.Name), - ("[amount]", deliveryFee.ToString()), + ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", deliveryFee)), ("[currencyname]", TextManager.Get("credit").ToLower()), - ("[yesvotecount]", yesVoteString), - ("[novotecount]", noVoteString)); + ("[yesvotecount]", yesVoteCount.ToString()), + ("[novotecount]", noVoteCount.ToString())); } else { result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed", ("[submarinename]", info.DisplayName), - ("[yesvotecount]", yesVoteString), - ("[novotecount]", noVoteString)); + ("[yesvotecount]", yesVoteCount.ToString()), + ("[novotecount]", noVoteCount.ToString())); } break; default: @@ -255,6 +263,58 @@ namespace Barotrauma } #endregion + + private void SetMoneyTransferVotingText(Client starter, Client from, Client to, int amount) + { + string name = starter.Name; + JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab; + Color nameColor = prefab != null ? prefab.UIColor : Color.White; + string characterRichString = $"‖color:{nameColor.R},{nameColor.G},{nameColor.B}‖{name}‖color:end‖"; + + LocalizedString text = string.Empty; + if (from == null && to != null) + { + text = TextManager.GetWithVariables("crewwallet.requestbanktoselfvote", + ("[requester]", characterRichString), + ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", amount))); + } + else if (from != null && to == null) + { + text = TextManager.GetWithVariables("crewwallet.requestselftobankvote", + ("[requester]", characterRichString), + ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", amount))); + } + else + { + //not supported atm: clients can only requests transfers between their own wallet and the bank + LocalizedString bankName = TextManager.Get("crewwallet.bank"); + text = TextManager.GetWithVariables("crewwallet.requesttransfervote", + ("[requester]", characterRichString), + ("[player1]", from?.Character == null ? bankName : from.Character.Name), + ("[player2]", to?.Character == null ? bankName : to.Character.Name), + ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", amount))); + } + + votingOnText = RichString.Rich(text); + } + private void SendMoneyTransferVoteEndMessage(Client from, Client to, int amount) + { + GameMain.NetworkMember.AddChatMessage(GetMoneyTransferVoteResultMessage(from, to, amount, yesVotes, noVotes, votePassed).Value, ChatMessageType.Server); + } + + public static LocalizedString GetMoneyTransferVoteResultMessage(Client from, Client to, int transferAmount, int yesVoteCount, int noVoteCount, bool votePassed) + { + LocalizedString result = string.Empty; + if (from != null) + { + result = TextManager.GetWithVariables(votePassed ? "crewwallet.banktoplayer.votepassed" : "crewwallet.banktoplayer.votefailed", + ("[playername]", from.Name), + ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", transferAmount)), + ("[yesvotecount]", yesVoteCount.ToString()), + ("[novotecount]", noVoteCount.ToString())); + } + return result; + } public void Remove() { if (frame != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index e4c8ab417..a046c051f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -17,6 +17,7 @@ using Barotrauma.Tutorials; using Barotrauma.Media; using Barotrauma.Extensions; using System.Threading.Tasks; +using Barotrauma.Transition; namespace Barotrauma { @@ -463,6 +464,7 @@ namespace Barotrauma yield return CoroutineStatus.Running; + UgcTransition.Prepare(); var contentPackageLoadRoutine = ContentPackageManager.Init(); foreach (var progress in contentPackageLoadRoutine) { @@ -691,14 +693,12 @@ namespace Barotrauma } else if (GameSettings.CurrentConfig.AutomaticCampaignLoadEnabled) { - IEnumerable saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer); - + var saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer); if (saveFiles.Count() > 0) { - saveFiles = saveFiles.OrderBy(file => File.GetLastWriteTime(file)); try { - SaveUtil.LoadGame(saveFiles.Last()); + SaveUtil.LoadGame(saveFiles.OrderBy(file => file.SaveTime).Last().FilePath); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs index 29cea3e9b..aa1b4528a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs @@ -67,6 +67,8 @@ namespace Barotrauma public void SetSoldItems(Dictionary> items) { + if (SoldItems.Count == 0 && items.Count == 0) { return; } + SoldItems.Clear(); foreach (var entry in items) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs index 6d334c7a2..6b0927e40 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs @@ -86,30 +86,14 @@ namespace Barotrauma /// /// There is a server-side implementation of the method in /// - public bool AllowedToEndRound() - { - //allow ending the round if the client has permissions, is the owner, the only client in the server - //or if no-one has management permissions - if (GameMain.Client == null) { return true; } - return - GameMain.Client.HasPermission(ClientPermissions.ManageRound) || - GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || - GameMain.Client.ConnectedClients.Count == 1 || - GameMain.Client.IsServerOwner || - GameMain.Client.ConnectedClients.None(c => - c.InGame && (c.IsOwner || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign))); - } - - /// - /// There is a server-side implementation of the method in - /// - public bool AllowedToManageCampaign(ClientPermissions permissions = ClientPermissions.ManageCampaign) + public bool AllowedToManageCampaign(ClientPermissions permissions) { //allow managing the round if the client has permissions, is the owner, the only client in the server, //or if no-one has management permissions if (GameMain.Client == null) { return true; } return GameMain.Client.HasPermission(permissions) || + GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || GameMain.Client.ConnectedClients.Count == 1 || GameMain.Client.IsServerOwner || GameMain.Client.ConnectedClients.None(c => c.InGame && (c.IsOwner || c.HasPermission(permissions))); @@ -210,7 +194,7 @@ namespace Barotrauma if (endRoundButton.Visible) { - if (!AllowedToEndRound()) + if (!AllowedToManageCampaign(ClientPermissions.ManageMap)) { buttonText = TextManager.Get("map"); } @@ -306,7 +290,7 @@ namespace Barotrauma default: ShowCampaignUI = true; CampaignUI.SelectTab(npc.CampaignInteractionType, storeIdentifier: npc.MerchantIdentifier); - CampaignUI.UpgradeStore?.RefreshAll(); + CampaignUI.UpgradeStore?.RequestRefresh(); break; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index 2847da846..3c2dd322e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -42,7 +42,7 @@ namespace Barotrauma return PersonalWallet; } - public static void StartCampaignSetup(IEnumerable saveFiles) + public static void StartCampaignSetup(List saveFiles) { var parent = GameMain.NetLobbyScreen.CampaignSetupFrame; parent.ClearChildren(); @@ -746,7 +746,7 @@ namespace Barotrauma if (reputation.HasValue) { campaign.Map.CurrentLocation.Reputation.SetReputation(reputation.Value); - campaign?.CampaignUI?.UpgradeStore?.RefreshAll(); + campaign?.CampaignUI?.UpgradeStore?.RequestRefresh(); } foreach (var availableMission in availableMissions) @@ -786,7 +786,7 @@ namespace Barotrauma if (shouldRefresh) { - campaign?.CampaignUI?.UpgradeStore?.RefreshAll(); + campaign?.CampaignUI?.UpgradeStore?.RequestRefresh(); } if (myCharacterInfo != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index ffa578204..53ba428fb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -110,7 +110,7 @@ namespace Barotrauma petsElement = subElement; break; case Wallet.LowerCaseSaveElementName: - Bank = new Wallet(subElement); + Bank = new Wallet(Option.None(), subElement); break; case "stats": LoadStats(subElement); @@ -129,7 +129,7 @@ namespace Barotrauma int oldMoney = element.GetAttributeInt("money", 0); if (oldMoney > 0) { - Bank = new Wallet + Bank = new Wallet(Option.None()) { Balance = oldMoney }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs index 0f794fa40..627f79236 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs @@ -32,6 +32,7 @@ namespace Barotrauma } else { + tabMenu?.OnClose(); tabMenu = null; NetLobbyScreen.JobInfoFrame = null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs index abe401df3..06e09c661 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs @@ -318,7 +318,7 @@ namespace Barotrauma new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub))); if (GameMain.IsMultiplayer && Character.Controlled is { } controlled) { - var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, Mission.GetSalaryEligibleCrew().Where(c => c != controlled), Option.Some(reward)); + var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option.Some(reward)); if (share > 0) { string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index 02ad190c4..a189f5470 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -38,11 +38,14 @@ namespace Barotrauma.Items.Components public readonly bool Loop; - public ItemSound(RoundSound sound, ActionType type, bool loop = false) + public readonly bool OnlyPlayInSameSub; + + public ItemSound(RoundSound sound, ActionType type, bool loop = false, bool onlyPlayInSameSub = false) { this.RoundSound = sound; this.Type = type; this.Loop = loop; + this.OnlyPlayInSameSub = onlyPlayInSameSub; } } @@ -339,6 +342,11 @@ namespace Barotrauma.Items.Components return; } + if (itemSound.OnlyPlayInSameSub && item.Submarine != null && Character.Controlled != null) + { + if (Character.Controlled.Submarine == null || !Character.Controlled.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return; } + } + if (itemSound.Loop) { if (loopingSoundChannel != null && loopingSoundChannel.Sound != itemSound.RoundSound.Sound) @@ -500,7 +508,9 @@ namespace Barotrauma.Items.Components RoundSound sound = RoundSound.Load(subElement); if (sound == null) { break; } - ItemSound itemSound = new ItemSound(sound, type, subElement.GetAttributeBool("loop", false)) + ItemSound itemSound = new ItemSound(sound, type, + subElement.GetAttributeBool("loop", false), + subElement.GetAttributeBool("onlyinsamesub", false)) { VolumeProperty = subElement.GetAttributeIdentifier("volumeproperty", "") }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 433243ee3..b5e745407 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -50,6 +50,9 @@ namespace Barotrauma.Items.Components [Serialize("FabricatorCreate", IsPropertySaveable.Yes)] public string CreateButtonText { get; set; } + [Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)] + public string FabricationLimitReachedText { get; set; } + partial void InitProjSpecific() { //CreateGUI(); @@ -195,7 +198,7 @@ namespace Barotrauma.Items.Components foreach (FabricationRecipe fi in fabricationRecipes.Values) { - var frame = new GUIFrame(new RectTransform(new Point(itemList.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null) + var frame = new GUIFrame(new RectTransform(new Point(itemList.Content.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null) { UserData = fi, HoverColor = Color.Gold * 0.2f, @@ -223,6 +226,13 @@ namespace Barotrauma.Items.Components AutoScaleVertical = true, ToolTip = fi.TargetItem.Description }; + + new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), frame.RectTransform, Anchor.BottomRight), + TextManager.Get(FabricationLimitReachedText), font: GUIStyle.SmallFont, textAlignment: Alignment.BottomRight) + { + UserData = nameof(FabricationLimitReachedText), + Visible = false + }; } } @@ -297,7 +307,8 @@ namespace Barotrauma.Items.Components } else { - sufficientSkillsText.Visible = false; + sufficientSkillsText.Visible = insufficientSkillsText.Visible = false; + sufficientSkillsText.Enabled = insufficientSkillsText.Enabled = false; } var requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), @@ -493,14 +504,15 @@ namespace Barotrauma.Items.Components if (string.IsNullOrWhiteSpace(filter)) { itemList.Content.Children.ForEach(c => c.Visible = true); - return true; } - - foreach (GUIComponent child in itemList.Content.Children) + else { - FabricationRecipe recipe = child.UserData as FabricationRecipe; - if (recipe?.DisplayName == null) { continue; } - child.Visible = recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase); + foreach (GUIComponent child in itemList.Content.Children) + { + FabricationRecipe recipe = child.UserData as FabricationRecipe; + if (recipe?.DisplayName == null) { continue; } + child.Visible = recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase); + } } HideEmptyItemListCategories(); @@ -516,7 +528,10 @@ namespace Barotrauma.Items.Components { if (!(child.UserData is FabricationRecipe recipe)) { - child.Visible = recipeVisible; + if (child.Enabled) + { + child.Visible = recipeVisible; + } recipeVisible = false; } else @@ -719,24 +734,26 @@ namespace Barotrauma.Items.Components { foreach (GUIComponent child in itemList.Content.Children) { - if (!(child.UserData is FabricationRecipe itemPrefab)) { continue; } + if (!(child.UserData is FabricationRecipe recipe)) { continue; } - if (itemPrefab != selectedItem && + if (recipe != selectedItem && (child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y)) { continue; } - bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients, character); - if (itemPrefab == selectedItem) + bool canBeFabricated = CanBeFabricated(recipe, availableIngredients, character); + if (recipe == selectedItem) { activateButton.Enabled = canBeFabricated; } var childContainer = child.GetChild(); - childContainer.GetChild().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f); - childContainer.GetChild().Color = itemPrefab.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f); + childContainer.GetChild().Color = recipe.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f); + + var limitReachedText = child.FindChild(nameof(FabricationLimitReachedText)); + limitReachedText.Visible = !canBeFabricated && fabricationLimits.TryGetValue(recipe.RecipeHash, out int amount) && amount <= 0; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index a1ea10952..87289c772 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -396,7 +396,7 @@ namespace Barotrauma.Items.Components private bool VisibleOnItemFinder(Item it) { - if (it.Submarine != item.Submarine) { return false; } + if (!item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; } if (it.NonInteractable || it.HiddenInGame) { return false; } if (it.GetComponent() == null) { return false; } @@ -432,10 +432,10 @@ namespace Barotrauma.Items.Components scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center)); miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false }; - ImmutableHashSet hullPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent() != null || it.GetComponent() != null)).ToImmutableHashSet(); + ImmutableHashSet hullPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent() != null || it.GetComponent() != null)).ToImmutableHashSet(); miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents); - IEnumerable electrialPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent() != null); + IEnumerable electrialPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.GetComponent() != null); electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), electrialPointsOfInterest, out electricalMapComponents); Dictionary electricChildren = new Dictionary(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs index 0b68a66ed..4f0dfbe2c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs @@ -412,7 +412,7 @@ namespace Barotrauma new GUIMessageBox(string.Empty, TextManager.Get("LockedPathTooltip")); } //clients aren't allowed to select the location without a permission - else if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign() ?? false) + else if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap) ?? false) { connectionHighlightState = 0.0f; SelectedConnection = connection; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 559fe77b3..1d3c335a7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -271,6 +271,7 @@ namespace Barotrauma.Networking otherClients = new List(); serverSettings = new ServerSettings(this, "Server", 0, 0, 0, false, false); + Voting = new Voting(); if (steamId == 0) { @@ -637,7 +638,7 @@ namespace Barotrauma.Networking if (gameStarted && Screen.Selected == GameMain.GameScreen) { - EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode); + EndVoteTickBox.Visible = ServerSettings.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode); respawnManager?.Update(deltaTime); @@ -898,13 +899,13 @@ namespace Barotrauma.Networking GUI.SetSavingIndicatorState(save); break; case ServerPacketHeader.CAMPAIGN_SETUP_INFO: - UInt16 saveCount = inc.ReadUInt16(); - List saveFiles = new List(); + byte saveCount = inc.ReadByte(); + List saveInfos = new List(); for (int i = 0; i < saveCount; i++) { - saveFiles.Add(inc.ReadString()); + saveInfos.Add(INetSerializableStruct.Read(inc)); } - MultiPlayerCampaign.StartCampaignSetup(saveFiles); + MultiPlayerCampaign.StartCampaignSetup(saveInfos); break; case ServerPacketHeader.PERMISSIONS: ReadPermissions(inc); @@ -1458,7 +1459,7 @@ namespace Barotrauma.Networking { if (GameMain.GameSession?.GameMode is CampaignMode campaign) { - campaign.CampaignUI?.UpgradeStore?.RefreshAll(); + campaign.CampaignUI?.UpgradeStore?.RequestRefresh(); campaign.CampaignUI?.CrewManagement?.RefreshPermissions(); } } @@ -1666,10 +1667,7 @@ namespace Barotrauma.Networking isOutpost = levelData.Type == LevelData.LevelType.Outpost; } - if (GameMain.Client?.ServerSettings?.Voting != null) - { - GameMain.Client.ServerSettings.Voting.ResetVotes(GameMain.Client.ConnectedClients); - } + Voting?.ResetVotes(GameMain.Client.ConnectedClients); if (loadTask != null) { @@ -1882,7 +1880,7 @@ namespace Barotrauma.Networking var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash); if (matchingSub == null) { - matchingSub = new SubmarineInfo(Path.Combine(SubmarineInfo.SavePath, subName) + ".sub", subHash, tryLoad: false) + matchingSub = new SubmarineInfo(Path.Combine(SaveUtil.SubmarineDownloadFolder, subName) + ".sub", subHash, tryLoad: false) { SubmarineClass = (SubmarineClass)subClass }; @@ -2086,7 +2084,7 @@ namespace Barotrauma.Networking { if (GameMain.GameSession?.GameMode is CampaignMode campaign) { - campaign.CampaignUI?.UpgradeStore?.RefreshAll(); + campaign.CampaignUI?.UpgradeStore?.RequestRefresh(); campaign.CampaignUI?.CrewManagement?.RefreshPermissions(); } } @@ -2208,8 +2206,8 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer); serverSettings.VoiceChatEnabled = voiceChatEnabled; - serverSettings.Voting.AllowSubVoting = allowSubVoting; - serverSettings.Voting.AllowModeVoting = allowModeVoting; + serverSettings.AllowSubVoting = allowSubVoting; + serverSettings.AllowModeVoting = allowModeVoting; if (clientPeer is SteamP2POwnerPeer) { @@ -2240,7 +2238,7 @@ namespace Barotrauma.Networking ChatMessage.ClientRead(inc); break; case ServerNetObject.VOTE: - serverSettings.Voting.ClientRead(inc); + Voting.ClientRead(inc); break; } } @@ -2826,12 +2824,12 @@ namespace Barotrauma.Networking public void Vote(VoteType voteType, object data) { - if (clientPeer == null) return; + if (clientPeer == null) { return; } IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY); msg.Write((byte)ClientNetObject.VOTE); - serverSettings.Voting.ClientWrite(msg, voteType, data); + Voting.ClientWrite(msg, voteType, data); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); clientPeer.Send(msg, DeliveryMethod.Reliable); @@ -2847,19 +2845,27 @@ namespace Barotrauma.Networking #region Submarine Change Voting public void InitiateSubmarineChange(SubmarineInfo sub, VoteType voteType) { - if (sub == null) return; - if (serverSettings.Voting.VoteRunning) - { - new GUIMessageBox(TextManager.Get("unabletoinitiateavoteheader"), TextManager.Get("votealreadyactivetext")); - return; - } + if (sub == null) { return; } Vote(voteType, sub); } public void ShowSubmarineChangeVoteInterface(Client starter, SubmarineInfo info, VoteType type, float timeOut) { - if (info == null || votingInterface != null) return; - votingInterface = new VotingInterface(starter, info, type, timeOut); + if (info == null || votingInterface != null) { return; } + votingInterface = VotingInterface.CreateSubmarineVotingInterface(starter, info, type, timeOut); + } + #endregion + + #region Money Transfer Voting + public void ShowMoneyTransferVoteInterface(Client starter, Client from, int amount, Client to, float timeOut) + { + if (votingInterface != null) { return; } + if (from == null && to == null) + { + DebugConsole.ThrowError("Tried to initiate a vote for transferring from null to null!"); + return; + } + votingInterface = VotingInterface.CreateMoneyTransferVotingInterface(starter, from, to, amount, timeOut); } #endregion @@ -3103,7 +3109,7 @@ namespace Barotrauma.Networking { if (!gameStarted) return false; - if (!serverSettings.Voting.AllowEndVoting || !HasSpawned) + if (!serverSettings.AllowEndVoting || !HasSpawned) { tickBox.Visible = false; return false; @@ -3322,15 +3328,17 @@ namespace Barotrauma.Networking inGameHUD.DrawManually(spriteBatch); - if (EndVoteCount > 0) + int endVoteCount = Voting.GetVoteCountYes(VoteType.EndRound); + int endVoteMax = Voting.GetVoteCountMax(VoteType.EndRound); + if (endVoteCount > 0) { if (EndVoteTickBox.Visible) { - EndVoteTickBox.Text = $"{endRoundVoteText} {EndVoteCount}/{EndVoteMax}"; + EndVoteTickBox.Text = $"{endRoundVoteText} {endVoteCount}/{endVoteMax}"; } else { - LocalizedString endVoteText = TextManager.GetWithVariables("EndRoundVotes", ("[votes]", EndVoteCount.ToString()), ("[max]", EndVoteMax.ToString())); + LocalizedString endVoteText = TextManager.GetWithVariables("EndRoundVotes", ("[votes]", endVoteCount.ToString()), ("[max]", endVoteMax.ToString())); GUI.DrawString(spriteBatch, EndVoteTickBox.Rect.Center.ToVector2() - GUIStyle.SmallFont.MeasureString(endVoteText) / 2, endVoteText.Value, Color.White, @@ -3498,7 +3506,7 @@ namespace Barotrauma.Networking OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.KickPlayer(client); return false; } }; } - else if (serverSettings.Voting.AllowVoteKick && client.AllowKicking) + else if (serverSettings.AllowVoteKick && client.AllowKicking) { var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.9f), buttonContainer.RectTransform), TextManager.Get("VoteToKick"), style: "GUIButtonSmall") diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs index 23458baca..e088e3b15 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs @@ -2,56 +2,42 @@ using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; -using Barotrauma.Extensions; namespace Barotrauma { partial class Voting { - public bool AllowSubVoting - { - get { return allowSubVoting; } - set - { - if (value == allowSubVoting) return; - allowSubVoting = value; - GameMain.NetLobbyScreen.SubList.Enabled = value || - (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub)); - var subVotesLabel = GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true) as GUITextBlock; - subVotesLabel.Visible = value; - var subVisButton = GameMain.NetLobbyScreen.SubVisibilityButton; - subVisButton.RectTransform.AbsoluteOffset - = new Point(value ? (int)(subVotesLabel.TextSize.X + subVisButton.Rect.Width) : 0, 0); + private readonly Dictionary + voteCountYes = new Dictionary(), + voteCountNo = new Dictionary(), + voteCountMax = new Dictionary(); - UpdateVoteTexts(null, VoteType.Sub); - GameMain.NetLobbyScreen.SubList.Deselect(); - } + public int GetVoteCountYes(VoteType voteType) + { + voteCountYes.TryGetValue(voteType, out int value); + return value; } - public bool AllowModeVoting + public int GetVoteCountNo(VoteType voteType) { - get { return allowModeVoting; } - set - { - if (value == allowModeVoting) return; - allowModeVoting = value; - GameMain.NetLobbyScreen.ModeList.Enabled = - value || - (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectMode)); - - GameMain.NetLobbyScreen.Frame.FindChild("modevotes", true).Visible = value; - - // Disable modes that cannot be voted on - foreach (var guiComponent in GameMain.NetLobbyScreen.ModeList.Content.Children) - { - if (guiComponent is GUIFrame frame) - { - frame.CanBeFocused = !allowModeVoting || ((GameModePreset) frame.UserData).Votable; - } - } - - UpdateVoteTexts(null, VoteType.Mode); - GameMain.NetLobbyScreen.ModeList.Deselect(); - } + voteCountNo.TryGetValue(voteType, out int value); + return value; + } + public int GetVoteCountMax(VoteType voteType) + { + voteCountMax.TryGetValue(voteType, out int value); + return value; + } + public void SetVoteCountYes(VoteType voteType, int value) + { + voteCountYes[voteType] = value; + } + public void SetVoteCountNo(VoteType voteType, int value) + { + voteCountNo[voteType] = value; + } + public void SetVoteCountMax(VoteType voteType, int value) + { + voteCountMax[voteType] = value; } public void UpdateVoteTexts(List clients, VoteType voteType) @@ -139,17 +125,15 @@ namespace Barotrauma msg.Write(votedClient.ID); break; case VoteType.StartRound: - if (!(data is bool)) return; + if (!(data is bool)) { return; } msg.Write((bool)data); break; - case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseSub: case VoteType.SwitchSub: - if (!VoteRunning) - { - SubmarineInfo voteSub = data as SubmarineInfo; - if (voteSub == null) return; + if (data is SubmarineInfo voteSub) + { + //initiate sub vote msg.Write(true); msg.Write(voteSub.Name); } @@ -159,7 +143,11 @@ namespace Barotrauma msg.Write(false); msg.Write((int)data); } - + break; + case VoteType.TransferMoney: + if (!(data is int)) { return; } + msg.Write(false); //not initiating a vote + msg.Write((int)data); break; } @@ -168,8 +156,8 @@ namespace Barotrauma public void ClientRead(IReadMessage inc) { - AllowSubVoting = inc.ReadBoolean(); - if (allowSubVoting) + GameMain.Client.ServerSettings.AllowSubVoting = inc.ReadBoolean(); + if (GameMain.Client.ServerSettings.AllowSubVoting) { UpdateVoteTexts(null, VoteType.Sub); int votableCount = inc.ReadByte(); @@ -186,8 +174,8 @@ namespace Barotrauma SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes); } } - AllowModeVoting = inc.ReadBoolean(); - if (allowModeVoting) + GameMain.Client.ServerSettings.AllowModeVoting = inc.ReadBoolean(); + if (GameMain.Client.ServerSettings.AllowModeVoting) { UpdateVoteTexts(null, VoteType.Mode); int votableCount = inc.ReadByte(); @@ -199,135 +187,136 @@ namespace Barotrauma SetVoteText(GameMain.NetLobbyScreen.ModeList, mode, votes); } } - AllowEndVoting = inc.ReadBoolean(); - if (AllowEndVoting) + GameMain.Client.ServerSettings.AllowEndVoting = inc.ReadBoolean(); + if (GameMain.Client.ServerSettings.AllowEndVoting) { - GameMain.NetworkMember.EndVoteCount = inc.ReadByte(); - GameMain.NetworkMember.EndVoteMax = inc.ReadByte(); + SetVoteCountYes(VoteType.EndRound, inc.ReadByte()); + SetVoteCountMax(VoteType.EndRound, inc.ReadByte()); } - AllowVoteKick = inc.ReadBoolean(); + GameMain.Client.ServerSettings.AllowVoteKick = inc.ReadBoolean(); - byte subVoteStateByte = inc.ReadByte(); - VoteState subVoteState = VoteState.None; - try - { - subVoteState = (VoteState)subVoteStateByte; - } + byte activeVoteStateByte = inc.ReadByte(); + + VoteState activeVoteState = VoteState.None; + try { activeVoteState = (VoteState)activeVoteStateByte; } catch (System.Exception e) { - DebugConsole.ThrowError("Failed to cast vote type \"" + subVoteStateByte + "\"", e); + DebugConsole.ThrowError("Failed to cast vote type \"" + activeVoteStateByte + "\"", e); } - if (subVoteState != VoteState.None) + if (activeVoteState != VoteState.None) { byte voteTypeByte = inc.ReadByte(); VoteType voteType = VoteType.Unknown; - - try - { - voteType = (VoteType)voteTypeByte; - } + try { voteType = (VoteType)voteTypeByte; } catch (System.Exception e) { DebugConsole.ThrowError("Failed to cast vote type \"" + voteTypeByte + "\"", e); } - if (voteType != VoteType.Unknown) + byte yesClientCount = inc.ReadByte(); + for (int i = 0; i < yesClientCount; i++) { - byte yesClientCount = inc.ReadByte(); - for (int i = 0; i < yesClientCount; i++) - { - byte clientID = inc.ReadByte(); - var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID); - matchingClient?.SetVote(voteType, 2); - } - - byte noClientCount = inc.ReadByte(); - for (int i = 0; i < noClientCount; i++) - { - byte clientID = inc.ReadByte(); - var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID); - matchingClient?.SetVote(voteType, 1); - } - - GameMain.NetworkMember.SubmarineVoteYesCount = yesClientCount; - GameMain.NetworkMember.SubmarineVoteNoCount = noClientCount; - GameMain.NetworkMember.SubmarineVoteMax = inc.ReadByte(); - - switch (subVoteState) - { - case VoteState.Started: - Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == GameMain.Client.ID); - if (!myClient.InGame) - { - VoteRunning = true; - return; - } - - string subName1 = inc.ReadString(); - SubmarineInfo info = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName1); - - if (info == null) - { - DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); - return; - } - - VoteRunning = true; - byte starterID = inc.ReadByte(); - Client starterClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == starterID); - float timeOut = inc.ReadByte(); - GameMain.Client.ShowSubmarineChangeVoteInterface(starterClient, info, voteType, timeOut); - break; - case VoteState.Running: - // Nothing specific - break; - case VoteState.Passed: - case VoteState.Failed: - VoteRunning = false; - - bool passed = inc.ReadBoolean(); - string subName2 = inc.ReadString(); - SubmarineInfo subInfo = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2); - - if (subInfo == null) - { - DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); - return; - } - - if (GameMain.Client.VotingInterface != null) - { - GameMain.Client.VotingInterface.EndVote(passed, yesClientCount, noClientCount); - } - else if (GameMain.Client.ConnectedClients.Count > 1) - { - GameMain.NetworkMember.AddChatMessage(VotingInterface.GetSubmarineVoteResultMessage(subInfo, voteType, yesClientCount.ToString(), noClientCount.ToString(), passed).Value, ChatMessageType.Server); - } - - if (passed) - { - int deliveryFee = inc.ReadInt16(); - switch (voteType) - { - case VoteType.PurchaseAndSwitchSub: - GameMain.GameSession.PurchaseSubmarine(subInfo); - GameMain.GameSession.SwitchSubmarine(subInfo, 0); - break; - case VoteType.PurchaseSub: - GameMain.GameSession.PurchaseSubmarine(subInfo); - break; - case VoteType.SwitchSub: - GameMain.GameSession.SwitchSubmarine(subInfo, deliveryFee); - break; - } - - SubmarineSelection.ContentRefreshRequired = true; - } - break; - } + byte clientID = inc.ReadByte(); + var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID); + matchingClient?.SetVote(voteType, 2); } - } + + byte noClientCount = inc.ReadByte(); + for (int i = 0; i < noClientCount; i++) + { + byte clientID = inc.ReadByte(); + var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID); + matchingClient?.SetVote(voteType, 1); + } + byte maxClientCount = inc.ReadByte(); + + SetVoteCountYes(voteType, yesClientCount); + SetVoteCountNo(voteType, noClientCount); + SetVoteCountMax(voteType, maxClientCount); + + switch (activeVoteState) + { + case VoteState.Started: + byte starterID = inc.ReadByte(); + Client starterClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == starterID); + float timeOut = inc.ReadByte(); + + Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == GameMain.Client.ID); + if (!myClient.InGame) { return; } + + switch (voteType) + { + case VoteType.PurchaseSub: + case VoteType.PurchaseAndSwitchSub: + case VoteType.SwitchSub: + string subName1 = inc.ReadString(); + SubmarineInfo info = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName1); + if (info == null) + { + DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); + return; + } + GameMain.Client.ShowSubmarineChangeVoteInterface(starterClient, info, voteType, timeOut); + break; + case VoteType.TransferMoney: + byte fromClientId = inc.ReadByte(); + byte toClientId = inc.ReadByte(); + int transferAmount = inc.ReadInt32(); + + Client fromClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == fromClientId); + Client toClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == toClientId); + GameMain.Client.ShowMoneyTransferVoteInterface(starterClient, fromClient, transferAmount, toClient, timeOut); + break; + } + break; + case VoteState.Running: + // Nothing specific + break; + case VoteState.Passed: + case VoteState.Failed: + bool passed = inc.ReadBoolean(); + + SubmarineInfo subInfo = null; + switch (voteType) + { + case VoteType.PurchaseSub: + case VoteType.PurchaseAndSwitchSub: + case VoteType.SwitchSub: + string subName2 = inc.ReadString(); + subInfo = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2); + if (subInfo == null) + { + DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); + return; + } + break; + } + + GameMain.Client.VotingInterface?.EndVote(passed, yesClientCount, noClientCount); + + if (passed && subInfo != null) + { + int deliveryFee = inc.ReadInt16(); + switch (voteType) + { + case VoteType.PurchaseAndSwitchSub: + GameMain.GameSession.PurchaseSubmarine(subInfo); + GameMain.GameSession.SwitchSubmarine(subInfo, 0); + break; + case VoteType.PurchaseSub: + GameMain.GameSession.PurchaseSubmarine(subInfo); + break; + case VoteType.SwitchSub: + GameMain.GameSession.SwitchSubmarine(subInfo, deliveryFee); + break; + } + + SubmarineSelection.ContentRefreshRequired = true; + } + break; + } + } GameMain.NetworkMember.ConnectedClients.ForEach(c => c.SetVote(VoteType.StartRound, false)); byte readyClientCount = inc.ReadByte(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs index ff0f4ffc2..c7cd94394 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs @@ -1,5 +1,8 @@ +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.IO; +using System.Linq; namespace Barotrauma { @@ -44,5 +47,60 @@ namespace Barotrauma this.newGameContainer = newGameContainer; this.loadGameContainer = loadGameContainer; } + + protected List prevSaveFiles; + protected GUIComponent CreateSaveElement(CampaignMode.SaveInfo saveInfo) + { + if (string.IsNullOrEmpty(saveInfo.FilePath)) + { + DebugConsole.AddWarning("Error when updating campaign load menu: path to a save file was empty.\n" + Environment.StackTrace); + return null; + } + + var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement") + { + UserData = saveInfo.FilePath + }; + + var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath)) + { + CanBeFocused = false + }; + + if (saveInfo.EnabledContentPackageNames != null && saveInfo.EnabledContentPackageNames.Any()) + { + if (!GameSession.IsCompatibleWithEnabledContentPackages(saveInfo.EnabledContentPackageNames, out LocalizedString errorMsg)) + { + nameText.TextColor = GUIStyle.Red; + saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning")); + } + } + + prevSaveFiles ??= new List(); + prevSaveFiles.Add(saveInfo); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), + text: saveInfo.SubmarineName, font: GUIStyle.SmallFont) + { + CanBeFocused = false, + UserData = saveInfo.FilePath + }; + + + string saveTimeStr = string.Empty; + if (saveInfo.SaveTime > 0) + { + DateTime time = ToolBox.Epoch.ToDateTime(saveInfo.SaveTime); + saveTimeStr = time.ToString(); + } + new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), + text: saveTimeStr, textAlignment: Alignment.Right, font: GUIStyle.SmallFont) + { + CanBeFocused = false, + UserData = saveInfo.FilePath + }; + + return saveFrame; + } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs index e93563192..97e08e561 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs @@ -12,7 +12,7 @@ namespace Barotrauma { private GUIButton deleteMpSaveButton; - public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable saveFiles = null) + public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, List saveFiles = null) : base(newGameContainer, loadGameContainer) { var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: false) @@ -219,8 +219,7 @@ namespace Barotrauma yield return CoroutineStatus.Success; } - private List prevSaveFiles; - public void UpdateLoadMenu(IEnumerable saveFiles = null) + public void UpdateLoadMenu(IEnumerable saveFiles = null) { prevSaveFiles?.Clear(); prevSaveFiles = null; @@ -242,73 +241,9 @@ namespace Barotrauma OnSelected = SelectSaveFile }; - foreach (string saveFile in saveFiles) + foreach (CampaignMode.SaveInfo saveInfo in saveFiles) { - if (string.IsNullOrEmpty(saveFile)) - { - DebugConsole.AddWarning("Error when updating campaign load menu: path to a save file was empty.\n" + Environment.StackTrace); - continue; - } - - string fileName = saveFile; - string subName = ""; - string saveTime = ""; - string contentPackageStr = ""; - var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement") - { - UserData = saveFile - }; - - var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), "") - { - CanBeFocused = false - }; - - bool isCompatible = true; - prevSaveFiles ??= new List(); - - prevSaveFiles?.Add(saveFile); - string[] splitSaveFile = saveFile.Split(';'); - saveFrame.UserData = splitSaveFile[0]; - fileName = Path.GetFileNameWithoutExtension(splitSaveFile[0]); - nameText.Text = fileName; - if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; } - if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; } - if (splitSaveFile.Length > 3) { contentPackageStr = splitSaveFile[3]; } - - if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime)) - { - DateTime time = ToolBox.Epoch.ToDateTime(unixTime); - saveTime = time.ToString(); - } - if (!string.IsNullOrEmpty(contentPackageStr)) - { - List contentPackagePaths = contentPackageStr.Split('|').ToList(); - if (!GameSession.IsCompatibleWithEnabledContentPackages(contentPackagePaths, out LocalizedString errorMsg)) - { - nameText.TextColor = GUIStyle.Red; - saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning")); - } - } - if (!isCompatible) - { - nameText.TextColor = GUIStyle.Red; - saveFrame.ToolTip = TextManager.Get("campaignmode.incompatiblesave"); - } - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), - text: subName, font: GUIStyle.SmallFont) - { - CanBeFocused = false, - UserData = fileName - }; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), - text: saveTime, textAlignment: Alignment.Right, font: GUIStyle.SmallFont) - { - CanBeFocused = false, - UserData = fileName - }; + CreateSaveElement(saveInfo); } saveList.Content.RectTransform.SortChildren((c1, c2) => @@ -380,7 +315,7 @@ namespace Barotrauma EventEditorScreen.AskForConfirmation(header, body, () => { SaveUtil.DeleteSave(saveFile); - prevSaveFiles?.RemoveAll(s => s.StartsWith(saveFile)); + prevSaveFiles?.RemoveAll(s => s.FilePath == saveFile); UpdateLoadMenu(prevSaveFiles.ToList()); return true; }); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs index 9c62c6816..870827d91 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs @@ -21,7 +21,7 @@ namespace Barotrauma private GUIButton nextButton; private GUILayoutGroup characterInfoColumns; - public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) + public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) : base(newGameContainer, loadGameContainer) { UpdateNewGameMenu(submarines); @@ -606,8 +606,7 @@ namespace Barotrauma } } - private List prevSaveFiles; - public void UpdateLoadMenu(IEnumerable saveFiles = null) + public void UpdateLoadMenu(IEnumerable saveFiles = null) { prevSaveFiles?.Clear(); prevSaveFiles = null; @@ -647,32 +646,16 @@ namespace Barotrauma } }; - foreach (string saveFile in saveFiles) + foreach (var saveInfo in saveFiles) { - string fileName = saveFile; - string subName = ""; - string saveTime = ""; - string contentPackageStr = ""; - var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement") - { - UserData = saveFile - }; - - var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), "") - { - CanBeFocused = false - }; - - bool isCompatible = true; - prevSaveFiles ??= new List(); - - nameText.Text = Path.GetFileNameWithoutExtension(saveFile); - XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile); - + var saveFrame = CreateSaveElement(saveInfo); + if (saveFrame == null) { continue; } + + XDocument doc = SaveUtil.LoadGameSessionDoc(saveInfo.FilePath); if (doc?.Root == null) { - DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted."); - nameText.TextColor = GUIStyle.Red; + DebugConsole.ThrowError("Error loading save file \"" + saveInfo.FilePath + "\". The file may be corrupted."); + saveFrame.GetChild().TextColor = GUIStyle.Red; continue; } if (doc.Root.GetChildElement("multiplayercampaign") != null) @@ -681,44 +664,11 @@ namespace Barotrauma saveList.Content.RemoveChild(saveFrame); continue; } - subName = doc.Root.GetAttributeString("submarine", ""); - saveTime = doc.Root.GetAttributeString("savetime", ""); - isCompatible = SaveUtil.IsSaveFileCompatible(doc); - contentPackageStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackages", ""); - prevSaveFiles?.Add(saveFile); - if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime)) + if (!SaveUtil.IsSaveFileCompatible(doc)) { - DateTime time = ToolBox.Epoch.ToDateTime(unixTime); - saveTime = time.ToString(); - } - if (!string.IsNullOrEmpty(contentPackageStr)) - { - List contentPackagePaths = contentPackageStr.Split('|').ToList(); - if (!GameSession.IsCompatibleWithEnabledContentPackages(contentPackagePaths, out LocalizedString errorMsg)) - { - nameText.TextColor = GUIStyle.Red; - saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning")); - } - } - if (!isCompatible) - { - nameText.TextColor = GUIStyle.Red; + saveFrame.GetChild().TextColor = GUIStyle.Red; saveFrame.ToolTip = TextManager.Get("campaignmode.incompatiblesave"); } - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), - text: subName, font: GUIStyle.SmallFont) - { - CanBeFocused = false, - UserData = fileName - }; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), - text: saveTime, textAlignment: Alignment.Right, font: GUIStyle.SmallFont) - { - CanBeFocused = false, - UserData = fileName - }; } saveList.Content.RectTransform.SortChildren((c1, c2) => @@ -830,7 +780,7 @@ namespace Barotrauma EventEditorScreen.AskForConfirmation(header, body, () => { SaveUtil.DeleteSave(saveFile); - prevSaveFiles?.RemoveAll(s => s.StartsWith(saveFile)); + prevSaveFiles?.RemoveAll(s => s.FilePath == saveFile); UpdateLoadMenu(prevSaveFiles.ToList()); return true; }); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index efbb7b0cc..c1864e3cc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -1,4 +1,5 @@ using Barotrauma.Extensions; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; @@ -332,7 +333,7 @@ namespace Barotrauma foreach (GUITickBox tickBox in missionTickBoxes) { bool disable = hasMaxMissions && !tickBox.Selected; - tickBox.Enabled = Campaign.AllowedToManageCampaign() && !disable; + tickBox.Enabled = Campaign.AllowedToManageCampaign(ClientPermissions.ManageMap) && !disable; tickBox.Box.DisabledColor = disable ? tickBox.Box.Color * 0.5f : tickBox.Box.Color * 0.8f; foreach (GUIComponent child in tickBox.Parent.Parent.Children) { @@ -480,7 +481,7 @@ namespace Barotrauma if (GUI.MouseOn == tickBox) { return false; } if (tickBox != null) { - if (Campaign.AllowedToManageCampaign() && tickBox.Enabled) + if (Campaign.AllowedToManageCampaign(ClientPermissions.ManageMap) && tickBox.Enabled) { tickBox.Selected = !tickBox.Selected; } @@ -521,10 +522,10 @@ namespace Barotrauma }; tickBox.RectTransform.MinSize = new Point(tickBox.Rect.Height, 0); tickBox.RectTransform.IsFixedSize = true; - tickBox.Enabled = Campaign.AllowedToManageCampaign(); + tickBox.Enabled = Campaign.AllowedToManageCampaign(ClientPermissions.ManageMap); tickBox.OnSelected += (GUITickBox tb) => { - if (!Campaign.AllowedToManageCampaign()) { return false; } + if (!Campaign.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap)) { return false; } if (tb.Selected) { @@ -544,7 +545,7 @@ namespace Barotrauma UpdateMaxMissions(connection.OtherLocation(currentDisplayLocation)); if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending && - Campaign.AllowedToManageCampaign()) + Campaign.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap)) { GameMain.Client?.SendCampaignState(); } @@ -665,7 +666,7 @@ namespace Barotrauma return true; }, Enabled = true, - Visible = Campaign.AllowedToEndRound() + Visible = Campaign.AllowedToManageCampaign(ClientPermissions.ManageMap) }; buttonArea.RectTransform.MinSize = new Point(0, StartButton.RectTransform.MinSize.Y); @@ -702,12 +703,10 @@ namespace Barotrauma { case CampaignMode.InteractionType.Repair: repairHullsButton.Enabled = - (Campaign.PurchasedHullRepairs || Campaign.Wallet.CanAfford(CampaignMode.HullRepairCost)) && - Campaign.AllowedToManageCampaign(); + (Campaign.PurchasedHullRepairs || Campaign.Wallet.CanAfford(CampaignMode.HullRepairCost)); repairHullsButton.GetChild().Selected = Campaign.PurchasedHullRepairs; repairItemsButton.Enabled = - (Campaign.PurchasedItemRepairs || Campaign.Wallet.CanAfford(CampaignMode.ItemRepairCost)) && - Campaign.AllowedToManageCampaign(); + (Campaign.PurchasedItemRepairs || Campaign.Wallet.CanAfford(CampaignMode.ItemRepairCost)); repairItemsButton.GetChild().Selected = Campaign.PurchasedItemRepairs; if (GameMain.GameSession?.SubmarineInfo == null || !GameMain.GameSession.SubmarineInfo.SubsLeftBehind) @@ -718,8 +717,7 @@ namespace Barotrauma else { replaceShuttlesButton.Enabled = - (Campaign.PurchasedLostShuttles || Campaign.Wallet.CanAfford(CampaignMode.ShuttleReplaceCost)) && - Campaign.AllowedToManageCampaign(); + (Campaign.PurchasedLostShuttles || Campaign.Wallet.CanAfford(CampaignMode.ShuttleReplaceCost)); replaceShuttlesButton.GetChild().Selected = Campaign.PurchasedLostShuttles; } break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs index 8fb996949..d0c67ae6a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs @@ -387,11 +387,14 @@ namespace Barotrauma.CharacterEditor contentPackageDropDown.Flash(); return false; } - if (!File.Exists(TexturePath)) + if (SourceCharacter?.SpeciesName != CharacterPrefab.HumanSpeciesName) { - GUI.AddMessage(GetCharacterEditorTranslation("TextureDoesNotExist"), GUIStyle.Red); - texturePathElement.Flash(GUIStyle.Red); - return false; + if (!File.Exists(TexturePath)) + { + GUI.AddMessage(GetCharacterEditorTranslation("TextureDoesNotExist"), GUIStyle.Red); + texturePathElement.Flash(GUIStyle.Red); + return false; + } } var path = Path.GetFileName(TexturePath); if (!path.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EditorScreen.cs index be295b3ed..7108479ce 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/EditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EditorScreen.cs @@ -10,11 +10,13 @@ namespace Barotrauma public override sealed void Deselect() { DeselectEditorSpecific(); +#if !DEBUG //reset cheats the player might have used in the editor GameMain.LightManager.LightingEnabled = true; GameMain.LightManager.LosEnabled = true; Hull.EditFire = false; Hull.EditWater = false; +#endif } protected virtual void DeselectEditorSpecific() { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index 7fb77b452..f8468ea3c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -1006,13 +1006,12 @@ namespace Barotrauma spriteBatch.End(); } - private void StartGame(SubmarineInfo selectedSub, string saveName, string mapSeed, CampaignSettings settings) + private void StartGame(SubmarineInfo selectedSub, string savePath, string mapSeed, CampaignSettings settings) { - if (string.IsNullOrEmpty(saveName)) return; + if (string.IsNullOrEmpty(savePath)) { return; } var existingSaveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer); - - if (existingSaveFiles.Any(s => s == saveName)) + if (existingSaveFiles.Any(s => s.FilePath == savePath)) { new GUIMessageBox(TextManager.Get("SaveNameInUseHeader"), TextManager.Get("SaveNameInUseText")); return; @@ -1045,7 +1044,7 @@ namespace Barotrauma selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub")); - GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.SinglePlayerCampaign, settings, mapSeed); + GameMain.GameSession = new GameSession(selectedSub, savePath, GameModePreset.SinglePlayerCampaign, settings, mapSeed); GameMain.GameSession.CrewManager.CharacterInfos.Clear(); foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index af54e3654..a4cf42674 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -1299,7 +1299,7 @@ namespace Barotrauma if (GameMain.Client != null) { - GameMain.Client.ServerSettings.Voting.ResetVotes(GameMain.Client.ConnectedClients); + GameMain.Client.Voting.ResetVotes(GameMain.Client.ConnectedClients); spectateButton.OnClicked = GameMain.Client.SpectateClicked; ReadyToStartBox.OnSelected = GameMain.Client.SetReadyToStart; } @@ -1307,9 +1307,6 @@ namespace Barotrauma roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible); roundControlsHolder.Recalculate(); - GameMain.NetworkMember.EndVoteCount = 0; - GameMain.NetworkMember.EndVoteMax = 1; - base.Select(); } @@ -1348,9 +1345,9 @@ namespace Barotrauma ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); ServerMessage.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); shuttleTickBox.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings) && !GameMain.Client.GameStarted; - SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.Voting.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub)); + SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub)); ShuttleList.Enabled = ShuttleList.ButtonEnabled = GameMain.Client.HasPermission(ClientPermissions.SelectSub) && !GameMain.Client.GameStarted; - ModeList.Enabled = GameMain.Client.ServerSettings.Voting.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode); + ModeList.Enabled = GameMain.Client.ServerSettings.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode); LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible); @@ -1889,7 +1886,7 @@ namespace Barotrauma } else { - var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) }, + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) }, TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont) { UserData = "classtext", @@ -1907,7 +1904,7 @@ namespace Barotrauma VoteType voteType; if (component.Parent == GameMain.NetLobbyScreen.SubList.Content) { - if (!GameMain.Client.ServerSettings.Voting.AllowSubVoting) + if (!GameMain.Client.ServerSettings.AllowSubVoting) { var selectedSub = component.UserData as SubmarineInfo; if (!selectedSub.RequiredContentPackagesInstalled) @@ -1942,7 +1939,7 @@ namespace Barotrauma } else if (component.Parent == GameMain.NetLobbyScreen.ModeList.Content) { - if (!GameMain.Client.ServerSettings.Voting.AllowModeVoting) + if (!GameMain.Client.ServerSettings.AllowModeVoting) { if (GameMain.Client.HasPermission(ClientPermissions.SelectMode)) { @@ -2384,6 +2381,7 @@ namespace Barotrauma return true; } }; + permissionTick.ToolTip = permissionTick.TextBlock.ToolTip = TextManager.Get("ClientPermission." + permission + ".description"); } var listBoxContainerRight = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), permissionContainer.RectTransform)) @@ -2478,7 +2476,7 @@ namespace Barotrauma if (GameMain.Client != null && GameMain.Client.ConnectedClients.Contains(selectedClient)) { - if (GameMain.Client.ServerSettings.Voting.AllowVoteKick && + if (GameMain.Client.ServerSettings.AllowVoteKick && selectedClient != null && selectedClient.AllowKicking) { var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaLower.RectTransform), @@ -3564,22 +3562,7 @@ namespace Barotrauma if (GameMain.Client.ServerSettings.AllowFileTransfers) { - errorMsg += TextManager.Get("DownloadSubQuestion"); - - var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg, - new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }) - { - UserData = "request" + subName - }; - requestFileBox.Buttons[0].UserData = new string[] { subName, md5Hash }; - requestFileBox.Buttons[0].OnClicked += requestFileBox.Close; - requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) => - { - string[] fileInfo = (string[])userdata; - GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo[0], fileInfo[1]); - return true; - }; - requestFileBox.Buttons[1].OnClicked += requestFileBox.Close; + GameMain.Client?.RequestFile(FileTransferType.Submarine, subName, md5Hash); } else { @@ -3596,7 +3579,7 @@ namespace Barotrauma public bool CheckIfCampaignSubMatches(SubmarineInfo serverSubmarine, SubmarineDeliveryData deliveryData) { - if (GameMain.Client == null) return false; + if (GameMain.Client == null) { return false; } //already downloading the selected sub file if (GameMain.Client.FileReceiver.ActiveTransfers.Any(t => t.FileName == serverSubmarine.Name + ".sub")) @@ -3610,59 +3593,19 @@ namespace Barotrauma return true; } - purchasableSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSubmarine.Name); + FailedSubInfo fileInfo = new FailedSubInfo(serverSubmarine.Name, serverSubmarine.MD5Hash.StringRepresentation); - LocalizedString errorMsg = ""; - if (purchasableSub == null) + switch (deliveryData) { - errorMsg = TextManager.GetWithVariable("SubNotFoundError", "[subname]", serverSubmarine.Name) + " "; - } - else if (purchasableSub.MD5Hash?.StringRepresentation == null) - { - errorMsg = TextManager.GetWithVariable("SubLoadError", "[subname]", serverSubmarine.Name) + " "; - /*GUITextBlock textBlock = subList.Content.GetChildByUserData(sub)?.GetChild(); - if (textBlock != null) { textBlock.TextColor = GUIStyle.Red; }*/ - } - else - { - errorMsg = TextManager.GetWithVariables("SubDoesntMatchError", - ("[subname]", purchasableSub.Name), - ("[myhash]", purchasableSub.MD5Hash.ShortRepresentation), - ("[serverhash]", Md5Hash.GetShortHash(serverSubmarine.MD5Hash.StringRepresentation))) + " "; - } - - errorMsg += TextManager.Get("DownloadSubQuestion"); - - //already showing a message about the same sub - if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "request" + serverSubmarine.Name)) - { - return false; - } - - var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg, - new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }) - { - UserData = "request" + serverSubmarine.Name - }; - requestFileBox.Buttons[0].UserData = new FailedSubInfo(serverSubmarine.Name, serverSubmarine.MD5Hash.StringRepresentation); - requestFileBox.Buttons[0].OnClicked += requestFileBox.Close; - requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) => - { - FailedSubInfo fileInfo = (FailedSubInfo)userdata; - - if (deliveryData == SubmarineDeliveryData.Owned) - { + case SubmarineDeliveryData.Owned: FailedOwnedSubs.Add(fileInfo); - } - else if (deliveryData == SubmarineDeliveryData.Campaign) - { + break; + case SubmarineDeliveryData.Campaign: FailedCampaignSubs.Add(fileInfo); - } + break; + } - GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo.Name, fileInfo.Hash); - return true; - }; - requestFileBox.Buttons[1].OnClicked += requestFileBox.Close; + GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo.Name, fileInfo.Hash); return false; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs index 5ec47b586..bd2b9b4c9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs @@ -1905,27 +1905,27 @@ namespace Barotrauma toolTip = TextManager.GetWithVariable("ServerListIncompatibleVersion", "[version]", serverInfo.GameVersion); } + int maxIncompatibleToList = 10; + List incompatibleModNames = new List(); for (int i = 0; i < serverInfo.ContentPackageNames.Count; i++) { - bool listAsIncompatible = false; - if (serverInfo.ContentPackageWorkshopIds[i] == 0) - { - listAsIncompatible = !ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.Hash.StringRepresentation == serverInfo.ContentPackageHashes[i]); - } - else - { - listAsIncompatible = ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.Hash.StringRepresentation != serverInfo.ContentPackageHashes[i] && - contentPackage.SteamWorkshopId == serverInfo.ContentPackageWorkshopIds[i]); - } + bool listAsIncompatible = !ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.Hash.StringRepresentation == serverInfo.ContentPackageHashes[i]); if (listAsIncompatible) { - if (toolTip != "") toolTip += "\n"; - toolTip += TextManager.GetWithVariables("ServerListIncompatibleContentPackage", - ("[contentpackage]", serverInfo.ContentPackageNames[i]), - ("[hash]", Md5Hash.GetShortHash(serverInfo.ContentPackageHashes[i]))); + incompatibleModNames.Add(TextManager.GetWithVariables("ModNameAndHashFormat", + ("[name]", serverInfo.ContentPackageNames[i]), + ("[hash]", Md5Hash.GetShortHash(serverInfo.ContentPackageHashes[i])))); + + } + } + if (incompatibleModNames.Any()) + { + toolTip += '\n' + TextManager.Get("ModDownloadHeader") + "\n" + string.Join(", ", incompatibleModNames.Take(maxIncompatibleToList)); + if (incompatibleModNames.Count > maxIncompatibleToList) + { + toolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (incompatibleModNames.Count - maxIncompatibleToList).ToString()); } } - serverContent.Children.ForEach(c => c.ToolTip = toolTip); serverName.TextColor *= 0.5f; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 7e6abbd01..05723864f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -237,7 +237,7 @@ namespace Barotrauma public override Camera Cam => cam; public static XDocument AutoSaveInfo; - private static readonly string autoSavePath = Path.Combine(SubmarineInfo.SavePath, ".AutoSaves"); + private static readonly string autoSavePath = Path.Combine("Submarines", ".AutoSaves"); private static readonly string autoSaveInfoPath = Path.Combine(autoSavePath, "autosaves.xml"); private static string GetSubDescription() @@ -678,7 +678,7 @@ namespace Barotrauma //----------------------------------------------- - showEntitiesPanel = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.5f), GUI.Canvas) + showEntitiesPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.5f), GUI.Canvas) { MinSize = new Point(190, 0) }) @@ -771,22 +771,26 @@ namespace Barotrauma } foreach (string subcategory in availableSubcategories) { - var tb = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), subcategoryList.Content.RectTransform), + var tb = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.15f), subcategoryList.Content.RectTransform), TextManager.Get("subcategory." + subcategory).Fallback(subcategory), font: GUIStyle.SmallFont) { UserData = subcategory, Selected = !IsSubcategoryHidden(subcategory), OnSelected = (GUITickBox obj) => { hiddenSubCategories[(string)obj.UserData] = !obj.Selected; return true; }, }; - if (tb.TextBlock.TextSize.X > tb.TextBlock.Rect.Width * 1.25f) + tb.TextBlock.Wrap = true; + } + + GUITextBlock.AutoScaleAndNormalize(subcategoryList.Content.Children.Where(c => c is GUITickBox).Select(c => ((GUITickBox)c).TextBlock)); + foreach (GUIComponent child in subcategoryList.Content.Children) + { + if (child is GUITickBox tb && tb.TextBlock.TextSize.X > tb.TextBlock.Rect.Width * 1.25f) { tb.ToolTip = tb.Text; tb.Text = ToolBox.LimitString(tb.Text.Value, tb.Font, (int)(tb.TextBlock.Rect.Width * 1.25f)); } } - GUITextBlock.AutoScaleAndNormalize(subcategoryList.Content.Children.Where(c => c is GUITickBox).Select(c => ((GUITickBox)c).TextBlock)); - showEntitiesPanel.RectTransform.NonScaledSize = new Point( (int)(paddedShowEntitiesPanel.RectTransform.Children.Max(c => (int)((c.GUIComponent as GUITickBox)?.TextBlock.TextSize.X ?? 0)) / paddedShowEntitiesPanel.RectTransform.RelativeSize.X), @@ -2933,7 +2937,7 @@ namespace Barotrauma if (deleteButtonHolder.FindChild("delete") is GUIButton deleteBtn) { deleteBtn.Enabled = userData is SubmarineInfo subInfo - && (GetContentPackageIntrinsicallyTiedToSub(subInfo) != null || Path.GetDirectoryName(subInfo.FilePath) == SubmarineInfo.SavePath); + && GetContentPackageIntrinsicallyTiedToSub(subInfo) != null; } return true; } @@ -3102,8 +3106,9 @@ namespace Barotrauma var loadedSub = Submarine.Load(new SubmarineInfo(filePath), true); // set the submarine file path to the "default" value - loadedSub.Info.FilePath = Path.Combine(SubmarineInfo.SavePath, $"{TextManager.Get("UnspecifiedSubFileName")}.sub"); - loadedSub.Info.Name = TextManager.Get("UnspecifiedSubFileName").Value; + var unspecifiedFileName = TextManager.Get("UnspecifiedSubFileName"); + loadedSub.Info.FilePath = Path.Combine(ContentPackage.LocalModsDir, unspecifiedFileName.Value, $"{unspecifiedFileName}.sub"); + loadedSub.Info.Name = unspecifiedFileName.Value; try { loadedSub.Info.Name = loadedSub.Info.SubmarineElement.GetAttributeString("name", loadedSub.Info.Name); @@ -3199,8 +3204,7 @@ namespace Barotrauma //check that it's a local content package and only allow deletion if it is. //(deleting from the Submarines folder is also currently allowed, but this is temporary) var subPackage = GetContentPackageIntrinsicallyTiedToSub(sub); - bool isInOldSavePath = Path.GetDirectoryName(sub.FilePath) == SubmarineInfo.SavePath; - if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage) && !isInOldSavePath) { return; } + if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { return; } var msgBox = new GUIMessageBox( TextManager.Get("DeleteDialogLabel"), @@ -3216,10 +3220,6 @@ namespace Barotrauma ContentPackageManager.LocalPackages.Refresh(); ContentPackageManager.EnabledPackages.DisableRemovedMods(); } - else if (isInOldSavePath && File.Exists(sub.FilePath)) - { - File.Delete(sub.FilePath); - } sub.Dispose(); SubmarineInfo.RefreshSavedSubs(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs index c809ad75f..823439ace 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs @@ -13,7 +13,7 @@ using OpenAL; namespace Barotrauma { - public class SettingsMenu + class SettingsMenu { public static SettingsMenu? Instance { get; private set; } @@ -709,7 +709,9 @@ namespace Barotrauma GUIFrame content = CreateNewContentFrame(Tab.Mods); content.RectTransform.RelativeSize = Vector2.One; - workshopMenu = new WorkshopMenu(content); + workshopMenu = Screen.Selected is MainMenuScreen + ? (WorkshopMenu)new MutableWorkshopMenu(content) + : (WorkshopMenu)new ImmutableWorkshopMenu(content); } private void CreateBottomButtons() @@ -729,7 +731,7 @@ namespace Barotrauma OnClicked = (btn, obj) => { GameSettings.SetCurrentConfig(unsavedConfig); - WorkshopMenu.Apply(); + if (WorkshopMenu is MutableWorkshopMenu mutableWorkshopMenu) { mutableWorkshopMenu.Apply(); } GameSettings.SaveCurrentConfig(); mainFrame.Flash(color: GUIStyle.Green); return false; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/BBCode.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/BBCode.cs similarity index 96% rename from Barotrauma/BarotraumaClient/ClientSource/Steam/BBCode.cs rename to Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/BBCode.cs index 93b6b3235..72a4861b5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/BBCode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/BBCode.cs @@ -8,9 +8,9 @@ using Microsoft.Xna.Framework.Graphics; namespace Barotrauma.Steam { - public partial class WorkshopMenu + abstract partial class WorkshopMenu { - private readonly struct BBWord + protected readonly struct BBWord { [Flags] public enum TagType @@ -42,10 +42,10 @@ namespace Barotrauma.Steam } } - private static readonly Regex bbTagRegex = new Regex(@"\[(.+?)\]", + protected static readonly Regex bbTagRegex = new Regex(@"\[(.+?)\]", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - private static GUICustomComponent CreateBBCodeElement(string bbCode, GUIListBox container) + protected static GUICustomComponent CreateBBCodeElement(string bbCode, GUIListBox container) { Point cachedContainerSize = Point.Zero; List bbWords = new List(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Immutable/ImmutableWorkshopMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Immutable/ImmutableWorkshopMenu.cs new file mode 100644 index 000000000..f94a64186 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Immutable/ImmutableWorkshopMenu.cs @@ -0,0 +1,41 @@ +#nullable enable +using Microsoft.Xna.Framework; + +namespace Barotrauma.Steam +{ + sealed class ImmutableWorkshopMenu : WorkshopMenu + { + public ImmutableWorkshopMenu(GUIFrame parent) : base(parent) + { + var mainLayout + = new GUILayoutGroup(new RectTransform((0.5f, 1.0f), parent.RectTransform, Anchor.Center), isHorizontal: false); + + Label(mainLayout, TextManager.Get("enabledcore"), GUIStyle.SubHeadingFont); + var coreBox = new GUIButton( + NewItemRectT(mainLayout), style: "GUITextBoxNoIcon", text: ContentPackageManager.EnabledPackages.Core!.Name, textAlignment: Alignment.CenterLeft) + { + CanBeFocused = false, + CanBeSelected = false + }; + coreBox.TextBlock.Padding = new Vector4(10.0f, 0.0f, 10.0f, 0.0f); + + Label(mainLayout, TextManager.Get("enabledregular"), GUIStyle.SubHeadingFont); + var regularList = new GUIListBox( + NewItemRectT(mainLayout, heightScale: 12f)) + { + OnSelected = (component, o) => false, + HoverCursor = CursorState.Default + }; + foreach (var p in ContentPackageManager.EnabledPackages.Regular) + { + var regularBox = new GUITextBlock( + new RectTransform((1.0f, 0.07f), regularList.Content.RectTransform), text: p.Name) + { + CanBeFocused = false + }; + } + + Label(mainLayout, TextManager.Get("CannotChangeMods"), GUIStyle.Font); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/ItemList.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs similarity index 99% rename from Barotrauma/BarotraumaClient/ClientSource/Steam/ItemList.cs rename to Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs index 5ab0a9d8a..3ae7116ea 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/ItemList.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs @@ -12,7 +12,7 @@ using ItemOrPackage = Barotrauma.Either itemOrPackage.TryGet(out ContentPackage package) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs similarity index 97% rename from Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu.cs rename to Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs index 59b42ef78..a66e87f55 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs @@ -10,7 +10,7 @@ using ItemOrPackage = Barotrauma.Either tabContents; + protected readonly GUILayoutGroup tabber; + protected readonly Dictionary tabContents; - private readonly GUIFrame contentFrame; + protected readonly GUIFrame contentFrame; private CorePackage EnabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected"); @@ -39,15 +39,17 @@ namespace Barotrauma.Steam private readonly GUIListBox popularModsList; private readonly GUIListBox selfModsList; - public WorkshopMenu(GUIFrame parent) + public MutableWorkshopMenu(GUIFrame parent) : base(parent) { - var mainLayout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: false); + var mainLayout + = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: false); - tabber = new GUILayoutGroup(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), isHorizontal: true) { Stretch = true }; + tabber = new GUILayoutGroup(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), isHorizontal: true) + { Stretch = true }; tabContents = new Dictionary(); contentFrame = new GUIFrame(new RectTransform((1.0f, 0.95f), mainLayout.RectTransform), style: null); - + CreateInstalledModsTab( out enabledCoreDropdown, out enabledRegularModsList, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/PublishTab.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/PublishTab.cs similarity index 99% rename from Barotrauma/BarotraumaClient/ClientSource/Steam/PublishTab.cs rename to Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/PublishTab.cs index 720121b09..ec55b9e72 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/PublishTab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/PublishTab.cs @@ -15,7 +15,7 @@ using Path = Barotrauma.IO.Path; namespace Barotrauma.Steam { - public partial class WorkshopMenu + sealed partial class MutableWorkshopMenu : WorkshopMenu { private class LocalThumbnail : IDisposable { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/UiUtil.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/UiUtil.cs similarity index 79% rename from Barotrauma/BarotraumaClient/ClientSource/Steam/UiUtil.cs rename to Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/UiUtil.cs index 9293df567..910fe6d1f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/UiUtil.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/UiUtil.cs @@ -7,22 +7,22 @@ using Microsoft.Xna.Framework; namespace Barotrauma.Steam { - public partial class WorkshopMenu + abstract partial class WorkshopMenu { - private static RectTransform NewItemRectT(GUILayoutGroup parent, float heightScale = 1.0f) + protected static RectTransform NewItemRectT(GUILayoutGroup parent, float heightScale = 1.0f) => new RectTransform((1.0f, 0.06f * heightScale), parent.RectTransform, Anchor.CenterLeft); - private static void Spacer(GUILayoutGroup parent, float height = 0.03f) + protected static void Spacer(GUILayoutGroup parent, float height = 0.03f) { new GUIFrame(new RectTransform((1.0f, height), parent.RectTransform, Anchor.CenterLeft), style: null); } - private static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font, float heightScale = 1.0f) + protected static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font, float heightScale = 1.0f) { return new GUITextBlock(NewItemRectT(parent, heightScale), str, font: font); } - private static GUITextBox ScrollableTextBox(GUILayoutGroup parent, float heightScale, string text) + protected static GUITextBox ScrollableTextBox(GUILayoutGroup parent, float heightScale, string text) { var containingListBox = new GUIListBox(NewItemRectT(parent, heightScale)); var textBox = new GUITextBox( @@ -53,12 +53,12 @@ namespace Barotrauma.Steam return textBox; } - private static GUIDropDown DropdownEnum( + protected static GUIDropDown DropdownEnum( GUILayoutGroup parent, Func textFunc, T currentValue, Action setter) where T : Enum => Dropdown(parent, textFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter); - private static GUIDropDown Dropdown( + protected static GUIDropDown Dropdown( GUILayoutGroup parent, Func textFunc, IReadOnlyList values, T currentValue, Action setter, float heightScale = 1.0f) { @@ -67,7 +67,7 @@ namespace Barotrauma.Steam return dropdown; } - private static void SwapDropdownValues( + protected static void SwapDropdownValues( GUIDropDown dropdown, Func textFunc, IReadOnlyList values, T currentValue, Action setter) { @@ -88,10 +88,10 @@ namespace Barotrauma.Steam }; } - private static int Round(float v) => (int)MathF.Round(v); - private static string Percentage(float v) => $"{Round(v * 100)}"; + protected static int Round(float v) => (int)MathF.Round(v); + protected static string Percentage(float v) => $"{Round(v * 100)}"; - private struct ActionCarrier + protected struct ActionCarrier { public readonly Identifier Id; public readonly Action Action; @@ -102,7 +102,7 @@ namespace Barotrauma.Steam } } - private GUIComponent CreateActionCarrier(GUIComponent parent, Identifier id, Action action) + protected GUIComponent CreateActionCarrier(GUIComponent parent, Identifier id, Action action) => new GUIFrame(new RectTransform(Vector2.Zero, parent.RectTransform), style: null) { UserData = new ActionCarrier(id, action) }; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/WorkshopMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/WorkshopMenu.cs new file mode 100644 index 000000000..1a3e8f53c --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/WorkshopMenu.cs @@ -0,0 +1,12 @@ +#nullable enable + +namespace Barotrauma.Steam +{ + abstract partial class WorkshopMenu + { + public WorkshopMenu(GUIFrame parent) + { + + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 65d8b3408..92e9d579c 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.4.0 + 0.17.5.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index d6beedd53..9f9f372a2 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.4.0 + 0.17.5.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 539cc7653..8ffe5ccb4 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.4.0 + 0.17.5.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index dbda5fa53..ab23d88c7 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.4.0 + 0.17.5.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 883d5184f..3e74a9d79 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.4.0 + 0.17.5.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs index 98015ddf0..671dedf34 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs @@ -129,7 +129,7 @@ namespace Barotrauma public void ApplyWalletData(Character character) { - character.Wallet = new Wallet(WalletData); + character.Wallet = new Wallet(Option.Some(character), WalletData); } public XElement Save() diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index d8cc3b09b..d509c9ae9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -135,7 +135,7 @@ namespace Barotrauma DebugConsole.NewMessage("Saved campaigns:", Color.White); for (int i = 0; i < saveFiles.Length; i++) { - DebugConsole.NewMessage(" " + i + ". " + saveFiles[i], Color.White); + DebugConsole.NewMessage(" " + i + ". " + saveFiles[i].FilePath, Color.White); } DebugConsole.ShowQuestionPrompt("Select a save file to load (0 - " + (saveFiles.Length - 1) + "):", (string selectedSave) => { @@ -148,7 +148,7 @@ namespace Barotrauma } else { - LoadCampaign(saveFiles[saveIndex]); + LoadCampaign(saveFiles[saveIndex].FilePath); } }); } @@ -166,28 +166,13 @@ namespace Barotrauma /// /// There is a client-side implementation of the method in /// - public bool AllowedToEndRound(Client client) - { - //allow ending the round if the client has permissions, is the owner, the only client in the server, - //or if no-one has permissions - return - client.HasPermission(ClientPermissions.ManageRound) || - client.HasPermission(ClientPermissions.ManageCampaign) || - GameMain.Server.ConnectedClients.Count == 1 || - IsOwner(client) || - GameMain.Server.ConnectedClients.None(c => - c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign))); - } - - /// - /// There is a client-side implementation of the method in - /// - public bool AllowedToManageCampaign(Client client, ClientPermissions permissions = ClientPermissions.ManageCampaign) + public bool AllowedToManageCampaign(Client client, ClientPermissions permissions) { //allow managing the campaign if the client has permissions, is the owner, or the only client in the server, //or if no-one has management permissions return client.HasPermission(permissions) || + client.HasPermission(ClientPermissions.ManageCampaign) || GameMain.Server.ConnectedClients.Count == 1 || IsOwner(client) || GameMain.Server.ConnectedClients.None(c => c.InGame && (IsOwner(c) || c.HasPermission(permissions))); @@ -519,7 +504,7 @@ namespace Barotrauma walletsToCheck.Clear(); walletsToCheck.Add(0, Bank); - foreach (Character character in Mission.GetSalaryEligibleCrew()) + foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Player)) { walletsToCheck.Add(character.ID, character.Wallet); } @@ -715,107 +700,102 @@ namespace Barotrauma purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall)); } - bool allowedToManageCampaign = AllowedToManageCampaign(sender); - if (AllowedToManageCampaign(sender)) + Location location = Map.CurrentLocation; + int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost; + int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost; + int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost; + Wallet personalWallet = GetWallet(sender); + + if (purchasedHullRepairs != PurchasedHullRepairs) { - Location location = Map.CurrentLocation; - int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost; - int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost; - int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost; - Wallet personalWallet = GetWallet(sender); - - if (purchasedHullRepairs != PurchasedHullRepairs) + switch (purchasedHullRepairs) { - switch (purchasedHullRepairs) - { - case true when personalWallet.CanAfford(hullRepairCost): - personalWallet.Deduct(hullRepairCost); - PurchasedHullRepairs = true; - GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs"); - break; - case false: - PurchasedHullRepairs = false; - personalWallet.Refund(hullRepairCost); - break; - } + case true when personalWallet.CanAfford(hullRepairCost): + personalWallet.Deduct(hullRepairCost); + PurchasedHullRepairs = true; + GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs"); + break; + case false: + PurchasedHullRepairs = false; + personalWallet.Refund(hullRepairCost); + break; } + } - if (purchasedItemRepairs != PurchasedItemRepairs) + if (purchasedItemRepairs != PurchasedItemRepairs) + { + switch (purchasedItemRepairs) { - switch (purchasedItemRepairs) - { - case true when personalWallet.CanAfford(itemRepairCost): - personalWallet.Deduct(itemRepairCost); - PurchasedItemRepairs = true; - GameAnalyticsManager.AddMoneySpentEvent(itemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs"); - break; - case false: - PurchasedItemRepairs = false; - personalWallet.Refund(itemRepairCost); - break; - } + case true when personalWallet.CanAfford(itemRepairCost): + personalWallet.Deduct(itemRepairCost); + PurchasedItemRepairs = true; + GameAnalyticsManager.AddMoneySpentEvent(itemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs"); + break; + case false: + PurchasedItemRepairs = false; + personalWallet.Refund(itemRepairCost); + break; } + } - if (purchasedLostShuttles != PurchasedLostShuttles) + if (purchasedLostShuttles != PurchasedLostShuttles) + { + if (GameMain.GameSession?.SubmarineInfo != null && GameMain.GameSession.SubmarineInfo.LeftBehindSubDockingPortOccupied) { - if (GameMain.GameSession?.SubmarineInfo != null && GameMain.GameSession.SubmarineInfo.LeftBehindSubDockingPortOccupied) - { - GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox); - } - else if (purchasedLostShuttles && personalWallet.TryDeduct(shuttleRetrieveCost)) - { - PurchasedLostShuttles = true; - GameAnalyticsManager.AddMoneySpentEvent(shuttleRetrieveCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle"); - } - else if (!purchasedItemRepairs) - { - PurchasedLostShuttles = false; - personalWallet.Refund(shuttleRetrieveCost); - } + GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox); } - - if (currentLocIndex < Map.Locations.Count && Map.AllowDebugTeleport) + else if (purchasedLostShuttles && personalWallet.TryDeduct(shuttleRetrieveCost)) { - Map.SetLocation(currentLocIndex); + PurchasedLostShuttles = true; + GameAnalyticsManager.AddMoneySpentEvent(shuttleRetrieveCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle"); } + else if (!purchasedItemRepairs) + { + PurchasedLostShuttles = false; + personalWallet.Refund(shuttleRetrieveCost); + } + } + if (currentLocIndex < Map.Locations.Count && Map.AllowDebugTeleport) + { + Map.SetLocation(currentLocIndex); + } + + if (AllowedToManageCampaign(sender, ClientPermissions.ManageMap)) + { Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); if (Map.SelectedLocation == null) { Map.SelectRandomLocation(preferUndiscovered: true); } if (Map.SelectedConnection != null) { Map.SelectMission(selectedMissionIndices); } CheckTooManyMissions(Map.CurrentLocation, sender); } - - bool allowedToUseStore = AllowedToManageCampaign(sender, ClientPermissions.CampaignStore); - if (allowedToManageCampaign || allowedToUseStore || AllowedToManageCampaign(sender, ClientPermissions.BuyItems)) + + var prevBuyCrateItems = new Dictionary>(CargoManager.ItemsInBuyCrate); + foreach (var store in prevBuyCrateItems) { - var prevBuyCrateItems = new Dictionary>(CargoManager.ItemsInBuyCrate); - foreach (var store in prevBuyCrateItems) + foreach (var item in store.Value) { - foreach (var item in store.Value) - { - CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender); - } - } - foreach (var store in buyCrateItems) - { - foreach (var item in store.Value) - { - CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender); - } - } - var prevPurchasedItems = new Dictionary>(CargoManager.PurchasedItems); - foreach (var store in prevPurchasedItems) - { - CargoManager.SellBackPurchasedItems(store.Key, store.Value); - } - foreach (var store in purchasedItems) - { - CargoManager.PurchaseItems(store.Key, store.Value, false, sender); + CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender); } } + foreach (var store in buyCrateItems) + { + foreach (var item in store.Value) + { + CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender); + } + } + var prevPurchasedItems = new Dictionary>(CargoManager.PurchasedItems); + foreach (var store in prevPurchasedItems) + { + CargoManager.SellBackPurchasedItems(store.Key, store.Value); + } + foreach (var store in purchasedItems) + { + CargoManager.PurchaseItems(store.Key, store.Value, false, sender); + } bool allowedToSellSubItems = AllowedToManageCampaign(sender, ClientPermissions.SellSubItems); - if (allowedToManageCampaign || allowedToUseStore || allowedToSellSubItems) + if (allowedToSellSubItems) { var prevSubSellCrateItems = new Dictionary>(CargoManager.ItemsInSellFromSubCrate); foreach (var store in prevSubSellCrateItems) @@ -834,7 +814,7 @@ namespace Barotrauma } } bool allowedToSellInventoryItems = AllowedToManageCampaign(sender, ClientPermissions.SellInventoryItems); - if (allowedToManageCampaign || allowedToUseStore || (allowedToSellInventoryItems && allowedToSellSubItems)) + if (allowedToSellInventoryItems && allowedToSellSubItems) { // 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 @@ -867,37 +847,34 @@ namespace Barotrauma bool predicate(SoldItem i) => allowedToSellInventoryItems != (i.Origin == SoldItem.SellOrigin.Character); } - if (allowedToManageCampaign) + foreach (var (prefab, category, _) in purchasedUpgrades) { - foreach (var (prefab, category, _) in purchasedUpgrades) - { - UpgradeManager.PurchaseUpgrade(prefab, category, client: sender); + UpgradeManager.PurchaseUpgrade(prefab, category, client: sender); - // unstable logging - int price = prefab.Price.GetBuyprice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation); - 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) + // unstable logging + int price = prefab.Price.GetBuyprice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation); + 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) { - if (purchasedItemSwap.ItemToInstall == null) - { - UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove); - } - else - { - UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, client: sender); - } + UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove); } - foreach (Item item in Item.ItemList) + else { - if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item)) - { - UpgradeManager.CancelItemSwap(item); - item.PendingItemSwap = null; - } + 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; + } + } } public void ServerReadMoney(IReadMessage msg, Client sender) @@ -907,19 +884,26 @@ namespace Barotrauma switch (transfer.Sender) { case Some { Value: var id }: - - if (id != sender.CharacterID && !AllowedToManageCampaign(sender)) { return; } + if (id != sender.CharacterID && !AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) { return; } Wallet wallet = GetWalletByID(id); if (wallet is InvalidWallet) { return; } TransferMoney(wallet); break; - case None _: - if (!AllowedToManageCampaign(sender)) { return; } - - TransferMoney(Bank); + if (!AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) + { + if (transfer.Receiver is Some { Value: var receiverId } && receiverId == sender.CharacterID) + { + GameMain.Server?.Voting.StartTransferVote(sender, null, transfer.Amount, sender); + } + return; + } + else + { + TransferMoney(Bank); + } break; } @@ -952,10 +936,10 @@ namespace Barotrauma { NetWalletSetSalaryUpdate update = INetSerializableStruct.Read(msg); - if (!AllowedToManageCampaign(sender)) { return; } + if (!AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) { return; } Character targetCharacter = Character.CharacterList.FirstOrDefault(c => c.ID == update.Target); - targetCharacter?.Wallet.SetRewardDistrubiton(update.NewRewardDistribution); + targetCharacter?.Wallet.SetRewardDistribution(update.NewRewardDistribution); } public void ServerReadCrew(IReadMessage msg, Client sender) @@ -994,7 +978,7 @@ namespace Barotrauma List hiredCharacters = new List(); CharacterInfo firedCharacter = null; - if (location != null && AllowedToManageCampaign(sender)) + if (location != null && AllowedToManageCampaign(sender, ClientPermissions.ManageHires)) { if (fireCharacter) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs index 6f19e2077..e9c23978c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs @@ -18,7 +18,7 @@ namespace Barotrauma private struct RateLimitInfo { public int Requests; - public const int MaxRequests = 5; + public const int MaxRequests = 10; public DateTimeOffset Expiry; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 4373de52d..0e2b53f6e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -130,6 +130,8 @@ namespace Barotrauma.Networking KarmaManager.SelectPreset(serverSettings.KarmaPreset); serverSettings.SetPassword(password); + Voting = new Voting(); + ownerKey = ownKey; ownerSteamId = steamId; @@ -383,23 +385,7 @@ namespace Barotrauma.Networking TraitorManager?.Update(deltaTime); - if (serverSettings.Voting.VoteRunning) - { - Voting.SubVote.Timer += deltaTime; - - if (Voting.SubVote.Timer >= serverSettings.SubmarineVoteTimeout) - { - // Do not take unanswered into account for total - if (SubmarineVoteYesCount / (float)(SubmarineVoteYesCount + SubmarineVoteNoCount) >= serverSettings.SubmarineVoteRequiredRatio) - { - SwitchSubmarine(); - } - else - { - serverSettings.Voting.StopSubmarineVote(false); - } - } - } + Voting.Update(deltaTime); bool isCrewDead = connectedClients.All(c => c.Character == null || c.Character.IsDead || c.Character.IsIncapacitated); @@ -1073,7 +1059,7 @@ namespace Barotrauma.Networking ChatMessage.ServerRead(inc, c); break; case ClientNetObject.VOTE: - serverSettings.Voting.ServerRead(inc, c); + Voting.ServerRead(inc, c); break; default: return; @@ -1220,7 +1206,7 @@ namespace Barotrauma.Networking entityEventManager.Read(inc, c); break; case ClientNetObject.VOTE: - serverSettings.Voting.ServerRead(inc, c); + Voting.ServerRead(inc, c); break; case ClientNetObject.SPECTATING_POS: c.SpectatePos = new Vector2(inc.ReadSingle(), inc.ReadSingle()); @@ -1309,17 +1295,11 @@ namespace Barotrauma.Networking var mpCampaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; if (command == ClientPermissions.ManageRound && mpCampaign != null) { - if (!mpCampaign.AllowedToEndRound(sender)) - { - return; - } + //do nothing, ending campaign rounds is checked in more detail below } else if (command == ClientPermissions.ManageCampaign && mpCampaign != null) { - if (!mpCampaign.AllowedToManageCampaign(sender)) - { - return; - } + //do nothing, campaign permissions are checked in more detail in MultiplayerCampaign.ServerRead } else if (!sender.HasPermission(command)) { @@ -1381,21 +1361,26 @@ namespace Barotrauma.Networking bool end = inc.ReadBoolean(); if (end) { - bool save = inc.ReadBoolean(); - if (gameStarted) + if (mpCampaign == null || + mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageRound) || + mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign)) { - Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); - if (mpCampaign != null && Level.IsLoadedOutpost && save) + bool save = inc.ReadBoolean(); + if (gameStarted) { - mpCampaign.SavePlayers(); - GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); - SaveUtil.SaveGame(GameMain.GameSession.SavePath); + Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); + if (mpCampaign != null && Level.IsLoadedOutpost && save) + { + mpCampaign.SavePlayers(); + GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); + SaveUtil.SaveGame(GameMain.GameSession.SavePath); + } + else + { + save = false; + } + EndGame(wasSaved: save); } - else - { - save = false; - } - EndGame(wasSaved: save); } } else @@ -1403,14 +1388,17 @@ namespace Barotrauma.Networking bool continueCampaign = inc.ReadBoolean(); if (mpCampaign != null && mpCampaign.GameOver || continueCampaign) { - MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath); + if (mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageMap)) + { + MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath); + } } else if (!gameStarted && !initiatedStartGame) { Log("Client \"" + ClientLogName(sender) + "\" started the round.", ServerLog.MessageType.ServerMessage); StartGame(); } - else if (mpCampaign != null) + else if (mpCampaign != null && (mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageMap))) { var availableTransition = mpCampaign.GetAvailableTransition(out _, out _); //don't force location if we've teleported @@ -1473,34 +1461,20 @@ namespace Barotrauma.Networking if (GameMain.NetLobbyScreen.GameModes[modeIndex].Identifier == "multiplayercampaign") { - string[] saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false).ToArray(); - for (int i = 0; i < saveFiles.Length; i++) - { - XDocument doc = SaveUtil.LoadGameSessionDoc(saveFiles[i]); - if (doc?.Root != null) - { - saveFiles[i] = - string.Join(";", - saveFiles[i].Replace(';', ' '), - doc.Root.GetAttributeStringUnrestricted("submarine", ""), - doc.Root.GetAttributeStringUnrestricted("savetime", ""), - doc.Root.GetAttributeStringUnrestricted("selectedcontentpackages", "")); - } - } - + const int MaxSaves = 255; + var saveInfos = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false); IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO); - msg.Write((UInt16)saveFiles.Count()); - foreach (string saveFile in saveFiles) + msg.Write((byte)Math.Min(saveInfos.Count, MaxSaves)); + for (int i = 0; i < saveInfos.Count && i < MaxSaves; i++) { - msg.Write(saveFile); + msg.Write(saveInfos[i]); } - serverPeer.Send(msg, sender.Connection, DeliveryMethod.Reliable); } break; case ClientPermissions.ManageCampaign: - (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.ServerRead(inc, sender); + mpCampaign?.ServerRead(inc, sender); break; case ClientPermissions.ConsoleCommands: { @@ -1900,8 +1874,8 @@ namespace Barotrauma.Networking outmsg.Write(selectedShuttle.Name); outmsg.Write(selectedShuttle.MD5Hash.ToString()); - outmsg.Write(serverSettings.Voting.AllowSubVoting); - outmsg.Write(serverSettings.Voting.AllowModeVoting); + outmsg.Write(serverSettings.AllowSubVoting); + outmsg.Write(serverSettings.AllowModeVoting); outmsg.Write(serverSettings.VoiceChatEnabled); @@ -2036,9 +2010,9 @@ namespace Barotrauma.Networking SubmarineInfo selectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle; SubmarineInfo selectedSub; - if (serverSettings.Voting.AllowSubVoting) + if (serverSettings.AllowSubVoting) { - selectedSub = serverSettings.Voting.HighestVoted(VoteType.Sub, connectedClients); + selectedSub = Voting.HighestVoted(VoteType.Sub, connectedClients); if (selectedSub == null) { selectedSub = GameMain.NetLobbyScreen.SelectedSub; } } else @@ -2051,7 +2025,7 @@ namespace Barotrauma.Networking return false; } - GameModePreset selectedMode = serverSettings.Voting.HighestVoted(VoteType.Mode, connectedClients); + GameModePreset selectedMode = Voting.HighestVoted(VoteType.Mode, connectedClients); if (selectedMode == null) { selectedMode = GameMain.NetLobbyScreen.SelectedMode; } if (selectedMode == null) @@ -2455,11 +2429,8 @@ namespace Barotrauma.Networking yield return CoroutineStatus.Running; - if (GameMain.Server?.ServerSettings?.Voting != null) - { - GameMain.Server.ServerSettings.Voting.ResetVotes(GameMain.Server.ConnectedClients); - } - + Voting?.ResetVotes(GameMain.Server.ConnectedClients); + GameMain.GameScreen.Select(); Log("Round started.", ServerLog.MessageType.ServerMessage); @@ -3233,23 +3204,24 @@ namespace Barotrauma.Networking serverPeer.Send(msg, transfer.Connection, DeliveryMethod.ReliableOrdered); } - public void UpdateVoteStatus() + public void UpdateVoteStatus(bool checkActiveVote = true) { - if (connectedClients.Count == 0) return; + if (connectedClients.Count == 0) { return; } - if (serverSettings.Voting.VoteRunning) + if (checkActiveVote && Voting.ActiveVote != null) { + int yes = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote(Voting.ActiveVote.VoteType) == 2); + int no = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote(Voting.ActiveVote.VoteType) == 1); + int max = GameMain.Server.ConnectedClients.Count(c => c.InGame); // Required ratio cannot be met - if (SubmarineVoteNoCount / (float)SubmarineVoteMax > 1f - serverSettings.SubmarineVoteRequiredRatio) + if (no / (float)max > 1f - serverSettings.VoteRequiredRatio) { - serverSettings.Voting.StopSubmarineVote(false); - return; // Update will be re-sent via StopSubmarineVote + Voting.ActiveVote.Finish(Voting, passed: false); } - else if (SubmarineVoteYesCount / (float)SubmarineVoteMax >= serverSettings.SubmarineVoteRequiredRatio) + else if (yes / max >= serverSettings.VoteRequiredRatio) { - SwitchSubmarine(); - return; // Update will be re-sent via StopSubmarineVote - } + Voting.ActiveVote.Finish(Voting, passed: true); + } } Client.UpdateKickVotes(connectedClients); @@ -3279,10 +3251,12 @@ namespace Barotrauma.Networking SendVoteStatus(connectedClients); - if (serverSettings.Voting.AllowEndVoting && EndVoteMax > 0 && - ((float)EndVoteCount / (float)EndVoteMax) >= serverSettings.EndVoteRequiredRatio) + int endVoteCount = ConnectedClients.Count(c => c.HasSpawned && c.GetVote(VoteType.EndRound)); + int endVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned); + if (serverSettings.AllowEndVoting && endVoteMax > 0 && + ((float)endVoteCount / (float)endVoteMax) >= serverSettings.EndVoteRequiredRatio) { - Log("Ending round by votes (" + EndVoteCount + "/" + (EndVoteMax - EndVoteCount) + ")", ServerLog.MessageType.ServerMessage); + Log("Ending round by votes (" + endVoteCount + "/" + (endVoteMax - endVoteCount) + ")", ServerLog.MessageType.ServerMessage); EndGame(wasSaved: false); } } @@ -3294,7 +3268,7 @@ namespace Barotrauma.Networking IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY); msg.Write((byte)ServerNetObject.VOTE); - serverSettings.Voting.ServerWrite(msg); + Voting.ServerWrite(msg); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); foreach (var c in recipients) @@ -3303,11 +3277,13 @@ namespace Barotrauma.Networking } } - private void SwitchSubmarine() + public void SwitchSubmarine() { - SubmarineInfo targetSubmarine = Voting.SubVote.Sub; - VoteType voteType = Voting.SubVote.VoteType; - Client starter = Voting.SubVote.VoteStarter; + if (!(Voting.ActiveVote is Voting.SubmarineVote subVote)) { return; } + + SubmarineInfo targetSubmarine = subVote.Sub; + VoteType voteType = Voting.ActiveVote.VoteType; + Client starter = Voting.ActiveVote.VoteStarter; int deliveryFee = 0; switch (voteType) @@ -3318,7 +3294,7 @@ namespace Barotrauma.Networking GameMain.GameSession.PurchaseSubmarine(targetSubmarine, starter); break; case VoteType.SwitchSub: - deliveryFee = Voting.SubVote.DeliveryFee; + deliveryFee = subVote.DeliveryFee; break; default: return; @@ -3326,10 +3302,10 @@ namespace Barotrauma.Networking if (voteType != VoteType.PurchaseSub) { - SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(targetSubmarine, deliveryFee, starter); + GameMain.GameSession.SwitchSubmarine(targetSubmarine, deliveryFee, starter); } - serverSettings.Voting.StopSubmarineVote(true); + Voting.StopSubmarineVote(true); } public void UpdateClientPermissions(Client client) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 5580a12fe..6bf8c9216 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -312,8 +312,8 @@ namespace Barotrauma.Networking AutoRestart = doc.Root.GetAttributeBool("autorestart", false); - Voting.AllowSubVoting = SubSelectionMode == SelectionMode.Vote; - Voting.AllowModeVoting = ModeSelectionMode == SelectionMode.Vote; + AllowSubVoting = SubSelectionMode == SelectionMode.Vote; + AllowModeVoting = ModeSelectionMode == SelectionMode.Vote; selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f); GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index f6d9cf671..985bcdf3c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -7,59 +7,161 @@ namespace Barotrauma { partial class Voting { - public bool AllowSubVoting + public interface IVote { - get { return allowSubVoting; } - set { allowSubVoting = value; } - } - public bool AllowModeVoting - { - get { return allowModeVoting; } - set { allowModeVoting = value; } - } + public Client VoteStarter { get; } + public VoteType VoteType { get; } + public float Timer { get; set; } - public struct SubmarineVote + public VoteState State { get; set; } + + public void Finish(Voting voting, bool passed); + } + + public class SubmarineVote : IVote { - public Client VoteStarter; + public Client VoteStarter { get; } + public VoteType VoteType { get; } + public float Timer { get; set; } + + public VoteState State { get; set; } + public SubmarineInfo Sub; - public VoteType VoteType; - public float Timer; public int DeliveryFee; - public VoteState State; + + public SubmarineVote(Client starter, SubmarineInfo subInfo, int deliveryFee, VoteType voteType) + { + Sub = subInfo; + DeliveryFee = deliveryFee; + VoteType = voteType; + State = VoteState.Started; + VoteStarter = starter; + } + + public void Finish(Voting voting, bool passed) + { + if (passed) + { + GameMain.Server?.SwitchSubmarine(); + } + voting.StopSubmarineVote(passed); + } } - public static SubmarineVote SubVote; + public static IVote ActiveVote; + + public class TransferVote : IVote + { + public Client VoteStarter { get; } + public VoteType VoteType { get; } + public float Timer { get; set; } + + public VoteState State { get; set; } + + //null = bank + public readonly Client From, To; + public readonly int TransferAmount; + + public TransferVote(Client starter, Client from, int transferAmount, Client to) + { + VoteStarter = starter; + From = from; + To = to; + TransferAmount = transferAmount; + State = VoteState.Started; + VoteType = VoteType.TransferMoney; + } + + public void Finish(Voting voting, bool passed) + { + if (passed) + { + Wallet fromWallet = From == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : From.Character?.Wallet; + if (fromWallet.TryDeduct(TransferAmount)) + { + Wallet toWallet = To == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : To.Character?.Wallet; + toWallet.Give(TransferAmount); + } + } + voting.StopMoneyTransferVote(passed); + } + } + + private static readonly Queue pendingVotes = new Queue(); private void StartSubmarineVote(SubmarineInfo subInfo, VoteType voteType, Client sender) { - SubVote.Sub = subInfo; - SubVote.DeliveryFee = voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0; - SubVote.VoteType = voteType; - SubVote.State = VoteState.Started; - SubVote.VoteStarter = sender; - VoteRunning = true; + var subVote = new SubmarineVote( + sender, + subInfo, + voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0, + voteType); + StartOrEnqueueVote(subVote); sender.SetVote(voteType, 2); + GameMain.Server.UpdateVoteStatus(checkActiveVote: false); } public void StopSubmarineVote(bool passed) { - VoteRunning = false; - SubVote.State = passed ? VoteState.Passed : VoteState.Failed; + if (!(ActiveVote is SubmarineVote)) { return; } + StopActiveVote(passed); + } - GameMain.Server.UpdateVoteStatus(); + public void StopMoneyTransferVote(bool passed) + { + if (!(ActiveVote is TransferVote)) { return; } + StopActiveVote(passed); + } + + public void StopActiveVote(bool passed) + { + ActiveVote.State = passed ? VoteState.Passed : VoteState.Failed; + GameMain.Server.UpdateVoteStatus(checkActiveVote: false); - GameMain.NetworkMember.SubmarineVoteYesCount = GameMain.NetworkMember.SubmarineVoteNoCount = GameMain.NetworkMember.SubmarineVoteMax = 0; for (int i = 0; i < GameMain.NetworkMember.ConnectedClients.Count; i++) { - GameMain.NetworkMember.ConnectedClients[i].SetVote(SubVote.VoteType, 0); + GameMain.NetworkMember.ConnectedClients[i].SetVote(ActiveVote.VoteType, 0); } - SubVote.Sub = null; - SubVote.DeliveryFee = 0; - SubVote.VoteType = VoteType.Unknown; - SubVote.Timer = 0.0f; - SubVote.State = VoteState.None; - SubVote.VoteStarter = null; + ActiveVote = null; + if (pendingVotes.Any()) + { + ActiveVote = pendingVotes.Dequeue(); + } + } + + public void StartTransferVote(Client starter, Client from, int transferAmount, Client to) + { + StartOrEnqueueVote(new TransferVote(starter, from, transferAmount, to)); + starter.SetVote(VoteType.TransferMoney, 2); + GameMain.Server.UpdateVoteStatus(checkActiveVote: false); + } + + private void StartOrEnqueueVote(IVote vote) + { + if (ActiveVote == null) + { + ActiveVote = vote; + } + else + { + pendingVotes.Enqueue(vote); + } + } + + public void Update(float deltaTime) + { + if (ActiveVote == null) { return; } + + ActiveVote.Timer += deltaTime; + + if (ActiveVote.Timer >= GameMain.NetworkMember.ServerSettings.VoteTimeout) + { + // Do not take unanswered into account for total + int yes = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote(ActiveVote.VoteType) == 2); + int no = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote(ActiveVote.VoteType) == 1); + ActiveVote.Finish(this, passed: yes / (float)(yes + no) >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio); + } } public void ServerRead(IReadMessage inc, Client sender) @@ -97,10 +199,6 @@ namespace Barotrauma case VoteType.EndRound: if (!sender.HasSpawned) { return; } sender.SetVote(voteType, inc.ReadBoolean()); - - GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote(VoteType.EndRound)); - GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned); - break; case VoteType.Kick: byte kickedClientID = inc.ReadByte(); @@ -126,24 +224,34 @@ namespace Barotrauma case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseSub: case VoteType.SwitchSub: + case VoteType.TransferMoney: bool startVote = inc.ReadBoolean(); if (startVote) { - string subName = inc.ReadString(); - SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName); - if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo))) + if (voteType == VoteType.TransferMoney) { - StartSubmarineVote(subInfo, voteType, sender); + int amount = inc.ReadInt32(); + int fromClientId = inc.ReadByte(); + int toClientId = inc.ReadByte(); + pendingVotes.Enqueue(new TransferVote(sender, + GameMain.Server.ConnectedClients.Find(c => c.ID == fromClientId), + amount, + GameMain.Server.ConnectedClients.Find(c => c.ID == toClientId))); + } + else + { + string subName = inc.ReadString(); + SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName); + if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo))) + { + StartSubmarineVote(subInfo, voteType, sender); + } } } else { sender.SetVote(voteType, (int)inc.ReadByte()); } - - GameMain.Server.SubmarineVoteYesCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote(SubVote.VoteType) == 2); - GameMain.Server.SubmarineVoteNoCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote(SubVote.VoteType) == 1); - GameMain.Server.SubmarineVoteMax = GameMain.Server.ConnectedClients.Count(c => c.InGame); break; } @@ -154,10 +262,10 @@ namespace Barotrauma public void ServerWrite(IWriteMessage msg) { - if (GameMain.Server == null) return; + if (GameMain.Server == null) { return; } - msg.Write(allowSubVoting); - if (allowSubVoting) + msg.Write(GameMain.Server.ServerSettings.AllowSubVoting); + if (GameMain.Server.ServerSettings.AllowSubVoting) { IReadOnlyDictionary voteList = GetVoteCounts(VoteType.Sub, GameMain.Server.ConnectedClients); msg.Write((byte)voteList.Count); @@ -167,8 +275,8 @@ namespace Barotrauma msg.Write(vote.Key.Name); } } - msg.Write(AllowModeVoting); - if (allowModeVoting) + msg.Write(GameMain.Server.ServerSettings.AllowModeVoting); + if (GameMain.Server.ServerSettings.AllowModeVoting) { IReadOnlyDictionary voteList = GetVoteCounts(VoteType.Mode, GameMain.Server.ConnectedClients); msg.Write((byte)voteList.Count); @@ -178,60 +286,78 @@ namespace Barotrauma msg.Write(vote.Key.Identifier); } } - msg.Write(AllowEndVoting); - if (AllowEndVoting) + msg.Write(GameMain.Server.ServerSettings.AllowEndVoting); + if (GameMain.Server.ServerSettings.AllowEndVoting) { msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote(VoteType.EndRound))); msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned)); } - msg.Write(AllowVoteKick); + msg.Write(GameMain.Server.ServerSettings.AllowVoteKick); - msg.Write((byte)SubVote.State); - if (SubVote.State != VoteState.None) + msg.Write((byte)(ActiveVote?.State ?? VoteState.None)); + if (ActiveVote != null) { - msg.Write((byte)SubVote.VoteType); - - if (SubVote.VoteType != VoteType.Unknown) - { - var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote(SubVote.VoteType) == 2); + msg.Write((byte)ActiveVote.VoteType); + if (ActiveVote.State != VoteState.None && ActiveVote.VoteType != VoteType.Unknown) + { + var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote(ActiveVote.VoteType) == 2); msg.Write((byte)yesClients.Count); foreach (Client c in yesClients) { msg.Write(c.ID); } - var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote(SubVote.VoteType) == 1); + var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote(ActiveVote.VoteType) == 1); msg.Write((byte)noClients.Count); foreach (Client c in noClients) { msg.Write(c.ID); } - msg.Write((byte)GameMain.Server.SubmarineVoteMax); + msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.InGame)); - switch (SubVote.State) + switch (ActiveVote.State) { case VoteState.Started: - msg.Write(SubVote.Sub.Name); - msg.Write(SubVote.VoteStarter.ID); - msg.Write((byte)GameMain.Server.ServerSettings.SubmarineVoteTimeout); + msg.Write(ActiveVote.VoteStarter.ID); + msg.Write((byte)GameMain.Server.ServerSettings.VoteTimeout); + + switch (ActiveVote.VoteType) + { + case VoteType.PurchaseSub: + case VoteType.PurchaseAndSwitchSub: + case VoteType.SwitchSub: + msg.Write((ActiveVote as SubmarineVote).Sub.Name); + break; + case VoteType.TransferMoney: + var transferVote = (ActiveVote as TransferVote); + msg.Write(transferVote.From?.ID ?? 0); + msg.Write(transferVote.To?.ID ?? 0); + msg.Write(transferVote.TransferAmount); + break; + } + break; case VoteState.Running: // Nothing specific break; case VoteState.Passed: case VoteState.Failed: - msg.Write(SubVote.State == VoteState.Passed); - msg.Write(SubVote.Sub.Name); - if (SubVote.State == VoteState.Passed) + msg.Write(ActiveVote.State == VoteState.Passed); + switch (ActiveVote.VoteType) { - msg.Write((short)SubVote.DeliveryFee); + case VoteType.PurchaseSub: + case VoteType.PurchaseAndSwitchSub: + case VoteType.SwitchSub: + msg.Write((ActiveVote as SubmarineVote).Sub.Name); + msg.Write((short)(ActiveVote as SubmarineVote).DeliveryFee); + break; } break; - } + } } - } + } var readyClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote(VoteType.StartRound)); msg.Write((byte)readyClients.Count); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs index aaccf295c..e746fac64 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs @@ -192,7 +192,7 @@ namespace Barotrauma public override void Select() { base.Select(); - GameMain.Server.ServerSettings.Voting.ResetVotes(GameMain.Server.ConnectedClients); + GameMain.Server.Voting.ResetVotes(GameMain.Server.ConnectedClients); if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this) { GameMain.GameSession = null; diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 96429c964..86864cc3c 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.4.0 + 0.17.5.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml index 0bad02e51..efff061b6 100644 --- a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml +++ b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml @@ -8,7 +8,7 @@ + permissions="ManageRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog,ManageSettings,ManageMoney"> diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index c63af2b86..4d307d4c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -130,7 +130,7 @@ namespace Barotrauma } } - private Wallet wallet = new Wallet(); + private Wallet wallet; public Wallet Wallet { @@ -1055,8 +1055,13 @@ namespace Barotrauma return newCharacter; } + private Character(Submarine submarine, ushort id): base(submarine, id) + { + wallet = new Wallet(Option.Some(this)); + } + protected Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, RagdollParams ragdollParams = null) - : base(null, id) + : this(null, id) { this.Seed = seed; this.Prefab = prefab; diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index 0758b7042..70f3ebdaf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -172,11 +172,21 @@ namespace Barotrauma SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.SteamWorkshopId == Core.SteamWorkshopId) ?? ContentPackageManager.CorePackages.First()); } - SetRegular(Regular - .Select(p => ContentPackageManager.RegularPackages.Contains(p) - ? p - : ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2 => p2.SteamWorkshopId == p.SteamWorkshopId)) - .ToArray()); + + List newRegular = new List(); + foreach (var p in Regular) + { + if (ContentPackageManager.RegularPackages.Contains(p)) + { + newRegular.Add(p); + } + else if (ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2 + => p2.SteamWorkshopId == p.SteamWorkshopId) is { } newP) + { + newRegular.Add(newP); + } + } + SetRegular(newRegular); } public static void BackUp() diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs index 5e378bcb0..5711e7769 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs @@ -115,6 +115,24 @@ namespace Barotrauma } public void Remove() => Element.Remove(); + + public override bool Equals(object? obj) + { + return obj is ContentXElement element && this == element; + } + + public override int GetHashCode() + { + return HashCode.Combine(ContentPackage, Element); + } + + public static bool operator ==(in ContentXElement? a, in ContentXElement? b) + { + return a?.ContentPackage == b?.ContentPackage && a?.Element == b?.Element; + } + + public static bool operator !=(in ContentXElement? a, in ContentXElement? b) => + !(a == b); } public static class ContentXElementExtensions diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index 6bba12392..94123f36e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -1,4 +1,6 @@ -namespace Barotrauma +using System; + +namespace Barotrauma { public enum TransitionMode { @@ -146,4 +148,11 @@ AlwaysStayConscious, } + [Flags] + public enum CharacterType + { + Bot = 0b01, + Player = 0b10, + Both = Bot | Player + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 413e835e1..dc062b81e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -350,7 +350,7 @@ namespace Barotrauma float difficultyMultiplier = 1 + level.Difficulty / 100f; baseExperienceGain *= difficultyMultiplier; - IEnumerable crewCharacters = GameSession.GetSessionCrewCharacters(); + IEnumerable crewCharacters = GameSession.GetSessionCrewCharacters(CharacterType.Both); // use multipliers here so that we can easily add them together without introducing multiplicative XP stacking var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f); @@ -380,7 +380,7 @@ namespace Barotrauma GameAnalyticsManager.AddMoneyGainedEvent(totalReward, GameAnalyticsManager.MoneySource.MissionReward, Prefab.Identifier.Value); #if SERVER - totalReward = DistributeRewardsToCrew(GetSalaryEligibleCrew(), totalReward); + totalReward = DistributeRewardsToCrew(GameSession.GetSessionCrewCharacters(CharacterType.Player), totalReward); #endif if (totalReward > 0) { @@ -436,18 +436,6 @@ namespace Barotrauma } #endif - public static IEnumerable GetSalaryEligibleCrew() - { - if (!(GameMain.GameSession.CrewManager is { } crewManager)) { return Array.Empty(); } - - IEnumerable characters = crewManager.GetCharacters(); -#if SERVER - return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead).Concat(characters); -#elif CLIENT - return characters; -#endif - } - public static int GetRewardDistibutionSum(IEnumerable crew, int rewardDistribution = 0) => crew.Sum(c => c.Wallet.RewardDistribution) + rewardDistribution; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index 192830b91..0ac70c5c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -95,7 +95,7 @@ namespace Barotrauma public readonly bool IsSideObjective; - public readonly bool RequireWreck; + public readonly bool RequireWreck, RequireRuin; /// /// The mission can only be received when travelling from a location of the first type to a location of the second type @@ -152,6 +152,7 @@ namespace Barotrauma AllowRetry = element.GetAttributeBool("allowretry", false); IsSideObjective = element.GetAttributeBool("sideobjective", false); RequireWreck = element.GetAttributeBool("requirewreck", false); + RequireRuin = element.GetAttributeBool("requireruin", false); Commonness = element.GetAttributeInt("commonness", 1); if (element.GetAttribute("difficulty") != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index 23a121111..3d60228e2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -226,6 +226,7 @@ namespace Barotrauma public void SetPurchasedItems(Dictionary> purchasedItems) { + if (purchasedItems.Count == 0 && PurchasedItems.Count == 0) { return; } PurchasedItems.Clear(); foreach (var entry in purchasedItems) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs index 3e33d0669..8a70b39e3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs @@ -64,7 +64,7 @@ namespace Barotrauma if (reputationChange > 0f) { float reputationGainMultiplier = 1f; - foreach (Character character in GameSession.GetSessionCrewCharacters()) + foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both)) { reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Wallet.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Wallet.cs index abd84ef03..e10894b20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Wallet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Wallet.cs @@ -6,6 +6,7 @@ namespace Barotrauma { internal readonly struct WalletChangedEvent { + public readonly Option Owner; public readonly Wallet Wallet; public readonly WalletInfo Info; public readonly WalletChangedData ChangedData; @@ -15,6 +16,7 @@ namespace Barotrauma Wallet = wallet; Info = info; ChangedData = changedData; + Owner = wallet.Owner; } } @@ -109,6 +111,8 @@ namespace Barotrauma // ReSharper disable ValueParameterNotUsed internal sealed class InvalidWallet : Wallet { + public InvalidWallet(): base(Option.None()) { } + public override int Balance { get => 0; @@ -132,6 +136,8 @@ namespace Barotrauma AttrubuteNameRewardDistribution = "rewarddistribution", SaveElementName = "Wallet"; + public readonly Option Owner; + private int balance; public virtual int Balance @@ -148,9 +154,12 @@ namespace Barotrauma set => rewardDistribution = ClampRewardDistribution(value); } - public Wallet() { } + public Wallet(Option owner) + { + Owner = owner; + } - public Wallet(XElement element) + public Wallet(Option owner, XElement element): this(owner) { balance = ClampBalance(element.GetAttributeInt(AttributeNameBalance, 0)); rewardDistribution = ClampBalance(element.GetAttributeInt(AttrubuteNameRewardDistribution, 0)); @@ -185,7 +194,7 @@ namespace Barotrauma SettingsChanged(balanceChanged: Option.Some(-price), rewardChanged: Option.None()); } - public void SetRewardDistrubiton(int value) + public void SetRewardDistribution(int value) { int oldValue = RewardDistribution; RewardDistribution = value; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 64bd03cb5..5dbf55fa0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -53,7 +53,7 @@ namespace Barotrauma public int GetAddedMissionCount() { int count = 0; - foreach (Character character in GameSession.GetSessionCrewCharacters()) + foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both)) { count += (int)character.GetStatValue(StatTypes.ExtraMissionCount); } @@ -68,6 +68,15 @@ namespace Barotrauma abstract partial class CampaignMode : GameMode { + [NetworkSerialize] + public struct SaveInfo : INetSerializableStruct + { + public string FilePath; + public int SaveTime; + public string SubmarineName; + public string[] EnabledContentPackageNames; + } + public const int MaxMoney = int.MaxValue / 2; //about 1 billion public const int InitialMoney = 8500; @@ -180,13 +189,37 @@ namespace Barotrauma protected CampaignMode(GameModePreset preset) : base(preset) { - Bank = new Wallet + Bank = new Wallet(Option.None()) { Balance = InitialMoney }; CargoManager = new CargoManager(this); MedicalClinic = new MedicalClinic(this); + Identifier messageIdentifier = new Identifier("money"); + +#if CLIENT + OnMoneyChanged.RegisterOverwriteExisting(new Identifier("CampaignMoneyChangeNotification"), e => + { + if (!(e.ChangedData.BalanceChanged is Some { Value: var changed })) { return; } + + bool isGain = changed > 0; + + Color clr = isGain ? GUIStyle.Yellow : GUIStyle.Red; + + switch (e.Owner) + { + case Some { Value: var owner}: + owner.AddMessage(FormatMessage(), clr, playSound: Character.Controlled == owner, messageIdentifier, changed); + break; + case None _ when IsSinglePlayer: + Character.Controlled?.AddMessage(FormatMessage(), clr, playSound: true, messageIdentifier, changed); + break; + } + + string FormatMessage() => TextManager.GetWithVariable(isGain ? "moneygainformat" : "moneyloseformat", "[money]", TextManager.FormatCurrency(Math.Abs(changed))).ToString(); + }); +#endif } public virtual Wallet GetWallet(Client client = null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs index 970f2eaff..781b6599e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -167,7 +167,7 @@ namespace Barotrauma LoadStats(subElement); break; case Wallet.LowerCaseSaveElementName: - Bank = new Wallet(subElement); + Bank = new Wallet(Option.None(), subElement); break; #if SERVER case "savedexperiencepoints": @@ -183,7 +183,7 @@ namespace Barotrauma int oldMoney = element.GetAttributeInt("money", 0); if (oldMoney > 0) { - Bank = new Wallet + Bank = new Wallet(Option.None()) { Balance = oldMoney }; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index e6fc1f3e8..ac1b2e40b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -735,14 +735,40 @@ namespace Barotrauma partial void UpdateProjSpecific(float deltaTime); - public static IEnumerable GetSessionCrewCharacters() + /// + /// Returns a list of crew characters currently in the game with a given filter. + /// + /// Character type filter + /// + /// + /// In singleplayer mode the CharacterType.Player returns the currently controlled player. + /// + public static ImmutableHashSet GetSessionCrewCharacters(CharacterType type) { + if (!(GameMain.GameSession.CrewManager is { } crewManager)) { return ImmutableHashSet.Empty; } + + IEnumerable players; + IEnumerable bots; + HashSet characters = new HashSet(); + #if SERVER - return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead); -#else - if (GameMain.GameSession?.CrewManager is null) { return Enumerable.Empty(); } - return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null && !c.IsDead); + players = GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead); + bots = crewManager.GetCharacters().Where(c => !c.IsRemotePlayer); +#elif CLIENT + players = crewManager.GetCharacters().Where(c => c.IsPlayer); + bots = crewManager.GetCharacters().Where(c => c.IsBot); #endif + if (type.HasFlag(CharacterType.Bot)) + { + foreach (Character bot in bots) { characters.Add(bot); } + } + + if (type.HasFlag(CharacterType.Player)) + { + foreach (Character player in players) { characters.Add(player); } + } + + return characters.ToImmutableHashSet(); } public void EndRound(string endMessage, List? traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) @@ -754,7 +780,7 @@ namespace Barotrauma try { - ImmutableArray crewCharacters = GetSessionCrewCharacters().ToImmutableArray(); + ImmutableHashSet crewCharacters = GetSessionCrewCharacters(CharacterType.Both); int prevMoney = GetAmountOfMoney(crewCharacters); @@ -898,7 +924,7 @@ namespace Barotrauma } } - foreach (Character c in GetSessionCrewCharacters()) + foreach (Character c in GetSessionCrewCharacters(CharacterType.Both)) { foreach (var itemSelectedDuration in c.ItemSelectedDurations) { @@ -948,25 +974,25 @@ namespace Barotrauma #endif } - public static bool IsCompatibleWithEnabledContentPackages(IList contentPackagePaths, out LocalizedString errorMsg) + public static bool IsCompatibleWithEnabledContentPackages(IList contentPackageNames, out LocalizedString errorMsg) { errorMsg = ""; //no known content packages, must be an older save file - if (!contentPackagePaths.Any()) { return true; } + if (!contentPackageNames.Any()) { return true; } List missingPackages = new List(); - foreach (string packagePath in contentPackagePaths) + foreach (string packageName in contentPackageNames) { - if (!ContentPackageManager.EnabledPackages.All.Any(cp => cp.Path == packagePath)) + if (!ContentPackageManager.EnabledPackages.All.Any(cp => cp.NameMatches(packageName))) { - missingPackages.Add(packagePath); + missingPackages.Add(packageName); } } List excessPackages = new List(); foreach (ContentPackage cp in ContentPackageManager.EnabledPackages.All) { if (!cp.HasMultiplayerSyncedContent) { continue; } - if (!contentPackagePaths.Any(p => p == cp.Path)) + if (!contentPackageNames.Any(p => cp.NameMatches(p))) { excessPackages.Add(cp.Name); } @@ -976,9 +1002,9 @@ namespace Barotrauma if (missingPackages.Count == 0 && missingPackages.Count == 0) { var enabledPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).ToImmutableArray(); - for (int i = 0; i < contentPackagePaths.Count && i < enabledPackages.Length; i++) + for (int i = 0; i < contentPackageNames.Count && i < enabledPackages.Length; i++) { - if (contentPackagePaths[i] != enabledPackages[i].Path) + if (!enabledPackages[i].NameMatches(contentPackageNames[i])) { orderMismatch = true; break; @@ -1009,7 +1035,7 @@ namespace Barotrauma if (orderMismatch) { if (!errorMsg.IsNullOrEmpty()) { errorMsg += "\n"; } - errorMsg += TextManager.GetWithVariable("campaignmode.contentpackageordermismatch", "[loadorder]", string.Join(", ", contentPackagePaths)); + errorMsg += TextManager.GetWithVariable("campaignmode.contentpackageordermismatch", "[loadorder]", string.Join(", ", contentPackageNames)); } return false; @@ -1040,8 +1066,8 @@ namespace Barotrauma } } if (Map != null) { rootElement.Add(new XAttribute("mapseed", Map.Seed)); } - rootElement.Add(new XAttribute("selectedcontentpackages", - string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).Select(cp => cp.Path)))); + rootElement.Add(new XAttribute("selectedcontentpackagenames", + string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).Select(cp => cp.Name.Replace("|", @"\|"))))); ((CampaignMode)GameMode).Save(doc.Root); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index 7d03844eb..296277c9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Xml.Linq; -using Barotrauma.Extensions; namespace Barotrauma.Items.Components { @@ -13,6 +12,7 @@ namespace Barotrauma.Items.Components { [Editable] public LimbType LimbType { get; set; } + [Editable] public Vector2 Position { get; set; } @@ -33,7 +33,7 @@ namespace Barotrauma.Items.Components partial class Controller : ItemComponent, IServerSerializable { //where the limbs of the user should be positioned when using the controller - private readonly List limbPositions; + private readonly List limbPositions = new List(); private Direction dir; @@ -117,38 +117,9 @@ namespace Barotrauma.Items.Components public Controller(Item item, ContentXElement element) : base(item, element) { - limbPositions = new List(); - userPos = element.GetAttributeVector2("UserPos", Vector2.Zero); - Enum.TryParse(element.GetAttributeString("direction", "None"), out dir); - - foreach (var subElement in element.Elements()) - { - if (subElement.Name != "limbposition") { continue; } - string limbStr = subElement.GetAttributeString("limb", ""); - if (!Enum.TryParse(subElement.GetAttribute("limb").Value, out LimbType limbType)) - { - DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type."); - } - else - { - LimbPos limbPos = new LimbPos(limbType, - subElement.GetAttributeVector2("position", Vector2.Zero), - subElement.GetAttributeBool("allowusinglimb", false)); - limbPositions.Add(limbPos); - if (!limbPos.AllowUsingLimb) - { - if (limbType == LimbType.RightHand || limbType == LimbType.RightForearm || limbType == LimbType.RightArm || - limbType == LimbType.LeftHand || limbType == LimbType.LeftForearm || limbType == LimbType.LeftArm) - { - AllowAiming = false; - } - } - } - - } - + LoadLimbPositions(element); IsActive = true; } @@ -529,5 +500,63 @@ namespace Barotrauma.Items.Components } partial void HideHUDs(bool value); + + public override XElement Save(XElement parentElement) + { + return SaveLimbPositions(base.Save(parentElement)); + } + + public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap) + { + base.Load(componentElement, usePrefabValues, idRemap); + if (GameMain.GameSession?.GameMode?.Preset == GameModePreset.TestMode) + { + LoadLimbPositions(componentElement); + } + } + + private XElement SaveLimbPositions(XElement element) + { + if (Screen.Selected == GameMain.SubEditorScreen) + { + foreach (var limbPos in limbPositions) + { + element.Add(new XElement("limbposition", + new XAttribute("limb", limbPos.LimbType), + new XAttribute("position", XMLExtensions.Vector2ToString(limbPos.Position)), + new XAttribute("allowusinglimb", limbPos.AllowUsingLimb))); + } + } + return element; + } + + private void LoadLimbPositions(XElement element) + { + limbPositions.Clear(); + foreach (var subElement in element.Elements()) + { + if (subElement.Name != "limbposition") { continue; } + string limbStr = subElement.GetAttributeString("limb", ""); + if (!Enum.TryParse(subElement.GetAttribute("limb").Value, out LimbType limbType)) + { + DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type."); + } + else + { + LimbPos limbPos = new LimbPos(limbType, + subElement.GetAttributeVector2("position", Vector2.Zero), + subElement.GetAttributeBool("allowusinglimb", false)); + limbPositions.Add(limbPos); + if (!limbPos.AllowUsingLimb) + { + if (limbType == LimbType.RightHand || limbType == LimbType.RightForearm || limbType == LimbType.RightArm || + limbType == LimbType.LeftHand || limbType == LimbType.LeftForearm || limbType == LimbType.LeftArm) + { + AllowAiming = false; + } + } + } + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 27b9ae460..ee10aee6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -80,6 +80,8 @@ namespace Barotrauma.Items.Components private float progressState; + private readonly Dictionary fabricationLimits = new Dictionary(); + public Fabricator(Item item, ContentXElement element) : base(item, element) { @@ -105,6 +107,10 @@ namespace Barotrauma.Items.Components } } fabricationRecipes.Add(recipe.RecipeHash, recipe); + if (recipe.FabricationLimitMax >= 0) + { + fabricationLimits.Add(recipe.RecipeHash, Rand.Range(recipe.FabricationLimitMin, recipe.FabricationLimitMax + 1)); + } } } this.fabricationRecipes = fabricationRecipes.ToImmutableDictionary(); @@ -244,7 +250,7 @@ namespace Barotrauma.Items.Components itemList.Enabled = true; if (activateButton != null) { - activateButton.Text = TextManager.Get("FabricatorCreate"); + activateButton.Text = TextManager.Get(CreateButtonText); } #endif fabricatedItem = null; @@ -400,8 +406,22 @@ namespace Barotrauma.Items.Components quality = GetFabricatedItemQuality(fabricatedItem, user); } + int amount = (int)fabricationitemAmount.Value; + if (fabricationLimits.ContainsKey(fabricatedItem.RecipeHash)) + { + if (amount > fabricationLimits[fabricatedItem.RecipeHash]) + { + amount = fabricationLimits[fabricatedItem.RecipeHash]; + fabricationLimits[fabricatedItem.RecipeHash] = 0; + } + else + { + fabricationLimits[fabricatedItem.RecipeHash] -= amount; + } + } + var tempUser = user; - for (int i = 0; i < (int)fabricationitemAmount.Value; i++) + for (int i = 0; i < amount; i++) { float outCondition = fabricatedItem.OutCondition; GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + fabricatedItem.TargetItem.Identifier); @@ -535,6 +555,11 @@ namespace Barotrauma.Items.Components } } + if (fabricationLimits.TryGetValue(fabricableItem.RecipeHash, out int amount) && amount <= 0) + { + return false; + } + return fabricableItem.RequiredItems.All(requiredItem => { int availablePrefabsAmount = 0; @@ -698,7 +723,6 @@ namespace Barotrauma.Items.Components componentElement.Add(new XAttribute("fabricateditemidentifier", fabricatedItem.TargetItem.Identifier)); componentElement.Add(new XAttribute("savedtimeuntilready", timeUntilReady.ToString("G", CultureInfo.InvariantCulture))); componentElement.Add(new XAttribute("savedrequiredtime", requiredTime.ToString("G", CultureInfo.InvariantCulture))); - } return componentElement; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 93c68e6ac..411627eef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -5,6 +5,7 @@ using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using Microsoft.Xna.Framework; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -114,7 +115,7 @@ namespace Barotrauma private readonly Quality qualityComponent; - private readonly Queue impactQueue = new Queue(); + private readonly ConcurrentQueue impactQueue = new ConcurrentQueue(); //a dictionary containing lists of the status effects in all the components of the item private readonly bool[] hasStatusEffectsOfType; @@ -1695,9 +1696,8 @@ namespace Barotrauma public override void Update(float deltaTime, Camera cam) { - while (impactQueue.Count > 0) + while (impactQueue.TryDequeue(out float impact)) { - float impact = impactQueue.Dequeue(); HandleCollision(impact); } @@ -1933,10 +1933,7 @@ namespace Barotrauma if (contact.FixtureA.Body == f1.Body) { normal = -normal; } float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal); - lock (impactQueue) - { - impactQueue.Enqueue(impact); - } + impactQueue.Enqueue(impact); return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 33d5b6289..8e25476cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -119,6 +119,11 @@ namespace Barotrauma public readonly uint RecipeHash; public readonly int Amount; + /// + /// How many of this item the fabricator can create (< 0 = unlimited) + /// + public readonly int FabricationLimitMin, FabricationLimitMax; + public FabricationRecipe(XElement element, Identifier itemPrefab) { TargetItemPrefabIdentifier = itemPrefab; @@ -141,6 +146,10 @@ namespace Barotrauma RequiresRecipe = element.GetAttributeBool("requiresrecipe", false); Amount = element.GetAttributeInt("amount", 1); + int limitDefault = element.GetAttributeInt("fabricationlimit", -1); + FabricationLimitMin = element.GetAttributeInt(nameof(FabricationLimitMin), limitDefault); + FabricationLimitMax = element.GetAttributeInt(nameof(FabricationLimitMax), limitDefault); + foreach (var subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -973,7 +982,11 @@ namespace Barotrauma public int? GetMinPrice() { - int? minPrice = StorePrices.Values.Min(p => p.Price); + int? minPrice = null; + if (StorePrices != null && StorePrices.Any()) + { + minPrice = StorePrices.Values.Min(p => p.Price); + } if (minPrice.HasValue) { if (DefaultPrice != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index f184f08f4..4db3d907b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -831,7 +831,13 @@ namespace Barotrauma } List ruinPositions = new List(); - for (int i = 0; i < GenerationParams.RuinCount; i++) + int ruinCount = GenerationParams.RuinCount; + if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireRuin) ?? false) + { + ruinCount = Math.Max(ruinCount, 1); + } + + for (int i = 0; i < ruinCount; i++) { Point ruinSize = new Point(5000); int limitLeft = Math.Max(startPosition.X, ruinSize.X / 2); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs index d1fc85553..cb6640f49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs @@ -387,14 +387,14 @@ namespace Barotrauma set; } - [Serialize(3, IsPropertySaveable.Yes, description: "Minimum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)] + [Serialize(10, IsPropertySaveable.Yes, description: "Minimum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)] public int AbyssResourceClustersMin { get; set; } - [Serialize(20, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)] + [Serialize(50, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)] public int AbyssResourceClustersMax { get; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 4b77d08fa..84d2510c2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -257,7 +257,7 @@ namespace Barotrauma } DailySpecials.Clear(); int extraSpecialSalesCount = Location.GetExtraSpecialSalesCount(); - for (int i = 0; i < DailySpecialsCount + extraSpecialSalesCount; i++) + for (int i = 0; i < Location.DailySpecialsCount + extraSpecialSalesCount; i++) { if (availableStock.None()) { break; } var item = ToolBox.SelectWeightedRandom(availableStock.Keys.ToList(), availableStock.Values.ToList(), Rand.RandSync.Unsynced); @@ -266,7 +266,7 @@ namespace Barotrauma availableStock.Remove(item); } RequestedGoods.Clear(); - for (int i = 0; i < RequestedGoodsCount; i++) + for (int i = 0; i < Location.RequestedGoodsCount; i++) { var item = ItemPrefab.Prefabs.GetRandom(p => p.CanBeSold && !RequestedGoods.Contains(p) && @@ -359,8 +359,8 @@ namespace Barotrauma /// How many map progress steps it takes before the discounts should be updated. /// private const int SpecialsUpdateInterval = 3; - private const int DailySpecialsCount = 3; - private const int RequestedGoodsCount = 3; + private int DailySpecialsCount => Type.DailySpecialsCount; + private int RequestedGoodsCount => Type.RequestedGoodsCount; private int StepsSinceSpecialsUpdated { get; set; } public HashSet StoreIdentifiers { get; } = new HashSet(); @@ -1226,7 +1226,7 @@ namespace Barotrauma public int GetExtraSpecialSalesCount() { - var characters = GameSession.GetSessionCrewCharacters(); + var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both); if (!characters.Any()) { return 0; } return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount)); } @@ -1252,7 +1252,7 @@ namespace Barotrauma Discovered = true; if (checkTalents) { - GameSession.GetSessionCrewCharacters().ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this))); + GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this))); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs index 2bfa64c65..e81e2795d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs @@ -1,13 +1,11 @@ -using Microsoft.Xna.Framework; +using Barotrauma.IO; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; -using Barotrauma.IO; using System.Linq; -using System.Xml.Linq; -using Barotrauma.Extensions; -using System.Collections.Immutable; namespace Barotrauma { @@ -21,8 +19,9 @@ namespace Barotrauma // private readonly ImmutableArray<(Identifier Name, float Commonness)> hireableJobs; private readonly float totalHireableWeight; - - public Dictionary CommonnessPerZone = new Dictionary(); + + public readonly Dictionary CommonnessPerZone = new Dictionary(); + public readonly Dictionary MinCountPerZone = new Dictionary(); public readonly LocalizedString Name; @@ -65,7 +64,7 @@ namespace Barotrauma get; private set; } - + public string ReplaceInRadiation { get; } public Sprite Sprite { get; private set; } @@ -86,6 +85,8 @@ namespace Barotrauma /// In percentages /// public int StorePriceModifierRange { get; } = 5; + public int DailySpecialsCount { get; } = 1; + public int RequestedGoodsCount { get; } = 1; public List StoreBalanceStatuses { get; } = new List() { @@ -144,7 +145,7 @@ namespace Barotrauma names = new List() { "Name file not found" }; } - string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", new string[] { "" }); + string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", Array.Empty()); foreach (string commonnessPerZoneStr in commonnessPerZoneStrs) { string[] splitCommonnessPerZone = commonnessPerZoneStr.Split(':'); @@ -152,12 +153,26 @@ namespace Barotrauma !int.TryParse(splitCommonnessPerZone[0].Trim(), out int zoneIndex) || !float.TryParse(splitCommonnessPerZone[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float zoneCommonness)) { - DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone0index: zone0commonness, zone1index: zone1commonness\""); + DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone1index: zone1commonness, zone2index: zone2commonness\""); break; } CommonnessPerZone[zoneIndex] = zoneCommonness; } + string[] minCountPerZoneStrs = element.GetAttributeStringArray("mincountperzone", Array.Empty()); + foreach (string minCountPerZoneStr in minCountPerZoneStrs) + { + string[] splitMinCountPerZone = minCountPerZoneStr.Split(':'); + if (splitMinCountPerZone.Length != 2 || + !int.TryParse(splitMinCountPerZone[0].Trim(), out int zoneIndex) || + !int.TryParse(splitMinCountPerZone[1].Trim(), out int minCount)) + { + DebugConsole.ThrowError("Failed to read minimum count values for location type \"" + Identifier + "\" - minimum counts should be given in the format \"zone1index: zone1mincount, zone2index: zone2mincount\""); + break; + } + MinCountPerZone[zoneIndex] = minCount; + } + var hireableJobs = new List<(Identifier, float)>(); foreach (var subElement in element.Elements()) { @@ -205,6 +220,8 @@ namespace Barotrauma StoreBalanceStatuses.Add(new StoreBalanceStatus(percentage, modifier, color)); } } + DailySpecialsCount = subElement.GetAttributeInt("dailyspecialscount", DailySpecialsCount); + RequestedGoodsCount = subElement.GetAttributeInt("requestedgoodscount", RequestedGoodsCount); break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 0a793e304..478350b10 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -218,16 +218,24 @@ namespace Barotrauma foreach (Location location in Locations) { - if (location.Type.Identifier != "city" && - location.Type.Identifier != "outpost") - { - continue; - } + if (location.Type.Identifier != "outpost") { continue; } if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X) { CurrentLocation = StartLocation = furthestDiscoveredLocation = location; } } + //if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost + if (CurrentLocation == null) + { + foreach (Location location in Locations) + { + if (!location.Type.HasOutpost) { continue; } + if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X) + { + CurrentLocation = StartLocation = furthestDiscoveredLocation = location; + } + } + } System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation."); CurrentLocation.Discover(true); @@ -273,6 +281,7 @@ namespace Barotrauma } voronoiSites.Clear(); + Dictionary> locationsPerZone = new Dictionary>(); foreach (GraphEdge edge in edges) { if (edge.Point1 == edge.Point2) { continue; } @@ -301,7 +310,24 @@ namespace Barotrauma Vector2 position = points[positionIndex]; if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; } int zone = GetZoneIndex(position.X); - newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.ServerAndClient), requireOutpost: false, existingLocations: Locations); + if (!locationsPerZone.ContainsKey(zone)) + { + locationsPerZone[zone] = new List(); + } + + LocationType forceLocationType = null; + foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier)) + { + if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount) + { + forceLocationType = locationType; + break; + } + } + + newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.ServerAndClient), + requireOutpost: false, forceLocationType: forceLocationType, existingLocations: Locations); + locationsPerZone[zone].Add(newLocations[i]); Locations.Add(newLocations[i]); } @@ -448,8 +474,7 @@ namespace Barotrauma Connections[i].Locations[1]; if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier == "abandoned") { - #warning TODO: determinism? - leftMostLocation.ChangeType(LocationType.Prefabs.First(lt => lt.HasOutpost && lt.Identifier != "abandoned")); + leftMostLocation.ChangeType(LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned")); } leftMostLocation.IsGateBetweenBiomes = true; Connections[i].Locked = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs index 336462b62..594adb116 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -752,6 +752,7 @@ namespace Barotrauma bool solutionFound = false; foreach (PlacedModule module in movableModules) { + if (module.ThisGap.ConnectedDoor == null && module.PreviousGap.ConnectedDoor == null) { continue; } Vector2 moveDir = GetMoveDir(module.ThisGapPosition); Vector2 moveStep = moveDir * 50.0f; Vector2 currentMove = Vector2.Zero; @@ -1093,6 +1094,10 @@ namespace Barotrauma } thisWayPoint.Remove(); } + else + { + DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.PreviousModule.Info.Name}\"."); + } gapToRemove.ConnectedDoor?.Item.Remove(); if (hallwayLength <= 1.0f) { gapToRemove?.Remove(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index 8dde595c0..c4e2cf5fa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -28,8 +28,6 @@ namespace Barotrauma partial class SubmarineInfo : IDisposable { - public const string SavePath = "Submarines"; - private static List savedSubmarines = new List(); public static IEnumerable SavedSubmarines => savedSubmarines; @@ -578,58 +576,14 @@ namespace Barotrauma if (File.Exists(savedSubmarines[i].FilePath)) { bool isDownloadedSub = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SaveUtil.SubmarineDownloadFolder); - bool isInSubmarinesFolder = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SavePath); bool isInContentPackage = contentPackageSubs.Any(f => f.Path == savedSubmarines[i].FilePath); if (isDownloadedSub) { continue; } - if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && (isInSubmarinesFolder || isInContentPackage)) { continue; } + if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && isInContentPackage) { continue; } } savedSubmarines[i].Dispose(); } - if (!Directory.Exists(SavePath)) - { - try - { - Directory.CreateDirectory(SavePath); - } - catch (Exception e) - { - DebugConsole.ThrowError("Directory \"" + SavePath + "\" not found and creating the directory failed.", e); - return; - } - } - - List filePaths; - string[] subDirectories; - - try - { - filePaths = Directory.GetFiles(SavePath).ToList(); - subDirectories = Directory.GetDirectories(SavePath).Where(s => - { - DirectoryInfo dir = new DirectoryInfo(s); - return !dir.Attributes.HasFlag(System.IO.FileAttributes.Hidden) && !dir.Name.StartsWith("."); - }).ToArray(); - } - catch (Exception e) - { - DebugConsole.ThrowError("Couldn't open directory \"" + SavePath + "\"!", e); - return; - } - - foreach (string subDirectory in subDirectories) - { - try - { - filePaths.AddRange(Directory.GetFiles(subDirectory).ToList()); - } - catch (Exception e) - { - DebugConsole.ThrowError("Couldn't open subdirectory \"" + subDirectory + "\"!", e); - return; - } - } - + List filePaths = new List(); foreach (BaseSubFile subFile in contentPackageSubs) { if (!filePaths.Any(fp => fp == subFile.Path)) @@ -643,34 +597,7 @@ namespace Barotrauma foreach (string path in filePaths) { var subInfo = new SubmarineInfo(path); - if (subInfo.IsFileCorrupted) - { -#if CLIENT - if (DebugConsole.IsOpen) { DebugConsole.Toggle(); } - var deleteSubPrompt = new GUIMessageBox( - TextManager.Get("Error"), - TextManager.GetWithVariable("SubLoadError", "[subname]", subInfo.Name) + "\n" + - TextManager.GetWithVariable("DeleteFileVerification", "[filename]", subInfo.Name), - new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }); - - string filePath = path; - deleteSubPrompt.Buttons[0].OnClicked += (btn, userdata) => - { - try - { - File.Delete(filePath); - } - catch (Exception e) - { - DebugConsole.ThrowError($"Failed to delete file \"{filePath}\".", e); - } - deleteSubPrompt.Close(); - return true; - }; - deleteSubPrompt.Buttons[1].OnClicked += deleteSubPrompt.Close; -#endif - } - else + if (!subInfo.IsFileCorrupted) { savedSubmarines.Add(subInfo); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs index 0c3b25d2f..d18e54eef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs @@ -22,11 +22,12 @@ namespace Barotrauma.Networking ManageSettings = 0x200, ManagePermissions = 0x400, KarmaImmunity = 0x800, - BuyItems = 0x1000, + ManageMoney = 0x1000, SellInventoryItems = 0x2000, SellSubItems = 0x4000, - CampaignStore = 0x8000, - All = 0xFFFF + ManageMap = 0x8000, + ManageHires = 0x10000, + All = 0x1FFFF } class PermissionPreset diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs index e405ecb4f..b8c2d6419 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs @@ -116,7 +116,8 @@ namespace Barotrauma.Networking StartRound, PurchaseAndSwitchSub, PurchaseSub, - SwitchSub + SwitchSub, + TransferMoney } public enum ReadyCheckState @@ -179,11 +180,11 @@ namespace Barotrauma.Networking protected ServerSettings serverSettings; + public Voting Voting { get; protected set; } + protected TimeSpan updateInterval; protected DateTime updateTimer; - public int EndVoteCount, EndVoteMax, SubmarineVoteYesCount, SubmarineVoteNoCount, SubmarineVoteMax; - protected bool gameStarted; protected RespawnManager respawnManager; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index f844f406a..c182cfdf5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -278,8 +278,6 @@ namespace Barotrauma.Networking { ServerLog = new ServerLog(serverName); - Voting = new Voting(); - Whitelist = new WhiteList(); BanList = new BanList(); @@ -378,8 +376,6 @@ namespace Barotrauma.Networking public ServerLog ServerLog; - public Voting Voting; - public Dictionary MonsterEnabled { get; private set; } public const int MaxExtraCargoItemsOfType = 10; @@ -577,34 +573,20 @@ namespace Barotrauma.Networking [Serialize(true, IsPropertySaveable.Yes)] public bool AllowVoteKick { - get - { - return Voting.AllowVoteKick; - } - set - { - Voting.AllowVoteKick = value; - } + get; set; } [Serialize(true, IsPropertySaveable.Yes)] public bool AllowEndVoting { - get - { - return Voting.AllowEndVoting; - } - set - { - Voting.AllowEndVoting = value; - } + get; set; } private bool allowRespawn; [Serialize(true, IsPropertySaveable.Yes)] public bool AllowRespawn { - get { return allowRespawn; ; } + get { return allowRespawn; } set { if (allowRespawn == value) { return; } @@ -779,7 +761,7 @@ namespace Barotrauma.Networking set { subSelectionMode = value; - Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote; + AllowSubVoting = subSelectionMode == SelectionMode.Vote; ServerDetailsChanged = true; } } @@ -792,7 +774,7 @@ namespace Barotrauma.Networking set { modeSelectionMode = value; - Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote; + AllowModeVoting = modeSelectionMode == SelectionMode.Vote; ServerDetailsChanged = true; } } @@ -807,14 +789,14 @@ namespace Barotrauma.Networking } [Serialize(0.6f, IsPropertySaveable.Yes)] - public float SubmarineVoteRequiredRatio + public float VoteRequiredRatio { get; private set; } [Serialize(30f, IsPropertySaveable.Yes)] - public float SubmarineVoteTimeout + public float VoteTimeout { get; private set; @@ -928,6 +910,59 @@ namespace Barotrauma.Networking set { maxMissionCount = MathHelper.Clamp(value, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); } } + private bool allowSubVoting; + //Don't serialize: the value is set based on SubSelectionMode + public bool AllowSubVoting + { + get { return allowSubVoting; } + set + { + if (value == allowSubVoting) { return; } + allowSubVoting = value; +#if CLIENT + GameMain.NetLobbyScreen.SubList.Enabled = value || + (GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.SelectSub)); + var subVotesLabel = GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true) as GUITextBlock; + subVotesLabel.Visible = value; + var subVisButton = GameMain.NetLobbyScreen.SubVisibilityButton; + subVisButton.RectTransform.AbsoluteOffset + = new Point(value ? (int)(subVotesLabel.TextSize.X + subVisButton.Rect.Width) : 0, 0); + + GameMain.Client?.Voting.UpdateVoteTexts(null, VoteType.Sub); + GameMain.NetLobbyScreen.SubList.Deselect(); +#endif + } + } + + private bool allowModeVoting; + //Don't serialize: the value is set based on ModeSelectionMode + public bool AllowModeVoting + { + get { return allowModeVoting; } + set + { + if (value == allowModeVoting) { return; } + allowModeVoting = value; +#if CLIENT + GameMain.NetLobbyScreen.ModeList.Enabled = + value || + (GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.SelectMode)); + GameMain.NetLobbyScreen.Frame.FindChild("modevotes", true).Visible = value; + // Disable modes that cannot be voted on + foreach (var guiComponent in GameMain.NetLobbyScreen.ModeList.Content.Children) + { + if (guiComponent is GUIFrame frame) + { + frame.CanBeFocused = !allowModeVoting || ((GameModePreset)frame.UserData).Votable; + } + } + GameMain.Client?.Voting.UpdateVoteTexts(null, VoteType.Mode); + GameMain.NetLobbyScreen.ModeList.Deselect(); +#endif + } + } + + public void SetPassword(string password) { if (string.IsNullOrEmpty(password)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs index ecab1d04a..5fd149b62 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs @@ -1,19 +1,11 @@ using Barotrauma.Networking; +using System; using System.Collections.Generic; -using System.Linq; namespace Barotrauma { partial class Voting { - private bool allowSubVoting, allowModeVoting; - - public bool AllowVoteKick = true; - - public bool AllowEndVoting = true; - - public bool VoteRunning = false; - public enum VoteState { None = 0, Started = 1, Running = 2, Passed = 3, Failed = 4 }; private IReadOnlyDictionary GetVoteCounts(VoteType voteType, List voters) @@ -39,12 +31,12 @@ namespace Barotrauma public T HighestVoted(VoteType voteType, List voters) { - if (voteType == VoteType.Sub && !AllowSubVoting) return default(T); - if (voteType == VoteType.Mode && !AllowModeVoting) return default(T); + if (voteType == VoteType.Sub && !GameMain.NetworkMember.ServerSettings.AllowSubVoting) { return default; } + if (voteType == VoteType.Mode && !GameMain.NetworkMember.ServerSettings.AllowModeVoting) { return default; } IReadOnlyDictionary voteList = GetVoteCounts(voteType, voters); - T selected = default(T); + T selected = default; int highestVotes = 0; foreach (KeyValuePair votable in voteList) { @@ -71,11 +63,13 @@ namespace Barotrauma { client.ResetVotes(); } - - GameMain.NetworkMember.EndVoteCount = 0; - GameMain.NetworkMember.EndVoteMax = 0; - #if CLIENT + foreach (VoteType voteType in Enum.GetValues(typeof(VoteType))) + { + SetVoteCountYes(voteType, 0); + SetVoteCountNo(voteType, 0); + SetVoteCountMax(voteType, 0); + } UpdateVoteTexts(connectedClients, VoteType.Mode); UpdateVoteTexts(connectedClients, VoteType.Sub); #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs b/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs index 9684fbe89..e44c75dd2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs @@ -348,6 +348,14 @@ namespace Barotrauma.Steam string val = attribute.Value.CleanUpPathCrossPlatform(correctFilenameCase: false); + //Handle mods that have been mangled by pre-modding-refactor + //copying of post-modding-refactor mods (what a clusterfuck) + int modDirStrIndex = val.IndexOf(ContentPath.ModDirStr, StringComparison.OrdinalIgnoreCase); + if (modDirStrIndex >= 0) + { + val = val[modDirStrIndex..]; + } + //Handle really old mods (0.9.0.4-era) that might be structured as //%ModDir%/Mods/[NAME]/[RESOURCE] string fullSrcPath = Path.Combine(fileListDir, val).CleanUpPath(); @@ -418,7 +426,7 @@ namespace Barotrauma.Steam File.Copy(from, to, overwrite: true); } - private static async Task CopyDirectory(string fileListDir, string modName, string from, string to) + public static async Task CopyDirectory(string fileListDir, string modName, string from, string to) { from = Path.GetFullPath(from); to = Path.GetFullPath(to); Directory.CreateDirectory(to); diff --git a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs index 3963df1ca..9f3239e41 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs @@ -79,10 +79,10 @@ namespace Barotrauma { roundData.EnteredCrushDepth.Add(c); } - else if (Level.Loaded.GetRealWorldDepth(c.WorldPosition.Y) < Level.Loaded.RealWorldCrushDepth * 0.5f) + else if (Level.Loaded.GetRealWorldDepth(c.WorldPosition.Y) < Level.Loaded.RealWorldCrushDepth - 500.0f) { //all characters that have entered crush depth and are still alive get an achievement - if (roundData.EnteredCrushDepth.Contains(c)) UnlockAchievement(c, "survivecrushdepth".ToIdentifier()); + if (roundData.EnteredCrushDepth.Contains(c)) { UnlockAchievement(c, "survivecrushdepth".ToIdentifier()); } } } } @@ -426,7 +426,7 @@ namespace Barotrauma !c.IsDead && c.TeamID != CharacterTeamType.FriendlyNPC && !(c.AIController is EnemyAIController) && - (c.Submarine == gameSession.Submarine || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost))); + (c.Submarine == gameSession.Submarine || gameSession.Submarine.GetConnectedSubs().Contains(c.Submarine) || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost))); if (charactersInSub.Count == 1) { @@ -454,6 +454,10 @@ namespace Barotrauma } foreach (Character character in charactersInSub) { + if (roundData.EnteredCrushDepth.Contains(character)) + { + UnlockAchievement(character, "survivecrushdepth".ToIdentifier()); + } if (character.Info.Job == null) { continue; } UnlockAchievement(character, $"{character.Info.Job.Prefab.Identifier}round".ToIdentifier()); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/Md5Hash.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs rename to Barotrauma/BarotraumaShared/SharedSource/Utils/Md5Hash.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/NamedEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/NamedEvent.cs index 7b46b9467..9f0a2f711 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/NamedEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/NamedEvent.cs @@ -37,6 +37,12 @@ namespace Barotrauma events.Remove(identifier); } + public void TryDeregister(Identifier identifier) + { + if (!HasEvent(identifier)) { return; } + Deregister(identifier); + } + public bool HasEvent(Identifier identifier) => events.ContainsKey(identifier); public void Invoke(T data) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index 4101d9513..e0b919969 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Xml.Linq; using Steamworks.Data; using Color = Microsoft.Xna.Framework.Color; +using System.Text.RegularExpressions; namespace Barotrauma { @@ -227,7 +228,7 @@ namespace Barotrauma return Path.Combine(folder, saveName); } - public static IEnumerable GetSaveFiles(SaveType saveType, bool includeInCompatible = true) + public static IReadOnlyList GetSaveFiles(SaveType saveType, bool includeInCompatible = true) { string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder; if (!Directory.Exists(folder)) @@ -250,18 +251,61 @@ namespace Barotrauma files.AddRange(Directory.GetFiles(legacyFolder, "*.save", System.IO.SearchOption.TopDirectoryOnly)); } - if (!includeInCompatible) + List saveInfos = new List(); + foreach (string file in files) { - for (int i = files.Count - 1; i >= 0; i--) + XDocument doc = LoadGameSessionDoc(file); + if (!includeInCompatible && !IsSaveFileCompatible(doc)) { - XDocument doc = LoadGameSessionDoc(files[i]); - if (!IsSaveFileCompatible(doc)) + continue; + } + if (doc?.Root == null) + { + saveInfos.Add(new CampaignMode.SaveInfo() { - files.RemoveAt(i); + FilePath = file + }); + } + else + { + List enabledContentPackageNames = new List(); + + //backwards compatibility + string enabledContentPackagePathsStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackages", string.Empty); + foreach (string packagePath in enabledContentPackagePathsStr.Split('|')) + { + if (string.IsNullOrEmpty(packagePath)) { continue; } + //change paths to names + string fileName = Path.GetFileNameWithoutExtension(packagePath); + if (fileName == "filelist") + { + enabledContentPackageNames.Add(Path.GetFileName(Path.GetDirectoryName(packagePath))); + } + else + { + enabledContentPackageNames.Add(fileName); + } } + + string enabledContentPackageNamesStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackagenames", string.Empty); + //split on pipes, excluding pipes preceded by \ + foreach (string packageName in Regex.Split(enabledContentPackageNamesStr, @"(?