diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs index 204452ec4..477cf476f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs @@ -21,6 +21,11 @@ namespace Barotrauma throw new System.Exception("Error in CargoMission.ClientReadInitial: item count does not match the server count (" + itemCount + " != " + items.Count + ", mission: " + Prefab.Identifier + ")"); } if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } + if (requiredDeliveryAmount > items.Count) + { + DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver."); + requiredDeliveryAmount = items.Count; + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index bba4952ca..0e9692371 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -1274,6 +1274,7 @@ namespace Barotrauma private static void UpdateSavingIndicator(float deltaTime) { + if (Style.SavingIndicator == null) { return; } lock (mutex) { if (timeUntilSavingIndicatorDisabled.HasValue) @@ -1651,7 +1652,7 @@ namespace Barotrauma private static void DrawSavingIndicator(SpriteBatch spriteBatch) { - if (!IsSavingIndicatorVisible) { return; } + if (!IsSavingIndicatorVisible || Style.SavingIndicator == null) { return; } var sheet = Style.SavingIndicator; Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2(); sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index f2d72b36f..c52aa864f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -1319,17 +1319,26 @@ namespace Barotrauma // When using Deselect to close the interface, make sure it's not a seconday mouse button click on a node // That should be reserved for opening manual assignment - var hitDeselect = PlayerInput.KeyHit(InputType.Deselect) && (!PlayerInput.SecondaryMouseButtonClicked() || - (optionNodes.None(n => GUI.IsMouseOn(n.Item1)) && shortcutNodes.None(n => GUI.IsMouseOn(n)))); + bool isMouseOnOptionNode = optionNodes.Any(n => GUI.IsMouseOn(n.Item1)); + bool isMouseOnShortcutNode = !isMouseOnOptionNode && shortcutNodes.Any(n => GUI.IsMouseOn(n)); + bool hitDeselect = PlayerInput.KeyHit(InputType.Deselect) && + (!PlayerInput.SecondaryMouseButtonClicked() || (!isMouseOnOptionNode && !isMouseOnShortcutNode)); + + bool isBoundToPrimaryMouse = GameMain.Config.KeyBind(InputType.Command).MouseButton is MouseButton mouseButton && + (mouseButton == MouseButton.PrimaryMouse || mouseButton == (PlayerInput.MouseButtonsSwapped() ? MouseButton.RightMouse : MouseButton.LeftMouse)); + bool canToggleInterface = !isBoundToPrimaryMouse || + (!isMouseOnOptionNode && !isMouseOnShortcutNode && extraOptionNodes.None(n => GUI.IsMouseOn(n)) && !GUI.IsMouseOn(returnNode)); + // TODO: Consider using HUD.CloseHUD() instead of KeyHit(Escape), the former method is also used for health UI if (hitDeselect || PlayerInput.KeyHit(Keys.Escape) || !CanIssueOrders || - (PlayerInput.KeyHit(InputType.Command) && selectedNode == null && !clicklessSelectionActive)) + (canToggleInterface && PlayerInput.KeyHit(InputType.Command) && selectedNode == null && !clicklessSelectionActive)) { DisableCommandUI(); } else if (PlayerInput.KeyUp(InputType.Command)) { - if (!isOpeningClick && clicklessSelectionActive && timeSelected < 0.15f) + // Clickless selection behavior + if (canToggleInterface && !isOpeningClick && clicklessSelectionActive && timeSelected < 0.15f) { DisableCommandUI(); } @@ -1344,6 +1353,7 @@ namespace Barotrauma } else if (PlayerInput.KeyDown(InputType.Command) && (targetFrame == null || !targetFrame.Visible)) { + // Clickless selection behavior if (!GUI.IsMouseOn(centerNode)) { clicklessSelectionActive = true; @@ -1914,6 +1924,7 @@ namespace Barotrauma private bool NavigateForward(GUIButton node, object userData) { + if (commandFrame == null) { return false; } if (!(optionNodes.Find(n => n.Item1 == node) is Tuple optionNode) || !optionNodes.Remove(optionNode)) { shortcutNodes.Remove(node); @@ -1953,6 +1964,7 @@ namespace Barotrauma private bool NavigateBackward(GUIButton node, object userData) { + if (commandFrame == null) { return false; } RemoveOptionNodes(); if (targetFrame != null) { targetFrame.Visible = false; } // TODO: Center node could move to option node instead of being removed diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 73cdda02b..8120ab775 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -474,8 +474,7 @@ namespace Barotrauma public override void Update(float deltaTime, Camera cam, bool isSubInventory = false) { - // Need to update the infiltrator's inventory because they use id cards to access the sub. TODO: We don't probably need to update everything. - if (!AccessibleWhenAlive && !character.IsDead && (character.Params.AI == null || !character.Params.AI.Infiltrate)) + if (!AccessibleWhenAlive && !character.IsDead) { syncItemsDelay = Math.Max(syncItemsDelay - deltaTime, 0.0f); return; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index 7986a538b..11c39d491 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -898,6 +898,22 @@ namespace Barotrauma.Items.Components signalWarningText.Visible = false; } + foreach (AITarget aiTarget in AITarget.List) + { + if (!aiTarget.Enabled) { continue; } + if (string.IsNullOrEmpty(aiTarget.SonarLabel) || aiTarget.SoundRange <= 0.0f) { continue; } + + if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < aiTarget.SoundRange * aiTarget.SoundRange) + { + DrawMarker(spriteBatch, + aiTarget.SonarLabel, + aiTarget.SonarIconIdentifier, + aiTarget, + aiTarget.WorldPosition, transducerCenter, + displayScale, center, DisplayRadius * 0.975f); + } + } + if (GameMain.GameSession == null || Level.Loaded == null) { return; } if (Level.Loaded.StartLocation != null) @@ -931,23 +947,7 @@ namespace Barotrauma.Items.Components cave.StartPos.ToVector2(), transducerCenter, displayScale, center, DisplayRadius); } - - foreach (AITarget aiTarget in AITarget.List) - { - if (!aiTarget.Enabled) { continue; } - if (string.IsNullOrEmpty(aiTarget.SonarLabel) || aiTarget.SoundRange <= 0.0f) { continue; } - - if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < aiTarget.SoundRange * aiTarget.SoundRange) - { - DrawMarker(spriteBatch, - aiTarget.SonarLabel, - aiTarget.SonarIconIdentifier, - aiTarget, - aiTarget.WorldPosition, transducerCenter, - displayScale, center, DisplayRadius * 0.975f); - } - } - + foreach (Mission mission in GameMain.GameSession.Missions) { if (!string.IsNullOrWhiteSpace(mission.SonarLabel)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index e370c2eee..a328939a1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -220,6 +220,8 @@ namespace Barotrauma public int tooltipDisplayedCondition; + public bool ForceTooltipRefresh; + public SlotReference(Inventory parentInventory, VisualSlot slot, int slotIndex, bool isSubSlot, Inventory subInventory = null) { ParentInventory = parentInventory; @@ -234,12 +236,14 @@ namespace Barotrauma public bool TooltipNeedsRefresh() { + if (ForceTooltipRefresh) { return true; } if (Item == null) { return false; } return (int)Item.ConditionPercentage != tooltipDisplayedCondition; } public void RefreshTooltip() { + ForceTooltipRefresh = false; if (Item == null) { return; } IEnumerable itemsInSlot = null; if (ParentInventory != null && Item != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs index 41525b40c..743c367f5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs @@ -160,17 +160,6 @@ namespace Barotrauma public void Update(float deltaTime) { - if (ParticleEmitters != null) - { - for (int i = 0; i < ParticleEmitters.Length; i++) - { - if (ParticleEmitterTriggers[i] != null && !ParticleEmitterTriggers[i].IsTriggered) continue; - Vector2 emitterPos = LocalToWorld(Prefab.EmitterPositions[i]); - ParticleEmitters[i].Emit(deltaTime, emitterPos, hullGuess: null, - angle: ParticleEmitters[i].Prefab.CopyEntityAngle ? Rotation : 0.0f); - } - } - CurrentRotation = Rotation; if (ActivePrefab.SwingFrequency > 0.0f) { @@ -214,6 +203,17 @@ namespace Barotrauma UpdateDeformations(deltaTime); } + if (ParticleEmitters != null) + { + for (int i = 0; i < ParticleEmitters.Length; i++) + { + if (ParticleEmitterTriggers[i] != null && !ParticleEmitterTriggers[i].IsTriggered) { continue; } + Vector2 emitterPos = LocalToWorld(Prefab.EmitterPositions[i]); + ParticleEmitters[i].Emit(deltaTime, emitterPos, hullGuess: null, + angle: ParticleEmitters[i].Prefab.CopyEntityAngle ? -CurrentRotation + MathHelper.PiOver2 : 0.0f); + } + } + for (int i = 0; i < Sounds.Length; i++) { if (Sounds[i] == null) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs index 6fb90c45e..067da4a73 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs @@ -573,7 +573,7 @@ namespace Barotrauma if (!spriteRecorder.ReadyToRender) { string waitText = !loadTask.IsCompleted ? - "Generating preview..." : + TextManager.Get("generatingsubmarinepreview", fallBackTag: "loading") : (loadTask.Exception?.ToString() ?? "Task completed without marking as ready to render"); Vector2 origin = (GUI.Font.MeasureString(waitText) * 0.5f); origin.X = MathF.Round(origin.X); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs index 3f4f5ab1c..cadf2a25b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs @@ -12,9 +12,10 @@ namespace Barotrauma.Networking { protected class ServerContentPackage { - public string Name; - public string Hash; - public UInt64 WorkshopId; + public readonly string Name; + public readonly string Hash; + public readonly UInt64 WorkshopId; + public readonly DateTime InstallTime; public ContentPackage RegularPackage { @@ -32,21 +33,22 @@ namespace Barotrauma.Networking } } - public ServerContentPackage(string name, string hash, UInt64 workshopId) + public ServerContentPackage(string name, string hash, UInt64 workshopId, DateTime installTime) { Name = name; Hash = hash; WorkshopId = workshopId; + InstallTime = installTime; } } protected string GetPackageStr(ContentPackage contentPackage) { - return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")"; + return $"\"{contentPackage.Name}\" (hash {contentPackage.MD5hash.ShortHash})"; } protected string GetPackageStr(ServerContentPackage contentPackage) { - return "\"" + contentPackage.Name + "\" (hash " + Md5Hash.GetShortHash(contentPackage.Hash) + ")"; + return $"\"{contentPackage.Name}\" (hash {Md5Hash.GetShortHash(contentPackage.Hash)})"; } public delegate void MessageCallback(IReadMessage message); @@ -129,7 +131,10 @@ namespace Barotrauma.Networking string name = inc.ReadString(); string hash = inc.ReadString(); UInt64 workshopId = inc.ReadUInt64(); - var pkg = new ServerContentPackage(name, hash, workshopId); + UInt32 installTimeDiffSeconds = inc.ReadUInt32(); + DateTime installTime = DateTime.UtcNow + TimeSpan.FromSeconds(installTimeDiffSeconds); + + var pkg = new ServerContentPackage(name, hash, workshopId, installTime); if (pkg.CorePackage != null) { corePackage = pkg; @@ -147,12 +152,12 @@ namespace Barotrauma.Networking if (missingPackages.Count > 0) { var nonDownloadable = missingPackages.Where(p => p.WorkshopId == 0); - var mismatchedButDownloaded = missingPackages.Where(p => + var mismatchedButDownloaded = missingPackages.Where(remote => { - var localMatching = ContentPackage.RegularPackages.Find(l => l.SteamWorkshopId != 0 && p.WorkshopId == l.SteamWorkshopId); - localMatching ??= ContentPackage.CorePackages.Find(l => l.SteamWorkshopId != 0 && p.WorkshopId == l.SteamWorkshopId); - - return localMatching != null; + return ContentPackage.AllPackages.Any(local => + local.SteamWorkshopId != 0 && /* is a Workshop item */ + remote.WorkshopId == local.SteamWorkshopId && /* ids match */ + remote.InstallTime < local.InstallTime/* remote is older than local */); }); if (mismatchedButDownloaded.Any()) @@ -212,7 +217,9 @@ namespace Barotrauma.Networking msgBox.Buttons[0].OnClicked = (yesBtn, userdata) => { GameMain.ServerListScreen.Select(); - GameMain.ServerListScreen.DownloadWorkshopItems(missingPackages.Select(p => p.WorkshopId), serverName, ServerConnection.EndPointString); + IEnumerable downloads = + missingPackages.Select(p => new ServerListScreen.PendingWorkshopDownload(p.Hash, p.WorkshopId)); + GameMain.ServerListScreen.DownloadWorkshopItems(downloads, serverName, ServerConnection.EndPointString); return true; }; msgBox.Buttons[0].OnClicked += msgBox.Close; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs index 76974601b..8f08b8966 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs @@ -319,7 +319,7 @@ namespace Barotrauma.Networking } if (!successSend) { - DebugConsole.ThrowError("Failed to send message to remote peer! (" + length.ToString() + " bytes)"); + DebugConsole.AddWarning("Failed to send message to remote peer! (" + length.ToString() + " bytes)"); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs index 0cbcc298c..19b81611c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs @@ -317,7 +317,7 @@ namespace Barotrauma.Networking } if (!successSend) { - DebugConsole.ThrowError("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)"); + DebugConsole.AddWarning("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)"); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs index 90e5bb010..d9647ac72 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs @@ -1,4 +1,5 @@ -using Barotrauma.IO; +using Barotrauma.Extensions; +using Barotrauma.IO; using Barotrauma.Networking; using RestSharp; using System; @@ -921,10 +922,6 @@ namespace Barotrauma.Steam return null; } -#if DEBUG - item = item?.WithPrivateVisibility(); -#endif - contentPackage.GameVersion = GameMain.Version; contentPackage.Save(contentPackage.Path); @@ -969,6 +966,9 @@ namespace Barotrauma.Steam } else { + //nuke the existing steamworks cache for the item we just published + ForceRedownload(task.Result.FileId); + workshopPublishStatus.Success = true; workshopPublishStatus.Result = task.Result; DebugConsole.NewMessage("Published workshop item " + item?.Title + " successfully.", Microsoft.Xna.Framework.Color.LightGreen); @@ -986,41 +986,61 @@ namespace Barotrauma.Steam yield return CoroutineStatus.Success; } + /// + /// Forces a Workshop item to redownload. + /// + public static void ForceRedownload(Steamworks.Data.PublishedFileId itemId, Action onDownloadFinished = null) + { + Steamworks.Ugc.Item itemToNuke = new Steamworks.Ugc.Item(itemId); + string directory = itemToNuke.Directory; + if (Directory.Exists(directory)) + { + try + { + Directory.Delete(directory, true); + } + catch (Exception e) { DebugConsole.ThrowError("Failed to delete Workshop item cache", e); } + } + DebugConsole.NewMessage($"{itemToNuke.Download(onDownloadFinished, highPriority: true)}"); + } + /// /// Installs a workshop item by moving it to the game folder. /// - public static bool InstallWorkshopItem(Steamworks.Ugc.Item? item, out string errorMsg, bool enableContentPackage = false, bool suppressInstallNotif = false) + public static bool InstallWorkshopItem(Steamworks.Ugc.Item? itemOrNull, out string errorMsg, bool enableContentPackage = false, bool suppressInstallNotif = false, Action onInstall = null) { - if (!(item?.IsInstalled ?? false)) + errorMsg = "Item is null"; + if (!itemOrNull.TryGetValue(out Steamworks.Ugc.Item item)) { return false; } + if (!item.IsInstalled) { - errorMsg = TextManager.GetWithVariable("WorkshopErrorInstallRequiredToEnable", "[itemname]", item?.Title ?? "[NULL]"); + errorMsg = TextManager.GetWithVariable("WorkshopErrorInstallRequiredToEnable", "[itemname]", item.Title); DebugConsole.NewMessage(errorMsg, Color.Red); return false; } - string metaDataFilePath = Path.Combine(item?.Directory, MetadataFileName); + string metaDataFilePath = Path.Combine(item.Directory, MetadataFileName); if (!File.Exists(metaDataFilePath)) { - errorMsg = TextManager.GetWithVariable("WorkshopErrorInstallRequiredToEnable", "[itemname]", item?.Title); + errorMsg = TextManager.GetWithVariable("WorkshopErrorInstallRequiredToEnable", "[itemname]", item.Title); DebugConsole.ThrowError(errorMsg); return false; } ContentPackage contentPackage = new ContentPackage(metaDataFilePath) { - SteamWorkshopId = item?.Id ?? 0 + SteamWorkshopId = item.Id }; string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage); List existingPackages = ContentPackage.AllPackages.Where(cp => cp.Path.CleanUpPath() == newContentPackagePath.CleanUpPath()).ToList(); if (existingPackages.Any()) { - if (item?.Owner.Id != Steamworks.SteamClient.SteamId) + if (item.Owner.Id != Steamworks.SteamClient.SteamId) { errorMsg = TextManager.GetWithVariables("WorkshopErrorSamePathInstalled", new string[] { "[itemname]", "[itempath]" }, - new string[] { item?.Title, Path.GetDirectoryName(newContentPackagePath) }); + new string[] { item.Title, Path.GetDirectoryName(newContentPackagePath) }); return false; } else @@ -1041,12 +1061,12 @@ namespace Barotrauma.Steam lock (modCopiesInProgress) { - if (modCopiesInProgress.ContainsKey(item.Value.Id)) + if (modCopiesInProgress.ContainsKey(item.Id)) { errorMsg = ""; return true; } newTask = CopyWorkShopItemAsync(item, contentPackage, newContentPackagePath, metaDataFilePath); - modCopiesInProgress.Add(item.Value.Id, newTask); + modCopiesInProgress.Add(item.Id, newTask); } TaskPool.Add("CopyWorkShopItemAsync", @@ -1058,14 +1078,14 @@ namespace Barotrauma.Steam { if (task.IsFaulted || task.IsCanceled) { - DebugConsole.ThrowError($"Failed to copy \"{item?.Title}\"", task.Exception); + DebugConsole.ThrowError($"Failed to copy \"{item.Title}\"", task.Exception); GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Red); return; } string errorMsg = ((Task)task).Result; if (!string.IsNullOrWhiteSpace(errorMsg)) { - DebugConsole.ThrowError($"Failed to copy \"{item?.Title}\": {errorMsg}"); + DebugConsole.ThrowError($"Failed to copy \"{item.Title}\": {errorMsg}"); GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Red); return; } @@ -1074,8 +1094,8 @@ namespace Barotrauma.Steam var newPackage = new ContentPackage(cp.Path, newContentPackagePath) { - SteamWorkshopId = item?.Id ?? 0, - InstallTime = item?.Updated > item?.Created ? item?.Updated : item?.Created + SteamWorkshopId = item.Id, + InstallTime = item.Updated > item.Created ? item.Updated : item.Created }; foreach (ContentFile contentFile in newPackage.Files) @@ -1123,8 +1143,9 @@ namespace Barotrauma.Steam GameMain.Config.SuppressModFolderWatcher = false; - GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Green); + onInstall?.Invoke(newPackage); + GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Green); } catch { @@ -1132,7 +1153,7 @@ namespace Barotrauma.Steam } finally { - modCopiesInProgress.Remove(item.Value.Id); + modCopiesInProgress.Remove(item.Id); } }); @@ -1144,9 +1165,27 @@ namespace Barotrauma.Steam /// Asynchronously copies a Workshop item into the Mods folder. /// /// Returns an empty string on success, otherwise returns an error message. - private async static Task CopyWorkShopItemAsync(Steamworks.Ugc.Item? item, ContentPackage contentPackage, string newContentPackagePath, string metaDataFilePath) + private async static Task CopyWorkShopItemAsync(Steamworks.Ugc.Item? itemOrNull, ContentPackage contentPackage, string newContentPackagePath, string metaDataFilePath) { await Task.Yield(); + if (!itemOrNull.TryGetValue(out Steamworks.Ugc.Item item)) { return "Item is null"; } + + if (item.NeedsUpdate) + { + item.Download(highPriority: true); + await Task.Delay(1000); + } + while (item.NeedsUpdate && !item.IsDownloading && !item.IsDownloadPending && !item.IsInstalled) + { + if (!item.IsDownloading && !item.IsDownloadPending) + { + if (!item.Download()) + { + return TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item.Title); + } + } + await Task.Delay(1000); + } string targetPath = Path.GetDirectoryName(GetWorkshopItemContentPackagePath(contentPackage)); string copyingPath = Path.Combine(targetPath, CopyIndicatorFileName); @@ -1157,18 +1196,18 @@ namespace Barotrauma.Steam Directory.CreateDirectory(targetPath); File.WriteAllText(copyingPath, "TEMPORARY FILE"); - SaveUtil.CopyFolder(item?.Directory, targetPath, copySubDirs: true, overwriteExisting: false); + SaveUtil.CopyFolder(item.Directory, targetPath, copySubDirs: true, overwriteExisting: false); File.Delete(copyingPath); return ""; } - var allPackageFiles = Directory.GetFiles(item?.Directory, "*", System.IO.SearchOption.AllDirectories); + var allPackageFiles = Directory.GetFiles(item.Directory, "*", System.IO.SearchOption.AllDirectories); List nonContentFiles = new List(); foreach (string file in allPackageFiles) { if (file == metaDataFilePath) { continue; } - string relativePath = UpdaterUtil.GetRelativePath(file, item?.Directory); + string relativePath = UpdaterUtil.GetRelativePath(file, item.Directory); string fullPath = Path.GetFullPath(relativePath); if (contentPackage.Files.Any(f => { string fp = Path.GetFullPath(f.Path); return fp == fullPath; })) { continue; } nonContentFiles.Add(relativePath); @@ -1198,14 +1237,14 @@ namespace Barotrauma.Steam foreach (ContentFile contentFile in contentPackage.Files) { - contentFile.Path = contentFile.Path.CleanUpPathCrossPlatform(correctFilenameCase: true, item?.Directory); - string sourceFile = Path.Combine(item?.Directory, contentFile.Path); + contentFile.Path = contentFile.Path.CleanUpPathCrossPlatform(correctFilenameCase: true, item.Directory); + string sourceFile = Path.Combine(item.Directory, contentFile.Path); if (!File.Exists(sourceFile)) { string[] splitPath = contentFile.Path.Split('/'); if (splitPath.Length >= 2 && splitPath[0] == "Mods") { - sourceFile = Path.Combine(item?.Directory, string.Join("/", splitPath.Skip(2))); + sourceFile = Path.Combine(item.Directory, string.Join("/", splitPath.Skip(2))); } } @@ -1225,7 +1264,7 @@ namespace Barotrauma.Steam //if the external file doesn't exist, we cannot enable the package else if (!File.Exists(contentFile.Path)) { - errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item?.Title) + " " + TextManager.GetWithVariable("WorkshopFileNotFound", "[path]", "\"" + contentFile.Path + "\""); + errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item.Title) + " " + TextManager.GetWithVariable("WorkshopFileNotFound", "[path]", "\"" + contentFile.Path + "\""); return errorMsg; } continue; @@ -1240,7 +1279,7 @@ namespace Barotrauma.Steam else { //file not present in either the mod or the game folder -> cannot enable the package - errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item?.Title) + " " + TextManager.GetWithVariable("WorkshopFileNotFound", "[path]", "\"" + contentFile.Path + "\""); + errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item.Title) + " " + TextManager.GetWithVariable("WorkshopFileNotFound", "[path]", "\"" + contentFile.Path + "\""); return errorMsg; } } @@ -1252,7 +1291,7 @@ namespace Barotrauma.Steam foreach (string nonContentFile in nonContentFiles) { - string sourceFile = Path.Combine(item?.Directory, nonContentFile); + string sourceFile = Path.Combine(item.Directory, nonContentFile); if (!File.Exists(sourceFile)) { continue; } string destinationPath = CorrectContentFilePath(nonContentFile, ContentType.None, contentPackage, false); Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); @@ -1351,43 +1390,50 @@ namespace Barotrauma.Steam string metaDataPath = Path.Combine(item?.Directory, MetadataFileName); if (!File.Exists(metaDataPath)) { - throw new System.IO.FileNotFoundException("Metadata file for the Workshop item \"" + item?.Title + "\" not found. The file may be corrupted."); + DebugConsole.ThrowError("Metadata file for the Workshop item \"" + item?.Title + "\" not found. The file may be corrupted.", appendStackTrace: true); + return null; } ContentPackage contentPackage = new ContentPackage(metaDataPath); return contentPackage.IsCompatible(); } - public static bool CheckWorkshopItemInstalled(Steamworks.Ugc.Item? item) + public static bool CheckWorkshopItemInstalled(Steamworks.Ugc.Item? itemOrNull) { - if (!(item?.IsInstalled ?? false)) { return false; } + if (!itemOrNull.TryGetValue(out Steamworks.Ugc.Item item)) { return false; } + if (!item.IsInstalled) { return false; } lock (modCopiesInProgress) { - if (modCopiesInProgress.ContainsKey(item.Value.Id)) + if (modCopiesInProgress.ContainsKey(item.Id)) { return true; } } - if (!Directory.Exists(item?.Directory)) + if (item.NeedsUpdate && !item.IsDownloading && !item.IsDownloadPending) { - DebugConsole.ThrowError("Workshop item \"" + item?.Title + "\" has been installed but the install directory cannot be found. Attempting to redownload..."); - item?.Download(); - return false; + item.Download(); + return false; + } + if (!Directory.Exists(item.Directory)) + { + DebugConsole.ThrowError("Workshop item \"" + item.Title + "\" has been installed but the install directory cannot be found. Attempting to redownload..."); + item.Download(); + return false; } string metaDataPath = ""; try { - metaDataPath = Path.Combine(item?.Directory, MetadataFileName); + metaDataPath = Path.Combine(item.Directory, MetadataFileName); } catch (ArgumentException) { - string errorMessage = "Metadata file for the Workshop item \"" + item?.Title + - "\" not found. Could not combine path (" + (item?.Directory ?? "directory name empty") + ")."; + string errorMessage = "Metadata file for the Workshop item \"" + item.Title + + "\" not found. Could not combine path (" + (item.Directory ?? "directory name empty") + ")."; DebugConsole.ThrowError(errorMessage); - GameAnalyticsManager.AddErrorEventOnce("SteamManager.CheckWorkshopItemInstalled:PathCombineException" + item?.Title, + GameAnalyticsManager.AddErrorEventOnce("SteamManager.CheckWorkshopItemInstalled:PathCombineException" + item.Title, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMessage); return false; @@ -1395,13 +1441,13 @@ namespace Barotrauma.Steam if (!File.Exists(metaDataPath)) { - DebugConsole.ThrowError("Metadata file for the Workshop item \"" + item?.Title + "\" not found. The file may be corrupted."); + DebugConsole.ThrowError("Metadata file for the Workshop item \"" + item.Title + "\" not found. The file may be corrupted."); return false; } ContentPackage contentPackage = new ContentPackage(metaDataPath) { - SteamWorkshopId = item?.Id ?? 0 + SteamWorkshopId = item.Id }; //make sure the contentpackage file is present if (!File.Exists(GetWorkshopItemContentPackagePath(contentPackage)) || @@ -1414,20 +1460,21 @@ namespace Barotrauma.Steam return true; } - public static bool CheckWorkshopItemUpToDate(Steamworks.Ugc.Item? item) + public static bool CheckWorkshopItemUpToDate(Steamworks.Ugc.Item? itemOrNull) { - if (!(item?.IsInstalled ?? false)) return false; + if (!itemOrNull.TryGetValue(out Steamworks.Ugc.Item item)) { return false; } + if (!item.IsInstalled || item.NeedsUpdate || item.IsDownloading || item.IsDownloadPending) { return false; } - string metaDataPath = Path.Combine(item?.Directory, MetadataFileName); + string metaDataPath = Path.Combine(item.Directory, MetadataFileName); if (!File.Exists(metaDataPath)) { - DebugConsole.ThrowError("Metadata file for the Workshop item \"" + item?.Title + "\" not found. The file may be corrupted."); + DebugConsole.ThrowError("Metadata file for the Workshop item \"" + item.Title + "\" not found. The file may be corrupted."); return false; } ContentPackage steamPackage = new ContentPackage(metaDataPath) { - SteamWorkshopId = item?.Id ?? 0 + SteamWorkshopId = item.Id }; ContentPackage myPackage = ContentPackage.AllPackages.FirstOrDefault(cp => cp.SteamWorkshopId == steamPackage.SteamWorkshopId); @@ -1435,7 +1482,7 @@ namespace Barotrauma.Steam { return false; } - DateTime latestTime = item.Value.Updated > item.Value.Created ? item.Value.Updated : item.Value.Created; + DateTime latestTime = item.Updated > item.Created ? item.Updated : item.Created; bool upToDate = latestTime <= myPackage.InstallTime.Value; return upToDate; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs index f9ca5c84c..49058b640 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs @@ -1,4 +1,5 @@ using Barotrauma.Sounds; +using Concentus.Structs; using Microsoft.Xna.Framework; using OpenAL; using System; @@ -23,6 +24,8 @@ namespace Barotrauma.Networking private bool capturing; + private OpusEncoder encoder; + public double LastdB { get; @@ -79,7 +82,7 @@ namespace Barotrauma.Networking { Disconnected = false; - VoipConfig.SetupEncoding(); + encoder = VoipConfig.CreateEncoder(); //set up capture device captureDevice = Alc.CaptureOpenDevice(deviceName, VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 5); @@ -265,10 +268,10 @@ namespace Barotrauma.Networking { if (!prevCaptured) //enqueue the previous buffer if not sent to avoid cutoff { - int compressedCountPrev = VoipConfig.Encoder.Encode(prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE); + int compressedCountPrev = encoder.Encode(prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE); EnqueueBuffer(compressedCountPrev); } - int compressedCount = VoipConfig.Encoder.Encode(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE); + int compressedCount = encoder.Encode(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE); EnqueueBuffer(compressedCount); } captureTimer -= (VoipConfig.BUFFER_SIZE * 1000) / VoipConfig.FREQUENCY; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipConfig.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipConfig.cs index bd907694a..1d9639fc0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipConfig.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipConfig.cs @@ -10,35 +10,22 @@ namespace Barotrauma.Networking { static partial class VoipConfig { - public static bool Ready = false; + public const int FREQUENCY = 48000; //48Khz + public const int BITRATE = 16000; //16Kbps + public const int BUFFER_SIZE = (8 * MAX_COMPRESSED_SIZE * FREQUENCY) / BITRATE; //20ms window - public const int FREQUENCY = 48000; - public const int BUFFER_SIZE = 960; //20ms window + public static OpusEncoder CreateEncoder() + { + var encoder = new OpusEncoder(FREQUENCY, 1, OpusApplication.OPUS_APPLICATION_VOIP); + encoder.Bandwidth = OpusBandwidth.OPUS_BANDWIDTH_AUTO; + encoder.Bitrate = BITRATE; + encoder.SignalType = OpusSignal.OPUS_SIGNAL_VOICE; + return encoder; + } - public static OpusEncoder Encoder + public static OpusDecoder CreateDecoder() { - get; - private set; - } - public static OpusDecoder Decoder - { - get; - private set; - } - - public static void SetupEncoding() - { - if (!Ready) - { - Encoder = new OpusEncoder(FREQUENCY, 1, OpusApplication.OPUS_APPLICATION_VOIP); - Encoder.Bandwidth = OpusBandwidth.OPUS_BANDWIDTH_AUTO; - Encoder.Bitrate = 8 * MAX_COMPRESSED_SIZE * FREQUENCY / BUFFER_SIZE; - Encoder.SignalType = OpusSignal.OPUS_SIGNAL_VOICE; - - Decoder = new OpusDecoder(FREQUENCY, 1); - - Ready = true; - } + return new OpusDecoder(FREQUENCY, 1); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Program.cs b/Barotrauma/BarotraumaClient/ClientSource/Program.cs index 14c1b3a6e..0ee887f01 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Program.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Program.cs @@ -156,10 +156,10 @@ namespace Barotrauma sb.AppendLine("Graphics mode: " + GameMain.Config.GraphicsWidth + "x" + GameMain.Config.GraphicsHeight + " (" + GameMain.Config.WindowMode.ToString() + ")"); sb.AppendLine("VSync " + (GameMain.Config.VSyncEnabled ? "ON" : "OFF")); sb.AppendLine("Language: " + (GameMain.Config.Language ?? "none")); - } - if (GameMain.Config.AllEnabledPackages != null) - { - sb.AppendLine("Selected content packages: " + (!GameMain.Config.AllEnabledPackages.Any() ? "None" : string.Join(", ", GameMain.Config.AllEnabledPackages.Select(c => c.Name)))); + if (GameMain.Config.AllEnabledPackages != null) + { + sb.AppendLine("Selected content packages: " + (!GameMain.Config.AllEnabledPackages.Any() ? "None" : string.Join(", ", GameMain.Config.AllEnabledPackages.Select(c => c.Name)))); + } } sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed)); sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")")); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index 7bc5bd0eb..92698cec8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -407,7 +407,7 @@ namespace Barotrauma ToolTip = TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip") }; new GUITextBlock(new RectTransform(Vector2.One, beaconStationContent.RectTransform), - TextManager.Get("submarinetype.beaconstation"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft) + TextManager.Get("submarinetype.beaconstation", fallBackTag: "beaconstationsonarlabel"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft) { Padding = Vector4.Zero, ToolTip = icon.ToolTip diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index f32b61d06..151e4e858 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -78,7 +78,7 @@ namespace Barotrauma private readonly GUIFrame playerInfoContainer; private GUILayoutGroup infoContainer; - private GUITextBlock changesPendingText; + private GUIComponent changesPendingText; public GUIButton PlayerFrame; private readonly GUIComponent subPreviewContainer; @@ -417,7 +417,7 @@ namespace Barotrauma { if (!(FileTransferFrame.UserData is FileReceiver.FileTransferIn transfer)) { return false; } GameMain.Client?.CancelFileTransfer(transfer); - GameMain.Client.FileReceiver.StopTransfer(transfer); + GameMain.Client?.FileReceiver.StopTransfer(transfer); return true; } }; @@ -1632,9 +1632,17 @@ namespace Barotrauma private void CreateChangesPendingText() { - if (changesPendingText != null || infoContainer == null) return; - changesPendingText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.065f), infoContainer.Parent.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0f, -0.065f) }, - TextManager.Get("tabmenu.characterchangespending"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, style: null) { IgnoreLayoutGroups = true }; + if (changesPendingText != null || infoContainer == null) { return; } + + changesPendingText = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.065f), infoContainer.Parent.Parent.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0f, -0.03f) }, + style: "OuterGlow") + { + Color = Color.Black, + IgnoreLayoutGroups = true + }; + var text = new GUITextBlock(new RectTransform(Vector2.One, changesPendingText.RectTransform, Anchor.Center), + TextManager.Get("tabmenu.characterchangespending"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, style: null); + changesPendingText.RectTransform.MinSize = new Point((int)(text.TextSize.X * 1.2f), (int)(text.TextSize.Y * 2.0f)); } private void CreateJobVariantTooltip(JobPrefab jobPrefab, int variant, GUIComponent parentSlot) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs index 9924096cf..6e70159f2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs @@ -189,7 +189,7 @@ namespace Barotrauma var particlePrefabs = GameMain.ParticleManager.GetPrefabList(); foreach (ParticlePrefab particlePrefab in particlePrefabs) { - var prefabText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), prefabList.Content.RectTransform) { MinSize = new Point(0, 20) }, + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), prefabList.Content.RectTransform) { MinSize = new Point(0, 20) }, particlePrefab.DisplayName) { Padding = Vector4.Zero, @@ -231,6 +231,7 @@ namespace Barotrauma private void SerializeAll() { + Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true; foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.Particles)) { XDocument doc = XMLExtensions.TryLoadXml(configFile.Path); @@ -259,6 +260,7 @@ namespace Barotrauma writer.Flush(); } } + Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false; } private void SerializeToClipboard(ParticlePrefab prefab) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs index 7c8aa1a6f..5ac93c54e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs @@ -38,10 +38,32 @@ namespace Barotrauma private GUIListBox friendsDropdown; //Workshop downloads + public struct PendingWorkshopDownload + { + public readonly string ExpectedHash; + public readonly ulong Id; + public readonly Steamworks.Ugc.Item? Item; + + public PendingWorkshopDownload(string expectedHash, Steamworks.Ugc.Item item) + { + ExpectedHash = expectedHash; + Item = item; + Id = item.Id; + } + + public PendingWorkshopDownload(string expectedHash, ulong id) + { + ExpectedHash = expectedHash; + Item = null; + Id = id; + } + } + private GUIFrame workshopDownloadsFrame = null; private Steamworks.Ugc.Item? currentlyDownloadingWorkshopItem = null; - private Dictionary pendingWorkshopDownloads = null; - private string autoConnectName; private string autoConnectEndpoint; + private Dictionary pendingWorkshopDownloads = null; + private string autoConnectName; + private string autoConnectEndpoint; private enum TernaryOption { @@ -1037,25 +1059,44 @@ namespace Barotrauma } } - if (currentlyDownloadingWorkshopItem?.IsInstalled ?? true) + if (currentlyDownloadingWorkshopItem == null) { if (pendingWorkshopDownloads?.Any() ?? false) { - Steamworks.Ugc.Item? item = pendingWorkshopDownloads.Values.FirstOrDefault(it => it != null); + Steamworks.Ugc.Item? item = pendingWorkshopDownloads.Values.FirstOrDefault(it => it.Item != null).Item; if (item != null) { ulong itemId = item.Value.Id; currentlyDownloadingWorkshopItem = item; - SteamManager.SubscribeToWorkshopItem(itemId, () => + SteamManager.ForceRedownload(item.Value.Id, () => { + if (!(item?.IsSubscribed ?? false)) + { + TaskPool.Add("SubscribeToServerMod", item?.Subscribe(), (t) => { }); + } + PendingWorkshopDownload clearedDownload = pendingWorkshopDownloads[itemId]; pendingWorkshopDownloads.Remove(itemId); + currentlyDownloadingWorkshopItem = null; + + void onInstall(ContentPackage resultingPackage) + { + if (!resultingPackage.MD5hash.Hash.Equals(clearedDownload.ExpectedHash)) + { + workshopDownloadsFrame?.FindChild((c) => c.UserData is ulong l && l == itemId, true)?.Flash(GUI.Style.Red); + CancelWorkshopDownloads(); + GameMain.Client?.Disconnect(); + GameMain.Client = null; + new GUIMessageBox( + TextManager.Get("ConnectionLost"), + TextManager.GetWithVariable("DisconnectMessage.MismatchedWorkshopMod", "[incompatiblecontentpackage]", $"\"{resultingPackage.Name}\" (hash {resultingPackage.MD5hash.ShortHash})")); + } + } if (SteamManager.CheckWorkshopItemInstalled(item)) { SteamManager.UninstallWorkshopItem(item, false, out _); } - - if (SteamManager.InstallWorkshopItem(item, out string errorMsg, enableContentPackage: false, suppressInstallNotif: true)) + if (SteamManager.InstallWorkshopItem(item, out string errorMsg, enableContentPackage: false, suppressInstallNotif: true, onInstall: onInstall)) { workshopDownloadsFrame?.FindChild((c) => c.UserData is ulong l && l == itemId, true)?.Flash(GUI.Style.Green); } @@ -2127,45 +2168,46 @@ namespace Barotrauma masterServerResponded = true; } - public void DownloadWorkshopItems(IEnumerable ids, string serverName, string endPointString) + public void DownloadWorkshopItems(IEnumerable downloads, string serverName, string endPointString) { if (workshopDownloadsFrame != null) { return; } - int rowCount = ids.Count() + 2; + int rowCount = downloads.Count() + 2; autoConnectName = serverName; autoConnectEndpoint = endPointString; workshopDownloadsFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), null, Color.Black * 0.5f); - pendingWorkshopDownloads = new Dictionary(); + currentlyDownloadingWorkshopItem = null; + pendingWorkshopDownloads = new Dictionary(); var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f + 0.03f * rowCount), workshopDownloadsFrame.RectTransform, Anchor.Center, Pivot.Center)); var innerLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, (float)rowCount / (float)(rowCount + 3)), innerFrame.RectTransform, Anchor.Center, Pivot.Center)); - foreach (ulong id in ids) + foreach (PendingWorkshopDownload entry in downloads) { - pendingWorkshopDownloads.Add(id, null); + pendingWorkshopDownloads.Add(entry.Id, entry); var itemLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f / rowCount), innerLayout.RectTransform), true, Anchor.CenterLeft) { - UserData = id + UserData = entry.Id }; - TaskPool.Add("RetrieveWorkshopItemData", Steamworks.SteamUGC.QueryFileAsync(id), (t) => + TaskPool.Add("RetrieveWorkshopItemData", Steamworks.SteamUGC.QueryFileAsync(entry.Id), (t) => { if (t.IsFaulted) { - TaskPool.PrintTaskExceptions(t, $"Failed to retrieve Workshop item info (ID {id})"); + TaskPool.PrintTaskExceptions(t, $"Failed to retrieve Workshop item info (ID {entry.Id})"); return; } Steamworks.Ugc.Item? item = ((Task)t).Result; if (!item.HasValue) { - DebugConsole.ThrowError($"Failed to find a Steam Workshop item with the ID {id}."); + DebugConsole.ThrowError($"Failed to find a Steam Workshop item with the ID {entry.Id}."); return; } - if (pendingWorkshopDownloads.ContainsKey(id)) + if (pendingWorkshopDownloads.ContainsKey(entry.Id)) { - pendingWorkshopDownloads[id] = item; + pendingWorkshopDownloads[entry.Id] = new PendingWorkshopDownload(entry.ExpectedHash, item.Value); new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.67f), itemLayout.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), item.Value.Title); @@ -2191,15 +2233,21 @@ namespace Barotrauma { OnClicked = (btn, obj) => { - autoConnectEndpoint = null; - autoConnectName = null; - pendingWorkshopDownloads.Clear(); - workshopDownloadsFrame = null; + CancelWorkshopDownloads(); return true; } }; } + public void CancelWorkshopDownloads() + { + autoConnectEndpoint = null; + autoConnectName = null; + pendingWorkshopDownloads.Clear(); + currentlyDownloadingWorkshopItem = null; + workshopDownloadsFrame = null; + } + private bool JoinServer(string endpoint, string serverName) { if (string.IsNullOrWhiteSpace(ClientNameBox.Text)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs index 74ccd5434..4cc90ee24 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs @@ -682,12 +682,23 @@ namespace Barotrauma try { bool reselect = GameMain.Config.AllEnabledPackages.Any(cp => cp.SteamWorkshopId != 0 && cp.SteamWorkshopId == item?.Id); - if (!SteamManager.UninstallWorkshopItem(item, false, out string errorMsg) || - !SteamManager.InstallWorkshopItem(item, out errorMsg, reselect, true)) + if (!SteamManager.UninstallWorkshopItem(item, false, out string errorMsg)) { DebugConsole.ThrowError($"Failed to reinstall \"{item?.Title}\": {errorMsg}", null, true); elem.Flash(GUI.Style.Red); + return true; } + + SteamManager.ForceRedownload(item?.Id ?? 0, () => + { + if (!SteamManager.InstallWorkshopItem(item, out string errorMsg, reselect, true)) + { + DebugConsole.ThrowError($"Failed to reinstall \"{item?.Title}\": {errorMsg}", null, true); + elem.Flash(GUI.Style.Red); + } + RefreshSubscribedItems(); + }); + RefreshSubscribedItems(); } catch (Exception e) { @@ -696,6 +707,7 @@ namespace Barotrauma } return true; }; + reinstallBtn.Enabled = !item.Value.IsDownloading && !item.Value.IsDownloadPending; var unsubBtn = new GUIButton(new RectTransform(new Point((int)(32 * GUI.Scale)), rightColumn.RectTransform), "", style: "GUIMinusButton") { ToolTip = TextManager.Get("WorkshopItemUnsubscribe"), diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index f6c80e961..d871d034e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -2815,13 +2815,17 @@ namespace Barotrauma foreach (GUIComponent child in categorizedEntityList.Content.Children) { child.Visible = !entityCategory.HasValue || (MapEntityCategory)child.UserData == entityCategory; - if (child.Visible && dummyCharacter?.SelectedConstruction?.OwnInventory != null) + var innerList = child.GetChild(); + foreach (GUIComponent grandChild in innerList.Content.Children) { - child.Visible = child.UserData is MapEntityPrefab item && IsItemPrefab(item); + grandChild.Visible = true; } } - if (!string.IsNullOrEmpty(entityFilterBox.Text)) { FilterEntities(entityFilterBox.Text); } + if (!string.IsNullOrEmpty(entityFilterBox.Text) || dummyCharacter?.SelectedConstruction?.OwnInventory != null) + { + FilterEntities(entityFilterBox.Text); + } categorizedEntityList.UpdateScrollBarSize(); categorizedEntityList.BarScroll = 0.0f; @@ -2831,7 +2835,7 @@ namespace Barotrauma private void FilterEntities(string filter) { - if (string.IsNullOrWhiteSpace(filter)) + if (string.IsNullOrWhiteSpace(filter) && dummyCharacter?.SelectedConstruction?.OwnInventory == null) { allEntityList.Visible = false; categorizedEntityList.Visible = true; @@ -2844,10 +2848,6 @@ namespace Barotrauma foreach (GUIComponent grandChild in innerList.Content.Children) { grandChild.Visible = ((MapEntityPrefab)grandChild.UserData).Name.ToLower().Contains(filter); - if (grandChild.Visible && dummyCharacter?.SelectedConstruction?.OwnInventory != null) - { - grandChild.Visible = grandChild.UserData is MapEntityPrefab item && IsItemPrefab(item); - } } }; categorizedEntityList.UpdateScrollBarSize(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs index 89800a93f..e7f1c6aad 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs @@ -1,5 +1,6 @@ using Barotrauma.IO; using Barotrauma.Networking; +using Concentus.Structs; using Microsoft.Xna.Framework; using OpenAL; using System; @@ -30,6 +31,8 @@ namespace Barotrauma.Sounds private SoundChannel soundChannel; + private OpusDecoder decoder; + public bool UseRadioFilter; public bool UseMuffleFilter; @@ -65,7 +68,7 @@ namespace Barotrauma.Sounds public VoipSound(string name, SoundManager owner, VoipQueue q) : base(owner, $"VoIP ({name})", true, true, getFullPath: false) { - VoipConfig.SetupEncoding(); + decoder = VoipConfig.CreateDecoder(); ALFormat = Al.FormatMono16; SampleRate = VoipConfig.FREQUENCY; @@ -152,7 +155,7 @@ namespace Barotrauma.Sounds { if (compressedSize > 0) { - VoipConfig.Decoder.Decode(compressedBuffer, 0, compressedSize, buffer, 0, VoipConfig.BUFFER_SIZE); + decoder.Decode(compressedBuffer, 0, compressedSize, buffer, 0, VoipConfig.BUFFER_SIZE); bufferID++; return VoipConfig.BUFFER_SIZE; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs index d4f357c04..d8abe8504 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs @@ -5,6 +5,7 @@ using Barotrauma.IO; using System.Text; using System.Text.RegularExpressions; using System.Linq; +using System.Globalization; namespace Barotrauma { @@ -217,7 +218,7 @@ namespace Barotrauma $""); + $"{GetVariable("commonness", NPCPersonalityTrait.List[i].Commonness.ToString(CultureInfo.InvariantCulture))}/>"); } xmlContent.Add(string.Empty); diff --git a/Barotrauma/BarotraumaClient/Content/Effects/grainshader.xnb b/Barotrauma/BarotraumaClient/Content/Effects/grainshader.xnb new file mode 100644 index 000000000..432e7191d Binary files /dev/null and b/Barotrauma/BarotraumaClient/Content/Effects/grainshader.xnb differ diff --git a/Barotrauma/BarotraumaClient/Content/Effects/grainshader_opengl.xnb b/Barotrauma/BarotraumaClient/Content/Effects/grainshader_opengl.xnb new file mode 100644 index 000000000..dcf39677d Binary files /dev/null and b/Barotrauma/BarotraumaClient/Content/Effects/grainshader_opengl.xnb differ diff --git a/Barotrauma/BarotraumaClient/Content/Effects/losshader.xnb b/Barotrauma/BarotraumaClient/Content/Effects/losshader.xnb index b17c38ef3..7f09d5fe6 100644 Binary files a/Barotrauma/BarotraumaClient/Content/Effects/losshader.xnb and b/Barotrauma/BarotraumaClient/Content/Effects/losshader.xnb differ diff --git a/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.xnb b/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.xnb index bd4516465..f8f1b3f6a 100644 Binary files a/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.xnb and b/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.xnb differ diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 705b948b6..3a58ebb92 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.13.0.11 + 0.13.3.11 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 53342a335..9dcf56a03 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.13.0.11 + 0.13.3.11 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/Shaders/Content.mgcb b/Barotrauma/BarotraumaClient/Shaders/Content.mgcb index 168994193..901a2171c 100644 --- a/Barotrauma/BarotraumaClient/Shaders/Content.mgcb +++ b/Barotrauma/BarotraumaClient/Shaders/Content.mgcb @@ -61,3 +61,9 @@ /processorParam:DebugMode=Auto /build:watershader.fx +#begin grainshader.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:grainshader.fx + diff --git a/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb b/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb index e18949d5d..cb119ed14 100644 --- a/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb +++ b/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb @@ -61,3 +61,9 @@ /processorParam:DebugMode=Auto /build:watershader_opengl.fx +#begin grainshader_opengl.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:grainshader_opengl.fx + diff --git a/Barotrauma/BarotraumaClient/Shaders/grainshader.fx b/Barotrauma/BarotraumaClient/Shaders/grainshader.fx new file mode 100644 index 000000000..c5191f3a0 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Shaders/grainshader.fx @@ -0,0 +1,28 @@ +// vim:ft=hlsl +//float4 baseColor; +float seed; + +float nrand(float2 uv) +{ + return frac(sin(dot(uv, float2(12.9898, 78.233) * seed)) * 43758.5453); +} + +float4 grain(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0 +{ + float4 baseColor = { 1, 1, 1, 0.25 }; + float4 color = baseColor * nrand(texCoord); + float2 center = { 0.5, 0.5 }; + float2 diff = texCoord - center; + float alpha = diff.x * diff.x + diff.y * diff.y; + color.a = alpha; + return clr * color; +} + + +technique Grain +{ + pass Pass1 + { + PixelShader = compile ps_4_0_level_9_1 grain(); + } +} diff --git a/Barotrauma/BarotraumaClient/Shaders/grainshader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/grainshader_opengl.fx new file mode 100644 index 000000000..bb9a45311 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Shaders/grainshader_opengl.fx @@ -0,0 +1,28 @@ +// vim:ft=hlsl +//float4 baseColor; +float seed; + +float nrand(float2 uv) +{ + return frac(sin(dot(uv, float2(12.9898, 78.233) * seed)) * 43758.5453); +} + +float4 grain(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0 +{ + float4 baseColor = { 1, 1, 1, 0.25 }; + float4 color = baseColor * nrand(texCoord); + float2 center = { 0.5, 0.5 }; + float2 diff = texCoord - center; + float alpha = diff.x * diff.x + diff.y * diff.y; + color.a = alpha; + return clr * color; +} + + +technique Grain +{ + pass Pass1 + { + PixelShader = compile ps_3_0 grain(); + } +} diff --git a/Barotrauma/BarotraumaClient/Shaders/losshader.fx b/Barotrauma/BarotraumaClient/Shaders/losshader.fx index d40f91c49..f761aa1bc 100644 --- a/Barotrauma/BarotraumaClient/Shaders/losshader.fx +++ b/Barotrauma/BarotraumaClient/Shaders/losshader.fx @@ -26,6 +26,8 @@ sampler TextureSampler : register (s0) = sampler_state { Texture = ; } Texture2D xLosTexture; sampler LosSampler = sampler_state { Texture = ; }; +float xLosAlpha; + float4 xColor; float4 mainPS(VertexShaderOutput input) : COLOR0 @@ -39,7 +41,7 @@ float4 mainPS(VertexShaderOutput input) : COLOR0 sampleColor.r * xColor.r, sampleColor.g * xColor.g, sampleColor.b * xColor.b, - obscureAmount); + obscureAmount * xLosAlpha); return outColor; } diff --git a/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx index 23a48e4d2..a799c320a 100644 --- a/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx +++ b/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx @@ -26,6 +26,8 @@ sampler TextureSampler : register (s0) = sampler_state { Texture = ; } Texture xLosTexture; sampler LosSampler = sampler_state { Texture = ; }; +float xLosAlpha; + float4 xColor; float4 mainPS(VertexShaderOutput input) : COLOR0 @@ -39,7 +41,7 @@ float4 mainPS(VertexShaderOutput input) : COLOR0 sampleColor.r * xColor.r, sampleColor.g * xColor.g, sampleColor.b * xColor.b, - obscureAmount); + obscureAmount * xLosAlpha); return outColor; } diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index b1eb76668..12c33e3e1 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.13.0.11 + 0.13.3.11 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 50c12c344..1f5d68a8f 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.13.0.11 + 0.13.3.11 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index b02374966..556643a5a 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.13.0.11 + 0.13.3.11 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs index 8c435e979..cfb20d850 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs @@ -101,9 +101,7 @@ namespace Barotrauma.Networking partial void InitProjSpecific() { - var jobs = JobPrefab.Prefabs.ToList(); - // TODO: modding support? - JobPreferences = new List>(jobs.GetRange(0, Math.Min(jobs.Count, 3)).Select(j => new Pair(j, 0))); + JobPreferences = new List>(); VoipQueue = new VoipQueue(ID, true, true); GameMain.Server.VoipServer.RegisterQueue(VoipQueue); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 3a45a2012..e452661de 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3497,8 +3497,6 @@ namespace Barotrauma.Networking sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, sender.Name); sender.CharacterInfo.RecreateHead(headSpriteId, race, gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); - //if the client didn't provide job preferences, we'll use the preferences that are randomly assigned in the Client constructor - Debug.Assert(sender.JobPreferences.Count > 0); if (jobPreferences.Count > 0) { sender.JobPreferences = jobPreferences; @@ -3549,7 +3547,7 @@ namespace Barotrauma.Networking for (int i = unassigned.Count - 1; i >= 0; i--) { if (unassigned[i].JobPreferences.Count == 0) { continue; } - if (!unassigned[i].JobPreferences[0].First.AllowAlways) { continue; } + if (!unassigned[i].JobPreferences.Any() || !unassigned[i].JobPreferences[0].First.AllowAlways) { continue; } unassigned[i].AssignedJob = unassigned[i].JobPreferences[0]; unassigned.RemoveAt(i); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs index c72467622..546ba9543 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs @@ -253,6 +253,8 @@ namespace Barotrauma.Networking outMsg.Write(mpContentPackages[i].Name); outMsg.Write(mpContentPackages[i].MD5hash.Hash); outMsg.Write(mpContentPackages[i].SteamWorkshopId); + UInt32 installTimeDiffSeconds = (UInt32)((mpContentPackages[i].InstallTime ?? DateTime.UtcNow) - DateTime.UtcNow).TotalSeconds; + outMsg.Write(installTimeDiffSeconds); } break; case ConnectionInitialization.Password: diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index 7647ecc1a..066dab290 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -223,6 +223,7 @@ namespace Barotrauma.Networking GameMain.Server.CreateEntityEvent(this); RespawnCountdownStarted = false; + ReturnCountdownStarted = false; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Program.cs b/Barotrauma/BarotraumaServer/ServerSource/Program.cs index ba8bb55fe..03bbb4e15 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Program.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Program.cs @@ -111,10 +111,10 @@ namespace Barotrauma if (GameMain.Config != null) { sb.AppendLine("Language: " + (GameMain.Config.Language ?? "none")); - } - if (GameMain.Config.AllEnabledPackages != null) - { - sb.AppendLine("Selected content packages: " + (!GameMain.Config.AllEnabledPackages.Any() ? "None" : string.Join(", ", GameMain.Config.AllEnabledPackages.Select(c => c.Name)))); + if (GameMain.Config.AllEnabledPackages != null) + { + sb.AppendLine("Selected content packages: " + (!GameMain.Config.AllEnabledPackages.Any() ? "None" : string.Join(", ", GameMain.Config.AllEnabledPackages.Select(c => c.Name)))); + } } sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed)); sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")")); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index ba178ceac..8000d63c5 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.13.0.11 + 0.13.3.11 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 6ba9ae846..4a2e438a9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -442,7 +442,7 @@ namespace Barotrauma Character.AnimController.InWater || Character.AnimController.HeadInWater || Character.CurrentHull == null || - Character.Submarine.TeamID != Character.TeamID || + Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.IsCurrentObjective() || ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character || // wait order ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 9a53951ea..b85e9ca21 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -245,6 +245,12 @@ namespace Barotrauma { IsCompleted = true; } + else if (Enemy.IsKnockedDown && + !objectiveManager.IsCurrentObjective() && + !HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false)) + { + IsCompleted = true; + } break; } } @@ -738,7 +744,7 @@ namespace Barotrauma }, onAbandon: () => Abandon = true); if (followTargetObjective == null) { return; } - if (Mode == CombatMode.Arrest && (Enemy.Stun > 1 || Enemy.IsKnockedDown)) + if (Mode == CombatMode.Arrest && Enemy.IsKnockedDown) { if (HumanAIController.HasItem(character, "handlocker", out _)) { @@ -786,6 +792,23 @@ namespace Barotrauma private void OnArrestTargetReached() { + if (!Enemy.IsKnockedDown) + { + RemoveFollowTarget(); + return; + } + if (character.TeamID == CharacterTeamType.FriendlyNPC) + { + // Confiscate stolen goods. + foreach (var item in Enemy.Inventory.AllItemsMod) + { + if (item.StolenDuringRound) + { + item.Drop(character); + character.Inventory.TryPutItem(item, character, CharacterInventory.anySlot); + } + } + } if (HumanAIController.HasItem(character, "handlocker", out IEnumerable matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy)) { var handCuffs = matchingItems.First(); @@ -794,20 +817,18 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage($"{character.Name}: Failed to handcuff the target.", Color.Red); #endif - } - // Confiscate stolen goods. - foreach (var item in Enemy.Inventory.AllItemsMod) - { - if (item == handCuffs) { continue; } - if (item.StolenDuringRound) + if (objectiveManager.IsCurrentObjective()) { - item.Drop(character); - character.Inventory.TryPutItem(item, character, CharacterInventory.anySlot); + Abandon = true; + return; } } character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f); } - IsCompleted = true; + if (!objectiveManager.IsCurrentObjective()) + { + IsCompleted = true; + } } /// @@ -941,14 +962,6 @@ namespace Barotrauma return; } if (reloadTimer > 0) { return; } - if (Mode == CombatMode.Arrest) - { - // If the target is arrested or if it's stunned and we can't lock the target up, consider the objective done. - if (Enemy.IsKnockedDown && !HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false) || HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true)) - { - IsCompleted = true; - } - } if (holdFireCondition != null && holdFireCondition()) { return; } float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position); if (WeaponComponent is MeleeWeapon meleeWeapon) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs index 2f961341e..09a0fd3f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs @@ -334,9 +334,15 @@ namespace Barotrauma return (int)Math.Round(MathHelper.Lerp(minValue, maxValue, Level.Loaded.Difficulty * 0.01f * Config.AgentSpawnCountDifficultyMultiplier)); } - private float GetSpawnTime() => - Math.Max(Config.AgentSpawnDelay * Rand.Range(Config.AgentSpawnDelayRandomFactor, 1 + Config.AgentSpawnDelayRandomFactor) - / (Math.Max(Level.Loaded.Difficulty, 1) * 0.01f * Config.AgentSpawnDelayDifficultyMultiplier), Config.AgentSpawnDelay); + private float GetSpawnTime() + { + float randomFactor = Config.AgentSpawnDelayRandomFactor; + float delay = Config.AgentSpawnDelay; + float min = delay; + float max = delay * 6; + float t = Level.Loaded.Difficulty * Config.AgentSpawnDelayDifficultyMultiplier * Rand.Range(1 - randomFactor, 1 + randomFactor); + return MathHelper.Lerp(max, min, MathUtils.InverseLerp(0, 100, t)); + } void UpdateReinforcements(float deltaTime) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 9367444cb..a9b8a40e4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -3414,7 +3414,7 @@ namespace Barotrauma /// Is the character knocked down regardless whether the technical state is dead, unconcious, paralyzed, or stunned. /// With stunning, the parameter uses a half a second delay before the character is treated as knocked down. The purpose of this is to ignore minor stunning. If you don't want to to ignore any stun, use the Stun property. /// - public bool IsKnockedDown => IsDead || IsIncapacitated || CharacterHealth.StunTimer > 0.5f; + public bool IsKnockedDown => IsDead || IsIncapacitated || CharacterHealth.StunTimer > 0.5f || IsRagdolled; public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index 34c017ebe..649d9afcd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -131,7 +131,12 @@ namespace Barotrauma public static string GetFolder(XDocument doc, string filePath) { - var folder = doc.Root?.Element("animations")?.GetAttributeString("folder", string.Empty); + var root = doc.Root; + if (root?.IsOverride() ?? false) + { + root = root.FirstElement(); + } + var folder = root?.Element("animations")?.GetAttributeString("folder", string.Empty); if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase)) { folder = Path.Combine(Path.GetDirectoryName(filePath), "Animations"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs index b3f6806dc..9c41dddb0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -105,7 +105,12 @@ namespace Barotrauma public static string GetFolder(XDocument doc, string filePath) { - var folder = doc.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty); + var root = doc.Root; + if (root?.IsOverride() ?? false) + { + root = root.FirstElement(); + } + var folder = root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty); if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase)) { folder = Path.Combine(Path.GetDirectoryName(filePath), "Ragdolls") + Path.DirectorySeparatorChar; diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs index 715d7bb40..305baebb7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs @@ -276,7 +276,16 @@ namespace Barotrauma { SteamWorkshopId = SteamManager.GetWorkshopItemIDFromUrl(workshopUrl); } - GameVersion = new Version(doc.Root.GetAttributeString("gameversion", "0.0.0.0")); + string versionStr = doc.Root.GetAttributeString("gameversion", "0.0.0.0"); + try + { + GameVersion = new Version(versionStr); + } + catch + { + DebugConsole.ThrowError($"Invalid version number in content package \"{Name}\" ({versionStr})."); + GameVersion = GameMain.Version; + } if (doc.Root.Attribute("installtime") != null) { InstallTime = ToolBox.Epoch.ToDateTime(doc.Root.GetAttributeUInt("installtime", 0)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ClearTagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ClearTagAction.cs index 567fdee02..f4b6d0f0a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ClearTagAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ClearTagAction.cs @@ -23,9 +23,9 @@ namespace Barotrauma { if (isFinished) { return; } - if (!string.IsNullOrWhiteSpace(Tag) && ParentEvent.Targets.ContainsKey(Tag)) + if (!string.IsNullOrWhiteSpace(Tag)) { - ParentEvent.Targets.Remove(Tag); + ParentEvent.RemoveTag(Tag); } isFinished = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index 2e1419d80..f5215e725 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -42,6 +42,11 @@ namespace Barotrauma } if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } + if (requiredDeliveryAmount > items.Count) + { + DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver."); + requiredDeliveryAmount = items.Count; + } } private void LoadItemAsChild(XElement element, Item parent) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs index 779136e9c..13c0fc231 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs @@ -138,6 +138,14 @@ namespace Barotrauma return targetsToReturn; } + public void RemoveTag(string tag) + { + if (string.IsNullOrWhiteSpace(tag)) { return; } + if (Targets.ContainsKey(tag)) { Targets.Remove(tag); } + if (cachedTargets.ContainsKey(tag)) { cachedTargets.Remove(tag); } + if (targetPredicates.ContainsKey(tag)) { targetPredicates.Remove(tag); } + } + public override void Update(float deltaTime) { int botCount = 0; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Extensions/StructExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Extensions/StructExtensions.cs new file mode 100644 index 000000000..b07fef62b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Extensions/StructExtensions.cs @@ -0,0 +1,16 @@ +namespace Barotrauma.Extensions +{ + public static class StructExtensions + { + public static bool TryGetValue(this T? nullableStruct, out T nonNullable) where T : struct + { + if (nullableStruct.HasValue) + { + nonNullable = nullableStruct.Value; + return true; + } + nonNullable = default(T); + return false; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index c5a02bcde..a52601939 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -265,7 +265,7 @@ namespace Barotrauma if (beaconMissionPrefabs.Any()) { Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed)); - var beaconMissionPrefab = beaconMissionPrefabs.GetRandom(rand); + var beaconMissionPrefab = ToolBox.SelectWeightedRandom(beaconMissionPrefabs, beaconMissionPrefabs.Select(p => (float)p.Commonness).ToList(), rand); if (!Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type)) { extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations)); @@ -282,7 +282,7 @@ namespace Barotrauma else { Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed)); - var huntingGroundsMissionPrefab = huntingGroundsMissionPrefabs.GetRandom(rand); + var huntingGroundsMissionPrefab = ToolBox.SelectWeightedRandom(huntingGroundsMissionPrefabs, huntingGroundsMissionPrefabs.Select(p => (float)p.Commonness).ToList(), rand); if (!Missions.Any(m => m.Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase)))) { extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations)); @@ -613,6 +613,13 @@ namespace Barotrauma public void EndCampaign() { + foreach (Character c in Character.CharacterList) + { + if (c.IsOnPlayerTeam) + { + c.CharacterHealth.RemoveAllAfflictions(); + } + } foreach (LocationConnection connection in Map.Connections) { connection.Difficulty = MathHelper.Lerp(connection.Difficulty, 100.0f, 0.25f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 3c8b2bd10..6b6e4b57b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -32,6 +32,8 @@ namespace Barotrauma public Level Level { get; private set; } public LevelData LevelData { get; private set; } + public bool MirrorLevel { get; private set; } + public Map Map { get @@ -318,6 +320,7 @@ namespace Barotrauma public void StartRound(LevelData levelData, bool mirrorLevel = false, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null) { + MirrorLevel = mirrorLevel; if (SubmarineInfo == null) { DebugConsole.ThrowError("Couldn't start game session, submarine not selected."); @@ -583,6 +586,7 @@ namespace Barotrauma if (ls.Sub == null || ls.Submarine != Submarine) { continue; } if (!ls.LoadSub || ls.Sub.DockedTo.Contains(Submarine)) { continue; } if (Submarine.Info.LeftBehindDockingPortIDs.Contains(ls.OriginalLinkedToID)) { continue; } + if (ls.Sub.Info.SubmarineElement.Attribute("location") != null) { continue; } ls.Sub.SetPosition(ls.Sub.WorldPosition + (Submarine.WorldPosition - originalSubPos)); } } @@ -743,7 +747,8 @@ namespace Barotrauma doc.Root.Add(new XAttribute("savetime", ToolBox.Epoch.NowLocal)); doc.Root.Add(new XAttribute("version", GameMain.Version)); - doc.Root.Add(new XAttribute("submarine", SubmarineInfo == null ? "" : SubmarineInfo.Name)); + var submarineInfo = Campaign?.PendingSubmarineSwitch ?? SubmarineInfo; + doc.Root.Add(new XAttribute("submarine", submarineInfo == null ? "" : submarineInfo.Name)); if (OwnedSubmarines != null) { List ownedSubmarineNames = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs index 4d69e9155..2348315d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs @@ -311,8 +311,6 @@ namespace Barotrauma public bool ModBreakerMode { get; set; } #endif - private System.IO.FileSystemWatcher modsFolderWatcher; - private static int ContentFileLoadOrder(ContentFile a) { switch (a.Type) @@ -805,87 +803,6 @@ namespace Barotrauma } LoadPlayerConfig(); - - modsFolderWatcher = new System.IO.FileSystemWatcher("Mods"); - modsFolderWatcher.Filter = "*"; - modsFolderWatcher.NotifyFilter = System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName; - modsFolderWatcher.Created += OnModFolderUpdate; - modsFolderWatcher.Deleted += OnModFolderUpdate; - modsFolderWatcher.Renamed += OnModFolderUpdate; - modsFolderWatcher.EnableRaisingEvents = true; - } - - private void OnModFolderUpdate(object sender, System.IO.FileSystemEventArgs e) - { - if (SuppressModFolderWatcher || (GameMain.NetworkMember?.IsClient ?? false)) { return; } - switch (e.ChangeType) - { - case System.IO.WatcherChangeTypes.Created: - { - string cpPath = Path.GetFullPath(Path.Combine(e.FullPath, Steam.SteamManager.MetadataFileName)).CleanUpPath(); - if (File.Exists(cpPath) && - !ContentPackage.AllPackages.Any(cp => Path.GetFullPath(cp.Path).CleanUpPath() == cpPath)) - { - var newPackage = new ContentPackage(cpPath); - if (!newPackage.IsCorrupt) { ContentPackage.AddPackage(newPackage); } - } - } - break; - case System.IO.WatcherChangeTypes.Deleted: - { - string cpPath = Path.GetFullPath(Path.Combine(e.FullPath, Steam.SteamManager.MetadataFileName)).CleanUpPath(); - var toRemove = ContentPackage.RegularPackages.Where(cp => Path.GetFullPath(cp.Path).CleanUpPath() == cpPath).ToList(); - foreach (var cp in toRemove) - { - if (enabledRegularPackages.Contains(cp)) { DisableRegularPackage(cp); } - } - - toRemove.AddRange(ContentPackage.CorePackages.Where(cp => Path.GetFullPath(cp.Path).CleanUpPath() == cpPath)); - bool reselectCore = false; - foreach (var cp in toRemove) - { - ContentPackage.RemovePackage(cp); - if (cp.IsCorePackage) - { - reselectCore = true; - } - } - if (reselectCore) { AutoSelectCorePackage(null); } - } - break; - case System.IO.WatcherChangeTypes.Renamed: - { - System.IO.RenamedEventArgs renameArgs = e as System.IO.RenamedEventArgs; - - string cpPath = Path.GetFullPath(Path.Combine(e.FullPath, Steam.SteamManager.MetadataFileName)).CleanUpPath(); - var toRemove = ContentPackage.RegularPackages.Where(cp => Path.GetFullPath(cp.Path).CleanUpPath() == cpPath).ToList(); - foreach (var cp in toRemove) - { - if (enabledRegularPackages.Contains(cp)) { DisableRegularPackage(cp); } - } - - toRemove.AddRange(ContentPackage.CorePackages.Where(cp => Path.GetFullPath(cp.Path).CleanUpPath() == cpPath)); - bool reselectCore = false; - foreach (var cp in toRemove) - { - ContentPackage.RemovePackage(cp); - if (cp.IsCorePackage) - { - reselectCore = true; - } - } - - cpPath = Path.GetFullPath(Path.Combine(renameArgs.FullPath, Steam.SteamManager.MetadataFileName)).CleanUpPath(); - if (File.Exists(cpPath) && - !ContentPackage.AllPackages.Any(cp => Path.GetFullPath(cp.Path).CleanUpPath() == cpPath)) - { - var newPackage = new ContentPackage(cpPath); - if (!newPackage.IsCorrupt) { ContentPackage.AddPackage(newPackage); } - } - if (reselectCore) { AutoSelectCorePackage(null); } - } - break; - } } private void LoadDefaultConfig(bool setLanguage = true, bool loadContentPackages = true) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs index 7e5aa6ddd..9e6ad682a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs @@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components : base(item,element) { } - + public override bool Use(float deltaTime, Character character = null) { if (character == null || character.Removed) return false; @@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components Vector2 propulsion = dir * Force * character.PropulsionSpeedMultiplier; - if (character.AnimController.InWater) character.AnimController.TargetMovement = dir; + if (character.AnimController.InWater && Force > 0.0f) { character.AnimController.TargetMovement = dir; } foreach (Limb limb in character.AnimController.Limbs) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index c4a873586..f6e4ce5bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -757,7 +757,7 @@ namespace Barotrauma.Items.Components bool reducesCondition = false; foreach (StatusEffect effect in statusEffects) { - if (broken && effect.type != ActionType.OnBroken) { continue; } + if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; } if (user != null) { effect.SetUser(user); } item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, false, false, worldPosition); reducesCondition |= effect.ReducesItemCondition(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index adfb53e05..173dbc428 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -153,6 +153,7 @@ namespace Barotrauma.Items.Components if (IsToggle) { item.SendSignal(State ? "1" : "0", "signal_out"); + item.SendSignal(State ? "1" : "0", "trigger_out"); } if (user == null @@ -263,6 +264,8 @@ namespace Barotrauma.Items.Components } } + private double lastUsed; + public override bool Use(float deltaTime, Character activator = null) { if (activator != user) @@ -277,7 +280,22 @@ namespace Barotrauma.Items.Components return false; } - item.SendSignal(new Signal("1", sender: user), "trigger_out"); + if (IsToggle && (activator == null || lastUsed < Timing.TotalTime - 0.1)) + { + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) + { + State = !State; +#if SERVER + item.CreateServerEvent(this); +#endif + } + } + else + { + item.SendSignal(new Signal("1", sender: user), "trigger_out"); + } + + lastUsed = Timing.TotalTime; ApplyStatusEffects(ActionType.OnUse, 1.0f, activator); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index eced05614..8287f8a42 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -308,7 +308,15 @@ namespace Barotrauma.Items.Components if (AutoPilot) { UpdateAutoPilot(deltaTime); - TargetVelocity = TargetVelocity.ClampLength(MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f); + float throttle = 1.0f; + if (controlledSub != null) + { + //if the sub is heading in the correct direction, throttle the speed according to the user's skill + //if it's e.g. sinking due to extra water, don't throttle, but allow emptying up the ballast completely + throttle = MathHelper.Clamp(Vector2.Dot(controlledSub.Velocity, TargetVelocity) / 100.0f, 0.0f, 1.0f); + } + float maxSpeed = MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f; + TargetVelocity = TargetVelocity.ClampLength(MathHelper.Lerp(100.0f, maxSpeed, throttle)); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 56fa478d5..a23a6ba9e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -372,7 +372,7 @@ namespace Barotrauma.Items.Components hits = hits.OrderBy(h => h.Fraction).ToList(); foreach (HitscanResult h in hits) { - item.body.SetTransform(h.Point, rotation); + item.SetTransform(h.Point, rotation); if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero)) { hitSomething = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/AndComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/AndComponent.cs index 2c5b8ba0e..70215590e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/AndComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/AndComponent.cs @@ -23,6 +23,17 @@ namespace Barotrauma.Items.Components } } + private int maxOutputLength; + [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] + public int MaxOutputLength + { + get { return maxOutputLength; } + set + { + maxOutputLength = Math.Max(value, 0); + } + } + [InGameEditable, Serialize("1", true, description: "The signal sent when the condition is met.", alwaysUseInstanceValues: true)] public string Output { @@ -53,17 +64,6 @@ namespace Barotrauma.Items.Components } } - private int maxOutputLength; - [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] - public int MaxOutputLength - { - get { return maxOutputLength; } - set - { - maxOutputLength = Math.Max(value, 0); - } - } - public AndComponent(Item item, XElement element) : base(item, element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/EqualsComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/EqualsComponent.cs index 48296b87b..6283a3449 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/EqualsComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/EqualsComponent.cs @@ -15,6 +15,17 @@ namespace Barotrauma.Items.Components //the output is sent if both inputs have received a signal within the timeframe protected float timeFrame; + private int maxOutputLength; + [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] + public int MaxOutputLength + { + get { return maxOutputLength; } + set + { + maxOutputLength = Math.Max(value, 0); + } + } + [InGameEditable, Serialize("1", true, description: "The signal sent when the condition is met.", alwaysUseInstanceValues: true)] public string Output { @@ -45,17 +56,6 @@ namespace Barotrauma.Items.Components } } - private int maxOutputLength; - [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] - public int MaxOutputLength - { - get { return maxOutputLength; } - set - { - maxOutputLength = Math.Max(value, 0); - } - } - [InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The maximum amount of time between the received signals. If set to 0, the signals must be received at the same time.", alwaysUseInstanceValues: true)] public float TimeFrame { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MemoryComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MemoryComponent.cs index 33a769f92..045ffd45a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MemoryComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MemoryComponent.cs @@ -6,6 +6,17 @@ namespace Barotrauma.Items.Components { partial class MemoryComponent : ItemComponent, IServerSerializable { + private int maxValueLength; + [Editable, Serialize(200, false, description: "The maximum length of the stored value. Warning: Large values can lead to large memory usage or networking issues.")] + public int MaxValueLength + { + get { return maxValueLength; } + set + { + maxValueLength = Math.Max(value, 0); + } + } + private string value; [InGameEditable, Serialize("", true, description: "The currently stored signal the item outputs.", alwaysUseInstanceValues: true)] @@ -23,17 +34,6 @@ namespace Barotrauma.Items.Components } } - private int maxValueLength; - [Editable, Serialize(200, false, description: "The maximum length of the stored value. Warning: Large values can lead to large memory usage or networking issues.")] - public int MaxValueLength - { - get { return maxValueLength; } - set - { - maxValueLength = Math.Max(value, 0); - } - } - protected bool writeable = true; public MemoryComponent(Item item, XElement element) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs index 53e7374c2..f6ca3513c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs @@ -74,6 +74,17 @@ namespace Barotrauma.Items.Components } } + private int maxOutputLength; + [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] + public int MaxOutputLength + { + get { return maxOutputLength; } + set + { + maxOutputLength = Math.Max(value, 0); + } + } + private string output; [InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected movement.", alwaysUseInstanceValues: true)] public string Output @@ -106,17 +117,6 @@ namespace Barotrauma.Items.Components } } - private int maxOutputLength; - [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] - public int MaxOutputLength - { - get { return maxOutputLength; } - set - { - maxOutputLength = Math.Max(value, 0); - } - } - [Editable(DecimalCount = 3), Serialize(0.01f, true, description: "How fast the objects within the detector's range have to be moving (in m/s).", alwaysUseInstanceValues: true)] public float MinimumVelocity { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs index cf86b139c..b2f512ca2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs @@ -18,6 +18,17 @@ namespace Barotrauma.Items.Components private bool nonContinuousOutputSent; + private int maxOutputLength; + [Editable, Serialize(200, false, description: "The maximum length of the output string. Warning: Large values can lead to large memory usage or networking issues.")] + public int MaxOutputLength + { + get { return maxOutputLength; } + set + { + maxOutputLength = Math.Max(value, 0); + } + } + private string output; [InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the regular expression.", alwaysUseInstanceValues: true)] @@ -61,23 +72,11 @@ namespace Barotrauma.Items.Components catch { - item.SendSignal("ERROR", "signal_out"); return; } } } - private int maxOutputLength; - [Editable, Serialize(200, false, description: "The maximum length of the output string. Warning: Large values can lead to large memory usage or networking issues.")] - public int MaxOutputLength - { - get { return maxOutputLength; } - set - { - maxOutputLength = Math.Max(value, 0); - } - } - public RegExFindComponent(Item item, XElement element) : base(item, element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SignalCheckComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SignalCheckComponent.cs index 3a0ce8ba1..9cc306e6a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SignalCheckComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SignalCheckComponent.cs @@ -5,6 +5,17 @@ namespace Barotrauma.Items.Components { class SignalCheckComponent : ItemComponent { + private int maxOutputLength; + [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] + public int MaxOutputLength + { + get { return maxOutputLength; } + set + { + maxOutputLength = Math.Max(value, 0); + } + } + private string output; [InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the target signal.", alwaysUseInstanceValues: true)] public string Output @@ -40,17 +51,6 @@ namespace Barotrauma.Items.Components [InGameEditable, Serialize("", true, description: "The value to compare the received signals against.", alwaysUseInstanceValues: true)] public string TargetSignal { get; set; } - private int maxOutputLength; - [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] - public int MaxOutputLength - { - get { return maxOutputLength; } - set - { - maxOutputLength = Math.Max(value, 0); - } - } - public SignalCheckComponent(Item item, XElement element) : base(item, element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs index e0d4a3a38..1a924aa6f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs @@ -10,6 +10,17 @@ namespace Barotrauma.Items.Components private bool fireInRange; + private int maxOutputLength; + [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] + public int MaxOutputLength + { + get { return maxOutputLength; } + set + { + maxOutputLength = Math.Max(value, 0); + } + } + private string output; [InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected a fire.", alwaysUseInstanceValues: true)] public string Output @@ -42,17 +53,6 @@ namespace Barotrauma.Items.Components } } - private int maxOutputLength; - [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] - public int MaxOutputLength - { - get { return maxOutputLength; } - set - { - maxOutputLength = Math.Max(value, 0); - } - } - public SmokeDetector(Item item, XElement element) : base(item, element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs index 97ee74d54..fc927d27b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs @@ -12,6 +12,17 @@ namespace Barotrauma.Items.Components private bool isInWater; private float stateSwitchDelay; + private int maxOutputLength; + [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] + public int MaxOutputLength + { + get { return maxOutputLength; } + set + { + maxOutputLength = Math.Max(value, 0); + } + } + private string output; [InGameEditable, Serialize("1", true, description: "The signal the item sends out when it's underwater.", alwaysUseInstanceValues: true)] public string Output @@ -44,17 +55,6 @@ namespace Barotrauma.Items.Components } } - private int maxOutputLength; - [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] - public int MaxOutputLength - { - get { return maxOutputLength; } - set - { - maxOutputLength = Math.Max(value, 0); - } - } - public WaterDetector(Item item, XElement element) : base(item, element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index 3a6743bd7..d9a1ba7fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -144,6 +144,11 @@ namespace Barotrauma // If the variant does not exist, parse the path so that it uses first variant. SpritePath = tempPath.Replace("[VARIANT]", "1"); } + if (!File.Exists(SpritePath) && _gender == Gender.None) + { + // If there's no sprite for Gender.None does not exist, try to use male sprite + SpritePath = tempPath.Replace("[GENDER]", "male"); + } if (parseSpritePath) { Sprite.ParseTexturePath(file: SpritePath); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index 6c6ebc229..999f1e80a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -507,7 +507,8 @@ namespace Barotrauma #if CLIENT if (visualSlots != null) { - visualSlots[i]?.ShowBorderHighlight(Color.White, 0.1f, 0.4f); + visualSlots[i].ShowBorderHighlight(Color.White, 0.1f, 0.4f); + if (selectedSlot?.Inventory == this) { selectedSlot.ForceTooltipRefresh = true; } } #endif @@ -625,7 +626,10 @@ namespace Barotrauma else { existingItems.Add(slots[index].FirstOrDefault()); - slots[index].RemoveItem(existingItems.First()); + for (int j = 0; j < capacity; j++) + { + if (existingItems.Any(existingItem => slots[j].Contains(existingItem))) { slots[j].RemoveItem(existingItems.First()); } + } } List stackedItems = new List(); @@ -831,7 +835,14 @@ namespace Barotrauma if (!slots[n].Contains(item)) { continue; } slots[n].RemoveItem(item); - item.ParentInventory = null; + item.ParentInventory = null; +#if CLIENT + if (visualSlots != null) + { + visualSlots[n].ShowBorderHighlight(Color.White, 0.1f, 0.4f); + if (selectedSlot?.Inventory == this) { selectedSlot.ForceTooltipRefresh = true; } + } +#endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 75a55da54..b6b8b989d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1356,11 +1356,11 @@ namespace Barotrauma { if (!isNetworkEvent && checkCondition) { - if (condition == 0.0f && effect.type != ActionType.OnBroken) return; + if (condition == 0.0f && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { return; } } - if (effect.type != type) return; + if (effect.type != type) { return; } - bool hasTargets = (effect.TargetIdentifiers == null); + bool hasTargets = effect.TargetIdentifiers == null; targets.Clear(); @@ -2611,7 +2611,7 @@ namespace Barotrauma foreach (XAttribute attribute in element.Attributes()) { - if (!item.SerializableProperties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) continue; + if (!item.SerializableProperties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) { continue; } bool shouldBeLoaded = false; foreach (var propertyAttribute in property.Attributes.OfType()) { @@ -2622,7 +2622,31 @@ namespace Barotrauma } } - if (shouldBeLoaded) { property.TrySetValue(item, attribute.Value); } + if (shouldBeLoaded) + { + object prevValue = property.GetValue(item); + property.TrySetValue(item, attribute.Value); + //create network events for properties that differ from the prefab values + //(e.g. if a character has an item with modified colors in their inventory) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && property.Attributes.OfType().Any() && + (submarine == null || !submarine.Loading )) + { + switch (property.Name) + { + case "Tags": + case "Condition": + case "Description": + //these can be ignored, they're always written in the spawn data + break; + default: + if (!(property.GetValue(item)?.Equals(prevValue) ?? true)) + { + GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ChangeProperty, property }); + } + break; + } + } + } } item.ParseLinks(element, idRemap); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index b68a548e7..7806c2708 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -424,15 +424,9 @@ namespace Barotrauma } foreach (var edge in cell.Edges) { - if (!MathUtils.GetLineIntersection(worldPosition, cell.Center, edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, out Vector2 intersection)) + if (MathUtils.LineSegmentToPointDistanceSquared((edge.Point1 + cell.Translation).ToPoint(), (edge.Point2 + cell.Translation).ToPoint(), worldPosition.ToPoint()) < worldRange * worldRange) { - continue; - } - - float wallDist = Vector2.DistanceSquared(worldPosition, intersection); - if (wallDist < worldRange * worldRange) - { - destructibleWall.AddDamage(damage, worldPosition); + destructibleWall.AddDamage(levelWallDamage, worldPosition); break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 991acbcc4..2703357f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -501,7 +501,7 @@ namespace Barotrauma EntityGrids.Add(newGrid); foreach (Hull hull in hullList) { - if (hull.Submarine == submarine) newGrid.InsertEntity(hull); + if (hull.Submarine == submarine && !hull.IdFreed) { newGrid.InsertEntity(hull); } } return newGrid; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index da0727689..50e8b87c2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -3663,7 +3663,7 @@ namespace Barotrauma } //break powered items - foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered))) + foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable))) { if (item.NonInteractable) { continue; } if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs index c948f79dc..62e9e02ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs @@ -252,6 +252,10 @@ namespace Barotrauma Vector2 worldPos = saveElement.GetAttributeVector2("worldpos", Vector2.Zero); if (worldPos != Vector2.Zero) { + if (GameMain.GameSession != null && GameMain.GameSession.MirrorLevel) + { + worldPos.X = GameMain.GameSession.LevelData.Size.X - worldPos.X; + } sub.SetPosition(worldPos); } else @@ -417,7 +421,12 @@ namespace Barotrauma if (leaveBehind) { saveElement.SetAttributeValue("location", Level.Loaded.Seed); - saveElement.SetAttributeValue("worldpos", XMLExtensions.Vector2ToString(sub.SubBody.Position)); + Vector2 position = sub.SubBody.Position; + if (Level.Loaded.Mirrored) + { + position.X = Level.Loaded.Size.X - position.X; + } + saveElement.SetAttributeValue("worldpos", XMLExtensions.Vector2ToString(position)); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index f82d99b94..5f27063a4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -975,7 +975,14 @@ namespace Barotrauma private void ChangeLocationType(Location location, LocationTypeChange change) { string prevName = location.Name; - location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase))); + + var newType = LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase)); + if (newType.OutpostTeam != location.Type.OutpostTeam || + newType.HasOutpost != location.Type.HasOutpost) + { + location.ClearMissions(); + } + location.ChangeType(newType); ChangeLocationTypeProjSpecific(location, prevName, change); foreach (var requirement in change.Requirements) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index e6e6254ee..9f81f4ab3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -1068,12 +1068,36 @@ namespace Barotrauma public void NeutralizeBallast() { float neutralBallastLevel = 0.5f; + int selectedSteeringValue = 0; foreach (Item item in Item.ItemList) { if (item.Submarine != this) { continue; } var steering = item.GetComponent(); if (steering == null) { continue; } - neutralBallastLevel = Math.Min(neutralBallastLevel, steering.NeutralBallastLevel); + + //find how many pumps/engines in this sub the steering item is connected to + int steeringValue = 1; + Connection connectionX = item.GetComponent()?.Connections.Find(c => c.Name == "velocity_x_out"); + Connection connectionY = item.GetComponent()?.Connections.Find(c => c.Name == "velocity_y_out"); + if (connectionX != null) + { + foreach (Engine engine in steering.Item.GetConnectedComponentsRecursive(connectionX)) + { + if (engine.Item.Submarine == this) { steeringValue++; } + } + } + if (connectionY != null) + { + foreach (Pump pump in steering.Item.GetConnectedComponentsRecursive(connectionY)) + { + if (pump.Item.Submarine == this) { steeringValue++; } + } + } + //the nav terminal that's connected to the most engines/pumps in the sub most likely controls the sub (instead of a shuttle or some other system) + if (steeringValue > selectedSteeringValue) + { + neutralBallastLevel = steering.NeutralBallastLevel; + } } HashSet ballastHulls = new HashSet(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index 6b789bec1..a81baf888 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -146,7 +146,7 @@ namespace Barotrauma foreach (Hull hull in Hull.hullList) { - if (hull.Submarine != submarine) { continue; } + if (hull.Submarine != submarine || hull.IdFreed) { continue; } Rectangle rect = hull.Rect; farseerBody.CreateRectangle( diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index 79f9df744..528f328bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -914,7 +914,6 @@ namespace Barotrauma.Networking { if (!HasPassword) return true; byte[] saltedPw = SaltPassword(Encoding.UTF8.GetBytes(password), salt); - DebugConsole.NewMessage(ToolBox.ByteArrayToString(input) + " " + ToolBox.ByteArrayToString(saltedPw)); if (input.Length != saltedPw.Length) return false; for (int i = 0; i < input.Length; i++) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 52a1e2aa7..8090206e6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -284,6 +284,11 @@ namespace Barotrauma // Currently only used for OnDamaged. TODO: is there a better, more generic way to do this? public readonly bool OnlyPlayerTriggered; + /// + /// Can the StatusEffect be applied when the item applying it is broken + /// + public readonly bool AllowWhenBroken = false; + public HashSet TargetIdentifiers { get { return targetIdentifiers; } @@ -356,6 +361,7 @@ namespace Barotrauma OnlyInside = element.GetAttributeBool("onlyinside", false); OnlyOutside = element.GetAttributeBool("onlyoutside", false); OnlyPlayerTriggered = element.GetAttributeBool("onlyplayertriggered", false); + AllowWhenBroken = element.GetAttributeBool("allowwhenbroken", false); Range = element.GetAttributeFloat("range", 0.0f); Offset = element.GetAttributeVector2("offset", Vector2.Zero); diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index c1640d13f..7a5dc04a7 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index a59bad499..a8cd9ff9f 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index c6d0ec1f7..9b74542ee 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,47 @@ +------------------------------------------------------------------------------------------------------ +v0.13.3.11 +--------------------------------------------------------------------------------------------------------- + +Changes: +- Adjusted autopilot logic to make it better at keeping the sub afloat when there's extra water on board. The maximum velocity of the autopilot is limited, which previously prevented it from emptying the ballast fully. Now it's only limited if the submarine is heading in the correct direction with enough speed, so if the sub starts sinking due to extra water, the autopilot can compensate and fully empty the ballast. +- Made large monsters immune to sufforin. + +Fixes: +- Fixes to issues that prevented mods installed from the Workshop from getting automatically updated and caused errors when trying to join modded servers. +- Fixed an issue in the voice chat that caused audio crackling when multiple people were speaking at the same time. +- Fixed inability to drag players who've ragdolled themselves with space bar. +- Fixed "failed to send message to remote peer" error spam when a client's internet connection goes down. +- Fixed certain submarines spawning with a non-neutral ballast level (Kastrull seems to have been the only affected vanilla submarine). Happened because the game would determine the neutral ballast level from the first nav terminal it finds in the sub, without checking whether that terminal controls a shuttle or the sub itself. +- Fixed explosions using wall damage value instead of level wall damage when the explosion happens outside a level wall. +- Fixed inability to place oxygenite tanks in oxygen tank shelves. +- Fixed crashing when a bot tries to find a diving suit inside ruins. +- Fixed minor wall draw order issue in Orca's top deck. +- Fixed bots trying to treat nausea even though the only cure for it is to wait it out. +- Fixed status monitor being messed up on mirrored subs that contain shuttles. +- Fixed railgun payloads not exploding. +- Fixed server sometimes assigning players who haven't set any job preferences as the captain, even if someone else wants to be the captain. +- Fixed ancient weapon propelling the character in an incorrect direction when using it underwater. +- Fixed sonar beacons not appearing on the sonar in the sub editor's test mode. +- Fixed "easterbunny" traitor mission failing after the mudraptor hatches. +- Fixed "changes to your character will be applied after the round ends" texts getting drawn behind the tab menu elements. +- Fixed "IsToggle" setting not working on periscopes. +- Fixed "Praise the Honkmother" mission being impossible to complete because it required more items to be delivered than the crate contains. +- Fixed humanhusk's inventory not being visible while controlling one. +- Fixed sub editor's entity list getting cleared when changing the entity category while a container/cabinet is selected. +- Fixed items that have been edited in the sub editor (e.g. recolored clothing) reverting back to default when starting a new campaign round while the item is in a character's inventory. +- Fixed clicking on command interface nodes crashing the game if the key is rebound to primary mouse button. +- Fixed tiling issues in some of the legacy background wall sprites. +- Fixed Spinelings attacking Leucocytes. Spinelings should avoid Leucocytes if they get close. +- Fixed bots being unable to stand on the platform below Humpback's docking hatch, preventing them from repairing the hatch. +- Fixed "beacon station" text and "generating preview..." in the submarine preview window not being translated when playing in a language other than English. + +Modding: +- Fixed console errors when a character with no gender tries to wear clothing that only has separate male and female sprites. +- Fixed crashing if the current style doesn't define a saving indicator. +- Fixed inability to load ragdoll/animation definitions from mods that override a character. +- Fixed ClearTagAction not properly clearing all the tags assigned by a scripted event. +- Fixed crashing if a mod's version number is incorrectly formatted in filelist.xml. + --------------------------------------------------------------------------------------------------------- v0.13.0.11 --------------------------------------------------------------------------------------------------------- diff --git a/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj b/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj index 8bdd4fac7..99073a079 100644 --- a/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj +++ b/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj @@ -12,4 +12,9 @@ https://github.com/lostromb/concentus + + full + true + + diff --git a/Libraries/Facepunch.Steamworks/SteamUgc.cs b/Libraries/Facepunch.Steamworks/SteamUgc.cs index 9d41b8ebe..03ada053c 100644 --- a/Libraries/Facepunch.Steamworks/SteamUgc.cs +++ b/Libraries/Facepunch.Steamworks/SteamUgc.cs @@ -25,7 +25,20 @@ namespace Steamworks internal static void InstallEvents( bool server ) { - Dispatch.Install( x => OnDownloadItemResult?.Invoke( x.Result ), server ); + Dispatch.Install( x => + { + if (x.AppID == SteamClient.AppId) + { + OnDownloadItemResult?.Invoke(x.Result); + + Ugc.Item item = new Ugc.Item(x.PublishedFileId); + if (item.IsInstalled && (onItemInstalled?.ContainsKey(x.PublishedFileId) ?? false)) + { + onItemInstalled[x.PublishedFileId]?.Invoke(); + onItemInstalled.Remove(x.PublishedFileId); + } + } + }, server ); Dispatch.Install(x => { if (x.AppID == SteamClient.AppId)