diff --git a/.gitignore b/.gitignore index c9d87be39..49930d4bc 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ bld/ [Rr]elease*/ # Barotrauma content folder -Content/ +BarotraumaShared/Content/ *.v12.suo *.suo diff --git a/Barotrauma/BarotraumaClient/OpenAL32.dll b/Barotrauma/BarotraumaClient/OpenAL32.dll new file mode 100644 index 000000000..f903a0c63 Binary files /dev/null and b/Barotrauma/BarotraumaClient/OpenAL32.dll differ diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index 860f23719..08da64291 100644 --- a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.9.4")] -[assembly: AssemblyFileVersion("0.8.9.4")] +[assembly: AssemblyVersion("0.8.9.5")] +[assembly: AssemblyFileVersion("0.8.9.5")] diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 96d53ee97..f4a5ce9cd 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -1051,6 +1051,102 @@ namespace Barotrauma NPCConversation.WriteToCSV(); })); + commands.Add(new Command("loadtexts", "loadtexts [sourcefile] [destinationfile]: Loads all lines of text from a given .txt file and inserts them sequientially into the elements of an xml file. If the file paths are omitted, EnglishVanilla.txt and EnglishVanilla.xml are used.", (string[] args) => + { + string sourcePath = args.Length > 0 ? args[0] : "Content/Texts/EnglishVanilla.txt"; + string destinationPath = args.Length > 1 ? args[1] : "Content/Texts/EnglishVanilla.xml"; + + string[] lines; + try + { + lines = File.ReadAllLines(sourcePath); + } + catch (Exception e) + { + ThrowError("Reading the file \"" + sourcePath + "\" failed.", e); + return; + } + var doc = XMLExtensions.TryLoadXml(destinationPath); + int i = 0; + foreach (XElement element in doc.Root.Elements()) + { + if (i >= lines.Length) + { + ThrowError("Error while loading texts to the xml file. The xml has more elements than the number of lines in the text file."); + return; + } + element.Value = lines[i]; + i++; + } + doc.Save(destinationPath); + }, + () => + { + var files = TextManager.GetTextFiles().Select(f => f.Replace("\\", "/")); + return new string[][] + { + files.Where(f => Path.GetExtension(f)==".txt").ToArray(), + files.Where(f => Path.GetExtension(f)==".xml").ToArray() + }; + })); + + commands.Add(new Command("updatetextfile", "updatetextfile [sourcefile] [destinationfile]: Inserts all the xml elements that are only present in the source file into the destination file. Can be used to update outdated translation files more easily.", (string[] args) => + { + if (args.Length < 2) return; + string sourcePath = args[0]; + string destinationPath = args[1]; + + var sourceDoc = XMLExtensions.TryLoadXml(sourcePath); + var destinationDoc = XMLExtensions.TryLoadXml(destinationPath); + + XElement destinationElement = destinationDoc.Root.Elements().First(); + foreach (XElement element in sourceDoc.Root.Elements()) + { + if (destinationDoc.Root.Element(element.Name) == null) + { + element.Value = "!!!!!!!!!!!!!" + element.Value; + destinationElement.AddAfterSelf(element); + } + XNode nextNode = destinationElement.NextNode; + while ((!(nextNode is XElement) || nextNode == element) && nextNode != null) nextNode = nextNode.NextNode; + destinationElement = nextNode as XElement; + } + destinationDoc.Save(destinationPath); + }, + () => + { + var files = TextManager.GetTextFiles().Where(f => Path.GetExtension(f) == ".xml").Select(f => f.Replace("\\", "/")).ToArray(); + return new string[][] + { + files, + files + }; + })); + + commands.Add(new Command("dumpentitytexts", "dumpentitytexts [filepath]: gets the names and descriptions of all entity prefabs and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EntityTexts.txt", (string[] args) => + { + string filePath = args.Length > 0 ? args[0] : "Content/Texts/EntityTexts.txt"; + List lines = new List(); + foreach (MapEntityPrefab me in MapEntityPrefab.List) + { + lines.Add("" + me.Name + ""); + lines.Add("" + me.Description + ""); + } + File.WriteAllLines(filePath, lines); + })); +#if DEBUG + commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) => + { + if (args.Length != 1) return; + TextManager.CheckForDuplicates(args[0]); + })); + + commands.Add(new Command("writetocsv", "Writes the default language (English) to a .csv file.", (string[] args) => + { + TextManager.WriteToCSV(); + NPCConversation.WriteToCSV(); + })); + commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) => { float defaultZoom = Screen.Selected.Cam.DefaultZoom; diff --git a/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs index ff3c8fa55..308aa869f 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs @@ -11,7 +11,7 @@ namespace Barotrauma class ChatBox { private static Sprite radioIcon; - + private GUIFrame guiFrame; private GUIListBox chatBox; @@ -25,7 +25,7 @@ namespace Barotrauma private bool isSinglePlayer; public bool IsSinglePlayer => isSinglePlayer; - + private bool toggleOpen = true; private float openState; @@ -78,7 +78,7 @@ namespace Barotrauma chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.9f), guiFrame.RectTransform), style: "ChatBox"); toggleButton = new GUIButton(new RectTransform(new Point(toggleButtonWidth, HUDLayoutSettings.ChatBoxArea.Height), parent.RectTransform), style: "UIToggleButton"); - + toggleButton.OnClicked += (GUIButton btn, object userdata) => { toggleOpen = !toggleOpen; @@ -89,13 +89,17 @@ namespace Barotrauma } return true; }; - + inputBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), guiFrame.RectTransform, Anchor.BottomCenter), style: "ChatTextBox") { Font = GUI.SmallFont, MaxTextLength = ChatMessage.MaxLength }; + inputBox.OnDeselected += (gui, Keys) => + { + gui.Text = ""; + }; radioButton = new GUIButton(new RectTransform(new Vector2(0.1f, 2.0f), inputBox.RectTransform, HUDLayoutSettings.ChatBoxAlignment == Alignment.Right ? Anchor.BottomRight : Anchor.BottomLeft, @@ -132,7 +136,7 @@ namespace Barotrauma { command = ""; } - } + } switch (command) { @@ -169,7 +173,7 @@ namespace Barotrauma { chatBox.RemoveChild(chatBox.Content.Children.First()); } - + float prevSize = chatBox.BarSize; string displayedText = message.TranslatedText; @@ -202,8 +206,8 @@ namespace Barotrauma CanBeFocused = true }; - if (message is OrderChatMessage orderChatMsg && - Character.Controlled != null && + if (message is OrderChatMessage orderChatMsg && + Character.Controlled != null && orderChatMsg.TargetCharacter == Character.Controlled) { msgHolder.Flash(Color.OrangeRed * 0.6f, flashDuration: 5.0f); @@ -237,7 +241,7 @@ namespace Barotrauma CanBeFocused = false }; int textWidth = (int)Math.Max( - msgText.Font.MeasureString(msgText.WrappedText).X, + msgText.Font.MeasureString(msgText.WrappedText).X, senderText.Font.MeasureString(senderText.WrappedText).X); popupMsg.RectTransform.Resize(new Point(textWidth + 20, msgText.Rect.Bottom - senderText.Rect.Y), resizeChildren: false); popupMessages.Enqueue(popupMsg); @@ -265,7 +269,7 @@ namespace Barotrauma { timer += CoroutineManager.DeltaTime; float wavePhase = timer / animDuration * MathHelper.TwoPi; - message.RectTransform.ScreenSpaceOffset = + message.RectTransform.ScreenSpaceOffset = new Point((int)(Math.Sin(wavePhase) * (1.0f - timer / animDuration) * 50.0f), 0); yield return CoroutineStatus.Running; } @@ -294,7 +298,7 @@ namespace Barotrauma new Point(HUDLayoutSettings.ChatBoxArea.X, HUDLayoutSettings.ChatBoxArea.Y) : new Point(HUDLayoutSettings.ChatBoxArea.Right - toggleButtonWidth, HUDLayoutSettings.ChatBoxArea.Y); } - + public void Update(float deltaTime) { if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale) @@ -305,7 +309,7 @@ namespace Barotrauma } - + if (toggleOpen || (inputBox != null && inputBox.Selected)) { openState += deltaTime * 5.0f; diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIProgressBar.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIProgressBar.cs index bc511a192..eb784d6a7 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIProgressBar.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIProgressBar.cs @@ -1,5 +1,6 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System; namespace Barotrauma { @@ -24,6 +25,14 @@ namespace Barotrauma get { return barSize; } set { + if (!MathUtils.IsValid(value)) + { + GameAnalyticsManager.AddErrorEventOnce( + "GUIProgressBar.BarSize_setter", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to set the BarSize of a GUIProgressBar to an invalid value (" + value + ")\n" + Environment.StackTrace); + return; + } barSize = MathHelper.Clamp(value, 0.0f, 1.0f); //UpdateRect(); } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index 3075e88ce..1755c22cd 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -26,7 +26,7 @@ namespace Barotrauma private List characters = new List(); private Point screenResolution; - + #region UI private GUIFrame guiFrame; @@ -61,9 +61,34 @@ namespace Barotrauma public CrewManager(XElement element, bool isSinglePlayer) : this(isSinglePlayer) { - foreach (XElement subElement in element.Elements()) + return characterListBox.Rect; + } + + partial void InitProjectSpecific() + { + guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent) { - if (subElement.Name.ToString().ToLowerInvariant() != "character") continue; + CanBeFocused = false + }; + + Point scrollButtonSize = new Point((int)(200 * GUI.Scale), (int)(30 * GUI.Scale)); + + crewArea = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), "", Color.Transparent) + { + CanBeFocused = false + }; + toggleCrewButton = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), HUDLayoutSettings.CrewArea.Height), guiFrame.RectTransform) + { AbsoluteOffset = HUDLayoutSettings.CrewArea.Location }, + "", style: "UIToggleButton"); + toggleCrewButton.OnClicked += (GUIButton btn, object userdata) => + { + toggleCrewAreaOpen = !toggleCrewAreaOpen; + foreach (GUIComponent child in btn.Children) + { + child.SpriteEffects = toggleCrewAreaOpen ? SpriteEffects.None : SpriteEffects.FlipHorizontally; + } + return true; + }; var characterInfo = new CharacterInfo(subElement); characterInfos.Add(characterInfo); @@ -75,21 +100,54 @@ namespace Barotrauma } } + var reports = Order.PrefabList.FindAll(o => o.TargetAllCharacters && o.SymbolSprite != null); + reportButtonFrame = new GUILayoutGroup(new RectTransform( + new Point((HUDLayoutSettings.CrewArea.Height - (int)((reports.Count - 1) * 5 * GUI.Scale)) / reports.Count, HUDLayoutSettings.CrewArea.Height), guiFrame.RectTransform)) + { + AbsoluteSpacing = (int)(5 * GUI.Scale), + UserData = "reportbuttons", + CanBeFocused = false + }; + + //report buttons + foreach (Order order in reports) + { + if (!order.TargetAllCharacters || order.SymbolSprite == null) continue; + var btn = new GUIButton(new RectTransform(new Point(reportButtonFrame.Rect.Width), reportButtonFrame.RectTransform), style: null) + { + OnClicked = (GUIButton button, object userData) => + { + if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false; + SetCharacterOrder(null, order, null, Character.Controlled); + return true; + }, + UserData = order, + ToolTip = order.Name + }; + + new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow") + { + Color = Color.Red * 0.8f, + HoverColor = Color.Red * 1.0f, + PressedColor = Color.Red * 0.6f, + UserData = "highlighted", + CanBeFocused = false, + Visible = false + }; + + var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite, scaleToFit: true) + { + Color = order.Color, + HoverColor = Color.Lerp(order.Color, Color.White, 0.5f), + ToolTip = order.Name + }; + } + screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); prevUIScale = GUI.Scale; } - - #endregion - - #region Character list management - - public Rectangle GetCharacterListArea() - { - return characterListBox.Rect; - } - partial void InitProjectSpecific() { guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent) @@ -167,10 +225,7 @@ namespace Barotrauma return true; } }; - chatBox.InputBox.OnDeselected += (gui, Keys) => - { - this.chatBox.InputBox.Text = ""; - }; + chatBox.InputBox.OnTextChanged += chatBox.TypingChatMessage; } @@ -283,6 +338,7 @@ namespace Barotrauma DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); return; } + } characterInfos.Add(characterInfo); } @@ -371,7 +427,7 @@ namespace Barotrauma frame.Color = character.Info.Job.Prefab.UIColor; frame.SelectedColor = Color.Lerp(frame.Color, Color.White, 0.5f); frame.HoverColor = Color.Lerp(frame.Color, Color.White, 0.9f); - + new GUIFrame(new RectTransform(new Point(characterInfoWidth, (int)(frame.Rect.Height * 1.3f)), frame.RectTransform, Anchor.CenterLeft), style: "OuterGlow") { UserData = "highlight", @@ -396,7 +452,7 @@ namespace Barotrauma HoverColor = frame.HoverColor, ToolTip = characterToolTip }; - + var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, "GUISoundIcon") { @@ -604,10 +660,6 @@ namespace Barotrauma characterListBox.BarScroll -= characterListBox.BarScroll % step; characterListBox.BarScroll += dir * step; - child.Visible = (Character)button.UserData != character; - } - } - private IEnumerable KillCharacterAnim(GUIComponent component) { List components = component.GetAllChildren().ToList(); @@ -619,6 +671,18 @@ namespace Barotrauma if (characterInfos.Contains(revivedCharacter.Info)) AddCharacter(revivedCharacter); } + private IEnumerable KillCharacterAnim(GUIComponent component) + { + List components = component.GetAllChildren().ToList(); + components.Add(component); + components.RemoveAll(c => c.UserData as string == "soundicon" || c.UserData as string == "soundicondisabled"); + + foreach (GUIComponent comp in components) + { + comp.Color = Color.DarkRed; + } + if (string.IsNullOrEmpty(text)) { return; } + yield return new WaitForSeconds(1.0f); float timer = 0.0f; @@ -685,7 +749,7 @@ namespace Barotrauma #region Voice chat - + public void SetPlayerVoiceIconState(Client client, bool muted, bool mutedLocally) { if (client?.Character == null) { return; } @@ -1101,7 +1165,7 @@ namespace Barotrauma if (child.Visible) { child.GetChildByUserData("highlight").Visible = character == Character.Controlled; - + var soundIcon = child.FindChild(character)?.FindChild("soundicon"); if (soundIcon != null) { diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameSession.cs index 3e27da6e0..fc3e1c8f8 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameSession.cs @@ -143,6 +143,9 @@ namespace Barotrauma infoFrame?.UpdateManually(deltaTime); } + + infoFrame?.UpdateManually(deltaTime); + } public void Draw(SpriteBatch spriteBatch) { diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/Source/GameSession/RoundSummary.cs index 1dc471e8e..275b357bf 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/RoundSummary.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/RoundSummary.cs @@ -79,10 +79,6 @@ namespace Barotrauma } } - foreach (GUIComponent child in infoTextBox.Content.Children) - { - child.CanBeFocused = false; - } foreach (GUIComponent child in infoTextBox.Content.Children) { child.CanBeFocused = false; diff --git a/Barotrauma/BarotraumaClient/Source/GameSettings.cs b/Barotrauma/BarotraumaClient/Source/GameSettings.cs index 23013d172..a111ddc03 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSettings.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Extensions; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -498,28 +499,43 @@ namespace Barotrauma voiceMode.OnSelect = (GUIRadioButtonGroup rbg, Enum value) => { if (rbg.Selected != null && rbg.Selected.Equals(value)) return; - VoiceMode vMode = (VoiceMode)value; - VoiceSetting = vMode; - - if (vMode == VoiceMode.Activity) + try { - voiceActivityGroup.Visible = true; - if (GameMain.Client == null && VoipCapture.Instance == null) + VoiceMode vMode = (VoiceMode)value; + VoiceSetting = vMode; + if (vMode == VoiceMode.Activity) { - VoipCapture.Create(GameMain.Config.VoiceCaptureDevice); + voiceActivityGroup.Visible = true; + if (GameMain.Client == null && VoipCapture.Instance == null) + { + VoipCapture.Create(GameMain.Config.VoiceCaptureDevice); + } + if (VoipCapture.Instance == null) + { + VoiceSetting = vMode = VoiceMode.Disabled; + voiceInputContainer.Visible = false; + voiceActivityGroup.Visible = false; + return; + } } - } - else - { - voiceActivityGroup.Visible = false; - if (GameMain.Client == null) + else { - VoipCapture.Instance?.Dispose(); + voiceActivityGroup.Visible = false; + if (GameMain.Client == null) + { + VoipCapture.Instance?.Dispose(); + } } - } - voiceInputContainer.Visible = (vMode == VoiceMode.PushToTalk); - UnsavedSettings = true; + voiceInputContainer.Visible = (vMode == VoiceMode.PushToTalk); + UnsavedSettings = true; + } + catch (Exception e) + { + DebugConsole.ThrowError("Failed to set voice capture mode.", e); + GameAnalyticsManager.AddErrorEventOnce("SetVoiceCaptureMode", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to set voice capture mode. " + e.Message + "\n" + e.StackTrace); + VoiceSetting = VoiceMode.Disabled; + } }; voiceMode.Selected = VoiceSetting; @@ -599,21 +615,38 @@ namespace Barotrauma .Replace("[missingfiletypes]", string.Join(", ", missingContentTypes)); } } - GUITextBlock aimAssistText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("AimAssist")); - GUIScrollBar aimAssistSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), - barSize: 0.1f) + languageDD.SelectItem(TextManager.Language); + languageDD.OnSelected = (guiComponent, obj) => { - UserData = aimAssistText, - BarScroll = MathUtils.InverseLerp(0.0f, 5.0f, AimAssistAmount), - OnMoved = (scrollBar, scroll) => - { - ChangeSliderText(scrollBar, scroll); - AimAssistAmount = MathHelper.Lerp(0.0f, 5.0f, scroll); - return true; - }, - Step = 0.1f + string newLanguage = obj as string; + if (newLanguage == Language) return true; + + UnsavedSettings = true; + Language = newLanguage; + + new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage")); + + return true; + }; + + //spacing + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null); + + new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft), + TextManager.Get("Cancel")) + { + IgnoreLayoutGroups = true, + OnClicked = (x, y) => + { + if (UnsavedSettings) + { + LoadPlayerConfig(); + } + if (Screen.Selected == GameMain.MainMenuScreen) GameMain.MainMenuScreen.ReturnToMainMenu(null, null); + GUI.SettingsMenuOpen = false; + return true; + } }; - aimAssistSlider.OnMoved(aimAssistSlider, aimAssistSlider.BarScroll); //spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs index 72589ccf5..94a9eee32 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs @@ -184,6 +184,63 @@ namespace Barotrauma.Items.Components } } } + + public void ApplyTo(RectTransform target) + { + if (RelativeOffset.HasValue) + { + target.RelativeOffset = RelativeOffset.Value; + } + else if (AbsoluteOffset.HasValue) + { + target.AbsoluteOffset = AbsoluteOffset.Value; + } + if (RelativeSize.HasValue) + { + target.RelativeSize = RelativeSize.Value; + } + else if (AbsoluteSize.HasValue) + { + target.NonScaledSize = AbsoluteSize.Value; + } + if (Anchor.HasValue) + { + target.Anchor = Anchor.Value; + } + if (Pivot.HasValue) + { + target.Pivot = Pivot.Value; + } + else + { + target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor); + } + target.RecalculateChildren(true, true); + } + } + + public GUIFrame GuiFrame { get; protected set; } + + [Serialize(false, false)] + public bool AllowUIOverlap + { + get; + set; + } + + private ItemComponent linkToUIComponent; + [Serialize("", false)] + public string LinkUIToComponent + { + get; + set; + } + + [Serialize(0, false)] + public int HudPriority + { + get; + private set; } private bool shouldMuffleLooping; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs index 440c75764..be6007236 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs @@ -982,7 +982,7 @@ namespace Barotrauma.Items.Components Vector2 textSize = GUI.SmallFont.MeasureString(wrappedLabel); //flip the text to left side when the marker is on the left side or goes outside the right edge of the interface - if (dir.X < 0.0f || labelPos.X + textSize.X + 10 > GuiFrame.Rect.X) labelPos.X -= textSize.X + 10; + if ((dir.X < 0.0f || labelPos.X + textSize.X + 10 > GuiFrame.Rect.X) && labelPos.X - textSize.X > 0) labelPos.X -= textSize.X + 10; GUI.DrawString(spriteBatch, new Vector2(labelPos.X + 10, labelPos.Y), diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index bddee1acf..41c6994b3 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -64,6 +64,31 @@ namespace Barotrauma public float SpriteRotation; + private GUITextBlock itemInUseWarning; + private GUITextBlock ItemInUseWarning + { + get + { + if (itemInUseWarning == null) + { + itemInUseWarning = new GUITextBlock(new RectTransform(new Point(10), GUI.Canvas), "", + textColor: Color.Orange, color: Color.Black, + textAlignment:Alignment.Center, style: "OuterGlow"); + } + return itemInUseWarning; + } + } + + public override bool SelectableInEditor + { + get + { + return parentInventory == null && (body == null || body.Enabled) && ShowItems; + } + } + + public float SpriteRotation; + public Color GetSpriteColor() { Color color = spriteColor; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs index 9c879c314..5f0b72f2e 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs @@ -544,6 +544,62 @@ namespace Barotrauma } } + public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime) + { + float newWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; + float newOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8); + + bool hasFireSources = message.ReadBoolean(); + int fireSourceCount = 0; + List newFireSources = new List(); + if (hasFireSources) + { + fireSourceCount = message.ReadRangedInteger(0, 16); + for (int i = 0; i < fireSourceCount; i++) + { + newFireSources.Add(new Vector3( + MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f), + MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f), + message.ReadRangedSingle(0.0f, 1.0f, 8))); + } + } + + if (serverUpdateDelay > 0.0f) { return; } + + WaterVolume = newWaterVolume; + OxygenPercentage = newOxygenPercentage; + + for (int i = 0; i < fireSourceCount; i++) + { + Vector2 pos = new Vector2( + rect.X + rect.Width * newFireSources[i].X, + rect.Y - rect.Height + (rect.Height * newFireSources[i].Y)); + float size = newFireSources[i].Z * rect.Width; + + var newFire = i < FireSources.Count ? + FireSources[i] : + new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true); + newFire.Position = pos; + newFire.Size = new Vector2(size, newFire.Size.Y); + + //ignore if the fire wasn't added to this room (invalid position)? + if (!FireSources.Contains(newFire)) + { + newFire.Remove(); + continue; + } + } + + for (int i = FireSources.Count - 1; i >= fireSourceCount; i--) + { + FireSources[i].Remove(); + if (i < FireSources.Count) + { + FireSources.RemoveAt(i); + } + } + } + public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime) { float newWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs index 85082e551..fcc45f7dc 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs @@ -26,8 +26,6 @@ namespace Barotrauma.Lights private float currLightMapScale; - private float currLightMapScale; - public Color AmbientLight; public RenderTarget2D LightMap diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs index 25b3c6218..8b80fc766 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs @@ -1,5 +1,6 @@ using Lidgren.Network; using System; +using System.Linq; namespace Barotrauma.Networking { @@ -65,13 +66,13 @@ namespace Barotrauma.Networking if (order.TargetAllCharacters) { GameMain.GameSession?.CrewManager?.AddOrder( - new Order(order.Prefab, targetEntity, (targetEntity as Item)?.GetComponent()), + new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType)), order.Prefab.FadeOutTime); } else if (targetCharacter != null) { targetCharacter.SetOrder( - new Order(order.Prefab, targetEntity, (targetEntity as Item)?.GetComponent()), + new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType)), orderOption, senderCharacter); } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 8aaf1986c..5ff7bdcac 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -89,7 +89,7 @@ namespace Barotrauma.Networking { get { return entityEventManager.MidRoundSyncing; } } - + private int ownerKey; public GameClient(string newName, string ip, int ownerKey=0) @@ -146,7 +146,7 @@ namespace Barotrauma.Networking OnSelected = ToggleEndRoundVote, Visible = false }; - + ShowLogButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.6f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) }, TextManager.Get("ServerLog")) { @@ -222,7 +222,7 @@ namespace Barotrauma.Networking // Create new instance of configs. Parameter is "application Id". It has to be same on client and server. NetPeerConfiguration = new NetPeerConfiguration("barotrauma"); - + NetPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt | NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error); @@ -245,7 +245,7 @@ namespace Barotrauma.Networking NetOutgoingMessage outmsg = client.CreateMessage(); WriteAuthRequest(outmsg); - // Connect client, to ip previously requested from user + // Connect client, to ip previously requested from user try { client.Connect(IPEndPoint, outmsg); @@ -557,16 +557,16 @@ namespace Barotrauma.Networking { System.Threading.Thread.Sleep(10); } - + outmsg.Write(SteamManager.GetSteamID()); outmsg.Write(steamAuthTicket.Data.Length); outmsg.Write(steamAuthTicket.Data); DebugConsole.Log("Sending Steam auth request"); DebugConsole.Log(" Steam ID: " + SteamManager.GetSteamID()); - DebugConsole.Log(" Ticket data: " + + DebugConsole.Log(" Ticket data: " + ToolBox.LimitString(string.Concat(steamAuthTicket.Data.Select(b => b.ToString("X2"))), 16)); - DebugConsole.Log(" Msg length: " + outmsg.LengthBytes); + DebugConsole.Log(" Msg length: " + outmsg.LengthBytes); } } @@ -580,7 +580,7 @@ namespace Barotrauma.Networking { c.UpdateSoundPosition(); } - + if (VoipCapture.Instance != null) { if (VoipCapture.Instance.LastEnqueueAudio > DateTime.Now - new TimeSpan(0, 0, 0, 0, milliseconds: 100)) @@ -633,7 +633,7 @@ namespace Barotrauma.Networking GameMain.MainMenuScreen.Select(); return; } - + if (gameStarted && Screen.Selected == GameMain.GameScreen) { EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned; @@ -658,7 +658,7 @@ namespace Barotrauma.Networking } // Update current time - updateTimer = DateTime.Now + updateInterval; + updateTimer = DateTime.Now + updateInterval; } private CoroutineHandle startGameCoroutine; @@ -735,8 +735,8 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox)); WriteCharacterInfo(readyToStartMsg); - - client.SendMessage(readyToStartMsg, NetDeliveryMethod.ReliableUnordered); + + client.SendMessage(readyToStartMsg, NetDeliveryMethod.ReliableUnordered); break; case ServerPacketHeader.STARTGAME: startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(inc), false); @@ -861,9 +861,10 @@ namespace Barotrauma.Networking else { msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString()); + for (int i = 1; i < splitMsg.Length; i++) { - msg += splitMsg[i]; + msg += TextManager.GetServerMessage(splitMsg[i]); } } var msgBox = new GUIMessageBox(TextManager.Get(allowReconnect ? "ConnectionLost" : "CouldNotConnectToServer"), msg); @@ -927,6 +928,15 @@ namespace Barotrauma.Networking } } + private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable permittedConsoleCommands) + { + if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || + permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) + { + if (newPermissions == permissions) return; + } + } + private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable permittedConsoleCommands) { if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || @@ -940,7 +950,7 @@ namespace Barotrauma.Networking //don't show the "permissions changed" popup if the client owns the server if (ownerKey == 0) { - GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "permissions"); + GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "permissions"); string msg = ""; if (newPermissions == ClientPermissions.None) @@ -977,7 +987,7 @@ namespace Barotrauma.Networking }; } } - } + } GameMain.NetLobbyScreen.UpdatePermissions(); } @@ -986,13 +996,13 @@ namespace Barotrauma.Networking { if (Character != null) Character.Remove(); HasSpawned = false; - + GameMain.LightManager.LightingEnabled = true; //enable spectate button in case we fail to start the round now //(for example, due to a missing sub file or an error) GameMain.NetLobbyScreen.ShowSpectateButton(); - + entityEventManager.Clear(); LastSentEntityEventID = 0; @@ -1063,14 +1073,14 @@ namespace Barotrauma.Networking else { if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset(); - GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, - reloadSub: true, + GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, + reloadSub: true, loadSecondSub: false, mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); } - + if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); - + gameStarted = true; GameMain.GameScreen.Select(); @@ -1137,7 +1147,7 @@ namespace Barotrauma.Networking submarines.Add(new Submarine(Path.Combine(Submarine.SavePath, subName) + ".sub", subHash, false)); } } - + GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, submarines); GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, submarines); @@ -1241,7 +1251,7 @@ namespace Barotrauma.Networking UInt16 settingsLen = inc.ReadUInt16(); byte[] settingsData = inc.ReadBytes(settingsLen); - + if (inc.ReadBoolean()) { if (GameSettings.VerboseLogging) @@ -1272,12 +1282,15 @@ namespace Barotrauma.Networking string levelSeed = inc.ReadString(); float levelDifficulty = inc.ReadFloat(); + byte botCount = inc.ReadByte(); + BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal; + byte botCount = inc.ReadByte(); BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal; bool autoRestartEnabled = inc.ReadBoolean(); float autoRestartTimer = autoRestartEnabled ? inc.ReadFloat() : 0.0f; - + //ignore the message if we already a more up-to-date one if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID)) { @@ -1299,13 +1312,13 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled); GameMain.NetLobbyScreen.SetMissionType(missionTypeIndex); - if (!allowModeVoting) GameMain.NetLobbyScreen.SelectMode(modeIndex); - + if (!allowModeVoting) GameMain.NetLobbyScreen.SelectMode(modeIndex); + GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating); GameMain.NetLobbyScreen.LevelSeed = levelSeed; GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty); GameMain.NetLobbyScreen.SetBotCount(botCount); - GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode); + GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode); GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer); serverSettings.VoiceChatEnabled = voiceChatEnabled; @@ -1373,7 +1386,7 @@ namespace Barotrauma.Networking entity.ClientRead(objHeader, inc, sendingTime); } - //force to the correct position in case the entity doesn't exist + //force to the correct position in case the entity doesn't exist //or the message wasn't read correctly for whatever reason inc.Position = msgEndPos; inc.ReadPadBits(); @@ -1409,7 +1422,7 @@ namespace Barotrauma.Networking errorLines.Add(" - " + e.ToString()); } } - + foreach (string line in errorLines) { DebugConsole.ThrowError(line); @@ -1476,7 +1489,7 @@ namespace Barotrauma.Networking chatMsgQueue[i].ClientWrite(outmsg); } outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE); - + if (outmsg.LengthBytes > client.Configuration.MaximumTransmissionUnit) { DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + client.Configuration.MaximumTransmissionUnit); @@ -1509,7 +1522,7 @@ namespace Barotrauma.Networking return; } chatMsgQueue[i].ClientWrite(outmsg); - } + } outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE); @@ -1523,7 +1536,7 @@ namespace Barotrauma.Networking public void SendChatMessage(ChatMessage msg) { - if (client.ServerConnection == null) return; + if (client.ServerConnection == null) return; lastQueueChatMsgID++; msg.NetStateID = lastQueueChatMsgID; chatMsgQueue.Add(msg); @@ -1536,7 +1549,7 @@ namespace Barotrauma.Networking ChatMessage chatMessage = ChatMessage.Create( gameStarted && myCharacter != null ? myCharacter.Name : name, message, - type, + type, gameStarted && myCharacter != null ? myCharacter : null); lastQueueChatMsgID++; @@ -1586,12 +1599,12 @@ namespace Barotrauma.Networking for (int i = 0; i < 2; i++) { - IEnumerable subListChildren = (i == 0) ? - GameMain.NetLobbyScreen.ShuttleList.ListBox.Content.Children : + IEnumerable subListChildren = (i == 0) ? + GameMain.NetLobbyScreen.ShuttleList.ListBox.Content.Children : GameMain.NetLobbyScreen.SubList.Content.Children; - var subElement = subListChildren.FirstOrDefault(c => - ((Submarine)c.UserData).Name == newSub.Name && + var subElement = subListChildren.FirstOrDefault(c => + ((Submarine)c.UserData).Name == newSub.Name && ((Submarine)c.UserData).MD5Hash.Hash == newSub.MD5Hash.Hash); if (subElement == null) continue; @@ -1613,7 +1626,7 @@ namespace Barotrauma.Networking }; } - if (GameMain.NetLobbyScreen.FailedSelectedSub != null && + if (GameMain.NetLobbyScreen.FailedSelectedSub != null && GameMain.NetLobbyScreen.FailedSelectedSub.First == newSub.Name && GameMain.NetLobbyScreen.FailedSelectedSub.Second == newSub.MD5Hash.Hash) { @@ -1636,7 +1649,7 @@ namespace Barotrauma.Networking if (GameMain.GameSession.Submarine == null) { var gameSessionDoc = SaveUtil.LoadGameSessionDoc(GameMain.GameSession.SavePath); - string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDoc.Root.GetAttributeString("submarine", "")) + ".sub"; + string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDoc.Root.GetAttributeString("submarine", "")) + ".sub"; GameMain.GameSession.Submarine = new Submarine(subPath, ""); } @@ -1662,7 +1675,7 @@ namespace Barotrauma.Networking if (!(entity is IClientSerializable)) throw new InvalidCastException("entity is not IClientSerializable"); entityEventManager.CreateEvent(entity as IClientSerializable, extraData); } - + public bool HasPermission(ClientPermissions permission) { return permissions.HasFlag(permission); @@ -1675,7 +1688,7 @@ namespace Barotrauma.Networking command = command.ToLowerInvariant(); return permittedConsoleCommands.Any(c => c.ToLowerInvariant() == command); } - + public override void Disconnect() { client.Shutdown(""); @@ -1711,7 +1724,7 @@ namespace Barotrauma.Networking VoipClient = null; GameMain.Client = null; } - + public void WriteCharacterInfo(NetOutgoingMessage msg) { msg.Write(characterInfo == null); @@ -1733,7 +1746,7 @@ namespace Barotrauma.Networking msg.Write(jobPreferences[i].Identifier); } } - + public void Vote(VoteType voteType, object data) { NetOutgoingMessage msg = client.CreateMessage(); @@ -1771,7 +1784,7 @@ namespace Barotrauma.Networking { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.Kick); + msg.Write((UInt16)ClientPermissions.Kick); msg.Write(kickedName); msg.Write(reason); @@ -1926,7 +1939,7 @@ namespace Barotrauma.Networking msg.Write(false); msg.WritePadBits(); msg.Write(saveName); - + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); GameMain.NetLobbyScreen.CampaignSetupUI = null; @@ -1948,13 +1961,13 @@ namespace Barotrauma.Networking public bool SpectateClicked(GUIButton button, object userData) { if (button != null) button.Enabled = false; - + NetOutgoingMessage readyToStartMsg = client.CreateMessage(); readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME); //assume we have the required sub files to start the round //(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer) - readyToStartMsg.Write(true); + readyToStartMsg.Write(true); WriteCharacterInfo(readyToStartMsg); @@ -2052,9 +2065,9 @@ namespace Barotrauma.Networking } SendChatMessage(message); - + textBox.Deselect(); - textBox.Text = ""; + textBox.Text = ""; return true; } @@ -2080,10 +2093,14 @@ namespace Barotrauma.Networking GUITextBox msgBox = null; if (Screen.Selected == GameMain.GameScreen) - { msgBox = chatBox.InputBox; } + { + msgBox = chatBox.InputBox; + } else if (Screen.Selected == GameMain.NetLobbyScreen) - { msgBox = GameMain.NetLobbyScreen.TextBox; } - + { + msgBox = GameMain.NetLobbyScreen.TextBox; + } + if (gameStarted && Screen.Selected == GameMain.GameScreen) { if (!GUI.DisableHUD && !GUI.DisableUpperHUD) @@ -2112,11 +2129,11 @@ namespace Barotrauma.Networking } } - //tab doesn't autoselect the chatbox when debug console is open, + //tab doesn't autoselect the chatbox when debug console is open, //because tab is used for autocompleting console commands if (msgBox != null) { - if ((PlayerInput.KeyHit(InputType.Chat) || PlayerInput.KeyHit(InputType.RadioChat)) && + if ((PlayerInput.KeyHit(InputType.Chat) || PlayerInput.KeyHit(InputType.RadioChat)) && GUI.KeyboardDispatcher.Subscriber == null) { if (msgBox.Selected) @@ -2182,15 +2199,15 @@ namespace Barotrauma.Networking GUI.DrawRectangle(spriteBatch, new Rectangle( (int)pos.X, - (int)pos.Y, - fileReceiver.ActiveTransfers.Count * 210 + 10, - 32), + (int)pos.Y, + fileReceiver.ActiveTransfers.Count * 210 + 10, + 32), Color.Black * 0.8f, true); - + for (int i = 0; i < fileReceiver.ActiveTransfers.Count; i++) { var transfer = fileReceiver.ActiveTransfers[i]; - + GUI.DrawString(spriteBatch, pos, ToolBox.LimitString(TextManager.Get("DownloadingFile").Replace("[filename]", transfer.FileName), GUI.SmallFont, 200), diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs new file mode 100644 index 000000000..1155547c9 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs @@ -0,0 +1,808 @@ +using Lidgren.Network; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace Barotrauma.Networking +{ + partial class ServerSettings : ISerializableEntity + { + partial class NetPropertyData + { + public GUIComponent GUIComponent; + public object TempValue; + + public void AssignGUIComponent(GUIComponent component) + { + GUIComponent = component; + GUIComponentValue = property.GetValue(serverSettings); + TempValue = GUIComponentValue; + } + + public object GUIComponentValue + { + get + { + if (GUIComponent == null) return null; + else if (GUIComponent is GUITickBox tickBox) return tickBox.Selected; + else if (GUIComponent is GUITextBox textBox) return textBox.Text; + else if (GUIComponent is GUIScrollBar scrollBar) return scrollBar.BarScrollValue; + else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) return radioButtonGroup.Selected; + return null; + } + set + { + if (GUIComponent == null) return; + else if (GUIComponent is GUITickBox tickBox) tickBox.Selected = (bool)value; + else if (GUIComponent is GUITextBox textBox) textBox.Text = (string)value; + else if (GUIComponent is GUIScrollBar scrollBar) scrollBar.BarScrollValue = (float)value; + else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) radioButtonGroup.Selected = (Enum)value; + } + } + + public bool ChangedLocally + { + get + { + if (GUIComponent == null) return false; + return !PropEquals(TempValue, GUIComponentValue); + } + } + + public bool PropEquals(object a,object b) + { + switch (typeString) + { + case "float": + if (!(a is float?)) return false; + if (!(b is float?)) return false; + return (float)a == (float)b; + case "int": + if (!(a is int?)) return false; + if (!(b is int?)) return false; + return (int)a == (int)b; + case "bool": + if (!(a is bool?)) return false; + if (!(b is bool?)) return false; + return (bool)a == (bool)b; + case "Enum": + if (!(a is Enum)) return false; + if (!(b is Enum)) return false; + return ((Enum)a).Equals((Enum)b); + default: + return a.ToString().Equals(b.ToString(),StringComparison.InvariantCulture); + } + } + } + private Dictionary tempMonsterEnabled; + + partial void InitProjSpecific() + { + var properties = TypeDescriptor.GetProperties(GetType()).Cast(); + + SerializableProperties = new Dictionary(); + + foreach (var property in properties) + { + SerializableProperty objProperty = new SerializableProperty(property, this); + SerializableProperties.Add(property.Name.ToLowerInvariant(), objProperty); + } + } + + public void ClientAdminRead(NetBuffer incMsg) + { + int count = incMsg.ReadUInt16(); + for (int i = 0; i < count; i++) + { + UInt32 key = incMsg.ReadUInt32(); + if (netProperties.ContainsKey(key)) + { + bool changedLocally = netProperties[key].ChangedLocally; + netProperties[key].Read(incMsg); + netProperties[key].TempValue = netProperties[key].Value; + + if (netProperties[key].GUIComponent != null) + { + if (!changedLocally) + { + netProperties[key].GUIComponentValue = netProperties[key].Value; + } + } + } + else + { + UInt32 size = incMsg.ReadVariableUInt32(); + incMsg.Position += 8 * size; + } + } + + ReadMonsterEnabled(incMsg); + BanList.ClientAdminRead(incMsg); + Whitelist.ClientAdminRead(incMsg); + } + + public void ClientRead(NetBuffer incMsg) + { + ServerName = incMsg.ReadString(); + ServerMessageText = incMsg.ReadString(); + TickRate = incMsg.ReadRangedInteger(1, 60); + GameMain.NetworkMember.TickRate = TickRate; + + ReadExtraCargo(incMsg); + + Voting.ClientRead(incMsg); + + bool isAdmin = incMsg.ReadBoolean(); + incMsg.ReadPadBits(); + if (isAdmin) + { + ClientAdminRead(incMsg); + } + } + + public void ClientAdminWrite(NetFlags dataToSend, int missionType = 0, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0) + { + if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return; + + NetOutgoingMessage outMsg = GameMain.NetworkMember.NetPeer.CreateMessage(); + + outMsg.Write((byte)ClientPacketHeader.SERVER_SETTINGS); + + outMsg.Write((byte)dataToSend); + + if (dataToSend.HasFlag(NetFlags.Name)) + { + if (GameMain.NetLobbyScreen.ServerName.Text != ServerName) + { + ServerName = GameMain.NetLobbyScreen.ServerName.Text; + } + outMsg.Write(ServerName); + } + + if (dataToSend.HasFlag(NetFlags.Message)) + { + if (GameMain.NetLobbyScreen.ServerMessage.Text != ServerMessageText) + { + ServerMessageText = GameMain.NetLobbyScreen.ServerMessage.Text; + } + outMsg.Write(ServerMessageText); + } + + if (dataToSend.HasFlag(NetFlags.Properties)) + { + //TODO: split this up? + WriteExtraCargo(outMsg); + + IEnumerable> changedProperties = netProperties.Where(kvp => kvp.Value.ChangedLocally); + UInt32 count = (UInt32)changedProperties.Count(); + bool changedMonsterSettings = tempMonsterEnabled != null && tempMonsterEnabled.Any(p => p.Value != MonsterEnabled[p.Key]); + + outMsg.Write(count); + foreach (KeyValuePair prop in changedProperties) + { + DebugConsole.NewMessage(prop.Value.Name, Color.Lime); + outMsg.Write(prop.Key); + prop.Value.Write(outMsg, prop.Value.GUIComponentValue); + } + + outMsg.Write(changedMonsterSettings); outMsg.WritePadBits(); + if (changedMonsterSettings) WriteMonsterEnabled(outMsg, tempMonsterEnabled); + BanList.ClientAdminWrite(outMsg); + Whitelist.ClientAdminWrite(outMsg); + } + + if (dataToSend.HasFlag(NetFlags.Misc)) + { + outMsg.Write((byte)(missionType + 1)); + outMsg.Write((byte)(traitorSetting + 1)); + outMsg.Write((byte)(botCount + 1)); + outMsg.Write((byte)(botSpawnMode + 1)); + + outMsg.Write(levelDifficulty ?? -1000.0f); + + outMsg.Write(autoRestart != null); + outMsg.Write(autoRestart ?? false); + outMsg.WritePadBits(); + } + + if (dataToSend.HasFlag(NetFlags.LevelSeed)) + { + outMsg.Write(GameMain.NetLobbyScreen.SeedBox.Text); + } + + (GameMain.NetworkMember.NetPeer as NetClient).SendMessage(outMsg, NetDeliveryMethod.ReliableOrdered); + } + + //GUI stuff + private GUIFrame settingsFrame; + private GUIFrame[] settingsTabs; + private GUIButton[] tabButtons; + private int settingsTabIndex; + + enum SettingsTab + { + Rounds, + Server, + Banlist, + Whitelist + } + + private NetPropertyData GetPropertyData(string name) + { + return netProperties.First(p => p.Value.Name == name).Value; + } + + public void AddToGUIUpdateList() + { + if (GUI.DisableHUD) return; + + settingsFrame?.AddToGUIUpdateList(); + } + + private void CreateSettingsFrame() + { + foreach (NetPropertyData prop in netProperties.Values) + { + prop.TempValue = prop.Value; + } + + //background frame + settingsFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null, color: Color.Black * 0.5f); + new GUIButton(new RectTransform(Vector2.One, settingsFrame.RectTransform), "", style: null).OnClicked += (btn, userData) => + { + if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) ToggleSettingsFrame(btn, userData); + return true; + }; + + new GUIButton(new RectTransform(Vector2.One, settingsFrame.RectTransform), "", style: null) + { + OnClicked = ToggleSettingsFrame + }; + + //center frames + GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.7f), settingsFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 430) }); + GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), innerFrame.RectTransform, Anchor.Center), style: null); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paddedFrame.RectTransform), TextManager.Get("Settings"), font: GUI.LargeFont); + + var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.01f + }; + + //tabs + var tabValues = Enum.GetValues(typeof(SettingsTab)).Cast().ToArray(); + string[] tabNames = new string[tabValues.Count()]; + for (int i = 0; i < tabNames.Length; i++) + { + tabNames[i] = TextManager.Get("ServerSettings" + tabValues[i] + "Tab"); + } + settingsTabs = new GUIFrame[tabNames.Length]; + tabButtons = new GUIButton[tabNames.Length]; + for (int i = 0; i < tabNames.Length; i++) + { + settingsTabs[i] = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.79f), paddedFrame.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.05f) }, + style: "InnerFrame"); + + tabButtons[i] = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), tabNames[i], style: "GUITabButton") + { + UserData = i, + OnClicked = SelectSettingsTab + }; + } + + SelectSettingsTab(tabButtons[0], 0); + + //"Close" + var closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), paddedFrame.RectTransform, Anchor.BottomRight), TextManager.Get("Close")) + { + OnClicked = ToggleSettingsFrame + }; + + //-------------------------------------------------------------------------------- + // game settings + //-------------------------------------------------------------------------------- + + var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center)) + { + Stretch = true, + RelativeSpacing = 0.02f + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsSubSelection")); + var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + + GUIRadioButtonGroup selectionMode = new GUIRadioButtonGroup(); + for (int i = 0; i < 3; i++) + { + var selectionTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), selectionFrame.RectTransform), TextManager.Get(((SelectionMode)i).ToString()), font: GUI.SmallFont); + selectionMode.AddRadioButton((SelectionMode)i, selectionTick); + } + DebugConsole.NewMessage(SubSelectionMode.ToString(),Color.White); + GetPropertyData("SubSelectionMode").AssignGUIComponent(selectionMode); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsModeSelection")); + selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + + selectionMode = new GUIRadioButtonGroup(); + for (int i = 0; i < 3; i++) + { + var selectionTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), selectionFrame.RectTransform), TextManager.Get(((SelectionMode)i).ToString()), font: GUI.SmallFont); + selectionMode.AddRadioButton((SelectionMode)i, selectionTick); + } + GetPropertyData("ModeSelectionMode").AssignGUIComponent(selectionMode); + + var endBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), + TextManager.Get("ServerSettingsEndRoundWhenDestReached")); + GetPropertyData("EndRoundAtLevelEnd").AssignGUIComponent(endBox); + + var endVoteBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), + TextManager.Get("ServerSettingsEndRoundVoting")); + GetPropertyData("AllowEndVoting").AssignGUIComponent(endVoteBox); + + CreateLabeledSlider(roundsTab, "ServerSettingsEndRoundVotesRequired", out GUIScrollBar slider, out GUITextBlock sliderLabel); + + string endRoundLabel = sliderLabel.Text; + slider.Step = 0.2f; + slider.Range = new Vector2(0.5f, 1.0f); + GetPropertyData("EndVoteRequiredRatio").AssignGUIComponent(slider); + slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + ((GUITextBlock)scrollBar.UserData).Text = endRoundLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %"; + return true; + }; + slider.OnMoved(slider, slider.BarScroll); + + var respawnBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), + TextManager.Get("ServerSettingsAllowRespawning")); + GetPropertyData("AllowRespawn").AssignGUIComponent(respawnBox); + + CreateLabeledSlider(roundsTab, "ServerSettingsRespawnInterval", out slider, out sliderLabel); + string intervalLabel = sliderLabel.Text; + slider.Step = 0.05f; + slider.Range = new Vector2(10.0f, 600.0f); + GetPropertyData("RespawnInterval").AssignGUIComponent(slider); + slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + GUITextBlock text = scrollBar.UserData as GUITextBlock; + text.Text = intervalLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); + return true; + }; + slider.OnMoved(slider, slider.BarScroll); + + var minRespawnText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), "") + { + ToolTip = TextManager.Get("ServerSettingsMinRespawnToolTip") + }; + + string minRespawnLabel = TextManager.Get("ServerSettingsMinRespawn"); + CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); + slider.ToolTip = minRespawnText.ToolTip; + slider.UserData = minRespawnText; + slider.Step = 0.1f; + slider.Range = new Vector2(0.0f, 1.0f); + GetPropertyData("MinRespawnRatio").AssignGUIComponent(slider); + slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + ((GUITextBlock)scrollBar.UserData).Text = minRespawnLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %"; + return true; + }; + slider.OnMoved(slider, MinRespawnRatio); + + var respawnDurationText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), "") + { + ToolTip = TextManager.Get("ServerSettingsRespawnDurationToolTip") + }; + + string respawnDurationLabel = TextManager.Get("ServerSettingsRespawnDuration"); + CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); + slider.ToolTip = respawnDurationText.ToolTip; + slider.UserData = respawnDurationText; + slider.Step = 0.1f; + slider.Range = new Vector2(60.0f, 660.0f); + slider.ScrollToValue = (GUIScrollBar scrollBar, float barScroll) => + { + return barScroll >= 1.0f ? 0.0f : barScroll * (scrollBar.Range.Y - scrollBar.Range.X) + scrollBar.Range.X; + }; + slider.ValueToScroll = (GUIScrollBar scrollBar, float value) => + { + return value <= 0.0f ? 1.0f : (value - scrollBar.Range.X) / (scrollBar.Range.Y - scrollBar.Range.X); + }; + GetPropertyData("MaxTransportTime").AssignGUIComponent(slider); + slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + if (barScroll == 1.0f) + { + ((GUITextBlock)scrollBar.UserData).Text = respawnDurationLabel + TextManager.Get("Unlimited"); + } + else + { + ((GUITextBlock)scrollBar.UserData).Text = respawnDurationLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); + } + + return true; + }; + slider.OnMoved(slider, slider.BarScroll); + + var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + + var monsterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonHolder.RectTransform), + TextManager.Get("ServerSettingsMonsterSpawns")) + { + Enabled = !GameMain.NetworkMember.GameStarted + }; + var monsterFrame = new GUIListBox(new RectTransform(new Vector2(0.6f, 0.7f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.BottomLeft, Pivot.BottomRight)) + { + Visible = false + }; + monsterButton.UserData = monsterFrame; + monsterButton.OnClicked = (button, obj) => + { + if (GameMain.NetworkMember.GameStarted) + { + ((GUIComponent)obj).Visible = false; + button.Enabled = false; + return true; + } + ((GUIComponent)obj).Visible = !((GUIComponent)obj).Visible; + return true; + }; + + List monsterNames = MonsterEnabled.Keys.ToList(); + tempMonsterEnabled = new Dictionary(MonsterEnabled); + foreach (string s in monsterNames) + { + string translatedLabel = TextManager.Get($"Character.{s}", true); + var monsterEnabledBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), monsterFrame.Content.RectTransform) { MinSize = new Point(0, 25) }, + label: translatedLabel != null ? translatedLabel : s) + { + Selected = tempMonsterEnabled[s], + OnSelected = (GUITickBox tb) => + { + tempMonsterEnabled[s] = tb.Selected; + return true; + } + }; + } + + var cargoButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonHolder.RectTransform), + TextManager.Get("ServerSettingsAdditionalCargo")) + { + Enabled = !GameMain.NetworkMember.GameStarted + }; + var cargoFrame = new GUIListBox(new RectTransform(new Vector2(0.6f, 0.7f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.BottomRight, Pivot.BottomLeft)) + { + Visible = false + }; + cargoButton.UserData = cargoFrame; + cargoButton.OnClicked = (button, obj) => + { + if (GameMain.NetworkMember.GameStarted) + { + ((GUIComponent)obj).Visible = false; + button.Enabled = false; + return true; + } + ((GUIComponent)obj).Visible = !((GUIComponent)obj).Visible; + return true; + }; + + foreach (ItemPrefab ip in MapEntityPrefab.List.Where(p => p is ItemPrefab).Select(p => p as ItemPrefab)) + { + if (!ip.CanBeBought && !ip.Tags.Contains("smallitem")) continue; + + var itemFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), cargoFrame.Content.RectTransform) { MinSize = new Point(0, 30) }, isHorizontal: true) + { + Stretch = true, + UserData = cargoFrame, + RelativeSpacing = 0.05f + }; + + + if (ip.InventoryIcon != null || ip.sprite != null) + { + GUIImage img = new GUIImage(new RectTransform(new Point(itemFrame.Rect.Height), itemFrame.RectTransform), + ip.InventoryIcon ?? ip.sprite, scaleToFit: true) + { + CanBeFocused = false + }; + img.Color = img.Sprite == ip.InventoryIcon ? ip.InventoryIconColor : ip.SpriteColor; + } + + ExtraCargo.TryGetValue(ip, out int cargoVal); + var amountInput = new GUINumberInput(new RectTransform(new Vector2(0.3f, 1.0f), itemFrame.RectTransform), + GUINumberInput.NumberType.Int, textAlignment: Alignment.CenterLeft) + { + MinValueInt = 0, + MaxValueInt = 100, + IntValue = cargoVal + }; + amountInput.OnValueChanged += (numberInput) => + { + if (ExtraCargo.ContainsKey(ip)) + { + ExtraCargo[ip] = numberInput.IntValue; + if (numberInput.IntValue <= 0) ExtraCargo.Remove(ip); + } + else + { + ExtraCargo.Add(ip, numberInput.IntValue); + } + }; + } + + //-------------------------------------------------------------------------------- + // server settings + //-------------------------------------------------------------------------------- + + var serverTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Server].RectTransform, Anchor.Center)) + { + Stretch = true, + RelativeSpacing = 0.02f + }; + + //*********************************************** + + var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), + TextManager.Get("ServerSettingsVoiceChatEnabled")); + GetPropertyData("VoiceChatEnabled").AssignGUIComponent(voiceChatEnabled); + + //*********************************************** + + string autoRestartDelayLabel = TextManager.Get("ServerSettingsAutoRestartDelay"); + var startIntervalText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), autoRestartDelayLabel); + var startIntervalSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), barSize: 0.1f) + { + UserData = startIntervalText, + Step = 0.05f, + OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + GUITextBlock text = scrollBar.UserData as GUITextBlock; + text.Text = autoRestartDelayLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); + return true; + } + }; + startIntervalSlider.Range = new Vector2(10.0f, 300.0f); + GetPropertyData("AutoRestartInterval").AssignGUIComponent(startIntervalSlider); + startIntervalSlider.OnMoved(startIntervalSlider, startIntervalSlider.BarScroll); + + //*********************************************** + + var startWhenClientsReady = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), + TextManager.Get("ServerSettingsStartWhenClientsReady")); + GetPropertyData("StartWhenClientsReady").AssignGUIComponent(startWhenClientsReady); + + CreateLabeledSlider(serverTab, "ServerSettingsStartWhenClientsReadyRatio", out slider, out sliderLabel); + string clientsReadyRequiredLabel = sliderLabel.Text; + slider.Step = 0.2f; + slider.Range = new Vector2(0.5f, 1.0f); + slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + ((GUITextBlock)scrollBar.UserData).Text = clientsReadyRequiredLabel.Replace("[percentage]", ((int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f)).ToString()); + return true; + }; + GetPropertyData("StartWhenClientsReadyRatio").AssignGUIComponent(slider); + slider.OnMoved(slider, slider.BarScroll); + + //*********************************************** + + var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowSpectating")); + GetPropertyData("AllowSpectating").AssignGUIComponent(allowSpecBox); + + var voteKickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowVoteKick")); + GetPropertyData("AllowVoteKick").AssignGUIComponent(voteKickBox); + + CreateLabeledSlider(serverTab, "ServerSettingsKickVotesRequired", out slider, out sliderLabel); + string votesRequiredLabel = sliderLabel.Text; + slider.Step = 0.2f; + slider.Range = new Vector2(0.5f, 1.0f); + slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + ((GUITextBlock)scrollBar.UserData).Text = votesRequiredLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %"; + return true; + }; + GetPropertyData("KickVoteRequiredRatio").AssignGUIComponent(slider); + slider.OnMoved(slider, slider.BarScroll); + + CreateLabeledSlider(serverTab, "ServerSettingsAutobanTime", out slider, out sliderLabel); + string autobanLabel = sliderLabel.Text; + slider.Step = 0.05f; + slider.Range = new Vector2(0.0f, MaxAutoBanTime); + slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + ((GUITextBlock)scrollBar.UserData).Text = autobanLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); + return true; + }; + GetPropertyData("AutoBanTime").AssignGUIComponent(slider); + slider.OnMoved(slider, slider.BarScroll); + + var shareSubsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsShareSubFiles")); + GetPropertyData("AllowFileTransfers").AssignGUIComponent(shareSubsBox); + + var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed")); + GetPropertyData("RandomizeSeed").AssignGUIComponent(randomizeLevelBox); + + var saveLogsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsSaveLogs")) + { + OnSelected = (GUITickBox) => + { + //TODO: fix? + //showLogButton.Visible = SaveServerLogs; + return true; + } + }; + GetPropertyData("SaveServerLogs").AssignGUIComponent(saveLogsBox); + + var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton")); + GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox); + + var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio")); + + CreateLabeledSlider(serverTab, "", out slider, out sliderLabel); + /*var traitorRatioText = new GUITextBlock(new Rectangle(20, y + 20, 20, 20), "Traitor ratio: 20 %", "", settingsTabs[1], GUI.SmallFont); + var traitorRatioSlider = new GUIScrollBar(new Rectangle(150, y + 22, 100, 15), "", 0.1f, settingsTabs[1]);*/ + var traitorRatioSlider = slider; + traitorRatioBox.OnSelected = (GUITickBox) => + { + traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); + return true; + }; + + if (TraitorUseRatio) + { + traitorRatioSlider.Range = new Vector2(0.1f, 1.0f); + } + else + { + traitorRatioSlider.Range = new Vector2(1.0f, maxPlayers); + } + + string traitorRatioLabel = TextManager.Get("ServerSettingsTraitorRatio"); + string traitorCountLabel = TextManager.Get("ServerSettingsTraitorCount"); + + traitorRatioSlider.Range = new Vector2(0.1f, 1.0f); + traitorRatioSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + GUITextBlock traitorText = scrollBar.UserData as GUITextBlock; + if (traitorRatioBox.Selected) + { + scrollBar.Step = 0.01f; + scrollBar.Range = new Vector2(0.1f, 1.0f); + traitorText.Text = traitorRatioLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 1.0f) + " %"; + } + else + { + scrollBar.Step = 1f / (maxPlayers - 1); + scrollBar.Range = new Vector2(1.0f, maxPlayers); + traitorText.Text = traitorCountLabel + scrollBar.BarScrollValue; + } + return true; + }; + + GetPropertyData("TraitorUseRatio").AssignGUIComponent(traitorRatioBox); + GetPropertyData("TraitorRatio").AssignGUIComponent(traitorRatioSlider); + + traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); + traitorRatioBox.OnSelected(traitorRatioBox); + + + var karmaBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsUseKarma")); + GetPropertyData("KarmaEnabled").AssignGUIComponent(karmaBox); + + //-------------------------------------------------------------------------------- + // banlist + //-------------------------------------------------------------------------------- + + BanList.CreateBanFrame(settingsTabs[2]); + + //-------------------------------------------------------------------------------- + // whitelist + //-------------------------------------------------------------------------------- + + Whitelist.CreateWhiteListFrame(settingsTabs[3]); + + } + + private void CreateLabeledSlider(GUIComponent parent, string labelTag, out GUIScrollBar slider, out GUITextBlock label) + { + var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + + slider = new GUIScrollBar(new RectTransform(new Vector2(0.5f, 0.8f), container.RectTransform), barSize: 0.1f); + label = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.8f), container.RectTransform), + string.IsNullOrEmpty(labelTag) ? "" : TextManager.Get(labelTag), font: GUI.SmallFont); + + //slider has a reference to the label to change the text when it's used + slider.UserData = label; + } + + private bool SelectSettingsTab(GUIButton button, object obj) + { + settingsTabIndex = (int)obj; + + for (int i = 0; i < settingsTabs.Length; i++) + { + settingsTabs[i].Visible = i == settingsTabIndex; + tabButtons[i].Selected = i == settingsTabIndex; + } + + return true; + } + + public bool ToggleSettingsFrame(GUIButton button, object obj) + { + if (settingsFrame == null) + { + CreateSettingsFrame(); + } + else + { + ClientAdminWrite(NetFlags.Properties); + foreach (NetPropertyData prop in netProperties.Values) + { + prop.GUIComponent = null; + } + settingsFrame = null; + } + + return false; + } + + public void ManagePlayersFrame(GUIFrame infoFrame) + { + GUIListBox cList = new GUIListBox(new RectTransform(Vector2.One, infoFrame.RectTransform)); + /*foreach (Client c in ConnectedClients) + { + var frame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), cList.Content.RectTransform), + c.Name + " (" + c.Connection.RemoteEndPoint.Address.ToString() + ")", style: "ListBoxElement") + { + Color = (c.InGame && c.Character != null && !c.Character.IsDead) ? Color.Gold * 0.2f : Color.Transparent, + HoverColor = Color.LightGray * 0.5f, + SelectedColor = Color.Gold * 0.5f + }; + + var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.85f), frame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.05f, 0.0f) }, + isHorizontal: true); + + var kickButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform), + TextManager.Get("Kick")) + { + UserData = c.Name, + OnClicked = GameMain.NetLobbyScreen.KickPlayer + }; + + var banButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform), + TextManager.Get("Ban")) + { + UserData = c.Name, + OnClicked = GameMain.NetLobbyScreen.BanPlayer + }; + + var rangebanButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform), + TextManager.Get("BanRange")) + { + UserData = c.Name, + OnClicked = GameMain.NetLobbyScreen.BanPlayerRange + }; + }*/ //TODO: reimplement + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipCapture.cs b/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipCapture.cs index 6acc6240b..6acc1821a 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipCapture.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipCapture.cs @@ -26,7 +26,7 @@ namespace Barotrauma.Networking private set; } - public DateTime LastEnqueueAudio; + public DateTime LastEnqueueAudio; public override byte QueueID { @@ -47,10 +47,14 @@ namespace Barotrauma.Networking throw new Exception("Tried to instance more than one VoipCapture object"); } - Instance = new VoipCapture(deviceName) + var capture = new VoipCapture(deviceName) { LatestBufferID = storedBufferID ?? BUFFER_COUNT - 1 }; + if (capture.captureDevice != IntPtr.Zero) + { + Instance = capture; + } } private VoipCapture(string deviceName) : base(GameMain.Client?.ID ?? 0, true, false) @@ -60,6 +64,14 @@ namespace Barotrauma.Networking //set up capture device captureDevice = Alc.CaptureOpenDevice(deviceName, VoipConfig.FREQUENCY, ALFormat.Mono16, VoipConfig.BUFFER_SIZE * 5); + if (captureDevice == IntPtr.Zero) + { + new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("VoipCaptureDeviceNotFound")); + Instance?.Dispose(); + Instance = null; + return; + } + ALError alError = AL.GetError(); AlcError alcError = Alc.GetError(captureDevice); if (alcError != AlcError.NoError) @@ -70,7 +82,7 @@ namespace Barotrauma.Networking { throw new Exception("Failed to open capture device: " + alError.ToString() + " (AL)"); } - + Alc.CaptureStart(captureDevice); alcError = Alc.GetError(captureDevice); if (alcError != AlcError.NoError) @@ -159,7 +171,7 @@ namespace Barotrauma.Networking } else if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.PushToTalk) { - if (PlayerInput.KeyDown(InputType.Voice)) + if (PlayerInput.KeyDown(InputType.Voice) && GUI.KeyboardDispatcher.Subscriber == null) { allowEnqueue = true; } @@ -201,7 +213,7 @@ namespace Barotrauma.Networking { Instance = null; capturing = false; - captureThread.Join(); + captureThread?.Join(); captureThread = null; } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipClient.cs index f7f925ca2..b1da9bde6 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipClient.cs @@ -54,7 +54,7 @@ namespace Barotrauma.Networking else { if (VoipCapture.Instance == null) VoipCapture.Create(GameMain.Config.VoiceCaptureDevice, storedBufferID); - if (VoipCapture.Instance.EnqueuedTotalLength <= 0) return; + if (VoipCapture.Instance == null || VoipCapture.Instance.EnqueuedTotalLength <= 0) return; } if (DateTime.Now >= lastSendTime + VoipConfig.SEND_INTERVAL) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs index fd3e9fedc..f75d58b4d 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs @@ -401,6 +401,43 @@ namespace Barotrauma UpdateSourceRect(limb, newRect); } } + UpdateJointCreation(); + if (PlayerInput.KeyHit(Keys.Left)) + { + foreach (var limb in selectedLimbs) + { + var newRect = limb.ActiveSprite.SourceRect; + newRect.X--; + UpdateSourceRect(limb, newRect); + } + } + if (PlayerInput.KeyHit(Keys.Right)) + { + foreach (var limb in selectedLimbs) + { + var newRect = limb.ActiveSprite.SourceRect; + newRect.X++; + UpdateSourceRect(limb, newRect); + } + } + if (PlayerInput.KeyHit(Keys.Down)) + { + foreach (var limb in selectedLimbs) + { + var newRect = limb.ActiveSprite.SourceRect; + newRect.Y++; + UpdateSourceRect(limb, newRect); + } + } + if (PlayerInput.KeyHit(Keys.Up)) + { + foreach (var limb in selectedLimbs) + { + var newRect = limb.ActiveSprite.SourceRect; + newRect.Y--; + UpdateSourceRect(limb, newRect); + } + } } if (!isFreezed) { diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 0648c7e07..463ed3365 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -410,18 +410,6 @@ namespace Barotrauma } } - private void UpdateTutorialList() - { - var tutorialList = menuTabs[(int)Tab.Tutorials].GetChild(); - foreach (GUITextBlock tutorialText in tutorialList.Content.Children) - { - if (((Tutorial)tutorialText.UserData).Completed) - { - tutorialText.TextColor = Color.LightGreen; - } - } - } - private bool ApplySettings(GUIButton button, object userData) { GameMain.Config.SaveNewPlayerConfig(); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 4a20ae6ed..7df8e1850 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -78,9 +78,6 @@ namespace Barotrauma private GUIButton faceSelectionLeft; private GUIButton faceSelectionRight; - private GUIButton faceSelectionLeft; - private GUIButton faceSelectionRight; - private float autoRestartTimer; //persistent characterinfo provided by the server @@ -125,12 +122,6 @@ namespace Barotrauma private set; } - public GUIButton ShowLogButton - { - get; - private set; - } - public GUIListBox SubList { get { return subList; } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index 68f1ea46b..4469d6211 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -64,10 +64,6 @@ namespace Barotrauma private readonly string containerDeleteTag = "containerdelete"; - private DateTime editorSelectedTime; - - private readonly string containerDeleteTag = "containerdelete"; - public override Camera Cam { get { return cam; } diff --git a/Barotrauma/BarotraumaClient/plugins/codec/libavcodec_plugin.dll b/Barotrauma/BarotraumaClient/plugins/codec/libavcodec_plugin.dll new file mode 100644 index 000000000..ecad5db4d Binary files /dev/null and b/Barotrauma/BarotraumaClient/plugins/codec/libavcodec_plugin.dll differ diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index 5102753ed..3dc185ec4 100644 --- a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.9.4")] -[assembly: AssemblyFileVersion("0.8.9.4")] +[assembly: AssemblyVersion("0.8.9.5")] +[assembly: AssemblyFileVersion("0.8.9.5")] diff --git a/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs index 6340c2d4b..4b4b86e42 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs @@ -116,22 +116,12 @@ namespace Barotrauma.Networking ChatMessageType messageType = CanUseRadio(orderMsg.Sender) ? ChatMessageType.Radio : ChatMessageType.Default; if (orderMsg.Order.TargetAllCharacters) { -#if CLIENT - //add the order to the crewmanager only if the host is not controlling a character - //OR the character is close enough to hear it - if (Character.Controlled == null || - !string.IsNullOrEmpty(ApplyDistanceEffect(orderMsg.Text, messageType, orderMsg.Sender, Character.Controlled))) - { - GameMain.GameSession?.CrewManager?.AddOrder( - new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.GetComponent()), - orderMsg.Order.Prefab.FadeOutTime); - } -#endif + //do nothing } else if (orderTargetCharacter != null) { orderTargetCharacter.SetOrder( - new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.GetComponent()), + new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == orderMsg.Order.ItemComponentType)), orderMsg.OrderOption, orderMsg.Sender); } diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs index bdf71c2cd..72facfe3c 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs @@ -483,7 +483,7 @@ namespace Barotrauma.Networking (OwnerConnection == null || c.Connection != OwnerConnection)); foreach (Client c in kickAFK) { - KickClient(c, TextManager.Get("DisconnectMessage.AFK")); + KickClient(c, "DisconnectMessage.AFK"); } NetIncomingMessage inc = null; @@ -956,11 +956,11 @@ namespace Barotrauma.Networking Log("Client \"" + sender.Name + "\" banned \"" + bannedClient.Name + "\".", ServerLog.MessageType.ServerMessage); if (durationSeconds > 0) { - BanClient(bannedClient, string.IsNullOrEmpty(banReason) ? "Banned by " + sender.Name : banReason, range, TimeSpan.FromSeconds(durationSeconds)); + BanClient(bannedClient, string.IsNullOrEmpty(banReason) ? $"ServerMessage.BannedBy_[initiator]={sender.Name}" : banReason, range, TimeSpan.FromSeconds(durationSeconds)); } else { - BanClient(bannedClient, string.IsNullOrEmpty(banReason) ? "Banned by " + sender.Name : banReason, range); + BanClient(bannedClient, string.IsNullOrEmpty(banReason) ? $"ServerMessage.BannedBy_[initiator]={sender.Name}" : banReason, range); } } break; @@ -1981,14 +1981,14 @@ namespace Barotrauma.Networking client.HasSpawned = false; client.InGame = false; - if (string.IsNullOrWhiteSpace(msg)) + if (string.IsNullOrWhiteSpace(msg)) msg = $"ServerMessage.ClientLeftServer_[client]={client.Name}"; + if (string.IsNullOrWhiteSpace(targetmsg)) targetmsg = "ServerMessage.YouLeftServer"; + if (!string.IsNullOrWhiteSpace(reason)) { - msg = $"ServerMessage.ClientLeftServer_[client]={client.Name}"; + msg += $"; ;ServerMessage.Reason;: ;{reason}"; + targetmsg += $";\n;ServerMessage.Reason;: ;{reason}"; } - if (string.IsNullOrWhiteSpace(targetmsg)) targetmsg = "ServerMessage.YouLeftServer"; - if (!string.IsNullOrWhiteSpace(reason)) msg += $";ServerMessage.Reason;{reason}"; - Log(msg, ServerLog.MessageType.ServerMessage); if (client.SteamID > 0) { SteamManager.StopAuthSession(client.SteamID); } @@ -2236,7 +2236,7 @@ namespace Barotrauma.Networking if (type.Value != ChatMessageType.MessageBox) { - string myReceivedMessage = message; + string myReceivedMessage = TextManager.GetServerMessage(message); if (!string.IsNullOrWhiteSpace(myReceivedMessage) && (targetClient == null || senderClient == null)) diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs new file mode 100644 index 000000000..e6c23011f --- /dev/null +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs @@ -0,0 +1,511 @@ +using Lidgren.Network; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Barotrauma.Networking +{ + class UnauthenticatedClient + { + public readonly NetConnection Connection; + public readonly ulong SteamID; + public Facepunch.Steamworks.ServerAuth.Status? SteamAuthStatus = null; + public readonly int Nonce; + + public int FailedAttempts; + + public float AuthTimer; + + public UnauthenticatedClient(NetConnection connection, int nonce, ulong steamID = 0) + { + Connection = connection; + SteamID = steamID; + Nonce = nonce; + AuthTimer = 10.0f; + FailedAttempts = 0; + } + } + + partial class GameServer : NetworkMember + { + private Int32 ownerKey = 0; + + List unauthenticatedClients = new List(); + + private void ReadClientSteamAuthRequest(NetIncomingMessage inc, NetConnection senderConnection, out ulong clientSteamID) + { + clientSteamID = 0; + if (!Steam.SteamManager.USE_STEAM) + { + DebugConsole.Log("Received a Steam auth request from " + senderConnection.RemoteEndPoint + ". Steam authentication not required, handling auth normally."); + //not using steam, handle auth normally + HandleClientAuthRequest(senderConnection, 0); + return; + } + + if (senderConnection == OwnerConnection) + { + //the client is the owner of the server, no need for authentication + //(it would fail with a "duplicate request" error anyway) + HandleClientAuthRequest(senderConnection, 0); + return; + } + + clientSteamID = inc.ReadUInt64(); + int authTicketLength = inc.ReadInt32(); + inc.ReadBytes(authTicketLength, out byte[] authTicketData); + + DebugConsole.Log("Received a Steam auth request"); + DebugConsole.Log(" Steam ID: "+ clientSteamID); + DebugConsole.Log(" Auth ticket length: " + authTicketLength); + DebugConsole.Log(" Auth ticket data: " + + ((authTicketData == null) ? "null" : ToolBox.LimitString(string.Concat(authTicketData.Select(b => b.ToString("X2"))), 16))); + + if (senderConnection != OwnerConnection && + serverSettings.BanList.IsBanned(senderConnection.RemoteEndPoint.Address, clientSteamID)) + { + return; + } + ulong steamID = clientSteamID; + if (unauthenticatedClients.Any(uc => uc.Connection == inc.SenderConnection)) + { + var steamAuthedClient = unauthenticatedClients.Find(uc => + uc.Connection == inc.SenderConnection && + uc.SteamID == steamID && + uc.SteamAuthStatus == Facepunch.Steamworks.ServerAuth.Status.OK); + if (steamAuthedClient != null) + { + DebugConsole.Log("Client already authenticated, sending AUTH_RESPONSE again..."); + HandleClientAuthRequest(inc.SenderConnection, steamID); + } + DebugConsole.Log("Steam authentication already pending..."); + return; + } + + if (authTicketData == null) + { + DebugConsole.Log("Invalid request"); + return; + } + + unauthenticatedClients.RemoveAll(uc => uc.Connection == senderConnection); + int nonce = CryptoRandom.Instance.Next(); + var unauthClient = new UnauthenticatedClient(senderConnection, nonce, clientSteamID) + { + AuthTimer = 20 + }; + unauthenticatedClients.Add(unauthClient); + + if (!Steam.SteamManager.StartAuthSession(authTicketData, clientSteamID)) + { + unauthenticatedClients.Remove(unauthClient); + if (GameMain.Config.RequireSteamAuthentication) + { + unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString()); + Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed.", ServerLog.MessageType.ServerMessage); + } + else + { + DebugConsole.Log("Steam authentication failed, skipping to basic auth..."); + HandleClientAuthRequest(senderConnection); + return; + } + } + + return; + } + + public void OnAuthChange(ulong steamID, ulong ownerID, Facepunch.Steamworks.ServerAuth.Status status) + { + DebugConsole.Log("************ OnAuthChange"); + DebugConsole.Log(" Steam ID: " + steamID); + DebugConsole.Log(" Owner ID: " + ownerID); + DebugConsole.Log(" Status: " + status); + + UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.SteamID == ownerID); + if (unauthClient != null) + { + unauthClient.SteamAuthStatus = status; + switch (status) + { + case Facepunch.Steamworks.ServerAuth.Status.OK: + ////steam authentication done, check password next + Log("Successfully authenticated client via Steam (Steam ID: " + steamID + ").", ServerLog.MessageType.ServerMessage); + HandleClientAuthRequest(unauthClient.Connection, unauthClient.SteamID); + break; + default: + unauthenticatedClients.Remove(unauthClient); + if (GameMain.Config.RequireSteamAuthentication) + { + Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed, (" + status + ").", ServerLog.MessageType.ServerMessage); + unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString() + "; (" + status.ToString() + ")"); + } + else + { + DebugConsole.Log("Steam authentication failed (" + status.ToString() + "), skipping to basic auth..."); + HandleClientAuthRequest(unauthClient.Connection); + return; + } + break; + } + return; + } + else + { + DebugConsole.Log(" No unauthenticated clients found with the Steam ID " + steamID); + } + + //kick connected client if status becomes invalid (e.g. VAC banned, not connected to steam) + if (status != Facepunch.Steamworks.ServerAuth.Status.OK && GameMain.Config.RequireSteamAuthentication) + { + var connectedClient = connectedClients.Find(c => c.SteamID == ownerID); + if (connectedClient != null) + { + Log("Disconnecting client " + connectedClient.Name + " (Steam ID: " + steamID + "). Steam authentication no longer valid (" + status + ").", ServerLog.MessageType.ServerMessage); + KickClient(connectedClient, $"DisconnectMessage.SteamAuthNoLongerValid_[status]={status.ToString()}"); + } + } + } + + private bool IsServerOwner(NetIncomingMessage inc, NetConnection senderConnection) + { + string address = senderConnection.RemoteEndPoint.Address.MapToIPv4().ToString(); + int incKey = inc.ReadInt32(); + + if (ownerKey == 0) + { + return false; //ownership key has been destroyed or has never existed + } + if (address.ToString() != "127.0.0.1") + { + return false; //not localhost + } + + if (incKey != ownerKey) + { + return false; //incorrect owner key, how did this even happen + } + return true; + } + + private void HandleOwnership(NetIncomingMessage inc, NetConnection senderConnection) + { + DebugConsole.Log("HandleOwnership (" + senderConnection.RemoteEndPoint.Address + ")"); + if (IsServerOwner(inc, senderConnection)) + { + ownerKey = 0; //destroy owner key so nobody else can take ownership of the server + OwnerConnection = senderConnection; + DebugConsole.NewMessage("Successfully set up server owner", Color.Lime); + } + } + + private void HandleClientAuthRequest(NetConnection connection, ulong steamID = 0) + { + DebugConsole.Log("HandleClientAuthRequest (steamID " + steamID + ")"); + + if (GameMain.Config.RequireSteamAuthentication && connection != OwnerConnection && steamID == 0) + { + DebugConsole.Log("Disconnecting " + connection.RemoteEndPoint + ", Steam authentication required."); + connection.Disconnect(DisconnectReason.SteamAuthenticationRequired.ToString()); + return; + } + + //client wants to know if server requires password + if (ConnectedClients.Find(c => c.Connection == connection) != null) + { + //this client has already been authenticated + return; + } + + UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == connection); + if (unauthClient == null) + { + DebugConsole.Log("Unauthed client, generating a nonce..."); + //new client, generate nonce and add to unauth queue + if (ConnectedClients.Count >= serverSettings.MaxPlayers) + { + //server is full, can't allow new connection + connection.Disconnect(DisconnectReason.ServerFull.ToString()); + if (steamID > 0) { Steam.SteamManager.StopAuthSession(steamID); } + return; + } + + int nonce = CryptoRandom.Instance.Next(); + unauthClient = new UnauthenticatedClient(connection, nonce, steamID); + unauthenticatedClients.Add(unauthClient); + } + unauthClient.AuthTimer = 10.0f; + //if the client is already in the queue, getting another unauth request means that our response was lost; resend + NetOutgoingMessage nonceMsg = server.CreateMessage(); + nonceMsg.Write((byte)ServerPacketHeader.AUTH_RESPONSE); + if (serverSettings.HasPassword && connection != OwnerConnection) + { + nonceMsg.Write(true); //true = password + nonceMsg.Write((Int32)unauthClient.Nonce); //here's nonce, encrypt with this + } + else + { + nonceMsg.Write(false); //false = no password + } + CompressOutgoingMessage(nonceMsg); + DebugConsole.Log("Sending auth response..."); + server.SendMessage(nonceMsg, connection, NetDeliveryMethod.Unreliable); + } + + private void ClientInitRequest(NetIncomingMessage inc) + { + DebugConsole.Log("Received client init request"); + if (ConnectedClients.Find(c => c.Connection == inc.SenderConnection) != null) + { + //this client was already authenticated + //another init request means they didn't get any update packets yet + DebugConsole.Log("Client already connected, ignoring..."); + return; + } + + UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == inc.SenderConnection); + if (unauthClient == null) + { + //client did not ask for nonce first, can't authorize + inc.SenderConnection.Disconnect(DisconnectReason.AuthenticationRequired.ToString()); + if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); } + return; + } + + if (serverSettings.HasPassword && inc.SenderConnection != OwnerConnection) + { + //decrypt message and compare password + string clPw = inc.ReadString(); + if (!serverSettings.IsPasswordCorrect(clPw, unauthClient.Nonce)) + { + unauthClient.FailedAttempts++; + if (unauthClient.FailedAttempts > 3) + { + //disconnect and ban after too many failed attempts + serverSettings.BanList.BanPlayer("Unnamed", unauthClient.Connection.RemoteEndPoint.Address, "DisconnectMessage.TooManyFailedLogins", duration: null); + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.TooManyFailedLogins, ""); + + Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", ServerLog.MessageType.Error); + DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", Color.Red); + return; + } + else + { + //not disconnecting the player here, because they'll still use the same connection and nonce if they try logging in again + NetOutgoingMessage reject = server.CreateMessage(); + reject.Write((byte)ServerPacketHeader.AUTH_FAILURE); + reject.Write("Wrong password! You have " + Convert.ToString(4 - unauthClient.FailedAttempts) + " more attempts before you're banned from the server."); + Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", ServerLog.MessageType.Error); + DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", Color.Red); + CompressOutgoingMessage(reject); + server.SendMessage(reject, unauthClient.Connection, NetDeliveryMethod.Unreliable); + unauthClient.AuthTimer = 10.0f; + return; + } + } + } + string clVersion = inc.ReadString(); + + UInt16 contentPackageCount = inc.ReadUInt16(); + List contentPackageNames = new List(); + List contentPackageHashes = new List(); + for (int i = 0; i < contentPackageCount; i++) + { + string packageName = inc.ReadString(); + string packageHash = inc.ReadString(); + contentPackageNames.Add(packageName); + contentPackageHashes.Add(packageHash); + if (contentPackageCount == 0) + { + DebugConsole.Log("Client is using content package " + + (packageName ?? "null") + " (" + (packageHash ?? "null" + ")")); + } + } + + if (contentPackageCount == 0) + { + DebugConsole.Log("Client did not list any content packages."); + } + + string clName = Client.SanitizeName(inc.ReadString()); + if (string.IsNullOrWhiteSpace(clName)) + { + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NoName, ""); + + Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", ServerLog.MessageType.Error); + DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", Color.Red); + return; + } + + if (clVersion != GameMain.Version.ToString()) + { + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidVersion, + $"DisconnectMessage.InvalidVersion_[version]={GameMain.Version.ToString()}_[clientversion]={clVersion}"); + + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", ServerLog.MessageType.Error); + DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", Color.Red); + return; + } + + //check if the client is missing any of the content packages the server requires + List missingPackages = new List(); + foreach (ContentPackage contentPackage in GameMain.SelectedPackages) + { + if (!contentPackage.HasMultiplayerIncompatibleContent) continue; + bool packageFound = false; + for (int i = 0; i < contentPackageCount; i++) + { + if (contentPackageNames[i] == contentPackage.Name && contentPackageHashes[i] == contentPackage.MD5hash.Hash) + { + packageFound = true; + break; + } + } + if (!packageFound) missingPackages.Add(contentPackage); + } + + if (missingPackages.Count == 1) + { + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackage_[missingcontentpackage]={GetPackageStr(missingPackages[0])}"); + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error); + return; + } + else if (missingPackages.Count > 1) + { + List packageStrs = new List(); + missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp))); + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackages_[missingcontentpackages]={string.Join(", ", packageStrs)}"); + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error); + return; + } + + string GetPackageStr(ContentPackage contentPackage) + { + return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")"; + } + + //check if the client is using any contentpackages that are not compatible with the server + List> incompatiblePackages = new List>(); + for (int i = 0; i < contentPackageNames.Count; i++) + { + if (!GameMain.Config.SelectedContentPackages.Any(cp => cp.Name == contentPackageNames[i] && cp.MD5hash.Hash == contentPackageHashes[i])) + { + incompatiblePackages.Add(new Pair(contentPackageNames[i], contentPackageHashes[i])); + } + } + + if (incompatiblePackages.Count == 1) + { + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage, + $"DisconnectMessage.IncompatibleContentPackage_[incompatiblecontentpackage]={GetPackageStr2(incompatiblePackages[0])}"); + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content package " + GetPackageStr2(incompatiblePackages[0]) + ")", ServerLog.MessageType.Error); + return; + } + else if (incompatiblePackages.Count > 1) + { + List packageStrs = new List(); + incompatiblePackages.ForEach(cp => packageStrs.Add(GetPackageStr2(cp))); + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage, + $"DisconnectMessage.IncompatibleContentPackages_[incompatiblecontentpackages]={string.Join(", ", packageStrs)}"); + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error); + return; + } + + string GetPackageStr2(Pair nameAndHash) + { + return "\"" + nameAndHash.First + "\" (hash " + Md5Hash.GetShortHash(nameAndHash.Second) + ")"; + } + + if (!serverSettings.Whitelist.IsWhiteListed(clName, inc.SenderConnection.RemoteEndPoint.Address)) + { + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NotOnWhitelist, ""); + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", ServerLog.MessageType.Error); + DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", Color.Red); + return; + } + if (!Client.IsValidName(clName, this)) + { + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidName, ""); + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", ServerLog.MessageType.Error); + DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", Color.Red); + return; + } + if (inc.SenderConnection != OwnerConnection && Homoglyphs.Compare(clName.ToLower(),Name.ToLower())) + { + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NameTaken, ""); + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", ServerLog.MessageType.Error); + DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", Color.Red); + return; + } + Client nameTaken = ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), clName.ToLower())); + if (nameTaken != null) + { + if (nameTaken.Connection.RemoteEndPoint.Address.ToString() == inc.SenderEndPoint.Address.ToString()) + { + //both name and IP address match, replace this player's connection + nameTaken.Connection.Disconnect(DisconnectReason.SessionTaken.ToString()); + nameTaken.Connection = unauthClient.Connection; + nameTaken.InitClientSync(); //reinitialize sync ids because this is a new connection + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + return; + } + else + { + //can't authorize this client + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NameTaken, ""); + Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", ServerLog.MessageType.Error); + DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", Color.Red); + return; + } + } + + //new client + Client newClient = new Client(clName, GetNewClientID()); + newClient.InitClientSync(); + newClient.Connection = unauthClient.Connection; + newClient.SteamID = unauthClient.SteamID; + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + ConnectedClients.Add(newClient); + LastClientListUpdateID++; + + if (newClient.Connection == OwnerConnection) + { + newClient.GivePermission(ClientPermissions.All); + newClient.PermittedConsoleCommands.AddRange(DebugConsole.Commands); + + GameMain.Server.UpdateClientPermissions(newClient); + GameMain.Server.SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient); + } + + GameMain.Server.SendChatMessage($"ServerMessage.JoinedServer_[client]={clName}", ChatMessageType.Server, null); + + var savedPermissions = serverSettings.ClientPermissions.Find(cp => + cp.SteamID > 0 ? + cp.SteamID == newClient.SteamID : + newClient.IPMatches(cp.IP)); + + if (savedPermissions != null) + { + newClient.SetPermissions(savedPermissions.Permissions, savedPermissions.PermittedCommands); + } + else + { + newClient.SetPermissions(ClientPermissions.None, new List()); + } + } + + private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, DisconnectReason reason, string message) + { + inc.SenderConnection.Disconnect(reason.ToString() + "; " + message); + if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); } + if (unauthClient != null) + { + unauthenticatedClients.Remove(unauthClient); + } + } + } +} diff --git a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs new file mode 100644 index 000000000..4b6418fb6 --- /dev/null +++ b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs @@ -0,0 +1,482 @@ +using Lidgren.Network; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; + +namespace Barotrauma.Networking +{ + partial class ServerSettings + { + public const string SettingsFile = "serversettings.xml"; + public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml"; + + partial void InitProjSpecific() + { + LoadSettings(); + LoadClientPermissions(); + } + + private void WriteNetProperties(NetBuffer outMsg) + { + outMsg.Write((UInt16)netProperties.Keys.Count); + foreach (UInt32 key in netProperties.Keys) + { + outMsg.Write(key); + netProperties[key].Write(outMsg); + } + } + + public void ServerAdminWrite(NetBuffer outMsg, Client c) + { + //outMsg.Write(isPublic); + //outMsg.Write(EnableUPnP); + //outMsg.WritePadBits(); + //outMsg.Write((UInt16)QueryPort); + + WriteNetProperties(outMsg); + WriteMonsterEnabled(outMsg); + BanList.ServerAdminWrite(outMsg, c); + Whitelist.ServerAdminWrite(outMsg, c); + } + + public void ServerWrite(NetBuffer outMsg,Client c) + { + outMsg.Write(ServerName); + outMsg.Write(ServerMessageText); + outMsg.WriteRangedInteger(1, 60, TickRate); + + WriteExtraCargo(outMsg); + + Voting.ServerWrite(outMsg); + + if (c.HasPermission(Networking.ClientPermissions.ManageSettings)) + { + outMsg.Write(true); + outMsg.WritePadBits(); + + ServerAdminWrite(outMsg, c); + } + else + { + outMsg.Write(false); + outMsg.WritePadBits(); + } + } + + public void ServerRead(NetIncomingMessage incMsg,Client c) + { + if (!c.HasPermission(Networking.ClientPermissions.ManageSettings)) return; + + NetFlags flags = (NetFlags)incMsg.ReadByte(); + + bool changed = false; + + if (flags.HasFlag(NetFlags.Name)) + { + string serverName = incMsg.ReadString(); + if (ServerName != serverName) changed = true; + ServerName = serverName; + } + + if (flags.HasFlag(NetFlags.Message)) + { + string serverMessageText = incMsg.ReadString(); + if (ServerMessageText != serverMessageText) changed = true; + ServerMessageText = serverMessageText; + } + + if (flags.HasFlag(NetFlags.Properties)) + { + changed |= ReadExtraCargo(incMsg); + + UInt32 count = incMsg.ReadUInt32(); + + for (int i = 0; i < count; i++) + { + UInt32 key = incMsg.ReadUInt32(); + + if (netProperties.ContainsKey(key)) + { + netProperties[key].Read(incMsg); + GameServer.Log(c.Name + " changed " + netProperties[key].Name + " to " + netProperties[key].Value.ToString(), ServerLog.MessageType.ServerMessage); + changed = true; + } + else + { + UInt32 size = incMsg.ReadVariableUInt32(); + incMsg.Position += 8 * size; + } + } + + bool changedMonsterSettings = incMsg.ReadBoolean(); incMsg.ReadPadBits(); + changed |= changedMonsterSettings; + if (changedMonsterSettings) ReadMonsterEnabled(incMsg); + changed |= BanList.ServerAdminRead(incMsg, c); + changed |= Whitelist.ServerAdminRead(incMsg, c); + } + + if (flags.HasFlag(NetFlags.Misc)) + { + int missionType = GameMain.NetLobbyScreen.MissionTypeIndex + incMsg.ReadByte() - 1; + while (missionType < 0) missionType += Enum.GetValues(typeof(MissionType)).Length; + while (missionType >= Enum.GetValues(typeof(MissionType)).Length) missionType -= Enum.GetValues(typeof(MissionType)).Length; + GameMain.NetLobbyScreen.MissionTypeIndex = missionType; + + int traitorSetting = (int)TraitorsEnabled + incMsg.ReadByte() - 1; + if (traitorSetting < 0) traitorSetting = 2; + if (traitorSetting > 2) traitorSetting = 0; + TraitorsEnabled = (YesNoMaybe)traitorSetting; + + int botCount = BotCount + incMsg.ReadByte() - 1; + if (botCount < 0) botCount = MaxBotCount; + if (botCount > MaxBotCount) botCount = 0; + BotCount = botCount; + + int botSpawnMode = (int)BotSpawnMode + incMsg.ReadByte() - 1; + if (botSpawnMode < 0) botSpawnMode = 1; + if (botSpawnMode > 1) botSpawnMode = 0; + BotSpawnMode = (BotSpawnMode)botSpawnMode; + + float levelDifficulty = incMsg.ReadFloat(); + if (levelDifficulty >= 0.0f) SelectedLevelDifficulty = levelDifficulty; + + bool changedAutoRestart = incMsg.ReadBoolean(); + bool autoRestart = incMsg.ReadBoolean(); + if (changedAutoRestart) + { + AutoRestart = autoRestart; + } + + changed |= true; + } + + if (flags.HasFlag(NetFlags.LevelSeed)) + { + GameMain.NetLobbyScreen.LevelSeed = incMsg.ReadString(); + changed |= true; + } + + if (changed) GameMain.NetLobbyScreen.LastUpdateID++; + } + + public void SaveSettings() + { + XDocument doc = new XDocument(new XElement("serversettings")); + + SerializableProperty.SerializeProperties(this, doc.Root, true); + + doc.Root.SetAttributeValue("name", ServerName); + doc.Root.SetAttributeValue("public", isPublic); + doc.Root.SetAttributeValue("port", GameMain.Server.NetPeerConfiguration.Port); + if (Steam.SteamManager.USE_STEAM) doc.Root.SetAttributeValue("queryport", QueryPort); + doc.Root.SetAttributeValue("maxplayers", maxPlayers); + doc.Root.SetAttributeValue("enableupnp", GameMain.Server.NetPeerConfiguration.EnableUPnP); + + doc.Root.SetAttributeValue("autorestart", autoRestart); + + doc.Root.SetAttributeValue("SubSelection", SubSelectionMode.ToString()); + doc.Root.SetAttributeValue("ModeSelection", ModeSelectionMode.ToString()); + doc.Root.SetAttributeValue("LevelDifficulty", ((int)selectedLevelDifficulty).ToString()); + doc.Root.SetAttributeValue("TraitorsEnabled", TraitorsEnabled.ToString()); + + /*doc.Root.SetAttributeValue("BotCount", BotCount); + doc.Root.SetAttributeValue("MaxBotCount", MaxBotCount);*/ + doc.Root.SetAttributeValue("BotSpawnMode", BotSpawnMode.ToString()); + + doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes)); + + doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => c.First + "-" + c.Second))); + + doc.Root.SetAttributeValue("ServerMessage", ServerMessageText); + + XmlWriterSettings settings = new XmlWriterSettings + { + Indent = true, + NewLineOnAttributes = true + }; + + using (var writer = XmlWriter.Create(SettingsFile, settings)) + { + doc.Save(writer); + } + } + + private void LoadSettings() + { + XDocument doc = null; + if (File.Exists(SettingsFile)) + { + doc = XMLExtensions.TryLoadXml(SettingsFile); + } + + if (doc == null || doc.Root == null) + { + doc = new XDocument(new XElement("serversettings")); + } + + SerializableProperties = SerializableProperty.DeserializeProperties(this, doc.Root); + + AutoRestart = doc.Root.GetAttributeBool("autorestart", false); + + Voting.AllowSubVoting = SubSelectionMode == SelectionMode.Vote; + Voting.AllowModeVoting = ModeSelectionMode == SelectionMode.Vote; + + selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f); + GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty); + + var traitorsEnabled = TraitorsEnabled; + Enum.TryParse(doc.Root.GetAttributeString("TraitorsEnabled", "No"), out traitorsEnabled); + TraitorsEnabled = traitorsEnabled; + GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled); + + var botSpawnMode = BotSpawnMode.Fill; + Enum.TryParse(doc.Root.GetAttributeString("BotSpawnMode", "Fill"), out botSpawnMode); + BotSpawnMode = botSpawnMode; + + //"65-90", "97-122", "48-59" = upper and lower case english alphabet and numbers + string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", new string[] { "65-90", "97-122", "48-59" }); + foreach (string allowedClientNameCharRange in allowedClientNameCharsStr) + { + string[] splitRange = allowedClientNameCharRange.Split('-'); + if (splitRange.Length == 0 || splitRange.Length > 2) + { + DebugConsole.ThrowError("Error in server settings - " + allowedClientNameCharRange + " is not a valid range for characters allowed in client names."); + continue; + } + + int min = -1; + if (!int.TryParse(splitRange[0], out min)) + { + DebugConsole.ThrowError("Error in server settings - " + allowedClientNameCharRange + " is not a valid range for characters allowed in client names."); + continue; + } + int max = min; + if (splitRange.Length == 2) + { + if (!int.TryParse(splitRange[1], out max)) + { + DebugConsole.ThrowError("Error in server settings - " + allowedClientNameCharRange + " is not a valid range for characters allowed in client names."); + continue; + } + } + + if (min > -1 && max > -1) AllowedClientNameChars.Add(new Pair(min, max)); + } + + AllowedRandomMissionTypes = new List(); + string[] allowedMissionTypeNames = doc.Root.GetAttributeStringArray( + "AllowedRandomMissionTypes", Enum.GetValues(typeof(MissionType)).Cast().Select(m => m.ToString()).ToArray()); + foreach (string missionTypeName in allowedMissionTypeNames) + { + if (Enum.TryParse(missionTypeName, out MissionType missionType)) + { + if (missionType == Barotrauma.MissionType.None) continue; + AllowedRandomMissionTypes.Add(missionType); + } + } + + ServerName = doc.Root.GetAttributeString("name", ""); + ServerMessageText = doc.Root.GetAttributeString("ServerMessage", ""); + + GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier; + GameMain.NetLobbyScreen.MissionTypeName = MissionType; + + GameMain.NetLobbyScreen.SetBotSpawnMode(BotSpawnMode); + GameMain.NetLobbyScreen.SetBotCount(BotCount); + + List monsterNames = GameMain.Instance.GetFilesOfType(ContentType.Character).ToList(); + for (int i = 0; i < monsterNames.Count; i++) + { + monsterNames[i] = Path.GetFileName(Path.GetDirectoryName(monsterNames[i])); + } + MonsterEnabled = new Dictionary(); + foreach (string s in monsterNames) + { + if (!MonsterEnabled.ContainsKey(s)) MonsterEnabled.Add(s, true); + } + + AutoBanTime = doc.Root.GetAttributeFloat("autobantime", 60); + MaxAutoBanTime = doc.Root.GetAttributeFloat("maxautobantime", 360); + } + + public void LoadClientPermissions() + { + ClientPermissions.Clear(); + + if (!File.Exists(ClientPermissionsFile)) + { + if (File.Exists("Data/clientpermissions.txt")) + { + LoadClientPermissionsOld("Data/clientpermissions.txt"); + } + return; + } + + XDocument doc = XMLExtensions.TryLoadXml(ClientPermissionsFile); + foreach (XElement clientElement in doc.Root.Elements()) + { + string clientName = clientElement.GetAttributeString("name", ""); + string clientIP = clientElement.GetAttributeString("ip", ""); + string steamIdStr = clientElement.GetAttributeString("steamid", ""); + + if (string.IsNullOrWhiteSpace(clientName)) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have a name and an IP address."); + continue; + } + if (string.IsNullOrWhiteSpace(clientIP) && string.IsNullOrWhiteSpace(steamIdStr)) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have an IP address or a Steam ID."); + continue; + } + + string permissionsStr = clientElement.GetAttributeString("permissions", ""); + ClientPermissions permissions = Networking.ClientPermissions.None; + if (permissionsStr.ToLowerInvariant() == "all") + { + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + permissions |= permission; + } + } + else if (!Enum.TryParse(permissionsStr, out permissions)) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + permissionsStr + "\" is not a valid client permission."); + continue; + } + + List permittedCommands = new List(); + if (permissions.HasFlag(Networking.ClientPermissions.ConsoleCommands)) + { + foreach (XElement commandElement in clientElement.Elements()) + { + if (commandElement.Name.ToString().ToLowerInvariant() != "command") continue; + + string commandName = commandElement.GetAttributeString("name", ""); + DebugConsole.Command command = DebugConsole.FindCommand(commandName); + if (command == null) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + commandName + "\" is not a valid console command."); + continue; + } + + permittedCommands.Add(command); + } + } + + if (!string.IsNullOrEmpty(steamIdStr)) + { + if (ulong.TryParse(steamIdStr, out ulong steamID)) + { + ClientPermissions.Add(new SavedClientPermission(clientName, steamID, permissions, permittedCommands)); + } + else + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + steamIdStr + "\" is not a valid Steam ID."); + continue; + } + } + else + { + ClientPermissions.Add(new SavedClientPermission(clientName, clientIP, permissions, permittedCommands)); + } + } + } + + /// + /// Method for loading old .txt client permission files to provide backwards compatibility + /// + private void LoadClientPermissionsOld(string file) + { + if (!File.Exists(file)) return; + + string[] lines; + try + { + lines = File.ReadAllLines(file); + } + catch (Exception e) + { + DebugConsole.ThrowError("Failed to open client permission file " + ClientPermissionsFile, e); + return; + } + + ClientPermissions.Clear(); + + foreach (string line in lines) + { + string[] separatedLine = line.Split('|'); + if (separatedLine.Length < 3) continue; + + string name = string.Join("|", separatedLine.Take(separatedLine.Length - 2)); + string ip = separatedLine[separatedLine.Length - 2]; + + ClientPermissions permissions = Networking.ClientPermissions.None; + if (Enum.TryParse(separatedLine.Last(), out permissions)) + { + ClientPermissions.Add(new SavedClientPermission(name, ip, permissions, new List())); + } + } + } + + public void SaveClientPermissions() + { + //delete old client permission file + if (File.Exists("Data/clientpermissions.txt")) + { + File.Delete("Data/clientpermissions.txt"); + } + + GameServer.Log("Saving client permissions", ServerLog.MessageType.ServerMessage); + + XDocument doc = new XDocument(new XElement("ClientPermissions")); + + foreach (SavedClientPermission clientPermission in ClientPermissions) + { + XElement clientElement = new XElement("Client", + new XAttribute("name", clientPermission.Name), + new XAttribute("permissions", clientPermission.Permissions.ToString())); + + if (clientPermission.SteamID > 0) + { + clientElement.Add(new XAttribute("steamid", clientPermission.SteamID)); + } + else + { + clientElement.Add(new XAttribute("ip", clientPermission.IP)); + } + + if (clientPermission.Permissions.HasFlag(Barotrauma.Networking.ClientPermissions.ConsoleCommands)) + { + foreach (DebugConsole.Command command in clientPermission.PermittedCommands) + { + clientElement.Add(new XElement("command", new XAttribute("name", command.names[0]))); + } + } + + doc.Root.Add(clientElement); + } + + try + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.NewLineOnAttributes = true; + + using (var writer = XmlWriter.Create(ClientPermissionsFile, settings)) + { + doc.Save(writer); + } + } + catch (Exception e) + { + DebugConsole.ThrowError("Saving client permissions to " + ClientPermissionsFile + " failed", e); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems index 2b7a4f89a..9139fe633 100644 --- a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems +++ b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems @@ -2908,7 +2908,7 @@ PreserveNewest - + PreserveNewest @@ -3322,4 +3322,4 @@ - \ No newline at end of file + diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index 5f8d1403b..ccb3eb2da 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -188,45 +188,6 @@ namespace Barotrauma newOrder = new Order(orderPrefab, Character.CurrentHull, null); } - if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - } - - foreach (Character c in Character.CharacterList) - { - if (c.CurrentHull == Character.CurrentHull && !c.IsDead && - (c.AIController is EnemyAIController || c.TeamID != Character.TeamID)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - } - } - } - - if (Character.CurrentHull != null && (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - } - - if (newOrder != null) - { - if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime)) - { - Character.Speak( - newOrder.GetChatMessage("", Character.CurrentHull?.RoomName), ChatMessageType.Order); - - if (GameMain.Server != null) - { - OrderChatMessage msg = new OrderChatMessage(newOrder, "", Character.CurrentHull, null, Character); - GameMain.Server.SendOrderChatMessage(msg); - } - } - } - } - partial void ReportProblems(); private void UpdateSpeaking() @@ -299,21 +260,5 @@ namespace Barotrauma float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f; shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall) != null; } - - private void CheckCrouching(float deltaTime) - { - crouchRaycastTimer -= deltaTime; - if (crouchRaycastTimer > 0.0f) return; - - crouchRaycastTimer = CrouchRaycastInterval; - - //start the raycast in front of the character in the direction it's heading to - Vector2 startPos = Character.SimPosition; - startPos.X += MathHelper.Clamp(Character.AnimController.TargetMovement.X, -1.0f, 1.0f); - - //do a raycast upwards to find any walls - float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f; - shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall) != null; - } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs index 3837e0ebd..aa850de0a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs @@ -697,6 +697,22 @@ namespace Barotrauma limb?.body.SmoothRotate(angle, torque, wrapAngle: false); } + private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque) + { + //make sure the angle "has the same number of revolutions" as the reference limb + //(e.g. we don't want to rotate the legs to 0 if the torso is at 360, because that'd blow up the hip joints) + while (referenceLimb.Rotation - angle > MathHelper.TwoPi) + { + angle += MathHelper.TwoPi; + } + while (referenceLimb.Rotation - angle < -MathHelper.TwoPi) + { + angle -= MathHelper.TwoPi; + } + + limb?.body.SmoothRotate(angle, torque, wrapAngle: false); + } + public override void Flip() { base.Flip(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index c3da3daf8..b0333fdc5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -1297,6 +1297,7 @@ namespace Barotrauma CheckValidity(Collider); foreach (Limb limb in limbs) { + if (limb.body == null || !limb.body.Enabled) { continue; } CheckValidity(limb.body); } } @@ -1323,7 +1324,11 @@ namespace Barotrauma } if (errorMsg != null) { +#if DEBUG DebugConsole.ThrowError(errorMsg); +#else + DebugConsole.NewMessage(errorMsg, Color.Red); +#endif GameAnalyticsManager.AddErrorEventOnce("Ragdoll.CheckValidity:" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); if (!MathUtils.IsValid(Collider.SimPosition) || Math.Abs(Collider.SimPosition.X) > 1e10f || Math.Abs(Collider.SimPosition.Y) > 1e10f) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index bd93653b8..e716766f0 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -10,6 +10,7 @@ using System.Xml.Linq; using Barotrauma.Items.Components; using FarseerPhysics.Dynamics; using Barotrauma.Extensions; +using System.Text; namespace Barotrauma { @@ -529,37 +530,6 @@ namespace Barotrauma set { canInventoryBeAccessed = value; } } - private bool canBeDragged = true; - public bool CanBeDragged - { - get - { - if (!canBeDragged) { return false; } - if (Removed || !AnimController.Draggable) { return false; } - return IsDead || Stun > 0.0f || LockHands || IsUnconscious; - } - set { canBeDragged = value; } - } - - //can other characters access the inventory of this character - private bool canInventoryBeAccessed = true; - public bool CanInventoryBeAccessed - { - get - { - if (!canInventoryBeAccessed || Removed || Inventory == null) { return false; } - if (!Inventory.AccessibleWhenAlive) - { - return IsDead; - } - else - { - return (IsDead || Stun > 0.0f || LockHands || IsUnconscious); - } - } - set { canInventoryBeAccessed = value; } - } - public override Vector2 SimPosition { get @@ -1017,10 +987,6 @@ namespace Barotrauma public Vector2? OverrideMovement { get; set; } public bool ForceRun { get; set; } - // TODO: reposition? there's also the overrideTargetMovement variable, but it's not in the same manner - public Vector2? OverrideMovement { get; set; } - public bool ForceRun { get; set; } - public Vector2 GetTargetMovement() { Vector2 targetMovement = Vector2.Zero; @@ -1069,7 +1035,24 @@ namespace Barotrauma ResetSpeedMultiplier(); // Reset, items will set the value before the next update - return targetMovement; + //? + //currMaxSpeed *= 1.5f; + + var leftFoot = AnimController.GetLimb(LimbType.LeftFoot); + if (leftFoot != null) + { + float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", leftFoot, true); + currMaxSpeed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); + } + + var rightFoot = AnimController.GetLimb(LimbType.RightFoot); + if (rightFoot != null) + { + float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", rightFoot, true); + currMaxSpeed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); + } + + return currMaxSpeed; } /// @@ -2202,16 +2185,17 @@ namespace Barotrauma #if SERVER if (attacker is Character attackingCharacter && attackingCharacter.AIController == null) { - string logMsg = LogName + " attacked by " + attackingCharacter.LogName + "."; + StringBuilder sb = new StringBuilder(); + sb.Append(LogName + " attacked by " + attackingCharacter.LogName + "."); if (attackResult.Afflictions != null) { foreach (Affliction affliction in attackResult.Afflictions) { if (affliction.Strength == 0.0f) continue; - logMsg += affliction.Prefab.Name + ": " + affliction.Strength; + sb.Append($" {affliction.Prefab.Name}: {affliction.Strength}"); } } - GameServer.Log(logMsg, ServerLog.MessageType.Attack); + GameServer.Log(sb.ToString(), ServerLog.MessageType.Attack); } #endif diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 9449341a4..f0b9a0930 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -980,68 +980,6 @@ namespace Barotrauma NewMessage("Set packet duplication to " + (int)(duplicates * 100) + "%.", Color.White); })); - commands.Add(new Command("simulatedlatency", "simulatedlatency [minimumlatencyseconds] [randomlatencyseconds]: applies a simulated latency to network messages. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) => - { - if (args.Count() < 2 || (GameMain.Client == null && GameMain.Server == null)) return; - if (!float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float minimumLatency)) - { - ThrowError(args[0] + " is not a valid latency value."); - return; - } - if (!float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float randomLatency)) - { - ThrowError(args[1] + " is not a valid latency value."); - return; - } - if (GameMain.Client != null) - { - GameMain.Client.NetPeerConfiguration.SimulatedMinimumLatency = minimumLatency; - GameMain.Client.NetPeerConfiguration.SimulatedRandomLatency = randomLatency; - } - else if (GameMain.Server != null) - { - GameMain.Server.NetPeerConfiguration.SimulatedMinimumLatency = minimumLatency; - GameMain.Server.NetPeerConfiguration.SimulatedRandomLatency = randomLatency; - } - NewMessage("Set simulated minimum latency to " + minimumLatency + " and random latency to " + randomLatency + ".", Color.White); - })); - commands.Add(new Command("simulatedloss", "simulatedloss [lossratio]: applies simulated packet loss to network messages. For example, a value of 0.1 would mean 10% of the packets are dropped. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) => - { - if (args.Count() < 1 || (GameMain.Client == null && GameMain.Server == null)) return; - if (!float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float loss)) - { - ThrowError(args[0] + " is not a valid loss ratio."); - return; - } - if (GameMain.Client != null) - { - GameMain.Client.NetPeerConfiguration.SimulatedLoss = loss; - } - else if (GameMain.Server != null) - { - GameMain.Server.NetPeerConfiguration.SimulatedLoss = loss; - } - NewMessage("Set simulated packet loss to " + (int)(loss * 100) + "%.", Color.White); - })); - commands.Add(new Command("simulatedduplicateschance", "simulatedduplicateschance [duplicateratio]: simulates packet duplication in network messages. For example, a value of 0.1 would mean there's a 10% chance a packet gets sent twice. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) => - { - if (args.Count() < 1 || (GameMain.Client == null && GameMain.Server == null)) return; - if (!float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float duplicates)) - { - ThrowError(args[0] + " is not a valid duplicate ratio."); - return; - } - if (GameMain.Client != null) - { - GameMain.Client.NetPeerConfiguration.SimulatedDuplicatesChance = duplicates; - } - else if (GameMain.Server != null) - { - GameMain.Server.NetPeerConfiguration.SimulatedDuplicatesChance = duplicates; - } - NewMessage("Set packet duplication to " + (int)(duplicates * 100) + "%.", Color.White); - })); - commands.Add(new Command("flipx", "flipx: mirror the main submarine horizontally", (string[] args) => { Submarine.MainSub?.FlipX(); diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs index f76427bc3..b961107de 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -116,11 +116,6 @@ namespace Barotrauma } } - if (GameMain.Server.Character != null) - { - c.Inventory?.DeleteAllItems(); - } - //remove all items that are in someone's inventory foreach (Character c in Character.CharacterList) { diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index 6c770ebef..ccb98ef24 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs @@ -45,6 +45,12 @@ namespace Barotrauma public bool SpecularityEnabled { get; set; } public bool ChromaticAberrationEnabled { get; set; } + public int ParticleLimit { get; set; } + + public float LightMapScale { get; set; } + public bool SpecularityEnabled { get; set; } + public bool ChromaticAberrationEnabled { get; set; } + public bool MuteOnFocusLost { get; set; } public enum VoiceMode @@ -304,11 +310,6 @@ namespace Barotrauma VerboseLogging = doc.Root.GetAttributeBool("verboselogging", false); SaveDebugConsoleLogs = doc.Root.GetAttributeBool("savedebugconsolelogs", false); -#if DEBUG - UseSteam = doc.Root.GetAttributeBool("usesteam", true); -#endif - QuickStartSubmarineName = doc.Root.GetAttributeString("quickstartsub", ""); - #if DEBUG UseSteam = doc.Root.GetAttributeBool("usesteam", true); #endif @@ -376,6 +377,8 @@ namespace Barotrauma AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f); + AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f); + AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f); keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length]; @@ -519,6 +522,34 @@ namespace Barotrauma TextManager.LoadTextPacks(SelectedContentPackages); + //display error messages after all content packages have been loaded + //to make sure the package that contains text files has been loaded before we attempt to use TextManager + foreach (string missingPackagePath in missingPackagePaths) + { + DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath)); + } + foreach (ContentPackage incompatiblePackage in incompatiblePackages) + { + DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage") + .Replace("[packagename]", incompatiblePackage.Name) + .Replace("[packageversion]", incompatiblePackage.GameVersion.ToString()) + .Replace("[gameversion]", GameMain.Version.ToString())); + } + foreach (ContentPackage contentPackage in SelectedContentPackages) + { + foreach (ContentFile file in contentPackage.Files) + { + if (!System.IO.File.Exists(file.Path)) + { + DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found."); + continue; + } + ToolBox.IsProperFilenameCase(file.Path); + } + } + + TextManager.LoadTextPacks(SelectedContentPackages); + //display error messages after all content packages have been loaded //to make sure the package that contains text files has been loaded before we attempt to use TextManager foreach (string missingPackagePath in missingPackagePaths) @@ -1071,6 +1102,32 @@ namespace Barotrauma NewLineOnAttributes = true }; +#if CLIENT + if (Tutorial.Tutorials != null) + { + foreach (Tutorial tutorial in Tutorial.Tutorials) + { + if (tutorial.Completed && !CompletedTutorialNames.Contains(tutorial.Name)) + { + CompletedTutorialNames.Add(tutorial.Name); + } + } + } +#endif + var tutorialElement = new XElement("tutorials"); + foreach (string tutorialName in CompletedTutorialNames) + { + tutorialElement.Add(new XElement("Tutorial", new XAttribute("name", tutorialName))); + } + doc.Root.Add(tutorialElement); + + XmlWriterSettings settings = new XmlWriterSettings + { + Indent = true, + OmitXmlDeclaration = true, + NewLineOnAttributes = true + }; + #if CLIENT if (Tutorial.Tutorials != null) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 3314c598b..7a79ef6b7 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -24,9 +24,6 @@ namespace Barotrauma.Items.Components private bool createdNewGap; private bool autoOrientGap; - private bool createdNewGap; - private bool autoOrientGap; - private bool isStuck; private float resetPredictionTimer; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index 863a5309a..62bffa330 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -252,10 +252,6 @@ namespace Barotrauma.Items.Components } - partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex); - partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter); - partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition); - partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex); partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter); partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs index 2b9945122..38a55074d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs @@ -187,6 +187,24 @@ namespace Barotrauma.Items.Components return base.Select(character); } + public override bool Select(Character character) + { + if (item.Container != null) { return false; } + + if (AutoInteractWithContained) + { + foreach (Item contained in Inventory.Items) + { + if (contained == null) continue; + if (contained.TryInteract(character)) + { + return false; + } + } + } + return base.Select(character); + } + public override bool Pick(Character picker) { if (AutoInteractWithContained) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index b935e3f8f..48e68d8ce 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -105,6 +105,8 @@ namespace Barotrauma.Items.Components progressTimer = 0.0f; } } + + voltage -= deltaTime * 10.0f; } private void PutItemsToLinkedContainer() @@ -162,8 +164,6 @@ namespace Barotrauma.Items.Components if (!IsActive) { progressState = 0.0f; } - if (!IsActive) { progressState = 0.0f; } - #if CLIENT if (!IsActive) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index dd99ce7aa..958396eb9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -493,7 +493,6 @@ namespace Barotrauma.Items.Components AutoTemp = false; unsentChanges = true; UpdateAutoTemp(2.0f + degreeOfSuccess * 5.0f, 1.0f); - } #if CLIENT onOffSwitch.BarScroll = 0.0f; @@ -505,6 +504,11 @@ namespace Barotrauma.Items.Components #if CLIENT onOffSwitch.BarScroll = 1.0f; #endif + if (AutoTemp || !shutDown || targetFissionRate > 0.0f || targetTurbineOutput > 0.0f) + { + unsentChanges = true; + } + AutoTemp = false; shutDown = true; targetFissionRate = 0.0f; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index 1a857dd7e..c5d01cc58 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs @@ -32,11 +32,6 @@ namespace Barotrauma.Items.Components //a list of powered devices connected directly to this item private readonly List> directlyConnected = new List>(10); - //charge indicator description - protected Vector2 indicatorPosition, indicatorSize; - - protected bool isHorizontal; - public float CurrPowerOutput { get; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs index 700322b4d..e2c055c74 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs @@ -178,9 +178,6 @@ namespace Barotrauma.Items.Components //items in a bad condition are more sensitive to overvoltage float maxOverVoltage = MathHelper.Lerp(Math.Min(OverloadVoltage, 1.0f), OverloadVoltage, item.Condition / item.Prefab.Health); - //items in a bad condition are more sensitive to overvoltage - float maxOverVoltage = MathHelper.Lerp(Math.Min(OverloadVoltage, 1.0f), OverloadVoltage, item.Condition / 100.0f); - //if the item can't be fixed, don't allow it to break if (!item.Repairables.Any() || !CanBeOverloaded) continue; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs index 341a16f1e..470830d07 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs @@ -54,7 +54,7 @@ namespace Barotrauma private LocationType(XElement element) { - Identifier = element.Name.ToString(); + Identifier = element.GetAttributeString("identifier", element.Name.ToString()); Name = TextManager.Get("LocationName." + Identifier); nameFormats = TextManager.GetAll("LocationNameFormat." + Identifier); @@ -65,7 +65,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Failed to read name file for location type \""+Identifier+"\"!", e); + DebugConsole.ThrowError("Failed to read name file for location type \"" + Identifier + "\"!", e); names = new List() { "Name file not found" }; } @@ -82,9 +82,7 @@ namespace Barotrauma } CommonnessPerZone[zoneIndex] = zoneCommonness; } - - string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt"); - try + catch (Exception e) { switch (subElement.Name.ToString().ToLowerInvariant()) { diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs index c3d5d2926..5e0b5c4dc 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs @@ -430,6 +430,13 @@ namespace Barotrauma } CurrentLocation.SelectedMissionIndex = missionIndex; + //the destination must be the same as the destination of the mission + if (CurrentLocation.SelectedMission != null && + CurrentLocation.SelectedMission.Locations[1] != SelectedLocation) + { + SelectLocation(CurrentLocation.SelectedMission.Locations[1]); + } + SelectedLocation = location; SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation)); OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index 4729b7675..919708fcc 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -518,6 +518,10 @@ namespace Barotrauma { maxX = Math.Min(maxX, ruin.Area.X - 100.0f); } + else + { + maxX = Math.Min(maxX, ruin.Area.X - 100.0f); + } if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs index b79a99f1c..2876896aa 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs @@ -40,16 +40,19 @@ namespace Barotrauma.Networking { get { - if (!Type.HasFlag(ChatMessageType.Server | ChatMessageType.Error)) + if (Type.HasFlag(ChatMessageType.Server) || Type.HasFlag(ChatMessageType.Error) || Type.HasFlag(ChatMessageType.ServerLog)) + { + if (translatedText == null || translatedText.Length == 0) + { + translatedText = TextManager.GetServerMessage(Text); + } + + return translatedText; + } + else { return Text; } - if (translatedText == null || translatedText.Length == 0) - { - translatedText = TextManager.GetServerMessage(Text); - } - - return translatedText; } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index 14111b85e..f701bdf9a 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs @@ -72,8 +72,6 @@ namespace Barotrauma.Networking public HashSet GivenAchievements = new HashSet(); - public HashSet GivenAchievements = new HashSet(); - public ClientPermissions Permissions = ClientPermissions.None; public List PermittedConsoleCommands { diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs index 6f9c11305..c5fd053ea 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs @@ -15,7 +15,7 @@ namespace Barotrauma.Networking public LogMessage(string text, MessageType type) { - Text = "[" + DateTime.Now.ToString() + "] " + text; + Text = "[" + DateTime.Now.ToString() + "] " + TextManager.GetServerMessage(text); Type = type; } } diff --git a/Barotrauma/BarotraumaShared/Source/PlayerInput.cs b/Barotrauma/BarotraumaShared/Source/PlayerInput.cs index 6b6f86b1c..ae64fea5b 100644 --- a/Barotrauma/BarotraumaShared/Source/PlayerInput.cs +++ b/Barotrauma/BarotraumaShared/Source/PlayerInput.cs @@ -145,6 +145,16 @@ namespace Barotrauma get { return binding; } } + public void SetState() + { + hit = binding.IsHit(); + if (hit) hitQueue = true; + + held = binding.IsDown(); + if (held) heldQueue = true; + } +#endif + public void SetState() { hit = binding.IsHit(); diff --git a/Barotrauma/BarotraumaShared/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaShared/Source/Screens/NetLobbyScreen.cs index 1f6f1429c..e479fb5c6 100644 --- a/Barotrauma/BarotraumaShared/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaShared/Source/Screens/NetLobbyScreen.cs @@ -82,33 +82,6 @@ namespace Barotrauma #endif } - public void SetBotCount(int botCount) - { - if (GameMain.Server != null) - { - if (botCount < 0) botCount = GameMain.Server.MaxBotCount; - if (botCount > GameMain.Server.MaxBotCount) botCount = 0; - - GameMain.Server.BotCount = botCount; - lastUpdateID++; - } -#if CLIENT - (botCountText as GUITextBlock).Text = botCount.ToString(); -#endif - } - - public void SetBotSpawnMode(BotSpawnMode botSpawnMode) - { - if (GameMain.Server != null) - { - GameMain.Server.BotSpawnMode = botSpawnMode; - lastUpdateID++; - } -#if CLIENT - (botSpawnModeText as GUITextBlock).Text = botSpawnMode.ToString(); -#endif - } - public void SetTraitorsEnabled(YesNoMaybe enabled) { #if SERVER diff --git a/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs index a6e11e951..3856ef078 100644 --- a/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs @@ -89,11 +89,6 @@ namespace Barotrauma public readonly AttributeCollection Attributes; public readonly Type PropertyType; - public object ParentObject - { - get { return obj; } - } - public SerializableProperty(PropertyDescriptor property, object obj) { Name = property.Name; @@ -187,9 +182,6 @@ namespace Barotrauma case "point": propertyInfo.SetValue(parentObject, XMLExtensions.ParsePoint(value)); break; - case "point": - propertyInfo.SetValue(obj, XMLExtensions.ParsePoint(value)); - break; case "vector2": propertyInfo.SetValue(parentObject, XMLExtensions.ParseVector2(value)); break; @@ -262,9 +254,6 @@ namespace Barotrauma case "point": propertyInfo.SetValue(parentObject, XMLExtensions.ParsePoint((string)value)); return true; - case "point": - propertyInfo.SetValue(obj, XMLExtensions.ParsePoint((string)value)); - return true; case "vector2": propertyInfo.SetValue(parentObject, XMLExtensions.ParseVector2((string)value)); return true; diff --git a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs index 207286bb9..a267c2344 100644 --- a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs @@ -486,38 +486,8 @@ namespace Barotrauma protected bool IsValidTarget(ISerializableEntity entity) { - if (entity is Item item) - { - if (item.HasTag(targetIdentifiers)) return true; - if (targetIdentifiers.Any(id => id == item.Prefab.Identifier)) return true; - } - else if (entity is ItemComponent itemComponent) - { - if (itemComponent.Item.HasTag(targetIdentifiers)) return true; - if (targetIdentifiers.Any(id => id == itemComponent.Item.Prefab.Identifier)) return true; - } - else if (entity is Structure structure) - { - if (targetIdentifiers.Any(id => id == structure.Prefab.Identifier)) return true; - } - else if (entity is Character character) - { - if (targetIdentifiers.Any(id => id == character.SpeciesName)) return true; - } + if (targetIdentifiers == null) { return true; } - return targetIdentifiers.Any(id => id == entity.Name); - } - - public void SetUser(Character user) - { - foreach (Affliction affliction in Afflictions) - { - affliction.Source = user; - } - } - - protected bool IsValidTarget(ISerializableEntity entity) - { if (entity is Item item) { if (item.HasTag(targetIdentifiers)) return true; @@ -744,6 +714,11 @@ namespace Barotrauma } } } + + bool isNotClient = true; +#if CLIENT + isNotClient = GameMain.Client == null; +#endif if (FireSize > 0.0f && entity != null) { @@ -812,62 +787,6 @@ namespace Barotrauma } } - if (GameMain.Client == null && entity != null && Entity.Spawner != null) //clients are not allowed to spawn items - { - foreach (ItemSpawnInfo itemSpawnInfo in spawnItems) - { - switch (itemSpawnInfo.SpawnPosition) - { - case ItemSpawnInfo.SpawnPositionType.This: - Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, entity.WorldPosition); - break; - case ItemSpawnInfo.SpawnPositionType.ThisInventory: - { - if (entity is Character character) - { - if (character.Inventory != null && character.Inventory.Items.Any(it => it == null)) - { - Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, character.Inventory); - } - } - else if (entity is Item item) - { - var inventory = item?.GetComponent()?.Inventory; - if (inventory != null && inventory.Items.Any(it => it == null)) - { - Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, inventory); - } - } - } - break; - case ItemSpawnInfo.SpawnPositionType.ContainedInventory: - { - Inventory thisInventory = null; - if (entity is Character character) - { - thisInventory = character.Inventory; - } - else if (entity is Item item) - { - thisInventory = item?.GetComponent()?.Inventory; - } - if (thisInventory != null) - { - foreach (Item item in thisInventory.Items) - { - if (item == null) continue; - Inventory containedInventory = item.GetComponent()?.Inventory; - if (containedInventory == null || !containedInventory.Items.Any(i => i == null)) continue; - Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, containedInventory); - break; - } - } - } - break; - } - } - } - #if CLIENT if (entity != null) { @@ -983,30 +902,6 @@ namespace Barotrauma limb.character.CharacterHealth.ReduceAffliction(limb, reduceAffliction.First, reduceAffliction.Second * deltaTime); } } - - foreach (Affliction affliction in element.Parent.Afflictions) - { - if (target is Character) - { - ((Character)target).CharacterHealth.ApplyAffliction(null, affliction.CreateMultiplied(deltaTime)); - } - else if (target is Limb limb) - { - limb.character.CharacterHealth.ApplyAffliction(limb, affliction.CreateMultiplied(deltaTime)); - } - } - - foreach (Pair reduceAffliction in element.Parent.ReduceAffliction) - { - if (target is Character) - { - ((Character)target).CharacterHealth.ReduceAffliction(null, reduceAffliction.First, reduceAffliction.Second * deltaTime); - } - else if (target is Limb limb) - { - limb.character.CharacterHealth.ReduceAffliction(limb, reduceAffliction.First, reduceAffliction.Second * deltaTime); - } - } } element.Timer -= deltaTime; diff --git a/Barotrauma/BarotraumaShared/Source/TextManager.cs b/Barotrauma/BarotraumaShared/Source/TextManager.cs index 2e0063bc3..e14874f1d 100644 --- a/Barotrauma/BarotraumaShared/Source/TextManager.cs +++ b/Barotrauma/BarotraumaShared/Source/TextManager.cs @@ -155,18 +155,29 @@ namespace Barotrauma { if (!messages[i].Contains("_")) // No variables, just translate { - messages[i] = Get(messages[i]); - continue; + string msg = Get(messages[i], true); + + if (msg != null) // If a translation was found, otherwise use the original + { + messages[i] = msg; + } } - - string[] messageWithVariables = messages[i].Split('_'); - messages[i] = Get(messageWithVariables[0]); - - // First index is always the message identifier -> start at 1 - for (int j = 1; j < messageWithVariables.Length; j++) + else { - string[] variableAndValue = messageWithVariables[j].Split('='); - messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]); + string[] messageWithVariables = messages[i].Split('_'); + string msg = Get(messageWithVariables[0], true); + + if (msg != null) // If a translation was found, otherwise use the original + { + messages[i] = msg; + } + + // First index is always the message identifier -> start at 1 + for (int j = 1; j < messageWithVariables.Length; j++) + { + string[] variableAndValue = messageWithVariables[j].Split('='); + messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Utils/Rand.cs b/Barotrauma/BarotraumaShared/Source/Utils/Rand.cs index 371cec29e..6b976d2c9 100644 --- a/Barotrauma/BarotraumaShared/Source/Utils/Rand.cs +++ b/Barotrauma/BarotraumaShared/Source/Utils/Rand.cs @@ -39,12 +39,6 @@ namespace Barotrauma return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum; } - public static double Range(double minimum, double maximum, RandSync sync = RandSync.Unsynced) - { - Assert(sync); - return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum; - } - public static int Range(int minimum, int maximum, RandSync sync = RandSync.Unsynced) { return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).Next(maximum - minimum) + minimum; diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 84de171e0..aacc41a31 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 0e76256e6..6f8e91fd6 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index c7c0cb47d..d3c065fe6 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 8341c5e76..72b0194da 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,26 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.9.5 +--------------------------------------------------------------------------------------------------------- + +Bugfixes: +- Fixed alien vents crashing the game when there's a character nearby. +- Fixed chatbox retaining the radio message prefix after being deselected. +- Push-to-talk doesn't trigger when typin in a text box. +- Fixed some server log messages and texts sent to clients being incorrect (= showing the tags that are +used to fetch the texts from the language files instead of the actual texts). +- Fixed AI orders that target a specific item (such as the order to power up the reactor) not working in +multiplayer. +- Fixed crashes when attempting to use voice capture or change voice capture settings when there are no +suitable capture devices available. +- Fixed clients not being notified when an AI character shuts down the reactor. +- Fixed deconstructors staying active without power in multiplayer. +- Fixed sonar labels going outside the screen when at the left side of the display. + +Additions & changes: +- Added some supplies to vanilla submarines. +- Balanced item deterioration rates and adjusted neutral ballast settings in vanilla subs. +- Increased the impact tolerance of crawlers to prevent them from killing themselves by bumping into walls. + --------------------------------------------------------------------------------------------------------- v0.8.9.4 --------------------------------------------------------------------------------------------------------- @@ -56,6 +79,7 @@ Additions: - Some new random events. - A bunch of new afflictions and medical items. - Some new item sprites. +- A couple of new cargo missions. - Added some new items that can be crafted from alien materials. - Display linked hulls as one room on the status monitor. - Tons of new sound effects.