diff --git a/Barotrauma/BarotraumaClient/ClientSource/Camera.cs b/Barotrauma/BarotraumaClient/ClientSource/Camera.cs index 02ab56891..0bfac9a82 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Camera.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Camera.cs @@ -179,9 +179,9 @@ namespace Barotrauma { if (Character.Controlled != null && !Character.Controlled.IsDead) { return; } - msg.Write((byte)ClientNetObject.SPECTATING_POS); - msg.Write(position.X); - msg.Write(position.Y); + msg.WriteByte((byte)ClientNetObject.SPECTATING_POS); + msg.WriteSingle(position.X); + msg.WriteSingle(position.Y); } private void CreateMatrices() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 6f32c617c..4ad12a2d4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -114,27 +114,27 @@ namespace Barotrauma public void ClientWriteInput(IWriteMessage msg) { - msg.Write((byte)ClientNetObject.CHARACTER_INPUT); + msg.WriteByte((byte)ClientNetObject.CHARACTER_INPUT); if (memInput.Count > 60) { memInput.RemoveRange(60, memInput.Count - 60); } - msg.Write(LastNetworkUpdateID); + msg.WriteUInt16(LastNetworkUpdateID); byte inputCount = Math.Min((byte)memInput.Count, (byte)60); - msg.Write(inputCount); + msg.WriteByte(inputCount); for (int i = 0; i < inputCount; i++) { msg.WriteRangedInteger((int)memInput[i].states, 0, (int)InputNetFlags.MaxVal); - msg.Write(memInput[i].intAim); + msg.WriteUInt16(memInput[i].intAim); if (memInput[i].states.HasFlag(InputNetFlags.Select) || memInput[i].states.HasFlag(InputNetFlags.Deselect) || memInput[i].states.HasFlag(InputNetFlags.Use) || memInput[i].states.HasFlag(InputNetFlags.Health) || memInput[i].states.HasFlag(InputNetFlags.Grab)) { - msg.Write(memInput[i].interact); + msg.WriteUInt16(memInput[i].interact); } } } @@ -150,16 +150,16 @@ namespace Barotrauma Inventory.ClientEventWrite(msg, inventoryStateEventData); break; case TreatmentEventData _: - msg.Write(AnimController.Anim == AnimController.Animation.CPR); + msg.WriteBoolean(AnimController.Anim == AnimController.Animation.CPR); break; case CharacterStatusEventData _: //do nothing break; case UpdateTalentsEventData _: - msg.Write((ushort)characterTalents.Count); + msg.WriteUInt16((ushort)characterTalents.Count); foreach (var unlockedTalent in characterTalents) { - msg.Write(unlockedTalent.Prefab.UintIdentifier); + msg.WriteUInt32(unlockedTalent.Prefab.UintIdentifier); } break; default: diff --git a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/ContentPackage/ModProject.cs b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/ContentPackage/ModProject.cs index 088fb2725..34d2a3a75 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/ContentPackage/ModProject.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/ContentPackage/ModProject.cs @@ -74,7 +74,7 @@ namespace Barotrauma get => name; set { - var charsToRemove = Path.GetInvalidFileNameChars(); + var charsToRemove = Path.GetInvalidFileNameCharsCrossPlatform(); name = string.Concat(value.Where(c => !charsToRemove.Contains(c))); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 22196a3e7..98fdaa449 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -723,7 +723,7 @@ namespace Barotrauma AssignOnExecute("explosion", (string[] args) => { - Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); + Vector2 explosionPos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition); float range = 500, force = 10, damage = 50, structureDamage = 20, itemDamage = 100, empStrength = 0.0f, ballastFloraStrength = 50f; if (args.Length > 0) float.TryParse(args[0], out range); if (args.Length > 1) float.TryParse(args[1], out force); @@ -3056,6 +3056,11 @@ namespace Barotrauma commands.Add(new Command("reloadwearables", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args => { + if (GameMain.GameSession != null) + { + ThrowError("Using the command is not allowed during an active game session: the command is intended to be used in the character editor or in the main menu."); + return; + } var character = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, true); if (character == null) { @@ -3067,6 +3072,11 @@ namespace Barotrauma commands.Add(new Command("loadwearable", "Force select certain variant for the selected character.", args => { + if (GameMain.GameSession != null) + { + ThrowError("Using the command is not allowed during an active game session: the command is intended to be used in the character editor or in the main menu."); + return; + } var character = Character.Controlled; if (character == null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs index e8bddc5f7..065feb05f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs @@ -381,18 +381,18 @@ namespace Barotrauma private static void SendResponse(UInt16 actionId, int selectedOption) { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE); - outmsg.Write(actionId); - outmsg.Write((byte)selectedOption); + outmsg.WriteByte((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE); + outmsg.WriteUInt16(actionId); + outmsg.WriteByte((byte)selectedOption); GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable); } private static void SendIgnore(UInt16 actionId) { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE); - outmsg.Write(actionId); - outmsg.Write(byte.MaxValue); + outmsg.WriteByte((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE); + outmsg.WriteUInt16(actionId); + outmsg.WriteByte(byte.MaxValue); GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs index 3957dff43..291366b9b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs @@ -1,6 +1,4 @@ -using Barotrauma.Networking; - -namespace Barotrauma +namespace Barotrauma { partial class CombatMission : Mission { @@ -8,7 +6,7 @@ namespace Barotrauma { get { - if (descriptions == null) return ""; + if (descriptions == null) { return ""; } if (GameMain.Client?.Character == null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs index cff6b76b9..28eafbc6c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs @@ -80,6 +80,10 @@ namespace Barotrauma LocalizedString header = messageIndex < Headers.Length ? Headers[messageIndex] : ""; LocalizedString message = messageIndex < Messages.Length ? Messages[messageIndex] : ""; + if (!message.IsNullOrEmpty()) + { + message = ModifyMessage(message); + } CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message)); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs index 114d62cdc..094a4b81c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs @@ -325,6 +325,12 @@ namespace Barotrauma ToggleOpen = PreferChatBoxOpen = GameSettings.CurrentConfig.ChatOpen; } + public void Toggle() + { + ToggleOpen = !ToggleOpen; + CloseAfterMessageSent = false; + } + public bool TypingChatMessage(GUITextBox textBox, string text) { string command = ChatMessage.GetChatMessageCommand(text, out _); @@ -605,22 +611,29 @@ namespace Barotrauma showNewMessagesButton.Visible = false; } - if (PlayerInput.KeyHit(InputType.ToggleChatMode) && GUI.KeyboardDispatcher.Subscriber == null && Screen.Selected == GameMain.GameScreen) + if (Screen.Selected == GameMain.GameScreen && GUI.KeyboardDispatcher.Subscriber == null) { - try + if (PlayerInput.KeyHit(InputType.ToggleChatMode)) { - var mode = GameMain.ActiveChatMode switch + try { - ChatMode.Local => ChatMode.Radio, - ChatMode.Radio => ChatMode.Local, - _ => throw new NotImplementedException() - }; - ChatModeDropDown.SelectItem(mode); - // TODO: Play a sound? + var mode = GameMain.ActiveChatMode switch + { + ChatMode.Local => ChatMode.Radio, + ChatMode.Radio => ChatMode.Local, + _ => throw new NotImplementedException() + }; + ChatModeDropDown.SelectItem(mode); + // TODO: Play a sound? + } + catch (NotImplementedException) + { + DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\""); + } } - catch (NotImplementedException) + else if (PlayerInput.KeyHit(InputType.ChatBox)) { - DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\""); + Toggle(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs index 80de95f63..b25b07ca4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs @@ -889,35 +889,35 @@ namespace Barotrauma if (campaign is MultiPlayerCampaign) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.CREW); + msg.WriteByte((byte)ClientPacketHeader.CREW); - msg.Write(updatePending); + msg.WriteBoolean(updatePending); if (updatePending) { - msg.Write((ushort)PendingHires.Count); + msg.WriteUInt16((ushort)PendingHires.Count); foreach (CharacterInfo pendingHire in PendingHires) { - msg.Write(pendingHire.GetIdentifierUsingOriginalName()); + msg.WriteInt32(pendingHire.GetIdentifierUsingOriginalName()); } } - msg.Write(validateHires); + msg.WriteBoolean(validateHires); bool validRenaming = renameCharacter.info != null && !string.IsNullOrEmpty(renameCharacter.newName); - msg.Write(validRenaming); + msg.WriteBoolean(validRenaming); if (validRenaming) { int identifier = renameCharacter.info.GetIdentifierUsingOriginalName(); - msg.Write(identifier); - msg.Write(renameCharacter.newName); + msg.WriteInt32(identifier); + msg.WriteString(renameCharacter.newName); bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.GetIdentifierUsingOriginalName() == identifier) ?? false; - msg.Write(existingCrewMember); + msg.WriteBoolean(existingCrewMember); } - msg.Write(firedCharacter != null); + msg.WriteBoolean(firedCharacter != null); if (firedCharacter != null) { - msg.Write(firedCharacter.GetIdentifier()); + msg.WriteInt32(firedCharacter.GetIdentifier()); } GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index 4e4f7c94d..cbe1cb50d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -1018,6 +1018,11 @@ namespace Barotrauma if (c.Enabled) { + var dragHandle = c as GUIDragHandle ?? parent as GUIDragHandle; + if (dragHandle != null) + { + return dragHandle.Dragging ? CursorState.Dragging : CursorState.Hand; + } // Some parent elements take priority // but not when the child is a GUIButton or GUITickBox if (!(parent is GUIButton) && !(parent is GUIListBox) || @@ -1027,6 +1032,7 @@ namespace Barotrauma } } + // Children in list boxes can be interacted with despite not having // a GUIButton inside of them so instead of hard coding we check if // the children can be interacted with by checking their hover state @@ -1097,6 +1103,8 @@ namespace Barotrauma return list; case GUIScrollBar bar: return bar; + case GUIDragHandle dragHandle: + return dragHandle; } } component = parent; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs index 61ddb8f50..764382577 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs @@ -813,9 +813,9 @@ namespace Barotrauma flashColor = (color == null) ? GUIStyle.Red : (Color)color; } - public void FadeOut(float duration, bool removeAfter, float wait = 0.0f) + public void FadeOut(float duration, bool removeAfter, float wait = 0.0f, Action onRemove = null) { - CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait)); + CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait, onRemove)); } public void FadeIn(float wait, float duration) @@ -877,7 +877,7 @@ namespace Barotrauma yield return CoroutineStatus.Success; } - private IEnumerable LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f) + private IEnumerable LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f, Action onRemove = null) { State = ComponentState.None; float t = 0.0f; @@ -902,6 +902,7 @@ namespace Barotrauma if (removeAfter && Parent != null) { Parent.RemoveChild(this); + onRemove?.Invoke(); } yield return CoroutineStatus.Success; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDragHandle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDragHandle.cs index 678b3fb29..492772759 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDragHandle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDragHandle.cs @@ -16,6 +16,8 @@ namespace Barotrauma public Func ValidatePosition; + public bool Dragging => dragStarted; + public GUIDragHandle(RectTransform rectT, RectTransform elementToMove, string style = "GUIDragIndicator") : base(style, rectT) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 579e492c2..b532b7900 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -57,7 +57,8 @@ namespace Barotrauma { SelectSingle, SelectMultiple, - RequireShiftToSelectMultiple + RequireShiftToSelectMultiple, + None } public SelectMode CurrentSelectMode = SelectMode.SelectSingle; @@ -1058,7 +1059,7 @@ namespace Barotrauma public void Select(int childIndex, Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus = TakeKeyBoardFocus.No, PlaySelectSound playSelectSound = PlaySelectSound.No) { - if (childIndex >= Content.CountChildren || childIndex < 0) { return; } + if (childIndex >= Content.CountChildren || childIndex < 0 || CurrentSelectMode == SelectMode.None) { return; } GUIComponent child = Content.GetChild(childIndex); if (child is null) { return; } @@ -1157,6 +1158,7 @@ namespace Barotrauma public void Select(IEnumerable children) { + if (CurrentSelectMode == SelectMode.None) { return; } Selected = true; selected.Clear(); selected.AddRange(children.Where(c => Content.Children.Contains(c))); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs index 8936fb126..f7bad7dc1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs @@ -1,9 +1,7 @@ using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; -using Barotrauma.Networking; using Barotrauma.Extensions; namespace Barotrauma @@ -25,9 +23,12 @@ namespace Barotrauma Default, InGame, Vote, - Hint + Hint, + Tutorial } + private bool IsAnimated => type == Type.InGame || type == Type.Hint || type == Type.Tutorial; + public List Buttons { get; private set; } = new List(); public GUILayoutGroup Content { get; private set; } public GUIFrame InnerFrame { get; private set; } @@ -60,6 +61,9 @@ namespace Barotrauma public GUIImage BackgroundIcon { get; private set; } private GUIImage newBackgroundIcon; + /// + /// Close the message box automatically after enough time has passed () + /// public bool AutoClose; private float openState; @@ -69,6 +73,11 @@ namespace Barotrauma private readonly Type type; + /// + /// Close the message box automatically if the condition is met + /// + private readonly Func autoCloseCondition; + public Type MessageBoxType => type; public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault(); @@ -79,7 +88,9 @@ namespace Barotrauma this.Buttons[0].OnClicked = Close; } - public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null) + public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons, + Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", + Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null, Func autoCloseCondition = null, bool hideCloseButton = false) : base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUIStyle.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox") { int width = (int)(DefaultWidth * type switch @@ -116,6 +127,7 @@ namespace Barotrauma Anchor anchor = type switch { Type.InGame => Anchor.TopCenter, + Type.Tutorial => Anchor.TopCenter, Type.Hint => Anchor.TopRight, Type.Vote => Anchor.TopRight, _ => Anchor.Center @@ -188,7 +200,7 @@ namespace Barotrauma Buttons.Add(button); } } - else if (type == Type.InGame) + else if (type == Type.InGame || type == Type.Tutorial) { InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight); CanBeFocused = false; @@ -212,37 +224,43 @@ namespace Barotrauma Content = new GUILayoutGroup(new RectTransform(new Vector2(Icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform)); - var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null); - Buttons = new List(1) + if (!hideCloseButton) { - new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center), - style: "UIToggleButton") + var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null); + Buttons = new List(1) { - OnClicked = Close - } - }; - - InputType? closeInput = null; - if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Use].MouseButton == MouseButton.None) - { - closeInput = InputType.Use; - } - else if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select].MouseButton == MouseButton.None) - { - closeInput = InputType.Select; - } - if (closeInput.HasValue) - { - Buttons[0].ToolTip = TextManager.ParseInputTypes($"{TextManager.Get("Close")} ([InputType.{closeInput.Value}])"); - Buttons[0].OnAddedToGUIUpdateList += (GUIComponent component) => - { - if (!closing && openState >= 1.0f && PlayerInput.KeyHit(closeInput.Value)) + new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center), + style: "UIToggleButton") { - GUIButton btn = component as GUIButton; - btn?.OnClicked(btn, btn.UserData); - btn?.Flash(GUIStyle.Green); + OnClicked = Close } }; + InputType? closeInput = null; + if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Use].MouseButton == MouseButton.None) + { + closeInput = InputType.Use; + } + else if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select].MouseButton == MouseButton.None) + { + closeInput = InputType.Select; + } + if (closeInput.HasValue) + { + Buttons[0].ToolTip = TextManager.ParseInputTypes($"{TextManager.Get("Close")} ([InputType.{closeInput.Value}])"); + Buttons[0].OnAddedToGUIUpdateList += (GUIComponent component) => + { + if (!closing && openState >= 1.0f && PlayerInput.KeyHit(closeInput.Value)) + { + GUIButton btn = component as GUIButton; + btn?.OnClicked(btn, btn.UserData); + btn?.Flash(GUIStyle.Green); + } + }; + } + } + else + { + Buttons.Clear(); } Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true); @@ -274,7 +292,10 @@ namespace Barotrauma Content.RectTransform.NonScaledSize = new Point(Content.Rect.Width, height); } - Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y); + if (!hideCloseButton) + { + Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y); + } } else if (type == Type.Hint) { @@ -408,6 +429,8 @@ namespace Barotrauma } } + this.autoCloseCondition = autoCloseCondition; + MessageBoxes.Add(this); } @@ -511,13 +534,15 @@ namespace Barotrauma } } - if (type == Type.InGame || type == Type.Hint) + if (IsAnimated) { Vector2 initialPos, defaultPos, endPos; - if (type == Type.InGame) + if (type == Type.InGame || type == Type.Tutorial) { initialPos = new Vector2(0.0f, GameMain.GraphicsHeight); - defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale); + defaultPos = type == Type.InGame ? + new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale) : + new Vector2(0.0f, GameMain.GraphicsHeight / 2); endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y); } else @@ -549,7 +574,7 @@ namespace Barotrauma inGameCloseTimer += deltaTime; } - if (inGameCloseTimer >= inGameCloseTime) + if (inGameCloseTimer >= inGameCloseTime || (autoCloseCondition != null && autoCloseCondition())) { Close(); } @@ -612,7 +637,7 @@ namespace Barotrauma public void Close() { - if (type == Type.InGame || type == Type.Hint) + if (IsAnimated) { closing = true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index 27be2752f..6451c8d24 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -141,7 +141,23 @@ namespace Barotrauma public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium"); public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh"); - public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale); + public static Point ItemFrameMargin + { + get + { + Point size = new Point(50, 56).Multiply(GUI.SlicedSpriteScale); + + var style = GetComponentStyle("ItemUI"); + var sprite = style?.Sprites[GUIComponent.ComponentState.None].First(); + if (sprite != null) + { + size.X = Math.Min(sprite.Slices[0].Width + sprite.Slices[2].Width, size.X); + size.Y = Math.Min(sprite.Slices[0].Height + sprite.Slices[6].Height, size.Y); + } + return size; + } + } + public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale); public static GUIComponentStyle GetComponentStyle(string name) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs index 404c09927..99461d746 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs @@ -587,7 +587,7 @@ namespace Barotrauma if (Shadow) { - Vector2 shadowOffset = new Vector2(GUI.IntScale(2)); + Vector2 shadowOffset = new Vector2(Math.Max(GUI.IntScale(2), 1)); Font.DrawString(spriteBatch, textToShow, pos + shadowOffset, Color.Black, 0.0f, origin, TextScale, SpriteEffects.None, textDepth, alignment: textAlignment, forceUpperCase: ForceUpperCase); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs index 0552340d0..ded2b870e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs @@ -25,6 +25,10 @@ namespace Barotrauma { get; private set; } + public static Rectangle TutorialObjectiveListArea + { + get; private set; + } public static Rectangle MessageAreaTop { @@ -167,6 +171,10 @@ namespace Barotrauma HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight); + int objectiveListAreaX = HealthWindowAreaLeft.Right + Padding; + int objectiveListAreaY = ButtonAreaTop.Bottom + Padding; + TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (AfflictionAreaLeft.Top - Padding) - objectiveListAreaY); + int votingAreaWidth = (int)(400 * GUI.Scale); int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth; int votingAreaY = Padding + ButtonAreaTop.Height; @@ -179,16 +187,19 @@ namespace Barotrauma public static void Draw(SpriteBatch spriteBatch) { - GUI.DrawRectangle(spriteBatch, ButtonAreaTop, Color.White * 0.5f); - GUI.DrawRectangle(spriteBatch, MessageAreaTop, GUIStyle.Orange * 0.5f); - GUI.DrawRectangle(spriteBatch, CrewArea, Color.Blue * 0.5f); - GUI.DrawRectangle(spriteBatch, ChatBoxArea, Color.Cyan * 0.5f); - GUI.DrawRectangle(spriteBatch, HealthBarArea, Color.Red * 0.5f); - GUI.DrawRectangle(spriteBatch, AfflictionAreaLeft, Color.Red * 0.5f); - GUI.DrawRectangle(spriteBatch, InventoryAreaLower, Color.Yellow * 0.5f); - GUI.DrawRectangle(spriteBatch, HealthWindowAreaLeft, Color.Red * 0.5f); - GUI.DrawRectangle(spriteBatch, BottomRightInfoArea, Color.Green * 0.5f); - GUI.DrawRectangle(spriteBatch, ItemHUDArea, Color.Magenta * 0.3f); + DrawRectangle(ButtonAreaTop, Color.White * 0.5f); + DrawRectangle(TutorialObjectiveListArea, GUIStyle.Blue * 0.5f); + DrawRectangle(MessageAreaTop, GUIStyle.Orange * 0.5f); + DrawRectangle(CrewArea, Color.Blue * 0.5f); + DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f); + DrawRectangle(HealthBarArea, Color.Red * 0.5f); + DrawRectangle(AfflictionAreaLeft, Color.Red * 0.5f); + DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f); + DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f); + DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f); + DrawRectangle(ItemHUDArea, Color.Magenta * 0.3f); + + void DrawRectangle(Rectangle r, Color c) => GUI.DrawRectangle(spriteBatch, r, c); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs index 47d184dd0..6e140e57b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs @@ -56,6 +56,7 @@ namespace Barotrauma public SubmarineInfo displayedSubmarine; public GUITextBlock submarineName; public GUITextBlock submarineClass; + public GUITextBlock submarineTier; public GUITextBlock submarineFee; public GUIButton selectSubmarineButton; public GUITextBlock middleTextBlock; @@ -162,7 +163,12 @@ namespace Barotrauma IgnoreLayoutGroups = true }; new GUIListBox(new RectTransform(Vector2.One, infoFrame.RectTransform)) { IgnoreLayoutGroups = true, CanBeFocused = false }; - specsFrame = new GUIListBox(new RectTransform(new Vector2(0.39f, 1f), infoFrame.RectTransform), style: null) { Spacing = GUI.IntScale(5), Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding, 0, 0) }; + specsFrame = new GUIListBox(new RectTransform(new Vector2(0.39f, 1f), infoFrame.RectTransform), style: null) + { + CurrentSelectMode = GUIListBox.SelectMode.None, + Spacing = GUI.IntScale(5), + Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding, 0, 0) + }; new GUIFrame(new RectTransform(new Vector2(0.02f, 0.8f), infoFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, style: "VerticalLine"); GUIListBox descriptionFrame = new GUIListBox(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform), style: null) { Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding / 2f) }; descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUIStyle.Font, wrap: true) { CanBeFocused = false }; @@ -221,7 +227,8 @@ namespace Barotrauma submarineDisplayElement.submarineImage = new GUIImage(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), null, true); submarineDisplayElement.middleTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), string.Empty, textAlignment: Alignment.Center); submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont); - submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Center); + submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Left); + submarineDisplayElement.submarineTier = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Right); submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont); submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null); submarineDisplayElement.previewButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, submarineDisplayElement.background.RectTransform, anchor: Anchor.BottomRight, pivot: Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * background.Rect.Height)) }, style: "ExpandButton") @@ -355,6 +362,7 @@ namespace Barotrauma submarineDisplays[i].submarineName.Text = string.Empty; submarineDisplays[i].submarineFee.Text = string.Empty; submarineDisplays[i].submarineClass.Text = string.Empty; + submarineDisplays[i].submarineTier.Text = string.Empty; submarineDisplays[i].selectSubmarineButton.Enabled = false; submarineDisplays[i].selectSubmarineButton.OnClicked = null; submarineDisplays[i].displayedSubmarine = null; @@ -389,6 +397,7 @@ namespace Barotrauma submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName; submarineDisplays[i].submarineClass.Text = TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}")); + submarineDisplays[i].submarineTier.Text = TextManager.Get($"submarinetier.{subToDisplay.Tier}"); if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay)) { @@ -847,7 +856,7 @@ namespace Barotrauma msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext", ("[submarinename]", selectedSubmarine.DisplayName), ("[amount]", selectedSubmarine.Price.ToString()), - ("[currencyname]", currencyName)), messageBoxOptions); + ("[currencyname]", currencyName)) + '\n' + TextManager.Get("submarineswitchinstruction"), messageBoxOptions); msgBox.Buttons[0].OnClicked = (applyButton, obj) => { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 1e7d1cd03..1f49c63f0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -1722,7 +1722,7 @@ namespace Barotrauma var subInfoTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, paddedFrame.RectTransform)); - LocalizedString className = !sub.Info.HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}") : TextManager.Get("shuttle"); + LocalizedString className = !sub.Info.HasTag(SubmarineTag.Shuttle) ? $"{TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}")} ({TextManager.Get($"submarinetier.{sub.Info.Tier}")})" : TextManager.Get("shuttle"); int nameHeight = (int)GUIStyle.LargeFont.MeasureString(sub.Info.DisplayName, true).Y; int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y; @@ -1763,7 +1763,10 @@ namespace Barotrauma } else { - var specsListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.57f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft)); + var specsListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.57f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft)) + { + CurrentSelectMode = GUIListBox.SelectMode.None + }; sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true); } } @@ -1954,14 +1957,15 @@ namespace Barotrauma Point iconSize = cornerIcon.RectTransform.NonScaledSize; cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2); - if (subTree.TalentOptionStages.Count <= i) { continue; } + if (subTree.TalentOptionStages.Length <= i) { continue; } TalentOption talentOption = subTree.TalentOptionStages[i]; GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft); GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; - foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier)) + foreach (Identifier talentId in talentOption.TalentIdentifiers.OrderBy(t => t)) { + if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab talent)) { continue; } GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null) { CanBeFocused = false diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index fc4cc62f2..db946f01a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -17,7 +17,7 @@ using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement; namespace Barotrauma { - internal class UpgradeStore + internal sealed class UpgradeStore { public readonly struct CategoryData { @@ -847,7 +847,8 @@ namespace Barotrauma foreach (UpgradePrefab prefab in prefabs) { - CreateUpgradeEntry(prefab, category, parent.Content, entitiesOnSub); + if (prefab.MaxLevel is 0) { continue; } + CreateUpgradeEntry(prefab, category, parent.Content, submarine, entitiesOnSub); } } @@ -1112,7 +1113,7 @@ namespace Barotrauma GUILayoutGroup? buyButtonLayout = null; if (addProgressBar) - { + { progressLayout = new GUILayoutGroup(rectT(1, 0.25f, textLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft) { UserData = "progressbar" }; new GUIProgressBar(rectT(0.8f, 0.75f, progressLayout), 0.0f, GUIStyle.Orange); new GUITextBlock(rectT(0.2f, 1, progressLayout), string.Empty, font: GUIStyle.SmallFont, textAlignment: Alignment.Center) { Padding = Vector4.Zero }; @@ -1124,7 +1125,11 @@ namespace Barotrauma //negative price = refund if (price < 0) { formattedPrice = "+" + formattedPrice; } buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" }; - var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center); + var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center) + { + //prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades + Visible = userData is ItemPrefab + }; if (price < 0) { priceText.TextColor = GUIStyle.Green; @@ -1183,9 +1188,10 @@ namespace Barotrauma } } - private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List? itemsOnSubmarine) + private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, Submarine submarine, List? itemsOnSubmarine) { - if (Campaign is null) { return; } + Submarine? sub = GameMain.GameSession?.Submarine ?? Submarine.MainSub; + if (Campaign is null || sub is null) { return; } GUIFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.25f, parent)); var prefabLayout = prefabFrame.GetChild(); @@ -1201,7 +1207,7 @@ namespace Barotrauma var buyButtonLayout = childLayouts[2]; var buyButton = buyButtonLayout.GetChild(); - if (!HasPermission || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab)))) + if (!HasPermission || !prefab.IsApplicable(submarine.Info) || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab)))) { prefabFrame.Enabled = false; description.Enabled = false; @@ -1446,7 +1452,7 @@ namespace Barotrauma * |--------------------------------------------------| * | name | * |--------------------------------------------------| - * | class | + * | class + tier | * |--------------------------------------------------| * | description | * | | @@ -1456,13 +1462,31 @@ namespace Barotrauma submarineInfoFrame = new GUILayoutGroup(rectT(0.25f, 0.2f, mainStoreLayout, Anchor.TopRight)) { IgnoreLayoutGroups = true }; // submarine name new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.DisplayName, textAlignment: Alignment.Right, font: GUIStyle.LargeFont); - // submarine class - new GUITextBlock(rectT(1, 0, submarineInfoFrame), $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}"))}", textAlignment: Alignment.Right, font: GUIStyle.Font); + + GUILayoutGroup classLayout = new GUILayoutGroup(rectT(1, 0.15f, submarineInfoFrame), isHorizontal: true) { Stretch = true }; + LocalizedString classText = $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}"))}"; + // submarine class + tier + new GUITextBlock(rectT(0.8f, 1, classLayout), classText, textAlignment: Alignment.Right, font: GUIStyle.Font) + { + ToolTip = TextManager.Get("submarineclass.description") + '\n' + TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}.description") + }; + int tier = submarine.Info.Tier; + string tierStyle = $"SubmarineTier.{tier}"; + if (GUIStyle.GetComponentStyle(tierStyle) != null) + { + LocalizedString tooltip = TextManager.Get("submarinetier.description").Fallback(string.Empty); + new GUIImage(rectT(0.15f, 1, classLayout), style: tierStyle, scaleToFit: false) + { + ToolTip = tooltip + }; + } + var description = new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.Description, textAlignment: Alignment.Right, wrap: true); submarineInfoFrame.RectTransform.ScreenSpaceOffset = new Point(0, (int)(16 * GUI.Scale)); - + description.Padding = new Vector4(description.Padding.X, 24 * GUI.Scale, description.Padding.Z, description.Padding.W); - List pointsOfInterest = (from category in UpgradeCategory.Categories from item in submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs) where category.CanBeApplied(item, null) && item.IsPlayerTeamInteractable select item).Cast().ToList(); + List pointsOfInterest = (from category in UpgradeCategory.Categories from item in submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs) + where category.CanBeApplied(item, null) && item.IsPlayerTeamInteractable select item).Cast().Distinct().ToList(); List ids = GameMain.GameSession.SubmarineInfo?.LeftBehindDockingPortIDs ?? new List(); pointsOfInterest.AddRange(submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs).Where(item => ids.Contains(item.ID))); @@ -1610,6 +1634,7 @@ namespace Barotrauma List textBlocks = buttonParent.GetAllChildren().ToList(); GUITextBlock priceLabel = textBlocks[0]; + priceLabel.Visible = true; int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); if (priceLabel != null && !WaitForServerUpdate) @@ -1648,9 +1673,15 @@ namespace Barotrauma IEnumerable applicableCategories) { // Disables the parent and only re-enables if the submarine contains valid items - if (!category.IsWallUpgrade && drawnSubmarine != null) + if (!category.IsWallUpgrade && drawnSubmarine?.Info != null) { - if (applicableCategories.Contains(category)) + if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0)) + { + parent.ToolTip = TextManager.Get("upgradecategorynotapplicable"); + parent.Enabled = false; + parent.SelectedColor = GUIStyle.Red * 0.5f; + } + else if (applicableCategories.Contains(category)) { parent.Enabled = true; parent.SelectedColor = parent.Style.SelectedColor; @@ -1670,14 +1701,27 @@ namespace Barotrauma { if (component.UserData != prefab) { continue; } + if (prefab.MaxLevel is 0) + { + component.Visible = false; + continue; + } + Dictionary styles = GUIStyle.GetComponentStyle("upgradeindicator").ChildStyles; if (!styles.ContainsKey("upgradeindicatoron") || !styles.ContainsKey("upgradeindicatordim") || !styles.ContainsKey("upgradeindicatoroff")) { continue; } GUIComponentStyle onStyle = styles["upgradeindicatoron".ToIdentifier()]; GUIComponentStyle dimStyle = styles["upgradeindicatordim".ToIdentifier()]; GUIComponentStyle offStyle = styles["upgradeindicatoroff".ToIdentifier()]; + int maxLevel = prefab.MaxLevel; - if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= prefab.MaxLevel) + if (maxLevel == 0) + { + SetOff(); + continue; + } + + if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= maxLevel) { // we check this to avoid flickering from re-applying the same style if (image.Style == onStyle) { continue; } @@ -1690,13 +1734,18 @@ namespace Barotrauma } else { - if (image.Style == offStyle) { continue; } + SetOff(); + } + + void SetOff() + { + if (image.Style == offStyle) { return; } image.ApplyStyle(offStyle); } } } } - + private void ScrollToCategory(Predicate predicate, GUIListBox.PlaySelectSound playSelectSound = GUIListBox.PlaySelectSound.No) { if (currentStoreLayout == null) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/VideoPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/VideoPlayer.cs index 5b596f54d..6152fa2dc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/VideoPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/VideoPlayer.cs @@ -131,9 +131,9 @@ namespace Barotrauma LoadContent(contentPath, videoSettings, textSettings, contentId, startPlayback, new RawLString(""), null); } - public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, Identifier contentId, bool startPlayback, LocalizedString objective, Action callback = null) + public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, Identifier contentId, bool startPlayback, LocalizedString objective, Action onStop = null) { - callbackOnStop = callback; + callbackOnStop = onStop; filePath = contentPath + videoSettings.File; if (!File.Exists(filePath)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index b1dcac1fe..ffd792e44 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -557,6 +557,8 @@ namespace Barotrauma new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError)); } + GameSettings.OnGameMainHasLoaded?.Invoke(); + TitleScreen.LoadState = 100.0f; HasLoaded = true; if (GameSettings.CurrentConfig.VerboseLogging) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 7614a8b8d..01f09f376 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -183,14 +183,13 @@ namespace Barotrauma { chatBox.ToggleButton = new GUIButton(new RectTransform(new Point((int)(182f * GUI.Scale * 0.4f), (int)(99f * GUI.Scale * 0.4f)), chatBox.GUIFrame.Parent.RectTransform), style: "ChatToggleButton") { - ToolTip = TextManager.Get("chat"), + ToolTip = TextManager.GetWithVariable("hudbutton.chatbox", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ChatBox)), ClampMouseRectToParent = false }; chatBox.ToggleButton.RectTransform.AbsoluteOffset = new Point(0, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height); chatBox.ToggleButton.OnClicked += (GUIButton btn, object userdata) => { - chatBox.ToggleOpen = !chatBox.ToggleOpen; - chatBox.CloseAfterMessageSent = false; + chatBox.Toggle(); return true; }; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index a757aa34a..6fff8e5e9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -540,37 +540,37 @@ namespace Barotrauma { System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue); - msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); - msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); + msg.WriteUInt16(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); + msg.WriteUInt16(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); var selectedMissionIndices = map.GetSelectedMissionIndices(); - msg.Write((byte)selectedMissionIndices.Count()); + msg.WriteByte((byte)selectedMissionIndices.Count()); foreach (int selectedMissionIndex in selectedMissionIndices) { - msg.Write((byte)selectedMissionIndex); + msg.WriteByte((byte)selectedMissionIndex); } - msg.Write(PurchasedHullRepairs); - msg.Write(PurchasedItemRepairs); - msg.Write(PurchasedLostShuttles); + msg.WriteBoolean(PurchasedHullRepairs); + msg.WriteBoolean(PurchasedItemRepairs); + msg.WriteBoolean(PurchasedLostShuttles); WriteItems(msg, CargoManager.ItemsInBuyCrate); WriteItems(msg, CargoManager.ItemsInSellFromSubCrate); WriteItems(msg, CargoManager.PurchasedItems); WriteItems(msg, CargoManager.SoldItems); - msg.Write((ushort)UpgradeManager.PurchasedUpgrades.Count); + msg.WriteUInt16((ushort)UpgradeManager.PurchasedUpgrades.Count); foreach (var (prefab, category, level) in UpgradeManager.PurchasedUpgrades) { - msg.Write(prefab.Identifier); - msg.Write(category.Identifier); - msg.Write((byte)level); + msg.WriteIdentifier(prefab.Identifier); + msg.WriteIdentifier(category.Identifier); + msg.WriteByte((byte)level); } - msg.Write((ushort)UpgradeManager.PurchasedItemSwaps.Count); + msg.WriteUInt16((ushort)UpgradeManager.PurchasedItemSwaps.Count); foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps) { - msg.Write(itemSwap.ItemToRemove.ID); - msg.Write(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty); + msg.WriteUInt16(itemSwap.ItemToRemove.ID); + msg.WriteIdentifier(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index 06f1d24df..dc7bc12b1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -1,12 +1,8 @@ -using Barotrauma.Extensions; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using System.Xml.Linq; -using Barotrauma.Networking; namespace Barotrauma { @@ -49,6 +45,31 @@ namespace Barotrauma private bool showCampaignResetText; + public override bool PurchasedHullRepairs + { + get { return PurchasedHullRepairsInLatestSave; } + set + { + PurchasedHullRepairsInLatestSave = value; + } + } + public override bool PurchasedLostShuttles + { + get { return PurchasedLostShuttlesInLatestSave; } + set + { + PurchasedLostShuttlesInLatestSave = value; + } + } + public override bool PurchasedItemRepairs + { + get { return PurchasedItemRepairsInLatestSave; } + set + { + PurchasedItemRepairsInLatestSave = value; + } + } + #region Constructors/initialization /// @@ -214,7 +235,7 @@ namespace Barotrauma base.Start(); CargoManager.CreatePurchasedItems(); UpgradeManager.ApplyUpgrades(); - UpgradeManager.SanityCheckUpgrades(Submarine.MainSub); + UpgradeManager.SanityCheckUpgrades(); if (!savedOnStart) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs deleted file mode 100644 index beb99fcb6..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs +++ /dev/null @@ -1,346 +0,0 @@ -using Barotrauma.Items.Components; -using Barotrauma.Networking; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma.Tutorials -{ - class CaptainTutorial : ScenarioTutorial - { - // Room 1 - private float shakeTimer = 1f; - private float shakeAmount = 20f; - - // Room 2 - private Door captain_firstDoor; - private LightComponent captain_firstDoorLight; - - // Room 3 - private Character captain_medic; - private MotionSensor captain_medicObjectiveSensor; - private Vector2 captain_medicSpawnPos; - private Door tutorial_submarineDoor; - private LightComponent tutorial_submarineDoorLight; - - // Submarine - private MotionSensor captain_enteredSubmarineSensor; - private Steering captain_navConsole; - private Sonar captain_sonar; - private Item captain_statusMonitor; - private Character captain_security; - private Character captain_mechanic; - private Character captain_engineer; - private Reactor tutorial_submarineReactor; - private Door tutorial_lockedDoor_1; - private Door tutorial_lockedDoor_2; - - // Variables - private Character captain; - private LocalizedString radioSpeakerName; - private Sprite captain_steerIcon; - private Color captain_steerIconColor; - - public CaptainTutorial() : base("tutorial.captaintraining".ToIdentifier(), - new Segment( - "Captain.CommandMedic".ToIdentifier(), - "Captain.CommandMedicObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Captain.CommandMedicText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_command.webm", TextTag = "Captain.CommandMedicText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Captain.CommandMechanic".ToIdentifier(), - "Captain.CommandMechanicObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Captain.CommandMechanicText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Captain.CommandSecurity".ToIdentifier(), - "Captain.CommandSecurityObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Captain.CommandSecurityText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Captain.CommandEngineer".ToIdentifier(), - "Captain.CommandEngineerObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Captain.CommandEngineerText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Captain.Undock".ToIdentifier(), - "Captain.UndockObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Captain.UndockText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_undock.webm", TextTag = "Captain.UndockText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Captain.Navigate".ToIdentifier(), - "Captain.NavigateObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Captain.NavigateText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_navigation.webm", TextTag = "Captain.NavigateText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Captain.Dock".ToIdentifier(), - "Captain.DockObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Captain.DockText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_docking.webm", TextTag = "Captain.DockText".ToIdentifier(), Width = 450, Height = 80 })) - { } - - protected override CharacterInfo GetCharacterInfo() - { - return new CharacterInfo( - CharacterPrefab.HumanSpeciesName, - jobOrJobPrefab: new Job( - JobPrefab.Prefabs["captain"], Rand.RandSync.Unsynced, 0, - new Skill("medical".ToIdentifier(), 20), - new Skill("weapons".ToIdentifier(), 20), - new Skill("mechanical".ToIdentifier(), 20), - new Skill("electrical".ToIdentifier(), 20), - new Skill("helm".ToIdentifier(), 70))); - } - - protected override void Initialize() - { - captain = Character.Controlled; - radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman"); - GameMain.GameSession.CrewManager.AllowCharacterSwitch = false; - - foreach (Item item in captain.Inventory.AllItemsMod) - { - if (item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; } - item.Unequip(captain); - captain.Inventory.RemoveItem(item); - } - - var steerOrder = OrderPrefab.Prefabs["steer"]; - captain_steerIcon = steerOrder.SymbolSprite; - captain_steerIconColor = steerOrder.Color; - - // Room 2 - captain_firstDoor = Item.ItemList.Find(i => i.HasTag("captain_firstdoor")).GetComponent(); - captain_firstDoorLight = Item.ItemList.Find(i => i.HasTag("captain_firstdoorlight")).GetComponent(); - - SetDoorAccess(captain_firstDoor, captain_firstDoorLight, true); - - // Room 3 - captain_medicObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_medicobjectivesensor")).GetComponent(); - captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition; - tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); - tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); - var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("medicaldoctor".ToIdentifier())) - { - TeamID = CharacterTeamType.Team1 - }; - captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor"); - captain_medic.GiveJobItems(null); - captain_medic.CanSpeak = captain_medic.AIController.Enabled = false; - SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); - - // Submarine - captain_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("captain_enteredsubmarinesensor")).GetComponent(); - tutorial_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent(); - captain_navConsole = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); - captain_sonar = captain_navConsole.Item.GetComponent(); - captain_statusMonitor = Item.ItemList.Find(i => i.HasTag("captain_statusmonitor")); - - tutorial_submarineReactor.CanBeSelected = false; - tutorial_submarineReactor.IsActive = tutorial_submarineReactor.AutoTemp = false; - - tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent(); - tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent(); - SetDoorAccess(tutorial_lockedDoor_1, null, false); - SetDoorAccess(tutorial_lockedDoor_2, null, false); - - var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("mechanic".ToIdentifier())) - { - TeamID = CharacterTeamType.Team1 - }; - captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "mechanic"); - captain_mechanic.GiveJobItems(); - - var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer".ToIdentifier())) - { - TeamID = CharacterTeamType.Team1 - }; - captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "securityofficer"); - captain_security.GiveJobItems(); - - var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier())) - { - TeamID = CharacterTeamType.Team1 - }; - captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "engineer"); - captain_engineer.GiveJobItems(); - - captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false; - captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = false; - - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Started"); - GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); - } - - public override IEnumerable UpdateState() - { - while (GameMain.Instance.LoadingScreenOpen) yield return null; - - // Room 1 - while (shakeTimer > 0.0f) // Wake up, shake - { - shakeTimer -= 0.1f; - GameMain.GameScreen.Cam.Shake = shakeAmount; - yield return new WaitForSeconds(0.1f, false); - } - - // Room 2 - do { yield return null; } while (!captain_firstDoor.IsOpen); - captain_medic.AIController.Enabled = true; - - // Room 3 - do { yield return null; } while (!captain_medicObjectiveSensor.MotionDetected); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(captain_medic.Info.DisplayName, TextManager.Get("Captain.Radio.Medic"), ChatMessageType.Radio, null); - yield return new WaitForSeconds(2f, false); - GameMain.GameSession.CrewManager.AutoShowCrewList(); - GameMain.GameSession.CrewManager.AddCharacter(captain_medic); - TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); - do - { - yield return null; - // TODO: Rework order highlighting for new command UI - // GameMain.GameSession.CrewManager.HighlightOrderButton(captain_medic, "follow", highlightColor, new Vector2(5, 5)); - } - while (!HasOrder(captain_medic, "follow")); - SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); - RemoveCompletedObjective(0); - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective0"); - - // Submarine - do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected); - yield return new WaitForSeconds(3f, false); - captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = true; - TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); - GameMain.GameSession.CrewManager.AddCharacter(captain_mechanic); - do - { - yield return null; - // TODO: Rework order highlighting for new command UI - // GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5)); - //HighlightOrderOption("jobspecific"); - } while (!HasOrder(captain_mechanic, "repairsystems") && !HasOrder(captain_mechanic, "repairmechanical") && !HasOrder(captain_mechanic, "repairelectrical")); - RemoveCompletedObjective(1); - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective1"); - - yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); - GameMain.GameSession.CrewManager.AddCharacter(captain_security); - do - { - yield return null; - // TODO: Rework order highlighting for new command UI - // GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5)); - HighlightOrderOption("fireatwill"); - } - while (!HasOrder(captain_security, "operateweapons")); - RemoveCompletedObjective(2); - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective2"); - - yield return new WaitForSeconds(4f, false); - TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); - GameMain.GameSession.CrewManager.AddCharacter(captain_engineer); - tutorial_submarineReactor.CanBeSelected = true; - //recreate autonomous objectives to make sure the engineer didn't abandon the operate reactor objective because it was not selectable - (captain_engineer.AIController as HumanAIController).ObjectiveManager.CreateAutonomousObjectives(); - do - { - yield return null; - // TODO: Rework order highlighting for new command UI - // GameMain.GameSession.CrewManager.HighlightOrderButton(captain_engineer, "operatereactor", highlightColor, new Vector2(5, 5)); - HighlightOrderOption("powerup"); - } - while (!HasOrder(captain_engineer, "operatereactor", "powerup")); - RemoveCompletedObjective(3); - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective3"); - - do { yield return null; } while (!tutorial_submarineReactor.IsActive); // Wait until reactor on - TriggerTutorialSegment(4); - while (ContentRunning) yield return null; - captain.AddActiveObjectiveEntity(captain_navConsole.Item, captain_steerIcon, captain_steerIconColor); - SetHighlight(captain_navConsole.Item, true); - SetHighlight(captain_sonar.Item, true); - SetHighlight(captain_statusMonitor, true); - do - { - //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); - yield return new WaitForSeconds(1.0f, false); - } while (Submarine.MainSub.DockedTo.Any()); - captain_navConsole.UseAutoDocking = false; - RemoveCompletedObjective(4); - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective4"); - - yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(5); // Navigate to destination - do - { - if (IsSelectedItem(captain_navConsole.Item)) - { - if (captain_sonar.SonarModeSwitch.Frame.FlashTimer <= 0) - { - captain_sonar.SonarModeSwitch.Frame.Flash(highlightColor, 1.5f, false, false, new Vector2(2.5f, 2.5f)); - } - } - yield return null; - } while (captain_sonar.CurrentMode != Sonar.Mode.Active); - do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f); - RemoveCompletedObjective(5); - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective5"); - - captain_navConsole.UseAutoDocking = true; - yield return new WaitForSeconds(4f, false); - TriggerTutorialSegment(6); // Docking - do - { - //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); - yield return new WaitForSeconds(1.0f, false); - } while (!Submarine.MainSub.AtEndExit || !Submarine.MainSub.DockedTo.Any()); - RemoveCompletedObjective(6); - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective6"); - - yield return new WaitForSeconds(3f, false); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null); - SetHighlight(captain_navConsole.Item, false); - SetHighlight(captain_sonar.Item, false); - SetHighlight(captain_statusMonitor, false); - captain.RemoveActiveObjectiveEntity(captain_navConsole.Item); - - GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Completed"); - CoroutineManager.StartCoroutine(TutorialCompleted()); - } - - private void HighlightOrderOption(string option) - { - if (GameMain.GameSession.CrewManager.OrderOptionButtons.Count == 0) return; - var order = GameMain.GameSession.CrewManager.OrderOptionButtons[0].UserData as Order; - - int orderIndex = 0; - for (int i = 0; i < GameMain.GameSession.CrewManager.OrderOptionButtons.Count; i++) - { - if (orderIndex >= order.Options.Length) - { - orderIndex = 0; - } - if (order.Options[orderIndex] == option) - { - if (GameMain.GameSession.CrewManager.OrderOptionButtons[i].FlashTimer <= 0) - { - GameMain.GameSession.CrewManager.OrderOptionButtons[i].Flash(highlightColor); - } - } - - orderIndex++; - } - } - - private bool IsSelectedItem(Item item) - { - return - captain?.SelectedItem == item || - (captain?.SelectedItem?.linkedTo?.Contains(item) ?? false); - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs deleted file mode 100644 index 442f36b07..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs +++ /dev/null @@ -1,521 +0,0 @@ -using Barotrauma.Items.Components; -using Barotrauma.Networking; -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma.Tutorials -{ - class DoctorTutorial : ScenarioTutorial - { - // Room 1 - private float shakeTimer = 1f; - private float shakeAmount = 20f; - - private LocalizedString radioSpeakerName; - private Character doctor; - - private ItemContainer doctor_suppliesCabinet; - private ItemContainer doctor_medBayCabinet; - private Character patient1, patient2; - private List subPatients; - private Hull medBay; - - private Door doctor_firstDoor; - private Door doctor_secondDoor; - private Door doctor_thirdDoor; - private Door tutorial_upperFinalDoor; - private Door tutorial_lockedDoor_2; - - private LightComponent doctor_firstDoorLight; - private LightComponent doctor_secondDoorLight; - private LightComponent doctor_thirdDoorLight; - private Door tutorial_submarineDoor; - private LightComponent tutorial_submarineDoorLight; - - // Variables - private Sprite doctor_firstAidIcon; - private Color doctor_firstAidIconColor; - - - public DoctorTutorial() : base("tutorial.medicaldoctortraining".ToIdentifier(), - new Segment( - "Doctor.Supplies".ToIdentifier(), - "Doctor.SuppliesObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Doctor.SuppliesText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Doctor.OpenMedicalInterface".ToIdentifier(), - "Doctor.OpenMedicalInterfaceObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Doctor.OpenMedicalInterfaceText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_medinterface1.webm", TextTag = "Doctor.OpenMedicalInterfaceText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Doctor.FirstAidSelf".ToIdentifier(), - "Doctor.FirstAidSelfObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Doctor.FirstAidSelfText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_medinterface1.webm", TextTag = "Doctor.FirstAidSelfText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Doctor.Medbay".ToIdentifier(), - "Doctor.MedbayObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Doctor.MedbayText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_command.webm", TextTag = "Doctor.MedbayText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Doctor.TreatBurns".ToIdentifier(), - "Doctor.TreatBurnsObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Doctor.TreatBurnsText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_medinterface2.webm", TextTag = "Doctor.TreatBurnsText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Doctor.CPR".ToIdentifier(), - "Doctor.CPRObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Doctor.CPRText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_cpr.webm", TextTag = "Doctor.CPRText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Doctor.Submarine".ToIdentifier(), - "Doctor.SubmarineObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Doctor.SubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center })) - { } - - protected override CharacterInfo GetCharacterInfo() - { - return new CharacterInfo( - CharacterPrefab.HumanSpeciesName, - jobOrJobPrefab: new Job( - JobPrefab.Prefabs["medicaldoctor"], Rand.RandSync.Unsynced, 0, - new Skill("medical".ToIdentifier(), 70), - new Skill("weapons".ToIdentifier(), 20), - new Skill("mechanical".ToIdentifier(), 20), - new Skill("electrical".ToIdentifier(), 20), - new Skill("helm".ToIdentifier(), 20))); - } - - protected override void Initialize() - { - var firstAidOrder = OrderPrefab.Prefabs["requestfirstaid"]; - doctor_firstAidIcon = firstAidOrder.SymbolSprite; - doctor_firstAidIconColor = firstAidOrder.Color; - - subPatients = new List(); - radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); - doctor = Character.Controlled; - - foreach (Item item in doctor.Inventory.AllItemsMod) - { - if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; } - item.Unequip(doctor); - doctor.Inventory.RemoveItem(item); - } - - doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent(); - doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent(); - - var patientHull1 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "waitingroom").CurrentHull; - var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull; - medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull; - - var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant".ToIdentifier())) - { - TeamID = CharacterTeamType.Team1 - }; - patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1"); - patient1.GiveJobItems(null); - patient1.CanSpeak = false; - patient1.Params.Health.BurnReduction = 0; - patient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 15.0f) }, stun: 0, playSound: false); - patient1.AIController.Enabled = false; - - assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant".ToIdentifier())) - { - TeamID = CharacterTeamType.Team1 - }; - patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2"); - patient2.GiveJobItems(null); - patient2.CanSpeak = false; - patient2.AIController.Enabled = false; - - var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier())) - { - TeamID = CharacterTeamType.Team1 - }; - var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3"); - subPatient1.Params.Health.BurnReduction = 0; - subPatient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); - subPatients.Add(subPatient1); - - var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer".ToIdentifier())); - var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3"); - subPatient2.TeamID = CharacterTeamType.Team1; - subPatient2.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); - subPatients.Add(subPatient2); - - var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier())) - { - TeamID = CharacterTeamType.Team1 - }; - var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3"); - subPatient3.Params.Health.BurnReduction = 0; - subPatient3.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); - subPatients.Add(subPatient3); - - doctor_firstDoor = Item.ItemList.Find(i => i.HasTag("doctor_firstdoor")).GetComponent(); - doctor_secondDoor = Item.ItemList.Find(i => i.HasTag("doctor_seconddoor")).GetComponent(); - doctor_thirdDoor = Item.ItemList.Find(i => i.HasTag("doctor_thirddoor")).GetComponent(); - tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent(); - doctor_firstDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_firstdoorlight")).GetComponent(); - doctor_secondDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_seconddoorlight")).GetComponent(); - doctor_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_thirddoorlight")).GetComponent(); - SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, false); - SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, false); - SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, false); - tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); - tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); - SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); - tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent(); - SetDoorAccess(tutorial_lockedDoor_2, null, true); - - - foreach (var patient in subPatients) - { - patient.CanSpeak = false; - patient.AIController.Enabled = false; - patient.GiveJobItems(); - } - - Item reactorItem = Item.ItemList.Find(i => i.Submarine == Submarine.MainSub && i.GetComponent() != null); - reactorItem.GetComponent().AutoTemp = true; - - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Started"); - GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); - } - - public override IEnumerable UpdateState() - { - while (GameMain.Instance.LoadingScreenOpen) yield return null; - - // explosions and radio messages ------------------------------------------------------ - - yield return new WaitForSeconds(3.0f, false); - - //SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition); - //// Room 1 - //while (shakeTimer > 0.0f) // Wake up, shake - //{ - // shakeTimer -= 0.1f; - // GameMain.GameScreen.Cam.Shake = shakeAmount; - // yield return new WaitForSeconds(0.1f); - //} - //yield return new WaitForSeconds(2.5f); - //GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null); - - //yield return new WaitForSeconds(2.5f); - - doctor.SetStun(1.5f); - var explosion = new Explosion(range: 100, force: 10, damage: 0, structureDamage: 0, itemDamage: 0); - explosion.DisableParticles(); - GameMain.GameScreen.Cam.Shake = shakeAmount; - explosion.Explode(Character.Controlled.WorldPosition - Vector2.UnitX * 25, null); - SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition - Vector2.UnitX * 25); - - yield return new WaitForSeconds(0.5f, false); - - doctor.DamageLimb( - Character.Controlled.WorldPosition, - doctor.AnimController.GetLimb(LimbType.Torso), - new List { new Affliction(AfflictionPrefab.InternalDamage, 10.0f) }, - stun: 3.0f, playSound: true, attackImpulse: 0.0f); - - shakeTimer = 0.5f; - while (shakeTimer > 0.0f) // Wake up, shake - { - shakeTimer -= 0.1f; - GameMain.GameScreen.Cam.Shake = shakeAmount; - yield return new WaitForSeconds(0.1f, false); - } - - yield return new WaitForSeconds(3.0f, false); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.KnockedDown"), ChatMessageType.Radio, null); - - // first tutorial segment, get medical supplies ------------------------------------------------------ - - yield return new WaitForSeconds(1.5f, false); - SetHighlight(doctor_suppliesCabinet.Item, true); - - /*while (doctor.CurrentHull != doctor_suppliesCabinet.Item.CurrentHull) - { - yield return new WaitForSeconds(2.0f); - }*/ - - TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), "None"); // Medical supplies objective - - do - { - for (int i = 0; i < doctor_suppliesCabinet.Inventory.Capacity; i++) - { - if (doctor_suppliesCabinet.Inventory.GetItemAt(i) != null) - { - HighlightInventorySlot(doctor_suppliesCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); - } - } - if (doctor.SelectedItem == doctor_suppliesCabinet.Item) - { - for (int i = 0; i < doctor.Inventory.Capacity; i++) - { - if (doctor.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); } - } - } - yield return null; - } while (doctor.Inventory.FindItemByIdentifier("antidama1".ToIdentifier()) == null); // Wait until looted - yield return new WaitForSeconds(1.0f, false); - - SetHighlight(doctor_suppliesCabinet.Item, false); - RemoveCompletedObjective(0); - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective0"); - - yield return new WaitForSeconds(1.0f, false); - - // 2nd tutorial segment, treat self ------------------------------------------------------------------------- - - TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // Open health interface - while (CharacterHealth.OpenHealthWindow == null) - { - doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f; - yield return null; - } - yield return null; - RemoveCompletedObjective(1); - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective1"); - yield return new WaitForSeconds(1.0f, false); - TriggerTutorialSegment(2); //Treat self - while (doctor.CharacterHealth.GetAfflictionStrength("damage") > 0.01f) - { - if (CharacterHealth.OpenHealthWindow == null) - { - doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f; - } - else - { - HighlightInventorySlot(doctor.Inventory, "antidama1".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - - yield return null; - } - - RemoveCompletedObjective(2); - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective2"); - SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, true); - - while (CharacterHealth.OpenHealthWindow != null) - { - yield return new WaitForSeconds(1.0f, false); - } - - // treat patient -------------------------------------------------------------------------------------------- - - //patient 1 requests first aid - var newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], patient1.CurrentHull, null, orderGiver: patient1); - doctor.AddActiveObjectiveEntity(patient1, doctor_firstAidIcon, doctor_firstAidIconColor); - //GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(patient1.Name, newOrder.GetChatMessage("", patient1.CurrentHull?.DisplayName?.Value, givingOrderToSelf: false), ChatMessageType.Order, null); - - while (doctor.CurrentHull != patient1.CurrentHull) - { - yield return new WaitForSeconds(1.0f, false); - } - yield return new WaitForSeconds(0.0f, false); - - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurns"), ChatMessageType.Radio, null); - GameMain.GameSession.CrewManager.AllowCharacterSwitch = false; - GameMain.GameSession.CrewManager.AddCharacter(doctor); - GameMain.GameSession.CrewManager.AddCharacter(patient1); - GameMain.GameSession.CrewManager.AutoShowCrewList(); - patient1.CharacterHealth.UseHealthWindow = false; - - yield return new WaitForSeconds(3.0f, false); - patient1.AIController.Enabled = true; - doctor.RemoveActiveObjectiveEntity(patient1); - TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); // Get the patient to medbay - - while (patient1.GetCurrentOrderWithTopPriority()?.Identifier != "follow") - { - // TODO: Rework order highlighting for new command UI - // GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5)); - yield return null; - } - - SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, true); - - while (patient1.CurrentHull != medBay) - { - yield return new WaitForSeconds(1.0f, false); - } - RemoveCompletedObjective(3); - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective3"); - SetHighlight(doctor_medBayCabinet.Item, true); - SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, true); - patient1.CharacterHealth.UseHealthWindow = true; - - yield return new WaitForSeconds(2.0f, false); - - TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // treat burns - - do - { - for (int i = 0; i < 3; i++) - { - if (doctor_medBayCabinet.Inventory.GetItemAt(i) != null) - { - HighlightInventorySlot(doctor_medBayCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); - } - } - if (doctor.SelectedItem == doctor_medBayCabinet.Item) - { - for (int i = 0; i < doctor.Inventory.Capacity; i++) - { - if (doctor.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); } - } - } - yield return null; - } while (doctor.Inventory.FindItemByIdentifier("antibleeding1".ToIdentifier()) == null); // Wait until looted - SetHighlight(doctor_medBayCabinet.Item, false); - SetHighlight(patient1, true); - - while (patient1.CharacterHealth.GetAfflictionStrength("burn") > 0.01f) - { - if (CharacterHealth.OpenHealthWindow == null) - { - doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f; - } - else - { - HighlightInventorySlot(doctor.Inventory, "antibleeding1".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - yield return null; - - } - RemoveCompletedObjective(4); - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective4"); - SetHighlight(patient1, false); - yield return new WaitForSeconds(1.0f, false); - - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurnsHealed"), ChatMessageType.Radio, null); - - // treat unconscious patient ------------------------------------------------------ - - //patient calls for help - //patient2.CanSpeak = true; - yield return new WaitForSeconds(2.0f, false); - newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], patient2.CurrentHull, null, orderGiver: patient2); - doctor.AddActiveObjectiveEntity(patient2, doctor_firstAidIcon, doctor_firstAidIconColor); - //GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(patient2.Name, newOrder.GetChatMessage("", patient1.CurrentHull?.DisplayName?.Value, givingOrderToSelf: false), ChatMessageType.Order, null); - patient2.AIController.Enabled = true; - patient2.Oxygen = -50; - CoroutineManager.StartCoroutine(KeepPatientAlive(patient2), "KeepPatient2Alive"); - - /*while (doctor.CurrentHull != patient2.CurrentHull) - { - yield return new WaitForSeconds(1.0f); - }*/ - do { yield return null; } while (!tutorial_upperFinalDoor.IsOpen); - yield return new WaitForSeconds(2.0f, false); - - TriggerTutorialSegment(5, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // perform CPR - SetHighlight(patient2, true); - while (patient2.IsUnconscious) - { - if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR) - { - //Disabled pulse until it's replaced by a better effect - //CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f); - if (CharacterHealth.OpenHealthWindow.CPRButton.FlashTimer <= 0.0f) - { - CharacterHealth.OpenHealthWindow.CPRButton.Flash(highlightColor); - } - } - yield return null; - } - RemoveCompletedObjective(5); - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective5"); - SetHighlight(patient2, false); - doctor.RemoveActiveObjectiveEntity(patient2); - CoroutineManager.StopCoroutines("KeepPatient2Alive"); - - SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); - - while (doctor.Submarine != Submarine.MainSub) - { - yield return new WaitForSeconds(1.0f, false); - } - - subPatients[2].Oxygen = -50; - CoroutineManager.StartCoroutine(KeepPatientAlive(subPatients[2]), "KeepPatient3Alive"); - - yield return new WaitForSeconds(5.0f, false); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null); - - yield return new WaitForSeconds(3.0f, false); - TriggerTutorialSegment(6, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // give treatment to anyone in need - - foreach (var patient in subPatients) - { - //patient.CanSpeak = true; - patient.AIController.Enabled = true; - SetHighlight(patient, true); - } - - double subEnterTime = Timing.TotalTime; - - bool[] patientCalledHelp = new bool[] { false, false, false }; - - while (subPatients.Any(p => p.Vitality < p.MaxVitality * 0.9f && !p.IsDead)) - { - for (int i = 0; i < subPatients.Count; i++) - { - //make patients call for help to make sure the player finds them - //(within 1 minute intervals of entering the sub) - if (!patientCalledHelp[i] && Timing.TotalTime > subEnterTime + 60 * (i + 1)) - { - doctor.AddActiveObjectiveEntity(subPatients[i], doctor_firstAidIcon, doctor_firstAidIconColor); - newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], subPatients[i].CurrentHull, null, orderGiver: subPatients[i]); - string message = newOrder.GetChatMessage("", subPatients[i].CurrentHull?.DisplayName?.Value, givingOrderToSelf: false); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(subPatients[i].Name, message, ChatMessageType.Order, null); - patientCalledHelp[i] = true; - } - - if (subPatients[i].ExternalHighlight && subPatients[i].Vitality >= subPatients[i].MaxVitality * 0.9f) - { - doctor.RemoveActiveObjectiveEntity(subPatients[i]); - SetHighlight(subPatients[i], false); - } - } - yield return new WaitForSeconds(1.0f, false); - } - RemoveCompletedObjective(6); - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective6"); - foreach (var patient in subPatients) - { - SetHighlight(patient, false); - doctor.RemoveActiveObjectiveEntity(patient); - } - - // END TUTORIAL - GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Completed"); - CoroutineManager.StartCoroutine(TutorialCompleted()); - } - - public IEnumerable KeepPatientAlive(Character patient) - { - while (patient != null && !patient.Removed) - { - patient.Oxygen = Math.Max(patient.Oxygen, -50); - yield return null; - } - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs deleted file mode 100644 index 3e30b7672..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs +++ /dev/null @@ -1,664 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using Barotrauma.Items.Components; -using Barotrauma.Networking; -using Microsoft.Xna.Framework; - -namespace Barotrauma.Tutorials -{ - class EngineerTutorial : ScenarioTutorial - { - // Other tutorial items - private LightComponent tutorial_securityFinalDoorLight; - private LightComponent tutorial_mechanicFinalDoorLight; - private Steering tutorial_submarineSteering; - - // Room 1 - private float shakeTimer = 1f; - private float shakeAmount = 20f; - - // Room 2 - private MotionSensor engineer_equipmentObjectiveSensor; - private ItemContainer engineer_equipmentCabinet; - private Door engineer_firstDoor; - private LightComponent engineer_firstDoorLight; - - // Room 3 - private Powered tutorial_oxygenGenerator; - private Reactor engineer_reactor; - private Door engineer_secondDoor; - private LightComponent engineer_secondDoorLight; - - // Room 4 - private Item engineer_brokenJunctionBox; - private Door engineer_thirdDoor; - private LightComponent engineer_thirdDoorLight; - - // Room 5 - private PowerTransfer[] engineer_disconnectedJunctionBoxes; - private ConnectionPanel[] engineer_disconnectedConnectionPanels; - private Item engineer_wire_1; - private Powered engineer_lamp_1; - private Item engineer_wire_2; - private Powered engineer_lamp_2; - private Door engineer_fourthDoor; - private LightComponent engineer_fourthDoorLight; - - // Room 6 - private Pump engineer_workingPump; - private Door tutorial_lockedDoor_1; - - // Submarine - private Door tutorial_submarineDoor; - private LightComponent tutorial_submarineDoorLight; - private MotionSensor tutorial_enteredSubmarineSensor; - private Item engineer_submarineJunctionBox_1; - private Item engineer_submarineJunctionBox_2; - private Item engineer_submarineJunctionBox_3; - private Reactor engineer_submarineReactor; - - // Variables - private LocalizedString radioSpeakerName; - private Character engineer; - private int[] reactorLoads = new int[5] { 1500, 3000, 2000, 5000, 3500 }; - private float reactorLoadChangeTime = 2f; - private float reactorLoadError = 200f; - private bool reactorOperatedProperly; - private const float waterVolumeBeforeOpening = 15f; - private Sprite engineer_repairIcon; - private Color engineer_repairIconColor; - private Sprite engineer_reactorIcon; - private Color engineer_reactorIconColor; - private bool wiringActive = false; - - public EngineerTutorial() : base("tutorial.engineertraining".ToIdentifier(), - new Segment( - "Mechanic.Equipment".ToIdentifier(), - "Mechanic.EquipmentObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Engineer.Reactor".ToIdentifier(), - "Engineer.ReactorObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_reactor.webm", TextTag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80 }), - new Segment( - "Engineer.OperateReactor".ToIdentifier(), - "Engineer.OperateReactorObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Engineer.OperateReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_reactor.webm", TextTag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80 }), - new Segment( - "Engineer.RepairJunctionBox".ToIdentifier(), - "Engineer.RepairJunctionBoxObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Engineer.RepairJunctionBoxText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Engineer.WireJunctionBoxes".ToIdentifier(), - "Engineer.WireJunctionBoxesObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Engineer.WireJunctionBoxesText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_wiring.webm", TextTag = "Engineer.WireJunctionBoxesText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Engineer.RepairElectricalRoom".ToIdentifier(), - "Engineer.RepairElectricalRoomObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Engineer.RepairElectricalRoomText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Engineer.PowerUpReactor".ToIdentifier(), - "Engineer.PowerUpReactorObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Engineer.PowerUpReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center })) - { } - - protected override CharacterInfo GetCharacterInfo() - { - return new CharacterInfo( - CharacterPrefab.HumanSpeciesName, - jobOrJobPrefab: new Job( - JobPrefab.Prefabs["engineer"], Rand.RandSync.Unsynced, 0, - new Skill("medical".ToIdentifier(), 0), - new Skill("weapons".ToIdentifier(), 0), - new Skill("mechanical".ToIdentifier(), 20), - new Skill("electrical".ToIdentifier(), 60), - new Skill("helm".ToIdentifier(), 0))); - } - - protected override void Initialize() - { - radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); - engineer = Character.Controlled; - - foreach (Item item in engineer.Inventory.AllItemsMod) - { - if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; } - item.Unequip(engineer); - engineer.Inventory.RemoveItem(item); - } - - var repairOrder = OrderPrefab.Prefabs["repairsystems"]; - engineer_repairIcon = repairOrder.SymbolSprite; - engineer_repairIconColor = repairOrder.Color; - - var reactorOrder = OrderPrefab.Prefabs["operatereactor"]; - engineer_reactorIcon = reactorOrder.SymbolSprite; - engineer_reactorIconColor = reactorOrder.Color; - - // Other tutorial items - tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); - tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); - tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); - - tutorial_submarineSteering.CanBeSelected = false; - foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components) - { - ic.CanBeSelected = false; - } - - SetDoorAccess(null, tutorial_securityFinalDoorLight, false); - SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false); - - // Room 2 - engineer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_equipmentobjectivesensor")).GetComponent(); - engineer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("engineer_equipmentcabinet")).GetComponent(); - engineer_firstDoor = Item.ItemList.Find(i => i.HasTag("engineer_firstdoor")).GetComponent(); - engineer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_firstdoorlight")).GetComponent(); - - SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, false); - - // Room 3 - tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent(); - engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent(); - engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity; - engineer_reactor.FuelConsumptionRate = 0.0f; - engineer_reactor.PowerOn = true; - reactorOperatedProperly = false; - - engineer_secondDoor = Item.ItemList.Find(i => i.HasTag("engineer_seconddoor")).GetComponent(); ; - engineer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_seconddoorlight")).GetComponent(); - - SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, false); - - // Room 4 - engineer_brokenJunctionBox = Item.ItemList.Find(i => i.HasTag("engineer_brokenjunctionbox")); - engineer_thirdDoor = Item.ItemList.Find(i => i.HasTag("engineer_thirddoor")).GetComponent(); - engineer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_thirddoorlight")).GetComponent(); - - engineer_brokenJunctionBox.Indestructible = false; - engineer_brokenJunctionBox.Condition = 0f; - - SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, false); - - // Room 5 - engineer_disconnectedJunctionBoxes = new PowerTransfer[4]; - engineer_disconnectedConnectionPanels = new ConnectionPanel[4]; - - for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) - { - engineer_disconnectedJunctionBoxes[i] = Item.ItemList.Find(item => item.HasTag($"engineer_disconnectedjunctionbox_{i + 1}")).GetComponent(); - engineer_disconnectedConnectionPanels[i] = engineer_disconnectedJunctionBoxes[i].Item.GetComponent(); - engineer_disconnectedConnectionPanels[i].Locked = false; - - for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++) - { - foreach (Wire wire in engineer_disconnectedJunctionBoxes[i].PowerConnections[j].Wires) - { - if (wire == null) continue; - wire.Locked = true; - } - } - } - - engineer_wire_1 = Item.ItemList.Find(i => i.HasTag("engineer_wire_1")); - engineer_wire_1.SpriteColor = Color.Transparent; - engineer_wire_2 = Item.ItemList.Find(i => i.HasTag("engineer_wire_2")); - engineer_wire_2.SpriteColor = Color.Transparent; - engineer_lamp_1 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_1")).GetComponent(); - engineer_lamp_2 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_2")).GetComponent(); - engineer_fourthDoor = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoor")).GetComponent(); - engineer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoorlight")).GetComponent(); - SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, false); - - // Room 6 - engineer_workingPump = Item.ItemList.Find(i => i.HasTag("engineer_workingpump")).GetComponent(); - engineer_workingPump.Item.CurrentHull.WaterVolume += engineer_workingPump.Item.CurrentHull.Volume; - engineer_workingPump.IsActive = true; - tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent(); - SetDoorAccess(tutorial_lockedDoor_1, null, true); - - // Submarine - tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); - tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); - SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); - - tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent(); - engineer_submarineJunctionBox_1 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_1")); - engineer_submarineJunctionBox_2 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_2")); - engineer_submarineJunctionBox_3 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_3")); - engineer_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent(); - engineer_submarineReactor.PowerOn = true; - engineer_submarineReactor.IsActive = engineer_submarineReactor.AutoTemp = false; - - engineer_submarineJunctionBox_1.Indestructible = false; - engineer_submarineJunctionBox_1.Condition = 0f; - engineer_submarineJunctionBox_2.Indestructible = false; - engineer_submarineJunctionBox_2.Condition = 0f; - engineer_submarineJunctionBox_3.Indestructible = false; - engineer_submarineJunctionBox_3.Condition = 0f; - - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Started"); - GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); - } - - public override IEnumerable UpdateState() - { - while (GameMain.Instance.LoadingScreenOpen) { yield return null; } - - // Room 1 - SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition); - while (shakeTimer > 0.0f) // Wake up, shake - { - shakeTimer -= 0.1f; - GameMain.GameScreen.Cam.Shake = shakeAmount; - yield return new WaitForSeconds(0.1f, false); - } - - //// Remove - //for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) - //{ - // SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true); - //} - //do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump - //CheckGhostWires(); - //// Remove - - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.WakeUp"), ChatMessageType.Radio, null); - SetHighlight(engineer_equipmentCabinet.Item, true); - - // Room 2 - do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null); - yield return new WaitForSeconds(0.5f, false); - TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Retrieve equipment - bool firstSlotRemoved = false; - bool secondSlotRemoved = false; - bool thirdSlotRemoved = false; - bool fourthSlotRemoved = false; - do - { - if (IsSelectedItem(engineer_equipmentCabinet.Item)) - { - if (!firstSlotRemoved) - { - HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f); - if (engineer_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; } - } - - if (!secondSlotRemoved) - { - HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); - if (engineer_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; } - } - - if (!thirdSlotRemoved) - { - HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); - if (engineer_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; } - } - - if (!fourthSlotRemoved) - { - HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 3, highlightColor, .5f, .5f, 0f); - if (engineer_equipmentCabinet.Inventory.GetItemAt(2) == null) { fourthSlotRemoved = true; } - } - - for (int i = 0; i < engineer.Inventory.visualSlots.Length; i++) - { - if (engineer.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(engineer.Inventory, i, highlightColor, .5f, .5f, 0f); } - } - } - - yield return null; - } while (!engineer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted - RemoveCompletedObjective(0); - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective0"); - SetHighlight(engineer_equipmentCabinet.Item, false); - SetHighlight(engineer_reactor.Item, true); - SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, true); - - // Room 3 - do { yield return null; } while (!IsSelectedItem(engineer_reactor.Item)); - yield return new WaitForSeconds(0.5f, false); - TriggerTutorialSegment(1); - do - { - if (IsSelectedItem(engineer_reactor.Item)) - { - engineer_reactor.AutoTemp = false; - if (engineer_reactor.PowerButton.FlashTimer <= 0) - { - engineer_reactor.PowerButton.Flash(highlightColor, 1.5f, false); - } - } - yield return null; - } while (!engineer_reactor.PowerOn); - do - { - if (IsSelectedItem(engineer_reactor.Item) && engineer_reactor.Item.OwnInventory.visualSlots != null) - { - engineer_reactor.AutoTemp = false; - HighlightInventorySlot(engineer.Inventory, "fuelrod".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f); - - for (int i = 0; i < engineer_reactor.Item.OwnInventory.visualSlots.Length; i++) - { - HighlightInventorySlot(engineer_reactor.Item.OwnInventory, i, highlightColor, 0.5f, 0.5f, 0f); - } - } - yield return null; - } while (engineer_reactor.AvailableFuel == 0); - RemoveCompletedObjective(1); - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective1"); - TriggerTutorialSegment(2); - CoroutineManager.StartCoroutine(ReactorOperatedProperly()); - do - { - if (IsSelectedItem(engineer_reactor.Item)) - { - engineer_reactor.AutoTemp = false; - if (engineer_reactor.FissionRateScrollBar.FlashTimer <= 0) - { - engineer_reactor.FissionRateScrollBar.Flash(highlightColor, 1.5f); - } - - if (engineer_reactor.TurbineOutputScrollBar.FlashTimer <= 0) - { - engineer_reactor.TurbineOutputScrollBar.Flash(highlightColor, 1.5f); - } - } - yield return null; - } while (!reactorOperatedProperly); - yield return new WaitForSeconds(2f, false); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ReactorStable"), ChatMessageType.Radio, null); - do - { - if (IsSelectedItem(engineer_reactor.Item)) - { - if (engineer_reactor.AutoTempSwitch.FlashTimer <= 0) - { - engineer_reactor.AutoTempSwitch.Flash(highlightColor, 1.5f, false, false, new Vector2(10, 10)); - } - } - yield return null; - } while (!engineer_reactor.AutoTemp); - - float wait = 1.5f; - do - { - yield return new WaitForSeconds(0.1f, false); - wait -= 0.1f; - engineer_reactor.AutoTemp = true; - } while (wait > 0.0f); - engineer.SelectedItem = null; - engineer_reactor.CanBeSelected = false; - RemoveCompletedObjective(2); - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective2"); - SetHighlight(engineer_reactor.Item, false); - SetHighlight(engineer_brokenJunctionBox, true); - SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, true); - - // Room 4 - do { yield return null; } while (!engineer_secondDoor.IsOpen); - yield return new WaitForSeconds(1f, false); - Repairable repairableJunctionBoxComponent = engineer_brokenJunctionBox.GetComponent(); - TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Repair the junction box - do - { - if (!engineer.HasEquippedItem("screwdriver".ToIdentifier())) - { - HighlightInventorySlot(engineer.Inventory, "screwdriver".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - else if (IsSelectedItem(engineer_brokenJunctionBox) && repairableJunctionBoxComponent.CurrentFixer == null) - { - if (repairableJunctionBoxComponent.RepairButton.FlashTimer <= 0) - { - repairableJunctionBoxComponent.RepairButton.Flash(); - } - } - yield return null; - } while (repairableJunctionBoxComponent.IsBelowRepairThreshold); // Wait until repaired - SetHighlight(engineer_brokenJunctionBox, false); - RemoveCompletedObjective(3); - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective3"); - SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true); - for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) - { - SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true); - } - - // Room 5 - do { yield return null; } while (!engineer_thirdDoor.IsOpen); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null); - yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Connect the junction boxes - do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump - CheckGhostWires(); - for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) - { - SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false); - } - RemoveCompletedObjective(4); - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective4"); - do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained - wiringActive = false; - SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ChangeOfPlans"), ChatMessageType.Radio, null); - - // Submarine - do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Submarine"), ChatMessageType.Radio, null); - yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(5); // Repair junction box - while (ContentRunning) yield return null; - engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor); - engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor); - engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_3, engineer_repairIcon, engineer_repairIconColor); - SetHighlight(engineer_submarineJunctionBox_1, true); - SetHighlight(engineer_submarineJunctionBox_2, true); - SetHighlight(engineer_submarineJunctionBox_3, true); - - Repairable repairableJunctionBoxComponent1 = engineer_submarineJunctionBox_1.GetComponent(); - Repairable repairableJunctionBoxComponent2 = engineer_submarineJunctionBox_2.GetComponent(); - Repairable repairableJunctionBoxComponent3 = engineer_submarineJunctionBox_3.GetComponent(); - - // Remove highlights when each individual machine is repaired - do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (repairableJunctionBoxComponent1.IsBelowRepairThreshold || repairableJunctionBoxComponent2.IsBelowRepairThreshold || repairableJunctionBoxComponent3.IsBelowRepairThreshold); - CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); - RemoveCompletedObjective(5); - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective5"); - yield return new WaitForSeconds(2f, false); - - TriggerTutorialSegment(6); // Powerup reactor - SetHighlight(engineer_submarineReactor.Item, true); - engineer.AddActiveObjectiveEntity(engineer_submarineReactor.Item, engineer_reactorIcon, engineer_reactorIconColor); - do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load - engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item); - SetHighlight(engineer_submarineReactor.Item, false); - RemoveCompletedObjective(6); - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective6"); - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null); - - yield return new WaitForSeconds(4f, false); - - GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Completed"); - CoroutineManager.StartCoroutine(TutorialCompleted()); - } - - public override void Update(float deltaTime) - { - base.Update(deltaTime); - - if (wiringActive) - { - for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) - { - for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++) - { - engineer_disconnectedJunctionBoxes[i].PowerConnections[j].UpdateFlashTimer(deltaTime); - } - } - } - } - - private bool IsSelectedItem(Item item) - { - return engineer?.SelectedItem == item; - } - - private IEnumerable ReactorOperatedProperly() - { - float timer; - - for (int i = 0; i < reactorLoads.Length; i++) - { - timer = reactorLoadChangeTime; - tutorial_oxygenGenerator.PowerConsumption = reactorLoads[i]; - while (timer > 0) - { - yield return CoroutineStatus.Running; - if (CoroutineManager.DeltaTime > 0.0f && IsReactorPoweredUp(engineer_reactor)) - { - timer -= CoroutineManager.DeltaTime; - } - } - } - - reactorOperatedProperly = true; - } - - private void CheckGhostWires() - { - Color wireColor = - Color.Orange * - MathHelper.Lerp(0.25f, 0.75f, (float)(Math.Sin((Timing.TotalTime * 4.0f)) + 1.0f) / 2.0f); - - if (engineer_wire_1 != null) - { - engineer_wire_1.SpriteColor = wireColor; - if (engineer_lamp_1.Voltage > engineer_lamp_1.MinVoltage) - { - engineer_wire_1.Remove(); - engineer_wire_1 = null; - } - } - - - if (engineer_wire_2 != null) - { - engineer_wire_2.SpriteColor = wireColor; - if (engineer_lamp_2.Voltage > engineer_lamp_2.MinVoltage) - { - engineer_wire_2.Remove(); - engineer_wire_2 = null; - } - } - - } - - private void HandleJunctionBoxWiringHighlights() - { - Item selected = engineer.SelectedItem; - - if (!engineer.HasEquippedItem("screwdriver".ToIdentifier())) - { - HighlightInventorySlot(engineer.Inventory, "screwdriver".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f); - } - - int selectedIndex = -1; - - if (selected != null) - { - for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) - { - if (selected == engineer_disconnectedJunctionBoxes[i].Item) - { - selectedIndex = i; - break; - } - } - } - - wiringActive = selectedIndex != -1; - - if (!engineer.HasEquippedItem("wire".ToIdentifier())) - { - HighlightInventorySlotWithTag(engineer.Inventory, "wire".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f); - } - else - { - if (!wiringActive) return; - for (int i = 0; i < engineer_disconnectedConnectionPanels[selectedIndex].Connections.Count; i++) - { - var connection = engineer_disconnectedConnectionPanels[selectedIndex].Connections[i]; - if (connection.IsPower && connection.FlashTimer <= 0) - { - foreach (Wire wire in engineer_disconnectedConnectionPanels[selectedIndex].Connections[i].Wires) - { - if (wire == null) continue; - if (!wire.Locked) - { - return; - } - } - - connection.Flash(highlightColor); - } - } - } - } - - private void CheckJunctionBoxHighlights(Repairable comp1, Repairable comp2, Repairable comp3) - { - if (!comp1.IsBelowRepairThreshold && engineer_submarineJunctionBox_1.ExternalHighlight) - { - SetHighlight(engineer_submarineJunctionBox_1, false); - engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1); - } - if (!comp2.IsBelowRepairThreshold && engineer_submarineJunctionBox_2.ExternalHighlight) - { - SetHighlight(engineer_submarineJunctionBox_2, false); - engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2); - } - if (!comp3.IsBelowRepairThreshold && engineer_submarineJunctionBox_3.ExternalHighlight) - { - SetHighlight(engineer_submarineJunctionBox_3, false); - engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3); - } - } - - private bool IsReactorPoweredUp(Reactor reactor) - { - float load = 0.0f; - List connections = reactor.Item.Connections; - if (connections != null && connections.Count > 0) - { - foreach (Connection connection in connections) - { - if (!connection.IsPower) continue; - foreach (Connection recipient in connection.Recipients) - { - if (!(recipient.Item is Item it)) continue; - - PowerTransfer pt = it.GetComponent(); - if (pt == null) continue; - - load = Math.Max(load, pt.PowerLoad); - } - } - } - - return Math.Abs(load + reactor.CurrPowerConsumption) < reactorLoadError; - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs deleted file mode 100644 index e96dfc137..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs +++ /dev/null @@ -1,737 +0,0 @@ -using System.Collections.Generic; -using System.Xml.Linq; -using Barotrauma.Items.Components; -using Barotrauma.Networking; -using Microsoft.Xna.Framework; - -namespace Barotrauma.Tutorials -{ - class MechanicTutorial : ScenarioTutorial - { - // Other tutorial items - private LightComponent tutorial_securityFinalDoorLight; - private Door tutorial_upperFinalDoor; - private Steering tutorial_submarineSteering; - - // Room 1 - private float shakeTimer = 1f; - private float shakeAmount = 20f; - private Door mechanic_firstDoor; - private LightComponent mechanic_firstDoorLight; - - // Room 2 - private MotionSensor mechanic_equipmentObjectiveSensor; - private ItemContainer mechanic_equipmentCabinet; - private Door mechanic_secondDoor; - private LightComponent mechanic_secondDoorLight; - - // Room 3 - private MotionSensor mechanic_weldingObjectiveSensor; - private Pump mechanic_workingPump; - private Door mechanic_thirdDoor; - private LightComponent mechanic_thirdDoorLight; - private Structure mechanic_brokenWall_1; - private Hull mechanic_brokenhull_1; - - // Room 4 - private MotionSensor mechanic_craftingObjectiveSensor; - private Deconstructor mechanic_deconstructor; - private Fabricator mechanic_fabricator; - private ItemContainer mechanic_craftingCabinet; - private Door mechanic_fourthDoor; - private LightComponent mechanic_fourthDoorLight; - - // Room 5 - private MotionSensor mechanic_fireSensor; - private DummyFireSource mechanic_fire; - private Door mechanic_fifthDoor; - private LightComponent mechanic_fifthDoorLight; - - // Room 6 - private MotionSensor mechanic_divingSuitObjectiveSensor; - private ItemContainer mechanic_divingSuitContainer; - private ItemContainer mechanic_oxygenContainer; - private Door tutorial_mechanicFinalDoor; - private LightComponent tutorial_mechanicFinalDoorLight; - - // Room 7 - private Pump mechanic_brokenPump; - private Structure mechanic_brokenWall_2; - private Hull mechanic_brokenhull_2; - private Door tutorial_submarineDoor; - private LightComponent tutorial_submarineDoorLight; - - // Submarine - private MotionSensor tutorial_enteredSubmarineSensor; - private Engine mechanic_submarineEngine; - private Pump mechanic_ballastPump_1; - private Pump mechanic_ballastPump_2; - - // Variables - private const float waterVolumeBeforeOpening = 15f; - private LocalizedString radioSpeakerName; - private Character mechanic; - private Sprite mechanic_repairIcon; - private Color mechanic_repairIconColor; - private Sprite mechanic_weldIcon; - - public MechanicTutorial() : base("tutorial.mechanictraining".ToIdentifier(), - new Segment( - "Mechanic.OpenDoor".ToIdentifier(), - "Mechanic.OpenDoorObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.OpenDoorText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Mechanic.Equipment".ToIdentifier(), - "Mechanic.EquipmentObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_inventory.webm", TextTag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Mechanic.Welding".ToIdentifier(), - "Mechanic.WeldingObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Mechanic.WeldingText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_equip.webm", TextTag = "Mechanic.WeldingText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Mechanic.Drain".ToIdentifier(), - "Mechanic.DrainObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.DrainText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Mechanic.Deconstruct".ToIdentifier(), - "Mechanic.DeconstructObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Mechanic.DeconstructText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_deconstruct.webm", TextTag = "Mechanic.DeconstructText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Mechanic.Fabricate".ToIdentifier(), - "Mechanic.FabricateObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Mechanic.FabricateText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_fabricate.webm", TextTag = "Mechanic.FabricateText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Mechanic.Extinguisher".ToIdentifier(), - "Mechanic.ExtinguisherObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.ExtinguisherText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Mechanic.DropExtinguisher".ToIdentifier(), - "Mechanic.DropExtinguisherObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.DropExtinguisherText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Mechanic.Diving".ToIdentifier(), - "Mechanic.DivingObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.DivingText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Mechanic.RepairPump".ToIdentifier(), - "Mechanic.RepairPumpObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.RepairPumpText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Mechanic.RepairSubmarine".ToIdentifier(), - "Mechanic.RepairSubmarineObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.RepairSubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "tutorial.laddertitle".ToIdentifier(), - "tutorial.laddertitle".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "tutorial.ladderdescription".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center })) - { } - - protected override CharacterInfo GetCharacterInfo() - { - return new CharacterInfo( - CharacterPrefab.HumanSpeciesName, - jobOrJobPrefab: new Job( - JobPrefab.Prefabs["mechanic"], Rand.RandSync.Unsynced, 0, - new Skill("medical".ToIdentifier(), 0), - new Skill("weapons".ToIdentifier(), 0), - new Skill("mechanical".ToIdentifier(), 50), - new Skill("electrical".ToIdentifier(), 20), - new Skill("helm".ToIdentifier(), 0))); - } - - protected override void Initialize() - { - radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); - mechanic = Character.Controlled; - - foreach (Item item in mechanic.Inventory.AllItemsMod) - { - if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; } - item.Unequip(mechanic); - mechanic.Inventory.RemoveItem(item); - } - - var repairOrder = OrderPrefab.Prefabs["repairsystems"]; - mechanic_repairIcon = repairOrder.SymbolSprite; - mechanic_repairIconColor = repairOrder.Color; - mechanic_weldIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(1, 256, 127, 127), new Vector2(0.5f, 0.5f)); - - // Other tutorial items - tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); - tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent(); - tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); - - tutorial_submarineSteering.CanBeSelected = false; - foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components) - { - ic.CanBeSelected = false; - } - - SetDoorAccess(null, tutorial_securityFinalDoorLight, false); - SetDoorAccess(tutorial_upperFinalDoor, null, false); - - // Room 1 - mechanic_firstDoor = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoor")).GetComponent(); - mechanic_firstDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoorlight")).GetComponent(); - - SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, false); - - // Room 2 - mechanic_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentobjectivesensor")).GetComponent(); - mechanic_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentcabinet")).GetComponent(); - mechanic_secondDoor = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoor")).GetComponent(); - mechanic_secondDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoorlight")).GetComponent(); - - SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, false); - - // Room 3 - mechanic_weldingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_weldingobjectivesensor")).GetComponent(); - mechanic_workingPump = Item.ItemList.Find(i => i.HasTag("mechanic_workingpump")).GetComponent(); - mechanic_thirdDoor = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoor")).GetComponent(); - mechanic_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoorlight")).GetComponent(); - mechanic_brokenWall_1 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_1"); - //mechanic_ladderSensor = Item.ItemList.Find(i => i.HasTag("mechanic_laddersensor")).GetComponent(); - - SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, false); - mechanic_brokenWall_1.Indestructible = false; - mechanic_brokenWall_1.SpriteColor = Color.White; - for (int i = 0; i < mechanic_brokenWall_1.SectionCount; i++) - { - mechanic_brokenWall_1.AddDamage(i, 85); - } - mechanic_brokenhull_1 = mechanic_brokenWall_1.Sections[0].gap.FlowTargetHull; - - // Room 4 - mechanic_craftingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_craftingobjectivesensor")).GetComponent(); - mechanic_deconstructor = Item.ItemList.Find(i => i.HasTag("mechanic_deconstructor")).GetComponent(); - mechanic_fabricator = Item.ItemList.Find(i => i.HasTag("mechanic_fabricator")).GetComponent(); - mechanic_craftingCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_craftingcabinet")).GetComponent(); - mechanic_fourthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoor")).GetComponent(); - mechanic_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoorlight")).GetComponent(); - - SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, false); - - // Room 5 - mechanic_fifthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoor")).GetComponent(); - mechanic_fifthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoorlight")).GetComponent(); - mechanic_fireSensor = Item.ItemList.Find(i => i.HasTag("mechanic_firesensor")).GetComponent(); - - SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, false); - - // Room 6 - mechanic_divingSuitObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitobjectivesensor")).GetComponent(); - mechanic_divingSuitContainer = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitcontainer")).GetComponent(); - foreach (Item item in mechanic_divingSuitContainer.Inventory.AllItems) - { - foreach (ItemComponent ic in item.Components) - { - ic.CanBePicked = true; - } - } - - mechanic_oxygenContainer = Item.ItemList.Find(i => i.HasTag("mechanic_oxygencontainer")).GetComponent(); - foreach (Item item in mechanic_oxygenContainer.Inventory.AllItems) - { - foreach (ItemComponent ic in item.Components) - { - ic.CanBePicked = true; - } - } - - tutorial_mechanicFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoor")).GetComponent(); - tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); - - SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, false); - - // Room 7 - mechanic_brokenPump = Item.ItemList.Find(i => i.HasTag("mechanic_brokenpump")).GetComponent(); - mechanic_brokenPump.Item.Indestructible = false; - mechanic_brokenPump.Item.Condition = 0; - mechanic_brokenPump.CanBeSelected = false; - mechanic_brokenPump.Item.GetComponent().CanBeSelected = false; - mechanic_brokenWall_2 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_2"); - tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); - tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); - - mechanic_brokenWall_2.Indestructible = false; - mechanic_brokenWall_2.SpriteColor = Color.White; - for (int i = 0; i < mechanic_brokenWall_2.SectionCount; i++) - { - mechanic_brokenWall_2.AddDamage(i, 85); - } - mechanic_brokenhull_2 = mechanic_brokenWall_2.Sections[0].gap.FlowTargetHull; - SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); - - // Submarine - tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent(); - mechanic_submarineEngine = Item.ItemList.Find(i => i.HasTag("mechanic_submarineengine")).GetComponent(); - mechanic_submarineEngine.Item.Indestructible = false; - mechanic_submarineEngine.Item.Condition = 0f; - mechanic_ballastPump_1 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_1")).GetComponent(); - mechanic_ballastPump_1.Item.Indestructible = false; - mechanic_ballastPump_1.Item.Condition = 0f; - mechanic_ballastPump_2 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_2")).GetComponent(); - mechanic_ballastPump_2.Item.Indestructible = false; - mechanic_ballastPump_2.Item.Condition = 0f; - - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Started"); - GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); - } - - public override void Update(float deltaTime) - { - if (mechanic_brokenhull_1 != null) - { - mechanic_brokenhull_1.WaterVolume = MathHelper.Clamp(mechanic_brokenhull_1.WaterVolume, 0, mechanic_brokenhull_1.Volume * 0.85f); - } - base.Update(deltaTime); - } - - public override IEnumerable UpdateState() - { - while (GameMain.Instance.LoadingScreenOpen) yield return null; - - // Room 1 - SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition); - while (shakeTimer > 0.0f) // Wake up, shake - { - shakeTimer -= 0.1f; - GameMain.GameScreen.Cam.Shake = shakeAmount; - yield return new WaitForSeconds(0.1f, false); - } - yield return new WaitForSeconds(2.5f, false); - - mechanic_fabricator.RemoveFabricationRecipes(allowedIdentifiers: - new[] { "extinguisher", "wrench", "weldingtool", "weldingfuel", "divingmask", "railgunshell", "nuclearshell", "uex", "harpoongun" }.ToIdentifiers()); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null); - - yield return new WaitForSeconds(2.5f, false); - TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Up), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Left), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Down), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Right), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Open door objective - yield return new WaitForSeconds(0.0f, false); - SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, true); - SetHighlight(mechanic_firstDoor.Item, true); - do { yield return null; } while (!mechanic_firstDoor.IsOpen); - SetHighlight(mechanic_firstDoor.Item, false); - yield return new WaitForSeconds(1.5f, false); - RemoveCompletedObjective(0); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective0"); - - // Room 2 - yield return new WaitForSeconds(0.0f, false); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null); - do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected); - TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Equipment & inventory objective - SetHighlight(mechanic_equipmentCabinet.Item, true); - bool firstSlotRemoved = false; - bool secondSlotRemoved = false; - bool thirdSlotRemoved = false; - do - { - if (IsSelectedItem(mechanic_equipmentCabinet.Item)) - { - if (!firstSlotRemoved) - { - HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f); - if (mechanic_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; } - } - - if (!secondSlotRemoved) - { - HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); - if (mechanic_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; } - } - - if (!thirdSlotRemoved) - { - HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); - if (mechanic_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; } - } - - for (int i = 0; i < mechanic.Inventory.Capacity; i++) - { - if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); } - } - } - - yield return null; - } while (mechanic.Inventory.FindItemByIdentifier("divingmask".ToIdentifier()) == null || mechanic.Inventory.FindItemByIdentifier("weldingtool".ToIdentifier()) == null || mechanic.Inventory.FindItemByIdentifier("wrench".ToIdentifier()) == null); // Wait until looted - SetHighlight(mechanic_equipmentCabinet.Item, false); - yield return new WaitForSeconds(1.5f, false); - RemoveCompletedObjective(1); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective1"); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Breach"), ChatMessageType.Radio, null); - - // Room 3 - do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected); - TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Welding objective - do - { - if (!mechanic.HasEquippedItem("divingmask".ToIdentifier())) - { - HighlightInventorySlot(mechanic.Inventory, "divingmask".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - - if (!mechanic.HasEquippedItem("weldingtool".ToIdentifier())) - { - HighlightInventorySlot(mechanic.Inventory, "weldingtool".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - yield return null; - } while (!mechanic.HasEquippedItem("divingmask".ToIdentifier()) || !mechanic.HasEquippedItem("weldingtool".ToIdentifier())); // Wait until equipped - SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, true); - mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_1, mechanic_weldIcon, mechanic_repairIconColor); - do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired - mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1); - RemoveCompletedObjective(2); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective2"); - - yield return new WaitForSeconds(1f, false); - TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Pump objective - SetHighlight(mechanic_workingPump.Item, true); - do - { - yield return null; - if (IsSelectedItem(mechanic_workingPump.Item)) - { - if (mechanic_workingPump.PowerButton.FlashTimer <= 0) - { - mechanic_workingPump.PowerButton.Flash(uiHighlightColor, 1.5f, true); - } - } - } while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining - SetHighlight(mechanic_workingPump.Item, false); - do { yield return null; } while (mechanic_brokenhull_1 != null && mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained - RemoveCompletedObjective(3); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective3"); - - SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, true); - //TriggerTutorialSegment(11, GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Up], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Down], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select]); // Ladder objective - //do { yield return null; } while (!mechanic_ladderSensor.MotionDetected); - //RemoveCompletedObjective(segments[11]); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.News"), ChatMessageType.Radio, null); - yield return new WaitForSeconds(1f, false); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Fire"), ChatMessageType.Radio, null); - - // Room 4 - do { yield return null; } while (!mechanic_thirdDoor.IsOpen); - yield return new WaitForSeconds(1f, false); - mechanic_fire = new DummyFireSource(new Vector2(20f, 2f), Item.ItemList.Find(i => i.HasTag("mechanic_fire")).WorldPosition); - //do { yield return null; } while (!mechanic_craftingObjectiveSensor.MotionDetected); - TriggerTutorialSegment(4); // Deconstruct - - SetHighlight(mechanic_craftingCabinet.Item, true); - - bool gotOxygenTank = false; - bool gotSodium = false; - do - { - if (mechanic.SelectedItem == mechanic_craftingCabinet.Item) - { - for (int i = 0; i < mechanic.Inventory.Capacity; i++) - { - if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); } - } - - if (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) == null && mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null) - { - for (int i = 0; i < mechanic_craftingCabinet.Capacity; i++) - { - Item item = mechanic_craftingCabinet.Inventory.GetItemAt(i); - if (item != null && item.Prefab.Identifier == "oxygentank") - { - HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); - } - } - } - - if (mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) == null) - { - for (int i = 0; i < mechanic_craftingCabinet.Inventory.Capacity; i++) - { - Item item = mechanic_craftingCabinet.Inventory.GetItemAt(i); - if (item != null && item.Prefab.Identifier == "sodium") - { - HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); - } - } - } - } - - if (!gotOxygenTank && (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null || - mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null)) - { - gotOxygenTank = true; - } - if (!gotSodium && mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null) - { - gotSodium = true; - } - yield return null; - } while (!gotOxygenTank || !gotSodium); // Wait until looted - - yield return new WaitForSeconds(1.0f, false); - SetHighlight(mechanic_craftingCabinet.Item, false); - SetHighlight(mechanic_deconstructor.Item, true); - do - { - if (IsSelectedItem(mechanic_deconstructor.Item)) - { - if (mechanic_deconstructor.OutputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null) - { - HighlightInventorySlot(mechanic_deconstructor.OutputContainer.Inventory, "aluminium".ToIdentifier(), highlightColor, .5f, .5f, 0f); - - for (int i = 0; i < mechanic.Inventory.Capacity; i++) - { - if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); } - } - } - else - { - if (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null && mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) == null) - { - HighlightInventorySlot(mechanic.Inventory, "oxygentank".ToIdentifier(), highlightColor, .5f, .5f, 0f); - for (int i = 0; i < mechanic_deconstructor.InputContainer.Inventory.Capacity; i++) - { - HighlightInventorySlot(mechanic_deconstructor.InputContainer.Inventory, i, highlightColor, .5f, .5f, 0f); - } - } - - if (mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null && !mechanic_deconstructor.IsActive) - { - if (mechanic_deconstructor.ActivateButton.FlashTimer <= 0) - { - mechanic_deconstructor.ActivateButton.Flash(highlightColor, 1.5f, false); - } - } - } - } - yield return null; - } while ( - mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null && - mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null); // Wait until aluminium obtained - - SetHighlight(mechanic_deconstructor.Item, false); - RemoveCompletedObjective(4); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective4"); - - yield return new WaitForSeconds(1f, false); - TriggerTutorialSegment(5); // Fabricate - SetHighlight(mechanic_fabricator.Item, true); - do - { - if (IsSelectedItem(mechanic_fabricator.Item)) - { - if (mechanic_fabricator.SelectedItem?.TargetItem.Identifier != "extinguisher") - { - mechanic_fabricator.HighlightRecipe("extinguisher", highlightColor); - } - else - { - if (mechanic_fabricator.OutputContainer.Inventory.FindItemByIdentifier("extinguisher".ToIdentifier()) != null) - { - HighlightInventorySlot(mechanic_fabricator.OutputContainer.Inventory, "extinguisher".ToIdentifier(), highlightColor, .5f, .5f, 0f); - - /*for (int i = 0; i < mechanic.Inventory.Capacity; i++) - { - if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); - }*/ - } - else if (mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null && mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null && !mechanic_fabricator.IsActive) - { - if (mechanic_fabricator.ActivateButton.FlashTimer <= 0) - { - mechanic_fabricator.ActivateButton.Flash(highlightColor, 1.5f, false); - } - } - else if (mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null || mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null) - { - HighlightInventorySlot(mechanic.Inventory, "aluminium".ToIdentifier(), highlightColor, .5f, .5f, 0f); - HighlightInventorySlot(mechanic.Inventory, "sodium".ToIdentifier(), highlightColor, .5f, .5f, 0f); - - if (mechanic_fabricator.InputContainer.Inventory.GetItemAt(0) == null) - { - HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 0, highlightColor, .5f, .5f, 0f); - } - - if (mechanic_fabricator.InputContainer.Inventory.GetItemAt(1) == null) - { - HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 1, highlightColor, .5f, .5f, 0f); - } - } - } - } - yield return null; - } while (mechanic.Inventory.FindItemByIdentifier("extinguisher".ToIdentifier()) == null); // Wait until extinguisher is created - RemoveCompletedObjective(5); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective5"); - SetHighlight(mechanic_fabricator.Item, false); - SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, true); - - // Room 5 - do { yield return null; } while (!mechanic_fireSensor.MotionDetected); - TriggerTutorialSegment(6, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Using the extinguisher - do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished - yield return new WaitForSeconds(3f, false); - RemoveCompletedObjective(6); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective6"); - - if (mechanic.HasEquippedItem("extinguisher".ToIdentifier())) // do not trigger if dropped already - { - TriggerTutorialSegment(7); - do - { - HighlightInventorySlot(mechanic.Inventory, "extinguisher".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f); - yield return null; - } while (mechanic.HasEquippedItem("extinguisher".ToIdentifier())); - RemoveCompletedObjective(7); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective7"); - } - SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, true); - - // Room 6 - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Diving"), ChatMessageType.Radio, null); - do { yield return null; } while (!mechanic_divingSuitObjectiveSensor.MotionDetected); - TriggerTutorialSegment(8); // Dangers of pressure, equip diving suit objective - SetHighlight(mechanic_divingSuitContainer.Item, true); - do - { - if (IsSelectedItem(mechanic_divingSuitContainer.Item)) - { - if (mechanic_divingSuitContainer.Inventory.visualSlots != null) - { - for (int i = 0; i < mechanic_divingSuitContainer.Inventory.Capacity; i++) - { - HighlightInventorySlot(mechanic_divingSuitContainer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); - } - } - } - yield return null; - } while (!mechanic.HasEquippedItem("divingsuit".ToIdentifier(), slotType: InvSlotType.OuterClothes)); - SetHighlight(mechanic_divingSuitContainer.Item, false); - RemoveCompletedObjective(8); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective8"); - SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true); - - // Room 7 - mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_2, mechanic_weldIcon, mechanic_repairIconColor); - do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_2)); - mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2); - yield return new WaitForSeconds(2f, false); - - TriggerTutorialSegment(9, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)); // Repairing machinery (pump) - SetHighlight(mechanic_brokenPump.Item, true); - mechanic_brokenPump.CanBeSelected = true; - Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent(); - repairablePumpComponent.CanBeSelected = true; - do - { - yield return null; - if (repairablePumpComponent.IsBelowRepairThreshold) - { - if (!mechanic.HasEquippedItem("wrench".ToIdentifier())) - { - HighlightInventorySlot(mechanic.Inventory, "wrench".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f); - } - else if (IsSelectedItem(mechanic_brokenPump.Item) && repairablePumpComponent.CurrentFixer == null) - { - if (repairablePumpComponent.RepairButton.FlashTimer <= 0) - { - repairablePumpComponent.RepairButton.Flash(); - } - } - } - else - { - if (IsSelectedItem(mechanic_brokenPump.Item)) - { - if (mechanic_brokenPump.PowerButton.FlashTimer <= 0) - { - mechanic_brokenPump.PowerButton.Flash(uiHighlightColor, 1.5f, true); - } - } - } - } while (repairablePumpComponent.IsBelowRepairThreshold || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive); - RemoveCompletedObjective(9); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective9"); - SetHighlight(mechanic_brokenPump.Item, false); - do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening); - SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); - - // Submarine - do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Submarine"), ChatMessageType.Radio, null); - TriggerTutorialSegment(10); // Repairing ballast pumps, engine - while (ContentRunning) yield return null; - mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_1.Item, mechanic_repairIcon, mechanic_repairIconColor); - mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_2.Item, mechanic_repairIcon, mechanic_repairIconColor); - mechanic.AddActiveObjectiveEntity(mechanic_submarineEngine.Item, mechanic_repairIcon, mechanic_repairIconColor); - SetHighlight(mechanic_ballastPump_1.Item, true); - SetHighlight(mechanic_ballastPump_2.Item, true); - SetHighlight(mechanic_submarineEngine.Item, true); - - Repairable repairablePumpComponent1 = mechanic_ballastPump_1.Item.GetComponent(); - Repairable repairablePumpComponent2 = mechanic_ballastPump_2.Item.GetComponent(); - Repairable repairableEngineComponent = mechanic_submarineEngine.Item.GetComponent(); - - // Remove highlights when each individual machine is repaired - do { CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); yield return null; } while (repairablePumpComponent1.IsBelowRepairThreshold || repairablePumpComponent2.IsBelowRepairThreshold || repairableEngineComponent.IsBelowRepairThreshold); - CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); - RemoveCompletedObjective(10); - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective10"); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null); - - // END TUTORIAL - GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Completed"); - CoroutineManager.StartCoroutine(TutorialCompleted()); - } - - private bool IsSelectedItem(Item item) - { - return mechanic?.SelectedItem == item; - } - - private bool WallHasDamagedSections(Structure wall) - { - for (int i = 0; i < wall.SectionCount; i++) - { - if (wall.Sections[i].damage > 0) return true; - } - - return false; - } - - private void CheckHighlights(Repairable comp1, Repairable comp2, Repairable comp3) - { - if (!comp1.IsBelowRepairThreshold && mechanic_ballastPump_1.Item.ExternalHighlight) - { - SetHighlight(mechanic_ballastPump_1.Item, false); - mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item); - } - if (!comp2.IsBelowRepairThreshold && mechanic_ballastPump_2.Item.ExternalHighlight) - { - SetHighlight(mechanic_ballastPump_2.Item, false); - mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item); - } - if (!comp3.IsBelowRepairThreshold && mechanic_submarineEngine.Item.ExternalHighlight) - { - SetHighlight(mechanic_submarineEngine.Item, false); - mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item); - } - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs deleted file mode 100644 index 3cfb4551f..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs +++ /dev/null @@ -1,526 +0,0 @@ -using System; -using System.Collections.Generic; -using Barotrauma.IO; -using System.Xml.Linq; -using System.Linq; -using Barotrauma.Items.Components; -using Barotrauma.Networking; -using Barotrauma.Extensions; -using Microsoft.Xna.Framework; - -namespace Barotrauma.Tutorials -{ - class OfficerTutorial : ScenarioTutorial - { - // Other tutorial items - private LightComponent tutorial_mechanicFinalDoorLight; - private Steering tutorial_submarineSteering; - - // Room 1 - private float shakeTimer = 1f; - private float shakeAmount = 20f; - - // Room 2 - private MotionSensor officer_equipmentObjectiveSensor; - private ItemContainer officer_equipmentCabinet; - private Door officer_firstDoor; - private LightComponent officer_firstDoorLight; - - // Room 3 - private MotionSensor officer_crawlerSensor; - private Character officer_crawler; - private Vector2 officer_crawlerSpawnPos; - private Door officer_secondDoor; - private LightComponent officer_secondDoorLight; - - // Room 4 - private MotionSensor officer_somethingBigSensor; - private ItemContainer officer_coilgunLoader; - private ItemContainer officer_ammoShelf_1; - private ItemContainer officer_ammoShelf_2; - private PowerContainer officer_superCapacitor; - private Item officer_coilgunPeriscope; - private Character officer_hammerhead; - private Vector2 officer_hammerheadSpawnPos; - private Door officer_thirdDoor; - private LightComponent officer_thirdDoorLight; - - // Room 5 - private MotionSensor officer_rangedWeaponSensor; - private ItemContainer officer_rangedWeaponCabinet; - private ItemContainer officer_rangedWeaponHolder; - private Door officer_fourthDoor; - private LightComponent officer_fourthDoorLight; - - // Room 6 - private MotionSensor officer_mudraptorObjectiveSensor; - private Vector2 officer_mudraptorSpawnPos; - private Character officer_mudraptor; - private Door tutorial_securityFinalDoor; - private LightComponent tutorial_securityFinalDoorLight; - - // Submarine - private Door tutorial_submarineDoor; - private LightComponent tutorial_submarineDoorLight; - private MotionSensor tutorial_enteredSubmarineSensor; - private Item officer_subAmmoBox_1; - private Item officer_subAmmoBox_2; - private ItemContainer officer_subAmmoShelf; - private ItemContainer officer_subLoader_1; - private ItemContainer officer_subLoader_2; - private PowerContainer officer_subSuperCapacitor_1; - private PowerContainer officer_subSuperCapacitor_2; - - // Variables - private LocalizedString radioSpeakerName; - private Character officer; - private float superCapacitorRechargeRate = 10; - private Sprite officer_gunIcon; - private Color officer_gunIconColor; - - public OfficerTutorial() : base("tutorial.securityofficertraining".ToIdentifier(), - new Segment( - "Mechanic.Equipment".ToIdentifier(), - "Mechanic.EquipmentObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Officer.MeleeWeapon".ToIdentifier(), - "Officer.MeleeWeaponObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Officer.MeleeWeaponText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Officer.Crawler".ToIdentifier(), - "Officer.CrawlerObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Officer.CrawlerText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Officer.SomethingBig".ToIdentifier(), - "Officer.SomethingBigObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Officer.SomethingBigText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_loaders.webm", TextTag = "Officer.SomethingBigText".ToIdentifier(), Width = 700, Height = 80 }), - new Segment( - "Officer.Hammerhead".ToIdentifier(), - "Officer.HammerheadObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Officer.HammerheadText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Officer.RangedWeapon".ToIdentifier(), - "Officer.RangedWeaponObjective".ToIdentifier(), - TutorialContentType.ManualVideo, - textContent: new Segment.Text { Tag = "Officer.RangedWeaponText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }, - videoContent: new Segment.Video { File = "tutorial_ranged.webm", TextTag = "Officer.RangedWeaponText".ToIdentifier(), Width = 450, Height = 80 }), - new Segment( - "Officer.Mudraptor".ToIdentifier(), - "Officer.MudraptorObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Officer.MudraptorText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }), - new Segment( - "Officer.ArmSubmarine".ToIdentifier(), - "Officer.ArmSubmarineObjective".ToIdentifier(), - TutorialContentType.TextOnly, - textContent: new Segment.Text { Tag = "Officer.ArmSubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center })) - { } - - protected override CharacterInfo GetCharacterInfo() - { - return new CharacterInfo( - CharacterPrefab.HumanSpeciesName, - jobOrJobPrefab: new Job( - JobPrefab.Prefabs["securityofficer"], Rand.RandSync.Unsynced, 0, - new Skill("medical".ToIdentifier(), 20), - new Skill("weapons".ToIdentifier(), 70), - new Skill("mechanical".ToIdentifier(), 20), - new Skill("electrical".ToIdentifier(), 20), - new Skill("helm".ToIdentifier(), 20))); - } - - protected override void Initialize() - { - radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); - officer = Character.Controlled; - - foreach (Item item in officer.Inventory.AllItemsMod) - { - if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; } - item.Unequip(officer); - officer.Inventory.RemoveItem(item); - } - - var gunOrder = OrderPrefab.Prefabs["operateweapons"]; - officer_gunIcon = gunOrder.SymbolSprite; - officer_gunIconColor = gunOrder.Color; - - var bandage = FindOrGiveItem(officer, "antibleeding1".ToIdentifier()); - bandage.Unequip(officer); - officer.Inventory.RemoveItem(bandage); - FindOrGiveItem(officer, "antibleeding1".ToIdentifier()); - - // Other tutorial items - tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); - tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); - - tutorial_submarineSteering.CanBeSelected = false; - foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components) - { - ic.CanBeSelected = false; - } - - SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false); - - // Room 2 - officer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_equipmentobjectivesensor")).GetComponent(); - officer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("officer_equipmentcabinet")).GetComponent(); - officer_firstDoor = Item.ItemList.Find(i => i.HasTag("officer_firstdoor")).GetComponent(); - officer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("officer_firstdoorlight")).GetComponent(); - - SetDoorAccess(officer_firstDoor, officer_firstDoorLight, false); - - // Room 3 - officer_crawlerSensor = Item.ItemList.Find(i => i.HasTag("officer_crawlerobjectivesensor")).GetComponent(); - officer_crawlerSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_crawlerspawn")).WorldPosition; - officer_secondDoor = Item.ItemList.Find(i => i.HasTag("officer_seconddoor")).GetComponent(); - officer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("officer_seconddoorlight")).GetComponent(); - - SetDoorAccess(officer_secondDoor, officer_secondDoorLight, false); - - // Room 4 - officer_somethingBigSensor = Item.ItemList.Find(i => i.HasTag("officer_somethingbigobjectivesensor")).GetComponent(); - officer_coilgunLoader = Item.ItemList.Find(i => i.HasTag("officer_coilgunloader")).GetComponent(); - officer_superCapacitor = Item.ItemList.Find(i => i.HasTag("officer_supercapacitor")).GetComponent(); - officer_coilgunPeriscope = Item.ItemList.Find(i => i.HasTag("officer_coilgunperiscope")); - officer_hammerheadSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_hammerheadspawn")).WorldPosition; - officer_thirdDoor = Item.ItemList.Find(i => i.HasTag("officer_thirddoor")).GetComponent(); - officer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("officer_thirddoorlight")).GetComponent(); - officer_ammoShelf_1 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_1")).GetComponent(); - officer_ammoShelf_2 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_2")).GetComponent(); - - SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, false); - - // Room 5 - officer_rangedWeaponSensor = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponobjectivesensor")).GetComponent(); - officer_rangedWeaponCabinet = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponcabinet")).GetComponent(); - officer_rangedWeaponHolder = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponholder")).GetComponent(); - officer_fourthDoor = Item.ItemList.Find(i => i.HasTag("officer_fourthdoor")).GetComponent(); - officer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("officer_fourthdoorlight")).GetComponent(); - - SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, false); - - // Room 6 - officer_mudraptorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_mudraptorobjectivesensor")).GetComponent(); - officer_mudraptorSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_mudraptorspawn")).WorldPosition; - tutorial_securityFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoor")).GetComponent(); - tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); - - SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, false); - - // Submarine - tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); - tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); - tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent(); - officer_subAmmoBox_1 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_1")); - officer_subAmmoBox_2 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_2")); - officer_subLoader_1 = Item.ItemList.Find(i => i.HasTag("officer_subloader_1")).GetComponent(); - officer_subLoader_2 = Item.ItemList.Find(i => i.HasTag("officer_subloader_2")).GetComponent(); - officer_subSuperCapacitor_1 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_1")).GetComponent(); - officer_subSuperCapacitor_2 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_2")).GetComponent(); - officer_subAmmoShelf = Item.ItemList.Find(i => i.HasTag("officer_subammoshelf")).GetComponent(); - SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); - - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Started"); - GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); - } - - public override IEnumerable UpdateState() - { - while (GameMain.Instance.LoadingScreenOpen) yield return null; - - yield return new WaitForSeconds(0.01f); - - // Room 1 - SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition); - while (shakeTimer > 0.0f) // Wake up, shake - { - shakeTimer -= 0.1f; - GameMain.GameScreen.Cam.Shake = shakeAmount; - yield return new WaitForSeconds(0.1f, false); - } - - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.WakeUp"), ChatMessageType.Radio, null); - - // Room 2 - do { yield return null; } while (!officer_equipmentObjectiveSensor.MotionDetected); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Equipment"), ChatMessageType.Radio, null); - yield return new WaitForSeconds(3f, false); - //TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Deselect]); // Retrieve equipment - SetHighlight(officer_equipmentCabinet.Item, true); - bool firstSlotRemoved = false; - bool secondSlotRemoved = false; - bool thirdSlotRemoved = false; - do - { - if (IsSelectedItem(officer_equipmentCabinet.Item)) - { - if (!firstSlotRemoved) - { - HighlightInventorySlot(officer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f); - if (officer_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; } - } - - if (!secondSlotRemoved) - { - HighlightInventorySlot(officer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); - if (officer_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; } - } - - if (!thirdSlotRemoved) - { - HighlightInventorySlot(officer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); - if (officer_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; } - } - - for (int i = 0; i < officer.Inventory.visualSlots.Length; i++) - { - if (officer.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(officer.Inventory, i, highlightColor, .5f, .5f, 0f); } - } - } - - yield return null; - } while (!officer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted - //RemoveCompletedObjective(segments[0]); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective0"); - SetHighlight(officer_equipmentCabinet.Item, false); - do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item)); - TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Equip melee weapon & armor - do - { - if (!officer.HasEquippedItem("stunbaton".ToIdentifier())) - { - HighlightInventorySlot(officer.Inventory, "stunbaton".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - if (!officer.HasEquippedItem("bodyarmor".ToIdentifier())) - { - HighlightInventorySlot(officer.Inventory, "bodyarmor".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - if (!officer.HasEquippedItem("ballistichelmet1".ToIdentifier())) - { - HighlightInventorySlot(officer.Inventory, "ballistichelmet1".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - yield return new WaitForSeconds(1f, false); - } while (!officer.HasEquippedItem("stunbaton".ToIdentifier()) || !officer.HasEquippedItem("bodyarmor".ToIdentifier()) || !officer.HasEquippedItem("ballistichelmet1".ToIdentifier())); - RemoveCompletedObjective(1); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective1"); - SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true); - - // Room 3 - do { yield return null; } while (!officer_crawlerSensor.MotionDetected); - TriggerTutorialSegment(2); - officer_crawler = SpawnMonster("crawler", officer_crawlerSpawnPos); - do { yield return null; } while (!officer_crawler.IsDead); - RemoveCompletedObjective(2); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective2"); - Heal(officer); - yield return new WaitForSeconds(1f, false); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.CrawlerDead"), ChatMessageType.Radio, null); - SetDoorAccess(officer_secondDoor, officer_secondDoorLight, true); - - // Room 4 - do { yield return null; } while (!officer_somethingBigSensor.MotionDetected); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.SomethingBig"), ChatMessageType.Radio, null); - yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(3); // Arm coilgun - do - { - SetHighlight(officer_coilgunLoader.Item, officer_coilgunLoader.Inventory.GetItemAt(0) == null || officer_coilgunLoader.Inventory.GetItemAt(0).Condition == 0); - HighlightInventorySlot(officer_coilgunLoader.Inventory, 0, highlightColor, .5f, .5f, 0f); - SetHighlight(officer_superCapacitor.Item, officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate); - SetHighlight(officer_ammoShelf_1.Item, officer_coilgunLoader.Item.ExternalHighlight ); - SetHighlight(officer_ammoShelf_2.Item, officer_coilgunLoader.Item.ExternalHighlight ); - if (IsSelectedItem(officer_coilgunLoader.Item)) - { - HighlightInventorySlot(officer.Inventory, "coilgunammobox".ToIdentifier(), highlightColor, .5f, .5f, 0f); - } - yield return null; - } while (officer_coilgunLoader.Inventory.GetItemAt(0) == null || officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate || officer_coilgunLoader.Inventory.GetItemAt(0).Condition == 0); - SetHighlight(officer_coilgunLoader.Item, false); - SetHighlight(officer_superCapacitor.Item, false); - SetHighlight(officer_ammoShelf_1.Item, false); - SetHighlight(officer_ammoShelf_2.Item, false); - RemoveCompletedObjective(3); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective3"); - - yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Kill hammerhead - officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos); - officer_hammerhead.Params.AI.AvoidAbyss = false; - officer_hammerhead.Params.AI.StayInAbyss = false; - officer_hammerhead.AIController.SelectTarget(officer.AiTarget); - SetHighlight(officer_coilgunPeriscope, true); - float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos); - do - { - float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition); - if (distance > originalDistance * 1.5f || officer_hammerhead.WorldPosition.Y > officer_coilgunPeriscope.WorldPosition.Y) - { - // Don't let the Hammerhead go too far. - officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000)); - } - if (distance > originalDistance) - { - // Ensure that the Hammerhead targets the player - officer.AiTarget.SoundRange = float.MaxValue; - officer.AiTarget.SightRange = float.MaxValue; - officer_hammerhead.AIController.SelectTarget(officer.AiTarget); - if ((officer_hammerhead.AIController as EnemyAIController)?.SelectedTargetingParams != null) - { - ((EnemyAIController)officer_hammerhead.AIController).SelectedTargetingParams.ReactDistance = 5000.0f; - } - /*var ai = officer_hammerhead.AIController as EnemyAIController; - ai.sight = 2.0f;*/ - } - yield return null; - } - while(!officer_hammerhead.IsDead); - Heal(officer); - SetHighlight(officer_coilgunPeriscope, false); - RemoveCompletedObjective(4); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective4"); - - yield return new WaitForSeconds(1f, false); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.HammerheadDead"), ChatMessageType.Radio, null); - SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, true); - - // Room 5 - //do { yield return null; } while (!officer_rangedWeaponSensor.MotionDetected); - do { yield return null; } while (!officer_thirdDoor.IsOpen); - yield return new WaitForSeconds(3f, false); - TriggerTutorialSegment(5, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Ranged weapons - SetHighlight(officer_rangedWeaponHolder.Item, true); - do { yield return null; } while (!officer_rangedWeaponHolder.Inventory.IsEmpty()); // Wait until looted - SetHighlight(officer_rangedWeaponHolder.Item, false); - do - { - HighlightInventorySlot(officer.Inventory, "shotgun".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f); - yield return null; - } while (!officer.HasEquippedItem("shotgun".ToIdentifier())); // Wait until equipped - ItemContainer shotGunChamber = officer.Inventory.FindItemByIdentifier("shotgun".ToIdentifier()).GetComponent(); - SetHighlight(officer_rangedWeaponCabinet.Item, true); - do - { - if (IsSelectedItem(officer_rangedWeaponCabinet.Item)) - { - if (officer_rangedWeaponCabinet.Inventory.visualSlots != null) - { - for (int i = 0; i < officer_rangedWeaponCabinet.Inventory.Capacity; i++) - { - if (officer_rangedWeaponCabinet.Inventory.GetItemAt(i)?.Prefab.Identifier == "shotgunshell") - { - HighlightInventorySlot(officer_rangedWeaponCabinet.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); - } - } - } - } - - for (int i = 0; i < officer.Inventory.Capacity; i++) - { - if (officer.Inventory.GetItemAt(i)?.Prefab.Identifier == "shotgunshell") - { - HighlightInventorySlot(officer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); - } - } - - if (officer.Inventory.FindItemByIdentifier("shotgunshell".ToIdentifier()) != null || (IsSelectedItem(officer_rangedWeaponCabinet.Item) && officer_rangedWeaponCabinet.Inventory.FindItemByIdentifier("shotgunshell".ToIdentifier()) != null)) - { - HighlightInventorySlot(officer.Inventory, "shotgun".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f); - } - yield return null; - } while (!shotGunChamber.Inventory.IsFull(takeStacksIntoAccount: true)); // Wait until all six harpoons loaded - RemoveCompletedObjective(5); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective5"); - SetHighlight(officer_rangedWeaponCabinet.Item, false); - SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, true); - - // Room 6 - do { yield return null; } while (!officer_mudraptorObjectiveSensor.MotionDetected); - TriggerTutorialSegment(6); - officer_mudraptor = SpawnMonster("mudraptor", officer_mudraptorSpawnPos); - do { yield return null; } while (!officer_mudraptor.IsDead); - Heal(officer); - RemoveCompletedObjective(6); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective6"); - SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, true); - - // Submarine - do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected); - TriggerTutorialSegment(7); - while (ContentRunning) yield return null; - officer.AddActiveObjectiveEntity(officer_subAmmoBox_1, officer_gunIcon, officer_gunIconColor); - officer.AddActiveObjectiveEntity(officer_subAmmoBox_2, officer_gunIcon, officer_gunIconColor); - officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_1.Item, officer_gunIcon, officer_gunIconColor); - officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_2.Item, officer_gunIcon, officer_gunIconColor); - SetHighlight(officer_subSuperCapacitor_1.Item, true); - SetHighlight(officer_subSuperCapacitor_2.Item, true); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Submarine"), ChatMessageType.Radio, null); - do - { - SetHighlight(officer_subLoader_1.Item, officer_subLoader_1.Inventory.GetItemAt(0) == null || officer_subLoader_1.Inventory.GetItemAt(0).Condition == 0); - SetHighlight(officer_subLoader_2.Item, officer_subLoader_2.Inventory.GetItemAt(0) == null || officer_subLoader_2.Inventory.GetItemAt(0).Condition == 0); - HighlightInventorySlot(officer_subLoader_1.Inventory, 0, highlightColor, .5f, .5f, 0f); - HighlightInventorySlot(officer_subLoader_2.Inventory, 0, highlightColor, .5f, .5f, 0f); - - if (officer_subSuperCapacitor_1.Item.ExternalHighlight && officer_subSuperCapacitor_1.RechargeSpeed >= superCapacitorRechargeRate) - { - SetHighlight(officer_subSuperCapacitor_1.Item, false); - officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item); - } - - if (officer_subSuperCapacitor_2.Item.ExternalHighlight && officer_subSuperCapacitor_2.RechargeSpeed >= superCapacitorRechargeRate) - { - SetHighlight(officer_subSuperCapacitor_2.Item, false); - officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item); - } - - SetHighlight(officer_subAmmoBox_1, officer_subAmmoBox_1.ParentInventory != officer_subLoader_1.Inventory && officer_subAmmoBox_1.ParentInventory != officer_subLoader_2.Inventory); - SetHighlight(officer_subAmmoBox_2, officer_subAmmoBox_2.ParentInventory != officer_subLoader_1.Inventory && officer_subAmmoBox_2.ParentInventory != officer_subLoader_2.Inventory); - SetHighlight(officer_subAmmoShelf.Item, officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight); - if (officer_subAmmoBox_1.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_1.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1); - if (officer_subAmmoBox_2.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_2.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2); - yield return null; - } while (officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight || officer_subSuperCapacitor_1.Item.ExternalHighlight || officer_subSuperCapacitor_2.Item.ExternalHighlight); - SetHighlight(officer_subLoader_1.Item, false); - SetHighlight(officer_subLoader_2.Item, false); - SetHighlight(officer_subSuperCapacitor_1.Item, false); - SetHighlight(officer_subSuperCapacitor_2.Item, false); - SetHighlight(officer_subAmmoBox_1, false); - SetHighlight(officer_subAmmoBox_2, false); - SetHighlight(officer_subAmmoShelf.Item, false); - officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item); - officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item); - officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1); - officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2); - RemoveCompletedObjective(7); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective7"); - GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Complete"), ChatMessageType.Radio, null); - - yield return new WaitForSeconds(4f, false); - GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Completed"); - CoroutineManager.StartCoroutine(TutorialCompleted()); - } - - private bool IsSelectedItem(Item item) - { - return officer?.SelectedItem == item; - } - - private Character SpawnMonster(string speciesName, Vector2 pos) - { - var character = Character.Create(speciesName, pos, ToolBox.RandomSeed(8)); - var ai = character.AIController as EnemyAIController; - ai.TargetOutposts = true; - character.CharacterHealth.SetVitality(character.Health / 2); - character.AnimController.Limbs.Where(l => l.attack != null).Select(l => l.attack).ForEach(a => a.AfterAttack = AIBehaviorAfterAttack.FallBack); - return character; - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs deleted file mode 100644 index 7d72179b1..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs +++ /dev/null @@ -1,307 +0,0 @@ -using Barotrauma.Items.Components; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma.Tutorials -{ - abstract class ScenarioTutorial : Tutorial - { - private CoroutineHandle tutorialCoroutine; - - private Character character; - - private const string submarinePath = "Content/Tutorials/Dugong_Tutorial.sub"; - private const string startOutpostPath = "Content/Tutorials/TutorialOutpost.sub"; - //private const string endOutpostPath = ""; - - private const string levelSeed = "nLoZLLtza"; - private const string levelParams = "ColdCavernsTutorial"; - - //private const string spawnSub = "startoutpost"; - private const SpawnType spawnPointType = SpawnType.Human; - - private SubmarineInfo startOutpost = null; - private SubmarineInfo endOutpost = null; - private bool currentTutorialCompleted = false; - private float fadeOutTime = 3f; - protected float waitBeforeFade = 4f; - - // Colors - protected Color highlightColor = Color.OrangeRed; - protected Color uiHighlightColor = new Color(150, 50, 0); - protected Color buttonHighlightColor = new Color(255, 100, 0); - protected Color inaccessibleColor = GUIStyle.Red; - protected Color accessibleColor = GUIStyle.Green; - - protected ScenarioTutorial(Identifier identifier, params Segment[] segments) : base(identifier, segments) { } - - protected abstract void Initialize(); - - protected override IEnumerable Loading() - { - SubmarineInfo subInfo = new SubmarineInfo(submarinePath); - - LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier == levelParams); - - yield return CoroutineStatus.Running; - - GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null); - (GameMain.GameSession.GameMode as TutorialMode).Tutorial = this; - - if (generationParams != null) - { - Biome biome = - Biome.Prefabs.FirstOrDefault(b => generationParams.AllowedBiomeIdentifiers.Contains(b.Identifier)) ?? - Biome.Prefabs.First(); - - if (!string.IsNullOrEmpty(startOutpostPath)) - { - startOutpost = new SubmarineInfo(startOutpostPath); - } - - /*if (!string.IsNullOrEmpty(endOutpostPath)) - { - endOutpost = new SubmarineInfo(endOutpostPath); - }*/ - - LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome); - GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost, endOutpost: endOutpost); - } - else - { - GameMain.GameSession.StartRound(levelSeed); - } - - GameMain.GameSession.EventManager.ActiveEvents.Clear(); - GameMain.GameSession.EventManager.Enabled = false; - GameMain.GameScreen.Select(); - - - Submarine.MainSub.GodMode = true; - foreach (Structure wall in Structure.WallList) - { - if (wall.Submarine != null && wall.Submarine.Info.IsOutpost) - { - wall.Indestructible = true; - } - } - - CharacterInfo charInfo = GetCharacterInfo(); - - WayPoint wayPoint = GetSpawnPoint(charInfo); - - if (wayPoint == null) - { - DebugConsole.ThrowError("A waypoint with the spawntype \"" + spawnPointType + "\" is required for the tutorial event"); - yield return CoroutineStatus.Failure; - yield break; - } - - character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false); - character.TeamID = CharacterTeamType.Team1; - Character.Controlled = character; - character.GiveJobItems(null); - - var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier()); - if (idCard == null) - { - DebugConsole.ThrowError("Item prefab \"ID Card\" not found!"); - yield return CoroutineStatus.Failure; - yield break; - } - idCard.AddTag("com"); - idCard.AddTag("eng"); - - foreach (Item item in Item.ItemList) - { - Door door = item.GetComponent(); - if (door != null) - { - door.CanBeWelded = false; - } - } - - tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState()); - - Initialize(); - - yield return CoroutineStatus.Success; - } - - protected abstract CharacterInfo GetCharacterInfo(); - - public override void AddToGUIUpdateList() - { - if (!currentTutorialCompleted) - { - base.AddToGUIUpdateList(); - } - } - - private WayPoint GetSpawnPoint(CharacterInfo charInfo) - { - /*Submarine spawnSub = null; - - if (this.spawnSub != string.Empty) - { - switch (this.spawnSub) - { - case "startoutpost": - spawnSub = Level.Loaded.StartOutpost; - break; - - case "endoutpost": - spawnSub = Level.Loaded.EndOutpost; - break; - - default: - spawnSub = Submarine.MainSub; - break; - } - }*/ - Submarine spawnSub = Level.Loaded.StartOutpost; - return WayPoint.GetRandom(spawnPointType, charInfo.Job?.Prefab, spawnSub); - } - - protected bool HasOrder(Character character, string identifier, string option = null) - { - var currentOrderInfo = character.GetCurrentOrderWithTopPriority(); - if (currentOrderInfo?.Identifier == identifier) - { - if (option == null) - { - return true; - } - else - { - return currentOrderInfo?.Option == option; - } - } - - return false; - } - - protected void SetHighlight(Item item, bool state) - { - if (item.ExternalHighlight == state) return; - item.SpriteColor = (state) ? highlightColor : Color.White; - item.ExternalHighlight = state; - } - - protected void SetHighlight(Structure structure, bool state) - { - structure.SpriteColor = (state) ? highlightColor : Color.White; - structure.ExternalHighlight = state; - } - - protected void SetHighlight(Character character, bool state) - { - character.ExternalHighlight = state; - } - - protected void SetDoorAccess(Door door, LightComponent light, bool state) - { - if (state && door != null) door.requiredItems.Clear(); - if (light != null) light.LightColor = (state) ? accessibleColor : inaccessibleColor; - } - - public override void Update(float deltaTime) - { - base.Update(deltaTime); - if (character != null) - { - if (character.Oxygen < 1) - { - character.Oxygen = 1; - } - if (character.IsDead) - { - CoroutineManager.StartCoroutine(Dead()); - } - else if (Character.Controlled == null) - { - if (tutorialCoroutine != null) - { - CoroutineManager.StopCoroutines(tutorialCoroutine); - } - GUI.PreventPauseMenuToggle = false; - ContentRunning = false; - infoBox = null; - } - else if (Character.Controlled.IsDead) - { - CoroutineManager.StartCoroutine(Dead()); - } - } - } - - public override void Stop() - { - if (tutorialCoroutine != null) - { - CoroutineManager.StopCoroutines(tutorialCoroutine); - } - base.Stop(); - } - - private IEnumerable Dead() - { - GUI.PreventPauseMenuToggle = true; - Character.Controlled = character = null; - Stop(); - - GameAnalyticsManager.AddDesignEvent("Tutorial:Died"); - - yield return new WaitForSeconds(3.0f); - - var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }); - - messageBox.Buttons[0].OnClicked += Restart; - messageBox.Buttons[0].OnClicked += messageBox.Close; - - - messageBox.Buttons[1].OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu; - messageBox.Buttons[1].OnClicked += messageBox.Close; - - yield return CoroutineStatus.Success; - } - - protected IEnumerable TutorialCompleted() - { - GUI.PreventPauseMenuToggle = true; - - Character.Controlled.ClearInputs(); - Character.Controlled = null; - - GameAnalyticsManager.AddDesignEvent("Tutorial:Completed"); - - yield return new WaitForSeconds(waitBeforeFade); - - var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: fadeOutTime); - currentTutorialCompleted = Completed = true; - while (endCinematic.Running) yield return null; - Stop(); - GameMain.MainMenuScreen.ReturnToMainMenu(null, null); - } - - protected void Heal(Character character) - { - character.SetAllDamage(0.0f, 0.0f, 0.0f); - character.Oxygen = 100.0f; - character.Bloodloss = 0.0f; - character.SetStun(0.0f, true); - } - - protected Item FindOrGiveItem(Character character, Identifier identifier) - { - var item = character.Inventory.FindItemByIdentifier(identifier); - if (item != null && !item.Removed) { return item; } - - ItemPrefab itemPrefab = MapEntityPrefab.Find(name: null, identifier: identifier) as ItemPrefab; - item = new Item(itemPrefab, Vector2.Zero, submarine: null); - character.Inventory.TryPutItem(item, character, item.AllowedSlots); - return item; - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs index 42795e0bf..ea4e51c49 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs @@ -1,30 +1,32 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using System.Reflection; -using System.Linq; -using System.Xml.Linq; -using Barotrauma.Items.Components; -using Barotrauma.Extensions; using System.Collections.Immutable; +using System.Linq; namespace Barotrauma.Tutorials { - enum TutorialContentType { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 }; + enum AutoPlayVideo { Yes, No }; - /// - /// If you're seeing this and are currently working on improving the tutorials, consider - /// deleting this class and all that derive from it, and starting from scratch. - /// - abstract class Tutorial + enum TutorialSegmentType { MessageBox, InfoBox }; + + class Tutorial { #region Constants - public const string PlayableContentPath = "Content/Tutorials/TutorialVideos/"; + + private const string PlayableContentPath = "Content/Tutorials/TutorialVideos/"; + private const SpawnType SpawnPointType = SpawnType.Human; + private const float FadeOutTime = 3f; + private const float WaitBeforeFade = 4f; + #endregion #region Tutorial variables + public static ImmutableHashSet Types; + static Tutorial() { Types = ReflectionUtils.GetDerivedNonAbstract() @@ -37,30 +39,41 @@ namespace Barotrauma.Tutorials public bool ContentRunning { get; protected set; } - protected GUIComponent infoBox; + private GUIComponent infoBox; private Action infoBoxClosedCallback; - protected VideoPlayer videoPlayer; - protected Point screenResolution; - protected WindowMode windowMode; - protected float prevUIScale; + private VideoPlayer videoPlayer; + private Point screenResolution; + private WindowMode windowMode; + private float prevUIScale; - private GUIFrame holderFrame, objectiveFrame; - private readonly List activeObjectives; - private readonly LocalizedString objectiveTranslated; + private GUILayoutGroup objectiveGroup; + private readonly LocalizedString objectiveTextTranslated; - protected readonly ImmutableArray segments; - protected Index activeContentSegmentIndex; - protected Segment activeContentSegment => segments[activeContentSegmentIndex]; + private readonly List ActiveObjectives = new List(); + private const float ObjectiveComponentRemovalTime = 1.5f; + private Segment ActiveContentSegment { get; set; } - protected class Segment + public class Segment { public struct Text { + private const Anchor DefaultAnchor = Anchor.Center; + public Identifier Tag; public int Width; public int Height; public Anchor Anchor; + + public Text(Identifier tag, int? width = null, int? height = null, Anchor? anchor = null) + { + Tag = tag; + Width = width ?? DefaultWidth; + Height = height ?? DefaultHeight; + Anchor = anchor ?? DefaultAnchor; + } + + public Text(string tag, int? width = null, int? height = null, Anchor? anchor = null) : this(tag.ToIdentifier(), width, height, anchor) { } } public struct Video @@ -69,36 +82,63 @@ namespace Barotrauma.Tutorials public Identifier TextTag; public int Width; public int Height; + + public Video(string file, Identifier textTag, int? width = null, int? height = null) + { + File = file; + TextTag = textTag; + Width = width ?? DefaultWidth; + Height = height ?? DefaultHeight; + } + + public Video(string file, string textTag, int? width = null, int? height = null) : this(file, textTag.ToIdentifier(), width, height) { } } - public bool IsTriggered; - public GUIButton ReplayButton; - public GUITextBlock LinkedTitle, LinkedText; - public object[] Args; - public LocalizedString Objective; + private const int DefaultWidth = 450; + private const int DefaultHeight = 80; + + public GUIImage ObjectiveStateIndicator; + public GUIButton ObjectiveButton; + public GUITextBlock LinkedTextBlock; + public LocalizedString ObjectiveText; public readonly Identifier Id; public readonly Text? TextContent; public readonly Video? VideoContent; - public readonly TutorialContentType ContentType; + public readonly AutoPlayVideo AutoPlayVideo; - public Segment(Identifier id, Identifier objectiveTextTag, TutorialContentType contentType, Text? textContent = null, Video? videoContent = null) + public Action OnClickToDisplayMessage; + + public readonly TutorialSegmentType SegmentType; + + public Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text? textContent = null, Video? videoContent = null) { Id = id; - Objective = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag)); - ContentType = contentType; + ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag)); + AutoPlayVideo = autoPlayVideo; TextContent = textContent; VideoContent = videoContent; + SegmentType = TutorialSegmentType.InfoBox; + } - IsTriggered = false; + public Segment(Identifier id, Action onClickToDisplayMessage) + { + Id = id; + var objetiveTextTag = $"{id}.objective".ToIdentifier(); + ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objetiveTextTag)); + OnClickToDisplayMessage = onClickToDisplayMessage; + SegmentType = TutorialSegmentType.MessageBox; } } private bool completed; public bool Completed { - get { return completed; } - protected set + get + { + return completed; + } + private set { if (completed == value) { return; } completed = value; @@ -109,26 +149,150 @@ namespace Barotrauma.Tutorials GameSettings.SaveCurrentConfig(); } } + + public readonly TutorialPrefab TutorialPrefab; + private readonly EventPrefab eventPrefab; + + private CoroutineHandle tutorialCoroutine; + + private Character character; + + private readonly string submarinePath = "Content/Tutorials/Dugong_Tutorial.sub"; + private readonly string startOutpostPath = "Content/Tutorials/TutorialOutpost.sub"; + + private readonly string levelSeed = "nLoZLLtza"; + private readonly string levelParams = "ColdCavernsTutorial"; + + private SubmarineInfo startOutpost = null; + #endregion #region Tutorial Controls - protected Tutorial(Identifier identifier, params Segment[] segments) + + public Tutorial(TutorialPrefab prefab) { - Identifier = identifier; - this.segments = segments.ToImmutableArray(); + Identifier = $"tutorial.{prefab?.Identifier ?? Identifier.Empty}".ToIdentifier(); DisplayName = TextManager.Get(Identifier); - activeObjectives = new List(); - objectiveTranslated = TextManager.Get("Tutorial.Objective"); + objectiveTextTranslated = TextManager.Get("Tutorial.Objective"); + + TutorialPrefab = prefab; + submarinePath = prefab.SubmarinePath.Value; + startOutpostPath = prefab.OutpostPath.Value; + levelSeed = prefab.LevelSeed; + levelParams = prefab.LevelParams; + eventPrefab = EventSet.GetEventPrefab(prefab.EventIdentifier); } - protected abstract IEnumerable Loading(); + private IEnumerable Loading() + { + SubmarineInfo subInfo = new SubmarineInfo(submarinePath); + + LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier == levelParams); + + yield return CoroutineStatus.Running; + + GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null); + (GameMain.GameSession.GameMode as TutorialMode).Tutorial = this; + + if (generationParams != null) + { + Biome biome = + Biome.Prefabs.FirstOrDefault(b => generationParams.AllowedBiomeIdentifiers.Contains(b.Identifier)) ?? + Biome.Prefabs.First(); + + if (!string.IsNullOrEmpty(startOutpostPath)) + { + startOutpost = new SubmarineInfo(startOutpostPath); + } + + LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome); + GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost); + } + else + { + GameMain.GameSession.StartRound(levelSeed); + } + + GameMain.GameSession.EventManager.ActiveEvents.Clear(); + GameMain.GameSession.EventManager.Enabled = true; + GameMain.GameScreen.Select(); + + if (Submarine.MainSub != null) + { + Submarine.MainSub.GodMode = true; + } + foreach (Structure wall in Structure.WallList) + { + if (wall.Submarine != null && wall.Submarine.Info.IsOutpost) + { + wall.Indestructible = true; + } + } + + var charInfo = TutorialPrefab.GetTutorialCharacterInfo(); + + var wayPoint = WayPoint.GetRandom(SpawnPointType, charInfo.Job?.Prefab, Level.Loaded.StartOutpost); + + if (wayPoint == null) + { + DebugConsole.ThrowError("A waypoint with the spawntype \"" + SpawnPointType + "\" is required for the tutorial event"); + yield return CoroutineStatus.Failure; + yield break; + } + + character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false); + character.TeamID = CharacterTeamType.Team1; + Character.Controlled = character; + character.GiveJobItems(null); + + var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier()); + if (idCard == null) + { + DebugConsole.ThrowError("Item prefab \"ID Card\" not found!"); + yield return CoroutineStatus.Failure; + yield break; + } + idCard.AddTag("com"); + idCard.AddTag("eng"); + + foreach (Item item in Item.ItemList) + { + Door door = item.GetComponent(); + if (door != null) + { + door.CanBeWelded = false; + } + } + + tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState()); + + Initialize(); + + yield return CoroutineStatus.Success; + } + + private void Initialize() + { + GameMain.GameSession.CrewManager.AllowCharacterSwitch = TutorialPrefab.AllowCharacterSwitch; + + if (Character.Controlled is Character character) + { + foreach (Item item in character.Inventory.AllItemsMod) + { + if (item.HasTag(TutorialPrefab.StartingItemTags)) { continue; } + item.Unequip(character); + character.Inventory.RemoveItem(item); + } + } + } public void Start() { videoPlayer = new VideoPlayer(); GameMain.Instance.ShowLoading(Loading()); - - activeObjectives.Clear(); + ActiveObjectives.Clear(); + ActiveContentSegment = null; + CreateObjectiveFrame(); // Setup doors: Clear all requirements, unless the door is setup as locked. @@ -145,35 +309,73 @@ namespace Barotrauma.Tutorials } } - public virtual void AddToGUIUpdateList() + public void AddToGUIUpdateList() { if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale || GameSettings.CurrentConfig.Graphics.DisplayMode != windowMode) { CreateObjectiveFrame(); } - - if (objectiveFrame != null && activeObjectives.Count > 0) + if (ActiveObjectives.Count > 0) { - objectiveFrame.AddToGUIUpdateList(order: -1); + objectiveGroup?.AddToGUIUpdateList(order: -1); } - - if (infoBox != null) infoBox.AddToGUIUpdateList(order: 100); - if (videoPlayer != null) videoPlayer.AddToGUIUpdateList(order: 100); + infoBox?.AddToGUIUpdateList(order: 100); + videoPlayer?.AddToGUIUpdateList(order: 100); } - public virtual void Update(float deltaTime) + public void Update() { videoPlayer?.Update(); - if (activeObjectives != null) + if (character != null) { - for (int i = 0; i < activeObjectives.Count; i++) + if (character.Oxygen < 1) { - CheckActiveObjectives(activeObjectives[i], deltaTime); + character.Oxygen = 1; + } + if (character.IsDead) + { + CoroutineManager.StartCoroutine(Dead()); + } + else if (Character.Controlled == null) + { + if (tutorialCoroutine != null) + { + CoroutineManager.StopCoroutines(tutorialCoroutine); + } + GUI.PreventPauseMenuToggle = false; + ContentRunning = false; + infoBox = null; + } + else if (Character.Controlled.IsDead) + { + CoroutineManager.StartCoroutine(Dead()); } } } + private IEnumerable Dead() + { + GUI.PreventPauseMenuToggle = true; + Character.Controlled = character = null; + Stop(); + + GameAnalyticsManager.AddDesignEvent("Tutorial:Died"); + + yield return new WaitForSeconds(3.0f); + + var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }); + + messageBox.Buttons[0].OnClicked += Restart; + messageBox.Buttons[0].OnClicked += messageBox.Close; + + + messageBox.Buttons[1].OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu; + messageBox.Buttons[1].OnClicked += messageBox.Close; + + yield return CoroutineStatus.Success; + } + public void CloseActiveContentGUI() { if (videoPlayer.IsPlaying) @@ -182,257 +384,307 @@ namespace Barotrauma.Tutorials } else if (infoBox != null) { - CloseInfoFrame(null, null); + CloseInfoFrame(); } } - public virtual IEnumerable UpdateState() + public IEnumerable UpdateState() { + while (GameMain.Instance.LoadingScreenOpen || Level.Loaded == null || Level.Loaded.Generating) + { + yield return new WaitForSeconds(0.1f); + } + + if (eventPrefab == null) + { + DebugConsole.ShowError($"No tutorial event defined for the tutorial (identifier: \"{TutorialPrefab?.Identifier.ToString() ?? "null"})\""); + yield return CoroutineStatus.Failure; + } + + if (eventPrefab.CreateInstance() is Event eventInstance) + { + GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventInstance); + while (!eventInstance.IsFinished) + { + yield return CoroutineStatus.Running; + } + } + else + { + DebugConsole.ShowError($"Failed to create an instance for a tutorial event (identifier: \"{eventPrefab.Identifier}\""); + yield return CoroutineStatus.Failure; + } + yield return CoroutineStatus.Success; } - protected bool Restart(GUIButton button, object obj) + public void Complete() + { + GameAnalyticsManager.AddDesignEvent($"Tutorial:{Identifier}:Completed"); + CoroutineManager.StartCoroutine(TutorialCompleted()); + + IEnumerable TutorialCompleted() + { + GUI.PreventPauseMenuToggle = true; + Character.Controlled.ClearInputs(); + Character.Controlled = null; + GameAnalyticsManager.AddDesignEvent("Tutorial:Completed"); + + yield return new WaitForSeconds(WaitBeforeFade); + + var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: FadeOutTime); + Completed = true; + + while (endCinematic.Running) { yield return null; } + + Stop(); + GameMain.MainMenuScreen.ReturnToMainMenu(null, null); + } + } + + private bool Restart(GUIButton button, object obj) { GUI.PreventPauseMenuToggle = false; return true; } - protected virtual void TriggerTutorialSegment(Index index, params object[] args) + public void TriggerTutorialSegment(Segment segment) { + if (segment.SegmentType == TutorialSegmentType.MessageBox) + { + ActiveObjectives.Add(segment); + AddToObjectiveList(segment); + return; + } + Inventory.DraggingItems.Clear(); ContentRunning = true; - activeContentSegmentIndex = index; - segments[index].Args = args; + ActiveContentSegment = segment; - LocalizedString tutorialText = TextManager.GetFormatted(segments[index].TextContent.Value.Tag, args); + var title = TextManager.Get(segment.Id); + LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Value.Tag); tutorialText = TextManager.ParseInputTypes(tutorialText); - LocalizedString objectiveText = string.Empty; - if (!segments[index].Objective.IsNullOrEmpty()) + switch (segment.AutoPlayVideo) { - if (args.Length == 0) - { - objectiveText = segments[index].Objective; - } - else - { - objectiveText = TextManager.GetFormatted(segments[index].Objective, args); - } - objectiveText = TextManager.ParseInputTypes(objectiveText); - segments[index].Objective = objectiveText; - } - else - { - segments[index].IsTriggered = true; // Complete at this stage only if no related objective - } - - - switch (segments[index].ContentType) - { - case TutorialContentType.None: + case AutoPlayVideo.Yes: + infoBox = CreateInfoFrame( + title, + tutorialText, + segment.TextContent.Value.Width, + segment.TextContent.Value.Height, + segment.TextContent.Value.Anchor, + hasButton: true, + onInfoBoxClosed: LoadActiveContentVideo); break; - case TutorialContentType.Video: - infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, - activeContentSegment.TextContent.Value.Width, - activeContentSegment.TextContent.Value.Height, - activeContentSegment.TextContent.Value.Anchor, true, () => LoadVideo(activeContentSegment)); - break; - case TutorialContentType.ManualVideo: - infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, - activeContentSegment.TextContent.Value.Width, - activeContentSegment.TextContent.Value.Height, - activeContentSegment.TextContent.Value.Anchor, true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment)); - break; - case TutorialContentType.TextOnly: - infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, - activeContentSegment.TextContent.Value.Width, - activeContentSegment.TextContent.Value.Height, - activeContentSegment.TextContent.Value.Anchor, true, StopCurrentContentSegment); + case AutoPlayVideo.No: + infoBox = CreateInfoFrame( + title, + tutorialText, + segment.TextContent.Value.Width, + segment.TextContent.Value.Height, + segment.TextContent.Value.Anchor, + hasButton: true, + onInfoBoxClosed: StopCurrentContentSegment, + onVideoButtonClicked: LoadActiveContentVideo); break; } } - public virtual void Stop() + public void CompleteTutorialSegment(Identifier segmentId) { + if (!(GetActiveObjective(segmentId) is Segment segment)) + { + DebugConsole.AddWarning($"Warning: tried to complete the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!"); + return; + } + if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style) + { + segment.ObjectiveStateIndicator.ApplyStyle(style); + segment.ObjectiveStateIndicator.Flash(color: GUIStyle.Green); + } + segment.ObjectiveButton.OnClicked = null; + segment.ObjectiveButton.CanBeFocused = false; + } + + public void RemoveTutorialSegment(Identifier segmentId) + { + if (!(GetActiveObjective(segmentId) is Segment segment)) + { + DebugConsole.AddWarning($"Warning: tried to remove the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!"); + return; + } + segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentRemovalTime, false); + segment.LinkedTextBlock.FadeOut(ObjectiveComponentRemovalTime, false); + var parent = segment.LinkedTextBlock.Parent; + parent.FadeOut(ObjectiveComponentRemovalTime, true, onRemove: () => + { + ActiveObjectives.Remove(segment); + objectiveGroup?.Recalculate(); + }); + var targetPos = new Point(GameMain.GraphicsWidth - parent.Rect.X, 0); + parent.RectTransform.MoveOverTime(targetPos, ObjectiveComponentRemovalTime); + segment.ObjectiveButton.OnClicked = null; + segment.ObjectiveButton.CanBeFocused = false; + } + + private Segment GetActiveObjective(Identifier id) => ActiveObjectives.FirstOrDefault(s => s.Id == id); + + public void Stop() + { + if (tutorialCoroutine != null) + { + CoroutineManager.StopCoroutines(tutorialCoroutine); + } ContentRunning = false; infoBox = null; - videoPlayer.Remove(); + videoPlayer?.Remove(); } + #endregion #region Objectives + + /// + /// Create the objective list that holds the objectives (called on start and on resolution change) + /// private void CreateObjectiveFrame() { - holderFrame = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center)); - objectiveFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ObjectiveAnchor, holderFrame.RectTransform), style: null); - - for (int i = 0; i < activeObjectives.Count; i++) + var objectiveListFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), style: null); + objectiveGroup = new GUILayoutGroup(new RectTransform(Vector2.One, objectiveListFrame.RectTransform)) { - CreateObjectiveGUI(activeObjectives[i], i, segments[activeObjectives[i]].ContentType); + AbsoluteSpacing = (int)GUIStyle.Font.LineHeight + }; + for (int i = 0; i < ActiveObjectives.Count; i++) + { + AddToObjectiveList(ActiveObjectives[i]); } - screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); windowMode = GameSettings.CurrentConfig.Graphics.DisplayMode; prevUIScale = GUI.Scale; } - protected void StopCurrentContentSegment() + /// + /// Stops content running and adds the active segment to the objective list + /// + private void StopCurrentContentSegment() { - if (!activeContentSegment.Objective.IsNullOrEmpty()) + if (!ActiveContentSegment.ObjectiveText.IsNullOrEmpty()) { - AddNewObjective(activeContentSegmentIndex, activeContentSegment.ContentType); + ActiveObjectives.Add(ActiveContentSegment); + AddToObjectiveList(ActiveContentSegment); } - ContentRunning = false; - activeContentSegmentIndex = Index.End; + ActiveContentSegment = null; } - protected virtual void CheckActiveObjectives(Index objective, float deltaTime) + /// + /// Adds the segment to the objective list + /// + private void AddToObjectiveList(Segment segment) { - - } - - protected bool HasObjective(Index segment) - { - return activeObjectives.Contains(segment); - } - - protected void AddNewObjective(Index segment, TutorialContentType type) - { - activeObjectives.Add(segment); - CreateObjectiveGUI(segment, activeObjectives.Count - 1, type); - } - - private void CreateObjectiveGUI(Index segmentIndex, int index, TutorialContentType type) - { - var segment = segments[segmentIndex]; - LocalizedString objectiveText = TextManager.ParseInputTypes(segment.Objective); - Point replayButtonSize = new Point((int)(GUIStyle.LargeFont.MeasureString(objectiveText).X), (int)(GUIStyle.LargeFont.MeasureString(objectiveText).Y * 1.45f)); - - segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null); - segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) => + var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform) { - if (type == TutorialContentType.Video) - { - ReplaySegmentVideo(segment); - } - else - { - ShowSegmentText(segment); - } - return true; + MinSize = new Point(0, objectiveGroup.AbsoluteSpacing) + }; + var frame = new GUIFrame(frameRt, style: null) + { + CanBeFocused = true }; - LocalizedString objectiveTitleText = TextManager.ParseInputTypes(objectiveTranslated); - int yOffset = (int)((GUIStyle.SubHeadingFont.MeasureString(objectiveTitleText).Y + 5)); - segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point((int)GUIStyle.SubHeadingFont.MeasureString(objectiveTitleText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.BottomLeft) /*{ AbsoluteOffset = new Point((int)(-10 * GUI.Scale), 0) }*/, - objectiveTitleText, textColor: Color.White, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft) - { - ForceUpperCase = ForceUpperCase.Yes - }; + segment.LinkedTextBlock = new GUITextBlock( + new RectTransform(new Point(frameRt.Rect.Width - objectiveGroup.AbsoluteSpacing, 0), frame.RectTransform, anchor: Anchor.TopRight), + TextManager.ParseInputTypes(segment.ObjectiveText), + wrap: true); - segment.LinkedText = new GUITextBlock(new RectTransform(new Point((int)GUIStyle.LargeFont.MeasureString(objectiveText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.TopLeft) /*{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }*/, - objectiveText, textColor: new Color(4, 180, 108), font: GUIStyle.LargeFont, textAlignment: Alignment.CenterLeft); - - segment.LinkedTitle.Color = segment.LinkedTitle.HoverColor = segment.LinkedTitle.PressedColor = segment.LinkedTitle.SelectedColor = Color.Transparent; - segment.LinkedText.Color = segment.LinkedText.HoverColor = segment.LinkedText.PressedColor = segment.LinkedText.SelectedColor = Color.Transparent; - segment.ReplayButton.Color = segment.ReplayButton.HoverColor = segment.ReplayButton.PressedColor = segment.ReplayButton.SelectedColor = Color.Transparent; + var size = new Point(segment.LinkedTextBlock.Rect.Width, segment.LinkedTextBlock.Rect.Height); + segment.LinkedTextBlock.RectTransform.NonScaledSize = size; + segment.LinkedTextBlock.RectTransform.MinSize = size; + segment.LinkedTextBlock.RectTransform.MaxSize = size; + segment.LinkedTextBlock.RectTransform.IsFixedSize = true; + frame.RectTransform.Resize(new Point(frame.Rect.Width, segment.LinkedTextBlock.RectTransform.Rect.Height), resizeChildren: false); + frame.RectTransform.IsFixedSize = true; + + var indicatorRt = new RectTransform(new Point(objectiveGroup.AbsoluteSpacing), frame.RectTransform, isFixedSize: true); + segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete");; + + SetTransparent(segment.LinkedTextBlock); + + segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null) + { + ToolTip = objectiveTextTranslated, + OnClicked = (GUIButton btn, object userdata) => + { + if (segment.SegmentType == TutorialSegmentType.InfoBox) + { + if (segment.AutoPlayVideo == AutoPlayVideo.Yes) + { + ReplaySegmentVideo(segment); + } + else + { + ShowSegmentText(segment); + } + } + else if (segment.SegmentType == TutorialSegmentType.MessageBox) + { + segment.OnClickToDisplayMessage?.Invoke(); + } + return true; + } + }; + SetTransparent(segment.ObjectiveButton); + + static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent; } private void ReplaySegmentVideo(Segment segment) { - if (ContentRunning) return; + if (ContentRunning) { return; } Inventory.DraggingItems.Clear(); ContentRunning = true; LoadVideo(segment); - //videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false); } private void ShowSegmentText(Segment segment) { - if (ContentRunning) return; + if (ContentRunning) { return; } Inventory.DraggingItems.Clear(); ContentRunning = true; - - LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Value.Tag, segment.Args); - - Action videoAction = null; - - if (segment.ContentType != TutorialContentType.TextOnly) - { - videoAction = () => LoadVideo(segment); - } - - infoBox = CreateInfoFrame(TextManager.Get(segment.Id), tutorialText, - segment.TextContent.Value.Width, - segment.TextContent.Value.Height, - segment.TextContent.Value.Anchor, true, () => ContentRunning = false, videoAction); - } - - protected void RemoveCompletedObjective(Index segmentIndex) - { - if (!HasObjective(segmentIndex)) return; - var segment = segments[segmentIndex]; - segment.IsTriggered = true; - segment.ReplayButton.OnClicked = null; - - int checkMarkHeight = (int)(segment.ReplayButton.Rect.Height * 1.2f); - int checkMarkWidth = (int)(checkMarkHeight * 0.93f); - - Color color = new Color(4, 180, 108); - - int objectiveTextWidth = segment.LinkedText.Rect.Width; - int objectiveTitleWidth = segment.LinkedTitle.Rect.Width; - - RectTransform rectTA; - if (objectiveTextWidth > objectiveTitleWidth) - { - rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight); - rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale), 0); - } - else - { - rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight); - rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale) - (objectiveTitleWidth - objectiveTextWidth), 0); - } - - GUIImage checkmark = new GUIImage(rectTA, "CheckMark"); - checkmark.Color = checkmark.SelectedColor = checkmark.HoverColor = checkmark.PressedColor = color; - - RectTransform rectTB = new RectTransform(new Vector2(1.0f, .8f), segment.LinkedText.RectTransform, Anchor.Center, Pivot.Center); - GUIImage stroke = new GUIImage(rectTB, "Stroke"); - stroke.Color = stroke.SelectedColor = stroke.HoverColor = stroke.PressedColor = color; - - CoroutineManager.StartCoroutine(WaitForObjectiveEnd(segmentIndex)); - } - - private IEnumerable WaitForObjectiveEnd(Index objectiveIndex) - { - var objective = segments[objectiveIndex]; - yield return new WaitForSeconds(2.0f); - objectiveFrame.RemoveChild(objective.ReplayButton); - activeObjectives.Remove(objectiveIndex); - - for (int i = 0; i < activeObjectives.Count; i++) - { - var activeObjective = segments[activeObjectives[i]]; - activeObjective.ReplayButton.RectTransform.AbsoluteOffset = new Point(0, (activeObjective.ReplayButton.Rect.Height + 20) * i); - } + ActiveContentSegment = segment; + infoBox = CreateInfoFrame( + TextManager.Get(segment.Id), + TextManager.Get(segment.TextContent.Value.Tag), + segment.TextContent.Value.Width, + segment.TextContent.Value.Height, + segment.TextContent.Value.Anchor, + hasButton: true, + onInfoBoxClosed: () => ContentRunning = false, + onVideoButtonClicked: () => LoadVideo(segment)); } #endregion #region InfoFrame - protected bool CloseInfoFrame(GUIButton button, object userData) + + private void CloseInfoFrame() => CloseInfoFrame(null, null); + + private bool CloseInfoFrame(GUIButton button, object userData) { infoBox = null; infoBoxClosedCallback?.Invoke(); return true; } - protected GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action callback = null, Action showVideo = null) + /// + // Creates and displays a tutorial info box + /// + private GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action onInfoBoxClosed = null, Action onVideoButtonClicked = null) { - if (hasButton) height += 60; + if (hasButton) + { + height += 60; + } width = (int)(width * GUI.Scale); height = (int)(height * GUI.Scale); @@ -467,7 +719,7 @@ namespace Barotrauma.Tutorials GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true); textBlock.RectTransform.IsFixedSize = true; - infoBoxClosedCallback = callback; + infoBoxClosedCallback = onInfoBoxClosed; if (hasButton) { @@ -477,7 +729,7 @@ namespace Barotrauma.Tutorials }; buttonContainer.RectTransform.IsFixedSize = true; - if (showVideo != null) + if (onVideoButtonClicked != null) { buttonContainer.Stretch = true; var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform), @@ -485,7 +737,7 @@ namespace Barotrauma.Tutorials { OnClicked = (GUIButton button, object obj) => { - showVideo(); + onVideoButtonClicked(); return true; } }; @@ -509,57 +761,39 @@ namespace Barotrauma.Tutorials return background; } + #endregion #region Video - protected void LoadVideo(Segment segment) + + private void LoadVideo(Segment segment) { - if (videoPlayer == null) videoPlayer = new VideoPlayer(); - if (segment.ContentType != TutorialContentType.ManualVideo) + videoPlayer ??= new VideoPlayer(); + if (segment.AutoPlayVideo == AutoPlayVideo.Yes) { videoPlayer.LoadContent( - PlayableContentPath, - new VideoPlayer.VideoSettings(segment.VideoContent.Value.File), - new VideoPlayer.TextSettings(segment.VideoContent.Value.TextTag, segment.VideoContent.Value.Width), - segment.Id, true, segment.Objective, StopCurrentContentSegment); + contentPath: PlayableContentPath, + videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.Value.File), + textSettings: new VideoPlayer.TextSettings(segment.VideoContent.Value.TextTag, segment.VideoContent.Value.Width), + contentId: segment.Id, + startPlayback: true, + objective: segment.ObjectiveText, + onStop: StopCurrentContentSegment); } else { - videoPlayer.LoadContent(PlayableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent.Value.File), null, segment.Id, true, string.Empty, null); - } - } - #endregion - - #region Highlights - protected void HighlightInventorySlot(Inventory inventory, Identifier identifier, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) - { - if (inventory.visualSlots == null) { return; } - for (int i = 0; i < inventory.Capacity; i++) - { - if (inventory.GetItemAt(i)?.Prefab.Identifier == identifier) - { - HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount); - } + videoPlayer.LoadContent( + contentPath: PlayableContentPath, + videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.Value.File), + textSettings: null, + contentId: segment.Id, + startPlayback: true, + objective: string.Empty); } } - protected void HighlightInventorySlotWithTag(Inventory inventory, Identifier tag, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) - { - if (inventory.visualSlots == null) { return; } - for (int i = 0; i < inventory.Capacity; i++) - { - if (inventory.GetItemAt(i)?.HasTag(tag) ?? false) - { - HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount); - } - } - } + private void LoadActiveContentVideo() => LoadVideo(ActiveContentSegment); - protected void HighlightInventorySlot(Inventory inventory, int index, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) - { - if (inventory.visualSlots == null || index < 0 || inventory.visualSlots[index].HighlightTimer > 0) { return; } - inventory.visualSlots[index].ShowBorderHighlight(color, fadeInDuration, fadeOutDuration, scaleUpAmount); - } #endregion } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/TutorialMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/TutorialMode.cs index 2b904f82f..d155a1c23 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/TutorialMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/TutorialMode.cs @@ -6,10 +6,7 @@ namespace Barotrauma { public Tutorial Tutorial; - public TutorialMode(GameModePreset preset) - : base(preset) - { - } + public TutorialMode(GameModePreset preset) : base(preset) { } public override void Start() { @@ -31,7 +28,7 @@ namespace Barotrauma public override void Update(float deltaTime) { base.Update(deltaTime); - Tutorial.Update(deltaTime); + Tutorial.Update(); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/MedicalClinic.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/MedicalClinic.cs index 0811b26ce..c06efe8d5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/MedicalClinic.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/MedicalClinic.cs @@ -354,14 +354,14 @@ namespace Barotrauma private static IWriteMessage StartSending() { IWriteMessage writeMessage = new WriteOnlyMessage(); - writeMessage.Write((byte)ClientPacketHeader.MEDICAL); + writeMessage.WriteByte((byte)ClientPacketHeader.MEDICAL); return writeMessage; } private static void ClientSend(INetSerializableStruct? netStruct, NetworkHeader header, DeliveryMethod deliveryMethod) { IWriteMessage msg = StartSending(); - msg.Write((byte)header); + msg.WriteByte((byte)header); netStruct?.Write(msg); GameMain.Client.ClientPeer?.Send(msg, deliveryMethod); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/ReadyCheck.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/ReadyCheck.cs index 1d1b488d3..eed8abac4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/ReadyCheck.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/ReadyCheck.cs @@ -139,7 +139,7 @@ namespace Barotrauma public static void ClientRead(IReadMessage inc) { - ReadyCheckState state = (ReadyCheckState) inc.ReadByte(); + ReadyCheckState state = (ReadyCheckState)inc.ReadByte(); CrewManager? crewManager = GameMain.GameSession?.CrewManager; var otherClients = GameMain.Client.ConnectedClients; if (crewManager == null || otherClients == null) @@ -196,7 +196,7 @@ namespace Barotrauma } break; case ReadyCheckState.Update: - ReadyStatus newState = (ReadyStatus) inc.ReadByte(); + ReadyStatus newState = (ReadyStatus)inc.ReadByte(); byte targetId = inc.ReadByte(); if (crewManager.ActiveReadyCheck != null) { @@ -208,7 +208,7 @@ namespace Barotrauma for (int i = 0; i < count; i++) { byte id = inc.ReadByte(); - ReadyStatus status = (ReadyStatus) inc.ReadByte(); + ReadyStatus status = (ReadyStatus)inc.ReadByte(); crewManager.ActiveReadyCheck?.UpdateState(id, status); } @@ -269,9 +269,9 @@ namespace Barotrauma private static void SendState(ReadyStatus status) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte) ClientPacketHeader.READY_CHECK); - msg.Write((byte) ReadyCheckState.Update); - msg.Write((byte) status); + msg.WriteByte((byte)ClientPacketHeader.READY_CHECK); + msg.WriteByte((byte)ReadyCheckState.Update); + msg.WriteByte((byte)status); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); } @@ -283,8 +283,8 @@ namespace Barotrauma ReadyCheckCooldown = DateTime.Now.AddMinutes(1); #endif IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte) ClientPacketHeader.READY_CHECK); - msg.Write((byte) ReadyCheckState.Start); + msg.WriteByte((byte)ClientPacketHeader.READY_CHECK); + msg.WriteByte((byte)ReadyCheckState.Start); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Growable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Growable.cs index 8428d53ff..5b8af4a6a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Growable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Growable.cs @@ -166,10 +166,10 @@ namespace Barotrauma.Items.Components List tiles = new List(); for (int i = 0; i < vineCount; i++) { - VineTileType vineType = (VineTileType) msg.ReadRangedInteger(0b0000, 0b1111); + VineTileType vineType = (VineTileType)msg.ReadRangedInteger(0b0000, 0b1111); int flowerConfig = msg.ReadRangedInteger(0, 0xFFF); int leafConfig = msg.ReadRangedInteger(0, 0xFFF); - sbyte posX = (sbyte) msg.ReadByte(), posY = (sbyte) msg.ReadByte(); + sbyte posX = (sbyte)msg.ReadByte(), posY = (sbyte)msg.ReadByte(); Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size); tiles.Add(new VineTile(this, pos, vineType, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig))); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Holdable.cs index 77da549e2..5380f8940 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Holdable.cs @@ -69,8 +69,8 @@ namespace Barotrauma.Items.Components var eventData = ExtractEventData(extraData); Vector2 attachPos = eventData.AttachPos; - msg.Write(attachPos.X); - msg.Write(attachPos.Y); + msg.WriteSingle(attachPos.X); + msg.WriteSingle(attachPos.Y); } public override void ClientEventRead(IReadMessage msg, float sendingTime) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index ebbc45e0b..a1b1857e8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -66,6 +66,8 @@ namespace Barotrauma.Items.Components public float IsActiveTimer; + public virtual bool RecreateGUIOnResolutionChange => false; + public GUILayoutSettings DefaultLayout { get; protected set; } public GUILayoutSettings AlternativeLayout { get; protected set; } @@ -574,7 +576,7 @@ namespace Barotrauma.Items.Components { GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged; } - GameMain.Instance.ResolutionChanged += OnResolutionChanged; + GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate; } protected void TryCreateDragHandle() @@ -610,13 +612,17 @@ namespace Barotrauma.Items.Components return false; } } + foreach (ItemComponent ic in activeHuds) + { + //refresh slots to ensure they're rendered at the correct position + (ic as ItemContainer)?.Inventory.CreateSlots(); + } return true; }; - int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f); - new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 10) }, - style: "GUIButtonRefresh") + new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4) }, + style: "GUIButtonSettings") { OnClicked = (btn, userdata) => { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index 5fe21e897..a759f9ac7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -45,6 +45,8 @@ namespace Barotrauma.Items.Components private set; } + public override bool RecreateGUIOnResolutionChange => true; + /// /// Depth at which the contained sprites are drawn. If not set, the original depth of the item sprites is used. /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs index 5bfc4e192..332f924cd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs @@ -90,8 +90,8 @@ namespace Barotrauma.Items.Components { Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None; - SetLightSourceTransformProjSpecific(); } + SetLightSourceTransformProjSpecific(); } partial void OnStateChanged() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index 1c4dfe80b..5a31e20c3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Linq; using System.Xml.Linq; +using Barotrauma.Extensions; namespace Barotrauma.Items.Components { @@ -33,14 +34,18 @@ namespace Barotrauma.Items.Components [Serialize(0.0f, IsPropertySaveable.Yes)] public float InfoAreaWidth { get; set; } - partial void InitProjSpecific(XElement element) + [Serialize(true, IsPropertySaveable.Yes)] + public bool ShowOutput { get; set; } + + partial void InitProjSpecific(XElement _) { CreateGUI(); } + public override bool RecreateGUIOnResolutionChange => true; + protected override void OnResolutionChanged() { - base.OnResolutionChanged(); OnItemLoadedProjSpecific(); } @@ -122,11 +127,14 @@ namespace Barotrauma.Items.Components // === OUTPUT SLOTS === // outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null); - GUILayoutGroup outputDisplayLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedFrame.RectTransform), childAnchor: Anchor.TopCenter); - GUILayoutGroup outDisplayTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.2f), outputDisplayLayout.RectTransform), isHorizontal: true); - GUITextBlock outDisplayBlock = new GUITextBlock(new RectTransform(Vector2.One, outDisplayTopGroup.RectTransform), TextManager.Get("deconstructor.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; - GUILayoutGroup outDisplayBottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.975f, 0.8f), outputDisplayLayout.RectTransform), isHorizontal: true); - outputDisplayListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), outDisplayBottomGroup.RectTransform), isHorizontal: true, style: null); + if (ShowOutput) + { + GUILayoutGroup outputDisplayLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedFrame.RectTransform), childAnchor: Anchor.TopCenter); + GUILayoutGroup outDisplayTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.2f), outputDisplayLayout.RectTransform), isHorizontal: true); + GUITextBlock outDisplayBlock = new GUITextBlock(new RectTransform(Vector2.One, outDisplayTopGroup.RectTransform), TextManager.Get("deconstructor.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; + GUILayoutGroup outDisplayBottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.975f, 0.8f), outputDisplayLayout.RectTransform), isHorizontal: true); + outputDisplayListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), outDisplayBottomGroup.RectTransform), isHorizontal: true, style: null); + } if (InfoAreaWidth >= 0.0f) { @@ -255,16 +263,17 @@ namespace Barotrauma.Items.Components foreach (DeconstructItem deconstructItem in it.Prefab.DeconstructItems) { + if (!deconstructItem.IsValidDeconstructor(item)) { continue; } RegisterItem(deconstructItem.ItemIdentifier, deconstructItem.Amount); } - if (it.OwnInventory is { } inventory) + /*if (it.OwnInventory is { } inventory) { foreach (Item inventoryItems in inventory.AllItems) { RegisterItem(inventoryItems.Prefab.Identifier); } - } + }*/ void RegisterItem(Identifier identifier, int amount = 1) { @@ -383,6 +392,8 @@ namespace Barotrauma.Items.Components inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform; outputContainer.AllowUIOverlap = true; outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform; + + inputContainer.Inventory.Locked = IsActive; } private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) @@ -446,7 +457,7 @@ namespace Barotrauma.Items.Components public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { - msg.Write(pendingState); + msg.WriteBoolean(pendingState); } public void ClientEventRead(IReadMessage msg, float sendingTime) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 90849cd19..0f81a432a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -51,11 +51,13 @@ namespace Barotrauma.Items.Components [Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)] public string FabricationLimitReachedText { get; set; } + public override bool RecreateGUIOnResolutionChange => true; + protected override void OnResolutionChanged() { if (GuiFrame != null) { - OnItemLoadedProjSpecific(); + InitInventoryUIs(); } } @@ -232,6 +234,17 @@ namespace Barotrauma.Items.Components } } + private void InitInventoryUIs() + { + if (inputInventoryHolder != null) + { + inputContainer.AllowUIOverlap = true; + inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform; + } + outputContainer.AllowUIOverlap = true; + outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform; + } + private LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe) { if (fabricationRecipe == null) { return ""; } @@ -249,13 +262,7 @@ namespace Barotrauma.Items.Components partial void OnItemLoadedProjSpecific() { CreateGUI(); - if (inputInventoryHolder != null) - { - inputContainer.AllowUIOverlap = true; - inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform; - } - outputContainer.AllowUIOverlap = true; - outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform; + InitInventoryUIs(); } partial void SelectProjSpecific(Character character) @@ -328,76 +335,112 @@ namespace Barotrauma.Items.Components } } + private readonly Dictionary missingIngredientCounts = new Dictionary(); + private float ingredientHighlightTimer; + private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) { overlayComponent.RectTransform.SetAsLastChild(); + missingIngredientCounts.Clear(); + FabricationRecipe targetItem = fabricatedItem ?? selectedItem; if (targetItem != null) { - int slotIndex = 0; - - var missingItems = new List(); - foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems) { - for (int i = 0; i < requiredItem.Amount; i++) + if (missingIngredientCounts.ContainsKey(requiredItem)) { - missingItems.Add(requiredItem); + missingIngredientCounts[requiredItem] += requiredItem.Amount; + } + else + { + missingIngredientCounts[requiredItem] = requiredItem.Amount; } } foreach (Item item in inputContainer.Inventory.AllItems) { - missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefabs.Contains(item.Prefab))); - } - var missingCounts = missingItems.GroupBy(missingItem => missingItem).ToDictionary(x => x.Key, x => x.Count()); - missingItems = missingItems.Distinct().ToList(); + var missingIngredient = missingIngredientCounts.Keys.FirstOrDefault(mi => mi.MatchesItem(item)); + if (missingIngredient == null) { continue; } - foreach (FabricationRecipe.RequiredItem requiredItem in missingItems) + if (missingIngredientCounts[missingIngredient] == 1) + { + missingIngredientCounts.Remove(missingIngredient); + } + else + { + missingIngredientCounts[missingIngredient]--; + } + } + + if (ingredientHighlightTimer <= 0.0f) { + //highlight inventory slots that contain suitable ingredients in linked inventories + foreach (var inventory in linkedInventories) + { + if (inventory.visualSlots == null) { continue; } + for (int i = 0; i < inventory.Capacity; i++) + { + if (inventory.visualSlots[i].HighlightTimer > 0.0f) { continue; } + var availableItem = inventory.GetItemAt(i); + if (availableItem == null) { continue; } + + if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableItem))) + { + inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f); + continue; + } + if (availableItem.OwnInventory != null) + { + for (int j = 0; j < availableItem.OwnInventory.Capacity; j++) + { + var availableContainedItem = availableItem.OwnInventory.GetItemAt(i); + if (availableContainedItem == null) { continue; } + if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableContainedItem))) + { + inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f); + break; + } + } + } + } + } + ingredientHighlightTimer = 1.0f; + } + + int slotIndex = 0; + foreach (var kvp in missingIngredientCounts) + { + var requiredItem = kvp.Key; + int missingCount = kvp.Value; + while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.GetItemAt(slotIndex) != null) { slotIndex++; } - requiredItem.ItemPrefabs - .Where(requiredPrefab => availableIngredients.ContainsKey(requiredPrefab.Identifier)) - .ForEach(requiredPrefab => { - var availableItems = availableIngredients[requiredPrefab.Identifier]; - foreach (Item it in availableItems) - { - if (it.ParentInventory == inputContainer.Inventory) { continue; } - var rootInventoryOwner = it.GetRootInventoryOwner(); - Inventory rootInventory = (rootInventoryOwner as Item)?.OwnInventory as Inventory ?? (rootInventoryOwner as Character)?.Inventory; - if (rootInventory?.visualSlots == null) { continue; } - int availableSlotIndex = rootInventory.FindIndex((it.Container != rootInventoryOwner ? it.Container : it) ?? it); - if (availableSlotIndex < 0) { continue; } - if (rootInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f) - { - rootInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f); - if (slotIndex < inputContainer.Capacity) - { - inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f); - } - } - } - }); - if (slotIndex >= inputContainer.Capacity) { break; } - var itemIcon = requiredItem.ItemPrefabs.First().InventoryIcon ?? requiredItem.ItemPrefabs.First().Sprite; + if (slotIndex < inputContainer.Capacity && + inputContainer.Inventory.visualSlots[slotIndex].HighlightTimer <= 0.0f && + availableIngredients.Any(i => i.Value.Any() && requiredItem.MatchesItem(i.Value.First()))) + { + inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f); + } + + var requiredItemPrefab = requiredItem.FirstMatchingPrefab; + var itemIcon = requiredItemPrefab.InventoryIcon ?? requiredItemPrefab.Sprite; Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect; itemIcon.Draw( spriteBatch, slotRect.Center.ToVector2(), - color: requiredItem.ItemPrefabs.First().InventoryIconColor * 0.3f, + color: requiredItemPrefab.InventoryIconColor * 0.3f, scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y)); - - - if (missingCounts[requiredItem] > 1) + + if (missingCount > 1) { Vector2 stackCountPos = new Vector2(slotRect.Right, slotRect.Bottom); - string stackCountText = "x" + missingCounts[requiredItem]; + string stackCountText = "x" + missingCount; stackCountPos -= GUIStyle.SmallFont.MeasureString(stackCountText) + new Vector2(4, 2); GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black); GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White); @@ -446,9 +489,9 @@ namespace Barotrauma.Items.Components { toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText); } - if (!requiredItem.ItemPrefabs.First().Description.IsNullOrEmpty()) + if (!requiredItemPrefab.Description.IsNullOrEmpty()) { - toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description; + toolTipText += '\n' + requiredItemPrefab.Description; } tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText }; } @@ -715,6 +758,8 @@ namespace Barotrauma.Items.Components activateButton.Enabled = false; inSufficientPowerWarning.Visible = IsActive && !hasPower; + ingredientHighlightTimer -= deltaTime; + if (!IsActive) { //only check ingredients if the fabricator isn't active (if it is, this is done in Update) @@ -763,7 +808,7 @@ namespace Barotrauma.Items.Components public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0; - msg.Write(recipeHash); + msg.WriteUInt32(recipeHash); } public void ClientEventRead(IReadMessage msg, float sendingTime) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 5a917c369..fce57ab20 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -510,7 +510,7 @@ namespace Barotrauma.Items.Components Vector2 origin = weaponSprite.Origin; float scale = parentWidth / Math.Max(weaponSprite.size.X, weaponSprite.size.Y); Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? Color.DimGray : GUIStyle.Green; - weaponSprite.Draw(batch, center, color, origin, rotation, scale, it.SpriteEffects); + weaponSprite.Draw(batch, center, color, origin, rotation, scale, SpriteEffects.None); } }); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs index f3bfe988b..4ca36908c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs @@ -219,7 +219,7 @@ namespace Barotrauma.Items.Components { //flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10); - msg.Write(IsActive); + msg.WriteBoolean(IsActive); } public void ClientEventRead(IReadMessage msg, float sendingTime) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs index 5e973af30..30a3a35ba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs @@ -34,9 +34,8 @@ namespace Barotrauma.Items.Components private Color optimalRangeColor = new Color(74,238,104,255); private Color offRangeColor = Color.Orange; private Color warningColor = Color.Red; - private Color coldColor = Color.LightBlue; - private Color warmColor = Color.Orange; - private Color hotColor = Color.Red; + + private readonly Color[] temperatureColors = new Color[] { Color.Blue, Color.LightBlue, Color.Orange, Color.Red }; private Color outputColor = Color.Goldenrod; private Color loadColor = Color.LightSteelBlue; @@ -66,10 +65,11 @@ namespace Barotrauma.Items.Components "ReactorWarningOverheating", "ReactorWarningHighOutput", "ReactorWarningFuelOut", "ReactorWarningSCRAM" }; + public override bool RecreateGUIOnResolutionChange => true; + partial void InitProjSpecific(ContentXElement element) { - // TODO: need to recreate the gui when the resolution changes - + CreateGUI(); fissionRateMeter = new Sprite(element.GetChildElement("fissionratemeter")?.GetChildElement("sprite")); turbineOutputMeter = new Sprite(element.GetChildElement("turbineoutputmeter")?.GetChildElement("sprite")); meterPointer = new Sprite(element.GetChildElement("meterpointer")?.GetChildElement("sprite")); @@ -81,7 +81,6 @@ namespace Barotrauma.Items.Components foreach (var subElement in element.Elements()) { - string textureDir = GetTextureDirectory(subElement); switch (subElement.Name.ToString().ToLowerInvariant()) { case "temperatureboostsoundup": @@ -92,11 +91,16 @@ namespace Barotrauma.Items.Components break; } } + } + + protected override void CreateGUI() + { + warningButtons.Clear(); paddedFrame = new GUILayoutGroup(new RectTransform( - GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) - { AbsoluteOffset = GUIStyle.ItemFrameOffset }, - isHorizontal: true) + GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) + { AbsoluteOffset = GUIStyle.ItemFrameOffset }, + isHorizontal: true) { RelativeSpacing = 0.012f, Stretch = true @@ -438,6 +442,19 @@ namespace Barotrauma.Items.Components }; LocalizedString outputStr = TextManager.Get("ReactorOutput"); outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}"; + + InitInventoryUI(); + } + + private void InitInventoryUI() + { + var itemContainer = item.GetComponent(); + if (itemContainer != null) + { + itemContainer.UILabel = ""; + itemContainer.AllowUIOverlap = true; + itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform; + } } public override void OnItemLoaded() @@ -445,23 +462,10 @@ namespace Barotrauma.Items.Components base.OnItemLoaded(); TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f; FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f; - var itemContainer = item.GetComponent(); - if (itemContainer != null) - { - itemContainer.UILabel = ""; - itemContainer.AllowUIOverlap = true; - itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform; - /*var inventoryLabel = inventoryContainer.Parent?.GetChild(); - if (inventoryLabel != null) - { - inventoryLabel.RectTransform.MinSize = new Point(100, 0); - inventoryLabel.Text = itemContainer.GetUILabel(); - inventoryLabel.CalculateHeightFromText(); - (inventoryLabel.Parent as GUILayoutGroup).Recalculate(); - }*/ - } + InitInventoryUI(); } + private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container) { Vector2 meterPos = new Vector2(container.Rect.X, container.Rect.Y); @@ -474,7 +478,7 @@ namespace Barotrauma.Items.Components while (meterBarPos.Y > container.Rect.Bottom + (int)(5 * GUI.yScale) - container.Rect.Height * tempFill) { float tempRatio = 1.0f - ((meterBarPos.Y - container.Rect.Y) / container.Rect.Height); - Color color = ToolBox.GradientLerp(tempRatio, coldColor, optimalRangeColor, warmColor, hotColor); + Color color = ToolBox.GradientLerp(tempRatio, temperatureColors); tempMeterBar.Draw(spriteBatch, meterBarPos, color: color, scale: meterBarScale); int spacing = 2; meterBarPos.Y -= tempMeterBar.size.Y * meterBarScale + spacing; @@ -691,7 +695,6 @@ namespace Barotrauma.Items.Components float normalizedValue = (value - range.X) / (range.Y - range.X); float valueRad = MathHelper.Lerp(sectorRad.X, sectorRad.Y, normalizedValue); - Vector2 offset = new Vector2(0, 40) * scale; meterPointer.Draw(spriteBatch, pointerPos, valueRad, scale); } @@ -769,8 +772,8 @@ namespace Barotrauma.Items.Components public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { - msg.Write(autoTemp); - msg.Write(PowerOn); + msg.WriteBoolean(autoTemp); + msg.WriteBoolean(PowerOn); msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8); msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index ca210f1fb..2deca5a59 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -140,8 +140,7 @@ namespace Barotrauma.Items.Components set; } - private bool AllowUsingMineralScanner => - HasMineralScanner && !isConnectedToSteering; + public override bool RecreateGUIOnResolutionChange => true; partial void InitProjSpecific(ContentXElement element) { @@ -195,7 +194,6 @@ namespace Barotrauma.Items.Components protected override void OnResolutionChanged() { - base.OnResolutionChanged(); UpdateGUIElements(); } @@ -302,7 +300,7 @@ namespace Barotrauma.Items.Components TextManager.Get("SonarDirectionalPing"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft); textBlocksToScaleAndNormalize.Add(directionalModeSwitchText); - if (AllowUsingMineralScanner) + if (HasMineralScanner) { AddMineralScannerSwitchToGUI(); } @@ -373,7 +371,7 @@ namespace Barotrauma.Items.Components { base.OnItemLoaded(); zoomSlider.BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom); - if (AllowUsingMineralScanner && mineralScannerSwitch == null) + if (HasMineralScanner && mineralScannerSwitch == null) { AddMineralScannerSwitchToGUI(); GUITextBlock.AutoScaleAndNormalize(textBlocksToScaleAndNormalize); @@ -417,12 +415,32 @@ namespace Barotrauma.Items.Components unsentChanges = true; correctionTimer = CorrectionDelay; } + return true; } }; var mineralScannerSwitchText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1), mineralScannerFrame.RectTransform, Anchor.CenterRight), TextManager.Get("SonarMineralScanner"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft); textBlocksToScaleAndNormalize.Add(mineralScannerSwitchText); + + PreventMineralScannerOverlap(); + } + + private void PreventMineralScannerOverlap() + { + if (item.GetComponent() is { } steering && controlContainer is { } container) + { + int containerBottom = container.Rect.Y + container.Rect.Height, + steeringTop = steering.ControlContainer.Rect.Top; + + int amountRaised = 0; + + while (GetContainerBottom() > steeringTop) { amountRaised++; } + + container.RectTransform.AbsoluteOffset = new Point(0, -amountRaised); + + int GetContainerBottom() => containerBottom - amountRaised; + } } public override void UpdateHUD(Character character, float deltaTime, Camera cam) @@ -502,7 +520,7 @@ namespace Barotrauma.Items.Components Vector2.DistanceSquared(sonarView.Rect.Center.ToVector2(), PlayerInput.MousePosition) < (sonarView.Rect.Width / 2 * sonarView.Rect.Width / 2); - if (AllowUsingMineralScanner && Level.Loaded != null && !Level.Loaded.Generating) + if (HasMineralScanner && Level.Loaded != null && !Level.Loaded.Generating) { if (MineralClusters == null) { @@ -985,7 +1003,7 @@ namespace Barotrauma.Items.Components missionIndex++; } - if (AllowUsingMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null && + if (HasMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null && (item.CurrentHull == null || !DetectSubmarineWalls)) { foreach (var c in MineralClusters) @@ -1754,17 +1772,17 @@ namespace Barotrauma.Items.Components public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { - msg.Write(currentMode == Mode.Active); + msg.WriteBoolean(currentMode == Mode.Active); if (currentMode == Mode.Active) { msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8); - msg.Write(useDirectionalPing); + msg.WriteBoolean(useDirectionalPing); if (useDirectionalPing) { float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection)); msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8); } - msg.Write(useMineralScanner); + msg.WriteBoolean(useMineralScanner); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs index 8eed52986..d4d043455 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs @@ -25,7 +25,9 @@ namespace Barotrauma.Items.Components private GUITickBox maintainPosTickBox, levelEndTickBox, levelStartTickBox; - private GUIComponent statusContainer, dockingContainer, controlContainer; + private GUIComponent statusContainer, dockingContainer; + + public GUIComponent ControlContainer { get; private set; } private bool dockingNetworkMessagePending; @@ -90,6 +92,24 @@ namespace Barotrauma.Items.Components } } + private bool disableControls; + /// + /// Can be used by status effects to disable all the UI controls + /// + public bool DisableControls + { + get { return disableControls; } + set + { + if (disableControls == value) { return; } + disableControls = value; + UpdateGUIElements(); + } + } + + public override bool RecreateGUIOnResolutionChange => true; + + partial void InitProjSpecific(ContentXElement element) { foreach (var subElement in element.Elements()) @@ -112,8 +132,8 @@ namespace Barotrauma.Items.Components protected override void CreateGUI() { - controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterRight), "ItemUI"); - var paddedControlContainer = new GUIFrame(new RectTransform(controlContainer.Rect.Size - GUIStyle.ItemFrameMargin, controlContainer.RectTransform, Anchor.Center) + ControlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterRight), "ItemUI"); + var paddedControlContainer = new GUIFrame(new RectTransform(ControlContainer.Rect.Size - GUIStyle.ItemFrameMargin, ControlContainer.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null); @@ -472,7 +492,6 @@ namespace Barotrauma.Items.Components protected override void OnResolutionChanged() { - base.OnResolutionChanged(); UpdateGUIElements(); } @@ -658,6 +677,11 @@ namespace Barotrauma.Items.Components } } + if (DisableControls) + { + dockingModeEnabled = false; + } + dockingContainer.Visible = DockingModeEnabled; statusContainer.Visible = !DockingModeEnabled; if (!DockingModeEnabled) @@ -745,7 +769,7 @@ namespace Barotrauma.Items.Components iceSpireWarningText.Visible = item.Submarine != null && !pressureWarningText.Visible && showIceSpireWarning && Timing.TotalTime % 1.0f < 0.8f; - if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) + if (!disableControls && Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) { if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen && (!GameMain.GameSession?.Campaign?.ShowCampaignUI ?? true) && !GUIMessageBox.MessageBoxes.Any(msgBox => msgBox is GUIMessageBox { MessageBoxType: GUIMessageBox.Type.Default })) @@ -815,7 +839,7 @@ namespace Barotrauma.Items.Components keyboardInput = Vector2.Zero; } - if (!UseAutoDocking) { return; } + if (!UseAutoDocking || DisableControls) { return; } if (checkConnectedPortsTimer <= 0.0f) { @@ -894,27 +918,27 @@ namespace Barotrauma.Items.Components public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { - msg.Write(AutoPilot); - msg.Write(dockingNetworkMessagePending); + msg.WriteBoolean(AutoPilot); + msg.WriteBoolean(dockingNetworkMessagePending); dockingNetworkMessagePending = false; if (!AutoPilot) { //no need to write steering info if autopilot is controlling - msg.Write(steeringInput.X); - msg.Write(steeringInput.Y); + msg.WriteSingle(steeringInput.X); + msg.WriteSingle(steeringInput.Y); } else { - msg.Write(posToMaintain != null); + msg.WriteBoolean(posToMaintain != null); if (posToMaintain != null) { - msg.Write(((Vector2)posToMaintain).X); - msg.Write(((Vector2)posToMaintain).Y); + msg.WriteSingle(((Vector2)posToMaintain).X); + msg.WriteSingle(((Vector2)posToMaintain).Y); } else { - msg.Write(LevelStartSelected); + msg.WriteBoolean(LevelStartSelected); } } } @@ -998,9 +1022,20 @@ namespace Barotrauma.Items.Components steeringModeSwitch.Selected = AutoPilot; autopilotIndicator.Selected = AutoPilot; manualPilotIndicator.Selected = !AutoPilot; - maintainPosTickBox.Enabled = AutoPilot; - levelEndTickBox.Enabled = AutoPilot; - levelStartTickBox.Enabled = AutoPilot; + if (DisableControls) + { + steeringModeSwitch.Enabled = false; + maintainPosTickBox.Enabled = false; + levelEndTickBox.Enabled = false; + levelStartTickBox.Enabled = false; + } + else + { + steeringModeSwitch.Enabled = true; + maintainPosTickBox.Enabled = AutoPilot; + levelEndTickBox.Enabled = AutoPilot; + levelStartTickBox.Enabled = AutoPilot; + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/Powered.cs index e3d4e35c4..ae0b6e456 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/Powered.cs @@ -1,8 +1,4 @@ -using Barotrauma.Sounds; -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Barotrauma.Items.Components +namespace Barotrauma.Items.Components { partial class Powered : ItemComponent { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index c67963162..83f660f0d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -451,7 +451,7 @@ namespace Barotrauma.Items.Components public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { msg.WriteRangedInteger((int)requestStartFixAction, 0, 2); - msg.Write(qteSuccess); + msg.WriteBoolean(qteSuccess); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs index 89d1a7583..80fbd44db 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs @@ -94,7 +94,6 @@ namespace Barotrauma.Items.Components protected override void OnResolutionChanged() { - base.OnResolutionChanged(); OnItemLoadedProjSpecific(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index fe6ba3270..022972368 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -21,12 +21,13 @@ namespace Barotrauma.Items.Components public float FlashTimer { get; private set; } public static Wire DraggingConnected { get; private set; } - public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character) + public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Rectangle dragArea, Character character) { if (DraggingConnected?.Item?.Removed ?? true) { DraggingConnected = null; } + Rectangle panelRect = panel.GuiFrame.Rect; int x = panelRect.X, y = panelRect.Y; int width = panelRect.Width, height = panelRect.Height; @@ -131,7 +132,10 @@ namespace Barotrauma.Items.Components { if (mouseInRect) { - DrawWire(spriteBatch, DraggingConnected, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height - 10), null, panel, ""); + Vector2 wireDragPos = new Vector2( + MathHelper.Clamp(PlayerInput.MousePosition.X, dragArea.X, dragArea.Right), + MathHelper.Clamp(PlayerInput.MousePosition.Y, dragArea.Y, dragArea.Bottom)); + DrawWire(spriteBatch, DraggingConnected, wireDragPos, new Vector2(x + width / 2, y + height - 10), null, panel, ""); } panel.TriggerRewiringSound(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs index c0d624511..63560b853 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs @@ -5,7 +5,6 @@ using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Items.Components { @@ -27,6 +26,10 @@ namespace Barotrauma.Items.Components private Point originalMaxSize; private Vector2 originalRelativeSize; + private GUIComponent dragArea; + + public override bool RecreateGUIOnResolutionChange => true; + partial void InitProjSpecific() { if (GuiFrame == null) { return; } @@ -40,9 +43,8 @@ namespace Barotrauma.Items.Components content.RectTransform.SetAsFirstChild(); //prevents inputs from going through the GUICustomComponent to the drag handle - var blocker = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) - { AbsoluteOffset = GUIStyle.ItemFrameOffset }, - style: null); + dragArea = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) + { AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null); } public void TriggerRewiringSound() @@ -111,7 +113,7 @@ namespace Barotrauma.Items.Components if (user != Character.Controlled || user == null) { return; } HighlightedWire = null; - Connection.DrawConnections(spriteBatch, this, user); + Connection.DrawConnections(spriteBatch, this, dragArea.Rect, user); foreach (UISprite sprite in GUIStyle.GetComponentStyle("ConnectionPanelFront").Sprites[GUIComponent.ComponentState.None]) { @@ -121,7 +123,6 @@ namespace Barotrauma.Items.Components protected override void OnResolutionChanged() { - base.OnResolutionChanged(); if (GuiFrame == null) { return; } CheckForLabelOverlap(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs index 706105771..cdb1d4ac9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs @@ -14,6 +14,8 @@ namespace Barotrauma.Items.Components private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale)); + public override bool RecreateGUIOnResolutionChange => true; + partial void InitProjSpecific() { CreateGUI(); @@ -354,29 +356,29 @@ namespace Barotrauma.Items.Components { if (!element.IsNumberInput) { - msg.Write(((GUITextBox)uiElements[i]).Text); + msg.WriteString(((GUITextBox)uiElements[i]).Text); } else { switch (element.NumberType) { case NumberType.Float: - msg.Write(((GUINumberInput)uiElements[i]).FloatValue.ToString()); + msg.WriteString(((GUINumberInput)uiElements[i]).FloatValue.ToString()); break; case NumberType.Int: default: - msg.Write(((GUINumberInput)uiElements[i]).IntValue.ToString()); + msg.WriteString(((GUINumberInput)uiElements[i]).IntValue.ToString()); break; } } } else if (element.ContinuousSignal) { - msg.Write(((GUITickBox)uiElements[i]).Selected); + msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected); } else { - msg.Write(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]); + msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs index 980a6f256..ec3054df9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs @@ -149,7 +149,7 @@ namespace Barotrauma.Items.Components { if (TryExtractEventData(extraData, out ClientEventData eventData)) { - msg.Write(eventData.Text); + msg.WriteString(eventData.Text); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs index 4c1ac0f07..17922b0b4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs @@ -603,11 +603,11 @@ namespace Barotrauma.Items.Components { var eventData = ExtractEventData(extraData); int nodeCount = eventData.NodeCount; - msg.Write((byte)nodeCount); + msg.WriteByte((byte)nodeCount); if (nodeCount > 0) { - msg.Write(nodes.Last().X); - msg.Write(nodes.Last().Y); + msg.WriteSingle(nodes.Last().X); + msg.WriteSingle(nodes.Last().Y); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs index 9466e4377..eeeb4f627 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs @@ -185,7 +185,7 @@ namespace Barotrauma.Items.Components public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { - msg.Write((byte)allowOutpostAutoDocking); + msg.WriteByte((byte)allowOutpostAutoDocking); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 8547be8c6..1a6590180 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -594,7 +594,10 @@ namespace Barotrauma if (body.LinearVelocity.Y < 0.0f) { int n = (int)((Position.X - CurrentHull.Rect.X) / Hull.WaveWidth); - CurrentHull.WaveVel[n] += MathHelper.Clamp(body.LinearVelocity.Y * massFactor, -5.0f, 5.0f); + if (n >= 0 && n < currentHull.WaveVel.Length) + { + CurrentHull.WaveVel[n] += MathHelper.Clamp(body.LinearVelocity.Y * massFactor, -5.0f, 5.0f); + } } SoundPlayer.PlaySplashSound(WorldPosition, Math.Abs(body.LinearVelocity.Y) + Rand.Range(-10.0f, -5.0f)); } @@ -1469,8 +1472,8 @@ namespace Barotrauma case TreatmentEventData treatmentEventData: Character targetCharacter = treatmentEventData.TargetCharacter; - msg.Write(targetCharacter.ID); - msg.Write(treatmentEventData.LimbIndex); + msg.WriteUInt16(targetCharacter.ID); + msg.WriteByte(treatmentEventData.LimbIndex); break; case ChangePropertyEventData changePropertyEventData: WritePropertyChange(msg, changePropertyEventData, inGameEditableOnly: true); @@ -1478,7 +1481,7 @@ namespace Barotrauma break; case CombineEventData combineEventData: Item combineTarget = combineEventData.CombineTarget; - msg.Write(combineTarget.ID); + msg.WriteUInt16(combineTarget.ID); break; default: throw error($"Unsupported event type {eventData.GetType().Name}"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs index cedbfbf72..bab357ba1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs @@ -222,7 +222,7 @@ namespace Barotrauma if (!ResizeHorizontal && !ResizeVertical) { - if (PlayerInput.PrimaryMouseButtonClicked()) + if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null) { var item = new Item(new Rectangle((int)position.X, (int)position.Y, (int)(Sprite.size.X * Scale), (int)(Sprite.size.Y * Scale)), this, Submarine.MainSub) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 01bab48a4..7024d24bb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -611,7 +611,7 @@ namespace Barotrauma case DecalEventData decalEventData: var decal = decalEventData.Decal; int decalIndex = decals.IndexOf(decal); - msg.Write((byte)(decalIndex < 0 ? 255 : decalIndex)); + msg.WriteByte((byte)(decalIndex < 0 ? 255 : decalIndex)); msg.WriteRangedSingle(decal.BaseAlpha, 0.0f, 1.0f, 8); break; default: diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index c7a818db0..a33dfa34a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -741,6 +741,15 @@ namespace Barotrauma /// public static void DrawSelecting(SpriteBatch spriteBatch, Camera cam) { + if (Screen.Selected is SubEditorScreen subEditor) + { + if (subEditor.IsMouseOnEditorGUI()) { return; } + } + else if (GUI.MouseOn != null) + { + return; + } + Vector2 position = PlayerInput.MousePosition; position = cam.ScreenToWorld(position); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index 596935d5b..1342557f5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -37,17 +37,6 @@ namespace Barotrauma } } -#if DEBUG - [Editable, Serialize("", IsPropertySaveable.Yes)] -#else - [Serialize("", IsPropertySaveable.Yes)] -#endif - public string SpecialTag - { - get; - set; - } - partial void InitProjSpecific() { Prefab.Sprite?.EnsureLazyLoaded(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs index 9ff0f0d9e..f031e7fab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs @@ -27,9 +27,10 @@ namespace Barotrauma if (placePosition == Vector2.Zero) { - if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null) + if (PlayerInput.PrimaryMouseButtonHeld() && GUI.MouseOn == null) + { placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); - + } newRect.X = (int)position.X; newRect.Y = (int)position.Y; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs index 463d94253..f0047e10c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs @@ -80,7 +80,8 @@ namespace Barotrauma { UserData = "descriptionbox", ScrollBarVisible = true, - Spacing = 5 + Spacing = 5, + CurrentSelectMode = GUIListBox.SelectMode.None }; GUIFont font = parent.Rect.Width < 350 ? GUIStyle.SmallFont : GUIStyle.Font; @@ -92,22 +93,32 @@ namespace Barotrauma { float leftPanelWidth = 0.6f; float rightPanelWidth = 0.4f / leftPanelWidth; - LocalizedString className = !HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{SubmarineClass}") : TextManager.Get("shuttle"); + LocalizedString className = !HasTag(SubmarineTag.Shuttle) + ? $"{TextManager.Get($"submarineclass.{SubmarineClass}")} ({TextManager.Get($"submarinetier.{Tier}")})" + : TextManager.Get("shuttle"); + int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y; - int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth); + int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth); GUITextBlock submarineNameText = null; GUITextBlock submarineClassText = null; if (includeTitle) { int nameHeight = (int)GUIStyle.LargeFont.MeasureString(DisplayName, true).Y; - submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUIStyle.LargeFont) { CanBeFocused = false }; + submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUIStyle.LargeFont) + { + CanBeFocused = false + }; submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y); } if (includeClass) { - submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont) { CanBeFocused = false }; + submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont) + { + ToolTip = TextManager.Get("submarinetierandclass.description")+"\n\n"+ TextManager.Get($"submarineclass.{SubmarineClass}.description") + }; + submarineClassText.HoverColor = Color.Transparent; submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs index e72fc7fb1..e547ec854 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs @@ -144,10 +144,11 @@ namespace Barotrauma specsContainer = new GUIListBox(new RectTransform(new Vector2(0.4f, 1f), innerPadded.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(0.015f, 0.07f) }) { + CurrentSelectMode = GUIListBox.SelectMode.None, Color = Color.Black * 0.65f, ScrollBarEnabled = false, ScrollBarVisible = false, - Spacing = 5 + Spacing = GUI.IntScale(5) }; subInfo.CreateSpecsWindow(specsContainer, GUIStyle.Font, includeTitle: false, includeDescription: true); int width = specsContainer.Rect.Width; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/BanList.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/BanList.cs index 23afe3580..8d0f64b01 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/BanList.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/BanList.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +#nullable enable +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -24,36 +25,31 @@ namespace Barotrauma.Networking partial class BanList { - private GUIComponent banFrame; - - public GUIComponent BanFrame - { - get { return banFrame; } - } + public GUIComponent? BanFrame { get; private set; } public List localRemovedBans = new List(); private void RecreateBanFrame() { - if (banFrame != null) + if (BanFrame != null) { - var parent = banFrame.Parent; - parent.RemoveChild(banFrame); + var parent = BanFrame.Parent; + parent.RemoveChild(BanFrame); CreateBanFrame(parent); } } public GUIComponent CreateBanFrame(GUIComponent parent) { - banFrame = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center)); + BanFrame = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center)); foreach (BannedPlayer bannedPlayer in bannedPlayers) { if (localRemovedBans.Contains(bannedPlayer.UniqueIdentifier)) { continue; } - var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)banFrame).Content.RectTransform) { MinSize = new Point(0, 70) }) + var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)BanFrame).Content.RectTransform) { MinSize = new Point(0, 70) }) { - UserData = banFrame + UserData = BanFrame }; var paddedPlayerFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), playerFrame.RectTransform, Anchor.Center)) @@ -102,16 +98,15 @@ namespace Barotrauma.Networking paddedPlayerFrame.Recalculate(); - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)banFrame).Content.RectTransform), style: "HorizontalLine"); + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)BanFrame).Content.RectTransform), style: "HorizontalLine"); } - return banFrame; + return BanFrame; } private bool RemoveBan(GUIButton button, object obj) { - BannedPlayer banned = obj as BannedPlayer; - if (banned == null) { return false; } + if (!(obj is BannedPlayer banned)) { return false; } localRemovedBans.Add(banned.UniqueIdentifier); RecreateBanFrame(); @@ -178,10 +173,10 @@ namespace Barotrauma.Networking bannedPlayers.Add(new BannedPlayer(uniqueIdentifier, name, addressOrAccountId, reason, expiration)); } - if (banFrame != null) + if (BanFrame != null) { - var parent = banFrame.Parent; - parent.RemoveChild(banFrame); + var parent = BanFrame.Parent; + parent.RemoveChild(BanFrame); CreateBanFrame(parent); } } @@ -191,7 +186,7 @@ namespace Barotrauma.Networking outMsg.WriteVariableUInt32((UInt32)localRemovedBans.Count); foreach (UInt32 uniqueId in localRemovedBans) { - outMsg.Write(uniqueId); + outMsg.WriteUInt32(uniqueId); } localRemovedBans.Clear(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs index ec7909d75..8c1319fe5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs @@ -8,11 +8,11 @@ namespace Barotrauma.Networking { public virtual void ClientWrite(IWriteMessage msg) { - msg.Write((byte)ClientNetObject.CHAT_MESSAGE); - msg.Write(NetStateID); + msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE); + msg.WriteUInt16(NetStateID); msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1); msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1); - msg.Write(Text); + msg.WriteString(Text); } public static void ClientRead(IReadMessage msg) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs index 59be7ad9e..1809817cb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs @@ -12,7 +12,7 @@ namespace Barotrauma.Networking class FileReceiver { public class FileTransferIn : IDisposable - { + { public string FileName { get; @@ -36,7 +36,7 @@ namespace Barotrauma.Networking get; private set; } - + public int LastSeen { get; set; } public FileTransferType FileType @@ -93,6 +93,12 @@ namespace Barotrauma.Networking public int ID; + public const int DataBufferSize = 50; + /// + /// Data that we've ignored because we're waiting for some earlier data. Key = byte offset, value = the actual data + /// + public readonly Dictionary DataBuffer = new Dictionary(); + public FileTransferIn(NetworkConnection connection, string filePath, FileTransferType fileType) { FilePath = filePath; @@ -128,20 +134,25 @@ namespace Barotrauma.Networking bytesToRead -= Received + bytesToRead - FileSize; } - byte[] all = inc.ReadBytes(bytesToRead); - Received += all.Length; - WriteStream.Write(all, 0, all.Length); + ReadBytes(inc.ReadBytes(bytesToRead)); + } + + public void ReadBytes(byte[] data) + { + Received += data.Length; + WriteStream.Write(data, 0, data.Length); int passed = Environment.TickCount - TimeStarted; float psec = passed / 1000.0f; - if (GameSettings.CurrentConfig.VerboseLogging) - { - DebugConsole.Log($"Received {all.Length} bytes of the file {FileName} ({Received / 1000}/{FileSize / 1000} kB received)"); - } - BytesPerSecond = Received / psec; + var outdatedKeys = DataBuffer.Keys.Where(k => k < Received).ToList(); + foreach (int key in outdatedKeys) + { + DataBuffer.Remove(key); + } + Status = Received >= FileSize ? FileTransferStatus.Finished : FileTransferStatus.Receiving; } @@ -349,6 +360,10 @@ namespace Barotrauma.Networking if (offset != activeTransfer.Received) { activeTransfer.LastSeen = Math.Max(offset, activeTransfer.LastSeen); + if (!activeTransfer.DataBuffer.ContainsKey(offset) && activeTransfer.DataBuffer.Count < FileTransferIn.DataBufferSize) + { + activeTransfer.DataBuffer.Add(offset, inc.ReadBytes(bytesToRead)); + } DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} (ignoring: offset {offset}, waiting for {activeTransfer.Received})"); GameMain.Client.UpdateFileTransfer(activeTransfer, activeTransfer.Received, activeTransfer.LastSeen); return; @@ -370,7 +385,16 @@ namespace Barotrauma.Networking try { - activeTransfer.ReadBytes(inc, bytesToRead); + activeTransfer.ReadBytes(inc, bytesToRead); + if (GameSettings.CurrentConfig.VerboseLogging) + { + DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)"); + } + while (activeTransfer.DataBuffer.TryGetValue(activeTransfer.Received, out byte[] data)) + { + activeTransfer.ReadBytes(data); + DebugConsole.Log($"Read {data.Length} bytes of buffer data of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)"); + } } catch (Exception e) { @@ -434,7 +458,7 @@ namespace Barotrauma.Networking } if (string.IsNullOrEmpty(fileName) || - fileName.IndexOfAny(Path.GetInvalidFileNameChars().ToArray()) > -1) + fileName.IndexOfAny(Path.GetInvalidFileNameCharsCrossPlatform().ToArray()) > -1) { errorMessage = "Illegal characters in file name ''" + fileName + "''"; return false; @@ -470,7 +494,7 @@ namespace Barotrauma.Networking System.IO.Stream stream; try { - stream = SaveUtil.DecompressFiletoStream(fileTransfer.FilePath); + stream = SaveUtil.DecompressFileToStream(fileTransfer.FilePath); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 9a04ff9e6..30ab2172f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -674,13 +674,13 @@ namespace Barotrauma.Networking { case ServerPacketHeader.PING_REQUEST: IWriteMessage response = new WriteOnlyMessage(); - response.Write((byte)ClientPacketHeader.PING_RESPONSE); + response.WriteByte((byte)ClientPacketHeader.PING_RESPONSE); byte requestLen = inc.ReadByte(); - response.Write(requestLen); + response.WriteByte(requestLen); for (int i = 0; i < requestLen; i++) { byte b = inc.ReadByte(); - response.Write(b); + response.WriteByte(b); } clientPeer.Send(response, DeliveryMethod.Unreliable); break; @@ -752,7 +752,7 @@ namespace Barotrauma.Networking } IWriteMessage readyToStartMsg = new WriteOnlyMessage(); - readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME); + readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME); if (campaign != null) { campaign.PendingSubmarineSwitch = null; } GameMain.NetLobbyScreen.UsingShuttle = usingShuttle; @@ -770,7 +770,7 @@ namespace Barotrauma.Networking campaign.LastSaveID == campaignSaveID && campaignUpdateIDs.All(kvp => campaign.GetLastUpdateIdForFlag(kvp.Key) == kvp.Value); } - readyToStartMsg.Write(readyToStart); + readyToStartMsg.WriteBoolean(readyToStart); DebugConsole.Log(readyToStart ? "Ready to start." : "Not ready to start."); @@ -1202,7 +1202,8 @@ namespace Barotrauma.Networking VoipClient = new VoipClient(this, clientPeer); - if (Screen.Selected != GameMain.GameScreen && !(Screen.Selected is RoundSummaryScreen)) + //if we're still in the game, roundsummary or lobby screen, we don't need to redownload the mods + if (!(Screen.Selected is GameScreen) && !(Screen.Selected is RoundSummaryScreen) && !(Screen.Selected is NetLobbyScreen)) { GameMain.ModDownloadScreen.Select(); } @@ -1639,7 +1640,7 @@ namespace Barotrauma.Networking DateTime requestFinalizeTime = DateTime.Now; TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2); IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE); + msg.WriteByte((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE); clientPeer.Send(msg, DeliveryMethod.Unreliable); GUIMessageBox interruptPrompt = null; @@ -1653,7 +1654,7 @@ namespace Barotrauma.Networking if (DateTime.Now > requestFinalizeTime) { msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE); + msg.WriteByte((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE); clientPeer.Send(msg, DeliveryMethod.Unreliable); requestFinalizeTime = DateTime.Now + requestFinalizeInterval; } @@ -1816,16 +1817,24 @@ namespace Barotrauma.Networking if (Screen.Selected == GameMain.GameScreen) { + Submarine refSub = Submarine.MainSub; + if (Submarine.MainSubs[1] != null && + GameMain.GameSession.GameMode is PvPMode && + GameMain.GameSession.WinningTeam.HasValue && GameMain.GameSession.WinningTeam == CharacterTeamType.Team1) + { + refSub = Submarine.MainSubs[1]; + } + // Enable characters near the main sub for the endCinematic foreach (Character c in Character.CharacterList) { - if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance)) + if (Vector2.DistanceSquared(refSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance)) { c.Enabled = true; } } - EndCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight); + EndCinematic = new CameraTransition(refSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight); while (EndCinematic.Running && Screen.Selected == GameMain.GameScreen) { yield return CoroutineStatus.Running; @@ -1874,7 +1883,7 @@ namespace Barotrauma.Networking } GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, ServerSubmarines); - GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, ServerSubmarines); + GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, ServerSubmarines.Where(s => s.HasTag(SubmarineTag.Shuttle))); gameStarted = inc.ReadBoolean(); bool allowSpectating = inc.ReadBoolean(); @@ -2073,7 +2082,7 @@ namespace Barotrauma.Networking (isInitialUpdate || initialUpdateReceived)) { ReadWriteMessage settingsBuf = new ReadWriteMessage(); - settingsBuf.Write(settingsData, 0, settingsLen); settingsBuf.BitPosition = 0; + settingsBuf.WriteBytes(settingsData, 0, settingsLen); settingsBuf.BitPosition = 0; serverSettings.ClientRead(settingsBuf); if (!IsServerOwner) { @@ -2325,38 +2334,38 @@ namespace Barotrauma.Networking private void SendLobbyUpdate() { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ClientPacketHeader.UPDATE_LOBBY); + outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY); - outmsg.Write((byte)ClientNetObject.SYNC_IDS); - outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID); - outmsg.Write(ChatMessage.LastID); - outmsg.Write(LastClientListUpdateID); - outmsg.Write(nameId); - outmsg.Write(Name); + outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS); + outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID); + outmsg.WriteUInt16(ChatMessage.LastID); + outmsg.WriteUInt16(LastClientListUpdateID); + outmsg.WriteUInt16(nameId); + outmsg.WriteString(Name); var jobPreferences = GameMain.NetLobbyScreen.JobPreferences; if (jobPreferences.Count > 0) { - outmsg.Write(jobPreferences[0].Prefab.Identifier); + outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier); } else { - outmsg.Write(""); + outmsg.WriteIdentifier(Identifier.Empty); } - outmsg.Write((byte)MultiplayerPreferences.Instance.TeamPreference); + outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference); if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0) { - outmsg.Write((UInt16)0); + outmsg.WriteUInt16((UInt16)0); } else { - outmsg.Write(campaign.LastSaveID); - outmsg.Write(campaign.CampaignID); + outmsg.WriteUInt16(campaign.LastSaveID); + outmsg.WriteByte(campaign.CampaignID); foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags))) { - outmsg.Write(campaign.GetLastUpdateIdForFlag(netFlag)); + outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag)); } - outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded); + outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded); } chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID)); @@ -2369,7 +2378,7 @@ namespace Barotrauma.Networking } chatMsgQueue[i].ClientWrite(outmsg); } - outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE); + outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE); if (outmsg.LengthBytes > MsgConstants.MTU) { @@ -2382,29 +2391,29 @@ namespace Barotrauma.Networking private void SendIngameUpdate() { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ClientPacketHeader.UPDATE_INGAME); - outmsg.Write(entityEventManager.MidRoundSyncingDone); + outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_INGAME); + outmsg.WriteBoolean(entityEventManager.MidRoundSyncingDone); outmsg.WritePadBits(); - outmsg.Write((byte)ClientNetObject.SYNC_IDS); + outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS); //outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID); - outmsg.Write(ChatMessage.LastID); - outmsg.Write(entityEventManager.LastReceivedID); - outmsg.Write(LastClientListUpdateID); + outmsg.WriteUInt16(ChatMessage.LastID); + outmsg.WriteUInt16(entityEventManager.LastReceivedID); + outmsg.WriteUInt16(LastClientListUpdateID); if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0) { - outmsg.Write((UInt16)0); + outmsg.WriteUInt16((UInt16)0); } else { - outmsg.Write(campaign.LastSaveID); - outmsg.Write(campaign.CampaignID); + outmsg.WriteUInt16(campaign.LastSaveID); + outmsg.WriteByte(campaign.CampaignID); foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags))) { - outmsg.Write(campaign.GetLastUpdateIdForFlag(flag)); + outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag)); } - outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded); + outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded); } Character.Controlled?.ClientWriteInput(outmsg); @@ -2423,7 +2432,7 @@ namespace Barotrauma.Networking chatMsgQueue[i].ClientWrite(outmsg); } - outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE); + outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE); if (outmsg.LengthBytes > MsgConstants.MTU) { @@ -2462,8 +2471,8 @@ namespace Barotrauma.Networking { WaitForNextRoundRespawn = waitForNextRoundRespawn; IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.READY_TO_SPAWN); - msg.Write((bool)waitForNextRoundRespawn); + msg.WriteByte((byte)ClientPacketHeader.READY_TO_SPAWN); + msg.WriteBoolean((bool)waitForNextRoundRespawn); clientPeer?.Send(msg, DeliveryMethod.Reliable); } @@ -2475,13 +2484,13 @@ namespace Barotrauma.Networking $"Sending a file request to the server (type: {fileType}, path: {file ?? "null"}"); IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.FILE_REQUEST); - msg.Write((byte)FileTransferMessageType.Initiate); - msg.Write((byte)fileType); + msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST); + msg.WriteByte((byte)FileTransferMessageType.Initiate); + msg.WriteByte((byte)fileType); if (fileType != FileTransferType.CampaignSave) { - msg.Write(file ?? throw new ArgumentNullException(nameof(file))); - msg.Write(fileHash ?? throw new ArgumentNullException(nameof(fileHash))); + msg.WriteString(file ?? throw new ArgumentNullException(nameof(file))); + msg.WriteString(fileHash ?? throw new ArgumentNullException(nameof(fileHash))); } clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2500,20 +2509,20 @@ namespace Barotrauma.Networking transfer.RecordOffsetAckTime(); IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.FILE_REQUEST); - msg.Write((byte)FileTransferMessageType.Data); - msg.Write((byte)transfer.ID); - msg.Write(expecting); - msg.Write(lastSeen); + msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST); + msg.WriteByte((byte)FileTransferMessageType.Data); + msg.WriteByte((byte)transfer.ID); + msg.WriteInt32(expecting); + msg.WriteInt32(lastSeen); clientPeer.Send(msg, reliable ? DeliveryMethod.Reliable : DeliveryMethod.Unreliable); } public void CancelFileTransfer(int id) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.FILE_REQUEST); - msg.Write((byte)FileTransferMessageType.Cancel); - msg.Write((byte)id); + msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST); + msg.WriteByte((byte)FileTransferMessageType.Cancel); + msg.WriteByte((byte)id); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2547,8 +2556,7 @@ namespace Barotrauma.Networking Color newSubTextColor = new Color(subElement.GetChild().TextColor, 1.0f); subElement.GetChild().TextColor = newSubTextColor; - GUITextBlock classTextBlock = subElement.GetChildByUserData("classtext") as GUITextBlock; - if (classTextBlock != null) + if (subElement.GetChildByUserData("classtext") is GUITextBlock classTextBlock) { Color newSubClassTextColor = new Color(classTextBlock.TextColor, 0.8f); classTextBlock.Text = TextManager.Get($"submarineclass.{newSub.SubmarineClass}"); @@ -2742,39 +2750,39 @@ namespace Barotrauma.Networking public void SendCharacterInfo(string newName = null) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.UPDATE_CHARACTERINFO); + msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO); WriteCharacterInfo(msg, newName); - msg.Write((byte)ServerNetObject.END_OF_MESSAGE); + msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); clientPeer?.Send(msg, DeliveryMethod.Reliable); } public void WriteCharacterInfo(IWriteMessage msg, string newName = null) { - msg.Write(characterInfo == null); + msg.WriteBoolean(characterInfo == null); if (characterInfo == null) { return; } - msg.Write(newName ?? string.Empty); + msg.WriteString(newName ?? string.Empty); - msg.Write((byte)characterInfo.Head.Preset.TagSet.Count); + msg.WriteByte((byte)characterInfo.Head.Preset.TagSet.Count); foreach (Identifier tag in characterInfo.Head.Preset.TagSet) { - msg.Write(tag); + msg.WriteIdentifier(tag); } - msg.Write((byte)characterInfo.Head.HairIndex); - msg.Write((byte)characterInfo.Head.BeardIndex); - msg.Write((byte)characterInfo.Head.MoustacheIndex); - msg.Write((byte)characterInfo.Head.FaceAttachmentIndex); + msg.WriteByte((byte)characterInfo.Head.HairIndex); + msg.WriteByte((byte)characterInfo.Head.BeardIndex); + msg.WriteByte((byte)characterInfo.Head.MoustacheIndex); + msg.WriteByte((byte)characterInfo.Head.FaceAttachmentIndex); msg.WriteColorR8G8B8(characterInfo.Head.SkinColor); msg.WriteColorR8G8B8(characterInfo.Head.HairColor); msg.WriteColorR8G8B8(characterInfo.Head.FacialHairColor); var jobPreferences = GameMain.NetLobbyScreen.JobPreferences; int count = Math.Min(jobPreferences.Count, 3); - msg.Write((byte)count); + msg.WriteByte((byte)count); for (int i = 0; i < count; i++) { - msg.Write(jobPreferences[i].Prefab.Identifier); - msg.Write((byte)jobPreferences[i].Variant); + msg.WriteIdentifier(jobPreferences[i].Prefab.Identifier); + msg.WriteByte((byte)jobPreferences[i].Variant); } } @@ -2783,10 +2791,10 @@ namespace Barotrauma.Networking if (clientPeer == null) { return; } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY); - msg.Write((byte)ClientNetObject.VOTE); + msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY); + msg.WriteByte((byte)ClientNetObject.VOTE); Voting.ClientWrite(msg, voteType, data); - msg.Write((byte)ServerNetObject.END_OF_MESSAGE); + msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2794,7 +2802,6 @@ namespace Barotrauma.Networking public void VoteForKick(Client votedClient) { if (votedClient == null) { return; } - votedClient.AddKickVote(ConnectedClients.FirstOrDefault(c => c.SessionId == SessionId)); Vote(VoteType.Kick, votedClient); } @@ -2840,10 +2847,10 @@ namespace Barotrauma.Networking public override void KickPlayer(string kickedName, string reason) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.Kick); - msg.Write(kickedName); - msg.Write(reason); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.Kick); + msg.WriteString(kickedName); + msg.WriteString(reason); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2851,11 +2858,11 @@ namespace Barotrauma.Networking public override void BanPlayer(string kickedName, string reason, TimeSpan? duration = null) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.Ban); - msg.Write(kickedName); - msg.Write(reason); - msg.Write(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.Ban); + msg.WriteString(kickedName); + msg.WriteString(reason); + msg.WriteDouble(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2863,28 +2870,28 @@ namespace Barotrauma.Networking public override void UnbanPlayer(string playerName) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.Unban); - msg.Write(true); msg.WritePadBits(); - msg.Write(playerName); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.Unban); + msg.WriteBoolean(true); msg.WritePadBits(); + msg.WriteString(playerName); clientPeer.Send(msg, DeliveryMethod.Reliable); } public override void UnbanPlayer(Endpoint endpoint) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.Unban); - msg.Write(false); msg.WritePadBits(); - msg.Write(endpoint.StringRepresentation); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.Unban); + msg.WriteBoolean(false); msg.WritePadBits(); + msg.WriteString(endpoint.StringRepresentation); clientPeer.Send(msg, DeliveryMethod.Reliable); } public void UpdateClientPermissions(Client targetClient) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.ManagePermissions); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.ManagePermissions); targetClient.WritePermissions(msg); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2897,10 +2904,10 @@ namespace Barotrauma.Networking return; } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.ManageCampaign); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign); campaign.ClientWrite(msg); - msg.Write((byte)ServerNetObject.END_OF_MESSAGE); + msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2913,12 +2920,12 @@ namespace Barotrauma.Networking } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.ConsoleCommands); - msg.Write(command); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.ConsoleCommands); + msg.WriteString(command); Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - msg.Write(cursorWorldPos.X); - msg.Write(cursorWorldPos.Y); + msg.WriteSingle(cursorWorldPos.X); + msg.WriteSingle(cursorWorldPos.Y); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2929,10 +2936,10 @@ namespace Barotrauma.Networking public void RequestStartRound(bool continueCampaign = false) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.ManageRound); - msg.Write(false); //indicates round start - msg.Write(continueCampaign); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.ManageRound); + msg.WriteBoolean(false); //indicates round start + msg.WriteBoolean(continueCampaign); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2940,25 +2947,16 @@ namespace Barotrauma.Networking /// /// Tell the server to select a submarine (permission required) /// - public void RequestSelectSub(int subIndex, bool isShuttle) + public void RequestSelectSub(SubmarineInfo sub, bool isShuttle) { - if (!HasPermission(ClientPermissions.SelectSub)) return; - - var subList = isShuttle ? GameMain.NetLobbyScreen.ShuttleList.ListBox : GameMain.NetLobbyScreen.SubList; - - if (subIndex < 0 || subIndex >= subList.Content.CountChildren) - { - DebugConsole.ThrowError("Submarine index out of bounds (" + subIndex + ")\n" + Environment.StackTrace.CleanupStackTrace()); - return; - } + if (!HasPermission(ClientPermissions.SelectSub) || sub == null) { return; } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.SelectSub); - msg.Write(isShuttle); msg.WritePadBits(); - msg.Write((UInt16)subIndex); - msg.Write((byte)ServerNetObject.END_OF_MESSAGE); - + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.SelectSub); + msg.WriteBoolean(isShuttle); msg.WritePadBits(); + msg.WriteString(sub.MD5Hash.StringRepresentation); + msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2975,10 +2973,10 @@ namespace Barotrauma.Networking } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.SelectMode); - msg.Write((UInt16)modeIndex); - msg.Write((byte)ServerNetObject.END_OF_MESSAGE); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.SelectMode); + msg.WriteUInt16((UInt16)modeIndex); + msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2991,14 +2989,14 @@ namespace Barotrauma.Networking saveName = Path.GetFileNameWithoutExtension(saveName); IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO); + msg.WriteByte((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO); - msg.Write(true); msg.WritePadBits(); - msg.Write(saveName); - msg.Write(mapSeed); - msg.Write(sub.Name); - msg.Write(sub.MD5Hash.StringRepresentation); - msg.Write(settings); + msg.WriteBoolean(true); msg.WritePadBits(); + msg.WriteString(saveName); + msg.WriteString(mapSeed); + msg.WriteString(sub.Name); + msg.WriteString(sub.MD5Hash.StringRepresentation); + msg.WriteNetSerializableStruct(settings); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -3011,10 +3009,10 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.CampaignFrame.Visible = false; IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO); + msg.WriteByte((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO); - msg.Write(false); msg.WritePadBits(); - msg.Write(saveName); + msg.WriteBoolean(false); msg.WritePadBits(); + msg.WriteString(saveName); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -3025,10 +3023,10 @@ namespace Barotrauma.Networking public void RequestRoundEnd(bool save) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.ManageRound); - msg.Write(true); //indicates round end - msg.Write(save); + msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); + msg.WriteUInt16((UInt16)ClientPermissions.ManageRound); + msg.WriteBoolean(true); //indicates round end + msg.WriteBoolean(save); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -3049,11 +3047,11 @@ namespace Barotrauma.Networking if (clientPeer == null) { return false; } IWriteMessage readyToStartMsg = new WriteOnlyMessage(); - readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME); + readyToStartMsg.WriteByte((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.WriteBoolean(true); WriteCharacterInfo(readyToStartMsg); @@ -3480,10 +3478,6 @@ namespace Barotrauma.Networking UserData = client, OnClicked = (btn, userdata) => { VoteForKick(client); btn.Enabled = false; return true; } }; - if (GameMain.NetworkMember.ConnectedClients != null) - { - kickVoteButton.Enabled = !client.HasKickVoteFromSessionId(SessionId); - } } } @@ -3568,21 +3562,21 @@ namespace Barotrauma.Networking public void ReportError(ClientNetError error, UInt16 expectedId = 0, UInt16 eventId = 0, UInt16 entityId = 0) { IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)ClientPacketHeader.ERROR); - outMsg.Write((byte)error); + outMsg.WriteByte((byte)ClientPacketHeader.ERROR); + outMsg.WriteByte((byte)error); switch (error) { case ClientNetError.MISSING_EVENT: - outMsg.Write(expectedId); - outMsg.Write(eventId); + outMsg.WriteUInt16(expectedId); + outMsg.WriteUInt16(eventId); break; case ClientNetError.MISSING_ENTITY: - outMsg.Write(eventId); - outMsg.Write(entityId); - outMsg.Write((byte)Submarine.Loaded.Count); + outMsg.WriteUInt16(eventId); + outMsg.WriteUInt16(entityId); + outMsg.WriteByte((byte)Submarine.Loaded.Count); foreach (Submarine sub in Submarine.Loaded) { - outMsg.Write(sub.Info.Name); + outMsg.WriteString(sub.Info.Name); } break; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs index 929701014..4a46f3c9e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -103,7 +103,7 @@ namespace Barotrauma.Networking eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now; } - msg.Write((byte)ClientNetObject.ENTITY_STATE); + msg.WriteByte((byte)ClientNetObject.ENTITY_STATE); Write(msg, eventsToSync, out _); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/NetEntityEvent.cs index 0bca588f9..f474cd2fd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/NetEntityEvent.cs @@ -17,7 +17,7 @@ namespace Barotrauma.Networking public void Write(IWriteMessage msg) { - msg.Write(CharacterStateID); + msg.WriteUInt16(CharacterStateID); serializable.ClientEventWrite(msg, Data); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/OrderChatMessage.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/OrderChatMessage.cs index 673a8423f..b21592b8d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/OrderChatMessage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/OrderChatMessage.cs @@ -6,8 +6,8 @@ namespace Barotrauma.Networking { public override void ClientWrite(IWriteMessage msg) { - msg.Write((byte)ClientNetObject.CHAT_MESSAGE); - msg.Write(NetStateID); + msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE); + msg.WriteUInt16(NetStateID); msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1); msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1); WriteOrder(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs index b7aa17ec1..e2ce04903 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs @@ -1,60 +1,23 @@ #nullable enable using Barotrauma.Steam; using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; namespace Barotrauma.Networking { - abstract class ClientPeer + internal abstract class ClientPeer { - public class ServerContentPackage - { - public readonly string Name; - public readonly Md5Hash Hash; - public readonly UInt64 WorkshopId; - public readonly DateTime InstallTime; - - public RegularPackage? RegularPackage - { - get - { - return ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash)); - } - } - - public CorePackage? CorePackage - { - get - { - return ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash)); - } - } - - public ContentPackage? ContentPackage - => (ContentPackage?)RegularPackage ?? CorePackage; - - - public string GetPackageStr() - => $"\"{Name}\" (hash {Hash.ShortRepresentation})"; - - public ServerContentPackage(string name, Md5Hash hash, UInt64 workshopId, DateTime installTime) - { - Name = name; - Hash = hash; - WorkshopId = workshopId; - InstallTime = installTime; - } - } - public ImmutableArray ServerContentPackages { get; set; } = ImmutableArray.Empty; public delegate void MessageCallback(IReadMessage message); + public delegate void DisconnectCallback(bool disableReconnect); + public delegate void DisconnectMessageCallback(string message); + public delegate void PasswordCallback(int salt, int retries); + public delegate void InitializationCompleteCallback(); [Obsolete("TODO: delete in nr3-layer-1-2-cleanup")] @@ -65,8 +28,12 @@ namespace Barotrauma.Networking public readonly DisconnectMessageCallback OnDisconnectMessageReceived; public readonly PasswordCallback OnRequestPassword; public readonly InitializationCompleteCallback OnInitializationComplete; - - public Callbacks(MessageCallback onMessageReceived, DisconnectCallback onDisconnect, DisconnectMessageCallback onDisconnectMessageReceived, PasswordCallback onRequestPassword, InitializationCompleteCallback onInitializationComplete) + + public Callbacks(MessageCallback onMessageReceived, + DisconnectCallback onDisconnect, + DisconnectMessageCallback onDisconnectMessageReceived, + PasswordCallback onRequestPassword, + InitializationCompleteCallback onInitializationComplete) { OnMessageReceived = onMessageReceived; OnDisconnect = onDisconnect; @@ -91,98 +58,106 @@ namespace Barotrauma.Networking this.ownerKey = ownerKey; isOwner = ownerKey.IsSome(); } - + public abstract void Start(); public abstract void Close(string? msg = null, bool disableReconnect = false); public abstract void Update(float deltaTime); public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true); public abstract void SendPassword(string password); - protected abstract void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg); + protected abstract void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body); protected ConnectionInitialization initializationStep; public bool ContentPackageOrderReceived { get; protected set; } protected int passwordSalt; protected Steamworks.AuthTicket? steamAuthTicket; - protected void ReadConnectionInitializationStep(IReadMessage inc) + + public struct IncomingInitializationMessage { - ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte(); + public ConnectionInitialization InitializationStep; + public IReadMessage Message; + } - IWriteMessage outMsg; - - switch (step) + protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc) + { + switch (inc.InitializationStep) { case ConnectionInitialization.SteamTicketAndVersion: + { if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; } - outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep); - outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion); - outMsg.Write(GameMain.Client.Name); - outMsg.Write(ownerKey.Fallback(0)); - outMsg.Write(SteamManager.GetSteamId().Select(steamId => steamId.Value).Fallback(0)); - if (steamAuthTicket == null) - { - outMsg.Write((UInt16)0); - } - else - { - outMsg.Write((UInt16)steamAuthTicket.Data.Length); - outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length); - } - outMsg.Write(GameMain.Version.ToString()); - outMsg.Write(GameSettings.CurrentConfig.Language.Value); - SendMsgInternal(DeliveryMethod.Reliable, outMsg); + PeerPacketHeaders headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsConnectionInitializationStep, + Initialization = ConnectionInitialization.SteamTicketAndVersion + }; + + ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket + { + Name = GameMain.Client.Name, + OwnerKey = ownerKey, + SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id), + SteamAuthTicket = steamAuthTicket switch + { + null => Option.None(), + var ticket => Option.Some(ticket.Data) + }, + GameVersion = GameMain.Version.ToString(), + Language = GameSettings.CurrentConfig.Language.Value + }; + + SendMsgInternal(headers, body); break; + } case ConnectionInitialization.ContentPackageOrder: + { if (initializationStep == ConnectionInitialization.SteamTicketAndVersion || - initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; } - if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; } - outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep); - outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder); - - UInt32 packageCount = inc.ReadVariableUInt32(); - List serverPackages = new List(); - for (int i = 0; i < packageCount; i++) + initializationStep == ConnectionInitialization.Password) { - string name = inc.ReadString(); - UInt32 hashByteCount = inc.ReadVariableUInt32(); - byte[] hashBytes = inc.ReadBytes((int)hashByteCount); - UInt64 workshopId = inc.ReadUInt64(); - UInt32 installTimeDiffSeconds = inc.ReadUInt32(); - DateTime installTime = DateTime.UtcNow + TimeSpan.FromSeconds(installTimeDiffSeconds); - - var pkg = new ServerContentPackage(name, Md5Hash.BytesAsHash(hashBytes), workshopId, installTime); - serverPackages.Add(pkg); + initializationStep = ConnectionInitialization.ContentPackageOrder; } + if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; } + + PeerPacketHeaders headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsConnectionInitializationStep, + Initialization = ConnectionInitialization.ContentPackageOrder + }; + + var orderPacket = INetSerializableStruct.Read(inc.Message); + if (!ContentPackageOrderReceived) { - ServerContentPackages = serverPackages.ToImmutableArray(); - if (serverPackages.Count == 0) + ServerContentPackages = orderPacket.ContentPackages; + if (ServerContentPackages.Length == 0) { string errorMsg = "Error in ContentPackageOrder message: list of content packages enabled on the server was empty."; GameAnalyticsManager.AddErrorEventOnce("ClientPeer.ReadConnectionInitializationStep:NoContentPackages", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); DebugConsole.ThrowError(errorMsg); } ContentPackageOrderReceived = true; - SendMsgInternal(DeliveryMethod.Reliable, outMsg); + + SendMsgInternal(headers, null); } + break; + } case ConnectionInitialization.Password: - if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; } + if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) + { + initializationStep = ConnectionInitialization.Password; + } + if (initializationStep != ConnectionInitialization.Password) { return; } - bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits(); - int retries = 0; - if (incomingSalt) - { - passwordSalt = inc.ReadInt32(); - } - else - { - retries = inc.ReadInt32(); - } + + var passwordPacket = INetSerializableStruct.Read(inc.Message); + + passwordPacket.Salt.TryUnwrap(out passwordSalt); + passwordPacket.RetriesLeft.TryUnwrap(out var retries); + callbacks.OnRequestPassword.Invoke(passwordSalt, retries); break; } @@ -192,4 +167,4 @@ namespace Barotrauma.Networking public abstract void ForceTimeOut(); #endif } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs index 80e5a7f65..5cb7795d0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs @@ -1,21 +1,22 @@ -using Barotrauma.Steam; -using Lidgren.Network; +#nullable enable using System; using System.Collections.Generic; using System.Text; +using Lidgren.Network; +using Barotrauma.Steam; namespace Barotrauma.Networking { - class LidgrenClientPeer : ClientPeer + internal sealed class LidgrenClientPeer : ClientPeer { private bool isActive; - private NetClient netClient; - private NetPeerConfiguration netPeerConfiguration; + private NetClient? netClient; + private readonly NetPeerConfiguration netPeerConfiguration; - List incomingLidgrenMessages; + private readonly List incomingLidgrenMessages; - private LidgrenEndpoint lidgrenEndpoint - => ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result } + private LidgrenEndpoint lidgrenEndpoint => + ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result } ? result : throw new InvalidOperationException(); @@ -25,13 +26,6 @@ namespace Barotrauma.Networking netClient = null; isActive = false; - } - - public override void Start() - { - if (isActive) { return; } - - ContentPackageOrderReceived = false; netPeerConfiguration = new NetPeerConfiguration("barotrauma") { @@ -45,6 +39,17 @@ namespace Barotrauma.Networking | NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error); + incomingLidgrenMessages = new List(); + } + + public override void Start() + { + if (isActive) { return; } + + incomingLidgrenMessages.Clear(); + + ContentPackageOrderReceived = false; + netClient = new NetClient(netPeerConfiguration); if (SteamManager.IsInitialized) @@ -56,14 +61,13 @@ namespace Barotrauma.Networking } } - incomingLidgrenMessages = new List(); - initializationStep = ConnectionInitialization.SteamTicketAndVersion; - if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpoint)) + if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpointValue)) { - throw new InvalidCastException("endPoint is not IPEndPoint"); + throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}"); } + if (ServerConnection != null) { throw new InvalidOperationException("ServerConnection is not null"); @@ -71,8 +75,8 @@ namespace Barotrauma.Networking netClient.Start(); - var netConnection = netClient.Connect(lidgrenEndpoint.NetEndpoint); - + var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint); + ServerConnection = new LidgrenConnection(netConnection) { Status = NetworkConnectionStatus.Connected @@ -85,11 +89,18 @@ namespace Barotrauma.Networking { if (!isActive) { return; } + ToolBox.ThrowIfNull(netClient); + ToolBox.ThrowIfNull(incomingLidgrenMessages); + if (isOwner && !(ChildServerRelay.Process is { HasExited: false })) { Close(); var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage); - msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; }; + msgBox.Buttons[0].OnClicked += (btn, obj) => + { + GameMain.MainMenuScreen.Select(); + return false; + }; return; } @@ -101,7 +112,11 @@ namespace Barotrauma.Networking foreach (NetIncomingMessage inc in incomingLidgrenMessages) { - if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint)) { continue; } + if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint)) + { + DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}"); + continue; + } switch (inc.MessageType) { @@ -115,15 +130,23 @@ namespace Barotrauma.Networking } } - private void HandleDataMessage(NetIncomingMessage inc) + private void HandleDataMessage(NetIncomingMessage lidgrenMsg) { if (!isActive) { return; } - PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); + ToolBox.ThrowIfNull(ServerConnection); + + IReadMessage inc = lidgrenMsg.ToReadMessage(); + + var (_, packetHeader, initialization) = INetSerializableStruct.Read(inc); if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success) { - ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false)); + ReadConnectionInitializationStep(new IncomingInitializationMessage + { + InitializationStep = initialization ?? throw new Exception("Initialization step missing"), + Message = inc + }); } else { @@ -132,9 +155,9 @@ namespace Barotrauma.Networking callbacks.OnInitializationComplete.Invoke(); initializationStep = ConnectionInitialization.Success; } - UInt16 length = inc.ReadUInt16(); - IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, ServerConnection); - callbacks.OnMessageReceived.Invoke(msg); + + var packet = INetSerializableStruct.Read(inc); + callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection)); } } @@ -142,7 +165,7 @@ namespace Barotrauma.Networking { if (!isActive) { return; } - NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte(); + NetConnectionStatus status = inc.ReadHeader(); switch (status) { case NetConnectionStatus.Disconnected: @@ -157,29 +180,38 @@ namespace Barotrauma.Networking { if (!isActive) { return; } + ToolBox.ThrowIfNull(netClient); + if (initializationStep != ConnectionInitialization.Password) { return; } - NetOutgoingMessage outMsg = netClient.CreateMessage(); - outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep); - outMsg.Write((byte)ConnectionInitialization.Password); - byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt); - outMsg.Write((byte)saltedPw.Length); - outMsg.Write(saltedPw, 0, saltedPw.Length); - NetSendResult result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered); - if (result != NetSendResult.Queued && result != NetSendResult.Sent) + + var headers = new PeerPacketHeaders { - DebugConsole.NewMessage("Failed to send " + initializationStep.ToString() + " message to host: " + result); - } + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsConnectionInitializationStep, + Initialization = ConnectionInitialization.Password + }; + var body = new ClientPeerPasswordPacket + { + Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt) + }; + + SendMsgInternal(headers, body); } - public override void Close(string msg = null, bool disableReconnect = false) + public override void Close(string? msg = null, bool disableReconnect = false) { if (!isActive) { return; } + ToolBox.ThrowIfNull(netClient); + isActive = false; netClient.Shutdown(msg ?? TextManager.Get("Disconnecting").Value); netClient = null; - steamAuthTicket?.Cancel(); steamAuthTicket = null; + + steamAuthTicket?.Cancel(); + steamAuthTicket = null; + callbacks.OnDisconnect.Invoke(disableReconnect); } @@ -187,19 +219,8 @@ namespace Barotrauma.Networking { if (!isActive) { return; } - NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable; - switch (deliveryMethod) - { - case DeliveryMethod.Unreliable: - lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable; - break; - case DeliveryMethod.Reliable: - lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered; - break; - case DeliveryMethod.ReliableOrdered: - lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered; - break; - } + ToolBox.ThrowIfNull(netClient); + ToolBox.ThrowIfNull(netPeerConfiguration); #if DEBUG netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance; @@ -208,30 +229,42 @@ namespace Barotrauma.Networking netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss; #endif - NetOutgoingMessage lidgrenMsg = netClient.CreateMessage(); - byte[] msgData = new byte[msg.LengthBytes]; - msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); - lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None)); - lidgrenMsg.Write((UInt16)length); - lidgrenMsg.Write(msgData, 0, length); + byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _); - NetSendResult result = netClient.SendMessage(lidgrenMsg, lidgrenDeliveryMethod); + var headers = new PeerPacketHeaders + { + DeliveryMethod = deliveryMethod, + PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None, + Initialization = null + }; + var body = new PeerPacketMessage + { + Buffer = bufAux + }; + + SendMsgInternal(headers, body); + } + + protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body) + { + ToolBox.ThrowIfNull(netClient); + + IWriteMessage msg = new WriteOnlyMessage(); + msg.WriteNetSerializableStruct(headers); + body?.Write(msg); + + NetSendResult result = ForwardToLidgren(msg, DeliveryMethod.Reliable); if (result != NetSendResult.Queued && result != NetSendResult.Sent) { - DebugConsole.NewMessage("Failed to send message to host: " + result); + DebugConsole.NewMessage($"Failed to send message to host: {result}\n{Environment.StackTrace}"); } } - protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg) + private NetSendResult ForwardToLidgren(IWriteMessage msg, DeliveryMethod deliveryMethod) { - NetOutgoingMessage lidgrenMsg = netClient.CreateMessage(); - lidgrenMsg.Write(msg.Buffer, 0, msg.LengthBytes); + ToolBox.ThrowIfNull(netClient); - NetSendResult result = netClient.SendMessage(lidgrenMsg, NetDeliveryMethod.ReliableUnordered); - if (result != NetSendResult.Queued && result != NetSendResult.Sent) - { - DebugConsole.NewMessage("Failed to send message to host: " + result + "\n" + Environment.StackTrace); - } + return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren()); } #if DEBUG @@ -241,4 +274,4 @@ namespace Barotrauma.Networking } #endif } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs index c4c30ce12..64c8b29b5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs @@ -1,13 +1,14 @@ -using Barotrauma.Steam; +#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Text; +using Barotrauma.Steam; using System.Threading; namespace Barotrauma.Networking { - class SteamP2PClientPeer : ClientPeer + internal sealed class SteamP2PClientPeer : ClientPeer { private bool isActive; private readonly SteamId hostSteamId; @@ -17,20 +18,20 @@ namespace Barotrauma.Networking private long sentBytes, receivedBytes; - private List incomingInitializationMessages; - private List incomingDataMessages; + private readonly List incomingInitializationMessages = new List(); + private readonly List incomingDataMessages = new List(); public SteamP2PClientPeer(SteamP2PEndpoint endpoint, Callbacks callbacks) : base(endpoint, callbacks, Option.None()) { ServerConnection = null; isActive = false; - + if (!(ServerEndpoint is SteamP2PEndpoint steamIdEndpoint)) { throw new InvalidCastException("endPoint is not SteamId"); } - + hostSteamId = steamIdEndpoint.SteamId; } @@ -55,16 +56,13 @@ namespace Barotrauma.Networking ServerConnection = new SteamP2PConnection(hostSteamId); ServerConnection.SetAccountInfo(new AccountInfo(hostSteamId)); - incomingInitializationMessages = new List(); - incomingDataMessages = new List(); - - IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)DeliveryMethod.Reliable); - outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep); - outMsg.Write((byte)ConnectionInitialization.ConnectionStarted); - - Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable); - sentBytes += outMsg.LengthBytes; + var headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsConnectionInitializationStep, + Initialization = ConnectionInitialization.ConnectionStarted + }; + SendMsgInternal(headers, null); initializationStep = ConnectionInitialization.SteamTicketAndVersion; @@ -78,6 +76,7 @@ namespace Barotrauma.Networking private void OnIncomingConnection(Steamworks.SteamId steamId) { if (!isActive) { return; } + if (steamId == hostSteamId.Value) { Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); @@ -86,16 +85,18 @@ namespace Barotrauma.Networking initializationStep != ConnectionInitialization.ContentPackageOrder && initializationStep != ConnectionInitialization.Success) { - DebugConsole.ThrowError($"Connection from incorrect SteamID was rejected: "+ - $"expected {hostSteamId}," + - $"got {new SteamId(steamId)}"); + DebugConsole.ThrowError("Connection from incorrect SteamID was rejected: " + + $"expected {hostSteamId}," + + $"got {new SteamId(steamId)}"); } } private void OnConnectionFailed(Steamworks.SteamId steamId, Steamworks.P2PSessionError error) { if (!isActive) { return; } + if (steamId != hostSteamId.Value) { return; } + Close($"SteamP2P connection failed: {error}"); callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P connection failed: {error}"); } @@ -103,27 +104,31 @@ namespace Barotrauma.Networking private void OnP2PData(ulong steamId, byte[] data, int dataLength) { if (!isActive) { return; } + if (steamId != hostSteamId.Value) { return; } - timeout = Screen.Selected == GameMain.GameScreen ? - NetworkConnection.TimeoutThresholdInGame : - NetworkConnection.TimeoutThreshold; - - PacketHeader packetHeader = (PacketHeader)data[0]; + timeout = Screen.Selected == GameMain.GameScreen + ? NetworkConnection.TimeoutThresholdInGame + : NetworkConnection.TimeoutThreshold; + + IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection); + + var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read(inc); if (!packetHeader.IsServerMessage()) { return; } - if (packetHeader.IsConnectionInitializationStep()) + if (packetHeader.IsConnectionInitializationStep() && initialization.HasValue) { - ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8); - ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8 + 32); - ulong lobbyId = low + (high << 32); + var relayPacket = INetSerializableStruct.Read(inc); - Steam.SteamManager.JoinLobby(lobbyId, false); - IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - (1 + 8), ServerConnection); + SteamManager.JoinLobby(relayPacket.LobbyID, false); if (initializationStep != ConnectionInitialization.Success) { - incomingInitializationMessages.Add(inc); + incomingInitializationMessages.Add(new IncomingInitializationMessage + { + InitializationStep = initialization.Value, + Message = relayPacket.Message.GetReadMessage() + }); } } else if (packetHeader.IsHeartbeatMessage()) @@ -132,17 +137,14 @@ namespace Barotrauma.Networking } else if (packetHeader.IsDisconnectMessage()) { - IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection); - string msg = inc.ReadString(); - Close(msg); - callbacks.OnDisconnectMessageReceived.Invoke(msg); + PeerDisconnectPacket packet = INetSerializableStruct.Read(inc); + Close(packet.Message); + callbacks.OnDisconnectMessageReceived.Invoke(packet.Message); } else { - UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(data, 16, 8); - - IReadMessage inc = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 3, length, ServerConnection); - incomingDataMessages.Add(inc); + var packet = INetSerializableStruct.Read(inc); + incomingDataMessages.Add(packet.GetReadMessage()); } } @@ -154,6 +156,7 @@ namespace Barotrauma.Networking { timeout -= deltaTime; } + heartbeatTimer -= deltaTime; if (initializationStep != ConnectionInitialization.Password && @@ -163,20 +166,20 @@ namespace Barotrauma.Networking connectionStatusTimer -= deltaTime; if (connectionStatusTimer <= 0.0) { - var state = Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value); - if (state == null) + if (Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value) is { } state) + { + if (state.P2PSessionError != Steamworks.P2PSessionError.None) + { + Close($"SteamP2P error code: {state.P2PSessionError}"); + callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state.P2PSessionError}"); + } + } + else { Close("SteamP2P connection could not be established"); callbacks.OnDisconnectMessageReceived.Invoke(DisconnectReason.SteamP2PError.ToString()); } - else - { - if (state?.P2PSessionError != Steamworks.P2PSessionError.None) - { - Close($"SteamP2P error code: {state?.P2PSessionError}"); - callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state?.P2PSessionError}"); - } - } + connectionStatusTimer = 1.0f; } } @@ -184,11 +187,12 @@ namespace Barotrauma.Networking for (int i = 0; i < 100; i++) { if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; } + var packet = Steamworks.SteamNetworking.ReadP2PPacket(); - if (packet.HasValue) + if (packet is { SteamId: var steamId, Data: var data }) { - OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0); - receivedBytes += packet?.Data.Length ?? 0; + OnP2PData(steamId, data, data.Length); + receivedBytes += data.Length; } } @@ -197,14 +201,13 @@ namespace Barotrauma.Networking if (heartbeatTimer < 0.0) { - IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)DeliveryMethod.Unreliable); - outMsg.Write((byte)PacketHeader.IsHeartbeatMessage); - - Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Unreliable); - sentBytes += outMsg.LengthBytes; - - heartbeatTimer = 5.0; + var headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Unreliable, + PacketHeader = PacketHeader.IsHeartbeatMessage, + Initialization = null + }; + SendMsgInternal(headers, null); } if (timeout < 0.0) @@ -238,7 +241,7 @@ namespace Barotrauma.Networking } else { - foreach (IReadMessage inc in incomingInitializationMessages) + foreach (var inc in incomingInitializationMessages) { ReadConnectionInitializationStep(inc); } @@ -261,76 +264,40 @@ namespace Barotrauma.Networking { if (!isActive) { return; } - byte[] buf = new byte[msg.LengthBytes + 4]; - buf[0] = (byte)deliveryMethod; + byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _); - byte[] bufAux = new byte[msg.LengthBytes]; - msg.PrepareForSending(ref bufAux, compressPastThreshold, out bool isCompressed, out int length); - - buf[1] = (byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None); - - buf[2] = (byte)(length & 0xff); - buf[3] = (byte)((length >> 8) & 0xff); - - Array.Copy(bufAux, 0, buf, 4, length); - - Steamworks.P2PSend sendType; - switch (deliveryMethod) + var headers = new PeerPacketHeaders { - case DeliveryMethod.Reliable: - case DeliveryMethod.ReliableOrdered: - //the documentation seems to suggest that the Reliable send type - //enforces packet order (TODO: verify) - sendType = Steamworks.P2PSend.Reliable; - break; - default: - sendType = Steamworks.P2PSend.Unreliable; - break; - } - - if (length + 8 >= MsgConstants.MTU) + DeliveryMethod = deliveryMethod, + PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None, + Initialization = null + }; + var body = new PeerPacketMessage { - DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + length.ToString() + " bytes)"); - sendType = Steamworks.P2PSend.Reliable; - } + Buffer = bufAux + }; heartbeatTimer = 5.0; + // Using an extra local method here to reduce chance of error whenever we need to change this + void performSend() => SendMsgInternal(headers, body); #if DEBUG CoroutineManager.Invoke(() => - { - if (GameMain.Client == null) { return; } - if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; } - int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1; - for (int i = 0; i < count; i++) { - Send(buf, length + 4, sendType); - } - }, - GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency)); -#else - Send(buf, length + 4, sendType); -#endif - } + if (GameMain.Client == null) { return; } - private void Send(byte[] buf, int length, Steamworks.P2PSend sendType) - { - bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, buf, length + 4, 0, sendType); - sentBytes += length + 4; - if (!successSend) - { - if (sendType != Steamworks.P2PSend.Reliable) - { - DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + length.ToString() + " bytes)"); - sendType = Steamworks.P2PSend.Reliable; - successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, buf, length + 4, 0, sendType); - sentBytes += length + 4; - } - if (!successSend) - { - DebugConsole.AddWarning("Failed to send message to remote peer! (" + length.ToString() + " bytes)"); - } - } + if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && deliveryMethod is DeliveryMethod.Unreliable) { return; } + + int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1; + for (int i = 0; i < count; i++) + { + performSend(); + } + }, + GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency)); +#else + performSend(); +#endif } public override void SendPassword(string password) @@ -338,20 +305,22 @@ namespace Barotrauma.Networking if (!isActive) { return; } if (initializationStep != ConnectionInitialization.Password) { return; } - IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)DeliveryMethod.Reliable); - outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep); - outMsg.Write((byte)ConnectionInitialization.Password); - byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt); - outMsg.Write((byte)saltedPw.Length); - outMsg.Write(saltedPw, 0, saltedPw.Length); - heartbeatTimer = 5.0; - Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable); - sentBytes += outMsg.LengthBytes; + var headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsConnectionInitializationStep, + Initialization = ConnectionInitialization.Password + }; + var body = new ClientPeerPasswordPacket + { + Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt) + }; + + SendMsgInternal(headers, body); } - - public override void Close(string msg = null, bool disableReconnect = false) + + public override void Close(string? msg = null, bool disableReconnect = false) { if (!isActive) { return; } @@ -359,54 +328,59 @@ namespace Barotrauma.Networking isActive = false; - IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)DeliveryMethod.Reliable); - outMsg.Write((byte)PacketHeader.IsDisconnectMessage); - outMsg.Write(msg ?? "Disconnected"); + var headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsDisconnectMessage, + Initialization = null + }; + var body = new PeerDisconnectPacket + { + Message = msg ?? "Disconnected" + }; - try - { - Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable); - sentBytes += outMsg.LengthBytes; - } - catch (Exception e) - { - DebugConsole.ThrowError("Failed to send a disconnect message to the server using SteamP2P.", e); - } + SendMsgInternal(headers, body); Thread.Sleep(100); Steamworks.SteamNetworking.ResetActions(); Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId.Value); - steamAuthTicket?.Cancel(); steamAuthTicket = null; + steamAuthTicket?.Cancel(); + steamAuthTicket = null; callbacks.OnDisconnect.Invoke(disableReconnect); } - protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg) + protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body) { - Steamworks.P2PSend sendType; - switch (deliveryMethod) + IWriteMessage msgToSend = new WriteOnlyMessage(); + msgToSend.WriteNetSerializableStruct(headers); + body?.Write(msgToSend); + ForwardToSteamP2P(msgToSend, headers.DeliveryMethod); + } + + private void ForwardToSteamP2P(IWriteMessage msg, DeliveryMethod deliveryMethod) + { + heartbeatTimer = 5.0; + int length = msg.LengthBytes; + + bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, deliveryMethod.ToSteam()); + sentBytes += length; + + if (successSend) { return; } + + if (deliveryMethod is DeliveryMethod.Unreliable) { - case DeliveryMethod.Reliable: - case DeliveryMethod.ReliableOrdered: - //the documentation seems to suggest that the Reliable send type - //enforces packet order (TODO: verify) - sendType = Steamworks.P2PSend.Reliable; - break; - default: - sendType = Steamworks.P2PSend.Unreliable; - break; + DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)"); + successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, DeliveryMethod.Reliable.ToSteam()); + sentBytes += length; } - IWriteMessage msgToSend = new WriteOnlyMessage(); - msgToSend.Write((byte)deliveryMethod); - msgToSend.Write(msg.Buffer, 0, msg.LengthBytes); - - heartbeatTimer = 5.0; - Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msgToSend.Buffer, msgToSend.LengthBytes, 0, sendType); - sentBytes += msg.LengthBytes; + if (!successSend) + { + DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)"); + } } #if DEBUG @@ -416,4 +390,4 @@ namespace Barotrauma.Networking } #endif } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs index 0f6641332..23deeacce 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs @@ -1,38 +1,46 @@ -using Barotrauma.Extensions; +#nullable enable using Barotrauma.Steam; using System; using System.Collections.Generic; using System.Threading; +using Barotrauma.Extensions; namespace Barotrauma.Networking { - class SteamP2POwnerPeer : ClientPeer + sealed class SteamP2POwnerPeer : ClientPeer { private bool isActive; private readonly SteamId selfSteamID; private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0)); - private SteamId ReadSteamId(IReadMessage inc) - => new SteamId(inc.ReadUInt64() ^ ownerKey64); - private void WriteSteamId(IWriteMessage msg, SteamId val) - => msg.Write(val.Value ^ ownerKey64); - + private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64); + private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64); + private long sentBytes, receivedBytes; - class RemotePeer + private sealed class RemotePeer { - public SteamId SteamId; + public readonly SteamId SteamId; public Option OwnerSteamId; public double? DisconnectTime; public bool Authenticating; public bool Authenticated; - public class UnauthedMessage + public readonly struct UnauthedMessage { - public DeliveryMethod DeliveryMethod; - public IWriteMessage Message; + public readonly SteamId Sender; + public readonly byte[] Bytes; + public readonly int Length; + + public UnauthedMessage(SteamId sender, byte[] bytes) + { + Sender = sender; + Bytes = bytes; + Length = bytes.Length; + } } + public readonly List UnauthedMessages; public RemotePeer(SteamId steamId) @@ -45,9 +53,9 @@ namespace Barotrauma.Networking UnauthedMessages = new List(); } - } - List remotePeers; + + private List remotePeers = null!; public SteamP2POwnerPeer(Callbacks callbacks, int ownerKey) : base(new PipeEndpoint(), callbacks, Option.Some(ownerKey)) { @@ -84,8 +92,8 @@ namespace Barotrauma.Networking private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status) { - RemotePeer remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId); - DebugConsole.Log(steamId + " validation: " + status + ", " + (remotePeer != null)); + RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId); + DebugConsole.Log($"{steamId} validation: {status}, {remotePeer != null}"); if (remotePeer == null) { return; } @@ -93,36 +101,32 @@ namespace Barotrauma.Networking { if (status != Steamworks.AuthResponse.OK) { - DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString()); + DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication status changed: {status}"); } + return; } if (status == Steamworks.AuthResponse.OK) { - remotePeer.OwnerSteamId = Option.Some(new SteamId(ownerId)); + SteamId ownerSteamId = new SteamId(ownerId); + remotePeer.OwnerSteamId = Option.Some(ownerSteamId); remotePeer.Authenticated = true; remotePeer.Authenticating = false; - foreach (var msg in remotePeer.UnauthedMessages) + foreach (var unauthedMessage in remotePeer.UnauthedMessages) { - //rewrite the owner id before - //forwarding the messages to - //the server, since it's only - //known now - int prevBitPosition = msg.Message.BitPosition; - msg.Message.BitPosition = sizeof(ulong) * 8; - WriteSteamId(msg.Message, new SteamId(ownerId)); - msg.Message.BitPosition = prevBitPosition; - byte[] msgToSend = (byte[])msg.Message.Buffer.Clone(); - Array.Resize(ref msgToSend, msg.Message.LengthBytes); - ChildServerRelay.Write(msgToSend); + IWriteMessage msg = new WriteOnlyMessage(); + WriteSteamId(msg, unauthedMessage.Sender); + WriteSteamId(msg, ownerSteamId); + msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.Length); + ForwardToServerProcess(msg); } + remotePeer.UnauthedMessages.Clear(); } else { - DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString()); - return; + DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication failed: {status}"); } } @@ -138,58 +142,54 @@ namespace Barotrauma.Networking Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); //accept all connections, the server will figure things out later } - private void OnP2PData(ulong steamId, byte[] data, int dataLength, int _) + private void OnP2PData(ulong steamId, IReadMessage inc) { if (!isActive) { return; } - RemotePeer remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId); + RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId); if (remotePeer == null) { return; } + if (remotePeer.DisconnectTime != null) { return; } - IWriteMessage outMsg = new WriteOnlyMessage(); - var steamUserId = new SteamId(steamId); - WriteSteamId(outMsg, steamUserId); - WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId)); - outMsg.Write(data, 1, dataLength - 1); - - DeliveryMethod deliveryMethod = (DeliveryMethod)data[0]; - - PacketHeader packetHeader = (PacketHeader)data[1]; + var peerPacketHeaders = INetSerializableStruct.Read(inc); + + PacketHeader packetHeader = peerPacketHeaders.PacketHeader; if (!remotePeer.Authenticated && !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep()) { remotePeer.DisconnectTime = null; - IReadMessage authMsg = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 2, dataLength - 2, null); - ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte(); - if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) + ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing"); + if (initialization == ConnectionInitialization.SteamTicketAndVersion) { remotePeer.Authenticating = true; - - authMsg.ReadString(); //skip name - authMsg.ReadInt32(); //skip owner key - authMsg.ReadUInt64(); //skip steamid - UInt16 ticketLength = authMsg.ReadUInt16(); - byte[] ticket = authMsg.ReadBytes(ticketLength); - Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId); + var packet = INetSerializableStruct.Read(inc); + + packet.SteamAuthTicket.TryUnwrap(out byte[] ticket); + + Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId); if (authSessionStartState != Steamworks.BeginAuthResult.OK) { - DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString()); + DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam auth session failed to start: {authSessionStartState}"); return; } } } + var steamUserId = new SteamId(steamId); if (remotePeer.Authenticating) { - remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage() { DeliveryMethod = deliveryMethod, Message = outMsg }); + remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(steamUserId, inc.Buffer)); } else { - byte[] msgToSend = (byte[])outMsg.Buffer.Clone(); - Array.Resize(ref msgToSend, outMsg.LengthBytes); - ChildServerRelay.Write(msgToSend); + IWriteMessage outMsg = new WriteOnlyMessage(); + WriteSteamId(outMsg, steamUserId); + WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId)); + outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes); + + ForwardToServerProcess(outMsg); } } @@ -201,7 +201,11 @@ namespace Barotrauma.Networking { Close(); var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage); - msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; }; + msgBox.Buttons[0].OnClicked += (btn, obj) => + { + GameMain.MainMenuScreen.Select(); + return false; + }; return; } @@ -216,11 +220,12 @@ namespace Barotrauma.Networking for (int i = 0; i < 100; i++) { if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; } + var packet = Steamworks.SteamNetworking.ReadP2PPacket(); - if (packet.HasValue) + if (packet is { SteamId: var steamId, Data: var data }) { - OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0, 0); - receivedBytes += packet?.Data.Length ?? 0; + OnP2PData(steamId, new ReadWriteMessage(data, 0, data.Length * 8, false)); + receivedBytes += data.Length; } } @@ -240,155 +245,144 @@ namespace Barotrauma.Networking if (!isActive) { return; } SteamId recipientSteamId = ReadSteamId(inc); - DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte(); - int p2pDataStart = inc.BytePosition; - - PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); + var peerPacketHeaders = INetSerializableStruct.Read(inc); if (recipientSteamId != selfSteamID) { - if (!packetHeader.IsServerMessage()) - { - DebugConsole.ThrowError("Received non-server message meant for remote peer"); - return; - } - - RemotePeer peer = remotePeers.Find(p => p.SteamId == recipientSteamId); - - if (peer == null) { return; } - - if (packetHeader.IsDisconnectMessage()) - { - DisconnectPeer(peer, inc.ReadString()); - return; - } - - Steamworks.P2PSend sendType; - switch (deliveryMethod) - { - case DeliveryMethod.Reliable: - case DeliveryMethod.ReliableOrdered: - //the documentation seems to suggest that the - //Reliable send type enforces packet order - sendType = Steamworks.P2PSend.Reliable; - break; - default: - sendType = Steamworks.P2PSend.Unreliable; - break; - } - - byte[] p2pData; - - if (packetHeader.IsConnectionInitializationStep()) - { - p2pData = new byte[inc.LengthBytes - p2pDataStart + 8]; - p2pData[0] = inc.Buffer[p2pDataStart]; - Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 8 * 8, p2pData, 1 * 8); - Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 1 + 8, inc.LengthBytes - p2pDataStart - 1); - } - else - { - p2pData = new byte[inc.LengthBytes - p2pDataStart]; - Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length); - - if (!packetHeader.IsHeartbeatMessage() && !packetHeader.IsDisconnectMessage()) - { - UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(p2pData, 16, 8); - if (length > p2pData.Length - 2) - { - string errorMsg = $"Length written in message to send to client is larger than buffer size ({length} > {p2pData.Length - 2})"; - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce( - "SteamP2POwnerPeerLengthValidationFail", - GameAnalyticsManager.ErrorSeverity.Error, - errorMsg); - } - } - } - - if (p2pData.Length + 4 >= MsgConstants.MTU) - { - DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + p2pData.Length.ToString() + " bytes)"); - sendType = Steamworks.P2PSend.Reliable; - } - - bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId.Value, p2pData, p2pData.Length, 0, sendType); - sentBytes += p2pData.Length; - - if (!successSend) - { - if (sendType != Steamworks.P2PSend.Reliable) - { - DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + p2pData.Length.ToString() + " bytes)"); - sendType = Steamworks.P2PSend.Reliable; - successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId.Value, p2pData, p2pData.Length, 0, sendType); - sentBytes += p2pData.Length; - } - if (!successSend) - { - DebugConsole.AddWarning("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)"); - } - } + HandleMessageForRemotePeer(peerPacketHeaders, recipientSteamId, inc); } else { - if (packetHeader.IsDisconnectMessage()) - { - DebugConsole.ThrowError("Received disconnect message from owned server"); - return; - } - if (!packetHeader.IsServerMessage()) - { - DebugConsole.ThrowError("Received non-server message from owned server"); - return; - } - if (packetHeader.IsHeartbeatMessage()) - { - return; //no timeout since we're using pipes, ignore this message - } - if (packetHeader.IsConnectionInitializationStep()) - { - IWriteMessage outMsg = new WriteOnlyMessage(); - WriteSteamId(outMsg, selfSteamID); - WriteSteamId(outMsg, selfSteamID); - outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep)); - outMsg.Write(GameMain.Client.Name); - - byte[] msgToSend = (byte[])outMsg.Buffer.Clone(); - Array.Resize(ref msgToSend, outMsg.LengthBytes); - ChildServerRelay.Write(msgToSend); - return; - } - else - { - if (initializationStep != ConnectionInitialization.Success) - { - callbacks.OnInitializationComplete.Invoke(); - initializationStep = ConnectionInitialization.Success; - } - UInt16 length = inc.ReadUInt16(); - IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, ServerConnection); - callbacks.OnMessageReceived.Invoke(msg); - - return; - } + HandleMessageForOwner(peerPacketHeaders, inc); } } + private static byte[] GetRemainingBytes(IReadMessage msg) + { + return msg.Buffer[msg.BytePosition..msg.LengthBytes]; + } + + private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, SteamId recipientSteamId, IReadMessage inc) + { + var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders; + + if (!packetHeader.IsServerMessage()) + { + DebugConsole.ThrowError("Received non-server message meant for remote peer"); + return; + } + + RemotePeer? peer = remotePeers.Find(p => p.SteamId == recipientSteamId); + if (peer is null) { return; } + + if (packetHeader.IsDisconnectMessage()) + { + var packet = INetSerializableStruct.Read(inc); + DisconnectPeer(peer, packet.Message); + return; + } + + IWriteMessage outMsg = new WriteOnlyMessage(); + + outMsg.WriteNetSerializableStruct(new PeerPacketHeaders + { + DeliveryMethod = deliveryMethod, + PacketHeader = packetHeader, + Initialization = initialization + }); + + if (packetHeader.IsConnectionInitializationStep()) + { + var initRelayPacket = new SteamP2PInitializationRelayPacket + { + LobbyID = SteamManager.CurrentLobbyID, + Message = new PeerPacketMessage + { + Buffer = GetRemainingBytes(inc) + } + }; + + outMsg.WriteNetSerializableStruct(initRelayPacket); + } + else + { + byte[] userMessage = GetRemainingBytes(inc); + outMsg.WriteBytes(userMessage, 0, userMessage.Length); + } + + ForwardToRemotePeer(deliveryMethod, recipientSteamId, outMsg); + } + + private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc) + { + var (_, packetHeader, _) = peerPacketHeaders; + + if (packetHeader.IsDisconnectMessage()) + { + DebugConsole.ThrowError("Received disconnect message from owned server"); + return; + } + + if (!packetHeader.IsServerMessage()) + { + DebugConsole.ThrowError("Received non-server message from owned server"); + return; + } + + if (packetHeader.IsHeartbeatMessage()) + { + return; //no timeout since we're using pipes, ignore this message + } + + if (packetHeader.IsConnectionInitializationStep()) + { + IWriteMessage outMsg = new WriteOnlyMessage(); + WriteSteamId(outMsg, selfSteamID); + WriteSteamId(outMsg, selfSteamID); + outMsg.WriteNetSerializableStruct(new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsConnectionInitializationStep, + Initialization = ConnectionInitialization.SteamTicketAndVersion + }); + outMsg.WriteNetSerializableStruct(new SteamP2PInitializationOwnerPacket + { + OwnerName = GameMain.Client.Name + }); + ForwardToServerProcess(outMsg); + } + else + { + if (initializationStep != ConnectionInitialization.Success) + { + callbacks.OnInitializationComplete.Invoke(); + initializationStep = ConnectionInitialization.Success; + } + + PeerPacketMessage packet = INetSerializableStruct.Read(inc); + IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection); + callbacks.OnMessageReceived.Invoke(msg); + } + } + private void DisconnectPeer(RemotePeer peer, string msg) { if (!string.IsNullOrWhiteSpace(msg)) { - if (peer.DisconnectTime == null) - { - peer.DisconnectTime = Timing.TotalTime + 1.0; - } + peer.DisconnectTime ??= Timing.TotalTime + 1.0; IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)(PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage)); - outMsg.Write(msg); - + outMsg.WriteNetSerializableStruct(new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage + }); + outMsg.WriteNetSerializableStruct(new PeerDisconnectPacket + { + Message = msg + }); + Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable); sentBytes += outMsg.LengthBytes; } @@ -406,10 +400,10 @@ namespace Barotrauma.Networking public override void SendPassword(string password) { - return; //owner doesn't send passwords + //owner doesn't send passwords } - public override void Close(string msg = null, bool disableReconnect = false) + public override void Close(string? msg = null, bool disableReconnect = false) { if (!isActive) { return; } @@ -441,25 +435,62 @@ namespace Barotrauma.Networking if (!isActive) { return; } IWriteMessage msgToSend = new WriteOnlyMessage(); - byte[] msgData = new byte[msg.LengthBytes]; - msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); + byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _); WriteSteamId(msgToSend, selfSteamID); WriteSteamId(msgToSend, selfSteamID); - msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None)); - msgToSend.Write((UInt16)length); - msgToSend.Write(msgData, 0, length); - - byte[] bufToSend = (byte[])msgToSend.Buffer.Clone(); - Array.Resize(ref bufToSend, msgToSend.LengthBytes); - ChildServerRelay.Write(bufToSend); + msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders + { + DeliveryMethod = deliveryMethod, + PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None + }); + msgToSend.WriteNetSerializableStruct(new PeerPacketMessage + { + Buffer = msgData + }); + ForwardToServerProcess(msgToSend); } - protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg) + protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body) { //not currently used by SteamP2POwnerPeer throw new NotImplementedException(); } + private static void ForwardToServerProcess(IWriteMessage msg) + { + byte[] bufToSend = new byte[msg.LengthBytes]; + msg.Buffer[..msg.LengthBytes].CopyTo(bufToSend.AsSpan()); + ChildServerRelay.Write(bufToSend); + } + + private void ForwardToRemotePeer(DeliveryMethod deliveryMethod, SteamId recipent, IWriteMessage outMsg) + { + byte[] buf = outMsg.PrepareForSending(compressPastThreshold: false, out _, out int length); + + if (length + 4 >= MsgConstants.MTU) + { + DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)"); + deliveryMethod = DeliveryMethod.Reliable; + } + + bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, deliveryMethod.ToSteam()); + sentBytes += length; + + if (successSend) { return; } + + if (deliveryMethod is DeliveryMethod.Unreliable) + { + DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)"); + successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, DeliveryMethod.Reliable.ToSteam()); + sentBytes += length; + } + + if (!successSend) + { + DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)"); + } + } + #if DEBUG public override void ForceTimeOut() { @@ -467,4 +498,4 @@ namespace Barotrauma.Networking } #endif } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/RespawnManager.cs index 092f871d4..30a3b78a8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/RespawnManager.cs @@ -44,14 +44,19 @@ namespace Barotrauma.Networking if (!UseRespawnPrompt) { return; } if (CoroutineManager.IsCoroutineRunning(respawnPromptCoroutine) || GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "respawnquestionprompt")) { - return; + return; } respawnPromptCoroutine = CoroutineManager.Invoke(() => { if (Character.Controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; } + + LocalizedString text = + TextManager.GetWithVariable("respawnskillpenalty", "[percentage]", ((int)(SkillReductionOnDeath * 100)).ToString()) + + "\n\n" + TextManager.Get("respawnquestionprompt"); + var respawnPrompt = new GUIMessageBox( - TextManager.Get("tutorial.tryagainheader"), TextManager.Get("respawnquestionprompt"), + TextManager.Get("tutorial.tryagainheader"), text, new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") }) { UserData = "respawnquestionprompt" diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs index acfa28618..93e854e66 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs @@ -348,7 +348,7 @@ namespace Barotrauma.Networking ServerName = element.GetAttributeString("ServerName", ""), ServerMessage = element.GetAttributeString("ServerMessage", ""), Endpoint = endpoint, - QueryPort = element.GetAttributeInt("QueryPort", 0), + QueryPort = !string.IsNullOrEmpty(element.GetAttributeString("QueryPort", string.Empty)) ? element.GetAttributeInt("QueryPort", 0) : 0, GameMode = element.GetAttributeIdentifier("GameMode", Identifier.Empty), GameVersion = element.GetAttributeString("GameVersion", ""), MaxPlayers = Math.Min(element.GetAttributeInt("MaxPlayers", 0), NetConfig.MaxPlayers), diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs index 26cc5098c..018a897a3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs @@ -182,9 +182,9 @@ namespace Barotrauma.Networking IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)ClientPacketHeader.SERVER_SETTINGS); + outMsg.WriteByte((byte)ClientPacketHeader.SERVER_SETTINGS); - outMsg.Write((byte)dataToSend); + outMsg.WriteByte((byte)dataToSend); if (dataToSend.HasFlag(NetFlags.Name)) { @@ -192,7 +192,7 @@ namespace Barotrauma.Networking { ServerName = GameMain.NetLobbyScreen.ServerName.Text; } - outMsg.Write(ServerName); + outMsg.WriteString(ServerName); } if (dataToSend.HasFlag(NetFlags.Message)) @@ -201,7 +201,7 @@ namespace Barotrauma.Networking { ServerMessageText = GameMain.NetLobbyScreen.ServerMessage.Text; } - outMsg.Write(ServerMessageText); + outMsg.WriteString(ServerMessageText); } if (dataToSend.HasFlag(NetFlags.Properties)) @@ -213,15 +213,15 @@ namespace Barotrauma.Networking UInt32 count = (UInt32)changedProperties.Count(); bool changedMonsterSettings = tempMonsterEnabled != null && tempMonsterEnabled.Any(p => p.Value != MonsterEnabled[p.Key]); - outMsg.Write(count); + outMsg.WriteUInt32(count); foreach (KeyValuePair prop in changedProperties) { DebugConsole.NewMessage(prop.Value.Name.Value, Color.Lime); - outMsg.Write(prop.Key); + outMsg.WriteUInt32(prop.Key); prop.Value.Write(outMsg, prop.Value.GUIComponentValue); } - outMsg.Write(changedMonsterSettings); outMsg.WritePadBits(); + outMsg.WriteBoolean(changedMonsterSettings); outMsg.WritePadBits(); if (changedMonsterSettings) WriteMonsterEnabled(outMsg, tempMonsterEnabled); BanList.ClientAdminWrite(outMsg); } @@ -235,23 +235,23 @@ namespace Barotrauma.Networking { outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All); outMsg.WriteRangedInteger(missionTypeAnd ?? (int)Barotrauma.MissionType.All, 0, (int)Barotrauma.MissionType.All); - outMsg.Write((byte)(traitorSetting + 1)); - outMsg.Write((byte)(botCount + 1)); - outMsg.Write((byte)(botSpawnMode + 1)); + outMsg.WriteByte((byte)(traitorSetting + 1)); + outMsg.WriteByte((byte)(botCount + 1)); + outMsg.WriteByte((byte)(botSpawnMode + 1)); - outMsg.Write(levelDifficulty ?? -1000.0f); + outMsg.WriteSingle(levelDifficulty ?? -1000.0f); - outMsg.Write(useRespawnShuttle ?? UseRespawnShuttle); + outMsg.WriteBoolean(useRespawnShuttle ?? UseRespawnShuttle); - outMsg.Write(autoRestart != null); - outMsg.Write(autoRestart ?? false); + outMsg.WriteBoolean(autoRestart != null); + outMsg.WriteBoolean(autoRestart ?? false); outMsg.WritePadBits(); } if (dataToSend.HasFlag(NetFlags.LevelSeed)) { - outMsg.Write(GameMain.NetLobbyScreen.SeedBox.Text); + outMsg.WriteString(GameMain.NetLobbyScreen.SeedBox.Text); } GameMain.Client.ClientPeer.Send(outMsg, DeliveryMethod.Reliable); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs index 617e7d40f..bfbba0c7f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs @@ -4,7 +4,6 @@ using Microsoft.Xna.Framework; using OpenAL; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Threading; @@ -22,13 +21,13 @@ namespace Barotrauma.Networking public static IReadOnlyList CaptureDeviceNames => Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier); - private IntPtr captureDevice; + private readonly IntPtr captureDevice; private Thread captureThread; private bool capturing; - private OpusEncoder encoder; + private readonly OpusEncoder encoder; public double LastdB { @@ -171,8 +170,8 @@ namespace Barotrauma.Networking } IntPtr nativeBuffer; - short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE]; - short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE]; + readonly short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE]; + readonly short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE]; bool prevCaptured = true; int captureTimer; @@ -227,16 +226,20 @@ namespace Barotrauma.Networking bool allowEnqueue = overrideSound != null; if (GameMain.WindowActive && SettingsMenu.Instance is null) { - bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice); bool usingLocalMode = PlayerInput.KeyDown(InputType.LocalVoice); bool usingRadioMode = PlayerInput.KeyDown(InputType.RadioVoice); - bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null; - if (pttDown || captureTimer <= 0) - { - ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode; - } if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.Activity) { + bool pttDown = (usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null; + if (pttDown) + { + ForceLocal = usingLocalMode; + } + //in Activity mode, we default to the active mode UNLESS a specific ptt key is held + else + { + ForceLocal = GameMain.ActiveChatMode == ChatMode.Local; + } if (dB > GameSettings.CurrentConfig.Audio.NoiseGateThreshold) { allowEnqueue = true; @@ -244,6 +247,13 @@ namespace Barotrauma.Networking } else if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.PushToTalk) { + //in push-to-talk mode, InputType.Voice uses the active chat mode + bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice); + bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null; + if (pttDown || captureTimer <= 0) + { + ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode; + } if (pttDown) { allowEnqueue = true; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs index eabc6004a..934820e0d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs @@ -72,8 +72,8 @@ namespace Barotrauma.Networking { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.VOICE); - msg.Write((byte)VoipCapture.Instance.QueueID); + msg.WriteByte((byte)ClientPacketHeader.VOICE); + msg.WriteByte((byte)VoipCapture.Instance.QueueID); VoipCapture.Instance.Write(msg); netClient.Send(msg, DeliveryMethod.Unreliable); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs index 38c935426..b2f044280 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs @@ -113,35 +113,35 @@ namespace Barotrauma public void ClientWrite(IWriteMessage msg, VoteType voteType, object data) { - msg.Write((byte)voteType); + msg.WriteByte((byte)voteType); switch (voteType) { case VoteType.Sub: if (!(data is SubmarineInfo sub)) { return; } - msg.Write(sub.EqualityCheckVal); + msg.WriteInt32(sub.EqualityCheckVal); if (sub.EqualityCheckVal == 0) { //sub doesn't exist client-side, use hash to let the server know which one we voted for - msg.Write(sub.MD5Hash.StringRepresentation); + msg.WriteString(sub.MD5Hash.StringRepresentation); } break; case VoteType.Mode: if (!(data is GameModePreset gameMode)) { return; } - msg.Write(gameMode.Identifier); + msg.WriteIdentifier(gameMode.Identifier); break; case VoteType.EndRound: if (!(data is bool)) { return; } - msg.Write((bool)data); + msg.WriteBoolean((bool)data); break; case VoteType.Kick: if (!(data is Client votedClient)) { return; } - msg.Write(votedClient.SessionId); + msg.WriteByte(votedClient.SessionId); break; case VoteType.StartRound: if (!(data is bool)) { return; } - msg.Write((bool)data); + msg.WriteBoolean((bool)data); break; case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseSub: @@ -149,22 +149,22 @@ namespace Barotrauma if (data is (SubmarineInfo voteSub, bool transferItems)) { //initiate sub vote - msg.Write(true); - msg.Write(voteSub.Name); - msg.Write(transferItems); + msg.WriteBoolean(true); + msg.WriteString(voteSub.Name); + msg.WriteBoolean(transferItems); } else { // vote if (!(data is int)) { return; } - msg.Write(false); - msg.Write((int)data); + msg.WriteBoolean(false); + msg.WriteInt32((int)data); } break; case VoteType.TransferMoney: if (!(data is int)) { return; } - msg.Write(false); //not initiating a vote - msg.Write((int)data); + msg.WriteBoolean(false); //not initiating a vote + msg.WriteInt32((int)data); break; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/WhiteList.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/WhiteList.cs deleted file mode 100644 index 554d32169..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/WhiteList.cs +++ /dev/null @@ -1,245 +0,0 @@ -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma.Networking -{ - partial class WhiteListedPlayer - { - public WhiteListedPlayer(string name, UInt16 identifier, string ip) - { - Name = name; - IP = ip; - - UniqueIdentifier = identifier; - } - } - - partial class WhiteList - { - private GUIComponent whitelistFrame; - - private GUITextBox nameBox; - private GUITextBox ipBox; - private GUIButton addNewButton; - - public class LocalAdded - { - public string Name; - public string IP; - }; - - public bool localEnabled; - public List localRemoved = new List(); - public List localAdded = new List(); - - public GUIComponent CreateWhiteListFrame(GUIComponent parent) - { - if (whitelistFrame != null) - { - whitelistFrame.Parent.ClearChildren(); - whitelistFrame = null; - } - - whitelistFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), parent.RectTransform, Anchor.Center)) - { - Stretch = true, - RelativeSpacing = 0.02f - }; - - var enabledTick = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.1f), whitelistFrame.RectTransform), TextManager.Get("WhiteListEnabled")) - { - Selected = localEnabled, - UpdateOrder = 1, - OnSelected = (GUITickBox box) => - { - nameBox.Enabled = box.Selected; - ipBox.Enabled = box.Selected; - addNewButton.Enabled = box.Selected && !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text); - localEnabled = box.Selected; - return true; - } - }; - - var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), whitelistFrame.RectTransform)); - foreach (WhiteListedPlayer wlp in whitelistedPlayers) - { - if (localRemoved.Contains(wlp.UniqueIdentifier)) continue; - string blockText = wlp.Name; - if (!string.IsNullOrWhiteSpace(wlp.IP)) blockText += " (" + wlp.IP + ")"; - GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform), - blockText) - { - UserData = wlp - }; - - var removeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), textBlock.RectTransform, Anchor.CenterRight), - TextManager.Get("WhiteListRemove"), style: "GUIButtonSmall") - { - UserData = wlp, - OnClicked = RemoveFromWhiteList - }; - } - - foreach (LocalAdded lad in localAdded) - { - string blockText = lad.Name; - if (!string.IsNullOrWhiteSpace(lad.IP)) blockText += " (" + lad.IP + ")"; - GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform), - blockText) - { - UserData = lad - }; - - var removeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), textBlock.RectTransform, Anchor.CenterRight), - TextManager.Get("WhiteListRemove"), style: "GUIButtonSmall") - { - UserData = lad, - OnClicked = RemoveFromWhiteList - }; - } - - foreach (GUIComponent c in listBox.Content.Children) - { - c.RectTransform.MinSize = new Point(0, Math.Max((int)(20 * GUI.Scale), c.RectTransform.Children.Max(c2 => c2.MinSize.Y))); - } - - var nameArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), whitelistFrame.RectTransform), isHorizontal: true) - { - Stretch = true, - RelativeSpacing = 0.05f - }; - new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), nameArea.RectTransform), TextManager.Get("WhiteListName")); - nameBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), nameArea.RectTransform), ""); - nameBox.OnTextChanged += (textBox, text) => - { - addNewButton.Enabled = !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text); - return true; - }; - nameArea.RectTransform.MinSize = new Point(0, nameBox.RectTransform.MinSize.Y); - - var ipArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), whitelistFrame.RectTransform), isHorizontal: true) - { - Stretch = true, - RelativeSpacing = 0.05f - }; - new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), ipArea.RectTransform), TextManager.Get("WhiteListIP")); - ipBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), ipArea.RectTransform), ""); - ipBox.OnTextChanged += (textBox, text) => - { - addNewButton.Enabled = !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text); - return true; - }; - ipBox.RectTransform.MinSize = new Point(0, ipBox.RectTransform.MinSize.Y); - - addNewButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), whitelistFrame.RectTransform), TextManager.Get("WhiteListAdd"), style: "GUIButtonSmall") - { - OnClicked = AddToWhiteList - }; - GUITextBlock.AutoScaleAndNormalize(addNewButton.TextBlock); - - nameBox.Enabled = localEnabled; - ipBox.Enabled = localEnabled; - addNewButton.Enabled = false; - - return parent; - } - - private bool RemoveFromWhiteList(GUIButton button, object obj) - { - if (obj is WhiteListedPlayer) - { - if (!(obj is WhiteListedPlayer wlp)) return false; - if (!localRemoved.Contains(wlp.UniqueIdentifier)) localRemoved.Add(wlp.UniqueIdentifier); - } - else if (obj is LocalAdded) - { - if (!(obj is LocalAdded lad)) return false; - if (localAdded.Contains(lad)) localAdded.Remove(lad); - } - - if (whitelistFrame != null) - { - CreateWhiteListFrame(whitelistFrame.Parent); - } - - return true; - } - - private bool AddToWhiteList(GUIButton button, object obj) - { - if (string.IsNullOrWhiteSpace(nameBox.Text)) return false; - if (whitelistedPlayers.Any(x => x.Name.ToLower() == nameBox.Text.ToLower() && x.IP == ipBox.Text)) return false; - - if (!localAdded.Any(p => p.IP == ipBox.Text)) localAdded.Add(new LocalAdded() { Name = nameBox.Text, IP = ipBox.Text }); - - if (whitelistFrame != null) - { - CreateWhiteListFrame(whitelistFrame.Parent); - } - return true; - } - - public void ClientAdminRead(IReadMessage incMsg) - { - bool hasPermission = incMsg.ReadBoolean(); - if (!hasPermission) - { - incMsg.ReadPadBits(); - return; - } - - bool isOwner = incMsg.ReadBoolean(); - localEnabled = incMsg.ReadBoolean(); - Enabled = localEnabled; - incMsg.ReadPadBits(); - - whitelistedPlayers.Clear(); - UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32(); - for (int i = 0; i < (int)bannedPlayerCount; i++) - { - string name = incMsg.ReadString(); - UInt16 uniqueIdentifier = incMsg.ReadUInt16(); - - string ip = ""; - if (isOwner) - { - ip = incMsg.ReadString(); - } - else - { - ip = "IP concealed by host"; - } - whitelistedPlayers.Add(new WhiteListedPlayer(name, uniqueIdentifier, ip)); - } - - if (whitelistFrame != null) - { - CreateWhiteListFrame(whitelistFrame.Parent); - } - } - - public void ClientAdminWrite(IWriteMessage outMsg) - { - outMsg.Write(localEnabled); - outMsg.WritePadBits(); - - outMsg.Write((UInt16)localRemoved.Count); - foreach (UInt16 uniqueId in localRemoved) - { - outMsg.Write(uniqueId); - } - - outMsg.Write((UInt16)localAdded.Count); - foreach (LocalAdded lad in localAdded) - { - outMsg.Write(lad.Name); - outMsg.Write(lad.IP); //TODO: ENCRYPT - } - - localRemoved.Clear(); - localAdded.Clear(); - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs index 0b33e0a5a..46dfef1ef 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs @@ -61,7 +61,8 @@ namespace Barotrauma UserData = saveInfo.FilePath }; - var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath)) + var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath), + textColor: GUIStyle.TextColorBright) { CanBeFocused = false }; @@ -85,7 +86,6 @@ namespace Barotrauma UserData = saveInfo.FilePath }; - string saveTimeStr = string.Empty; if (saveInfo.SaveTime > 0) { @@ -187,9 +187,9 @@ namespace Barotrauma SettingValue startingSetInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("startitemset"), TextManager.Get("startitemsettooltip"), prevStartingSet, verticalSize, startingSetOptions); ImmutableArray> fundOptions = ImmutableArray.Create( - new SettingCarouselElement(StartingBalanceAmount.High, "startingfunds.high"), + new SettingCarouselElement(StartingBalanceAmount.Low, "startingfunds.low"), new SettingCarouselElement(StartingBalanceAmount.Medium, "startingfunds.medium"), - new SettingCarouselElement(StartingBalanceAmount.Low, "startingfunds.low") + new SettingCarouselElement(StartingBalanceAmount.High, "startingfunds.high") ); SettingCarouselElement prevStartingFund = fundOptions.FirstOrNull(element => element.Value == prevSettings.StartingBalanceAmount) ?? fundOptions[1]; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs index 0c297bbf1..70a8bbd8c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs @@ -11,7 +11,9 @@ namespace Barotrauma class MultiPlayerCampaignSetupUI : CampaignSetupUI { private GUIButton deleteMpSaveButton; - + + private int prevInitialMoney; + public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, List saveFiles = null) : base(newGameContainer, loadGameContainer) { @@ -133,6 +135,7 @@ namespace Barotrauma StartButton.RectTransform.MaxSize = RectTransform.MaxPoint; StartButton.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint); + prevInitialMoney = 8000; InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUIStyle.SmallFont, textColor: GUIStyle.Green) { TextGetter = () => @@ -142,11 +145,17 @@ namespace Barotrauma { initialMoney = definition.GetInt(elements.StartingFunds.GetValue().ToIdentifier()); } + if (prevInitialMoney != initialMoney) + { + GameMain.NetLobbyScreen.RefreshEnabledElements(); + prevInitialMoney = initialMoney; + } if (GameMain.NetLobbyScreen.SelectedSub != null) { initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price; } - initialMoney = Math.Max(initialMoney, MultiPlayerCampaign.MinimumInitialMoney); + initialMoney = Math.Max(initialMoney, 0); + return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney)); } }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs index 7f98cdc43..5688ea044 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs @@ -476,8 +476,7 @@ namespace Barotrauma { foreach (GUIComponent child in subList.Content.Children) { - SubmarineInfo sub = child.UserData as SubmarineInfo; - if (sub == null) { return; } + if (!(child.UserData is SubmarineInfo sub)) { return; } child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.Contains(filter.ToLower(), StringComparison.OrdinalIgnoreCase); } } @@ -523,9 +522,11 @@ namespace Barotrauma subsToShow.Sort((s1, s2) => { - int p1 = s1.Price > CurrentSettings.InitialMoney ? 10 : 0; - int p2 = s2.Price > CurrentSettings.InitialMoney ? 10 : 0; - return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name); + int p1 = s1.Price; + if (!s1.IsCampaignCompatible) { p1 += 100000; } + int p2 = s2.Price; + if (!s2.IsCampaignCompatible) { p2 += 100000; } + return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name); }); subList.ClearChildren(); @@ -533,7 +534,7 @@ namespace Barotrauma foreach (SubmarineInfo sub in subsToShow) { var textBlock = new GUITextBlock( - new RectTransform(new Vector2(1, 0.1f), subList.Content.RectTransform) { MinSize = new Point(0, 30) }, + new RectTransform(new Vector2(1, 0.15f), subList.Content.RectTransform) { MinSize = new Point(0, 30) }, ToolBox.LimitString(sub.DisplayName.Value, GUIStyle.Font, subList.Rect.Width - 65), style: "ListBoxElement") { ToolTip = sub.Description, @@ -546,12 +547,19 @@ namespace Barotrauma textBlock.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + textBlock.ToolTip.SanitizedString; } - var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), - TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont) + var infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), isHorizontal: false); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform), + TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.BottomRight, font: GUIStyle.SmallFont) { TextColor = sub.Price > CurrentSettings.InitialMoney ? GUIStyle.Red : textBlock.TextColor * 0.8f, ToolTip = textBlock.ToolTip }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform), + TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.TopRight, font: GUIStyle.SmallFont) + { + TextColor = textBlock.TextColor * 0.8f, + ToolTip = textBlock.ToolTip + }; #if !DEBUG if (!GameMain.DebugDraw) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs index 22b86c510..620f10795 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs @@ -179,7 +179,7 @@ namespace Barotrauma // Ok button msgBox.Buttons[1].OnClicked = delegate { - foreach (var illegalChar in Path.GetInvalidFileNameChars()) + foreach (var illegalChar in Path.GetInvalidFileNameCharsCrossPlatform()) { if (!nameInput.Text.Contains(illegalChar)) { continue; } @@ -274,7 +274,7 @@ namespace Barotrauma // Ok button msgBox.Buttons[1].OnClicked = delegate { - foreach (var illegalChar in Path.GetInvalidFileNameChars()) + foreach (var illegalChar in Path.GetInvalidFileNameCharsCrossPlatform()) { if (!nameInput.Text.Contains(illegalChar)) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index fa017d9c0..0f1ec526f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -434,25 +434,18 @@ namespace Barotrauma { PlaySoundOnSelect = true, }; - var tutorialTypes = new List() + foreach (var tutorialPrefab in TutorialPrefab.Prefabs.OrderBy(p => p.Order)) { - typeof(MechanicTutorial), - typeof(EngineerTutorial), - typeof(DoctorTutorial), - typeof(OfficerTutorial), - typeof(CaptainTutorial), - }; - foreach (Type tutorialType in tutorialTypes) - { - Tutorial tutorial = (Tutorial)Activator.CreateInstance(tutorialType); + var tutorial = new Tutorial(tutorialPrefab); var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), tutorialList.Content.RectTransform), tutorial.DisplayName, textAlignment: Alignment.Center, font: GUIStyle.LargeFont) { + TextColor = GUIStyle.Green, UserData = tutorial }; } tutorialList.OnSelected += (component, obj) => { - (obj as Tutorial).Start(); + (obj as Tutorial)?.Start(); return true; }; @@ -737,34 +730,12 @@ namespace Barotrauma private void UpdateTutorialList() { - var tutorialList = menuTabs[Tab.Tutorials].GetChild(); - - int completedTutorials = 0; - - foreach (GUITextBlock tutorialText in tutorialList.Content.Children) + foreach (GUITextBlock tutorialText in menuTabs[Tab.Tutorials].GetChild().Content.Children) { - if (CompletedTutorials.Instance.Contains(((Tutorial)tutorialText.UserData).Identifier)) - { - completedTutorials++; - } - } - - for (int i = 0; i < tutorialList.Content.Children.Count(); i++) - { - if (i < completedTutorials + 1) - { - (tutorialList.Content.GetChild(i) as GUITextBlock).TextColor = GUIStyle.Green; -#if !DEBUG - (tutorialList.Content.GetChild(i) as GUITextBlock).CanBeFocused = true; -#endif - } - else - { - (tutorialList.Content.GetChild(i) as GUITextBlock).TextColor = Color.Gray; -#if !DEBUG - (tutorialList.Content.GetChild(i) as GUITextBlock).CanBeFocused = false; -#endif - } + var tutorial = (Tutorial)tutorialText.UserData; + tutorialText.Text = CompletedTutorials.Instance.Contains(tutorial.Identifier) ? + TextManager.GetWithVariable("tutorialcompleted", "[tutorialname]", tutorial.DisplayName) : + tutorial.DisplayName; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs index 599aa2c60..e638bf0e5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs @@ -9,7 +9,7 @@ using Barotrauma.Steam; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Color = Microsoft.Xna.Framework.Color; -using ServerContentPackage = Barotrauma.Networking.ClientPeer.ServerContentPackage; +using ServerContentPackage = Barotrauma.Networking.ServerContentPackage; namespace Barotrauma { @@ -21,7 +21,7 @@ namespace Barotrauma private readonly List downloadedPackages = new List(); public IEnumerable DownloadedPackages => downloadedPackages; - + private bool confirmDownload; public void Reset() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 7321d64d6..9c9e999d6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Barotrauma @@ -866,7 +867,7 @@ namespace Barotrauma { OnSelected = (component, obj) => { - GameMain.Client?.RequestSelectSub(component.Parent.GetChildIndex(component), isShuttle: true); + GameMain.Client?.RequestSelectSub(obj as SubmarineInfo, isShuttle: true); return true; } }; @@ -1794,7 +1795,7 @@ namespace Barotrauma MissionType = missionType; } - public void UpdateSubList(GUIComponent subList, List submarines) + public void UpdateSubList(GUIComponent subList, IEnumerable submarines) { if (subList == null) { return; } @@ -1817,7 +1818,7 @@ namespace Barotrauma subList = dropDown.ListBox.Content; } - var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), subList.RectTransform) { MinSize = new Point(0, 20) }, + var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), subList.RectTransform) { MinSize = new Point(0, 25) }, style: "ListBoxElement") { ToolTip = sub.Description, @@ -1873,7 +1874,7 @@ namespace Barotrauma { if (sub.HasTag(SubmarineTag.Shuttle)) { - var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) }, + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) }, TextManager.Get("Shuttle", "RespawnShuttle"), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont) { TextColor = subTextBlock.TextColor * 0.8f, @@ -1881,7 +1882,7 @@ namespace Barotrauma CanBeFocused = false }; //make shuttles more dim in the sub list (selecting a shuttle as the main sub is allowed but not recommended) - if (subList == this.SubList.Content) + if (subList == SubList.Content) { subTextBlock.TextColor *= 0.8f; foreach (GUIComponent child in parent.Children) @@ -1892,8 +1893,16 @@ namespace Barotrauma } else { - new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) }, - TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont) + var infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) }, isHorizontal: false); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform), + TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.BottomRight, font: GUIStyle.SmallFont) + { + UserData = "pricetext", + TextColor = subTextBlock.TextColor * 0.8f, + CanBeFocused = false + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform), + TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.TopRight, font: GUIStyle.SmallFont) { UserData = "classtext", TextColor = subTextBlock.TextColor * 0.8f, @@ -1913,6 +1922,17 @@ namespace Barotrauma if (!GameMain.Client.ServerSettings.AllowSubVoting) { var selectedSub = component.UserData as SubmarineInfo; + if (SelectedMode == GameModePreset.MultiPlayerCampaign && CampaignSetupUI != null) + { + if (selectedSub.Price > CampaignSetupUI.CurrentSettings.InitialMoney) + { + new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("campaignsubtooexpensive")); + } + if (!selectedSub.IsCampaignCompatible) + { + new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("campaignsubincompatible")); + } + } if (!selectedSub.RequiredContentPackagesInstalled) { var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"), @@ -1924,7 +1944,7 @@ namespace Barotrauma msgBox.Buttons[0].OnClicked = msgBox.Close; msgBox.Buttons[0].OnClicked += (button, obj) => { - GameMain.Client.RequestSelectSub(component.Parent.GetChildIndex(component), isShuttle: false); + GameMain.Client.RequestSelectSub(obj as SubmarineInfo, isShuttle: false); return true; }; msgBox.Buttons[1].OnClicked = msgBox.Close; @@ -1932,7 +1952,7 @@ namespace Barotrauma } else if (GameMain.Client.HasPermission(ClientPermissions.SelectSub)) { - GameMain.Client.RequestSelectSub(component.Parent.GetChildIndex(component), isShuttle: false); + GameMain.Client.RequestSelectSub(selectedSub, isShuttle: false); return true; } return false; @@ -2518,7 +2538,6 @@ namespace Barotrauma var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaLower.RectTransform), TextManager.Get("VoteToKick")) { - Enabled = !selectedClient.HasKickVoteFromSessionId(GameMain.Client.SessionId), OnClicked = (btn, userdata) => { GameMain.Client.VoteForKick(selectedClient); btn.Enabled = false; return true; }, UserData = selectedClient }; @@ -3223,6 +3242,22 @@ namespace Barotrauma { if (GameMain.Client == null) { return; } + foreach (var subElement in SubList.Content.Children) + { + subElement.CanBeFocused = true; + foreach (var textBlock in subElement.GetAllChildren()) + { + textBlock.Enabled = true; + } + } + + SubList.Content.RectTransform.SortChildren((rt1, rt2) => + { + SubmarineInfo s1 = rt1.GUIComponent.UserData as SubmarineInfo; + SubmarineInfo s2 = rt2.GUIComponent.UserData as SubmarineInfo; + return s1.Name.CompareTo(s2.Name); + }); + autoRestartBox.Parent.Visible = true; settingsBlocker.Visible = false; if (SelectedMode == GameModePreset.Mission || SelectedMode == GameModePreset.PvP) @@ -3255,6 +3290,33 @@ namespace Barotrauma TextManager.Get("campaignstarting"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center, wrap: true); } } + + if (CampaignSetupUI != null) + { + foreach (var subElement in SubList.Content.Children) + { + var sub = subElement.UserData as SubmarineInfo; + bool tooExpensive = sub.Price > CampaignSetupUI.CurrentSettings.InitialMoney; + if (tooExpensive || !sub.IsCampaignCompatible) + { + foreach (var textBlock in subElement.GetAllChildren()) + { + textBlock.DisabledTextColor = (textBlock.UserData as string == "pricetext" && tooExpensive ? GUIStyle.Red : GUIStyle.TextColorNormal) * 0.7f; + textBlock.Enabled = false; + } + } + } + SubList.Content.RectTransform.SortChildren((rt1, rt2) => + { + SubmarineInfo s1 = rt1.GUIComponent.UserData as SubmarineInfo; + SubmarineInfo s2 = rt2.GUIComponent.UserData as SubmarineInfo; + int p1 = s1.Price; + if (!s1.IsCampaignCompatible) { p1 += 100000; } + int p2 = s2.Price; + if (!s2.IsCampaignCompatible) { p2 += 100000; } + return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name); + }); + } } else { @@ -3656,7 +3718,7 @@ namespace Barotrauma } } - private List visibilityMenuOrder = new List(); + private readonly List visibilityMenuOrder = new List(); private void CreateSubmarineVisibilityMenu() { var messageBox = new GUIMessageBox(TextManager.Get("SubmarineVisibility"), "", diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 96e66ded3..3178cff7e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1839,7 +1839,7 @@ namespace Barotrauma return false; } - foreach (var illegalChar in Path.GetInvalidFileNameChars()) + foreach (var illegalChar in Path.GetInvalidFileNameCharsCrossPlatform()) { if (!name.Contains(illegalChar)) { continue; } GUI.AddMessage(TextManager.GetWithVariable("SubNameIllegalCharsWarning", "[illegalchar]", illegalChar.ToString()), GUIStyle.Red); @@ -2410,12 +2410,15 @@ namespace Barotrauma Stretch = true }; var classText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), classGroup.RectTransform), - TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true); + TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true) + { + ToolTip = TextManager.Get("submarineclass.description") + }; GUIDropDown classDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), classGroup.RectTransform)); classDropDown.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y)); - foreach (SubmarineClass @class in Enum.GetValues(typeof(SubmarineClass))) + foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass))) { - classDropDown.AddItem(TextManager.Get($"{nameof(SubmarineClass)}.{@class}"), @class); + classDropDown.AddItem(TextManager.Get($"{nameof(SubmarineClass)}.{subClass}"), subClass, toolTip: TextManager.Get($"submarineclass.{subClass}.description")); } classDropDown.AddItem(TextManager.Get(nameof(SubmarineTag.Shuttle)), SubmarineTag.Shuttle); classDropDown.OnSelected += (selected, userdata) => @@ -2435,6 +2438,31 @@ namespace Barotrauma }; classDropDown.SelectItem(!MainSub.Info.HasTag(SubmarineTag.Shuttle) ? MainSub.Info.SubmarineClass : (object)SubmarineTag.Shuttle); + var tierGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), tierGroup.RectTransform), + TextManager.Get("subeditor.tier"), textAlignment: Alignment.CenterLeft, wrap: true) + { + ToolTip = TextManager.Get("submarinetier.description") + }; + + new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), tierGroup.RectTransform), NumberType.Int) + { + IntValue = SubmarineInfo.GetDefaultTier(MainSub.Info.Price), + MinValueInt = 1, + MaxValueInt = 3, + OnValueChanged = (numberInput) => + { + MainSub.Info.Tier = numberInput.IntValue; + } + }; + if (MainSub?.Info != null) + { + MainSub.Info.Tier = Math.Clamp(MainSub.Info.Tier, 1, 3); + } + var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true) { Stretch = true, @@ -2970,7 +2998,7 @@ namespace Barotrauma return false; } - foreach (char illegalChar in Path.GetInvalidFileNameChars()) + foreach (char illegalChar in Path.GetInvalidFileNameCharsCrossPlatform()) { if (nameBox.Text.Contains(illegalChar)) { @@ -5014,6 +5042,10 @@ namespace Barotrauma SkipInventorySlotUpdate = false; ImageManager.Update((float)deltaTime); +#if DEBUG + Hull.UpdateCheats((float)deltaTime, cam); +#endif + if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y) { saveFrame = null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs index b11d5eaf1..8e2172550 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs @@ -229,7 +229,7 @@ namespace Barotrauma }; } - private string Percentage(float v) => TextManager.GetWithVariable("percentageformat", "[value]", Round(v * 100).ToString()).Value; + private string Percentage(float v) => ToolBox.GetFormattedPercentage(v); private int Round(float v) => (int)MathF.Round(v); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index 0705c3084..5870847e0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -282,7 +282,7 @@ namespace Barotrauma } else { - if (FlowSounds[i] == null) { continue; } + if (FlowSounds[i]?.Sound == null) { continue; } Vector2 soundPos = new Vector2(GameMain.SoundManager.ListenerPosition.X + (flowVolumeRight[i] - flowVolumeLeft[i]) * 100, GameMain.SoundManager.ListenerPosition.Y); if (flowSoundChannels[i] == null || !flowSoundChannels[i].IsPlaying) { diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 560289dd2..ce5e7b251 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -2,40 +2,30 @@ WinExe - netcoreapp3.1 + net6.0 Barotrauma FakeFish, Undertow Games Barotrauma - 0.19.1.0 + 0.19.3.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma ..\BarotraumaShared\Icon.ico Debug;Release;Unstable + true ;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 - - DEBUG;TRACE;CLIENT;LINUX;USE_STEAM x64 ..\bin\$(Configuration)Linux\ - net6.0 - 8 TRACE;DEBUG;CLIENT;LINUX;X64;USE_STEAM x64 ..\bin\$(Configuration)Linux\ - net6.0 - 8 diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 4e6d3e5a8..fd0d561b2 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -2,16 +2,17 @@ WinExe - netcoreapp3.1 + net6.0 Barotrauma FakeFish, Undertow Games Barotrauma - 0.19.1.0 + 0.19.3.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma ..\BarotraumaShared\Icon.ico Debug;Release;Unstable + true ;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 54cc0ce82..4e4bc2642 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -2,16 +2,17 @@ WinExe - netcoreapp3.1 + net6.0 Barotrauma FakeFish, Undertow Games Barotrauma - 0.19.1.0 + 0.19.3.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma ..\BarotraumaShared\Icon.ico Debug;Release;Unstable + true app.manifest ;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 83cba5533..f60b3ea38 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -2,40 +2,30 @@ Exe - netcoreapp3.1 + net6.0 Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.19.1.0 + 0.19.3.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer ..\BarotraumaShared\Icon.ico Debug;Release;Unstable + true ;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 - - DEBUG;TRACE;SERVER;LINUX;USE_STEAM x64 ..\bin\$(Configuration)Linux\ - net6.0 - 8 TRACE;DEBUG;SERVER;LINUX;X64;USE_STEAM x64 ..\bin\$(Configuration)Linux\ - net6.0 - 8 diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 36a90f48b..2f621c063 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -2,16 +2,17 @@ Exe - netcoreapp3.1 + net6.0 Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.19.1.0 + 0.19.3.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer ..\BarotraumaShared\Icon.ico Debug;Release;Unstable + true ;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 5dc354ccb..561218a63 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -46,39 +46,39 @@ namespace Barotrauma public void ServerWrite(IWriteMessage msg) { - msg.Write(ID); - msg.Write(Name); - msg.Write(OriginalName); - msg.Write((byte)Head.Preset.TagSet.Count); + msg.WriteUInt16(ID); + msg.WriteString(Name); + msg.WriteString(OriginalName); + msg.WriteByte((byte)Head.Preset.TagSet.Count); foreach (Identifier tag in Head.Preset.TagSet) { - msg.Write(tag); + msg.WriteIdentifier(tag); } - msg.Write((byte)Head.HairIndex); - msg.Write((byte)Head.BeardIndex); - msg.Write((byte)Head.MoustacheIndex); - msg.Write((byte)Head.FaceAttachmentIndex); + msg.WriteByte((byte)Head.HairIndex); + msg.WriteByte((byte)Head.BeardIndex); + msg.WriteByte((byte)Head.MoustacheIndex); + msg.WriteByte((byte)Head.FaceAttachmentIndex); msg.WriteColorR8G8B8(Head.SkinColor); msg.WriteColorR8G8B8(Head.HairColor); msg.WriteColorR8G8B8(Head.FacialHairColor); - msg.Write(ragdollFileName); + msg.WriteString(ragdollFileName); if (Job != null) { - msg.Write(Job.Prefab.UintIdentifier); - msg.Write((byte)Job.Variant); + msg.WriteUInt32(Job.Prefab.UintIdentifier); + msg.WriteByte((byte)Job.Variant); foreach (SkillPrefab skillPrefab in Job.Prefab.Skills.OrderBy(s => s.Identifier)) { - msg.Write(Job.GetSkill(skillPrefab.Identifier).Level); + msg.WriteSingle(Job.GetSkill(skillPrefab.Identifier).Level); } } else { - msg.Write((uint)0); - msg.Write((byte)0); + msg.WriteUInt32((uint)0); + msg.WriteByte((byte)0); } - msg.Write((ushort)ExperiencePoints); + msg.WriteUInt16((ushort)ExperiencePoints); msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 37dd065db..953e09573 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -8,7 +8,7 @@ namespace Barotrauma { partial class Character { - public Endpoint OwnerClientEndpoint; + public Address OwnerClientAddress; public string OwnerClientName; public bool ClientDisconnected; public float KillDisconnectedTimer; @@ -301,25 +301,25 @@ namespace Barotrauma public void ServerWritePosition(IWriteMessage msg, Client c) { - msg.Write(ID); + msg.WriteUInt16(ID); IWriteMessage tempBuffer = new WriteOnlyMessage(); if (this == c.Character) { - tempBuffer.Write(true); + tempBuffer.WriteBoolean(true); if (LastNetworkUpdateID < memInput.Count + 1) { - tempBuffer.Write((UInt16)0); + tempBuffer.WriteUInt16((UInt16)0); } else { - tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1)); + tempBuffer.WriteUInt16((UInt16)(LastNetworkUpdateID - memInput.Count - 1)); } } else { - tempBuffer.Write(false); + tempBuffer.WriteBoolean(false); bool aiming = false; bool use = false; @@ -342,41 +342,41 @@ namespace Barotrauma networkUpdateSent = true; } - tempBuffer.Write(aiming); - tempBuffer.Write(shoot); - tempBuffer.Write(use); + tempBuffer.WriteBoolean(aiming); + tempBuffer.WriteBoolean(shoot); + tempBuffer.WriteBoolean(use); if (AnimController is HumanoidAnimController) { - tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching); + tempBuffer.WriteBoolean(((HumanoidAnimController)AnimController).Crouching); } - tempBuffer.Write(attack); + tempBuffer.WriteBoolean(attack); Vector2 relativeCursorPos = cursorPosition - AimRefPosition; - tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI))); + tempBuffer.WriteUInt16((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI))); - tempBuffer.Write(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated); + tempBuffer.WriteBoolean(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated); - tempBuffer.Write(AnimController.Dir > 0.0f); + tempBuffer.WriteBoolean(AnimController.Dir > 0.0f); } if (SelectedCharacter != null || HasSelectedAnyItem) { - tempBuffer.Write(true); - tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID); - tempBuffer.Write(SelectedItem != null ? SelectedItem.ID : NullEntityID); - tempBuffer.Write(SelectedSecondaryItem != null ? SelectedSecondaryItem.ID : NullEntityID); + tempBuffer.WriteBoolean(true); + tempBuffer.WriteUInt16(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID); + tempBuffer.WriteUInt16(SelectedItem != null ? SelectedItem.ID : NullEntityID); + tempBuffer.WriteUInt16(SelectedSecondaryItem != null ? SelectedSecondaryItem.ID : NullEntityID); if (SelectedCharacter != null) { - tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR); + tempBuffer.WriteBoolean(AnimController.Anim == AnimController.Animation.CPR); } } else { - tempBuffer.Write(false); + tempBuffer.WriteBoolean(false); } - tempBuffer.Write(SimPosition.X); - tempBuffer.Write(SimPosition.Y); + tempBuffer.WriteSingle(SimPosition.X); + tempBuffer.WriteSingle(SimPosition.Y); float MaxVel = NetConfig.MaxPhysicsBodyVelocity; AnimController.Collider.LinearVelocity = new Vector2( MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel), @@ -385,17 +385,17 @@ namespace Barotrauma tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12); bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled; - tempBuffer.Write(fixedRotation); + tempBuffer.WriteBoolean(fixedRotation); if (!fixedRotation) { - tempBuffer.Write(AnimController.Collider.Rotation); + tempBuffer.WriteSingle(AnimController.Collider.Rotation); float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; AnimController.Collider.AngularVelocity = NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8); tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8); } bool writeStatus = healthUpdateTimer <= 0.0f; - tempBuffer.Write(writeStatus); + tempBuffer.WriteBoolean(writeStatus); if (writeStatus) { WriteStatus(tempBuffer); @@ -406,7 +406,7 @@ namespace Barotrauma tempBuffer.WritePadBits(); msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes); - msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); + msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); } public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) @@ -417,13 +417,13 @@ namespace Barotrauma switch (eventData) { case InventoryStateEventData _: - msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); + msg.WriteUInt16(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); Inventory.ServerEventWrite(msg, c); break; case ControlEventData controlEventData: Client owner = controlEventData.Owner; - msg.Write(owner == c && owner.Character == this); - msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.SessionId : (byte)0); + msg.WriteBoolean(owner == c && owner.Character == this); + msg.WriteByte(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.SessionId : (byte)0); break; case CharacterStatusEventData statusEventData: WriteStatus(msg, statusEventData.ForceAfflictionData); @@ -431,16 +431,16 @@ namespace Barotrauma case UpdateSkillsEventData _: if (Info?.Job == null) { - msg.Write((byte)0); + msg.WriteByte((byte)0); } else { var skills = Info.Job.GetSkills(); - msg.Write((byte)skills.Count()); + msg.WriteByte((byte)skills.Count()); foreach (Skill skill in skills) { - msg.Write(skill.Identifier); - msg.Write(skill.Level); + msg.WriteIdentifier(skill.Identifier); + msg.WriteSingle(skill.Level); } } break; @@ -457,33 +457,33 @@ namespace Barotrauma targetLimbIndex = targetLimbsArray.IndexOf(attackEventData.TargetLimb); } } - msg.Write((byte)(attackLimbIndex < 0 ? 255 : attackLimbIndex)); - msg.Write((ushort)targetEntityId); - msg.Write((byte)(targetLimbIndex < 0 ? 255 : targetLimbIndex)); - msg.Write(attackEventData.TargetSimPos.X); - msg.Write(attackEventData.TargetSimPos.Y); + msg.WriteByte((byte)(attackLimbIndex < 0 ? 255 : attackLimbIndex)); + msg.WriteUInt16((ushort)targetEntityId); + msg.WriteByte((byte)(targetLimbIndex < 0 ? 255 : targetLimbIndex)); + msg.WriteSingle(attackEventData.TargetSimPos.X); + msg.WriteSingle(attackEventData.TargetSimPos.Y); } break; case AssignCampaignInteractionEventData _: - msg.Write((byte)CampaignInteractionType); - msg.Write(RequireConsciousnessForCustomInteract); + msg.WriteByte((byte)CampaignInteractionType); + msg.WriteBoolean(RequireConsciousnessForCustomInteract); break; case ObjectiveManagerStateEventData objectiveManagerStateEventData: AIObjectiveManager.ObjectiveType type = objectiveManagerStateEventData.ObjectiveType; msg.WriteRangedInteger((int)type, (int)AIObjectiveManager.ObjectiveType.MinValue, (int)AIObjectiveManager.ObjectiveType.MaxValue); if (!(AIController is HumanAIController controller)) { - msg.Write(false); + msg.WriteBoolean(false); break; } if (type == AIObjectiveManager.ObjectiveType.Order) { var currentOrderInfo = controller.ObjectiveManager.GetCurrentOrderInfo(); bool validOrder = currentOrderInfo != null; - msg.Write(validOrder); + msg.WriteBoolean(validOrder); if (!validOrder) { break; } var orderPrefab = currentOrderInfo.Prefab; - msg.Write(orderPrefab.UintIdentifier); + msg.WriteUInt32(orderPrefab.UintIdentifier); if (!orderPrefab.HasOptions) { break; } int optionIndex = orderPrefab.AllOptions.IndexOf(currentOrderInfo.Option); if (optionIndex == -1) @@ -496,65 +496,65 @@ namespace Barotrauma { var objective = controller.ObjectiveManager.CurrentObjective; bool validObjective = objective?.Identifier is { IsEmpty: false }; - msg.Write(validObjective); + msg.WriteBoolean(validObjective); if (!validObjective) { break; } - msg.Write(objective.Identifier); - msg.Write(objective.Option); + msg.WriteIdentifier(objective.Identifier); + msg.WriteIdentifier(objective.Option); UInt16 targetEntityId = 0; if (objective is AIObjectiveOperateItem operateObjective && operateObjective.OperateTarget != null) { targetEntityId = operateObjective.OperateTarget.ID; } - msg.Write(targetEntityId); + msg.WriteUInt16(targetEntityId); } break; case TeamChangeEventData _: - msg.Write((byte)TeamID); + msg.WriteByte((byte)TeamID); break; case AddToCrewEventData addToCrewEventData: - msg.Write((byte)addToCrewEventData.TeamType); // team id + msg.WriteByte((byte)addToCrewEventData.TeamType); // team id ushort[] inventoryItemIDs = addToCrewEventData.InventoryItems.Select(item => item.ID).ToArray(); - msg.Write((ushort)inventoryItemIDs.Length); + msg.WriteUInt16((ushort)inventoryItemIDs.Length); for (int i = 0; i < inventoryItemIDs.Length; i++) { - msg.Write(inventoryItemIDs[i]); + msg.WriteUInt16(inventoryItemIDs[i]); } break; case UpdateExperienceEventData _: - msg.Write(Info.ExperiencePoints); + msg.WriteInt32(Info.ExperiencePoints); break; case UpdateTalentsEventData _: - msg.Write((ushort)characterTalents.Count); + msg.WriteUInt16((ushort)characterTalents.Count); foreach (var unlockedTalent in characterTalents) { - msg.Write(unlockedTalent.AddedThisRound); - msg.Write(unlockedTalent.Prefab.UintIdentifier); + msg.WriteBoolean(unlockedTalent.AddedThisRound); + msg.WriteUInt32(unlockedTalent.Prefab.UintIdentifier); } break; case UpdateMoneyEventData _: - msg.Write(GameMain.GameSession.Campaign.GetWallet(c).Balance); + msg.WriteInt32(GameMain.GameSession.Campaign.GetWallet(c).Balance); break; case UpdatePermanentStatsEventData updatePermanentStatsEventData: StatTypes statType = updatePermanentStatsEventData.StatType; if (Info == null) { - msg.Write((byte)0); - msg.Write((byte)0); + msg.WriteByte((byte)0); + msg.WriteByte((byte)0); } else if (!Info.SavedStatValues.ContainsKey(statType)) { - msg.Write((byte)0); - msg.Write((byte)statType); + msg.WriteByte((byte)0); + msg.WriteByte((byte)statType); } else { - msg.Write((byte)Info.SavedStatValues[statType].Count); - msg.Write((byte)statType); + msg.WriteByte((byte)Info.SavedStatValues[statType].Count); + msg.WriteByte((byte)statType); foreach (var savedStatValue in Info.SavedStatValues[statType]) { - msg.Write(savedStatValue.StatIdentifier); - msg.Write(savedStatValue.StatValue); - msg.Write(savedStatValue.RemoveOnDeath); + msg.WriteString(savedStatValue.StatIdentifier); + msg.WriteSingle(savedStatValue.StatValue); + msg.WriteBoolean(savedStatValue.RemoveOnDeath); } } break; @@ -568,15 +568,15 @@ namespace Barotrauma /// Normally full affliction data is not written for dead characters, this can be used to force them to be written private void WriteStatus(IWriteMessage msg, bool forceAfflictionData = false) { - msg.Write(IsDead); + msg.WriteBoolean(IsDead); if (IsDead) { msg.WriteRangedInteger((int)CauseOfDeath.Type, 0, Enum.GetValues(typeof(CauseOfDeathType)).Length - 1); if (CauseOfDeath.Type == CauseOfDeathType.Affliction) { - msg.Write(CauseOfDeath.Affliction.UintIdentifier); + msg.WriteUInt32(CauseOfDeath.Affliction.UintIdentifier); } - msg.Write(forceAfflictionData); + msg.WriteBoolean(forceAfflictionData); if (forceAfflictionData) { CharacterHealth.ServerWrite(msg); @@ -589,7 +589,7 @@ namespace Barotrauma if (AnimController?.LimbJoints == null) { //0 limbs severed - msg.Write((byte)0); + msg.WriteByte((byte)0); } else { @@ -601,10 +601,10 @@ namespace Barotrauma severedJointIndices.Add(i); } } - msg.Write((byte)severedJointIndices.Count); + msg.WriteByte((byte)severedJointIndices.Count); foreach (int jointIndex in severedJointIndices) { - msg.Write((byte)jointIndex); + msg.WriteByte((byte)jointIndex); } } } @@ -615,24 +615,24 @@ namespace Barotrauma int initialMsgLength = msg.LengthBytes; - msg.Write(Info == null); - msg.Write(entityId); - msg.Write(SpeciesName); - msg.Write(Seed); + msg.WriteBoolean(Info == null); + msg.WriteUInt16(entityId); + msg.WriteIdentifier(SpeciesName); + msg.WriteString(Seed); if (Removed) { - msg.Write(0.0f); - msg.Write(0.0f); + msg.WriteSingle(0.0f); + msg.WriteSingle(0.0f); } else { - msg.Write(WorldPosition.X); - msg.Write(WorldPosition.Y); + msg.WriteSingle(WorldPosition.X); + msg.WriteSingle(WorldPosition.Y); } - msg.Write(Enabled); - msg.Write(DisabledByEvent); + msg.WriteBoolean(Enabled); + msg.WriteBoolean(DisabledByEvent); //character with no characterinfo (e.g. some monster) if (Info == null) @@ -644,54 +644,54 @@ namespace Barotrauma Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this); if (ownerClient != null) { - msg.Write(true); - msg.Write(ownerClient.SessionId); + msg.WriteBoolean(true); + msg.WriteByte(ownerClient.SessionId); } else if (GameMain.Server.Character == this) { - msg.Write(true); - msg.Write((byte)0); + msg.WriteBoolean(true); + msg.WriteByte((byte)0); } else { - msg.Write(false); + msg.WriteBoolean(false); } - msg.Write(HumanPrefabHealthMultiplier); - msg.Write(Wallet.Balance); + msg.WriteSingle(HumanPrefabHealthMultiplier); + msg.WriteInt32(Wallet.Balance); msg.WriteRangedInteger(Wallet.RewardDistribution, 0, 100); - msg.Write((byte)TeamID); - msg.Write(this is AICharacter); - msg.Write(info.SpeciesName); + msg.WriteByte((byte)TeamID); + msg.WriteBoolean(this is AICharacter); + msg.WriteIdentifier(info.SpeciesName); int msgLengthBeforeInfo = msg.LengthBytes; info.ServerWrite(msg); int infoLength = msg.LengthBytes - msgLengthBeforeInfo; - msg.Write((byte)CampaignInteractionType); + msg.WriteByte((byte)CampaignInteractionType); if (CampaignInteractionType == CampaignMode.InteractionType.Store) { - msg.Write(MerchantIdentifier); + msg.WriteIdentifier(MerchantIdentifier); } int msgLengthBeforeOrders = msg.LengthBytes; // Current orders - msg.Write((byte)info.CurrentOrders.Count(o => o != null)); + msg.WriteByte((byte)info.CurrentOrders.Count(o => o != null)); foreach (var orderInfo in info.CurrentOrders) { if (orderInfo == null) { continue; } - msg.Write(orderInfo.Prefab.UintIdentifier); - msg.Write(orderInfo.TargetEntity == null ? (UInt16)0 : orderInfo.TargetEntity.ID); + msg.WriteUInt32(orderInfo.Prefab.UintIdentifier); + msg.WriteUInt16(orderInfo.TargetEntity == null ? (UInt16)0 : orderInfo.TargetEntity.ID); var hasOrderGiver = orderInfo.OrderGiver != null; - msg.Write(hasOrderGiver); - if (hasOrderGiver) { msg.Write(orderInfo.OrderGiver.ID); } - msg.Write((byte)(orderInfo.Option == Identifier.Empty ? 0 : orderInfo.Prefab.Options.IndexOf(orderInfo.Option))); - msg.Write((byte)orderInfo.ManualPriority); + msg.WriteBoolean(hasOrderGiver); + if (hasOrderGiver) { msg.WriteUInt16(orderInfo.OrderGiver.ID); } + msg.WriteByte((byte)(orderInfo.Option == Identifier.Empty ? 0 : orderInfo.Prefab.Options.IndexOf(orderInfo.Option))); + msg.WriteByte((byte)orderInfo.ManualPriority); var hasTargetPosition = orderInfo.TargetPosition != null; - msg.Write(hasTargetPosition); + msg.WriteBoolean(hasTargetPosition); if (hasTargetPosition) { - msg.Write(orderInfo.TargetPosition.Position.X); - msg.Write(orderInfo.TargetPosition.Position.Y); - msg.Write(orderInfo.TargetPosition.Hull == null ? (UInt16)0 : orderInfo.TargetPosition.Hull.ID); + msg.WriteSingle(orderInfo.TargetPosition.Position.X); + msg.WriteSingle(orderInfo.TargetPosition.Position.Y); + msg.WriteUInt16(orderInfo.TargetPosition.Hull == null ? (UInt16)0 : orderInfo.TargetPosition.Hull.ID); } } int ordersLength = msg.LengthBytes - msgLengthBeforeOrders; @@ -713,7 +713,7 @@ namespace Barotrauma WriteStatus(tempBuffer, forceAfflictionData: true); if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize) { - msg.Write(false); + msg.WriteBoolean(false); if (msgLengthBeforeStatus < 255) { string errorMsg = $"Error when writing character spawn data for \"{Name}\": status data caused the length of the message to exceed 255 bytes ({msgLengthBeforeStatus} + {tempBuffer.LengthBytes})"; @@ -723,7 +723,7 @@ namespace Barotrauma } else { - msg.Write(true); + msg.WriteBoolean(true); WriteStatus(msg, forceAfflictionData: true); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 6c3a29f56..29f57d01b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -302,9 +302,9 @@ namespace Barotrauma { client ??= GameMain.Server.ConnectedClients.Find(c => c.SessionId == id); } - if (Endpoint.Parse(arg).TryUnwrap(out var endpoint)) + if (Address.Parse(arg).TryUnwrap(out var address)) { - client ??= GameMain.Server.ConnectedClients.Find(c => c.EndpointMatches(endpoint)); + client ??= GameMain.Server.ConnectedClients.Find(c => c.AddressMatches(address)); } if (AccountId.Parse(arg).TryUnwrap(out var argAccountId)) { @@ -915,7 +915,7 @@ namespace Barotrauma { if (GameMain.Server == null || args.Length == 0) return; - if (!(Endpoint.Parse(args[0]).TryUnwrap(out var endpoint))) { return; } + if (!(Address.Parse(args[0]).TryUnwrap(out var address))) { return; } ShowQuestionPrompt("Reason for banning the endpoint \"" + args[0] + "\"? (c to cancel)", (reason) => { @@ -934,10 +934,10 @@ namespace Barotrauma banDuration = parsedBanDuration; } - var clients = GameMain.Server.ConnectedClients.Where(c => c.EndpointMatches(endpoint)).ToList(); + var clients = GameMain.Server.ConnectedClients.Where(c => c.AddressMatches(address)).ToList(); if (clients.Count == 0) { - GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", endpoint, reason, banDuration); + GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", address, reason, banDuration); } else { @@ -1510,8 +1510,8 @@ namespace Barotrauma (Client client, Vector2 cursorPos, string[] args) => { if (args.Length < 1) { return; } - if (!(Endpoint.Parse(args[0]).TryUnwrap(out var endpoint))) { return; } - var clients = GameMain.Server.ConnectedClients.Where(c => c.EndpointMatches(endpoint)).ToList(); + if (!(Address.Parse(args[0]).TryUnwrap(out var address))) { return; } + var clients = GameMain.Server.ConnectedClients.Where(c => c.AddressMatches(address)).ToList(); TimeSpan? duration = null; if (args.Length > 1) { @@ -1530,7 +1530,7 @@ namespace Barotrauma if (clients.Count == 0) { - GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", endpoint, reason, duration); + GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", address, reason, duration); } else { @@ -1866,20 +1866,17 @@ namespace Barotrauma } foreach (var talentTree in talentTrees) - { - foreach (var subTree in talentTree.TalentSubTrees) + { + foreach (var talentId in talentTree.AllTalentIdentifiers) { - foreach (var option in subTree.TalentOptionStages) + if (TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab talentPrefab)) { - foreach (var talent in option.Talents) - { - targetCharacter.GiveTalent(talent); - NewMessage($"Talent \"{talent.DisplayName}\" given to \"{targetCharacter.Name}\" by \"{client.Name}\"."); - GameMain.Server.SendConsoleMessage($"Gave talent \"{talent.DisplayName}\" to \"{targetCharacter.Name}\".", client); - NewMessage($"Unlocked talent \"{talent.DisplayName}\"."); - } + targetCharacter.GiveTalent(talentPrefab); + NewMessage($"Talent \"{talentPrefab.DisplayName}\" given to \"{targetCharacter.Name}\" by \"{client.Name}\"."); + GameMain.Server.SendConsoleMessage($"Gave talent \"{talentPrefab.DisplayName}\" to \"{targetCharacter.Name}\".", client); + NewMessage($"Unlocked talent \"{talentPrefab.DisplayName}\"."); } - } + } } } ); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs index 0336b9ed0..c4a043bbd 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs @@ -115,36 +115,36 @@ namespace Barotrauma public void ServerWrite(Character speaker, Client client, bool interrupt) { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ServerPacketHeader.EVENTACTION); - outmsg.Write((byte)EventManager.NetworkEventType.CONVERSATION); - outmsg.Write(Identifier); - outmsg.Write(EventSprite); - outmsg.Write((byte)DialogType); - outmsg.Write(ContinueConversation); + outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION); + outmsg.WriteByte((byte)EventManager.NetworkEventType.CONVERSATION); + outmsg.WriteUInt16(Identifier); + outmsg.WriteString(EventSprite); + outmsg.WriteByte((byte)DialogType); + outmsg.WriteBoolean(ContinueConversation); if (interrupt) { - outmsg.Write(speaker?.ID ?? Entity.NullEntityID); - outmsg.Write(string.Empty); - outmsg.Write(false); - outmsg.Write((byte)0); - outmsg.Write((byte)0); + outmsg.WriteUInt16(speaker?.ID ?? Entity.NullEntityID); + outmsg.WriteString(string.Empty); + outmsg.WriteBoolean(false); + outmsg.WriteByte((byte)0); + outmsg.WriteByte((byte)0); } else { - outmsg.Write(speaker?.ID ?? Entity.NullEntityID); - outmsg.Write(Text ?? string.Empty); - outmsg.Write(FadeToBlack); - outmsg.Write((byte)Options.Count); + outmsg.WriteUInt16(speaker?.ID ?? Entity.NullEntityID); + outmsg.WriteString(Text ?? string.Empty); + outmsg.WriteBoolean(FadeToBlack); + outmsg.WriteByte((byte)Options.Count); for (int i = 0; i < Options.Count; i++) { - outmsg.Write(Options[i].Text); + outmsg.WriteString(Options[i].Text); } int[] endings = GetEndingOptions(); - outmsg.Write((byte)endings.Length); + outmsg.WriteByte((byte)endings.Length); foreach (var end in endings) { - outmsg.Write((byte)end); + outmsg.WriteByte((byte)end); } } GameMain.Server?.ServerPeer?.Send(outmsg, client.Connection, DeliveryMethod.Reliable); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/StatusEffectAction.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/StatusEffectAction.cs index 6eccd6150..a8119efe2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/StatusEffectAction.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/StatusEffectAction.cs @@ -10,14 +10,14 @@ namespace Barotrauma private void ServerWrite(IEnumerable targets) { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ServerPacketHeader.EVENTACTION); - outmsg.Write((byte)EventManager.NetworkEventType.STATUSEFFECT); - outmsg.Write(ParentEvent.Prefab.Identifier); - outmsg.Write((UInt16)actionIndex); - outmsg.Write((UInt16)targets.Count()); + outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION); + outmsg.WriteByte((byte)EventManager.NetworkEventType.STATUSEFFECT); + outmsg.WriteIdentifier(ParentEvent.Prefab.Identifier); + outmsg.WriteUInt16((UInt16)actionIndex); + outmsg.WriteUInt16((UInt16)targets.Count()); foreach (Entity target in targets) { - outmsg.Write(target.ID); + outmsg.WriteUInt16(target.ID); } foreach (Client c in GameMain.Server.ConnectedClients) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs index 1ab83b7ab..bfa52267f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs @@ -12,19 +12,19 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { base.ServerWriteInitial(msg, c); - msg.Write((ushort)spawnedItems.Count); + msg.WriteUInt16((ushort)spawnedItems.Count); foreach (Item item in spawnedItems) { item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0, -1); } - msg.Write((byte)characters.Count); + msg.WriteByte((byte)characters.Count); foreach (Character character in characters) { character.WriteSpawnData(msg, character.ID, restrictMessageSize: false); - msg.Write(requireKill.Contains(character)); - msg.Write(requireRescue.Contains(character)); - msg.Write((ushort)characterItems[character].Count()); + msg.WriteBoolean(requireKill.Contains(character)); + msg.WriteBoolean(requireRescue.Contains(character)); + msg.WriteUInt16((ushort)characterItems[character].Count()); foreach (Item item in characterItems[character]) { item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0, item.ParentInventory?.FindIndex(item) ?? -1); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs index dd8138d18..af7c2c1a9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs @@ -7,12 +7,12 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { base.ServerWriteInitial(msg, c); - msg.Write((ushort)existingTargets.Count); + msg.WriteUInt16((ushort)existingTargets.Count); foreach (var t in existingTargets) { - msg.Write(t != null ? t.ID : Entity.NullEntityID); + msg.WriteUInt16(t != null ? t.ID : Entity.NullEntityID); } - msg.Write((ushort)spawnedTargets.Count); + msg.WriteUInt16((ushort)spawnedTargets.Count); foreach (var t in spawnedTargets) { t.WriteSpawnData(msg, t.ID, false); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs index 6b54790d7..3a144fa3b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs @@ -7,7 +7,7 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { base.ServerWriteInitial(msg, c); - msg.Write((ushort)items.Count); + msg.WriteUInt16((ushort)items.Count); foreach (Item item in items) { item.WriteSpawnData(msg, diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs index 5446c04e1..f7656922a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs @@ -5,12 +5,16 @@ namespace Barotrauma { partial class CombatMission { + const float RoundEndDuration = 5.0f; + private readonly bool[] teamDead = new bool[2]; private List[] crews; private bool initialized = false; + private float roundEndTimer; + public override LocalizedString Description { get @@ -53,6 +57,7 @@ namespace Barotrauma { teamDead[0] = crews[0].All(c => c.IsDead || c.IsIncapacitated); teamDead[1] = crews[1].All(c => c.IsDead || c.IsIncapacitated); + if (teamDead[0] && teamDead[1]) { state = 1; } } if (state == 0) @@ -66,13 +71,17 @@ namespace Barotrauma GameMain.GameSession.WinningTeam = i == 0 ? CharacterTeamType.Team1 : CharacterTeamType.Team2; - state = 1; + //state 1 = team 1 won, 2 = team 2 won + State = i + 1; break; } } } else { + roundEndTimer -= deltaTime; + if (roundEndTimer > 0.0f) { return; } + if (teamDead[0] && teamDead[1]) { GameMain.GameSession.WinningTeam = CharacterTeamType.None; @@ -81,7 +90,7 @@ namespace Barotrauma else if (GameMain.GameSession.WinningTeam != CharacterTeamType.None) { GameMain.Server.EndGame(); - } + } } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs index dea3acc83..5415379fb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs @@ -16,12 +16,12 @@ namespace Barotrauma throw new InvalidOperationException("Server attempted to write escort mission data when no characters had been spawned."); } - msg.Write((byte)characters.Count); + msg.WriteByte((byte)characters.Count); foreach (Character character in characters) { character.WriteSpawnData(msg, character.ID, restrictMessageSize: false); - msg.Write(terroristCharacters.Contains(character)); - msg.Write((ushort)characterItems[character].Count()); + msg.WriteBoolean(terroristCharacters.Contains(character)); + msg.WriteUInt16((ushort)characterItems[character].Count()); foreach (Item item in characterItems[character]) { item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0, item.ParentInventory?.FindIndex(item) ?? -1); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs index ada2e763a..65a5653e8 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs @@ -7,17 +7,17 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { base.ServerWriteInitial(msg, c); - msg.Write((byte)caves.Count); + msg.WriteByte((byte)caves.Count); foreach (var cave in caves) { - msg.Write((byte)(Level.Loaded == null || !Level.Loaded.Caves.Contains(cave) ? 255 : Level.Loaded.Caves.IndexOf(cave))); + msg.WriteByte((byte)(Level.Loaded == null || !Level.Loaded.Caves.Contains(cave) ? 255 : Level.Loaded.Caves.IndexOf(cave))); } foreach (var kvp in spawnedResources) { - msg.Write((byte)kvp.Value.Count); + msg.WriteByte((byte)kvp.Value.Count); var rotation = resourceClusters[kvp.Key].Rotation; - msg.Write(rotation); + msg.WriteSingle(rotation); foreach (var r in kvp.Value) { r.WriteSpawnData(msg, r.ID, Entity.NullEntityID, 0, -1); @@ -26,11 +26,11 @@ namespace Barotrauma foreach (var kvp in relevantLevelResources) { - msg.Write(kvp.Key); - msg.Write((byte)kvp.Value.Length); + msg.WriteIdentifier(kvp.Key); + msg.WriteByte((byte)kvp.Value.Length); foreach (var i in kvp.Value) { - msg.Write(i.ID); + msg.WriteUInt16(i.ID); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs index 064ccd3e3..433b33075 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs @@ -12,18 +12,22 @@ namespace Barotrauma LocalizedString header = messageIndex < Headers.Length ? Headers[messageIndex] : ""; LocalizedString message = messageIndex < Messages.Length ? Messages[messageIndex] : ""; + if (!message.IsNullOrEmpty()) + { + message = ModifyMessage(message, color: false); + } GameServer.Log($"{TextManager.Get("MissionInfo")}: {header} - {message}", ServerLog.MessageType.ServerMessage); } public virtual void ServerWriteInitial(IWriteMessage msg, Client c) { - msg.Write((ushort)State); + msg.WriteUInt16((ushort)State); } public virtual void ServerWrite(IWriteMessage msg) { - msg.Write((ushort)State); + msg.WriteUInt16((ushort)State); } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs index 937e23511..9af7f048e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs @@ -14,7 +14,7 @@ namespace Barotrauma throw new InvalidOperationException("Server attempted to write monster mission data when no monsters had been spawned."); } - msg.Write((byte)monsters.Count); + msg.WriteByte((byte)monsters.Count); foreach (Character monster in monsters) { monster.WriteSpawnData(msg, monster.ID, restrictMessageSize: false); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/NestMission.cs index f8e974834..35649469e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/NestMission.cs @@ -9,10 +9,10 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { base.ServerWriteInitial(msg, c); - msg.Write((byte)(selectedCave == null || Level.Loaded == null || !Level.Loaded.Caves.Contains(selectedCave) ? 255 : Level.Loaded.Caves.IndexOf(selectedCave))); - msg.Write(nestPosition.X); - msg.Write(nestPosition.Y); - msg.Write((ushort)items.Count); + msg.WriteByte((byte)(selectedCave == null || Level.Loaded == null || !Level.Loaded.Caves.Contains(selectedCave) ? 255 : Level.Loaded.Caves.IndexOf(selectedCave))); + msg.WriteSingle(nestPosition.X); + msg.WriteSingle(nestPosition.Y); + msg.WriteUInt16((ushort)items.Count); foreach (Item item in items) { item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0, -1); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/PirateMission.cs index c04b48601..3295780ce 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/PirateMission.cs @@ -17,11 +17,11 @@ namespace Barotrauma throw new InvalidOperationException("Server attempted to write escort mission data when no characters had been spawned."); } - msg.Write((byte)characters.Count); + msg.WriteByte((byte)characters.Count); foreach (Character character in characters) { character.WriteSpawnData(msg, character.ID, restrictMessageSize: false); - msg.Write((ushort)characterItems[character].Count()); + msg.WriteUInt16((ushort)characterItems[character].Count()); foreach (Item item in characterItems[character]) { item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0, item.ParentInventory?.FindIndex(item) ?? -1); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs index cdc8e3622..52a944a4e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs @@ -18,21 +18,21 @@ namespace Barotrauma { base.ServerWriteInitial(msg, c); - msg.Write(usedExistingItem); + msg.WriteBoolean(usedExistingItem); if (usedExistingItem) { - msg.Write(item.ID); + msg.WriteUInt16(item.ID); } else { item.WriteSpawnData(msg, item.ID, originalInventoryID, originalItemContainerIndex, originalSlotIndex); } - msg.Write((byte)executedEffectIndices.Count); + msg.WriteByte((byte)executedEffectIndices.Count); foreach (Pair effectIndex in executedEffectIndices) { - msg.Write((byte)effectIndex.First); - msg.Write((byte)effectIndex.Second); + msg.WriteByte((byte)effectIndex.First); + msg.WriteByte((byte)effectIndex.Second); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs index 010ed0224..ec766fd9a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs @@ -7,7 +7,7 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { base.ServerWriteInitial(msg, c); - msg.Write((ushort)startingItems.Count); + msg.WriteUInt16((ushort)startingItems.Count); foreach (var item in startingItems) { item.WriteSpawnData(msg, @@ -27,11 +27,11 @@ namespace Barotrauma private void ServerWriteScanTargetStatus(IWriteMessage msg) { - msg.Write((byte)scanTargets.Count); + msg.WriteByte((byte)scanTargets.Count); foreach (var kvp in scanTargets) { - msg.Write(kvp.Key != null ? kvp.Key.ID : Entity.NullEntityID); - msg.Write(kvp.Value); + msg.WriteUInt16(kvp.Key != null ? kvp.Key.ID : Entity.NullEntityID); + msg.WriteBoolean(kvp.Value); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs index 2c7358b04..c14233f30 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs @@ -47,7 +47,7 @@ namespace Barotrauma public void ServerWriteActiveOrders(IWriteMessage msg) { ushort count = (ushort)ActiveOrders.Count(o => o.Order != null && !o.FadeOutTime.HasValue); - msg.Write(count); + msg.WriteUInt16(count); if (count > 0) { foreach (var activeOrder in ActiveOrders) @@ -55,10 +55,10 @@ namespace Barotrauma if (!(activeOrder?.Order is Order order) || activeOrder.FadeOutTime.HasValue) { continue; } OrderChatMessage.WriteOrder(msg, order, null, isNewOrder: true); bool hasOrderGiver = order.OrderGiver != null; - msg.Write(hasOrderGiver); + msg.WriteBoolean(hasOrderGiver); if (hasOrderGiver) { - msg.Write(order.OrderGiver.ID); + msg.WriteUInt16(order.OrderGiver.ID); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index e9d0dfbe0..accb5e13a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -575,57 +575,57 @@ namespace Barotrauma NetFlags requiredFlags = lastUpdateID.Keys.Where(k => IsFlagRequired(c, k)).Aggregate((NetFlags)0, (f1, f2) => f1 | f2); - msg.Write((UInt16)requiredFlags); + msg.WriteUInt16((UInt16)requiredFlags); - msg.Write(IsFirstRound); - msg.Write(CampaignID); - msg.Write(lastSaveID); - msg.Write(map.Seed); + msg.WriteBoolean(IsFirstRound); + msg.WriteByte(CampaignID); + msg.WriteUInt16(lastSaveID); + msg.WriteString(map.Seed); if (requiredFlags.HasFlag(NetFlags.Misc)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.Misc)); - msg.Write(PurchasedHullRepairs); - msg.Write(PurchasedItemRepairs); - msg.Write(PurchasedLostShuttles); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.Misc)); + msg.WriteBoolean(PurchasedHullRepairs); + msg.WriteBoolean(PurchasedItemRepairs); + msg.WriteBoolean(PurchasedLostShuttles); } if (requiredFlags.HasFlag(NetFlags.MapAndMissions)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.MapAndMissions)); - msg.Write(ForceMapUI); - msg.Write(map.AllowDebugTeleport); - msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); - msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.MapAndMissions)); + msg.WriteBoolean(ForceMapUI); + msg.WriteBoolean(map.AllowDebugTeleport); + msg.WriteUInt16(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); + msg.WriteUInt16(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); if (map.CurrentLocation != null) { - msg.Write((byte)map.CurrentLocation.AvailableMissions.Count()); + msg.WriteByte((byte)map.CurrentLocation.AvailableMissions.Count()); foreach (Mission mission in map.CurrentLocation.AvailableMissions) { - msg.Write(mission.Prefab.Identifier); + msg.WriteIdentifier(mission.Prefab.Identifier); if (mission.Locations[0] == mission.Locations[1]) { - msg.Write((byte)255); + msg.WriteByte((byte)255); } else { Location missionDestination = mission.Locations[0] == map.CurrentLocation ? mission.Locations[1] : mission.Locations[0]; LocationConnection connection = map.CurrentLocation.Connections.Find(c => c.OtherLocation(map.CurrentLocation) == missionDestination); - msg.Write((byte)map.CurrentLocation.Connections.IndexOf(connection)); + msg.WriteByte((byte)map.CurrentLocation.Connections.IndexOf(connection)); } } } else { - msg.Write((byte)0); + msg.WriteByte((byte)0); } var selectedMissionIndices = map.GetSelectedMissionIndices(); - msg.Write((byte)selectedMissionIndices.Count()); + msg.WriteByte((byte)selectedMissionIndices.Count()); foreach (int selectedMissionIndex in selectedMissionIndices) { - msg.Write((byte)selectedMissionIndex); + msg.WriteByte((byte)selectedMissionIndex); } WriteStores(msg); @@ -633,7 +633,7 @@ namespace Barotrauma if (requiredFlags.HasFlag(NetFlags.SubList)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.SubList)); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.SubList)); var subList = GameMain.NetLobbyScreen.GetSubList(); List ownedSubmarineIndices = new List(); for (int i = 0; i < subList.Count; i++) @@ -643,83 +643,83 @@ namespace Barotrauma ownedSubmarineIndices.Add(i); } } - msg.Write((ushort)ownedSubmarineIndices.Count); + msg.WriteUInt16((ushort)ownedSubmarineIndices.Count); foreach (int index in ownedSubmarineIndices) { - msg.Write((ushort)index); + msg.WriteUInt16((ushort)index); } } if (requiredFlags.HasFlag(NetFlags.UpgradeManager)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.UpgradeManager)); - msg.Write((ushort)UpgradeManager.PendingUpgrades.Count); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.UpgradeManager)); + msg.WriteUInt16((ushort)UpgradeManager.PendingUpgrades.Count); foreach (var (prefab, category, level) in UpgradeManager.PendingUpgrades) { - msg.Write(prefab.Identifier); - msg.Write(category.Identifier); - msg.Write((byte)level); + msg.WriteIdentifier(prefab.Identifier); + msg.WriteIdentifier(category.Identifier); + msg.WriteByte((byte)level); } - msg.Write((ushort)UpgradeManager.PurchasedItemSwaps.Count); + msg.WriteUInt16((ushort)UpgradeManager.PurchasedItemSwaps.Count); foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps) { - msg.Write(itemSwap.ItemToRemove.ID); - msg.Write(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty); + msg.WriteUInt16(itemSwap.ItemToRemove.ID); + msg.WriteIdentifier(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty); } } if (requiredFlags.HasFlag(NetFlags.ItemsInBuyCrate)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.ItemsInBuyCrate)); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.ItemsInBuyCrate)); WriteItems(msg, CargoManager.ItemsInBuyCrate); WriteStores(msg); } if (requiredFlags.HasFlag(NetFlags.ItemsInSellFromSubCrate)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.ItemsInSellFromSubCrate)); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.ItemsInSellFromSubCrate)); WriteItems(msg, CargoManager.ItemsInSellFromSubCrate); WriteStores(msg); } if (requiredFlags.HasFlag(NetFlags.PurchasedItems)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.PurchasedItems)); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.PurchasedItems)); WriteItems(msg, CargoManager.PurchasedItems); WriteStores(msg); } if (requiredFlags.HasFlag(NetFlags.SoldItems)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.SoldItems)); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.SoldItems)); WriteItems(msg, CargoManager.SoldItems); WriteStores(msg); } if (requiredFlags.HasFlag(NetFlags.Reputation)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.Reputation)); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.Reputation)); Reputation reputation = Map?.CurrentLocation?.Reputation; - msg.Write(reputation != null); - if (reputation != null) { msg.Write(reputation.Value); } + msg.WriteBoolean(reputation != null); + if (reputation != null) { msg.WriteSingle(reputation.Value); } // hopefully we'll never have more than 128 factions - msg.Write((byte)Factions.Count); + msg.WriteByte((byte)Factions.Count); foreach (Faction faction in Factions) { - msg.Write(faction.Prefab.Identifier); - msg.Write(faction.Reputation.Value); + msg.WriteIdentifier(faction.Prefab.Identifier); + msg.WriteSingle(faction.Reputation.Value); } } if (requiredFlags.HasFlag(NetFlags.CharacterInfo)) { - msg.Write(GetLastUpdateIdForFlag(NetFlags.CharacterInfo)); + msg.WriteUInt16(GetLastUpdateIdForFlag(NetFlags.CharacterInfo)); var characterData = GetClientCharacterData(c); if (characterData?.CharacterInfo == null) { - msg.Write(false); + msg.WriteBoolean(false); } else { - msg.Write(true); + msg.WriteBoolean(true); characterData.CharacterInfo.ServerWrite(msg); } } @@ -730,22 +730,22 @@ namespace Barotrauma { // Store balance bool hasStores = map.CurrentLocation.Stores != null && map.CurrentLocation.Stores.Any(); - msg.Write(hasStores); + msg.WriteBoolean(hasStores); if (hasStores) { - msg.Write((byte)map.CurrentLocation.Stores.Count); + msg.WriteByte((byte)map.CurrentLocation.Stores.Count); foreach (var store in map.CurrentLocation.Stores.Values) { - msg.Write(store.Identifier); - msg.Write((UInt16)store.Balance); + msg.WriteIdentifier(store.Identifier); + msg.WriteUInt16((UInt16)store.Balance); } } } else { - msg.Write((byte)0); + msg.WriteByte((byte)0); // Store balance - msg.Write(false); + msg.WriteBoolean(false); } } } @@ -897,9 +897,10 @@ namespace Barotrauma { if (map?.CurrentLocation?.Stores == null || !map.CurrentLocation.Stores.ContainsKey(store.Key)) { continue; } int availableQuantity = map.CurrentLocation.Stores[store.Key].Stock.Find(s => s.ItemPrefab == item.ItemPrefab)?.Quantity ?? 0; - int alreadyPurchasedQuantity = CargoManager.GetBuyCrateItem(store.Key, item.ItemPrefab)?.Quantity ?? 0; - item.Quantity = Math.Min(availableQuantity - alreadyPurchasedQuantity, item.Quantity); - if (item.Quantity <= 0) { continue; } + int alreadyPurchasedQuantity = + CargoManager.GetBuyCrateItem(store.Key, item.ItemPrefab)?.Quantity ?? 0 + + CargoManager.GetPurchasedItem(store.Key, item.ItemPrefab)?.Quantity ?? 0; + item.Quantity = MathHelper.Clamp(item.Quantity, 0, availableQuantity - alreadyPurchasedQuantity); CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender); } } @@ -909,13 +910,22 @@ namespace Barotrauma { prevPurchasedItems.Add(kvp.Key, new List(kvp.Value)); } - foreach (var store in prevPurchasedItems) + foreach (var kvp in prevPurchasedItems) { - CargoManager.SellBackPurchasedItems(store.Key, store.Value, sender); + CargoManager.SellBackPurchasedItems(kvp.Key, kvp.Value, sender); } - foreach (var store in purchasedItems) + + foreach (var kvp in purchasedItems) { - CargoManager.PurchaseItems(store.Key, store.Value, false, sender); + foreach (var purchasedItemList in purchasedItems.Values) + { + foreach (var purchasedItem in purchasedItemList) + { + int availableQuantity = map.CurrentLocation.Stores[kvp.Key].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0; + purchasedItem.Quantity = Math.Min(purchasedItem.Quantity, availableQuantity); + } + } + CargoManager.PurchaseItems(kvp.Key, kvp.Value, false, sender); } foreach (var (storeIdentifier, items) in CargoManager.PurchasedItems) @@ -1264,41 +1274,41 @@ namespace Barotrauma foreach (Client client in GameMain.Server.ConnectedClients) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.CREW); + msg.WriteByte((byte)ServerPacketHeader.CREW); - msg.Write((ushort)availableHires.Count); + msg.WriteUInt16((ushort)availableHires.Count); foreach (CharacterInfo hire in availableHires) { hire.ServerWrite(msg); - msg.Write(hire.Salary); + msg.WriteInt32(hire.Salary); } - msg.Write((ushort)pendingHires.Count); + msg.WriteUInt16((ushort)pendingHires.Count); foreach (CharacterInfo pendingHire in pendingHires) { - msg.Write(pendingHire.GetIdentifierUsingOriginalName()); + msg.WriteInt32(pendingHire.GetIdentifierUsingOriginalName()); } - msg.Write((ushort)(hiredCharacters?.Count ?? 0)); + msg.WriteUInt16((ushort)(hiredCharacters?.Count ?? 0)); if(hiredCharacters != null) { foreach (CharacterInfo info in hiredCharacters) { info.ServerWrite(msg); - msg.Write(info.Salary); + msg.WriteInt32(info.Salary); } } bool validRenaming = renamedCrewMember.id > -1 && !string.IsNullOrEmpty(renamedCrewMember.newName); - msg.Write(validRenaming); + msg.WriteBoolean(validRenaming); if (validRenaming) { - msg.Write(renamedCrewMember.id); - msg.Write(renamedCrewMember.newName); + msg.WriteInt32(renamedCrewMember.id); + msg.WriteString(renamedCrewMember.newName); } - msg.Write(firedCharacter != null); - if (firedCharacter != null) { msg.Write(firedCharacter.GetIdentifier()); } + msg.WriteBoolean(firedCharacter != null); + if (firedCharacter != null) { msg.WriteInt32(firedCharacter.GetIdentifier()); } GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs index e9c23978c..e30b1148f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs @@ -154,7 +154,7 @@ namespace Barotrauma private IWriteMessage StartSending() { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.MEDICAL); + msg.WriteByte((byte)ServerPacketHeader.MEDICAL); return msg; } @@ -181,8 +181,8 @@ namespace Barotrauma } IWriteMessage msg = StartSending(); - msg.Write((byte)header); - msg.Write((byte)flag); + msg.WriteByte((byte)header); + msg.WriteByte((byte)flag); netStruct?.Write(msg); GameMain.Server.ServerPeer.Send(msg, c.Connection, deliveryMethod); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/ReadyCheck.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/ReadyCheck.cs index d7e547b2b..13411d051 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/ReadyCheck.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/ReadyCheck.cs @@ -17,26 +17,26 @@ namespace Barotrauma if (client != null && !client.Spectating) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte) ServerPacketHeader.READY_CHECK); - msg.Write((byte) ReadyCheckState.Start); - msg.Write(new DateTimeOffset(startTime).ToUnixTimeSeconds()); - msg.Write(new DateTimeOffset(endTime).ToUnixTimeSeconds()); - msg.Write(author); + msg.WriteByte((byte)ServerPacketHeader.READY_CHECK); + msg.WriteByte((byte)ReadyCheckState.Start); + msg.WriteInt64(new DateTimeOffset(startTime).ToUnixTimeSeconds()); + msg.WriteInt64(new DateTimeOffset(endTime).ToUnixTimeSeconds()); + msg.WriteString(author); if (sender != null) { - msg.Write(true); - msg.Write(sender.SessionId); + msg.WriteBoolean(true); + msg.WriteByte(sender.SessionId); } else { - msg.Write(false); + msg.WriteBoolean(false); } - msg.Write((ushort) ActivePlayers.Count); + msg.WriteUInt16((ushort)ActivePlayers.Count); foreach (byte clientId in Clients.Keys) { - msg.Write(clientId); + msg.WriteByte(clientId); } GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); @@ -55,10 +55,10 @@ namespace Barotrauma foreach (Client client in ActivePlayers) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.READY_CHECK); - msg.Write((byte)ReadyCheckState.Update); - msg.Write((byte)state); - msg.Write(otherClient); + msg.WriteByte((byte)ServerPacketHeader.READY_CHECK); + msg.WriteByte((byte)ReadyCheckState.Update); + msg.WriteByte((byte)state); + msg.WriteByte(otherClient); GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } } @@ -72,13 +72,13 @@ namespace Barotrauma if (client != null && !client.Spectating) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte) ServerPacketHeader.READY_CHECK); - msg.Write((byte) ReadyCheckState.End); - msg.Write((ushort) Clients.Count); + msg.WriteByte((byte)ServerPacketHeader.READY_CHECK); + msg.WriteByte((byte)ReadyCheckState.End); + msg.WriteUInt16((ushort)Clients.Count); foreach (var (id, state) in Clients) { - msg.Write(id); - msg.Write((byte) state); + msg.WriteByte(id); + msg.WriteByte((byte)state); } GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); @@ -88,7 +88,7 @@ namespace Barotrauma public static void ServerRead(IReadMessage inc, Client client) { - ReadyCheckState state = (ReadyCheckState) inc.ReadByte(); + ReadyCheckState state = (ReadyCheckState)inc.ReadByte(); ReadyCheck? readyCheck = GameMain.GameSession?.CrewManager?.ActiveReadyCheck; switch (state) @@ -98,7 +98,7 @@ namespace Barotrauma break; case ReadyCheckState.Update when readyCheck != null: - ReadyStatus status = (ReadyStatus) inc.ReadByte(); + ReadyStatus status = (ReadyStatus)inc.ReadByte(); if (!readyCheck.Clients.ContainsKey(client.SessionId)) { return; } readyCheck.Clients[client.SessionId] = status; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/DockingPort.cs index 607149e26..b5349e891 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/DockingPort.cs @@ -6,11 +6,11 @@ namespace Barotrauma.Items.Components { public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(docked); + msg.WriteBoolean(docked); if (docked) { - msg.Write(DockingTarget.item.ID); - msg.Write(IsLocked); + msg.WriteUInt16(DockingTarget.item.ID); + msg.WriteBoolean(IsLocked); } } public void ServerEventRead(IReadMessage msg, Client c) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Door.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Door.cs index 3edb8ff4e..6f980eab7 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Door.cs @@ -38,13 +38,13 @@ namespace Barotrauma.Items.Components bool forcedOpen = TryExtractEventData(extraData, out var eventData) && eventData.ForcedOpen; base.ServerEventWrite(msg, c, extraData); - msg.Write(isOpen); - msg.Write(isBroken); - msg.Write(forcedOpen); //forced open - msg.Write(isStuck); - msg.Write(isJammed); + msg.WriteBoolean(isOpen); + msg.WriteBoolean(isBroken); + msg.WriteBoolean(forcedOpen); //forced open + msg.WriteBoolean(isStuck); + msg.WriteBoolean(isJammed); msg.WriteRangedSingle(stuck, 0.0f, 100.0f, 8); - msg.Write(lastUser == null ? (UInt16)0 : lastUser.ID); + msg.WriteUInt16(lastUser == null ? (UInt16)0 : lastUser.ID); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs index ff82cc9d8..6951f65c5 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs @@ -6,14 +6,14 @@ namespace Barotrauma.Items.Components { public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(tainted); + msg.WriteBoolean(tainted); if (tainted) { - msg.Write(selectedTaintedEffect?.UintIdentifier ?? 0); + msg.WriteUInt32(selectedTaintedEffect?.UintIdentifier ?? 0); } else { - msg.Write(selectedEffect?.UintIdentifier ?? 0); + msg.WriteUInt32(selectedEffect?.UintIdentifier ?? 0); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Growable.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Growable.cs index 8fd930fc8..8749382f1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Growable.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Growable.cs @@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.WriteRangedSingle(Health, 0f, (float) MaxHealth, 8); + msg.WriteRangedSingle(Health, 0f, (float)MaxHealth, 8); if (TryExtractEventData(extraData, out EventData eventData)) { int offset = eventData.Offset; @@ -48,11 +48,11 @@ namespace Barotrauma.Items.Components { VineTile vine = Vines[i]; var (x, y) = vine.Position; - msg.WriteRangedInteger((byte) vine.Type, 0b0000, 0b1111); + msg.WriteRangedInteger((byte)vine.Type, 0b0000, 0b1111); msg.WriteRangedInteger(vine.FlowerConfig.Serialize(), 0, 0xFFF); msg.WriteRangedInteger(vine.LeafConfig.Serialize(), 0, 0xFFF); - msg.Write((byte) (x / VineTile.Size)); - msg.Write((byte) (y / VineTile.Size)); + msg.WriteByte((byte)(x / VineTile.Size)); + msg.WriteByte((byte)(y / VineTile.Size)); } } else diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Holdable/Holdable.cs index 0b8521700..98c6a0fa9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Holdable/Holdable.cs @@ -10,13 +10,13 @@ namespace Barotrauma.Items.Components base.ServerEventWrite(msg, c, extraData); bool writeAttachData = attachable && body != null; - msg.Write(writeAttachData); + msg.WriteBoolean(writeAttachData); if (!writeAttachData) { return; } - msg.Write(Attached); - msg.Write(body.SimPosition.X); - msg.Write(body.SimPosition.Y); - msg.Write(item.Submarine?.ID ?? Entity.NullEntityID); + msg.WriteBoolean(Attached); + msg.WriteSingle(body.SimPosition.X); + msg.WriteSingle(body.SimPosition.Y); + msg.WriteUInt16(item.Submarine?.ID ?? Entity.NullEntityID); } public void ServerEventRead(IReadMessage msg, Client c) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Holdable/LevelResource.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Holdable/LevelResource.cs index 3d372c206..bd9a54c1d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Holdable/LevelResource.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Holdable/LevelResource.cs @@ -8,7 +8,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(deattachTimer); + msg.WriteSingle(deattachTimer); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs index ad7dd525f..dbcf85e4d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs @@ -78,7 +78,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(Text); + msg.WriteString(Text); lastSentText = Text; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/LightComponent.cs index 4396628d9..bf7e8e2c4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/LightComponent.cs @@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(IsActive); + msg.WriteBoolean(IsActive); lastSentState = IsActive; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Controller.cs index dc87c0b4a..8d8fe2933 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Controller.cs @@ -6,8 +6,8 @@ namespace Barotrauma.Items.Components { public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(State); - msg.Write(user == null ? (ushort)0 : user.ID); + msg.WriteBoolean(State); + msg.WriteUInt16(user == null ? (ushort)0 : user.ID); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Deconstructor.cs index 9897bc414..91b7515b2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Deconstructor.cs @@ -18,9 +18,9 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(user?.ID ?? 0); - msg.Write(IsActive); - msg.Write(progressTimer); + msg.WriteUInt16(user?.ID ?? 0); + msg.WriteBoolean(IsActive); + msg.WriteSingle(progressTimer); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Engine.cs index a1f431279..b611a9b7f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Engine.cs @@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components { //force can only be adjusted at 10% intervals -> no need for more accuracy than this msg.WriteRangedInteger((int)(targetForce / 10.0f), -10, 10); - msg.Write(User == null ? Entity.NullEntityID : User.ID); + msg.WriteUInt16(User == null ? Entity.NullEntityID : User.ID); } public void ServerEventRead(IReadMessage msg, Client c) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Fabricator.cs index bd1572c90..c3a383431 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Fabricator.cs @@ -55,18 +55,18 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { var componentData = ExtractEventData(extraData); - msg.Write((byte)componentData.State); - msg.Write(timeUntilReady); + msg.WriteByte((byte)componentData.State); + msg.WriteSingle(timeUntilReady); uint recipeHash = fabricatedItem?.RecipeHash ?? 0; - msg.Write(recipeHash); + msg.WriteUInt32(recipeHash); UInt16 userId = fabricatedItem is null || user is null ? (UInt16)0 : user.ID; - msg.Write(userId); + msg.WriteUInt16(userId); var reachedLimits = fabricationLimits.Where(kvp => kvp.Value <= 0); - msg.Write((ushort)reachedLimits.Count()); + msg.WriteUInt16((ushort)reachedLimits.Count()); foreach (var kvp in reachedLimits) { - msg.Write(kvp.Key); + msg.WriteUInt32(kvp.Key); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Pump.cs index 125557284..a026feea0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Pump.cs @@ -49,16 +49,16 @@ namespace Barotrauma.Items.Components { //flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10); - msg.Write(IsActive); - msg.Write(Hijacked); + msg.WriteBoolean(IsActive); + msg.WriteBoolean(Hijacked); if (TargetLevel != null) { - msg.Write(true); - msg.Write(TargetLevel.Value); + msg.WriteBoolean(true); + msg.WriteSingle(TargetLevel.Value); } else { - msg.Write(false); + msg.WriteBoolean(false); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Reactor.cs index 91f9f02ad..0cfbe9026 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Reactor.cs @@ -45,8 +45,8 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(autoTemp); - msg.Write(_powerOn); + msg.WriteBoolean(autoTemp); + msg.WriteBoolean(_powerOn); msg.WriteRangedSingle(temperature, 0.0f, 100.0f, 8); msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8); msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Steering.cs index 4145c0b35..f618c0a72 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Steering.cs @@ -100,30 +100,30 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Barotrauma.Networking.Client c, NetEntityEvent.IData extraData = null) { - msg.Write(autoPilot); - msg.Write(TryExtractEventData(extraData, out var eventData) && eventData.DockingButtonClicked); - msg.Write(user?.ID ?? Entity.NullEntityID); + msg.WriteBoolean(autoPilot); + msg.WriteBoolean(TryExtractEventData(extraData, out var eventData) && eventData.DockingButtonClicked); + msg.WriteUInt16(user?.ID ?? Entity.NullEntityID); if (!autoPilot) { //no need to write steering info if autopilot is controlling - msg.Write(steeringInput.X); - msg.Write(steeringInput.Y); - msg.Write(targetVelocity.X); - msg.Write(targetVelocity.Y); - msg.Write(steeringAdjustSpeed); + msg.WriteSingle(steeringInput.X); + msg.WriteSingle(steeringInput.Y); + msg.WriteSingle(targetVelocity.X); + msg.WriteSingle(targetVelocity.Y); + msg.WriteSingle(steeringAdjustSpeed); } else { - msg.Write(posToMaintain != null); + msg.WriteBoolean(posToMaintain != null); if (posToMaintain != null) { - msg.Write(((Vector2)posToMaintain).X); - msg.Write(((Vector2)posToMaintain).Y); + msg.WriteSingle(((Vector2)posToMaintain).X); + msg.WriteSingle(((Vector2)posToMaintain).Y); } else { - msg.Write(LevelStartSelected); + msg.WriteBoolean(LevelStartSelected); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Projectile.cs index 7bf233c95..60cc5c609 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Projectile.cs @@ -25,39 +25,39 @@ namespace Barotrauma.Items.Components var eventData = ExtractEventData(extraData); bool launch = eventData.Launch; - msg.Write(launch); + msg.WriteBoolean(launch); if (launch) { - msg.Write(User.ID); - msg.Write(launchPos.X); - msg.Write(launchPos.Y); - msg.Write(launchRot); + msg.WriteUInt16(User.ID); + msg.WriteSingle(launchPos.X); + msg.WriteSingle(launchPos.Y); + msg.WriteSingle(launchRot); } bool stuck = StickTarget != null && !item.Removed && !StickTargetRemoved(); - msg.Write(stuck); + msg.WriteBoolean(stuck); if (stuck) { - msg.Write(item.Submarine?.ID ?? Entity.NullEntityID); - msg.Write(item.CurrentHull?.ID ?? Entity.NullEntityID); - msg.Write(item.SimPosition.X); - msg.Write(item.SimPosition.Y); - msg.Write(jointAxis.X); - msg.Write(jointAxis.Y); + msg.WriteUInt16(item.Submarine?.ID ?? Entity.NullEntityID); + msg.WriteUInt16(item.CurrentHull?.ID ?? Entity.NullEntityID); + msg.WriteSingle(item.SimPosition.X); + msg.WriteSingle(item.SimPosition.Y); + msg.WriteSingle(jointAxis.X); + msg.WriteSingle(jointAxis.Y); if (StickTarget.UserData is Structure structure) { - msg.Write(structure.ID); + msg.WriteUInt16(structure.ID); int bodyIndex = structure.Bodies.IndexOf(StickTarget); - msg.Write((byte)(bodyIndex == -1 ? 0 : bodyIndex)); + msg.WriteByte((byte)(bodyIndex == -1 ? 0 : bodyIndex)); } else if (StickTarget.UserData is Entity entity) { - msg.Write(entity.ID); + msg.WriteUInt16(entity.ID); } else if (StickTarget.UserData is Limb limb) { - msg.Write(limb.character.ID); - msg.Write((byte)Array.IndexOf(limb.character.AnimController.Limbs, limb)); + msg.WriteUInt16(limb.character.ID); + msg.WriteByte((byte)Array.IndexOf(limb.character.AnimController.Limbs, limb)); } else { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs index 41d2f5c1d..e4e5845c9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs @@ -44,13 +44,13 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(deteriorationTimer); - msg.Write(deteriorateAlwaysResetTimer); - msg.Write(DeteriorateAlways); - msg.Write(tinkeringDuration); - msg.Write(tinkeringStrength); - msg.Write(tinkeringPowersDevices); - msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID); + msg.WriteSingle(deteriorationTimer); + msg.WriteSingle(deteriorateAlwaysResetTimer); + msg.WriteBoolean(DeteriorateAlways); + msg.WriteSingle(tinkeringDuration); + msg.WriteSingle(tinkeringStrength); + msg.WriteBoolean(tinkeringPowersDevices); + msg.WriteUInt16(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID); msg.WriteRangedInteger((int)currentFixerAction, 0, 2); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Rope.cs index 81b46fe2a..11342ebed 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Rope.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Rope.cs @@ -6,25 +6,25 @@ namespace Barotrauma.Items.Components { public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(Snapped); + msg.WriteBoolean(Snapped); if (!Snapped) { - msg.Write(target?.ID ?? Entity.NullEntityID); + msg.WriteUInt16(target?.ID ?? Entity.NullEntityID); if (source is Entity entity && !entity.Removed) { - msg.Write(entity?.ID ?? Entity.NullEntityID); - msg.Write((byte)0); + msg.WriteUInt16(entity?.ID ?? Entity.NullEntityID); + msg.WriteByte((byte)0); } else if (source is Limb limb && limb.character != null && !limb.character.Removed) { - msg.Write(limb.character?.ID ?? Entity.NullEntityID); - msg.Write((byte)limb.character.AnimController.Limbs.IndexOf(limb)); + msg.WriteUInt16(limb.character?.ID ?? Entity.NullEntityID); + msg.WriteByte((byte)limb.character.AnimController.Limbs.IndexOf(limb)); } else { - msg.Write(Entity.NullEntityID); - msg.Write((byte)0); + msg.WriteUInt16(Entity.NullEntityID); + msg.WriteByte((byte)0); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs index 2af1acfea..3b1b85227 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs @@ -8,7 +8,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(scanTimer); + msg.WriteSingle(scanTimer); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs index a9cffe9ef..7ba476df9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs @@ -208,7 +208,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(user == null ? (ushort)0 : user.ID); + msg.WriteUInt16(user == null ? (ushort)0 : user.ID); ClientEventWrite(msg, extraData); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CustomInterface.cs index c1742cfa7..b103e8e7f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CustomInterface.cs @@ -72,15 +72,15 @@ namespace Barotrauma.Items.Components var element = customInterfaceElementList[i]; if (element.HasPropertyName) { - msg.Write(element.Signal); + msg.WriteString(element.Signal); } else if(element.ContinuousSignal) { - msg.Write(element.State); + msg.WriteBoolean(element.State); } else { - msg.Write(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]); + msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/MemoryComponent.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/MemoryComponent.cs index d99732838..189f07c27 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/MemoryComponent.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/MemoryComponent.cs @@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(Value); + msg.WriteString(Value); lastSentValue = Value; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs index 9fdd21863..8beb42942 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs @@ -101,11 +101,11 @@ namespace Barotrauma.Items.Components { if (TryExtractEventData(extraData, out ServerEventData eventData)) { - msg.Write(eventData.MsgToSend); + msg.WriteString(eventData.MsgToSend); } else { - msg.Write(OutputValue); + msg.WriteString(OutputValue); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Wire.cs index 51a0fa393..59adff519 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Wire.cs @@ -41,8 +41,8 @@ namespace Barotrauma.Items.Components msg.WriteRangedInteger(nodeCount, 0, MaxNodesPerNetworkEvent); for (int i = nodeStartIndex; i < nodeStartIndex + nodeCount; i++) { - msg.Write(nodes[i].X); - msg.Write(nodes[i].Y); + msg.WriteSingle(nodes[i].X); + msg.WriteSingle(nodes[i].Y); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index 32f261856..862478ce6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -62,14 +62,14 @@ namespace Barotrauma throw error("component \"" + components[containerIndex] + "\" is not server serializable"); } msg.WriteRangedInteger(containerIndex, 0, components.Count - 1); - msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); + msg.WriteUInt16(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); itemContainer.Inventory.ServerEventWrite(msg, c); break; case ItemStatusEventData _: - msg.Write(condition); + msg.WriteSingle(condition); break; case AssignCampaignInteractionEventData _: - msg.Write((byte)CampaignInteractionType); + msg.WriteByte((byte)CampaignInteractionType); break; case ApplyStatusEffectEventData applyStatusEffectEventData: { @@ -83,15 +83,15 @@ namespace Barotrauma byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255; msg.WriteRangedInteger((int)actionType, 0, Enum.GetValues(typeof(ActionType)).Length - 1); - msg.Write((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent))); - msg.Write(applyStatusEffectEventData.TargetCharacter?.ID ?? (ushort)0); - msg.Write(targetLimbIndex); - msg.Write(applyStatusEffectEventData.UseTarget?.ID ?? (ushort)0); - msg.Write(worldPosition.HasValue); + msg.WriteByte((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent))); + msg.WriteUInt16(applyStatusEffectEventData.TargetCharacter?.ID ?? (ushort)0); + msg.WriteByte(targetLimbIndex); + msg.WriteUInt16(applyStatusEffectEventData.UseTarget?.ID ?? (ushort)0); + msg.WriteBoolean(worldPosition.HasValue); if (worldPosition.HasValue) { - msg.Write(worldPosition.Value.X); - msg.Write(worldPosition.Value.Y); + msg.WriteSingle(worldPosition.Value.X); + msg.WriteSingle(worldPosition.Value.Y); } } break; @@ -109,16 +109,16 @@ namespace Barotrauma case UpgradeEventData upgradeEventData: var upgrade = upgradeEventData.Upgrade; var upgradeTargets = upgrade.TargetComponents; - msg.Write(upgrade.Identifier); - msg.Write((byte)upgrade.Level); - msg.Write((byte)upgradeTargets.Count); + msg.WriteIdentifier(upgrade.Identifier); + msg.WriteByte((byte)upgrade.Level); + msg.WriteByte((byte)upgradeTargets.Count); foreach (var (_, value) in upgrade.TargetComponents) { - msg.Write((byte)value.Length); + msg.WriteByte((byte)value.Length); foreach (var propertyReference in value) { object originalValue = propertyReference.OriginalValue; - msg.Write((float)(originalValue ?? -1)); + msg.WriteSingle((float)(originalValue ?? -1)); } } break; @@ -189,35 +189,35 @@ namespace Barotrauma { if (GameMain.Server == null) { return; } - msg.Write(Prefab.OriginalName); - msg.Write(Prefab.Identifier); - msg.Write(Description != base.Prefab.Description); + msg.WriteString(Prefab.OriginalName); + msg.WriteIdentifier(Prefab.Identifier); + msg.WriteBoolean(Description != base.Prefab.Description); if (Description != base.Prefab.Description) { - msg.Write(Description); + msg.WriteString(Description); } - msg.Write(entityID); + msg.WriteUInt16(entityID); if (ParentInventory == null || ParentInventory.Owner == null || originalInventoryID == 0) { - msg.Write((ushort)0); + msg.WriteUInt16((ushort)0); - msg.Write(Position.X); - msg.Write(Position.Y); + msg.WriteSingle(Position.X); + msg.WriteSingle(Position.Y); msg.WriteRangedSingle(body == null ? 0.0f : MathUtils.WrapAngleTwoPi(body.Rotation), 0.0f, MathHelper.TwoPi, 8); - msg.Write(Submarine != null ? Submarine.ID : (ushort)0); + msg.WriteUInt16(Submarine != null ? Submarine.ID : (ushort)0); } else { - msg.Write(originalInventoryID); - msg.Write(originalItemContainerIndex); - msg.Write(originalSlotIndex < 0 ? (byte)255 : (byte)originalSlotIndex); + msg.WriteUInt16(originalInventoryID); + msg.WriteByte(originalItemContainerIndex); + msg.WriteByte(originalSlotIndex < 0 ? (byte)255 : (byte)originalSlotIndex); } - msg.Write(body == null ? (byte)0 : (byte)body.BodyType); - msg.Write(SpawnedInCurrentOutpost); - msg.Write(AllowStealing); + msg.WriteByte(body == null ? (byte)0 : (byte)body.BodyType); + msg.WriteBoolean(SpawnedInCurrentOutpost); + msg.WriteBoolean(AllowStealing); msg.WriteRangedInteger(Quality, 0, Items.Components.Quality.MaxQuality); byte teamID = 0; @@ -237,39 +237,39 @@ namespace Barotrauma } } - msg.Write(teamID); + msg.WriteByte(teamID); bool hasIdCard = idCardComponent != null; - msg.Write(hasIdCard); + msg.WriteBoolean(hasIdCard); if (hasIdCard) { - msg.Write(idCardComponent.OwnerName); - msg.Write(idCardComponent.OwnerTags); - msg.Write((byte)Math.Max(0, idCardComponent.OwnerBeardIndex+1)); - msg.Write((byte)Math.Max(0, idCardComponent.OwnerHairIndex+1)); - msg.Write((byte)Math.Max(0, idCardComponent.OwnerMoustacheIndex+1)); - msg.Write((byte)Math.Max(0, idCardComponent.OwnerFaceAttachmentIndex+1)); + msg.WriteString(idCardComponent.OwnerName); + msg.WriteString(idCardComponent.OwnerTags); + msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerBeardIndex+1)); + msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerHairIndex+1)); + msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerMoustacheIndex+1)); + msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerFaceAttachmentIndex+1)); msg.WriteColorR8G8B8(idCardComponent.OwnerHairColor); msg.WriteColorR8G8B8(idCardComponent.OwnerFacialHairColor); msg.WriteColorR8G8B8(idCardComponent.OwnerSkinColor); - msg.Write(idCardComponent.OwnerJobId); - msg.Write((byte)idCardComponent.OwnerSheetIndex.X); - msg.Write((byte)idCardComponent.OwnerSheetIndex.Y); + msg.WriteIdentifier(idCardComponent.OwnerJobId); + msg.WriteByte((byte)idCardComponent.OwnerSheetIndex.X); + msg.WriteByte((byte)idCardComponent.OwnerSheetIndex.Y); } bool tagsChanged = tags.Count != base.Prefab.Tags.Count || !tags.All(t => base.Prefab.Tags.Contains(t)); - msg.Write(tagsChanged); + msg.WriteBoolean(tagsChanged); if (tagsChanged) { IEnumerable splitTags = Tags.Split(',').ToIdentifiers(); - msg.Write(string.Join(',', splitTags.Where(t => !base.Prefab.Tags.Contains(t)))); - msg.Write(string.Join(',', base.Prefab.Tags.Where(t => !splitTags.Contains(t)))); + msg.WriteString(string.Join(',', splitTags.Where(t => !base.Prefab.Tags.Contains(t)))); + msg.WriteString(string.Join(',', base.Prefab.Tags.Where(t => !splitTags.Contains(t)))); } var nameTag = GetComponent(); - msg.Write(nameTag != null); + msg.WriteBoolean(nameTag != null); if (nameTag != null) { - msg.Write(nameTag.WrittenName ?? ""); + msg.WriteString(nameTag.WrittenName ?? ""); } } @@ -342,12 +342,12 @@ namespace Barotrauma public void ServerWritePosition(IWriteMessage msg, Client c) { - msg.Write(ID); + msg.WriteUInt16(ID); IWriteMessage tempBuffer = new WriteOnlyMessage(); body.ServerWrite(tempBuffer); msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes); - msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); + msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); msg.WritePadBits(); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Levels/Level.cs b/Barotrauma/BarotraumaServer/ServerSource/Levels/Level.cs index cdbd0c952..f9bf125de 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Levels/Level.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Levels/Level.cs @@ -32,21 +32,21 @@ namespace Barotrauma { if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed level event: expected {nameof(Level)}.{nameof(IEventData)}"); } - msg.Write((byte)eventData.EventType); + msg.WriteByte((byte)eventData.EventType); switch (eventData) { case SingleLevelWallEventData { Wall: var destructibleWall }: int index = ExtraWalls.IndexOf(destructibleWall); - msg.Write((ushort)(index == -1 ? ushort.MaxValue : index)); + msg.WriteUInt16((ushort)(index == -1 ? ushort.MaxValue : index)); //write health using one byte - msg.Write((byte)MathHelper.Clamp((int)(MathUtils.InverseLerp(0.0f, destructibleWall.MaxHealth, destructibleWall.Damage) * 255.0f), 0, 255)); + msg.WriteByte((byte)MathHelper.Clamp((int)(MathUtils.InverseLerp(0.0f, destructibleWall.MaxHealth, destructibleWall.Damage) * 255.0f), 0, 255)); break; case GlobalLevelWallEventData _: foreach (LevelWall levelWall in ExtraWalls) { if (levelWall.Body.BodyType == BodyType.Static) { continue; } - msg.Write(levelWall.Body.Position.X); - msg.Write(levelWall.Body.Position.Y); + msg.WriteSingle(levelWall.Body.Position.X); + msg.WriteSingle(levelWall.Body.Position.Y); msg.WriteRangedSingle(levelWall.MoveState, 0.0f, MathHelper.TwoPi, 16); } break; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs index 877d0b4b1..671413642 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs @@ -56,7 +56,7 @@ namespace Barotrauma.MapCreatures.Behavior public void ServerWrite(IWriteMessage msg, IEventData eventData) { - msg.Write((byte)eventData.NetworkHeader); + msg.WriteByte((byte)eventData.NetworkHeader); switch (eventData) { @@ -80,51 +80,51 @@ namespace Barotrauma.MapCreatures.Behavior break; } - msg.Write(PowerConsumptionTimer); + msg.WriteSingle(PowerConsumptionTimer); } private void ServerWriteSpawn(IWriteMessage msg) { - msg.Write(Prefab.Identifier); - msg.Write(Offset.X); - msg.Write(Offset.Y); + msg.WriteIdentifier(Prefab.Identifier); + msg.WriteSingle(Offset.X); + msg.WriteSingle(Offset.Y); } private void ServerWriteBranchGrowth(IWriteMessage msg, BallastFloraBranch branch, int parentId = -1) { var (x, y) = branch.Position; - msg.Write(parentId); - msg.Write((int)branch.ID); - msg.Write(branch.IsRootGrowth); + msg.WriteInt32(parentId); + msg.WriteInt32((int)branch.ID); + msg.WriteBoolean(branch.IsRootGrowth); msg.WriteRangedInteger((byte)branch.Type, 0b0000, 0b1111); msg.WriteRangedInteger((byte)branch.Sides, 0b0000, 0b1111); msg.WriteRangedInteger(branch.FlowerConfig.Serialize(), 0, 0xFFF); msg.WriteRangedInteger(branch.LeafConfig.Serialize(), 0, 0xFFF); - msg.Write((ushort)branch.MaxHealth); - msg.Write((int)(x / VineTile.Size)); - msg.Write((int)(y / VineTile.Size)); - msg.Write(branch.ParentBranch == null ? -1 : Branches.IndexOf(branch.ParentBranch)); + msg.WriteUInt16((ushort)branch.MaxHealth); + msg.WriteInt32((int)(x / VineTile.Size)); + msg.WriteInt32((int)(y / VineTile.Size)); + msg.WriteInt32(branch.ParentBranch == null ? -1 : Branches.IndexOf(branch.ParentBranch)); } private void ServerWriteBranchDamage(IWriteMessage msg, BallastFloraBranch branch) { - msg.Write((int)branch.ID); - msg.Write(branch.Health); + msg.WriteInt32((int)branch.ID); + msg.WriteSingle(branch.Health); } private void ServerWriteInfect(IWriteMessage msg, UInt16 itemID, InfectEventData.InfectState infect, BallastFloraBranch infector = null) { - msg.Write(itemID); - msg.Write(infect == InfectEventData.InfectState.Yes); + msg.WriteUInt16(itemID); + msg.WriteBoolean(infect == InfectEventData.InfectState.Yes); if (infect == InfectEventData.InfectState.Yes) { - msg.Write(infector?.ID ?? -1); + msg.WriteInt32(infector?.ID ?? -1); } } private void ServerWriteBranchRemove(IWriteMessage msg, BallastFloraBranch branch) { - msg.Write(branch.ID); + msg.WriteInt32(branch.ID); } public void CreateNetworkMessage(IEventData extraData) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs index 9efc0c125..74ca52bb7 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs @@ -94,8 +94,8 @@ namespace Barotrauma msg.WriteRangedInteger(decals.Count, 0, MaxDecalsPerHull); foreach (Decal decal in decals) { - msg.Write(decal.Prefab.UintIdentifier); - msg.Write((byte)decal.SpriteIndex); + msg.WriteUInt32(decal.Prefab.UintIdentifier); + msg.WriteByte((byte)decal.SpriteIndex); float normalizedXPos = MathHelper.Clamp(MathUtils.InverseLerp(0.0f, rect.Width, decal.CenterPosition.X), 0.0f, 1.0f); float normalizedYPos = MathHelper.Clamp(MathUtils.InverseLerp(-rect.Height, 0.0f, decal.CenterPosition.Y), 0.0f, 1.0f); msg.WriteRangedSingle(normalizedXPos, 0.0f, 1.0f, 8); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Structure.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Structure.cs index 1f6f58360..7a5a03496 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Structure.cs @@ -11,7 +11,7 @@ namespace Barotrauma public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write((byte)Sections.Length); + msg.WriteByte((byte)Sections.Length); for (int i = 0; i < Sections.Length; i++) { msg.WriteRangedSingle(Sections[i].damage / MaxHealth, 0.0f, 1.0f, 8); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Submarine.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Submarine.cs index 9f634bd1d..572ffaab8 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Submarine.cs @@ -7,11 +7,11 @@ namespace Barotrauma { public void ServerWritePosition(IWriteMessage msg, Client c) { - msg.Write(ID); + msg.WriteUInt16(ID); IWriteMessage tempBuffer = new WriteOnlyMessage(); subBody.Body.ServerWrite(tempBuffer); - msg.Write((byte)tempBuffer.LengthBytes); - msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); + msg.WriteByte((byte)tempBuffer.LengthBytes); + msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); msg.WritePadBits(); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/BanList.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/BanList.cs index 3b628dea4..6ba6dea70 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/BanList.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/BanList.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; +#nullable enable +using System; using Barotrauma.IO; using System.Linq; -using System.Net; -using Barotrauma.Steam; +using System.Xml.Linq; +using Barotrauma.Extensions; namespace Barotrauma.Networking { @@ -11,6 +11,8 @@ namespace Barotrauma.Networking { private static UInt32 LastIdentifier = 0; + public bool Expired => ExpirationTime is { } expirationTime && DateTime.Now > expirationTime; + public BannedPlayer( string name, Either addressOrAccountId, string reason, DateTime? expirationTime) { @@ -24,20 +26,33 @@ namespace Barotrauma.Networking partial class BanList { - const string SavePath = "Data/bannedplayers.txt"; + private const string SavePath = "Data/bannedplayers.xml"; + private const string LegacySavePath = "Data/bannedplayers.txt"; partial void InitProjectSpecific() { - if (!File.Exists(SavePath)) { return; } + if (!File.Exists(SavePath)) + { + LoadLegacyBanList(); + } + else + { + LoadBanList(); + } + } + private void LoadLegacyBanList() + { + if (!File.Exists(LegacySavePath)) { return; } + string[] lines; try { - lines = File.ReadAllLines(SavePath); + lines = File.ReadAllLines(LegacySavePath); } catch (Exception e) { - DebugConsole.ThrowError("Failed to open the list of banned players in " + SavePath, e); + DebugConsole.ThrowError($"Failed to open the list of banned players in {LegacySavePath}", e); return; } @@ -70,11 +85,46 @@ namespace Barotrauma.Networking bannedPlayers.Add(new BannedPlayer(name, address, reason, expirationTime)); } } + + Save(); + File.Delete(LegacySavePath); } - public void RemoveExpired() + private void LoadBanList() { - bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value); + XDocument? doc = XMLExtensions.TryLoadXml(SavePath); + + if (doc?.Root is null) { return; } + + static Option loadFromElement(XElement element) + { + var accountId = AccountId.Parse(element.GetAttributeString("accountid", "")); + var address = Address.Parse(element.GetAttributeString("address", "")); + + var name = element.GetAttributeString("name", "")!; + var reason = element.GetAttributeString("reason", "")!; + DateTime? expirationTime = DateTime.FromBinary(unchecked((long)element.GetAttributeUInt64("expirationtime", 0))); + + if (expirationTime < DateTime.Now) { expirationTime = null; } + + if (accountId.IsNone() && address.IsNone()) { return Option.None(); } + + Either addressOrAccountId = accountId.TryUnwrap(out var accId) + ? (Either)accId + : address.TryUnwrap(out var addr) + ? addr + : throw new InvalidCastException(); + + return Option.Some(new BannedPlayer(name, addressOrAccountId, reason, expirationTime)); + } + + bannedPlayers.AddRange(doc.Root.Elements().Select(loadFromElement) + .OfType>().Select(o => o.Value)); + } + + private void RemoveExpired() + { + bannedPlayers.RemoveAll(bp => bp.Expired); } public bool IsBanned(Endpoint endpoint, out string reason) @@ -107,17 +157,7 @@ namespace Barotrauma.Networking public void BanPlayer(string name, Either addressOrAccountId, string reason, TimeSpan? duration) { var existingBan = bannedPlayers.Find(bp => bp.AddressOrAccountId == addressOrAccountId); - if (existingBan != null) - { - if (!duration.HasValue) { return; } - - DebugConsole.Log("Set \"" + name + "\"'s ban duration to " + duration.Value); - existingBan.ExpirationTime = DateTime.Now + duration.Value; - Save(); - return; - } - - System.Diagnostics.Debug.Assert(!name.Contains(',')); + if (existingBan != null) { bannedPlayers.Remove(existingBan); } string logMsg = "Banned " + name; if (!string.IsNullOrEmpty(reason)) { logMsg += ", reason: " + reason; } @@ -132,7 +172,6 @@ namespace Barotrauma.Networking } bannedPlayers.Add(new BannedPlayer(name, addressOrAccountId, reason, expirationTime)); - Save(); } @@ -168,27 +207,32 @@ namespace Barotrauma.Networking GameMain.Server?.ServerSettings?.UpdateFlag(ServerSettings.NetFlags.Properties); - bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value); + RemoveExpired(); - List lines = new List(); - foreach (BannedPlayer banned in bannedPlayers) + static XElement saveToElement(BannedPlayer bannedPlayer) { - string line = banned.Name; - line += "," + (banned.AddressOrAccountId.ToString()); - line += "," + (banned.ExpirationTime.HasValue ? banned.ExpirationTime.Value.ToString() : ""); - if (!string.IsNullOrWhiteSpace(banned.Reason)) { line += "," + banned.Reason; } + XElement retVal = new XElement("ban"); + retVal.SetAttributeValue("name", bannedPlayer.Name); + retVal.SetAttributeValue("reason", bannedPlayer.Reason); + if (bannedPlayer.AddressOrAccountId.TryGet(out AccountId accountId)) + { + retVal.SetAttributeValue("accountid", accountId.StringRepresentation); + } + else if (bannedPlayer.AddressOrAccountId.TryGet(out Address address)) + { + retVal.SetAttributeValue("address", address.StringRepresentation); + } + if (bannedPlayer.ExpirationTime is { } expirationTime) + { + retVal.SetAttributeValue("expirationtime", unchecked((ulong)expirationTime.ToBinary())); + } - lines.Add(line); + return retVal; } - try - { - File.WriteAllLines(SavePath, lines); - } - catch (Exception e) - { - DebugConsole.ThrowError("Saving the list of banned players to " + SavePath + " failed", e); - } + XDocument doc = new XDocument(new XElement("bannedplayers")); + bannedPlayers.Select(saveToElement).ForEach(doc.Root!.Add); + doc.SaveSafe(SavePath); } public void ServerAdminWrite(IWriteMessage outMsg, Client c) @@ -200,12 +244,12 @@ namespace Barotrauma.Networking if (!c.HasPermission(ClientPermissions.Ban)) { - outMsg.Write(false); outMsg.WritePadBits(); + outMsg.WriteBoolean(false); outMsg.WritePadBits(); return; } - outMsg.Write(true); - outMsg.Write(c.Connection == GameMain.Server.OwnerConnection); + outMsg.WriteBoolean(true); + outMsg.WriteBoolean(c.Connection == GameMain.Server.OwnerConnection); outMsg.WritePadBits(); outMsg.WriteVariableUInt32((UInt32)bannedPlayers.Count); @@ -213,29 +257,29 @@ namespace Barotrauma.Networking { BannedPlayer bannedPlayer = bannedPlayers[i]; - outMsg.Write(bannedPlayer.Name); - outMsg.Write(bannedPlayer.UniqueIdentifier); - outMsg.Write(bannedPlayer.ExpirationTime != null); + outMsg.WriteString(bannedPlayer.Name); + outMsg.WriteUInt32(bannedPlayer.UniqueIdentifier); + outMsg.WriteBoolean(bannedPlayer.ExpirationTime != null); outMsg.WritePadBits(); if (bannedPlayer.ExpirationTime != null) { double hoursFromNow = (bannedPlayer.ExpirationTime.Value - DateTime.Now).TotalHours; - outMsg.Write(hoursFromNow); + outMsg.WriteDouble(hoursFromNow); } - outMsg.Write(bannedPlayer.Reason ?? ""); + outMsg.WriteString(bannedPlayer.Reason ?? ""); if (c.Connection == GameMain.Server.OwnerConnection) { if (bannedPlayer.AddressOrAccountId.TryGet(out Address endpoint)) { - outMsg.Write(true); outMsg.WritePadBits(); - outMsg.Write(endpoint.StringRepresentation); + outMsg.WriteBoolean(true); outMsg.WritePadBits(); + outMsg.WriteString(endpoint.StringRepresentation); } else { - outMsg.Write(false); outMsg.WritePadBits(); - outMsg.Write(((SteamId)bannedPlayer.AddressOrAccountId).StringRepresentation); + outMsg.WriteBoolean(false); outMsg.WritePadBits(); + outMsg.WriteString(((SteamId)bannedPlayer.AddressOrAccountId).StringRepresentation); } } } @@ -262,7 +306,7 @@ namespace Barotrauma.Networking for (int i = 0; i < removeCount; i++) { UInt32 id = incMsg.ReadUInt32(); - BannedPlayer bannedPlayer = bannedPlayers.Find(p => p.UniqueIdentifier == id); + BannedPlayer? bannedPlayer = bannedPlayers.Find(p => p.UniqueIdentifier == id); if (bannedPlayer != null) { GameServer.Log(GameServer.ClientLogName(c) + " unbanned " + bannedPlayer.Name + " (" + bannedPlayer.AddressOrAccountId + ")", ServerLog.MessageType.ConsoleUsage); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs index d2a56a792..07748966a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs @@ -202,24 +202,24 @@ namespace Barotrauma.Networking public virtual void ServerWrite(IWriteMessage msg, Client c) { - msg.Write((byte)ServerNetObject.CHAT_MESSAGE); - msg.Write(NetStateID); + msg.WriteByte((byte)ServerNetObject.CHAT_MESSAGE); + msg.WriteUInt16(NetStateID); msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1); - msg.Write((byte)ChangeType); - msg.Write(Text); + msg.WriteByte((byte)ChangeType); + msg.WriteString(Text); - msg.Write(SenderName); - msg.Write(SenderClient != null); + msg.WriteString(SenderName); + msg.WriteBoolean(SenderClient != null); if (SenderClient != null) { - msg.Write(SenderClient.AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : SenderClient.SessionId.ToString()); + msg.WriteString(SenderClient.AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : SenderClient.SessionId.ToString()); } - msg.Write(Sender != null && c.InGame); + msg.WriteBoolean(Sender != null && c.InGame); if (Sender != null && c.InGame) { - msg.Write(Sender.ID); + msg.WriteUInt16(Sender.ID); } - msg.Write(customTextColor != null); + msg.WriteBoolean(customTextColor != null); if (customTextColor != null) { msg.WriteColorR8G8B8A8(customTextColor.Value); @@ -227,7 +227,7 @@ namespace Barotrauma.Networking msg.WritePadBits(); if (Type == ChatMessageType.ServerMessageBoxInGame) { - msg.Write(IconStyle); + msg.WriteString(IconStyle); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs index 63c6ad087..551e32707 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs @@ -8,12 +8,16 @@ namespace Barotrauma.Networking { public bool VoiceEnabled = true; - public UInt16 LastRecvClientListUpdate = 0; + public UInt16 LastRecvClientListUpdate + = NetIdUtils.GetIdOlderThan(GameMain.Server.LastClientListUpdateID); - public UInt16 LastSentServerSettingsUpdate = 0; - public UInt16 LastRecvServerSettingsUpdate = 0; + public UInt16 LastSentServerSettingsUpdate + = NetIdUtils.GetIdOlderThan(GameMain.Server.ServerSettings.LastUpdateIdForFlag[ServerSettings.NetFlags.Properties]); + public UInt16 LastRecvServerSettingsUpdate + = NetIdUtils.GetIdOlderThan(GameMain.Server.ServerSettings.LastUpdateIdForFlag[ServerSettings.NetFlags.Properties]); - public UInt16 LastRecvLobbyUpdate = 0; + public UInt16 LastRecvLobbyUpdate + = NetIdUtils.GetIdOlderThan(GameMain.NetLobbyScreen.LastUpdateID); public UInt16 LastSentChatMsgID = 0; //last msg this client said public UInt16 LastRecvChatMsgID = 0; //last msg this client knows about @@ -21,7 +25,8 @@ namespace Barotrauma.Networking public UInt16 LastSentEntityEventID = 0; public UInt16 LastRecvEntityEventID = 0; - public readonly Dictionary LastRecvCampaignUpdate = new Dictionary(); + public readonly Dictionary LastRecvCampaignUpdate + = new Dictionary(); public UInt16 LastRecvCampaignSave = 0; public (UInt16 saveId, float time) LastCampaignSaveSendTime; @@ -107,8 +112,17 @@ namespace Barotrauma.Networking } } + private List kickVoters; + + public int KickVoteCount + { + get { return kickVoters.Count; } + } + partial void InitProjSpecific() { + kickVoters = new List(); + JobPreferences = new List(); VoipQueue = new VoipQueue(SessionId, true, true); @@ -151,7 +165,22 @@ namespace Barotrauma.Networking { if (string.IsNullOrWhiteSpace(name)) { return false; } - char[] disallowedChars = new char[] { ';', ',', '<', '>', '/', '\\', '[', ']', '"', '?' }; + char[] disallowedChars = + { + //',', //previously disallowed because of the ban list format + + ';', + '<', + '>', + + '/', //disallowed because of server messages using forward slash as a delimiter (TODO: implement escaping) + + '\\', + '[', + ']', + '"', + '?' + }; if (name.Any(c => disallowedChars.Contains(c))) { return false; } foreach (char character in name) @@ -162,19 +191,45 @@ namespace Barotrauma.Networking return true; } - public bool EndpointMatches(Endpoint endPoint) + public bool AddressMatches(Address address) { - return Connection.EndpointMatches(endPoint); + return Connection.Endpoint.Address.Equals(address); } + public void AddKickVote(Client voter) + { + if (voter != null && !kickVoters.Contains(voter)) { kickVoters.Add(voter); } + } + + public void RemoveKickVote(Client voter) + { + kickVoters.Remove(voter); + } + + public bool HasKickVoteFrom(Client voter) + { + return kickVoters.Contains(voter); + } + + public bool HasKickVoteFromSessionId(int id) + { + return kickVoters.Any(k => k.SessionId == id); + } + + public static void UpdateKickVotes(IReadOnlyList connectedClients) + { + foreach (Client client in connectedClients) + { + client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter)); + } + } + + public void SetPermissions(ClientPermissions permissions, IEnumerable permittedConsoleCommands) { this.Permissions = permissions; this.PermittedConsoleCommands.Clear(); - foreach (var command in permittedConsoleCommands) - { - this.PermittedConsoleCommands.Add(command); - } + this.PermittedConsoleCommands.UnionWith(permittedConsoleCommands); } public void GivePermission(ClientPermissions permission) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs index 146ae81c0..0b4937265 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs @@ -32,23 +32,23 @@ namespace Barotrauma if (GameMain.Server is null) { return; } if (!(extraData is SpawnOrRemove entities)) { throw new Exception($"Malformed {nameof(EntitySpawner)} event: expected {nameof(SpawnOrRemove)}"); } - message.Write(entities is RemoveEntity); + message.WriteBoolean(entities is RemoveEntity); if (entities is RemoveEntity) { - message.Write(entities.ID); + message.WriteUInt16(entities.ID); } else { switch (entities.Entity) { case Item item: - message.Write((byte)SpawnableType.Item); + message.WriteByte((byte)SpawnableType.Item); DebugConsole.Log( $"Writing item spawn data {item} (ID: {entities.ID})"); item.WriteSpawnData(message, entities.ID, entities.InventoryID, entities.ItemContainerIndex, entities.SlotIndex); break; case Character character: - message.Write((byte)SpawnableType.Character); + message.WriteByte((byte)SpawnableType.Character); DebugConsole.Log( $"Writing character spawn data: {character} (ID: {entities.ID})"); character.WriteSpawnData(message, entities.ID, restrictMessageSize: true); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs index b79b94e64..eae96b706 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs @@ -52,7 +52,7 @@ namespace Barotrauma.Networking } } - public const int MaxPacketsPerUpdate = 4; + public const int MaxPacketsPerUpdate = 10; public float PacketsPerUpdate { get; set; } = 1.0f; public byte[] Data { get; } @@ -219,27 +219,27 @@ namespace Barotrauma.Networking if (!transfer.Acknowledged) { message = new WriteOnlyMessage(); - message.Write((byte)ServerPacketHeader.FILE_TRANSFER); + message.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER); //if the recipient is the owner of the server (= a client running the server from the main exe) //we don't need to send anything, the client can just read the file directly if (transfer.Connection == GameMain.Server.OwnerConnection) { - message.Write((byte)FileTransferMessageType.TransferOnSameMachine); - message.Write((byte)transfer.ID); - message.Write((byte)transfer.FileType); - message.Write(transfer.FilePath); + message.WriteByte((byte)FileTransferMessageType.TransferOnSameMachine); + message.WriteByte((byte)transfer.ID); + message.WriteByte((byte)transfer.FileType); + message.WriteString(transfer.FilePath); peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); transfer.Status = FileTransferStatus.Finished; } else { - message.Write((byte)FileTransferMessageType.Initiate); - message.Write((byte)transfer.ID); - message.Write((byte)transfer.FileType); + message.WriteByte((byte)FileTransferMessageType.Initiate); + message.WriteByte((byte)transfer.ID); + message.WriteByte((byte)transfer.FileType); //message.Write((ushort)chunkLen); - message.Write(transfer.Data.Length); - message.Write(transfer.FileName); + message.WriteInt32(transfer.Data.Length); + message.WriteString(transfer.FileName); peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); transfer.Status = FileTransferStatus.Sending; @@ -262,13 +262,13 @@ namespace Barotrauma.Networking int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining); message = new WriteOnlyMessage(); - message.Write((byte)ServerPacketHeader.FILE_TRANSFER); - message.Write((byte)FileTransferMessageType.Data); + message.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER); + message.WriteByte((byte)FileTransferMessageType.Data); - message.Write((byte)transfer.ID); - message.Write(transfer.SentOffset); + message.WriteByte((byte)transfer.ID); + message.WriteInt32(transfer.SentOffset); - message.Write((ushort)sendByteCount); + message.WriteUInt16((ushort)sendByteCount); int chunkDestPos = message.BytePosition; message.BitPosition += sendByteCount * 8; message.LengthBits = Math.Max(message.LengthBits, message.BitPosition); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index ce5282fa4..c3bdd614c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -323,7 +323,7 @@ namespace Barotrauma.Networking } else { - newClient.SetPermissions(ClientPermissions.None, new List()); + newClient.SetPermissions(ClientPermissions.None, Enumerable.Empty()); } } @@ -383,7 +383,7 @@ namespace Barotrauma.Networking character.KillDisconnectedTimer += deltaTime; character.SetStun(1.0f); - Client owner = connectedClients.Find(c => (c.Character == null || c.Character == character) && c.EndpointMatches(character.OwnerClientEndpoint)); + Client owner = connectedClients.Find(c => (c.Character == null || c.Character == character) && c.AddressMatches(character.OwnerClientAddress)); if ((OwnerConnection == null || owner?.Connection != OwnerConnection) && character.KillDisconnectedTimer > serverSettings.KillDisconnectedTime) { @@ -675,18 +675,18 @@ namespace Barotrauma.Networking ConnectedClients.ForEach(c => { IWriteMessage pingReq = new WriteOnlyMessage(); - pingReq.Write((byte)ServerPacketHeader.PING_REQUEST); - pingReq.Write((byte)lastPingData.Length); - pingReq.Write(lastPingData, 0, lastPingData.Length); + pingReq.WriteByte((byte)ServerPacketHeader.PING_REQUEST); + pingReq.WriteByte((byte)lastPingData.Length); + pingReq.WriteBytes(lastPingData, 0, lastPingData.Length); serverPeer.Send(pingReq, c.Connection, DeliveryMethod.Unreliable); IWriteMessage pingInf = new WriteOnlyMessage(); - pingInf.Write((byte)ServerPacketHeader.CLIENT_PINGS); - pingInf.Write((byte)ConnectedClients.Count); + pingInf.WriteByte((byte)ServerPacketHeader.CLIENT_PINGS); + pingInf.WriteByte((byte)ConnectedClients.Count); ConnectedClients.ForEach(c2 => { - pingInf.Write(c2.SessionId); - pingInf.Write(c2.Ping); + pingInf.WriteByte(c2.SessionId); + pingInf.WriteUInt16(c2.Ping); }); serverPeer.Send(pingInf, c.Connection, DeliveryMethod.Unreliable); }); @@ -1475,21 +1475,22 @@ namespace Barotrauma.Networking case ClientPermissions.SelectSub: bool isShuttle = inc.ReadBoolean(); inc.ReadPadBits(); - UInt16 subIndex = inc.ReadUInt16(); + string subHash = inc.ReadString(); var subList = GameMain.NetLobbyScreen.GetSubList(); - if (subIndex >= subList.Count) + var sub = GameMain.NetLobbyScreen.GetSubList().FirstOrDefault(s => s.MD5Hash.StringRepresentation == subHash); + if (sub == null) { - DebugConsole.NewMessage($"Client \"{ClientLogName(sender)}\" attempted to select a sub, index out of bounds ({subIndex})", Color.Red); + DebugConsole.NewMessage($"Client \"{ClientLogName(sender)}\" attempted to select a sub, could not find a sub with the MD5 hash \"{subHash}\".", Color.Red); } else { if (isShuttle) { - GameMain.NetLobbyScreen.SelectedShuttle = subList[subIndex]; + GameMain.NetLobbyScreen.SelectedShuttle = sub; } else { - GameMain.NetLobbyScreen.SelectedSub = subList[subIndex]; + GameMain.NetLobbyScreen.SelectedSub = sub; } } break; @@ -1503,11 +1504,11 @@ namespace Barotrauma.Networking const int MaxSaves = 255; var saveInfos = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false); IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO); - msg.Write((byte)Math.Min(saveInfos.Count, MaxSaves)); + msg.WriteByte((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO); + msg.WriteByte((byte)Math.Min(saveInfos.Count, MaxSaves)); for (int i = 0; i < saveInfos.Count && i < MaxSaves; i++) { - msg.Write(saveInfos[i]); + msg.WriteNetSerializableStruct(saveInfos[i]); } serverPeer.Send(msg, sender.Connection, DeliveryMethod.Reliable); } @@ -1610,21 +1611,21 @@ namespace Barotrauma.Networking DebugConsole.NewMessage("Sending initial lobby update", Color.Gray); } - outmsg.Write(c.SessionId); + outmsg.WriteByte(c.SessionId); var subList = GameMain.NetLobbyScreen.GetSubList(); - outmsg.Write((UInt16)subList.Count); + outmsg.WriteUInt16((UInt16)subList.Count); for (int i = 0; i < subList.Count; i++) { var sub = subList[i]; - outmsg.Write(sub.Name); - outmsg.Write(sub.MD5Hash.ToString()); - outmsg.Write((byte)sub.SubmarineClass); - outmsg.Write(sub.RequiredContentPackagesInstalled); + outmsg.WriteString(sub.Name); + outmsg.WriteString(sub.MD5Hash.ToString()); + outmsg.WriteByte((byte)sub.SubmarineClass); + outmsg.WriteBoolean(sub.RequiredContentPackagesInstalled); } - outmsg.Write(GameStarted); - outmsg.Write(serverSettings.AllowSpectating); + outmsg.WriteBoolean(GameStarted); + outmsg.WriteBoolean(serverSettings.AllowSpectating); c.WritePermissions(outmsg); } @@ -1697,23 +1698,23 @@ namespace Barotrauma.Networking } IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME); + outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME); - outmsg.Write((float)NetTime.Now); + outmsg.WriteSingle((float)NetTime.Now); - outmsg.Write((byte)ServerNetObject.SYNC_IDS); - outmsg.Write(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server - outmsg.Write(c.LastSentEntityEventID); + outmsg.WriteByte((byte)ServerNetObject.SYNC_IDS); + outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server + outmsg.WriteUInt16(c.LastSentEntityEventID); if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode) { - outmsg.Write(true); + outmsg.WriteBoolean(true); outmsg.WritePadBits(); campaign.ServerWrite(outmsg, c); } else { - outmsg.Write(false); + outmsg.WriteBoolean(false); outmsg.WritePadBits(); } @@ -1739,8 +1740,8 @@ namespace Barotrauma.Networking } IWriteMessage tempBuffer = new ReadWriteMessage(); - tempBuffer.Write(entity is Item); tempBuffer.WritePadBits(); - tempBuffer.Write(entity is MapEntity me ? me.Prefab.UintIdentifier : (UInt32)0); + tempBuffer.WriteBoolean(entity is Item); tempBuffer.WritePadBits(); + tempBuffer.WriteUInt32(entity is MapEntity me ? me.Prefab.UintIdentifier : (UInt32)0); entityPositionSync.ServerWritePosition(tempBuffer, c); //no more room in this packet @@ -1749,9 +1750,9 @@ namespace Barotrauma.Networking break; } - outmsg.Write((byte)ServerNetObject.ENTITY_POSITION); + outmsg.WriteByte((byte)ServerNetObject.ENTITY_POSITION); outmsg.WritePadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly - outmsg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); + outmsg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); outmsg.WritePadBits(); c.PositionUpdateLastSent[entity] = (float)NetTime.Now; @@ -1759,7 +1760,7 @@ namespace Barotrauma.Networking } positionUpdateBytes = outmsg.LengthBytes - positionUpdateBytes; - outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); + outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); if (outmsg.LengthBytes > MsgConstants.MTU) { @@ -1779,8 +1780,8 @@ namespace Barotrauma.Networking for (int i = 0; i < NetConfig.MaxEventPacketsPerUpdate; i++) { outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME); - outmsg.Write((float)Lidgren.Network.NetTime.Now); + outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME); + outmsg.WriteSingle((float)Lidgren.Network.NetTime.Now); int eventManagerBytes = outmsg.LengthBytes; entityEventManager.Write(c, outmsg, out List sentEvents); @@ -1791,7 +1792,7 @@ namespace Barotrauma.Networking break; } - outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); + outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); if (outmsg.LengthBytes > MsgConstants.MTU) { @@ -1821,10 +1822,10 @@ namespace Barotrauma.Networking bool hasChanged = NetIdUtils.IdMoreRecent(LastClientListUpdateID, c.LastRecvClientListUpdate); if (!hasChanged) { return; } - outmsg.Write((byte)ServerNetObject.CLIENT_LIST); - outmsg.Write(LastClientListUpdateID); + outmsg.WriteByte((byte)ServerNetObject.CLIENT_LIST); + outmsg.WriteUInt16(LastClientListUpdateID); - outmsg.Write((byte)connectedClients.Count); + outmsg.WriteByte((byte)connectedClients.Count); foreach (Client client in connectedClients) { var tempClientData = new TempClient @@ -1850,7 +1851,7 @@ namespace Barotrauma.Networking IsDownloading = FileSender.ActiveTransfers.Any(t => t.Connection == client.Connection) }; - outmsg.Write(tempClientData); + outmsg.WriteNetSerializableStruct(tempClientData); outmsg.WritePadBits(); } } @@ -1860,27 +1861,32 @@ namespace Barotrauma.Networking bool isInitialUpdate = false; IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ServerPacketHeader.UPDATE_LOBBY); + outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_LOBBY); - outmsg.Write((byte)ServerNetObject.SYNC_IDS); + outmsg.WriteByte((byte)ServerNetObject.SYNC_IDS); int settingsBytes = outmsg.LengthBytes; int initialUpdateBytes = 0; + if (ServerSettings.UnsentFlags() != ServerSettings.NetFlags.None) + { + GameMain.NetLobbyScreen.LastUpdateID++; + } + IWriteMessage settingsBuf = null; if (NetIdUtils.IdMoreRecent(GameMain.NetLobbyScreen.LastUpdateID, c.LastRecvLobbyUpdate)) { - outmsg.Write(true); + outmsg.WriteBoolean(true); outmsg.WritePadBits(); - outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID); + outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID); settingsBuf = new ReadWriteMessage(); serverSettings.ServerWrite(settingsBuf, c); - outmsg.Write((UInt16)settingsBuf.LengthBytes); - outmsg.Write(settingsBuf.Buffer, 0, settingsBuf.LengthBytes); + outmsg.WriteUInt16((UInt16)settingsBuf.LengthBytes); + outmsg.WriteBytes(settingsBuf.Buffer, 0, settingsBuf.LengthBytes); - outmsg.Write(c.LastRecvLobbyUpdate < 1); + outmsg.WriteBoolean(c.LastRecvLobbyUpdate < 1); if (c.LastRecvLobbyUpdate < 1) { isInitialUpdate = true; @@ -1888,42 +1894,42 @@ namespace Barotrauma.Networking ClientWriteInitial(c, outmsg); initialUpdateBytes = outmsg.LengthBytes - initialUpdateBytes; } - outmsg.Write(GameMain.NetLobbyScreen.SelectedSub.Name); - outmsg.Write(GameMain.NetLobbyScreen.SelectedSub.MD5Hash.ToString()); - outmsg.Write(IsUsingRespawnShuttle()); + outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.Name); + outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.MD5Hash.ToString()); + outmsg.WriteBoolean(IsUsingRespawnShuttle()); var selectedShuttle = gameStarted && respawnManager != null && respawnManager.UsingShuttle ? respawnManager.RespawnShuttle.Info : GameMain.NetLobbyScreen.SelectedShuttle; - outmsg.Write(selectedShuttle.Name); - outmsg.Write(selectedShuttle.MD5Hash.ToString()); + outmsg.WriteString(selectedShuttle.Name); + outmsg.WriteString(selectedShuttle.MD5Hash.ToString()); - outmsg.Write(serverSettings.AllowSubVoting); - outmsg.Write(serverSettings.AllowModeVoting); + outmsg.WriteBoolean(serverSettings.AllowSubVoting); + outmsg.WriteBoolean(serverSettings.AllowModeVoting); - outmsg.Write(serverSettings.VoiceChatEnabled); + outmsg.WriteBoolean(serverSettings.VoiceChatEnabled); - outmsg.Write(serverSettings.AllowSpectating); + outmsg.WriteBoolean(serverSettings.AllowSpectating); outmsg.WriteRangedInteger((int)serverSettings.TraitorsEnabled, 0, 2); outmsg.WriteRangedInteger((int)GameMain.NetLobbyScreen.MissionType, 0, (int)MissionType.All); - outmsg.Write((byte)GameMain.NetLobbyScreen.SelectedModeIndex); - outmsg.Write(GameMain.NetLobbyScreen.LevelSeed); - outmsg.Write(serverSettings.SelectedLevelDifficulty); + outmsg.WriteByte((byte)GameMain.NetLobbyScreen.SelectedModeIndex); + outmsg.WriteString(GameMain.NetLobbyScreen.LevelSeed); + outmsg.WriteSingle(serverSettings.SelectedLevelDifficulty); - outmsg.Write((byte)serverSettings.BotCount); - outmsg.Write(serverSettings.BotSpawnMode == BotSpawnMode.Fill); + outmsg.WriteByte((byte)serverSettings.BotCount); + outmsg.WriteBoolean(serverSettings.BotSpawnMode == BotSpawnMode.Fill); - outmsg.Write(serverSettings.AutoRestart); + outmsg.WriteBoolean(serverSettings.AutoRestart); if (serverSettings.AutoRestart) { - outmsg.Write(autoRestartTimerRunning ? serverSettings.AutoRestartTimer : 0.0f); + outmsg.WriteSingle(autoRestartTimerRunning ? serverSettings.AutoRestartTimer : 0.0f); } } else { - outmsg.Write(false); + outmsg.WriteBoolean(false); outmsg.WritePadBits(); } settingsBytes = outmsg.LengthBytes - settingsBytes; @@ -1933,18 +1939,18 @@ namespace Barotrauma.Networking if (outmsg.LengthBytes < MsgConstants.MTU - 500 && campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode) { - outmsg.Write(true); + outmsg.WriteBoolean(true); outmsg.WritePadBits(); campaign.ServerWrite(outmsg, c); } else { - outmsg.Write(false); + outmsg.WriteBoolean(false); outmsg.WritePadBits(); } campaignBytes = outmsg.LengthBytes - campaignBytes; - outmsg.Write(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server + outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server int clientListBytes = outmsg.LengthBytes; if (outmsg.LengthBytes < MsgConstants.MTU - 500) @@ -1957,7 +1963,7 @@ namespace Barotrauma.Networking WriteChatMessages(outmsg, c); chatMessageBytes = outmsg.LengthBytes - chatMessageBytes; - outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); + outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); bool messageTooLarge = outmsg.LengthBytes > MsgConstants.MTU; if (messageTooLarge && !isInitialUpdate) @@ -2071,21 +2077,21 @@ namespace Barotrauma.Networking if (connectedClients.Any()) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.QUERY_STARTGAME); + msg.WriteByte((byte)ServerPacketHeader.QUERY_STARTGAME); - msg.Write(selectedSub.Name); - msg.Write(selectedSub.MD5Hash.StringRepresentation); + msg.WriteString(selectedSub.Name); + msg.WriteString(selectedSub.MD5Hash.StringRepresentation); - msg.Write(IsUsingRespawnShuttle()); - msg.Write(selectedShuttle.Name); - msg.Write(selectedShuttle.MD5Hash.StringRepresentation); + msg.WriteBoolean(IsUsingRespawnShuttle()); + msg.WriteString(selectedShuttle.Name); + msg.WriteString(selectedShuttle.MD5Hash.StringRepresentation); var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; - msg.Write(campaign == null ? (byte)0 : campaign.CampaignID); - msg.Write(campaign == null ? (UInt16)0 : campaign.LastSaveID); + msg.WriteByte(campaign == null ? (byte)0 : campaign.CampaignID); + msg.WriteUInt16(campaign == null ? (UInt16)0 : campaign.LastSaveID); foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags))) { - msg.Write(campaign == null ? (UInt16)0 : campaign.GetLastUpdateIdForFlag(flag)); + msg.WriteUInt16(campaign == null ? (UInt16)0 : campaign.GetLastUpdateIdForFlag(flag)); } connectedClients.ForEach(c => c.ReadyToStart = false); @@ -2400,7 +2406,7 @@ namespace Barotrauma.Networking mpCampaign.ClearSavedExperiencePoints(teamClients[i]); } - spawnedCharacter.OwnerClientEndpoint = teamClients[i].Connection.Endpoint; + spawnedCharacter.OwnerClientAddress = teamClients[i].Connection.Endpoint.Address; spawnedCharacter.OwnerClientName = teamClients[i].Name; } @@ -2498,50 +2504,50 @@ namespace Barotrauma.Networking MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.STARTGAME); - msg.Write(seed); - msg.Write(gameSession.GameMode.Preset.Identifier); + msg.WriteByte((byte)ServerPacketHeader.STARTGAME); + msg.WriteInt32(seed); + msg.WriteIdentifier(gameSession.GameMode.Preset.Identifier); bool missionAllowRespawn = missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn); - msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); - msg.Write(serverSettings.AllowDisguises); - msg.Write(serverSettings.AllowRewiring); - msg.Write(serverSettings.AllowFriendlyFire); - msg.Write(serverSettings.LockAllDefaultWires); - msg.Write(serverSettings.AllowRagdollButton); - msg.Write(serverSettings.AllowLinkingWifiToChat); - msg.Write(serverSettings.MaximumMoneyTransferRequest); - msg.Write(IsUsingRespawnShuttle()); - msg.Write((byte)serverSettings.LosMode); - msg.Write(includesFinalize); msg.WritePadBits(); + msg.WriteBoolean(serverSettings.AllowRespawn && missionAllowRespawn); + msg.WriteBoolean(serverSettings.AllowDisguises); + msg.WriteBoolean(serverSettings.AllowRewiring); + msg.WriteBoolean(serverSettings.AllowFriendlyFire); + msg.WriteBoolean(serverSettings.LockAllDefaultWires); + msg.WriteBoolean(serverSettings.AllowRagdollButton); + msg.WriteBoolean(serverSettings.AllowLinkingWifiToChat); + msg.WriteInt32(serverSettings.MaximumMoneyTransferRequest); + msg.WriteBoolean(IsUsingRespawnShuttle()); + msg.WriteByte((byte)serverSettings.LosMode); + msg.WriteBoolean(includesFinalize); msg.WritePadBits(); serverSettings.WriteMonsterEnabled(msg); if (campaign == null) { - msg.Write(levelSeed); - msg.Write(serverSettings.SelectedLevelDifficulty); - msg.Write(gameSession.SubmarineInfo.Name); - msg.Write(gameSession.SubmarineInfo.MD5Hash.StringRepresentation); + msg.WriteString(levelSeed); + msg.WriteSingle(serverSettings.SelectedLevelDifficulty); + msg.WriteString(gameSession.SubmarineInfo.Name); + msg.WriteString(gameSession.SubmarineInfo.MD5Hash.StringRepresentation); var selectedShuttle = gameStarted && respawnManager != null && respawnManager.UsingShuttle ? respawnManager.RespawnShuttle.Info : GameMain.NetLobbyScreen.SelectedShuttle; - msg.Write(selectedShuttle.Name); - msg.Write(selectedShuttle.MD5Hash.StringRepresentation); - msg.Write((byte)GameMain.GameSession.GameMode.Missions.Count()); + msg.WriteString(selectedShuttle.Name); + msg.WriteString(selectedShuttle.MD5Hash.StringRepresentation); + msg.WriteByte((byte)GameMain.GameSession.GameMode.Missions.Count()); foreach (Mission mission in GameMain.GameSession.GameMode.Missions) { - msg.Write(mission.Prefab.UintIdentifier); + msg.WriteUInt32(mission.Prefab.UintIdentifier); } } else { int nextLocationIndex = campaign.Map.Locations.FindIndex(l => l.LevelData == campaign.NextLevel); int nextConnectionIndex = campaign.Map.Connections.FindIndex(c => c.LevelData == campaign.NextLevel); - msg.Write(campaign.CampaignID); - msg.Write(campaign.LastSaveID); - msg.Write(nextLocationIndex); - msg.Write(nextConnectionIndex); - msg.Write(campaign.Map.SelectedLocationIndex); - msg.Write(campaign.MirrorLevel); + msg.WriteByte(campaign.CampaignID); + msg.WriteUInt16(campaign.LastSaveID); + msg.WriteInt32(nextLocationIndex); + msg.WriteInt32(nextConnectionIndex); + msg.WriteInt32(campaign.Map.SelectedLocationIndex); + msg.WriteBoolean(campaign.MirrorLevel); } if (includesFinalize) @@ -2560,7 +2566,7 @@ namespace Barotrauma.Networking private void SendRoundStartFinalize(Client client) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.STARTGAMEFINALIZE); + msg.WriteByte((byte)ServerPacketHeader.STARTGAMEFINALIZE); WriteRoundStartFinalize(msg, client); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } @@ -2569,26 +2575,26 @@ namespace Barotrauma.Networking { //tell the client what content files they should preload var contentToPreload = GameMain.GameSession.EventManager.GetFilesToPreload(); - msg.Write((ushort)contentToPreload.Count()); + msg.WriteUInt16((ushort)contentToPreload.Count()); foreach (ContentFile contentFile in contentToPreload) { - msg.Write(contentFile.Path.Value); + msg.WriteString(contentFile.Path.Value); } - msg.Write(Submarine.MainSub?.Info.EqualityCheckVal ?? 0); - msg.Write((byte)GameMain.GameSession.Missions.Count()); + msg.WriteInt32(Submarine.MainSub?.Info.EqualityCheckVal ?? 0); + msg.WriteByte((byte)GameMain.GameSession.Missions.Count()); foreach (Mission mission in GameMain.GameSession.Missions) { - msg.Write(mission.Prefab.Identifier); + msg.WriteIdentifier(mission.Prefab.Identifier); } foreach (Level.LevelGenStage stage in Enum.GetValues(typeof(Level.LevelGenStage)).OfType().OrderBy(s => s)) { - msg.Write(GameMain.GameSession.Level.EqualityCheckValues[stage]); + msg.WriteInt32(GameMain.GameSession.Level.EqualityCheckValues[stage]); } foreach (Mission mission in GameMain.GameSession.Missions) { mission.ServerWriteInitial(msg, client); } - msg.Write(GameMain.GameSession.CrewManager != null); + msg.WriteBoolean(GameMain.GameSession.CrewManager != null); GameMain.GameSession.CrewManager?.ServerWriteActiveOrders(msg); } @@ -2651,18 +2657,18 @@ namespace Barotrauma.Networking if (connectedClients.Count > 0) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.ENDGAME); - msg.Write((byte)transitionType); - msg.Write(wasSaved); - msg.Write(endMessage); - msg.Write((byte)missions.Count); + msg.WriteByte((byte)ServerPacketHeader.ENDGAME); + msg.WriteByte((byte)transitionType); + msg.WriteBoolean(wasSaved); + msg.WriteString(endMessage); + msg.WriteByte((byte)missions.Count); foreach (Mission mission in missions) { - msg.Write(mission.Completed); + msg.WriteBoolean(mission.Completed); } - msg.Write(GameMain.GameSession?.WinningTeam == null ? (byte)0 : (byte)GameMain.GameSession.WinningTeam); + msg.WriteByte(GameMain.GameSession?.WinningTeam == null ? (byte)0 : (byte)GameMain.GameSession.WinningTeam); - msg.Write((byte)traitorResults.Count); + msg.WriteByte((byte)traitorResults.Count); foreach (var traitorResult in traitorResults) { traitorResult.ServerWrite(msg); @@ -2868,7 +2874,7 @@ namespace Barotrauma.Networking //reset karma to a neutral value, so if/when the ban is revoked the client wont get immediately punished by low karma again previousPlayer.Karma = Math.Max(previousPlayer.Karma, 50.0f); - serverSettings.BanList.BanPlayer(previousPlayer.Name, previousPlayer.Endpoint, reason, duration); + serverSettings.BanList.BanPlayer(previousPlayer.Name, previousPlayer.Address, reason, duration); if (previousPlayer.AccountInfo.AccountId.TryUnwrap(out var accountId)) { serverSettings.BanList.BanPlayer(previousPlayer.Name, accountId, reason, duration); @@ -3252,9 +3258,9 @@ namespace Barotrauma.Networking public void SendCancelTransferMsg(FileSender.FileTransferOut transfer) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.FILE_TRANSFER); - msg.Write((byte)FileTransferMessageType.Cancel); - msg.Write((byte)transfer.ID); + msg.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER); + msg.WriteByte((byte)FileTransferMessageType.Cancel); + msg.WriteByte((byte)transfer.ID); serverPeer.Send(msg, transfer.Connection, DeliveryMethod.ReliableOrdered); } @@ -3290,7 +3296,7 @@ namespace Barotrauma.Networking Client.UpdateKickVotes(connectedClients); var kickVoteEligibleClients = connectedClients.Where(c => (DateTime.Now - c.JoinTime).TotalSeconds > ServerSettings.DisallowKickVoteTime); - int minimumKickVotes = Math.Max(2, (int)(kickVoteEligibleClients.Count() * serverSettings.KickVoteRequiredRatio)); + float minimumKickVotes = Math.Max(2.0f, kickVoteEligibleClients.Count() * serverSettings.KickVoteRequiredRatio); var clientsToKick = connectedClients.FindAll(c => c.Connection != OwnerConnection && !c.HasPermission(ClientPermissions.Kick) && @@ -3299,12 +3305,10 @@ namespace Barotrauma.Networking c.KickVoteCount >= minimumKickVotes); foreach (Client c in clientsToKick) { + //reset the client's kick votes (they can rejoin after their ban expires) + c.ResetVotes(); var previousPlayer = previousPlayers.Find(p => p.MatchesClient(c)); - if (previousPlayer != null) - { - //reset the client's kick votes (they can rejoin after their ban expires) - previousPlayer.KickVoters.Clear(); - } + previousPlayer?.KickVoters.Clear(); SendChatMessage($"ServerMessage.KickedFromServer~[client]={c.Name}", ChatMessageType.Server, null, changeType: PlayerConnectionChangeType.Kicked); KickClient(c, "ServerMessage.KickedByVote"); @@ -3330,10 +3334,10 @@ namespace Barotrauma.Networking if (!recipients.Any()) { return; } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY); - msg.Write((byte)ServerNetObject.VOTE); + msg.WriteByte((byte)ServerPacketHeader.UPDATE_LOBBY); + msg.WriteByte((byte)ServerNetObject.VOTE); Voting.ServerWrite(msg); - msg.Write((byte)ServerNetObject.END_OF_MESSAGE); + msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE); foreach (var c in recipients) { @@ -3427,7 +3431,7 @@ namespace Barotrauma.Networking if (recipient?.Connection == null) { return; } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.PERMISSIONS); + msg.WriteByte((byte)ServerPacketHeader.PERMISSIONS); client.WritePermissions(msg); serverPeer.Send(msg, recipient.Connection, DeliveryMethod.Reliable); } @@ -3462,9 +3466,9 @@ namespace Barotrauma.Networking client.GivenAchievements.Add(achievementIdentifier); IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.ACHIEVEMENT); - msg.Write(achievementIdentifier); - msg.Write(0); + msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT); + msg.WriteIdentifier(achievementIdentifier); + msg.WriteInt32(0); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } @@ -3474,9 +3478,9 @@ namespace Barotrauma.Networking if (client.GivenAchievements.Contains(achievementIdentifier)) { return; } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.ACHIEVEMENT); - msg.Write(achievementIdentifier); - msg.Write(amount); + msg.WriteByte((byte)ServerPacketHeader.ACHIEVEMENT); + msg.WriteIdentifier(achievementIdentifier); + msg.WriteInt32(amount); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } @@ -3485,10 +3489,10 @@ namespace Barotrauma.Networking { if (client == null) { return; } var msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.TRAITOR_MESSAGE); - msg.Write((byte)messageType); - msg.Write(missionIdentifier); - msg.Write(message); + msg.WriteByte((byte)ServerPacketHeader.TRAITOR_MESSAGE); + msg.WriteByte((byte)messageType); + msg.WriteIdentifier(missionIdentifier); + msg.WriteString(message); serverPeer.Send(msg, client.Connection, DeliveryMethod.ReliableOrdered); } @@ -3497,8 +3501,8 @@ namespace Barotrauma.Networking if (!connectedClients.Any()) { return; } IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED); - msg.Write(DebugConsole.CheatsEnabled); + msg.WriteByte((byte)ServerPacketHeader.CHEATS_ENABLED); + msg.WriteBoolean(DebugConsole.CheatsEnabled); msg.WritePadBits(); foreach (Client c in connectedClients) @@ -3515,7 +3519,7 @@ namespace Barotrauma.Networking if (client.Character != null) { client.Character.IsRemotePlayer = false; - client.Character.OwnerClientEndpoint = null; + client.Character.OwnerClientAddress = null; client.Character.OwnerClientName = null; } @@ -3542,7 +3546,7 @@ namespace Barotrauma.Networking newCharacter.Info.Character = newCharacter; } - newCharacter.OwnerClientEndpoint = client.Connection.Endpoint; + newCharacter.OwnerClientAddress = client.Connection.Endpoint.Address; newCharacter.OwnerClientName = client.Name; newCharacter.IsRemotePlayer = true; newCharacter.Enabled = true; @@ -3900,9 +3904,9 @@ namespace Barotrauma.Networking foreach (var client in connectedClients) { IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.MISSION); + msg.WriteByte((byte)ServerPacketHeader.MISSION); int missionIndex = GameMain.GameSession.GetMissionIndex(mission); - msg.Write((byte)(missionIndex == -1 ? 255: missionIndex)); + msg.WriteByte((byte)(missionIndex == -1 ? 255: missionIndex)); mission?.ServerWrite(msg); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } @@ -3963,7 +3967,7 @@ namespace Barotrauma.Networking class PreviousPlayer { public string Name; - public Endpoint Endpoint; + public Address Address; public AccountInfo AccountInfo; public float Karma; public int KarmaKickCount; @@ -3972,14 +3976,14 @@ namespace Barotrauma.Networking public PreviousPlayer(Client c) { Name = c.Name; - Endpoint = c.Connection.Endpoint; + Address = c.Connection.Endpoint.Address; AccountInfo = c.AccountInfo; } public bool MatchesClient(Client c) { if (c.AccountInfo.AccountId.IsSome() && AccountInfo.AccountId.IsSome()) { return c.AccountInfo.AccountId == AccountInfo.AccountId; } - return c.EndpointMatches(Endpoint); + return c.AddressMatches(Address); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs index e587e882e..0bc4f9d87 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -344,15 +344,15 @@ namespace Barotrauma.Networking if (client.NeedsMidRoundSync) { - msg.Write((byte)ServerNetObject.ENTITY_EVENT_INITIAL); - msg.Write(client.UnreceivedEntityEventCount); - msg.Write(client.FirstNewEventID); + msg.WriteByte((byte)ServerNetObject.ENTITY_EVENT_INITIAL); + msg.WriteUInt16(client.UnreceivedEntityEventCount); + msg.WriteUInt16(client.FirstNewEventID); Write(msg, eventsToSync, out sentEvents, client); } else { - msg.Write((byte)ServerNetObject.ENTITY_EVENT); + msg.WriteByte((byte)ServerNetObject.ENTITY_EVENT); Write(msg, eventsToSync, out sentEvents, client); } @@ -499,7 +499,7 @@ namespace Barotrauma.Networking ReadWriteMessage buffer = new ReadWriteMessage(); byte[] temp = msg.ReadBytes(msgLength - 2); - buffer.Write(temp, 0, msgLength - 2); + buffer.WriteBytes(temp, 0, msgLength - 2); buffer.BitPosition = 0; BufferEvent(new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer)); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/OrderChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/OrderChatMessage.cs index 0cb30a078..dbf398cae 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/OrderChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/OrderChatMessage.cs @@ -7,21 +7,21 @@ namespace Barotrauma.Networking { public override void ServerWrite(IWriteMessage msg, Client c) { - msg.Write((byte)ServerNetObject.CHAT_MESSAGE); - msg.Write(NetStateID); + msg.WriteByte((byte)ServerNetObject.CHAT_MESSAGE); + msg.WriteUInt16(NetStateID); msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1); - msg.Write(SenderName); - msg.Write(SenderClient != null); + msg.WriteString(SenderName); + msg.WriteBoolean(SenderClient != null); if (SenderClient != null) { - msg.Write(SenderClient.AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : SenderClient.SessionId.ToString()); + msg.WriteString(SenderClient.AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : SenderClient.SessionId.ToString()); } - msg.Write(Sender != null && c.InGame); + msg.WriteBoolean(Sender != null && c.InGame); if (Sender != null && c.InGame) { - msg.Write(Sender.ID); + msg.WriteUInt16(Sender.ID); } - msg.Write(false); //text color (no custom text colors for order messages) + msg.WriteBoolean(false); //text color (no custom text colors for order messages) msg.WritePadBits(); WriteOrder(msg); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs index 4cea49857..51e8c3749 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Net; using System.Linq; @@ -7,10 +8,10 @@ using Lidgren.Network; namespace Barotrauma.Networking { - class LidgrenServerPeer : ServerPeer + internal sealed class LidgrenServerPeer : ServerPeer { - private NetPeerConfiguration netPeerConfiguration; - private NetServer netServer; + private readonly NetPeerConfiguration netPeerConfiguration; + private NetServer? netServer; private readonly List incomingLidgrenMessages; @@ -20,6 +21,25 @@ namespace Barotrauma.Networking netServer = null; + netPeerConfiguration = new NetPeerConfiguration("barotrauma") + { + AcceptIncomingConnections = true, + AutoExpandMTU = false, + MaximumConnections = NetConfig.MaxPlayers * 2, + EnableUPnP = serverSettings.EnableUPnP, + Port = serverSettings.Port + }; + + netPeerConfiguration.DisableMessageType( + NetIncomingMessageType.DebugMessage + | NetIncomingMessageType.WarningMessage + | NetIncomingMessageType.Receipt + | NetIncomingMessageType.ErrorMessage + | NetIncomingMessageType.Error + | NetIncomingMessageType.UnconnectedData); + + netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval); + connectedClients = new List(); pendingClients = new List(); @@ -32,21 +52,7 @@ namespace Barotrauma.Networking { if (netServer != null) { return; } - netPeerConfiguration = new NetPeerConfiguration("barotrauma") - { - AcceptIncomingConnections = true, - AutoExpandMTU = false, - MaximumConnections = NetConfig.MaxPlayers * 2, - EnableUPnP = serverSettings.EnableUPnP, - Port = serverSettings.Port - }; - - netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | - NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt | - NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error | - NetIncomingMessageType.UnconnectedData); - - netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval); + incomingLidgrenMessages.Clear(); netServer = new NetServer(netPeerConfiguration); @@ -62,7 +68,7 @@ namespace Barotrauma.Networking } } - public override void Close(string msg = null) + public override void Close(string? msg = null) { if (netServer == null) { return; } @@ -90,7 +96,9 @@ namespace Barotrauma.Networking public override void Update(float deltaTime) { - if (netServer == null) { return; } + if (netServer is null) { return; } + + ToolBox.ThrowIfNull(incomingLidgrenMessages); if (OnOwnerDetermined != null && OwnerConnection != null) { @@ -99,7 +107,7 @@ namespace Barotrauma.Networking } netServer.ReadMessages(incomingLidgrenMessages); - + //process incoming connections first foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval)) { @@ -126,7 +134,7 @@ namespace Barotrauma.Networking catch (Exception e) { string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("LidgrenServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce($"LidgrenServerPeer.Update:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #else @@ -138,7 +146,8 @@ namespace Barotrauma.Networking { PendingClient pendingClient = pendingClients[i]; - var connection = pendingClient.Connection as LidgrenConnection; + LidgrenConnection connection = (LidgrenConnection)pendingClient.Connection; + if (connection.NetConnection.Status == NetConnectionStatus.InitiatedConnect || connection.NetConnection.Status == NetConnectionStatus.ReceivedInitiation || connection.NetConnection.Status == NetConnectionStatus.RespondedAwaitingApproval || @@ -146,6 +155,7 @@ namespace Barotrauma.Networking { continue; } + UpdatePendingClient(pendingClient); if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; } } @@ -155,7 +165,9 @@ namespace Barotrauma.Networking private void InitUPnP() { - if (netServer == null) { return; } + if (netServer is null) { return; } + + ToolBox.ThrowIfNull(netPeerConfiguration); netServer.UPnP.ForwardPort(netPeerConfiguration.Port, "barotrauma"); #if USE_STEAM @@ -178,7 +190,7 @@ namespace Barotrauma.Networking private void HandleConnection(NetIncomingMessage inc) { if (netServer == null) { return; } - + if (connectedClients.Count >= serverSettings.MaxPlayers) { inc.SenderConnection.Deny(DisconnectReason.ServerFull.ToString()); @@ -188,13 +200,13 @@ namespace Barotrauma.Networking if (serverSettings.BanList.IsBanned(new LidgrenEndpoint(inc.SenderConnection.RemoteEndPoint), out string banReason)) { //IP banned: deny immediately - inc.SenderConnection.Deny(DisconnectReason.Banned.ToString() + "/ " + banReason); + inc.SenderConnection.Deny($"{DisconnectReason.Banned}/ {banReason}"); return; } - PendingClient pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection); + PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection); - if (pendingClient == null) + if (pendingClient is null) { pendingClient = new PendingClient(new LidgrenConnection(inc.SenderConnection)); pendingClients.Add(pendingClient); @@ -203,51 +215,52 @@ namespace Barotrauma.Networking inc.SenderConnection.Approve(); } - private void HandleDataMessage(NetIncomingMessage inc) + private void HandleDataMessage(NetIncomingMessage lidgrenMsg) { if (netServer == null) { return; } - PendingClient pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection); + PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == lidgrenMsg.SenderConnection); - PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); + IReadMessage inc = lidgrenMsg.ToReadMessage(); - if (packetHeader.IsConnectionInitializationStep() && pendingClient != null) + var (_, packetHeader, initialization) = INetSerializableStruct.Read(inc); + + if (packetHeader.IsConnectionInitializationStep() && pendingClient != null && initialization.HasValue) { - ReadConnectionInitializationStep(pendingClient, new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false)); + ReadConnectionInitializationStep(pendingClient, inc, initialization.Value); } else if (!packetHeader.IsConnectionInitializationStep()) { - LidgrenConnection conn = connectedClients.Find(c => (c is LidgrenConnection l) && l.NetConnection == inc.SenderConnection) as LidgrenConnection; - if (conn == null) + if (!(connectedClients.Find(c => c is LidgrenConnection l && l.NetConnection == lidgrenMsg.SenderConnection) is LidgrenConnection conn)) { if (pendingClient != null) { RemovePendingClient(pendingClient, DisconnectReason.AuthenticationRequired, "Received data message from unauthenticated client"); } - else if (inc.SenderConnection.Status != NetConnectionStatus.Disconnected && - inc.SenderConnection.Status != NetConnectionStatus.Disconnecting) + else if (lidgrenMsg.SenderConnection.Status != NetConnectionStatus.Disconnected && + lidgrenMsg.SenderConnection.Status != NetConnectionStatus.Disconnecting) { - inc.SenderConnection.Disconnect(DisconnectReason.AuthenticationRequired.ToString() + "/ Received data message from unauthenticated client"); + lidgrenMsg.SenderConnection.Disconnect($"{DisconnectReason.AuthenticationRequired}/ Received data message from unauthenticated client"); } + return; } + if (pendingClient != null) { pendingClients.Remove(pendingClient); } + if (serverSettings.BanList.IsBanned(conn.Endpoint, out string banReason) || (conn.AccountInfo.AccountId.TryUnwrap(out var accountId) && serverSettings.BanList.IsBanned(accountId, out banReason)) || conn.AccountInfo.OtherMatchingIds.Any(id => serverSettings.BanList.IsBanned(id, out banReason))) { - Disconnect(conn, DisconnectReason.Banned.ToString() + "/ " + banReason); + Disconnect(conn, $"{DisconnectReason.Banned}/ {banReason}"); return; } - UInt16 length = inc.ReadUInt16(); - //DebugConsole.NewMessage(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte + " " + length); - - IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, conn); - OnMessageReceived?.Invoke(conn, msg); + var packet = INetSerializableStruct.Read(inc); + OnMessageReceived?.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn)); } } - + private void HandleStatusChanged(NetIncomingMessage inc) { if (netServer == null) { return; } @@ -255,30 +268,30 @@ namespace Barotrauma.Networking switch (inc.SenderConnection.Status) { case NetConnectionStatus.Disconnected: - string disconnectMsg; - LidgrenConnection conn = connectedClients.Select(c => c as LidgrenConnection).FirstOrDefault(c => c.NetConnection == inc.SenderConnection); + LidgrenConnection? conn = connectedClients.Cast().FirstOrDefault(c => c.NetConnection == inc.SenderConnection); if (conn != null) { if (conn == OwnerConnection) { DebugConsole.NewMessage("Owner disconnected: closing the server..."); GameServer.Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage); - Close(DisconnectReason.ServerShutdown.ToString() + "/ Owner disconnected"); + Close($"{DisconnectReason.ServerShutdown}/ Owner disconnected"); } else { - disconnectMsg = $"ServerMessage.HasDisconnected~[client]={GameMain.Server.ConnectedClients.First(c => c.Connection == conn).Name}"; - Disconnect(conn, disconnectMsg); +#warning TODO: kill off disconnect in layer 1 + Disconnect(conn, $"ServerMessage.HasDisconnected~[client]={GameMain.Server.ConnectedClients.First(c => c.Connection == conn).Name}"); } } else { - PendingClient pendingClient = pendingClients.Find(c => (c.Connection is LidgrenConnection l) && l.NetConnection == inc.SenderConnection); + PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection); if (pendingClient != null) { RemovePendingClient(pendingClient, DisconnectReason.Unknown, $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}"); } } + break; } } @@ -292,25 +305,23 @@ namespace Barotrauma.Networking { if (netServer == null) { return; } - PendingClient pendingClient = pendingClients.Find(c => c.AccountInfo.AccountId is Some { Value: SteamId id } && id.Value == steamId); - DebugConsole.Log(steamId + " validation: " + status+", "+(pendingClient!=null)); - - if (pendingClient == null) + PendingClient? pendingClient = pendingClients.Find(c => c.AccountInfo.AccountId is Some { Value: SteamId id } && id.Value == steamId); + DebugConsole.Log($"{steamId} validation: {status}, {(pendingClient != null)}"); + + if (pendingClient is null) { - if (status != Steamworks.AuthResponse.OK) + if (status == Steamworks.AuthResponse.OK) { return; } + + if (connectedClients.Find(c => c.AccountInfo.AccountId is Some { Value: SteamId id } && id.Value == steamId) is LidgrenConnection connection) { - LidgrenConnection connection = connectedClients.Find(c => c.AccountInfo.AccountId is Some { Value: SteamId id } && id.Value == steamId) as LidgrenConnection; - if (connection != null) - { - Disconnect(connection, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString()); - } + Disconnect(connection, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication status changed: {status}"); } + return; } - LidgrenConnection pendingConnection = pendingClient.Connection as LidgrenConnection; - string banReason; - if (serverSettings.BanList.IsBanned(pendingConnection.Endpoint, out banReason) + LidgrenConnection pendingConnection = (LidgrenConnection)pendingClient.Connection; + if (serverSettings.BanList.IsBanned(pendingConnection.Endpoint, out string banReason) || serverSettings.BanList.IsBanned(new SteamId(steamId), out banReason) || serverSettings.BanList.IsBanned(new SteamId(ownerId), out banReason)) { @@ -326,8 +337,7 @@ namespace Barotrauma.Networking } else { - RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "Steam authentication failed: " + status.ToString()); - return; + RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, $"Steam authentication failed: {status}"); } } @@ -335,86 +345,62 @@ namespace Barotrauma.Networking { if (netServer == null) { return; } - if (!(conn is LidgrenConnection lidgrenConn)) return; - if (!connectedClients.Contains(lidgrenConn)) + if (!connectedClients.Contains(conn)) { - DebugConsole.ThrowError("Tried to send message to unauthenticated connection: " + lidgrenConn.Endpoint.StringRepresentation); + DebugConsole.ThrowError($"Tried to send message to unauthenticated connection: {conn.Endpoint.StringRepresentation}"); return; } - NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable; - switch (deliveryMethod) - { - case DeliveryMethod.Unreliable: - lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable; - break; - case DeliveryMethod.Reliable: - lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered; - break; - case DeliveryMethod.ReliableOrdered: - lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered; - break; - } + byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _); #if DEBUG + ToolBox.ThrowIfNull(netPeerConfiguration); netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Server.SimulatedDuplicatesChance; netPeerConfiguration.SimulatedMinimumLatency = GameMain.Server.SimulatedMinimumLatency; netPeerConfiguration.SimulatedRandomLatency = GameMain.Server.SimulatedRandomLatency; netPeerConfiguration.SimulatedLoss = GameMain.Server.SimulatedLoss; #endif - NetOutgoingMessage lidgrenMsg = netServer.CreateMessage(); - byte[] msgData = new byte[msg.LengthBytes]; - msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); - lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None)); - lidgrenMsg.Write((UInt16)length); - lidgrenMsg.Write(msgData, 0, length); - - NetSendResult result = netServer.SendMessage(lidgrenMsg, lidgrenConn.NetConnection, lidgrenDeliveryMethod); - if (result != NetSendResult.Sent && result != NetSendResult.Queued) + var headers = new PeerPacketHeaders { - DebugConsole.NewMessage("Failed to send message to "+conn.Endpoint.StringRepresentation+": " + result.ToString(), Microsoft.Xna.Framework.Color.Yellow); - } + DeliveryMethod = deliveryMethod, + PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None, + Initialization = null + }; + var body = new PeerPacketMessage + { + Buffer = bufAux + }; + SendMsgInternal(conn, headers, body); } - - public override void Disconnect(NetworkConnection conn,string msg=null) + + public override void Disconnect(NetworkConnection conn, string? msg = null) { if (netServer == null) { return; } if (!(conn is LidgrenConnection lidgrenConn)) { return; } + if (connectedClients.Contains(lidgrenConn)) { lidgrenConn.Status = NetworkConnectionStatus.Disconnected; connectedClients.Remove(lidgrenConn); OnDisconnect?.Invoke(conn, msg); - if (conn.AccountInfo.AccountId is Some { Value: SteamId steamId }) { Steam.SteamManager.StopAuthSession(steamId); } + if (conn.AccountInfo.AccountId is Some { Value: SteamId steamId }) { SteamManager.StopAuthSession(steamId); } } + lidgrenConn.NetConnection.Disconnect(msg ?? "Disconnected"); } - protected override void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg) + protected override void SendMsgInternal(NetworkConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body) { - LidgrenConnection lidgrenConn = conn as LidgrenConnection; - NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable; - switch (deliveryMethod) - { - case DeliveryMethod.Unreliable: - lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable; - break; - case DeliveryMethod.Reliable: - lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered; - break; - case DeliveryMethod.ReliableOrdered: - lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered; - break; - } + IWriteMessage msgToSend = new WriteOnlyMessage(); + msgToSend.WriteNetSerializableStruct(headers); + body?.Write(msgToSend); - NetOutgoingMessage lidgrenMsg = netServer.CreateMessage(); - lidgrenMsg.Write(msg.Buffer, 0, msg.LengthBytes); - NetSendResult result = netServer.SendMessage(lidgrenMsg, lidgrenConn.NetConnection, lidgrenDeliveryMethod); + NetSendResult result = ForwardToLidgren(msgToSend, conn, headers.DeliveryMethod); if (result != NetSendResult.Sent && result != NetSendResult.Queued) { - DebugConsole.NewMessage("Failed to send message to " + conn.Endpoint.StringRepresentation + ": " + result.ToString(), Microsoft.Xna.Framework.Color.Yellow); + DebugConsole.NewMessage($"Failed to send message to {conn.Endpoint}: {result}", Microsoft.Xna.Framework.Color.Yellow); } } @@ -430,7 +416,7 @@ namespace Barotrauma.Networking } } - protected override void ProcessAuthTicket(string name, Option ownKey, Option steamId, PendingClient pendingClient, byte[] ticket) + protected override void ProcessAuthTicket(ClientSteamTicketAndVersionPacket packet, PendingClient pendingClient) { if (pendingClient.AccountInfo.AccountId.IsNone()) { @@ -438,19 +424,19 @@ namespace Barotrauma.Networking #if DEBUG requireSteamAuth = false; #endif + bool hasSteamAuth = packet.SteamAuthTicket.TryUnwrap(out var ticket); //steam auth cannot be done (SteamManager not initialized or no ticket given), //but it's not required either -> let the client join without auth - if ((!SteamManager.IsInitialized || (ticket?.Length ?? 0) == 0) - && !requireSteamAuth) + if ((!SteamManager.IsInitialized || !hasSteamAuth) && !requireSteamAuth) { - pendingClient.Name = name; - pendingClient.OwnerKey = ownKey; + pendingClient.Name = packet.Name; + pendingClient.OwnerKey = packet.OwnerKey; pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder; } else { - if (!steamId.TryUnwrap(out var id)) + if (!packet.SteamId.TryUnwrap(out var id) || !(id is SteamId steamId)) { if (requireSteamAuth) { @@ -460,36 +446,42 @@ namespace Barotrauma.Networking } else { - Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, id); + Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId); if (authSessionStartState != Steamworks.BeginAuthResult.OK) { if (requireSteamAuth) { - RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "Steam auth session failed to start: " + authSessionStartState.ToString()); - return; + RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, $"Steam auth session failed to start: {authSessionStartState}"); } else { - steamId = Option.None(); + packet.SteamId = Option.None(); pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder; } } } - pendingClient.Connection.SetAccountInfo(new AccountInfo(steamId.Select(uid => (AccountId)uid))); - pendingClient.Name = name; - pendingClient.OwnerKey = ownKey; + pendingClient.Connection.SetAccountInfo(new AccountInfo(packet.SteamId.Select(uid => (AccountId)uid))); + pendingClient.Name = packet.Name; + pendingClient.OwnerKey = packet.OwnerKey; pendingClient.AuthSessionStarted = true; } } else { - if (pendingClient.AccountInfo.AccountId != steamId.Select(uid => (AccountId)uid)) + if (pendingClient.AccountInfo.AccountId != packet.SteamId.Select(uid => (AccountId)uid)) { RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "SteamID mismatch"); - return; } } } + + private NetSendResult ForwardToLidgren(IWriteMessage msg, NetworkConnection connection, DeliveryMethod deliveryMethod) + { + ToolBox.ThrowIfNull(netServer); + + LidgrenConnection conn = (LidgrenConnection)connection; + return netServer.SendMessage(msg.ToLidgren(netServer), conn.NetConnection, deliveryMethod.ToLidgren()); + } } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs index f70f77724..a00aa7a17 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs @@ -1,39 +1,41 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Barotrauma.Extensions; namespace Barotrauma.Networking { - abstract class ServerPeer + internal abstract class ServerPeer { public delegate void MessageCallback(NetworkConnection connection, IReadMessage message); - public delegate void DisconnectCallback(NetworkConnection connection, string reason); - public delegate void InitializationCompleteCallback(NetworkConnection connection, string clientName); + + public delegate void DisconnectCallback(NetworkConnection connection, string? reason); + + public delegate void InitializationCompleteCallback(NetworkConnection connection, string? clientName); + public delegate void ShutdownCallback(); + public delegate void OwnerDeterminedCallback(NetworkConnection connection); - public MessageCallback OnMessageReceived; - public DisconnectCallback OnDisconnect; - public InitializationCompleteCallback OnInitializationComplete; - public ShutdownCallback OnShutdown; - public OwnerDeterminedCallback OnOwnerDetermined; - - protected Option ownerKey; - - public NetworkConnection OwnerConnection { get; protected set; } + public MessageCallback? OnMessageReceived; + public DisconnectCallback? OnDisconnect; + public InitializationCompleteCallback? OnInitializationComplete; + public ShutdownCallback? OnShutdown; + public OwnerDeterminedCallback? OnOwnerDetermined; public abstract void InitializeSteamServerCallbacks(); public abstract void Start(); - public abstract void Close(string msg = null); + public abstract void Close(string? msg = null); public abstract void Update(float deltaTime); - protected class PendingClient + protected sealed class PendingClient { - public string Name; + public string? Name; public Option OwnerKey; - public NetworkConnection Connection; + public readonly NetworkConnection Connection; public ConnectionInitialization InitializationStep; public double UpdateTime; public double TimeOut; @@ -60,76 +62,69 @@ namespace Barotrauma.Networking TimeOut = NetworkConnection.TimeoutThreshold; } } - protected List connectedClients; - protected List pendingClients; - protected ServerSettings serverSettings; + protected List connectedClients = null!; + protected List pendingClients = null!; + protected ServerSettings serverSettings = null!; + protected Option ownerKey = null!; + protected NetworkConnection? OwnerConnection; - protected void ReadConnectionInitializationStep(PendingClient pendingClient, IReadMessage inc) + protected void ReadConnectionInitializationStep(PendingClient pendingClient, IReadMessage inc, ConnectionInitialization initializationStep) { pendingClient.TimeOut = NetworkConnection.TimeoutThreshold; - ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte(); - - if (pendingClient.InitializationStep != initializationStep) return; + if (pendingClient.InitializationStep != initializationStep) { return; } pendingClient.UpdateTime = Timing.TotalTime + Timing.Step; switch (initializationStep) { case ConnectionInitialization.SteamTicketAndVersion: - string name = Client.SanitizeName(inc.ReadString()); - int ownerKey = inc.ReadInt32(); - UInt64 steamIdVal = inc.ReadUInt64(); - Option steamId = steamIdVal != 0 - ? Option.Some(new SteamId(steamIdVal)) - : Option.None(); - UInt16 ticketLength = inc.ReadUInt16(); - byte[] ticketBytes = inc.ReadBytes(ticketLength); + var authPacket = INetSerializableStruct.Read(inc); - if (!Client.IsValidName(name, serverSettings)) + if (!Client.IsValidName(authPacket.Name, serverSettings)) { RemovePendingClient(pendingClient, DisconnectReason.InvalidName, ""); return; } - string version = inc.ReadString(); - bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false; + bool isCompatibleVersion = NetworkMember.IsCompatible(authPacket.GameVersion, GameMain.Version.ToString()) ?? false; if (!isCompatibleVersion) { RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion, - $"DisconnectMessage.InvalidVersion~[version]={GameMain.Version}~[clientversion]={version}"); + $"DisconnectMessage.InvalidVersion~[version]={GameMain.Version}~[clientversion]={authPacket.GameVersion}"); - GameServer.Log($"{name} ({steamId}) couldn't join the server (incompatible game version)", ServerLog.MessageType.Error); - DebugConsole.NewMessage($"{name} ({steamId}) couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red); + GameServer.Log($"{authPacket.Name} ({authPacket.SteamId}) couldn't join the server (incompatible game version)", ServerLog.MessageType.Error); + DebugConsole.NewMessage($"{authPacket.Name} ({authPacket.SteamId}) couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red); return; } - LanguageIdentifier language = inc.ReadIdentifier().ToLanguageIdentifier(); - pendingClient.Connection.Language = language; + pendingClient.Connection.Language = authPacket.Language.ToLanguageIdentifier(); - Client nameTaken = GameMain.Server.ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), name.ToLower())); + Client nameTaken = GameMain.Server.ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), authPacket.Name.ToLower())); if (nameTaken != null) { RemovePendingClient(pendingClient, DisconnectReason.NameTaken, ""); - GameServer.Log($"{name} ({steamId}) couldn't join the server (name too similar to the name of the client \"" + nameTaken.Name + "\").", ServerLog.MessageType.Error); + GameServer.Log($"{authPacket.Name} ({authPacket.SteamId}) couldn't join the server (name too similar to the name of the client \"" + nameTaken.Name + "\").", ServerLog.MessageType.Error); return; } if (!pendingClient.AuthSessionStarted) { - ProcessAuthTicket(name, ownerKey != 0 ? Option.Some(ownerKey) : Option.None(), steamId, pendingClient, ticketBytes); + ProcessAuthTicket(authPacket, pendingClient); } + break; case ConnectionInitialization.Password: - int pwLength = inc.ReadByte(); - byte[] incPassword = inc.ReadBytes(pwLength); - if (pendingClient.PasswordSalt == null) + var passwordPacket = INetSerializableStruct.Read(inc); + + if (pendingClient.PasswordSalt is null) { DebugConsole.ThrowError("Received password message from client without salt"); return; } - if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value)) + + if (serverSettings.IsPasswordCorrect(passwordPacket.Password, pendingClient.PasswordSalt.Value)) { pendingClient.InitializationStep = ConnectionInitialization.ContentPackageOrder; } @@ -138,13 +133,13 @@ namespace Barotrauma.Networking pendingClient.Retries++; if (serverSettings.BanAfterWrongPassword && pendingClient.Retries > serverSettings.MaxPasswordRetriesBeforeBan) { - string banMsg = "Failed to enter correct password too many times"; + const string banMsg = "Failed to enter correct password too many times"; BanPendingClient(pendingClient, banMsg, null); - RemovePendingClient(pendingClient, DisconnectReason.Banned, banMsg); return; } } + pendingClient.UpdateTime = Timing.TotalTime; break; case ConnectionInitialization.ContentPackageOrder: @@ -154,25 +149,25 @@ namespace Barotrauma.Networking } } - protected abstract void ProcessAuthTicket(string name, Option ownKey, Option steamId, PendingClient pendingClient, byte[] ticket); + protected abstract void ProcessAuthTicket(ClientSteamTicketAndVersionPacket packet, PendingClient pendingClient); protected void BanPendingClient(PendingClient pendingClient, string banReason, TimeSpan? duration) { void banAccountId(AccountId accountId) { - serverSettings.BanList.BanPlayer(pendingClient.Name, accountId, banReason, duration); + serverSettings.BanList.BanPlayer(pendingClient.Name ?? "Player", accountId, banReason, duration); } - + if (pendingClient.AccountInfo.AccountId.TryUnwrap(out var id)) { banAccountId(id); } + pendingClient.AccountInfo.OtherMatchingIds.ForEach(banAccountId); - serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.Connection.Endpoint, banReason, duration); + serverSettings.BanList.BanPlayer(pendingClient.Name ?? "Player", pendingClient.Connection.Endpoint, banReason, duration); } - - protected bool IsPendingClientBanned(PendingClient pendingClient, out string banReason) + + protected bool IsPendingClientBanned(PendingClient pendingClient, out string? banReason) { - bool isAccountIdBanned(AccountId accountId, out string banReason) + bool isAccountIdBanned(AccountId accountId, out string? banReason) { - banReason = default; return serverSettings.BanList.IsBanned(accountId, out banReason); } @@ -182,16 +177,18 @@ namespace Barotrauma.Networking foreach (var otherId in pendingClient.AccountInfo.OtherMatchingIds) { if (isBanned) { break; } + isBanned |= isAccountIdBanned(otherId, out banReason); } + return isBanned; } - protected abstract void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg); + protected abstract void SendMsgInternal(NetworkConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body); protected void UpdatePendingClient(PendingClient pendingClient) { - if (IsPendingClientBanned(pendingClient, out string banReason)) + if (IsPendingClientBanned(pendingClient, out string? banReason)) { RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason); return; @@ -220,52 +217,61 @@ namespace Barotrauma.Networking } if (Timing.TotalTime < pendingClient.UpdateTime) { return; } + pendingClient.UpdateTime = Timing.TotalTime + 1.0; - IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep | - PacketHeader.IsServerMessage)); - outMsg.Write((byte)pendingClient.InitializationStep); + PeerPacketHeaders headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage, + Initialization = pendingClient.InitializationStep + }; + + INetSerializableStruct? structToSend = null; + switch (pendingClient.InitializationStep) { case ConnectionInitialization.ContentPackageOrder: - var mpContentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).ToList(); - outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count); - for (int i = 0; i < mpContentPackages.Count; i++) + + DateTime timeNow = DateTime.UtcNow; + structToSend = new ServerPeerContentPackageOrderPacket { - outMsg.Write(mpContentPackages[i].Name); - byte[] hashBytes = mpContentPackages[i].Hash.ByteRepresentation; - outMsg.WriteVariableUInt32((UInt32)hashBytes.Length); - outMsg.Write(hashBytes, 0, hashBytes.Length); - outMsg.Write(mpContentPackages[i].SteamWorkshopId); - UInt32 installTimeDiffSeconds = (UInt32)((mpContentPackages[i].InstallTime ?? DateTime.UtcNow) - DateTime.UtcNow).TotalSeconds; - outMsg.Write(installTimeDiffSeconds); - } + ServerName = GameMain.Server.ServerName, + ContentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent) + .Select(contentPackage => new ServerContentPackage(contentPackage, timeNow)) + .ToImmutableArray() + }; + break; case ConnectionInitialization.Password: - outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits(); - if (pendingClient.PasswordSalt == null) + structToSend = new ServerPeerPasswordPacket { - pendingClient.PasswordSalt = Lidgren.Network.CryptoRandom.Instance.Next(); - outMsg.Write(pendingClient.PasswordSalt.Value); - } - else + Salt = GetSalt(pendingClient), + RetriesLeft = Option.Some(pendingClient.Retries) + }; + + static Option GetSalt(PendingClient client) { - outMsg.Write(pendingClient.Retries); + if (client.PasswordSalt is { } salt) { return Option.Some(salt); } + + salt = Lidgren.Network.CryptoRandom.Instance.Next(); + client.PasswordSalt = salt; + return Option.Some(salt); } + break; } - SendMsgInternal(pendingClient.Connection, DeliveryMethod.Reliable, outMsg); + SendMsgInternal(pendingClient.Connection, headers, structToSend); } protected virtual void CheckOwnership(PendingClient pendingClient) { } - protected void RemovePendingClient(PendingClient pendingClient, DisconnectReason reason, string msg) + protected void RemovePendingClient(PendingClient pendingClient, DisconnectReason reason, string? msg) { if (pendingClients.Contains(pendingClient)) { - Disconnect(pendingClient.Connection, reason + "/" + msg); + Disconnect(pendingClient.Connection, $"{reason}/{msg}"); pendingClients.Remove(pendingClient); @@ -279,6 +285,6 @@ namespace Barotrauma.Networking } public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true); - public abstract void Disconnect(NetworkConnection conn, string msg = null); + public abstract void Disconnect(NetworkConnection conn, string? msg = null); } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs index e33afdbb9..70976272e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs @@ -1,21 +1,20 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; namespace Barotrauma.Networking { - class SteamP2PServerPeer : ServerPeer + internal sealed class SteamP2PServerPeer : ServerPeer { private bool started; private readonly SteamId ownerSteamId; private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0)); - - private SteamId ReadSteamId(IReadMessage inc) - => new SteamId(inc.ReadUInt64() ^ ownerKey64); - private void WriteSteamId(IWriteMessage msg, SteamId val) - => msg.Write(val.Value ^ ownerKey64); + + private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64); + private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64); public SteamP2PServerPeer(SteamId steamId, int ownerKey, ServerSettings settings) { @@ -33,23 +32,22 @@ namespace Barotrauma.Networking public override void Start() { - IWriteMessage outMsg = new WriteOnlyMessage(); - WriteSteamId(outMsg, ownerSteamId); - outMsg.Write((byte)DeliveryMethod.Reliable); - outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage)); - - byte[] msgToSend = (byte[])outMsg.Buffer.Clone(); - Array.Resize(ref msgToSend, outMsg.LengthBytes); - ChildServerRelay.Write(msgToSend); + var headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage, + Initialization = null + }; + SendMsgInternal(ownerSteamId, headers, null); started = true; } - public override void Close(string msg = null) + public override void Close(string? msg = null) { if (!started) { return; } - if (OwnerConnection != null) OwnerConnection.Status = NetworkConnectionStatus.Disconnected; + if (OwnerConnection != null) { OwnerConnection.Status = NetworkConnectionStatus.Disconnected; } for (int i = pendingClients.Count - 1; i >= 0; i--) { @@ -82,7 +80,7 @@ namespace Barotrauma.Networking //backwards for loop so we can remove elements while iterating for (int i = connectedClients.Count - 1; i >= 0; i--) { - SteamP2PConnection conn = connectedClients[i] as SteamP2PConnection; + SteamP2PConnection conn = (SteamP2PConnection)connectedClients[i]; conn.Decay(deltaTime); if (conn.Timeout < 0.0) { @@ -103,7 +101,7 @@ namespace Barotrauma.Networking catch (Exception e) { string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce($"SteamP2PServerPeer.Update:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #else @@ -118,36 +116,36 @@ namespace Barotrauma.Networking if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; } } } - + private void HandleDataMessage(IReadMessage inc) { if (!started) { return; } SteamId senderSteamId = ReadSteamId(inc); - SteamId ownerSteamId = ReadSteamId(inc); - - PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); - + SteamId sentOwnerSteamId = ReadSteamId(inc); + + var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read(inc); + if (packetHeader.IsServerMessage()) { - DebugConsole.ThrowError("Got server message from" + senderSteamId.ToString()); + DebugConsole.ThrowError($"Got server message from {senderSteamId}"); return; } - if (senderSteamId != this.ownerSteamId) //sender is remote, handle disconnects and heartbeats + if (senderSteamId != ownerSteamId) //sender is remote, handle disconnects and heartbeats { - bool connectionMatches(NetworkConnection conn) - => conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var steamId } } - && steamId == senderSteamId; - PendingClient pendingClient = pendingClients.Find(c => connectionMatches(c.Connection)); - SteamP2PConnection connectedClient = connectedClients.Find(connectionMatches) as SteamP2PConnection; + bool connectionMatches(NetworkConnection conn) => + conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var steamId } } + && steamId == senderSteamId; + + PendingClient? pendingClient = pendingClients.Find(c => connectionMatches(c.Connection)); + SteamP2PConnection? connectedClient = connectedClients.Find(connectionMatches) as SteamP2PConnection; pendingClient?.Heartbeat(); connectedClient?.Heartbeat(); - string banReason; - if (serverSettings.BanList.IsBanned(senderSteamId, out banReason) || - serverSettings.BanList.IsBanned(ownerSteamId, out banReason)) + if (serverSettings.BanList.IsBanned(senderSteamId, out string banReason) || + serverSettings.BanList.IsBanned(sentOwnerSteamId, out banReason)) { if (pendingClient != null) { @@ -155,9 +153,8 @@ namespace Barotrauma.Networking } else if (connectedClient != null) { - Disconnect(connectedClient, DisconnectReason.Banned.ToString() + "/ "+ banReason); + Disconnect(connectedClient, $"{DisconnectReason.Banned}/ {banReason}"); } - return; } else if (packetHeader.IsDisconnectMessage()) { @@ -171,7 +168,6 @@ namespace Barotrauma.Networking string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={GameMain.Server.ConnectedClients.First(c => c.Connection == connectedClient).Name}"; Disconnect(connectedClient, disconnectMsg, false); } - return; } else if (packetHeader.IsHeartbeatMessage()) { @@ -182,12 +178,15 @@ namespace Barotrauma.Networking { if (pendingClient != null) { - pendingClient.Connection.SetAccountInfo(new AccountInfo(senderSteamId, ownerSteamId)); - ReadConnectionInitializationStep(pendingClient, new ReadOnlyMessage(inc.Buffer, false, inc.BytePosition, inc.LengthBytes - inc.BytePosition, null)); + pendingClient.Connection.SetAccountInfo(new AccountInfo(senderSteamId, sentOwnerSteamId)); + ReadConnectionInitializationStep( + pendingClient, + new ReadWriteMessage(inc.Buffer, inc.BitPosition, inc.LengthBits, false), + initialization ?? throw new Exception("Initialization step missing")); } - else + else if (initialization.HasValue) { - ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte(); + ConnectionInitialization initializationStep = initialization.Value; if (initializationStep == ConnectionInitialization.ConnectionStarted) { pendingClients.Add(new PendingClient(new SteamP2PConnection(senderSteamId))); @@ -196,51 +195,53 @@ namespace Barotrauma.Networking } else if (connectedClient != null) { - UInt16 length = inc.ReadUInt16(); - - IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, connectedClient); + var packet = INetSerializableStruct.Read(inc); + IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, connectedClient); OnMessageReceived?.Invoke(connectedClient, msg); } } else //sender is owner { - if (OwnerConnection != null) { (OwnerConnection as SteamP2PConnection).Heartbeat(); } + (OwnerConnection as SteamP2PConnection)?.Heartbeat(); if (packetHeader.IsDisconnectMessage()) { DebugConsole.ThrowError("Received disconnect message from owner"); return; } + if (packetHeader.IsServerMessage()) { DebugConsole.ThrowError("Received server message from owner"); return; } + if (packetHeader.IsConnectionInitializationStep()) { - if (OwnerConnection == null) + if (OwnerConnection is null) { - string ownerName = inc.ReadString(); - OwnerConnection = new SteamP2PConnection(this.ownerSteamId) + var packet = INetSerializableStruct.Read(inc); + OwnerConnection = new SteamP2PConnection(ownerSteamId) { Language = GameSettings.CurrentConfig.Language }; - OwnerConnection.SetAccountInfo(new AccountInfo(this.ownerSteamId, this.ownerSteamId)); + OwnerConnection.SetAccountInfo(new AccountInfo(ownerSteamId, ownerSteamId)); - OnInitializationComplete?.Invoke(OwnerConnection, ownerName); + OnInitializationComplete?.Invoke(OwnerConnection, packet.OwnerName); } + return; } + if (packetHeader.IsHeartbeatMessage()) { return; } else { - UInt16 length = inc.ReadUInt16(); - - IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, OwnerConnection); - OnMessageReceived?.Invoke(OwnerConnection, msg); + var packet = INetSerializableStruct.Read(inc); + IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, OwnerConnection); + OnMessageReceived?.Invoke(OwnerConnection!, msg); } } } @@ -249,59 +250,67 @@ namespace Barotrauma.Networking { throw new InvalidOperationException("Called InitializeSteamServerCallbacks on SteamP2PServerPeer!"); } - + public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true) { if (!started) { return; } - if (!(conn is SteamP2PConnection steamp2pConn)) { return; } - if (!connectedClients.Contains(steamp2pConn) && conn != OwnerConnection) + if (!(conn is SteamP2PConnection steamP2PConn)) { return; } + + if (!connectedClients.Contains(steamP2PConn) && conn != OwnerConnection) { - DebugConsole.ThrowError("Tried to send message to unauthenticated connection: " + steamp2pConn.AccountInfo.AccountId.ToString()); + DebugConsole.ThrowError($"Tried to send message to unauthenticated connection: {steamP2PConn.AccountInfo.AccountId}"); return; } if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || !(connAccountId is SteamId connSteamId)) { return; } - IWriteMessage msgToSend = new WriteOnlyMessage(); - byte[] msgData = new byte[16]; - msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); - WriteSteamId(msgToSend, connSteamId); - msgToSend.Write((byte)deliveryMethod); - msgToSend.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage)); - msgToSend.Write((UInt16)length); - msgToSend.Write(msgData, 0, length); + byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _); - byte[] bufToSend = (byte[])msgToSend.Buffer.Clone(); - Array.Resize(ref bufToSend, msgToSend.LengthBytes); - ChildServerRelay.Write(bufToSend); + var headers = new PeerPacketHeaders + { + DeliveryMethod = deliveryMethod, + PacketHeader = (isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) + | PacketHeader.IsServerMessage, + Initialization = null + }; + var body = new PeerPacketMessage + { + Buffer = bufAux + }; + SendMsgInternal(steamP2PConn, headers, body); } - private void SendDisconnectMessage(SteamId steamId, string msg) + private void SendDisconnectMessage(SteamId steamId, string? msg) { if (!started) { return; } + if (string.IsNullOrWhiteSpace(msg)) { return; } - IWriteMessage msgToSend = new WriteOnlyMessage(); - WriteSteamId(msgToSend, steamId); - msgToSend.Write((byte)DeliveryMethod.Reliable); - msgToSend.Write((byte)(PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage)); - msgToSend.Write(msg); + var headers = new PeerPacketHeaders + { + DeliveryMethod = DeliveryMethod.Reliable, + PacketHeader = PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage, + Initialization = null + }; + var packet = new PeerDisconnectPacket + { + Message = msg + }; - byte[] bufToSend = (byte[])msgToSend.Buffer.Clone(); - Array.Resize(ref bufToSend, msgToSend.LengthBytes); - ChildServerRelay.Write(bufToSend); + SendMsgInternal(steamId, headers, packet); } - private void Disconnect(NetworkConnection conn, string msg, bool sendDisconnectMessage) + private void Disconnect(NetworkConnection conn, string? msg, bool sendDisconnectMessage) { if (!started) { return; } if (!(conn is SteamP2PConnection steamp2pConn)) { return; } if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || !(connAccountId is SteamId connSteamId)) { return; } - + if (sendDisconnectMessage) { SendDisconnectMessage(connSteamId, msg); } + if (connectedClients.Contains(steamp2pConn)) { steamp2pConn.Status = NetworkConnectionStatus.Disconnected; @@ -315,32 +324,41 @@ namespace Barotrauma.Networking } } - public override void Disconnect(NetworkConnection conn, string msg = null) + public override void Disconnect(NetworkConnection conn, string? msg = null) { Disconnect(conn, msg, true); } - protected override void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg) + protected override void SendMsgInternal(NetworkConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body) { - var connSteamId = conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var id } } - ? id : null; + var connSteamId = conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var id } } ? id : null; if (connSteamId is null) { return; } - + + SendMsgInternal(connSteamId, headers, body); + } + + private void SendMsgInternal(SteamId connSteamId, PeerPacketHeaders headers, INetSerializableStruct? body) + { IWriteMessage msgToSend = new WriteOnlyMessage(); WriteSteamId(msgToSend, connSteamId); - msgToSend.Write((byte)deliveryMethod); - msgToSend.Write(msg.Buffer, 0, msg.LengthBytes); - byte[] bufToSend = (byte[])msgToSend.Buffer.Clone(); - Array.Resize(ref bufToSend, msgToSend.LengthBytes); + msgToSend.WriteNetSerializableStruct(headers); + body?.Write(msgToSend); + + ForwardToOwnerProcess(msgToSend); + } + + private static void ForwardToOwnerProcess(IWriteMessage msg) + { + byte[] bufToSend = (byte[])msg.Buffer.Clone(); + Array.Resize(ref bufToSend, msg.LengthBytes); ChildServerRelay.Write(bufToSend); } - protected override void ProcessAuthTicket(string name, Option ownKey, Option steamId, PendingClient pendingClient, byte[] ticket) + protected override void ProcessAuthTicket(ClientSteamTicketAndVersionPacket packet, PendingClient pendingClient) { pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder; - - pendingClient.Name = name; + pendingClient.Name = packet.Name; pendingClient.AuthSessionStarted = true; } } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index cfa43ea1d..6d0acea10 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -8,11 +8,6 @@ namespace Barotrauma.Networking { partial class RespawnManager : Entity, IServerSerializable { - /// - /// How much skills drop towards the job's default skill levels when respawning midround in the campaign - /// - const float SkillReductionOnCampaignMidroundRespawn = 0.75f; - private DateTime despawnTime; private float shuttleEmptyTimer; @@ -444,7 +439,7 @@ namespace Barotrauma.Networking } clients[i].Character = character; - character.OwnerClientEndpoint = clients[i].Connection.Endpoint; + character.OwnerClientAddress = clients[i].Connection.Endpoint.Address; character.OwnerClientName = clients[i].Name; GameServer.Log( $"Respawning {GameServer.ClientLogName(clients[i])} ({clients[i].Connection.Endpoint}) as {characterInfos[i].Job.Name}", ServerLog.MessageType.Spawning); @@ -561,7 +556,7 @@ namespace Barotrauma.Networking { var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier == s.Identifier); if (skillPrefab == null) { continue; } - skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.Start, SkillReductionOnCampaignMidroundRespawn); + skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.End, SkillReductionOnDeath); } } @@ -572,20 +567,20 @@ namespace Barotrauma.Networking switch (CurrentState) { case State.Transporting: - msg.Write(ReturnCountdownStarted); - msg.Write(GameMain.Server.ServerSettings.MaxTransportTime); - msg.Write((float)(ReturnTime - DateTime.Now).TotalSeconds); + msg.WriteBoolean(ReturnCountdownStarted); + msg.WriteSingle(GameMain.Server.ServerSettings.MaxTransportTime); + msg.WriteSingle((float)(ReturnTime - DateTime.Now).TotalSeconds); break; case State.Waiting: MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign; var matchingData = campaign?.GetClientCharacterData(c); bool forceSpawnInMainSub = matchingData != null && !matchingData.HasSpawned; - msg.Write((ushort)pendingRespawnCount); - msg.Write((ushort)requiredRespawnCount); - msg.Write(IsRespawnPromptPendingForClient(c)); - msg.Write(RespawnCountdownStarted); - msg.Write(forceSpawnInMainSub); - msg.Write((float)(RespawnTime - DateTime.Now).TotalSeconds); + msg.WriteUInt16((ushort)pendingRespawnCount); + msg.WriteUInt16((ushort)requiredRespawnCount); + msg.WriteBoolean(IsRespawnPromptPendingForClient(c)); + msg.WriteBoolean(RespawnCountdownStarted); + msg.WriteBoolean(forceSpawnInMainSub); + msg.WriteSingle((float)(RespawnTime - DateTime.Now).TotalSeconds); break; case State.Returning: break; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index d39451492..3a997b41a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -27,20 +27,26 @@ namespace Barotrauma.Networking public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml"; public static readonly char SubmarineSeparatorChar = '|'; - public readonly Dictionary LastUpdateIdForFlag = new Dictionary(); - public UInt16 LastPropertyUpdateId { get; private set; } = 1; - + public readonly Dictionary LastUpdateIdForFlag + = ((NetFlags[])Enum.GetValues(typeof(NetFlags))) + .Select(f => (f, (ushort)1)) + .ToDictionary(); + public void UpdateFlag(NetFlags flag) => LastUpdateIdForFlag[flag] = (UInt16)(GameMain.NetLobbyScreen.LastUpdateID + 1); + public NetFlags UnsentFlags() + => LastUpdateIdForFlag.Keys + .Where(k => NetIdUtils.IdMoreRecent(LastUpdateIdForFlag[k], GameMain.NetLobbyScreen.LastUpdateID)) + .Aggregate(NetFlags.None, (f1, f2) => f1 | f2); + private bool IsFlagRequired(Client c, NetFlags flag) => NetIdUtils.IdMoreRecent(LastUpdateIdForFlag[flag], c.LastRecvLobbyUpdate); public NetFlags GetRequiredFlags(Client c) => LastUpdateIdForFlag.Keys .Where(k => IsFlagRequired(c, k)) - .Concat(NetFlags.None.ToEnumerable()) //prevents InvalidOperationException in Aggregate - .Aggregate((f1, f2) => f1 | f2); + .Aggregate(NetFlags.None, (f1, f2) => f1 | f2); partial void InitProjSpecific() { @@ -56,16 +62,16 @@ namespace Barotrauma.Networking property.SyncValue(); if (NetIdUtils.IdMoreRecent(property.LastUpdateID, c.LastRecvLobbyUpdate)) { - outMsg.Write(key); + outMsg.WriteUInt32(key); netProperties[key].Write(outMsg); } } - outMsg.Write((UInt32)0); + outMsg.WriteUInt32((UInt32)0); } public void ServerAdminWrite(IWriteMessage outMsg, Client c) { - c.LastSentServerSettingsUpdate = LastPropertyUpdateId; + c.LastSentServerSettingsUpdate = LastUpdateIdForFlag[NetFlags.Properties]; WriteNetProperties(outMsg, c); WriteMonsterEnabled(outMsg); BanList.ServerAdminWrite(outMsg, c); @@ -74,21 +80,21 @@ namespace Barotrauma.Networking public void ServerWrite(IWriteMessage outMsg, Client c) { NetFlags requiredFlags = GetRequiredFlags(c); - outMsg.Write((byte)requiredFlags); + outMsg.WriteByte((byte)requiredFlags); if (requiredFlags.HasFlag(NetFlags.Name)) { - outMsg.Write(ServerName); + outMsg.WriteString(ServerName); } if (requiredFlags.HasFlag(NetFlags.Message)) { - outMsg.Write(ServerMessageText); + outMsg.WriteString(ServerMessageText); } - outMsg.Write((byte)PlayStyle); - outMsg.Write((byte)MaxPlayers); - outMsg.Write(HasPassword); - outMsg.Write(IsPublic); - outMsg.Write(AllowFileTransfers); + outMsg.WriteByte((byte)PlayStyle); + outMsg.WriteByte((byte)MaxPlayers); + outMsg.WriteBoolean(HasPassword); + outMsg.WriteBoolean(IsPublic); + outMsg.WriteBoolean(AllowFileTransfers); outMsg.WritePadBits(); outMsg.WriteRangedInteger(TickRate, 1, 60); @@ -103,16 +109,18 @@ namespace Barotrauma.Networking } if (c.HasPermission(Networking.ClientPermissions.ManageSettings) - && !NetIdUtils.IdMoreRecentOrMatches(c.LastRecvServerSettingsUpdate, LastPropertyUpdateId)) + && NetIdUtils.IdMoreRecent( + newID: LastUpdateIdForFlag[NetFlags.Properties], + oldID: c.LastRecvServerSettingsUpdate)) { - outMsg.Write(true); + outMsg.WriteBoolean(true); outMsg.WritePadBits(); ServerAdminWrite(outMsg, c); } else { - outMsg.Write(false); + outMsg.WriteBoolean(false); outMsg.WritePadBits(); } } @@ -174,7 +182,6 @@ namespace Barotrauma.Networking if (propertiesChanged) { UpdateFlag(NetFlags.Properties); - LastPropertyUpdateId = (UInt16)(GameMain.NetLobbyScreen.LastUpdateID + 1); } changed |= propertiesChanged; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs index 4fcdc6f73..f4063a8d1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs @@ -55,8 +55,8 @@ namespace Barotrauma.Networking IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ServerPacketHeader.VOICE); - msg.Write((byte)queue.QueueID); + msg.WriteByte((byte)ServerPacketHeader.VOICE); + msg.WriteByte((byte)queue.QueueID); queue.Write(msg); netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index c21a8f293..ff031da8e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -254,7 +254,7 @@ namespace Barotrauma break; case VoteType.Kick: byte kickedClientID = inc.ReadByte(); - if ((DateTime.Now - sender.JoinTime).TotalSeconds > GameMain.Server.ServerSettings.DisallowKickVoteTime) + if ((DateTime.Now - sender.JoinTime).TotalSeconds < GameMain.Server.ServerSettings.DisallowKickVoteTime) { GameMain.Server.SendDirectChatMessage($"ServerMessage.kickvotedisallowed", sender); } @@ -328,66 +328,66 @@ namespace Barotrauma { if (GameMain.Server == null) { return; } - msg.Write(GameMain.Server.ServerSettings.AllowSubVoting); + msg.WriteBoolean(GameMain.Server.ServerSettings.AllowSubVoting); if (GameMain.Server.ServerSettings.AllowSubVoting) { IReadOnlyDictionary voteList = GetVoteCounts(VoteType.Sub, GameMain.Server.ConnectedClients); - msg.Write((byte)voteList.Count); + msg.WriteByte((byte)voteList.Count); foreach (KeyValuePair vote in voteList) { - msg.Write((byte)vote.Value); - msg.Write(vote.Key.Name); + msg.WriteByte((byte)vote.Value); + msg.WriteString(vote.Key.Name); } } - msg.Write(GameMain.Server.ServerSettings.AllowModeVoting); + msg.WriteBoolean(GameMain.Server.ServerSettings.AllowModeVoting); if (GameMain.Server.ServerSettings.AllowModeVoting) { IReadOnlyDictionary voteList = GetVoteCounts(VoteType.Mode, GameMain.Server.ConnectedClients); - msg.Write((byte)voteList.Count); + msg.WriteByte((byte)voteList.Count); foreach (KeyValuePair vote in voteList) { - msg.Write((byte)vote.Value); - msg.Write(vote.Key.Identifier); + msg.WriteByte((byte)vote.Value); + msg.WriteIdentifier(vote.Key.Identifier); } } - msg.Write(GameMain.Server.ServerSettings.AllowEndVoting); + msg.WriteBoolean(GameMain.Server.ServerSettings.AllowEndVoting); if (GameMain.Server.ServerSettings.AllowEndVoting) { - msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote(VoteType.EndRound))); - msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned)); + msg.WriteByte((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote(VoteType.EndRound))); + msg.WriteByte((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned)); } - msg.Write(GameMain.Server.ServerSettings.AllowVoteKick); + msg.WriteBoolean(GameMain.Server.ServerSettings.AllowVoteKick); - msg.Write((byte)(ActiveVote?.State ?? VoteState.None)); + msg.WriteByte((byte)(ActiveVote?.State ?? VoteState.None)); if (ActiveVote != null) { - msg.Write((byte)ActiveVote.VoteType); + msg.WriteByte((byte)ActiveVote.VoteType); if (ActiveVote.State != VoteState.None && ActiveVote.VoteType != VoteType.Unknown) { var eligibleClients = GameMain.Server.ConnectedClients.Where(c => c.InGame && c != ActiveVote.VoteStarter); var yesClients = eligibleClients.Where(c => c.GetVote(ActiveVote.VoteType) == 2); - msg.Write((byte)yesClients.Count()); + msg.WriteByte((byte)yesClients.Count()); foreach (Client c in yesClients) { - msg.Write(c.SessionId); + msg.WriteByte(c.SessionId); } var noClients = eligibleClients.Where(c => c.GetVote(ActiveVote.VoteType) == 1); - msg.Write((byte)noClients.Count()); + msg.WriteByte((byte)noClients.Count()); foreach (Client c in noClients) { - msg.Write(c.SessionId); + msg.WriteByte(c.SessionId); } - msg.Write((byte)eligibleClients.Count()); + msg.WriteByte((byte)eligibleClients.Count()); switch (ActiveVote.State) { case VoteState.Started: - msg.Write(ActiveVote.VoteStarter.SessionId); - msg.Write((byte)GameMain.Server.ServerSettings.VoteTimeout); + msg.WriteByte(ActiveVote.VoteStarter.SessionId); + msg.WriteByte((byte)GameMain.Server.ServerSettings.VoteTimeout); switch (ActiveVote.VoteType) { @@ -395,14 +395,14 @@ namespace Barotrauma case VoteType.PurchaseAndSwitchSub: case VoteType.SwitchSub: SubmarineVote vote = ActiveVote as SubmarineVote; - msg.Write(vote.Sub.Name); - msg.Write(vote.TransferItems); + msg.WriteString(vote.Sub.Name); + msg.WriteBoolean(vote.TransferItems); break; case VoteType.TransferMoney: var transferVote = (ActiveVote as TransferVote); - msg.Write(transferVote.From?.SessionId ?? 0); - msg.Write(transferVote.To?.SessionId ?? 0); - msg.Write(transferVote.TransferAmount); + msg.WriteByte(transferVote.From?.SessionId ?? 0); + msg.WriteByte(transferVote.To?.SessionId ?? 0); + msg.WriteInt32(transferVote.TransferAmount); break; } @@ -412,16 +412,16 @@ namespace Barotrauma break; case VoteState.Passed: case VoteState.Failed: - msg.Write(ActiveVote.State == VoteState.Passed); + msg.WriteBoolean(ActiveVote.State == VoteState.Passed); switch (ActiveVote.VoteType) { case VoteType.PurchaseSub: case VoteType.PurchaseAndSwitchSub: case VoteType.SwitchSub: var subVote = ActiveVote as SubmarineVote; - msg.Write(subVote.Sub.Name); - msg.Write(subVote.TransferItems); - msg.Write((short)subVote.DeliveryFee); + msg.WriteString(subVote.Sub.Name); + msg.WriteBoolean(subVote.TransferItems); + msg.WriteInt16((short)subVote.DeliveryFee); break; } break; @@ -430,10 +430,10 @@ namespace Barotrauma } var readyClients = GameMain.Server.ConnectedClients.Where(c => c.GetVote(VoteType.StartRound)); - msg.Write((byte)readyClients.Count()); + msg.WriteByte((byte)readyClients.Count()); foreach (Client c in readyClients) { - msg.Write(c.SessionId); + msg.WriteByte(c.SessionId); } msg.WritePadBits(); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/WhiteList.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/WhiteList.cs deleted file mode 100644 index 0e6d43c44..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/WhiteList.cs +++ /dev/null @@ -1,207 +0,0 @@ -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using Barotrauma.IO; -using System.Linq; -using System.Net; - -namespace Barotrauma.Networking -{ - partial class WhiteListedPlayer - { - private static UInt16 LastIdentifier = 0; - - public WhiteListedPlayer(string name,string ip) - { - Name = name; - IP = ip; - - UniqueIdentifier = LastIdentifier; LastIdentifier++; - } - } - - partial class WhiteList - { - partial void InitProjSpecific() - { - if (!File.Exists(SavePath)) { return; } - - string[] lines; - try - { - lines = File.ReadAllLines(SavePath); - } - catch (Exception e) - { - DebugConsole.ThrowError("Failed to open whitelist in " + SavePath, e); - return; - } - - foreach (string line in lines) - { - if (line[0] == '#') - { - string lineval = line.Substring(1, line.Length - 1); - Int32.TryParse(lineval, out int intVal); - if (lineval.ToLower() == "true" || intVal != 0) - { - Enabled = true; - } - else - { - Enabled = false; - } - } - else - { - string[] separatedLine = line.Split(','); - if (separatedLine.Length < 2) continue; - - string name = string.Join(",", separatedLine.Take(separatedLine.Length - 1)); - string ip = separatedLine.Last(); - - whitelistedPlayers.Add(new WhiteListedPlayer(name, ip)); - } - } - } - - public void Save() - { - GameServer.Log("Saving whitelist", ServerLog.MessageType.ServerMessage); - - GameMain.Server?.ServerSettings?.UpdateFlag(ServerSettings.NetFlags.Properties); - - List lines = new List(); - - if (Enabled) - { - lines.Add("#true"); - } - else - { - lines.Add("#false"); - } - foreach (WhiteListedPlayer wlp in whitelistedPlayers) - { - lines.Add(wlp.Name + "," + wlp.IP); - } - - try - { - File.WriteAllLines(SavePath, lines); - } - catch (Exception e) - { - DebugConsole.ThrowError("Saving the whitelist to " + SavePath + " failed", e); - } - } - - public bool IsWhiteListed(string name, IPAddress address) - { - if (!Enabled) return true; - WhiteListedPlayer wlp = whitelistedPlayers.Find(p => p.Name == name); - if (wlp == null) return false; - if (!string.IsNullOrWhiteSpace(wlp.IP)) - { - if (address.IsIPv4MappedToIPv6 && wlp.IP == address.MapToIPv4NoThrow().ToString()) - { - return true; - } - else - { - return wlp.IP == address.ToString(); - } - } - return true; - } - - private void RemoveFromWhiteList(WhiteListedPlayer wlp) - { - GameServer.Log("Removing " + wlp.Name + " from whitelist", ServerLog.MessageType.ServerMessage); - whitelistedPlayers.Remove(wlp); - } - - private void AddToWhiteList(string name, string ip) - { - if (string.IsNullOrWhiteSpace(name)) return; - if (whitelistedPlayers.Any(x => x.Name.ToLower() == name.ToLower() && x.IP == ip)) return; - whitelistedPlayers.Add(new WhiteListedPlayer(name, ip)); - } - - public void ServerAdminWrite(IWriteMessage outMsg, Client c) - { - if (!c.HasPermission(ClientPermissions.ManageSettings)) - { - outMsg.Write(false); outMsg.WritePadBits(); - return; - } - outMsg.Write(true); - outMsg.Write(c.Connection == GameMain.Server.OwnerConnection); - outMsg.Write(Enabled); - - outMsg.WritePadBits(); - outMsg.WriteVariableUInt32((UInt32)whitelistedPlayers.Count); - for (int i = 0; i < whitelistedPlayers.Count; i++) - { - WhiteListedPlayer whitelistedPlayer = whitelistedPlayers[i]; - - outMsg.Write(whitelistedPlayer.Name); - outMsg.Write(whitelistedPlayer.UniqueIdentifier); - if (c.Connection == GameMain.Server.OwnerConnection) - { - outMsg.Write(whitelistedPlayer.IP); - //outMsg.Write(whitelistedPlayer.SteamID); //TODO: add steamid to whitelisted players - } - } - } - - public bool ServerAdminRead(IReadMessage incMsg, Client c) - { - if (!c.HasPermission(ClientPermissions.ManageSettings)) - { - bool enabled = incMsg.ReadBoolean(); incMsg.ReadPadBits(); - UInt16 removeCount = incMsg.ReadUInt16(); - incMsg.BitPosition += removeCount * 4 * 8; - UInt16 addCount = incMsg.ReadUInt16(); - for (int i = 0; i < addCount; i++) - { - incMsg.ReadString(); //skip name - incMsg.ReadString(); //skip ip - } - return false; - } - else - { - bool prevEnabled = Enabled; - bool enabled = incMsg.ReadBoolean(); incMsg.ReadPadBits(); - Enabled = enabled; - - UInt16 removeCount = incMsg.ReadUInt16(); - for (int i = 0; i < removeCount; i++) - { - UInt16 id = incMsg.ReadUInt16(); - WhiteListedPlayer whitelistedPlayer = whitelistedPlayers.Find(p => p.UniqueIdentifier == id); - if (whitelistedPlayer != null) - { - GameServer.Log(GameServer.ClientLogName(c) + " removed " + whitelistedPlayer.Name + " from whitelist (" + whitelistedPlayer.IP + ")", ServerLog.MessageType.ConsoleUsage); - RemoveFromWhiteList(whitelistedPlayer); - } - } - - UInt16 addCount = incMsg.ReadUInt16(); - for (int i = 0; i < addCount; i++) - { - string name = incMsg.ReadString(); - string ip = incMsg.ReadString(); - - GameServer.Log(GameServer.ClientLogName(c) + " added " + name + " to whitelist (" + ip + ")", ServerLog.MessageType.ConsoleUsage); - AddToWhiteList(name, ip); - } - - bool changed = removeCount > 0 || addCount > 0 || prevEnabled != enabled; - if (changed) { Save(); } - return changed; - } - } - } -} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaServer/ServerSource/Physics/PhysicsBody.cs index 0b03fe680..04abe986f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Physics/PhysicsBody.cs @@ -11,8 +11,8 @@ namespace Barotrauma float MaxVel = NetConfig.MaxPhysicsBodyVelocity; float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; - msg.Write(SimPosition.X); - msg.Write(SimPosition.Y); + msg.WriteSingle(SimPosition.X); + msg.WriteSingle(SimPosition.Y); #if DEBUG if (Math.Abs(FarseerBody.LinearVelocity.X) > MaxVel || @@ -22,8 +22,8 @@ namespace Barotrauma } #endif - msg.Write(FarseerBody.Awake); - msg.Write(FarseerBody.FixedRotation); + msg.WriteBoolean(FarseerBody.Awake); + msg.WriteBoolean(FarseerBody.FixedRotation); if (!FarseerBody.FixedRotation) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorMissionResult.cs b/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorMissionResult.cs index 2f90a016d..6fe640c28 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorMissionResult.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorMissionResult.cs @@ -17,13 +17,13 @@ namespace Barotrauma public void ServerWrite(IWriteMessage msg) { - msg.Write(MissionIdentifier); - msg.Write(EndMessage); - msg.Write(Success); - msg.Write((byte)Characters.Count); + msg.WriteIdentifier(MissionIdentifier); + msg.WriteString(EndMessage); + msg.WriteBoolean(Success); + msg.WriteByte((byte)Characters.Count); foreach (Character character in Characters) { - msg.Write(character.ID); + msg.WriteUInt16(character.ID); } } } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 07f7633bd..319a53b07 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -2,16 +2,17 @@ Exe - netcoreapp3.1 + net6.0 Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.19.1.0 + 0.19.3.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer ..\BarotraumaShared\Icon.ico Debug;Release;Unstable + true ;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 diff --git a/Barotrauma/BarotraumaShared/DeployGameAnalytics.props b/Barotrauma/BarotraumaShared/DeployGameAnalytics.props index 7c7f3bac4..e4818697e 100644 --- a/Barotrauma/BarotraumaShared/DeployGameAnalytics.props +++ b/Barotrauma/BarotraumaShared/DeployGameAnalytics.props @@ -12,7 +12,7 @@ - + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 7ecbbdaa3..1708c86da 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -3847,7 +3847,7 @@ namespace Barotrauma public override void ServerWrite(IWriteMessage msg) { - msg.Write((byte)State); + msg.WriteByte((byte)State); PetBehavior?.ServerWrite(msg); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs index 1a14bd206..cb7523470 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Barotrauma.Extensions; +using System.Collections.Immutable; namespace Barotrauma { @@ -529,6 +530,15 @@ namespace Barotrauma } return canEquip; } + protected bool CheckItemIdentifiersOrTags(Item item, ImmutableHashSet identifiersOrTags) + { + if (identifiersOrTags.Contains(item.Prefab.Identifier)) { return true; } + foreach (var identifier in identifiersOrTags) + { + if (item.HasTag(identifier)) { return true; } + } + return false; + } protected bool CanEquip(Item item) => CanEquip(character, item); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 34fed7024..625a177a7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using FarseerPhysics.Dynamics; using static Barotrauma.AIObjectiveFindSafety; +using System.Collections.Immutable; namespace Barotrauma { @@ -967,7 +968,7 @@ namespace Barotrauma /// /// Seeks for more ammunition. Creates a new subobjective. /// - private void SeekAmmunition(Identifier[] ammunitionIdentifiers) + private void SeekAmmunition(ImmutableHashSet ammunitionIdentifiers) { retreatTarget = null; RemoveSubObjective(ref retreatObjective); @@ -1002,7 +1003,7 @@ namespace Barotrauma HumanAIController.UnequipEmptyItems(Weapon); RelatedItem item = null; Item ammunition = null; - Identifier[] ammunitionIdentifiers = null; + ImmutableHashSet ammunitionIdentifiers = null; if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained]) @@ -1028,8 +1029,8 @@ namespace Barotrauma if (ammunitionIdentifiers != null) { // Try reload ammunition from inventory - bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio"); - ammunition = character.Inventory.FindItem(i => ammunitionIdentifiers.Any(id => id == i.Prefab.Identifier || i.HasTag(id)) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true); + static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio"); + ammunition = character.Inventory.FindItem(i => CheckItemIdentifiersOrTags(i, ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true); if (ammunition != null) { var container = Weapon.GetComponent(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs index dea08ca34..8a5c606b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -1,6 +1,8 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace Barotrauma @@ -11,14 +13,14 @@ namespace Barotrauma public Func GetItemPriority; - public Identifier[] ignoredContainerIdentifiers; + public ImmutableHashSet ignoredContainerIdentifiers; public bool checkInventory = true; //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs and in some cases also enemy NPCs, like pirates) private readonly bool spawnItemIfNotFound; //can either be a tag or an identifier - public readonly Identifier[] itemIdentifiers; + public readonly ImmutableHashSet itemIdentifiers; public readonly ItemContainer container; private readonly Item item; public Item ItemToContain { get; private set; } @@ -61,9 +63,9 @@ namespace Barotrauma } public AIObjectiveContainItem(Character character, Identifier itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false) - : this(character, new Identifier[] { itemIdentifier }, container, objectiveManager, priorityModifier, spawnItemIfNotFound) { } + : this(character, itemIdentifier.ToEnumerable().ToImmutableHashSet(), container, objectiveManager, priorityModifier, spawnItemIfNotFound) { } - public AIObjectiveContainItem(Character character, Identifier[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false) + public AIObjectiveContainItem(Character character, ImmutableHashSet itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false) : base(character, objectiveManager, priorityModifier) { this.itemIdentifiers = itemIdentifiers; @@ -102,7 +104,10 @@ namespace Barotrauma return containedItemCount >= ItemCount; } - private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel && i.HasAccess(character); + private bool CheckItem(Item item) + { + return CheckItemIdentifiersOrTags(item, itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character); + } protected override void Act(float deltaTime) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs index 047c1b636..a41303c20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs @@ -1,5 +1,7 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using System; +using System.Collections.Immutable; using System.Linq; namespace Barotrauma @@ -13,7 +15,7 @@ namespace Barotrauma //can either be a tag or an identifier private readonly string[] itemIdentifiers; private readonly ItemContainer sourceContainer; - private ItemContainer targetContainer; + private readonly ItemContainer targetContainer; private readonly Item targetItem; private AIObjectiveGetItem getItemObjective; @@ -127,7 +129,7 @@ namespace Barotrauma RemoveExistingPredicate = RemoveExistingPredicate, RemoveMax = RemoveExistingMax, GetItemPriority = GetItemPriority, - ignoredContainerIdentifiers = sourceContainer != null ? new Identifier[] { sourceContainer.Item.Prefab.Identifier } : null + ignoredContainerIdentifiers = sourceContainer?.Item.Prefab.Identifier.ToEnumerable().ToImmutableHashSet() }, onCompleted: () => IsCompleted = true, onAbandon: () => Abandon = true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs index a2567e72b..3e5531d20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -177,7 +177,7 @@ namespace Barotrauma TargetName = Leak.FlowTargetHull?.DisplayName, requiredCondition = () => Leak.Submarine == character.Submarine && - Leak.linkedTo.Any(e => e is Hull h && character.CurrentHull == h), + Leak.linkedTo.Any(e => e is Hull h && (character.CurrentHull == h || h.linkedTo.Contains(character.CurrentHull))), endNodeFilter = n => n.Waypoint.CurrentHull != null && Leak.linkedTo.Any(e => e is Hull h && h == n.Waypoint.CurrentHull), // The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue) SpeakCannotReachCondition = () => !CheckObjectiveSpecific() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index 853abb82e..407b65230 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -21,10 +21,10 @@ namespace Barotrauma public float TargetCondition { get; set; } = 1; public bool AllowDangerousPressure { get; set; } - public readonly ImmutableArray IdentifiersOrTags; + public readonly ImmutableHashSet IdentifiersOrTags; //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs) - private bool spawnItemIfNotFound = false; + private readonly bool spawnItemIfNotFound = false; private Item targetItem; private readonly Item originalTarget; @@ -32,8 +32,8 @@ namespace Barotrauma private bool isDoneSeeking; public Item TargetItem => targetItem; private int currSearchIndex; - public Identifier[] ignoredContainerIdentifiers; - public Identifier[] ignoredIdentifiersOrTags; + public ImmutableHashSet ignoredContainerIdentifiers; + public ImmutableHashSet ignoredIdentifiersOrTags; private AIObjectiveGoTo goToObjective; private float currItemPriority; private readonly bool checkInventory; @@ -93,8 +93,8 @@ namespace Barotrauma Equip = equip; this.spawnItemIfNotFound = spawnItemIfNotFound; this.checkInventory = checkInventory; - IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray(); - ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToArray(); + IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableHashSet(); + ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToImmutableHashSet(); } public static IEnumerable ParseGearTags(IEnumerable identifiersOrTags) @@ -558,11 +558,11 @@ namespace Barotrauma { if (!item.HasAccess(character)) { return false; } if (ignoredItems.Contains(item)) { return false; }; - if (ignoredIdentifiersOrTags != null && ignoredIdentifiersOrTags.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { return false; } + if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; } if (item.Condition < TargetCondition) { return false; } if (ItemFilter != null && !ItemFilter(item)) { return false; } if (RequireLoaded && item.Components.Any(i => !i.IsLoaded(character))) { return false; } - return IdentifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && item.Prefab.VariantOf == id)); + return CheckItemIdentifiersOrTags(item, IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf)); } public override void Reset() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItems.cs index 0587b597f..e6d81bd12 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItems.cs @@ -25,7 +25,7 @@ namespace Barotrauma public bool RequireAllItems { get; set; } private readonly ImmutableArray gearTags; - private readonly Identifier[] ignoredTags; + private readonly ImmutableHashSet ignoredTags; private bool subObjectivesCreated; public readonly HashSet achievedItems = new HashSet(); @@ -33,7 +33,7 @@ namespace Barotrauma public AIObjectiveGetItems(Character character, AIObjectiveManager objectiveManager, IEnumerable identifiersOrTags, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { gearTags = AIObjectiveGetItem.ParseGearTags(identifiersOrTags).ToImmutableArray(); - ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToArray(); + ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToImmutableHashSet(); } protected override bool CheckObjectiveSpecific() => subObjectivesCreated && subObjectives.None(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs index 4d5fbb243..72afb8181 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -259,7 +259,8 @@ namespace Barotrauma // Check that there is no unsafe hulls on the way to the target if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; } return true; - }, endNodeFilter: node => !isCurrentHullAllowed | !IsForbidden(node.Waypoint.CurrentHull)); + //don't stop at ladders when idling + }, endNodeFilter: node => node.Waypoint.Ladders == null && (!isCurrentHullAllowed || !IsForbidden(node.Waypoint.CurrentHull))); if (path.Unreachable) { //can't go to this room, remove it from the list and try another room @@ -290,7 +291,9 @@ namespace Barotrauma } else if (currentTarget != null) { - PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null); + PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, + nodeFilter: node => node.Waypoint.CurrentHull != null, + endNodeFilter: node => node.Waypoint.Ladders == null); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs index 25d23300b..ceeeab73b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs @@ -444,7 +444,7 @@ namespace Barotrauma #if SERVER public void ServerEventWrite(IWriteMessage msg, Client client, NetEntityEvent.IData extraData = null) { - msg.Write(IsAlive); + msg.WriteBoolean(IsAlive); } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index e8c70509d..02bb65dd5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -331,39 +331,41 @@ namespace Barotrauma Collider.SetTransform(Collider.SimPosition, Collider.Rotation + angleDiff); } } - - if (character.LockHands) - { - var leftHand = GetLimb(LimbType.LeftHand); - var rightHand = GetLimb(LimbType.RightHand); - - var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso); - - rightHand.Disabled = true; - leftHand.Disabled = true; - - Vector2 midPos = waist.SimPosition; - Matrix torsoTransform = Matrix.CreateRotationZ(waist.Rotation); - - midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform); - - if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f; - HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); - HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); - } - else if (character.AnimController.AnimationTestPose) + + if (character.AnimController.AnimationTestPose) { ApplyTestPose(); } - else if (Anim != Animation.UsingItem) + else { - if (Anim != Animation.UsingItemWhileClimbing) + if (character.LockHands) { - ResetPullJoints(); + var leftHand = GetLimb(LimbType.LeftHand); + var rightHand = GetLimb(LimbType.RightHand); + + var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso); + + rightHand.Disabled = true; + leftHand.Disabled = true; + + Vector2 midPos = waist.SimPosition; + Matrix torsoTransform = Matrix.CreateRotationZ(waist.Rotation); + + midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform); + if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f; + HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); + HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); } - else + if (Anim != Animation.UsingItem && character.SelectedBy == null) { - ResetPullJoints(l => l.IsLowerBody); + if (Anim != Animation.UsingItemWhileClimbing) + { + ResetPullJoints(); + } + else + { + ResetPullJoints(l => l.IsLowerBody); + } } } @@ -1489,17 +1491,17 @@ namespace Barotrauma public override void DragCharacter(Character target, float deltaTime) { - if (target == null) return; + if (target == null) { return; } Limb torso = GetLimb(LimbType.Torso); Limb leftHand = GetLimb(LimbType.LeftHand); Limb rightHand = GetLimb(LimbType.RightHand); - Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftHand); + Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftForearm); if (targetLeftHand == null) targetLeftHand = target.AnimController.GetLimb(LimbType.Torso); if (targetLeftHand == null) targetLeftHand = target.AnimController.MainLimb; - Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightHand); + Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightForearm); if (targetRightHand == null) targetRightHand = target.AnimController.GetLimb(LimbType.Torso); if (targetRightHand == null) targetRightHand = target.AnimController.MainLimb; @@ -1628,7 +1630,7 @@ namespace Barotrauma } //only pull with one hand when swimming - if (i > 0 && inWater) continue; + if (i > 0 && inWater) { continue; } Vector2 diff = ConvertUnits.ToSimUnits(targetLimb.WorldPosition - pullLimb.WorldPosition); @@ -1680,14 +1682,15 @@ namespace Barotrauma targetForce = 5000.0f; } - if (!target.AllowInput) - { - targetLimb.PullJointEnabled = true; - targetLimb.PullJointMaxForce = targetForce; - targetLimb.PullJointWorldAnchorB = targetAnchor; - } + targetLimb.PullJointEnabled = true; + targetLimb.PullJointMaxForce = targetForce; + targetLimb.PullJointWorldAnchorB = targetAnchor; + targetLimb.Disabled = true; - target.AnimController.movement = -diff; + if (diff.LengthSquared() > 0.1f) + { + target.AnimController.movement = -diff; + } } float dist = ConvertUnits.ToSimUnits(Vector2.Distance(target.WorldPosition, WorldPosition)); @@ -1714,7 +1717,8 @@ namespace Barotrauma else if (target is AICharacter && target != Character.Controlled) { target.AnimController.TargetDir = WorldPosition.X > target.WorldPosition.X ? Direction.Right : Direction.Left; - target.AnimController.TargetMovement = (character.SimPosition + Vector2.UnitX * Dir) - target.SimPosition; + Vector2 movement = (character.SimPosition + Vector2.UnitX * 0.5f * Dir) - target.SimPosition; + target.AnimController.TargetMovement = movement.LengthSquared() > 0.01f ? movement : Vector2.Zero; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index cdc68d100..dcb2deff3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -2969,7 +2969,7 @@ namespace Barotrauma } else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && PressureProtection < (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f) && - WorldPosition.Y < CharacterHealth.CrushDepth) + WorldPosition.Y < CharacterHealth.CrushDepth && !HasAbilityFlag(AbilityFlags.ImmuneToPressure)) { //implode if below crush depth, and either outside or in a high-pressure hull if (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f) @@ -4776,7 +4776,7 @@ namespace Barotrauma { foreach (TalentOption talentOption in talentSubTree.TalentOptionStages) { - if (talentOption.Talents.None(t => HasTalent(t.Identifier))) + if (talentOption.TalentIdentifiers.None(t => HasTalent(t))) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 8c37271bd..f2d68c7c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -809,8 +809,8 @@ namespace Barotrauma } HumanPrefabIds = ( - element.GetAttributeIdentifier("npcsetid", Identifier.Empty), - element.GetAttributeIdentifier("npcid", Identifier.Empty)); + infoElement.GetAttributeIdentifier("npcsetid", Identifier.Empty), + infoElement.GetAttributeIdentifier("npcid", Identifier.Empty)); MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index e91b9d77e..58c1de0d0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -1146,14 +1146,14 @@ namespace Barotrauma activeAfflictions.Add(affliction); } } - msg.Write((byte)activeAfflictions.Count); + msg.WriteByte((byte)activeAfflictions.Count); foreach (Affliction affliction in activeAfflictions) { - msg.Write(affliction.Prefab.UintIdentifier); + msg.WriteUInt32(affliction.Prefab.UintIdentifier); msg.WriteRangedSingle( MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength), 0.0f, affliction.Prefab.MaxStrength, 8); - msg.Write((byte)affliction.Prefab.PeriodicEffects.Count()); + msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count()); foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects) { msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8); @@ -1170,15 +1170,15 @@ namespace Barotrauma limbAfflictions.Add((limbHealth, limbAffliction)); } - msg.Write((byte)limbAfflictions.Count); + msg.WriteByte((byte)limbAfflictions.Count); foreach (var (limbHealth, affliction) in limbAfflictions) { msg.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1); - msg.Write(affliction.Prefab.UintIdentifier); + msg.WriteUInt32(affliction.Prefab.UintIdentifier); msg.WriteRangedSingle( MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength), 0.0f, affliction.Prefab.MaxStrength, 8); - msg.Write((byte)affliction.Prefab.PeriodicEffects.Count()); + msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count()); foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects) { msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index 1e041438a..e3d7bedf4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -85,7 +85,7 @@ namespace Barotrauma public readonly List<(XElement element, float commonness)> ItemSets = new List<(XElement element, float commonness)>(); public readonly List<(XElement element, float commonness)> CustomCharacterInfos = new List<(XElement element, float commonness)>(); - private readonly Identifier npcSetIdentifier; + public readonly Identifier NpcSetIdentifier; public HumanPrefab(ContentXElement element, ContentFile file, Identifier npcSetIdentifier) : base(file, element.GetAttributeIdentifier("identifier", "")) { @@ -94,7 +94,7 @@ namespace Barotrauma element.GetChildElements("itemset").ForEach(e => ItemSets.Add((e, e.GetAttributeFloat("commonness", 1)))); element.GetChildElements("character").ForEach(e => CustomCharacterInfos.Add((e, e.GetAttributeFloat("commonness", 1)))); PreferredOutpostModuleTypes = element.GetAttributeIdentifierArray("preferredoutpostmoduletypes", Array.Empty()); - this.npcSetIdentifier = npcSetIdentifier; + this.NpcSetIdentifier = npcSetIdentifier; } public IEnumerable GetModuleFlags() @@ -183,7 +183,7 @@ namespace Barotrauma { characterInfo = new CharacterInfo(characterElement, Identifier); } - characterInfo.HumanPrefabIds = (npcSetIdentifier, Identifier); + characterInfo.HumanPrefabIds = (NpcSetIdentifier, Identifier); return characterInfo; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs index de5597011..e3997e128 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs @@ -1,6 +1,4 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class AbilityConditionAboveVitality : AbilityConditionDataless { @@ -13,7 +11,7 @@ namespace Barotrauma.Abilities protected override bool MatchesConditionSpecific() { - return character.HealthPercentage / 100f > vitalityPercentage; + return character.Vitality / character.MaxVitality > vitalityPercentage; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAlliesAboveVitality.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAlliesAboveVitality.cs index 55a640a17..92f7af301 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAlliesAboveVitality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAlliesAboveVitality.cs @@ -1,11 +1,10 @@ using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Abilities { class AbilityConditionAlliesAboveVitality : AbilityConditionDataless { - float vitalityPercentage; + readonly float vitalityPercentage; public AbilityConditionAlliesAboveVitality(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCoauthor.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCoauthor.cs index 50c650f1d..b368fad6c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCoauthor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCoauthor.cs @@ -1,7 +1,4 @@ -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class AbilityConditionCoauthor : AbilityConditionDataless { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrouched.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrouched.cs index cc44ec6ac..e15b36dec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrouched.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrouched.cs @@ -1,7 +1,4 @@ -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class AbilityConditionCrouched : AbilityConditionDataless { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasAffliction.cs index 68c46cd12..8470362c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasAffliction.cs @@ -1,28 +1,22 @@ -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class AbilityConditionHasAffliction : AbilityConditionDataless { - private string afflictionIdentifier; - private float minimumPercentage; - + private readonly Identifier afflictionIdentifier; + private readonly float minimumPercentage; public AbilityConditionHasAffliction(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement) { - afflictionIdentifier = conditionElement.GetAttributeString("afflictionidentifier", ""); + afflictionIdentifier = conditionElement.GetAttributeIdentifier("afflictionidentifier", Identifier.Empty); minimumPercentage = conditionElement.GetAttributeFloat("minimumpercentage", 0f); } protected override bool MatchesConditionSpecific() { - if (!string.IsNullOrEmpty(afflictionIdentifier)) + if (!afflictionIdentifier.IsEmpty) { var affliction = character.CharacterHealth.GetAffliction(afflictionIdentifier); - if (affliction == null) { return false; } - return minimumPercentage <= affliction.Strength / affliction.Prefab.MaxStrength; } return false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs index 4407fcb18..04d7ebf62 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs @@ -3,55 +3,43 @@ using Barotrauma.Items.Components; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Barotrauma.Extensions; namespace Barotrauma.Abilities { class AbilityConditionHasItem : AbilityConditionDataless { - // not used for anything atm, will be used for clown subclass private readonly string[] tags; - private InvSlotType? invSlotType; - bool requireAll; - - private List items = new List(); + readonly bool requireAll; public AbilityConditionHasItem(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement) { - tags = conditionElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); + tags = conditionElement.GetAttributeStringArray("tags", Array.Empty()); requireAll = conditionElement.GetAttributeBool("requireall", false); - //this.invSlotType = invSlotType; } protected override bool MatchesConditionSpecific() { - items.Clear(); - if (tags.Any()) + if (tags.None()) { - foreach (string tag in tags) - { - // there is a better method, should use that instead - if (character.GetEquippedItem(tag, invSlotType) is Item foundItem) - { - items.Add(foundItem); - } - } - - } - else - { - if (character.GetEquippedItem(null, invSlotType) is Item foundItem) - { - items.Add(foundItem); - } + return character.GetEquippedItem(null) is Item; } if (requireAll) { - return (items.Count >= tags.Count()); + foreach (string tag in tags) + { + if (character.GetEquippedItem(tag) == null) { return false; } + } + return true; } else { - return items.Any(); + foreach (string tag in tags) + { + if (character.GetEquippedItem(tag) != null) { return true; } + } + return false; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs index d54fd0839..b6abddd3d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs @@ -1,7 +1,4 @@ -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class AbilityConditionHasVelocity : AbilityConditionDataless { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs index f75f98a89..a8d33433a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs @@ -1,7 +1,4 @@ -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class AbilityConditionShipFlooded : AbilityConditionDataless { @@ -14,8 +11,14 @@ namespace Barotrauma.Abilities protected override bool MatchesConditionSpecific() { if (!character.IsInFriendlySub) { return false; } - float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage); - return currentFloodPercentage / 100 > floodPercentage; + float waterVolume = 0.0f, totalVolume = 0.0f; + foreach (Hull hull in Hull.HullList) + { + if (hull.Submarine != character.Submarine) { continue; } + waterVolume += hull.WaterVolume; + totalVolume += hull.Volume; + } + return (waterVolume / totalVolume) > floodPercentage; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs index 29d1fdf3c..d17d62b98 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs @@ -1,7 +1,4 @@ -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityModifyStatToFlooding : CharacterAbility { @@ -22,8 +19,14 @@ namespace Barotrauma.Abilities if (conditionsMatched && Character.IsInFriendlySub) { - float currentFloodPercentage = Character.Submarine.GetHulls(false).Average(h => h.WaterPercentage); - lastValue = currentFloodPercentage / 100f * maxValue; + float waterVolume = 0.0f, totalVolume = 0.0f; + foreach (Hull hull in Hull.HullList) + { + if (hull.Submarine != Character.Submarine) { continue; } + waterVolume += hull.WaterVolume; + totalVolume += hull.Volume; + } + lastValue = (totalVolume == 0.0f ? 1.0f : waterVolume / totalVolume) * maxValue; Character.ChangeStat(statType, lastValue); } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs index c76b7fb01..e8d0ad788 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityUnlockTree : CharacterAbility { @@ -14,22 +10,19 @@ namespace Barotrauma.Abilities { if (!TalentTree.JobTalentTrees.TryGet(Character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } - var subTree = talentTree.TalentSubTrees.Find(t => t.TalentOptionStages.Any(ts => ts.Talents.Contains(CharacterTalent.Prefab))); + var subTree = talentTree.TalentSubTrees.Find(t => t.AllTalentIdentifiers.Contains(CharacterTalent.Prefab.Identifier)); if (subTree == null) { return; } subTree.ForceUnlock = true; if (!addingFirstTime) { return; } - foreach (var talentOption in subTree.TalentOptionStages) + foreach (var talentId in subTree.AllTalentIdentifiers) { - foreach (var talent in talentOption.Talents) + if (talentId == CharacterTalent.Prefab.Identifier) { continue; } + if (Character.GiveTalent(talentId)) { - if (talent == CharacterTalent.Prefab) { continue; } - if (Character.GiveTalent(talent)) - { - Character.Info.AdditionalTalentPoints++; - } - } + Character.Info.AdditionalTalentPoints++; + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs index 3ea78fd56..982cb2371 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs @@ -1,10 +1,4 @@ -using Barotrauma.Networking; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities +namespace Barotrauma.Abilities { class CharacterAbilityInsurancePolicy : CharacterAbility { @@ -19,10 +13,10 @@ namespace Barotrauma.Abilities protected override void ApplyEffect(AbilityObject abilityObject) { - if (Character?.Info is CharacterInfo info) + if (Character?.Info is CharacterInfo info && GameMain.GameSession?.GameMode is CampaignMode campaign) { int totalAmount = moneyPerMission * info.MissionsCompletedSinceDeath; - Character.GiveMoney(totalAmount); + campaign.Bank.Give(totalAmount); GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier.Value); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs index 656e5d751..bd4b45998 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs @@ -15,7 +15,7 @@ namespace Barotrauma.Abilities public readonly AbilityEffectType AbilityEffectType; - protected int maxTriggerCount { get; } + protected readonly int maxTriggerCount; protected int timesTriggered = 0; @@ -88,8 +88,6 @@ namespace Barotrauma.Abilities // XML private AbilityCondition ConstructCondition(CharacterTalent characterTalent, ContentXElement conditionElement, bool errorMessages = true) { - AbilityCondition newCondition = null; - Type conditionType; string type = conditionElement.Name.ToString().ToLowerInvariant(); try @@ -109,6 +107,7 @@ namespace Barotrauma.Abilities object[] args = { characterTalent, conditionElement }; + AbilityCondition newCondition; try { newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args); @@ -210,8 +209,7 @@ namespace Barotrauma.Abilities public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier) { - AbilityFlags flagType = AbilityFlags.None; - if (!Enum.TryParse(flagTypeString, true, out flagType)) + if (!Enum.TryParse(flagTypeString, true, out AbilityFlags flagType)) { DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 0d6c08118..e3379314f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Xml.Linq; namespace Barotrauma { @@ -19,7 +18,12 @@ namespace Barotrauma public static readonly PrefabCollection JobTalentTrees = new PrefabCollection(); - public readonly List TalentSubTrees = new List(); + public readonly ImmutableArray TalentSubTrees; + + /// + /// Talent identifiers of all the talents in this tree + /// + public readonly ImmutableHashSet AllTalentIdentifiers; public ContentXElement ConfigElement { @@ -36,16 +40,19 @@ namespace Barotrauma DebugConsole.ThrowError($"No job defined for talent tree in \"{file.Path}\"!"); return; } - + + List subTrees = new List(); foreach (var subTreeElement in element.GetChildElements("subtree")) { - TalentSubTrees.Add(new TalentSubTree(subTreeElement)); + subTrees.Add(new TalentSubTree(subTreeElement)); } + TalentSubTrees = subTrees.ToImmutableArray(); + AllTalentIdentifiers = TalentSubTrees.SelectMany(t => t.AllTalentIdentifiers).ToImmutableHashSet(); } public bool TalentIsInTree(Identifier talentIdentifier) { - return TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))).Any(c => c == talentIdentifier); + return AllTalentIdentifiers.Contains(talentIdentifier); } public static bool IsViableTalentForCharacter(Character character, Identifier talentIdentifier) @@ -54,6 +61,7 @@ namespace Barotrauma } // i hate this function - markus + // me too - joonas public static TalentTreeStageState GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, List selectedTalents) { if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; } @@ -66,12 +74,12 @@ namespace Barotrauma TalentOption targetTalentOption = subTree.TalentOptionStages[index]; - if (targetTalentOption.Talents.Any(t => character.HasTalent(t.Identifier))) + if (targetTalentOption.TalentIdentifiers.Any(t => character.HasTalent(t))) { return TalentTreeStageState.Unlocked; } - if (targetTalentOption.Talents.Any(t => selectedTalents.Contains(t.Identifier))) + if (targetTalentOption.TalentIdentifiers.Any(t => selectedTalents.Contains(t))) { return TalentTreeStageState.Highlighted; } @@ -83,8 +91,8 @@ namespace Barotrauma if (lastindex >= 0) { TalentOption lastLatentOption = subTree.TalentOptionStages[lastindex]; - hasTalentInLastTier = lastLatentOption.Talents.Any(HasTalent); - isLastTalentPurchased = lastLatentOption.Talents.Any(t => character.HasTalent(t.Identifier)); + hasTalentInLastTier = lastLatentOption.TalentIdentifiers.Any(HasTalent); + isLastTalentPurchased = lastLatentOption.TalentIdentifiers.Any(t => character.HasTalent(t)); } if (!hasTalentInLastTier) @@ -101,9 +109,9 @@ namespace Barotrauma return TalentTreeStageState.Locked; - bool HasTalent(TalentPrefab t) + bool HasTalent(Identifier talentId) { - return selectedTalents.Contains(t.Identifier); + return selectedTalents.Contains(talentId); } } @@ -117,14 +125,14 @@ namespace Barotrauma foreach (var subTree in talentTree.TalentSubTrees) { - if (subTree.ForceUnlock && subTree.TalentOptionStages.Any(option => option.Talents.Any(t => t.Identifier == talentIdentifier))) { return true; } + if (subTree.ForceUnlock && subTree.TalentOptionStages.Any(option => option.TalentIdentifiers.Contains(talentIdentifier))) { return true; } foreach (var talentOptionStage in subTree.TalentOptionStages) { - bool hasTalentInThisTier = talentOptionStage.Talents.Any(t => selectedTalents.Contains(t.Identifier)); + bool hasTalentInThisTier = talentOptionStage.TalentIdentifiers.Any(t => selectedTalents.Contains(t)); if (!hasTalentInThisTier) { - if (talentOptionStage.Talents.Any(t => t.Identifier == talentIdentifier)) + if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier)) { return true; } @@ -170,18 +178,21 @@ namespace Barotrauma public bool ForceUnlock; - public readonly List TalentOptionStages = new List(); + public readonly ImmutableArray TalentOptionStages; + + public readonly ImmutableHashSet AllTalentIdentifiers; public TalentSubTree(ContentXElement subTreeElement) { Identifier = subTreeElement.GetAttributeIdentifier("identifier", ""); - DisplayName = TextManager.Get("talenttree." + Identifier).Fallback(Identifier.Value); - + List talentOptionStages = new List(); foreach (var talentOptionsElement in subTreeElement.GetChildElements("talentoptions")) { - TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier)); + talentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier)); } + TalentOptionStages = talentOptionStages.ToImmutableArray(); + AllTalentIdentifiers = TalentOptionStages.SelectMany(t => t.TalentIdentifiers).ToImmutableHashSet(); } } @@ -190,8 +201,12 @@ namespace Barotrauma { private readonly ImmutableHashSet talentIdentifiers; - public IEnumerable Talents - => talentIdentifiers.Select(id => TalentPrefab.TalentPrefabs[id]); + public IEnumerable TalentIdentifiers => talentIdentifiers; + + public bool HasTalent(Identifier talentIdentifier) + { + return talentIdentifiers.Contains(talentIdentifier); + } public TalentOption(ContentXElement talentOptionsElement, Identifier debugIdentifier) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TutorialsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TutorialsFile.cs new file mode 100644 index 000000000..a2409d954 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TutorialsFile.cs @@ -0,0 +1,16 @@ +namespace Barotrauma +{ + [RequiredByCorePackage] + sealed class TutorialsFile : GenericPrefabFile + { + protected override PrefabCollection Prefabs => TutorialPrefab.Prefabs; + + public TutorialsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { } + + protected override bool MatchesSingular(Identifier identifier) => identifier == "Tutorial"; + + protected override bool MatchesPlural(Identifier identifier) => identifier == "Tutorials"; + + protected override TutorialPrefab CreatePrefab(ContentXElement element) => new TutorialPrefab(this, element); + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index fc7e77728..ad6fcde98 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -965,16 +965,10 @@ namespace Barotrauma foreach (var talentTree in talentTrees) { - foreach (var subTree in talentTree.TalentSubTrees) + foreach (var talentId in talentTree.AllTalentIdentifiers) { - foreach (var option in subTree.TalentOptionStages) - { - foreach (var talent in option.Talents) - { - character.GiveTalent(talent); - NewMessage($"Unlocked talent \"{talent.DisplayName}\"."); - } - } + character.GiveTalent(talentId); + NewMessage($"Unlocked talent \"{talentId}\"."); } } }, @@ -1304,102 +1298,7 @@ namespace Barotrauma } } }, null, true)); - - commands.Add(new Command("upgradeitem", "upgradeitem [upgrade] [level] [items]: Adds an upgrade to the current targeted item.", args => - { - if (args.Length > 0) - { - int level; - if (args.Length > 1) - { - if (int.TryParse(args[1], out int result)) - { - level = result; - } - else - { - ThrowError($"\"{args[1]}\" is not a valid level."); - return; - } - - } - else - { - ThrowError("Parameter \"level\" is required."); - return; - } - var upgradePrefab = UpgradePrefab.Find(args[0].ToIdentifier()); - - if (upgradePrefab == null) - { - ThrowError($"Unknown upgrade: {args[0]}."); - return; - } - - List targetItems = new List(); - - if (upgradePrefab.IsWallUpgrade) - { - targetItems.AddRange(Submarine.MainSub.GetWalls(true).Cast()); - } - else - { - if (args.Length > 2) - { - targetItems.AddRange(Item.ItemList.Where(item => item.Submarine == Submarine.MainSub).Where(item => item.HasTag(args[2])).Cast()); - } - else - { - ThrowError("Argument \"tag\" is required."); - return; - } - } - - if (!targetItems.Any()) - { - ThrowError("No valid items found."); - return; - } - - foreach (MapEntity targetItem in targetItems) - { - Upgrade existingUpgrade = targetItem.GetUpgrade(args[0].ToIdentifier()); - - if (!(targetItem is ISerializableEntity sEntity)) { continue; } - - var upgrade = new Upgrade(sEntity, upgradePrefab, level); - if (targetItem.AddUpgrade(upgrade, true)) - { - if (existingUpgrade == null) - { - NewMessage($"Added {upgradePrefab.Identifier}:{level} to {sEntity.Name}.", Color.Green); - upgrade.ApplyUpgrade(); - } - else - { - NewMessage($"Set {sEntity.Name}'s {upgradePrefab.Identifier} upgrade to level {existingUpgrade.Level}.", Color.Cyan); - existingUpgrade.ApplyUpgrade(); - } - } - else - { - ThrowError($"{upgrade.Prefab.Identifier} cannot be applied to {sEntity.Name}"); - } - } - } - else - { - ThrowError("Parameter \"upgrade\" is required."); - } - }, () => - { - return new[] - { - UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().Select(i => i.Value).ToArray() - }; - }, true)); - commands.Add(new Command("maxupgrades", "maxupgrades [category] [prefab]: Maxes out all upgrades or only specific one if given arguments.", args => { UpgradeManager upgradeManager = GameMain.GameSession?.Campaign?.UpgradeManager; @@ -2362,7 +2261,7 @@ namespace Barotrauma { NewMessage(msg, color.Value, isCommand: false, isError: false); } -#if DEBUG +#if DEBUG && CLIENT Console.WriteLine(msg); #endif } @@ -2547,7 +2446,7 @@ namespace Barotrauma #endif fileName += DateTime.Now.ToShortDateString() + "_" + DateTime.Now.ToShortTimeString(); - var invalidChars = Path.GetInvalidFileNameChars(); + var invalidChars = Path.GetInvalidFileNameCharsCrossPlatform(); foreach (char invalidChar in invalidChars) { fileName = fileName.Replace(invalidChar.ToString(), ""); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckAfflictionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckAfflictionAction.cs index 1aca440b3..953d6f31f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckAfflictionAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckAfflictionAction.cs @@ -1,8 +1,6 @@ #nullable enable -using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma { @@ -20,6 +18,9 @@ namespace Barotrauma [Serialize(true, IsPropertySaveable.Yes, "When set to false when TargetLimb is not specified prevent checking limb-specific afflictions")] public bool AllowLimbAfflictions { get; set; } + [Serialize(0.0f, IsPropertySaveable.Yes, "Minimum strength of the affliction")] + public float MinStrength { get; set; } + public CheckAfflictionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } protected override bool? DetermineSuccess() @@ -32,14 +33,15 @@ namespace Barotrauma if (target.CharacterHealth == null) { continue; } if (TargetLimb == LimbType.None) { - if (target.CharacterHealth.GetAffliction(Identifier, AllowLimbAfflictions) != null) { return true; } + var affliction = target.CharacterHealth.GetAffliction(Identifier, AllowLimbAfflictions); + if (affliction != null && affliction.Strength >= MinStrength) { return true; } } IEnumerable afflictions = target.CharacterHealth.GetAllAfflictions().Where(affliction => { LimbType? limbType = target.CharacterHealth.GetAfflictionLimb(affliction)?.type; if (limbType == null) { return false; } - return limbType == TargetLimb || true; + return limbType == TargetLimb && affliction.Strength >= MinStrength; }); if (afflictions.Any(a => a.Identifier == Identifier)) { return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs new file mode 100644 index 000000000..cd9087d16 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs @@ -0,0 +1,71 @@ +using System.Xml.Linq; + +namespace Barotrauma +{ + class CheckConditionalAction : BinaryOptionAction + { + [Serialize("", IsPropertySaveable.Yes)] + public Identifier TargetTag { get; set; } + + private PropertyConditional Conditional { get; } + + public CheckConditionalAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) + { + if (TargetTag.IsEmpty) + { + DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no target tag! This will cause the check to automatically succeed."); + } + foreach (var attribute in element.Attributes()) + { + if (PropertyConditional.IsValid(attribute) && !IsTargetTagAttribute(attribute)) + { + Conditional = new PropertyConditional(attribute); + break; + } + } + if (Conditional == null) + { + DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed."); + } + + static bool IsTargetTagAttribute(XAttribute attribute) + { + return attribute.Name.ToString().Equals("targettag", System.StringComparison.OrdinalIgnoreCase); + } + } + + private string GetEventName() + { + return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event"; + } + + protected override bool? DetermineSuccess() + { + ISerializableEntity target = null; + if (!TargetTag.IsEmpty) + { + foreach (var t in ParentEvent.GetTargets(TargetTag)) + { + if (t is ISerializableEntity e) + { + target = e; + break; + } + } + } + if (target == null) + { + DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed."); + } + if (target == null || Conditional == null) + { + return true; + } + if (target is Item item) + { + return item.ConditionalMatches(Conditional); + } + return Conditional.Matches(target); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs new file mode 100644 index 000000000..8413b6746 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs @@ -0,0 +1,59 @@ +namespace Barotrauma +{ + class CheckOrderAction : BinaryOptionAction + { + [Serialize("", IsPropertySaveable.Yes)] + public Identifier TargetTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier OrderIdentifier { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier OrderOption { get; set; } + + public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + + protected override bool? DetermineSuccess() + { + ISerializableEntity target = null; + if (!TargetTag.IsEmpty) + { + foreach (var t in ParentEvent.GetTargets(TargetTag)) + { + if (t is ISerializableEntity e) + { + target = e; + break; + } + } + } + if (target == null) + { + DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed."); + return true; + } + if (target is Character character) + { + var currentOrderInfo = character.GetCurrentOrderWithTopPriority(); + if (currentOrderInfo?.Identifier == OrderIdentifier) + { + if (OrderOption.IsEmpty) + { + return true; + } + else + { + return currentOrderInfo?.Option == OrderOption; + } + } + return false; + } + return true; + } + + private string GetEventName() + { + return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs new file mode 100644 index 000000000..1577c7514 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; + +namespace Barotrauma +{ + class MessageBoxAction : EventAction + { + [Serialize("", IsPropertySaveable.Yes)] + public Identifier Header { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier Text { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public string IconStyle { get; set; } + + [Serialize(false, IsPropertySaveable.Yes)] + public bool HideCloseButton { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier TargetTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public string CloseOnInput { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier CloseOnInteractTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier CloseOnPickUpTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier CloseOnEquipTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier CloseOnExitRoomName { get; set; } + + [Serialize(false, IsPropertySaveable.Yes)] + public bool IsTutorialObjective { get; set; } + + private bool isFinished = false; + + public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } +#if CLIENT + CreateMessageBox(); + if (IsTutorialObjective && GameMain.GameSession?.GameMode is TutorialMode tutorialMode) + { + tutorialMode.Tutorial?.TriggerTutorialSegment(new Tutorials.Tutorial.Segment(Text, CreateMessageBox)); + } +#endif + isFinished = true; + } + +#if CLIENT + public void CreateMessageBox() + { + new GUIMessageBox( + headerText: TextManager.Get(Header), + text: RichString.Rich(TextManager.ParseInputTypes(TextManager.Get(Text).Fallback(Text.ToString()), useColorHighlight: true)), + buttons: Array.Empty(), + type: GUIMessageBox.Type.Tutorial, + iconStyle: IconStyle, + autoCloseCondition: GetAutoCloseCondition(), + hideCloseButton: HideCloseButton); + } +#endif + + private Func GetAutoCloseCondition() + { + var character = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Character; + Func autoCloseCondition = null; + if (!string.IsNullOrEmpty(CloseOnInput) && Enum.TryParse(CloseOnInput, true, out InputType closeOnInput)) + { +#if CLIENT + autoCloseCondition = () => PlayerInput.KeyDown(closeOnInput); +#endif + } + else if (!CloseOnInteractTag.IsEmpty) + { + autoCloseCondition = () => character?.SelectedItem != null && character.SelectedItem.HasTag(CloseOnInteractTag); + } + else if (!CloseOnPickUpTag.IsEmpty) + { + autoCloseCondition = () => character?.Inventory != null && character.Inventory.FindItemByTag(CloseOnPickUpTag, recursive: true) != null; + } + else if (!CloseOnEquipTag.IsEmpty) + { + autoCloseCondition = () => character != null && character.HasEquippedItem(CloseOnEquipTag); + } + else if (!CloseOnExitRoomName.IsEmpty) + { + autoCloseCondition = () => character?.CurrentHull != null && character.CurrentHull.RoomName.ToIdentifier() != CloseOnExitRoomName; + } + return autoCloseCondition; + } + + public override bool IsFinished(ref string goToLabel) + { + return isFinished; + } + + public override void Reset() + { + isFinished = false; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MessageBoxAction)}"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs index d4d95ebbb..ff3d4d9bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs @@ -145,9 +145,9 @@ namespace Barotrauma foreach (Client client in GameMain.Server.ConnectedClients) { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ServerPacketHeader.EVENTACTION); - outmsg.Write((byte)EventManager.NetworkEventType.MISSION); - outmsg.Write(prefab.Identifier); + outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION); + outmsg.WriteByte((byte)EventManager.NetworkEventType.MISSION); + outmsg.WriteIdentifier(prefab.Identifier); GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index 5250521eb..3bd798efd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -57,6 +57,9 @@ namespace Barotrauma private readonly HashSet targetModuleTags = new HashSet(); + [Serialize(true, IsPropertySaveable.Yes, description: "If false, we won't spawn another character if one with the same identifier has already been spawned.")] + public bool AllowDuplicates { get; set; } + [Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the entity prefer to spawn in.")] public string TargetModuleTags { @@ -115,6 +118,12 @@ namespace Barotrauma HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier); if (humanPrefab != null) { + if (!AllowDuplicates && + Character.CharacterList.Any(c => c.Info?.HumanPrefabIds.NpcIdentifier == NPCIdentifier && c.Info?.HumanPrefabIds.NpcSetIdentifier == NPCSetIdentifier)) + { + spawned = true; + return; + } ISpatialEntity spawnPos = GetSpawnPos(); if (spawnPos != null) { @@ -145,6 +154,11 @@ namespace Barotrauma } else if (!SpeciesName.IsEmpty) { + if (!AllowDuplicates && Character.CharacterList.Any(c => c.SpeciesName == SpeciesName)) + { + spawned = true; + return; + } ISpatialEntity spawnPos = GetSpawnPos(); if (spawnPos != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs index 377fa8eb0..208a7fbfa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs @@ -1,10 +1,7 @@ using Barotrauma.Extensions; using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Xml.Linq; namespace Barotrauma { @@ -36,6 +33,7 @@ namespace Barotrauma ("crew", v => TagCrew()), ("humanprefabidentifier", TagHumansByIdentifier), ("structureidentifier", TagStructuresByIdentifier), + ("structurespecialtag", TagStructuresBySpecialTag), ("itemidentifier", TagItemsByIdentifier), ("itemtag", TagItemsByTag), ("hullname", TagHullsByName) @@ -100,6 +98,11 @@ namespace Barotrauma ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier); } + private void TagStructuresBySpecialTag(Identifier tag) + { + ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.SpecialTag.ToIdentifier() == tag); + } + private void TagItemsByIdentifier(Identifier identifier) { ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.Prefab.Identifier == identifier); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialCompleteAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialCompleteAction.cs new file mode 100644 index 000000000..9544f9371 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialCompleteAction.cs @@ -0,0 +1,32 @@ +namespace Barotrauma +{ + class TutorialCompleteAction : EventAction + { + private bool isFinished; + + public TutorialCompleteAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + +#if CLIENT + if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode) + { + tutorialMode.Tutorial?.Complete(); + } +#endif + isFinished = true; + } + + public override bool IsFinished(ref string goToLabel) + { + return isFinished; + } + + public override void Reset() + { + isFinished = false; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs new file mode 100644 index 000000000..a7e6a7658 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs @@ -0,0 +1,95 @@ +#if CLIENT +using Barotrauma.Tutorials; +#endif + +namespace Barotrauma +{ + class TutorialSegmentAction : EventAction + { + public enum SegmentActionType { Trigger, Complete, Remove }; + + [Serialize(SegmentActionType.Trigger, IsPropertySaveable.Yes)] + public SegmentActionType Type { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier Id { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier ObjectiveTextTag { get; set; } + + [Serialize(false, IsPropertySaveable.Yes)] + public bool AutoPlayVideo { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier TextTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public string VideoFile { get; set; } + + [Serialize(450, IsPropertySaveable.Yes)] + public int Width { get; set; } + + [Serialize(80, IsPropertySaveable.Yes)] + public int Height { get; set; } + +#if CLIENT + private readonly Tutorial.Segment segment; +#endif + private bool isFinished; + + public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) + { +#if CLIENT + // Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance) + if (Type == SegmentActionType.Trigger) + { + segment = new Tutorial.Segment(Id, ObjectiveTextTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No, + new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center), + new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height)); + } +#endif + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + +#if CLIENT + if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode) + { + if (tutorialMode.Tutorial is Tutorial tutorial) + { + switch (Type) + { + case SegmentActionType.Trigger: + tutorial.TriggerTutorialSegment(segment); + break; + case SegmentActionType.Complete: + tutorial.CompleteTutorialSegment(Id); + break; + case SegmentActionType.Remove: + tutorial.RemoveTutorialSegment(Id); + break; + } + } + } + else + { + DebugConsole.ShowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!"); + } +#endif + + isFinished = true; + } + + public override bool IsFinished(ref string goToLabel) + { + return isFinished; + } + + public override void Reset() + { + isFinished = false; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UnlockPathAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UnlockPathAction.cs index ac2b2fe9f..a69ee509a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UnlockPathAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UnlockPathAction.cs @@ -55,9 +55,9 @@ namespace Barotrauma foreach (Client client in GameMain.Server.ConnectedClients) { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ServerPacketHeader.EVENTACTION); - outmsg.Write((byte)EventManager.NetworkEventType.UNLOCKPATH); - outmsg.Write((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection)); + outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION); + outmsg.WriteByte((byte)EventManager.NetworkEventType.UNLOCKPATH); + outmsg.WriteUInt16((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection)); GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs index 9e2e4c921..4812d5085 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs @@ -109,6 +109,7 @@ namespace Barotrauma subs[1].FlipX(); #if SERVER crews = new List[] { new List(), new List() }; + roundEndTimer = RoundEndDuration; #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs index c4a787a40..d0a99fe80 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs @@ -3,7 +3,9 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using PositionType = Barotrauma.Level.PositionType; namespace Barotrauma { @@ -29,6 +31,25 @@ namespace Barotrauma private readonly HashSet caves = new HashSet(); + private readonly PositionType positionType = PositionType.Cave; + /// + /// The list order is important. + /// It defines the order in which we "override" in case no valid position types are found + /// in the level when generating them in . + /// + public static readonly ImmutableArray ValidPositionTypes = new PositionType[] + { + PositionType.Cave, + PositionType.SidePath, + PositionType.MainPath, + PositionType.AbyssCave, + }.ToImmutableArray(); + + /// + /// Percentage. Value between 0 and 1. + /// + private readonly float resourceHandoverAmount; + public override IEnumerable SonarPositions { get @@ -39,8 +60,23 @@ namespace Barotrauma } } + public override LocalizedString SuccessMessage => ModifyMessage(base.SuccessMessage); + public override LocalizedString FailureMessage => ModifyMessage(base.FailureMessage); + public override LocalizedString Description => ModifyMessage(description); + public override LocalizedString Name => ModifyMessage(base.Name, false); + public override LocalizedString SonarLabel => ModifyMessage(base.SonarLabel, false); + public MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) { + var positionType = prefab.ConfigElement.GetAttributeEnum("PositionType", in this.positionType); + if (ValidPositionTypes.Contains(positionType)) + { + this.positionType = positionType; + } + + float handoverAmount = prefab.ConfigElement.GetAttributeFloat("ResourceHandoverAmount", 0.0f); + resourceHandoverAmount = Math.Clamp(handoverAmount, 0.0f, 1.0f); + var configElement = prefab.ConfigElement.GetChildElement("Items"); foreach (var c in configElement.GetChildElements("Item")) { @@ -92,27 +128,28 @@ namespace Barotrauma caves.Clear(); if (IsClient) { return; } - foreach (var kvp in resourceClusters) + + foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters) { - var prefab = ItemPrefab.Find(null, kvp.Key); - if (prefab == null) + if (!(MapEntityPrefab.FindByIdentifier(identifier) is ItemPrefab prefab)) { - DebugConsole.ThrowError("Error in MineralMission - " + - "couldn't find an item prefab with the identifier " + kvp.Key); + DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")"); continue; } - var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.Amount, out float rotation); - if (spawnedResources.Count < kvp.Value.Amount) - { - DebugConsole.ThrowError("Error in MineralMission - " + - "spawned " + spawnedResources.Count + "/" + kvp.Value.Amount + " of " + prefab.Name); - } - if (spawnedResources.None()) { continue; } - this.spawnedResources.Add(kvp.Key, spawnedResources); - foreach (Level.Cave cave in Level.Loaded.Caves) + var spawnedResources = level.GenerateMissionResources(prefab, cluster.Amount, positionType, out float rotation); + if (spawnedResources.Count < cluster.Amount) { - foreach (Item spawnedResource in spawnedResources) + DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{cluster.Amount} of {prefab.Name}"); + } + + if (spawnedResources.None()) { continue; } + + this.spawnedResources.Add(identifier, spawnedResources); + + foreach (var cave in Level.Loaded.Caves) + { + foreach (var spawnedResource in spawnedResources) { if (cave.Area.Contains(spawnedResource.WorldPosition)) { @@ -123,6 +160,7 @@ namespace Barotrauma } } } + CalculateMissionClusterPositions(); FindRelevantLevelResources(); } @@ -151,6 +189,28 @@ namespace Barotrauma { ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); } + if (!IsClient) + { + // When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission) + var handoverResources = new List(); + foreach (Identifier identifier in resourceClusters.Keys) + { + if (relevantLevelResources.TryGetValue(identifier, out var availableResources)) + { + var collectedResources = availableResources.Where(HasBeenCollected); + if (collectedResources.Count() < 1) { continue; } + int handoverCount = (int)MathF.Round(resourceHandoverAmount * collectedResources.Count()); + for (int i = 0; i < handoverCount; i++) + { + handoverResources.Add(collectedResources.ElementAt(i)); + } + } + } + foreach (var resource in handoverResources) + { + resource.Remove(); + } + } GiveReward(); completed = true; } @@ -237,5 +297,27 @@ namespace Barotrauma missionClusterPositions.Add((kvp.Key, pos)); } } + + protected override LocalizedString ModifyMessage(LocalizedString message, bool color = true) + { + int i = 1; + foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters) + { + Replace($"[resourcename{i}]", ItemPrefab.FindByIdentifier(identifier)?.Name.Value ?? ""); + Replace($"[resourcequantity{i}]", cluster.Amount.ToString()); + i++; + } + Replace("[handoverpercentage]", ToolBox.GetFormattedPercentage(resourceHandoverAmount)); + return message; + + void Replace(string find, string replace) + { + if (color) + { + replace = $"‖color:gui.orange‖{replace}‖end‖"; + } + message = message.Replace(find, replace); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index e26735cc3..497992319 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -41,7 +41,7 @@ namespace Barotrauma public readonly ImmutableArray Headers; public readonly ImmutableArray Messages; - public LocalizedString Name => Prefab.Name; + public virtual LocalizedString Name => Prefab.Name; private readonly LocalizedString successMessage; public virtual LocalizedString SuccessMessage @@ -276,6 +276,10 @@ namespace Barotrauma partial void ShowMessageProjSpecific(int missionState); + protected virtual LocalizedString ModifyMessage(LocalizedString message, bool color = true) + { + return message; + } private void TryTriggerEvents(int state) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 421af50e0..8671f08d3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -1,4 +1,7 @@ using Barotrauma.Extensions; +#if CLIENT +using Barotrauma.Tutorials; +#endif using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -348,6 +351,9 @@ namespace Barotrauma private void UpdateConversations(float deltaTime) { if (GameMain.GameSession?.GameMode?.Preset == GameModePreset.TestMode) { return; } +#if CLIENT + if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial is Tutorial tutorial && tutorial.TutorialPrefab.DisableBotConversations) { return; } +#endif if (GameMain.NetworkMember != null && GameMain.NetworkMember.ServerSettings.DisableBotConversations) { return; } conversationTimer -= deltaTime; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs index 32fbcd28a..9e1e19aa6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -10,8 +10,6 @@ namespace Barotrauma { partial class MultiPlayerCampaign : CampaignMode { - public const int MinimumInitialMoney = 500; - [Flags] public enum NetFlags : UInt16 { @@ -294,14 +292,14 @@ namespace Barotrauma private static void WriteItems(IWriteMessage msg, Dictionary> purchasedItems) { - msg.Write((byte)purchasedItems.Count); + msg.WriteByte((byte)purchasedItems.Count); foreach (var storeItems in purchasedItems) { - msg.Write(storeItems.Key); - msg.Write((UInt16)storeItems.Value.Count); + msg.WriteIdentifier(storeItems.Key); + msg.WriteUInt16((UInt16)storeItems.Value.Count); foreach (var item in storeItems.Value) { - msg.Write(item.ItemPrefabIdentifier); + msg.WriteIdentifier(item.ItemPrefabIdentifier); msg.WriteRangedInteger(item.Quantity, 0, CargoManager.MaxQuantity); } } @@ -328,18 +326,18 @@ namespace Barotrauma private static void WriteItems(IWriteMessage msg, Dictionary> soldItems) { - msg.Write((byte)soldItems.Count); + msg.WriteByte((byte)soldItems.Count); foreach (var storeItems in soldItems) { - msg.Write(storeItems.Key); - msg.Write((UInt16)storeItems.Value.Count); + msg.WriteIdentifier(storeItems.Key); + msg.WriteUInt16((UInt16)storeItems.Value.Count); foreach (var item in storeItems.Value) { - msg.Write(item.ItemPrefab.Identifier); - msg.Write((UInt16)item.ID); - msg.Write(item.Removed); - msg.Write(item.SellerID); - msg.Write((byte)item.Origin); + msg.WriteIdentifier(item.ItemPrefab.Identifier); + msg.WriteUInt16((UInt16)item.ID); + msg.WriteBoolean(item.Removed); + msg.WriteByte(item.SellerID); + msg.WriteByte((byte)item.Origin); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/PvPMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/PvPMode.cs index 1ac387dee..4599c3857 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/PvPMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/PvPMode.cs @@ -1,5 +1,7 @@ +using Barotrauma.Extensions; using Barotrauma.Networking; using System.Collections.Generic; +using System.Linq; namespace Barotrauma { @@ -11,37 +13,37 @@ namespace Barotrauma public void AssignTeamIDs(IEnumerable clients) { - int teamWeight = 0; - List randList = new List(clients); - for (int i = 0; i < randList.Count; i++) + int team1Count = 0, team2Count = 0; + //if a client has a preference, assign them to that team + List unassignedClients = new List(clients); + for (int i = 0; i < unassignedClients.Count; i++) { - if (randList[i].PreferredTeam == CharacterTeamType.Team1 || - randList[i].PreferredTeam == CharacterTeamType.Team2) + if (unassignedClients[i].PreferredTeam == CharacterTeamType.Team1 || + unassignedClients[i].PreferredTeam == CharacterTeamType.Team2) { - randList[i].TeamID = randList[i].PreferredTeam; - teamWeight += randList[i].PreferredTeam == CharacterTeamType.Team1 ? -1 : 1; - randList.RemoveAt(i); + assignTeam(unassignedClients[i], unassignedClients[i].PreferredTeam); i--; } } - for (int i = 0; i Prefabs = new PrefabCollection(); + + public readonly int Order; + public readonly bool DisableBotConversations; + public readonly bool AllowCharacterSwitch; + + public readonly ContentPath SubmarinePath = ContentPath.FromRaw("Content/Tutorials/Dugong_Tutorial.sub"); + public readonly ContentPath OutpostPath = ContentPath.FromRaw("Content/Tutorials/TutorialOutpost.sub"); + public readonly string LevelSeed; + public readonly string LevelParams; + + private readonly ContentXElement tutorialCharacterElement; + public readonly ImmutableArray StartingItemTags; + + public readonly Identifier EventIdentifier; + + public TutorialPrefab(ContentFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", "")) + { + Order = element.GetAttributeInt("order", int.MaxValue); + DisableBotConversations = element.GetAttributeBool("disablebotconversations", true); + AllowCharacterSwitch = element.GetAttributeBool("allowcharacterswitch", false); + + SubmarinePath = element.GetAttributeContentPath("submarinepath") ?? SubmarinePath; + OutpostPath = element.GetAttributeContentPath("outpostpath") ?? OutpostPath; + LevelSeed = element.GetAttributeString("levelseed", "nLoZLLtza"); + LevelParams = element.GetAttributeString("levelparams", "ColdCavernsTutorial"); + + tutorialCharacterElement = element.GetChildElement("characterinfo"); + if (tutorialCharacterElement != null) + { + StartingItemTags = tutorialCharacterElement + .GetAttributeIdentifierArray("startingitemtags", new Identifier[0]) + .ToImmutableArray(); + } + else + { + StartingItemTags = ImmutableArray.Empty; + } + + EventIdentifier = element.GetChildElement("scriptedevent")?.GetAttributeIdentifier("identifier", "") ?? Identifier.Empty; + } + + public CharacterInfo GetTutorialCharacterInfo() + { + if (tutorialCharacterElement == null) + { + return null; + } + Identifier speciesName = tutorialCharacterElement.GetAttributeIdentifier("speciesname", CharacterPrefab.HumanSpeciesName); + string jobPrefabIdentifier = tutorialCharacterElement.GetAttributeString("jobidentifier", "assistant"); + var jobPrefab = JobPrefab.Prefabs.FirstOrDefault(p => p.Identifier == jobPrefabIdentifier) ?? JobPrefab.Prefabs.First(); + int jobVariant = tutorialCharacterElement.GetAttributeInt("variant", 0); + var characterInfo = new CharacterInfo(speciesName, jobOrJobPrefab: jobPrefab, variant: jobVariant); + foreach (var skillElement in tutorialCharacterElement.GetChildElements("skill")) + { + Identifier skillIdentifier = skillElement.GetAttributeIdentifier("identifier", ""); + if (skillIdentifier.IsEmpty) { continue; } + float level = skillElement.GetAttributeFloat("level", 0.0f); + characterInfo.SetSkillLevel(skillIdentifier, level); + } + return characterInfo; + } + + public override void Dispose() { } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index bb0dfda53..1b56b6f5c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -211,7 +211,7 @@ namespace Barotrauma if (selectedSub != null) { campaign.Bank.Deduct(selectedSub.Price); - campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, MultiPlayerCampaign.MinimumInitialMoney); + campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, 0); } return campaign; } @@ -222,7 +222,7 @@ namespace Barotrauma if (selectedSub != null) { campaign.Bank.TryDeduct(selectedSub.Price); - campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, MultiPlayerCampaign.MinimumInitialMoney); + campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, 0); } return campaign; } @@ -602,7 +602,7 @@ namespace Barotrauma if (GameMode is MultiPlayerCampaign mpCampaign) { mpCampaign.UpgradeManager.ApplyUpgrades(); - mpCampaign.UpgradeManager.SanityCheckUpgrades(Submarine); + mpCampaign.UpgradeManager.SanityCheckUpgrades(); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs index 59e15246d..37d8c9369 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -512,8 +512,11 @@ namespace Barotrauma /// Should be called after every round start right after /// /// - public void SanityCheckUpgrades(Submarine submarine) + public void SanityCheckUpgrades() { + Submarine submarine = GameMain.GameSession?.Submarine ?? Submarine.MainSub; + if (submarine is null) { return; } + // check walls foreach (Structure wall in submarine.GetWalls(UpgradeAlsoConnectedSubs)) { @@ -521,23 +524,8 @@ namespace Barotrauma { foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) { - int level = GetRealUpgradeLevel(prefab, category); - if (level == 0 || !prefab.IsWallUpgrade) { continue; } - - Upgrade? upgrade = wall.GetUpgrade(prefab.Identifier); - - bool isOverMax = IsOverMaxLevel(level, prefab); - if (isOverMax) - { - SetUpgradeLevel(prefab, category, prefab.MaxLevel); - level = prefab.MaxLevel; - } - - if (upgrade == null || upgrade.Level != level || isOverMax) - { - DebugLog($"{((MapEntity)wall).Prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing..."); - FixUpgradeOnItem(wall, prefab, level); - } + if (!prefab.IsWallUpgrade) { continue; } + TryFixUpgrade(wall, category, prefab); } } } @@ -549,35 +537,38 @@ namespace Barotrauma { foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) { - if (!category.CanBeApplied(item, prefab)) { continue; } - - int level = GetRealUpgradeLevel(prefab, category); - if (level == 0) { continue; } - - Upgrade? upgrade = item.GetUpgrade(prefab.Identifier); - bool isOverMax = IsOverMaxLevel(level, prefab); - if (isOverMax) - { - SetUpgradeLevel(prefab, category, prefab.MaxLevel); - level = prefab.MaxLevel; - } - - if (upgrade == null || upgrade.Level != level || isOverMax) - { - DebugLog($"{((MapEntity)item).Prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}{(isOverMax ? " (Over max level!)" : string.Empty)}. Fixing..."); - FixUpgradeOnItem(item, prefab, level); - } + TryFixUpgrade(item, category, prefab); } } } - static bool IsOverMaxLevel(int level, UpgradePrefab prefab) => level > prefab.MaxLevel; + void TryFixUpgrade(MapEntity entity, UpgradeCategory category, UpgradePrefab prefab) + { + if (!category.CanBeApplied(entity, prefab)) { return; } + + int level = GetRealUpgradeLevel(prefab, category); + int maxLevel = submarine.Info is { } info ? prefab.GetMaxLevel(info) : prefab.MaxLevel; + if (maxLevel < level) { level = maxLevel; } + + if (level == 0) { return; } + + Upgrade? upgrade = entity.GetUpgrade(prefab.Identifier); + + if (upgrade == null || upgrade.Level != level) + { + DebugLog($"{entity.Prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing..."); + FixUpgradeOnItem((ISerializableEntity)entity, prefab, level); + } + } } private static void FixUpgradeOnItem(ISerializableEntity target, UpgradePrefab prefab, int level) { if (target is MapEntity mapEntity) { + // do not fix what's not broken + if (level == 0) { return; } + mapEntity.SetUpgrade(new Upgrade(target, prefab, level), false); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/InputType.cs b/Barotrauma/BarotraumaShared/SharedSource/InputType.cs index 04c8c9239..911fc6a4e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/InputType.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/InputType.cs @@ -23,5 +23,6 @@ namespace Barotrauma PreviousFireMode, ActiveChat, ToggleChatMode, + ChatBox } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs index 405b6a22a..4952f01f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs @@ -1191,7 +1191,7 @@ namespace Barotrauma.Items.Components //trying to dock/undock from an outpost and the signal was sent by some automated system instead of a character // -> ask if the player really wants to dock/undock to prevent a softlock if someone's wired the docking port // in a way that makes always makes it dock/undock immediately at the start of the roun - if (tryingToToggleOutpostDocking && signal.sender == null) + if (GameMain.NetworkMember != null && tryingToToggleOutpostDocking && signal.sender == null) { if (allowOutpostAutoDocking == AllowOutpostAutoDocking.Ask) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs index bff4ee42c..ccb9ba004 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs @@ -132,8 +132,8 @@ namespace Barotrauma.Items.Components public static FoliageConfig CreateRandomConfig(int maxVariants, float minScale, float maxScale, Random? random = null) { int flowerVariant = Growable.RandomInt(0, maxVariants, random); - float flowerScale = (float) Growable.RandomDouble(minScale, maxScale, random); - float flowerRotation = (float) Growable.RandomDouble(0, MathHelper.TwoPi, random); + float flowerScale = (float)Growable.RandomDouble(minScale, maxScale, random); + float flowerRotation = (float)Growable.RandomDouble(0, MathHelper.TwoPi, random); return new FoliageConfig { Variant = flowerVariant, Scale = flowerScale, Rotation = flowerRotation }; } } @@ -169,10 +169,10 @@ namespace Barotrauma.Items.Components { const float limit = 1.0f; growthStep = value; - VineStep = Math.Min((float) Math.Pow(value, 2), limit); + VineStep = Math.Min((float)Math.Pow(value, 2), limit); if (value > limit) { - FlowerStep = Math.Min((float) Math.Pow(value - limit, 2), limit); + FlowerStep = Math.Min((float)Math.Pow(value - limit, 2), limit); } } } @@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components { if (Type == VineTileType.Stem) { return; } - Type = (VineTileType) Sides; + Type = (VineTileType)Sides; } /// @@ -310,7 +310,7 @@ namespace Barotrauma.Items.Components public bool IsSideBlocked(TileSide side) => BlockedSides.HasFlag(side) || Sides.HasFlag(side); - public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int) pos.X - Size / 2, (int) pos.Y + Size / 2, Size, Size); + public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int)pos.X - Size / 2, (int)pos.Y + Size / 2, Size, Size); } internal static class GrowthSideExtension @@ -318,7 +318,7 @@ namespace Barotrauma.Items.Components // K&R algorithm for counting how many bits are set in a bit field public static int Count(this TileSide side) { - int n = (int) side; + int n = (int)side; int count = 0; while (n != 0) { @@ -607,7 +607,7 @@ namespace Barotrauma.Items.Components #if CLIENT foreach (VineTile vine in Vines) { - vine.DecayDelay = (float) RandomDouble(0f, 30f); + vine.DecayDelay = (float)RandomDouble(0f, 30f); } #endif #if SERVER @@ -742,7 +742,7 @@ namespace Barotrauma.Items.Components { var (x, y, z, w) = GrowthWeights; float[] weights = { x, y, z, w }; - int index = (int) Math.Log2((int) side); + int index = (int)Math.Log2((int)side); if (MathUtils.NearlyEqual(weights[index], 0f)) { oldVines.FailedGrowthAttempts++; @@ -778,7 +778,7 @@ namespace Barotrauma.Items.Components foreach (VineTile otherVine in Vines) { var (distX, distY) = pos - otherVine.Position; - int absDistX = (int) Math.Abs(distX), absDistY = (int) Math.Abs(distY); + int absDistX = (int)Math.Abs(distX), absDistY = (int)Math.Abs(distY); // check if the tile is within the with or height distance from us but ignore diagonals if (absDistX > newVine.Rect.Width || absDistY > newVine.Rect.Height || absDistX > 0 && absDistY > 0) { continue; } @@ -872,10 +872,10 @@ namespace Barotrauma.Items.Components foreach (VineTile vine in Vines) { XElement vineElement = new XElement("Vine"); - vineElement.Add(new XAttribute("sides", (int) vine.Sides)); - vineElement.Add(new XAttribute("blockedsides", (int) vine.BlockedSides)); + vineElement.Add(new XAttribute("sides", (int)vine.Sides)); + vineElement.Add(new XAttribute("blockedsides", (int)vine.BlockedSides)); vineElement.Add(new XAttribute("pos", XMLExtensions.Vector2ToString(vine.Position))); - vineElement.Add(new XAttribute("tile", (int) vine.Type)); + vineElement.Add(new XAttribute("tile", (int)vine.Type)); vineElement.Add(new XAttribute("failedattempts", vine.FailedGrowthAttempts)); #if SERVER vineElement.Add(new XAttribute("growthscale", Decayed ? 1.0f : 2.0f)); @@ -902,10 +902,10 @@ namespace Barotrauma.Items.Components { if (element.Name.ToString().Equals("vine", StringComparison.OrdinalIgnoreCase)) { - VineTileType type = (VineTileType) element.GetAttributeInt("tile", 0); + VineTileType type = (VineTileType)element.GetAttributeInt("tile", 0); Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero); - TileSide sides = (TileSide) element.GetAttributeInt("sides", 0); - TileSide blockedSides = (TileSide) element.GetAttributeInt("blockedsides", 0); + TileSide sides = (TileSide)element.GetAttributeInt("sides", 0); + TileSide blockedSides = (TileSide)element.GetAttributeInt("blockedsides", 0); int failedAttempts = element.GetAttributeInt("failedattempts", 0); float growthscale = element.GetAttributeFloat("growthscale", 0f); int flowerConfig = element.GetAttributeInt("flowerconfig", FoliageConfig.EmptyConfigValue); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index 74ef0f776..da4e9b663 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -4,6 +4,7 @@ using FarseerPhysics.Dynamics.Contacts; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace Barotrauma.Items.Components @@ -62,7 +63,7 @@ namespace Barotrauma.Items.Components /// /// Defines items that boost the weapon functionality, like battery cell for stun batons. /// - public readonly Identifier[] PreferredContainedItems; + public readonly ImmutableHashSet PreferredContainedItems; public MeleeWeapon(Item item, ContentXElement element) : base(item, element) @@ -77,7 +78,7 @@ namespace Barotrauma.Items.Components } item.IsShootable = true; item.RequireAimToUse = element.Parent.GetAttributeBool("requireaimtouse", true); - PreferredContainedItems = element.GetAttributeIdentifierArray("preferredcontaineditems", Array.Empty()); + PreferredContainedItems = element.GetAttributeIdentifierArray("preferredcontaineditems", Array.Empty()).ToImmutableHashSet(); } public override void Equip(Character character) @@ -387,14 +388,16 @@ namespace Barotrauma.Items.Components User = null; return; } - + + float damageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier); + damageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier); + Limb targetLimb = target.UserData as Limb; Character targetCharacter = targetLimb?.character ?? target.UserData as Character; if (Attack != null) { Attack.SetUser(User); - Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier); - Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier); + Attack.DamageMultiplier = damageMultiplier; if (targetLimb != null) { @@ -467,7 +470,7 @@ namespace Barotrauma.Items.Components if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe?? { - ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User); + ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User, afflictionMultiplier: damageMultiplier); } if (DeleteOnUse) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index 89b3f95f7..95c40bb0f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -287,7 +287,7 @@ namespace Barotrauma.Items.Components public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(activePicker?.ID ?? (ushort)0); + msg.WriteUInt16(activePicker?.ID ?? (ushort)0); } public virtual void ClientEventRead(IReadMessage msg, float sendingTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index a95f15214..af53b043f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -229,8 +229,6 @@ namespace Barotrauma.Items.Components set; } - public virtual bool RecreateGUIOnResolutionChange => false; - /// /// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced). /// @@ -397,7 +395,7 @@ namespace Barotrauma.Items.Components RelatedItem ri = RelatedItem.Load(element, returnEmpty, item.Name); if (ri != null) { - if (ri.Identifiers.Length == 0) + if (ri.Identifiers.Count == 0) { DisabledRequiredItems.Add(ri); } @@ -816,7 +814,7 @@ namespace Barotrauma.Items.Components } } - public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float applyOnUserFraction = 0.0f) + public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float afflictionMultiplier = 1.0f, float applyOnUserFraction = 0.0f) { if (statusEffectLists == null) { return; } @@ -828,13 +826,14 @@ namespace Barotrauma.Items.Components { if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; } if (user != null) { effect.SetUser(user); } + effect.AfflictionMultiplier = afflictionMultiplier; item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition); if (user != null && applyOnUserFraction > 0.0f && effect.HasTargetType(StatusEffect.TargetType.Character)) { effect.AfflictionMultiplier = applyOnUserFraction; item.ApplyStatusEffect(effect, type, deltaTime, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), useTarget, false, false, worldPosition); - effect.AfflictionMultiplier = 1.0f; } + effect.AfflictionMultiplier = 1.0f; reducesCondition |= effect.ReducesItemCondition(); } //if any of the effects reduce the item's condition, set the user for OnBroken effects as well @@ -1070,7 +1069,7 @@ namespace Barotrauma.Items.Components AIObjectiveContainItem containObjective = null; if (character.AIController is HumanAIController aiController) { - containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound) + containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers, container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound) { ItemCount = itemCount, Equip = equip, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 26b9713d6..7ca37cb5e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -216,9 +216,7 @@ namespace Barotrauma.Items.Components } private ImmutableHashSet containableItemIdentifiers; - public IEnumerable ContainableItemIdentifiers => containableItemIdentifiers; - - public override bool RecreateGUIOnResolutionChange => true; + public ImmutableHashSet ContainableItemIdentifiers => containableItemIdentifiers; public List ContainableItems { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index d7042894f..1de5e3c80 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -40,8 +40,6 @@ namespace Barotrauma.Items.Components [Editable, Serialize(1.0f, IsPropertySaveable.Yes)] public float DeconstructionSpeed { get; set; } - public override bool RecreateGUIOnResolutionChange => true; - public Deconstructor(Item item, ContentXElement element) : base(item, element) { @@ -122,7 +120,7 @@ namespace Barotrauma.Items.Components { if ((Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false) || !inputContainer.Inventory.AllItems.Contains(targetItem)) { continue; } var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => - (it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier == r)) && + it.IsValidDeconstructor(item) && (it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it != targetItem && (it.HasTag(r) || it.Prefab.Identifier == r))))).ToList(); ProcessItem(targetItem, items, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any()); @@ -140,9 +138,7 @@ namespace Barotrauma.Items.Components var targetItem = inputContainer.Inventory.LastOrDefault(); if (targetItem == null) { return; } - var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => - it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier == r)).ToList(); - + var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => it.IsValidDeconstructor(item)).ToList(); float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f; progressState = Math.Min(progressTimer / deconstructTime, 1.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index c3a9cb39e..9047d2f5e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -76,8 +76,6 @@ namespace Barotrauma.Items.Components get { return outputContainer; } } - public override bool RecreateGUIOnResolutionChange => true; - private float progressState; private readonly Dictionary fabricationLimits = new Dictionary(); @@ -647,10 +645,13 @@ namespace Barotrauma.Items.Components if (skills.Length == 0) { return 1.0f; } if (character == null) { return 0.0f; } - float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum(); - float average = skillSum / skills.Length; - - return (average + 100.0f) / 2.0f / 100.0f; + float minDegreeOfSuccess = 1.0f; + foreach (var skill in skills) + { + float characterLevel = character.GetSkillLevel(skill.Identifier); + minDegreeOfSuccess = Math.Min(minDegreeOfSuccess, (characterLevel - (skill.Level * SkillRequirementMultiplier) + 100.0f) / 2.0f / 100.0f); + } + return minDegreeOfSuccess; } public override float GetSkillMultiplier() @@ -658,13 +659,16 @@ namespace Barotrauma.Items.Components return SkillRequirementMultiplier; } + + private readonly HashSet linkedInventories = new HashSet(); + private void RefreshAvailableIngredients() { Character user = this.user; #if CLIENT user ??= Character.Controlled; #endif - + linkedInventories.Clear(); List itemList = new List(); itemList.AddRange(inputContainer.Inventory.AllItems); foreach (MapEntity linkedTo in item.linkedTo) @@ -684,6 +688,7 @@ namespace Barotrauma.Items.Components itemContainer = deconstructor.OutputContainer; } + linkedInventories.Add(itemContainer.Inventory); itemList.AddRange(itemContainer.Inventory.AllItems); } } @@ -698,6 +703,7 @@ namespace Barotrauma.Items.Components if (user?.Inventory != null) { itemList.AddRange(user.Inventory.AllItems); + linkedInventories.Add(user.Inventory); } availableIngredients.Clear(); foreach (Item item in itemList) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index 3bca94419..4630a0bf2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -48,6 +48,9 @@ namespace Barotrauma.Items.Components private Vector2 optimalFissionRate, allowedFissionRate; private Vector2 optimalTurbineOutput, allowedTurbineOutput; + private float? signalControlledTargetFissionRate, signalControlledTargetTurbineOutput; + private double lastReceivedFissionRateSignalTime, lastReceivedTurbineOutputSignalTime; + private float temperatureBoost; private bool _powerOn; @@ -245,7 +248,7 @@ namespace Barotrauma.Items.Components } #endif - if (signalControlledTargetFissionRate.HasValue && Math.Abs(signalControlledTargetFissionRate.Value - TargetFissionRate) > 1.0f) + if (signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1) { TargetFissionRate = adjustValueWithoutOverShooting(TargetFissionRate, signalControlledTargetFissionRate.Value, deltaTime * 5.0f); #if CLIENT @@ -256,7 +259,7 @@ namespace Barotrauma.Items.Components { signalControlledTargetFissionRate = null; } - if (signalControlledTargetTurbineOutput.HasValue && Math.Abs(signalControlledTargetTurbineOutput.Value - TargetTurbineOutput) > 1.0f) + if (signalControlledTargetTurbineOutput.HasValue && lastReceivedTurbineOutputSignalTime > Timing.TotalTime - 1) { TargetTurbineOutput = adjustValueWithoutOverShooting(TargetTurbineOutput, signalControlledTargetTurbineOutput.Value, deltaTime * 5.0f); #if CLIENT @@ -841,6 +844,7 @@ namespace Barotrauma.Items.Components if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate)) { signalControlledTargetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f); + lastReceivedFissionRateSignalTime = Timing.TotalTime; registerUnsentChanges(); } break; @@ -848,6 +852,7 @@ namespace Barotrauma.Items.Components if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput)) { signalControlledTargetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f); + lastReceivedTurbineOutputSignalTime = Timing.TotalTime; registerUnsentChanges(); } break; @@ -858,7 +863,5 @@ namespace Barotrauma.Items.Components if (GameMain.NetworkMember is { IsServer: true }) { unsentChanges = true; } } } - - private float? signalControlledTargetFissionRate, signalControlledTargetTurbineOutput; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs index d60aad468..44065d74e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs @@ -113,9 +113,23 @@ namespace Barotrauma.Items.Components set; } - [Editable, Serialize(false, IsPropertySaveable.No, description: "Does the sonar have mineral scanning mode. " + - "Only available in-game when the Item has no Steering component.")] - public bool HasMineralScanner { get; set; } + private bool hasMineralScanner; + + [Editable, Serialize(false, IsPropertySaveable.No, description: "Does the sonar have mineral scanning mode. ")] + public bool HasMineralScanner + { + get => hasMineralScanner; + set + { +#if CLIENT + if (controlContainer != null && !hasMineralScanner && value) + { + AddMineralScannerSwitchToGUI(); + } +#endif + hasMineralScanner = value; + } + } public float Zoom { @@ -144,8 +158,6 @@ namespace Barotrauma.Items.Components } } - public override bool RecreateGUIOnResolutionChange => true; - public Sonar(Item item, ContentXElement element) : base(item, element) { @@ -396,17 +408,17 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(currentMode == Mode.Active); + msg.WriteBoolean(currentMode == Mode.Active); if (currentMode == Mode.Active) { msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8); - msg.Write(useDirectionalPing); + msg.WriteBoolean(useDirectionalPing); if (useDirectionalPing) { float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection)); msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8); } - msg.Write(useMineralScanner); + msg.WriteBoolean(useMineralScanner); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index fc32c5c30..75776f814 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -166,8 +166,6 @@ namespace Barotrauma.Items.Components set { posToMaintain = value; } } - public override bool RecreateGUIOnResolutionChange => true; - struct ObstacleDebugInfo { public Vector2 Point1; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index c5d847e8e..3954c06ed 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs @@ -401,14 +401,14 @@ namespace Barotrauma.Items.Components msg.WriteVariableUInt32((uint)connection.Wires.Count); foreach (Wire wire in connection.Wires) { - msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID); + msg.WriteUInt16(wire?.Item == null ? (ushort)0 : wire.Item.ID); } } - msg.Write((ushort)DisconnectedWires.Count); + msg.WriteUInt16((ushort)DisconnectedWires.Count); foreach (Wire disconnectedWire in DisconnectedWires) { - msg.Write(disconnectedWire.Item.ID); + msg.WriteUInt16(disconnectedWire.Item.ID); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs index 2cbdc98fa..dc2ae516c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs @@ -155,7 +155,7 @@ namespace Barotrauma.Items.Components { //use semicolon as a separator because comma may be needed in the signals (for color or vector values for example) //kind of hacky, we should probably add support for (string) arrays to SerializableEntityEditor so this wouldn't be needed - get { return signals == null ? "" : string.Join(";", signals); } + get { return signals == null ? string.Empty : string.Join(";", signals); } set { if (value == null) { return; } @@ -167,7 +167,31 @@ namespace Barotrauma.Items.Components } } - public override bool RecreateGUIOnResolutionChange => true; + private bool[] elementStates; + [Serialize("", IsPropertySaveable.Yes, description: "", alwaysUseInstanceValues: true)] + public string ElementStates + { + get { return elementStates == null ? string.Empty : string.Join(",", elementStates); } + set + { + if (value == null) { return; } + if (customInterfaceElementList.Count > 0) + { + string[] splitValues = value == "" ? Array.Empty() : value.Split(','); + for (int i = 0; i < customInterfaceElementList.Count && i < splitValues.Length; i++) + { + if (!bool.TryParse(splitValues[i], out bool val)) { continue; } + customInterfaceElementList[i].State = val; +#if CLIENT + if (uiElements != null && i < uiElements.Count && uiElements[i] is GUITickBox tickBox) + { + tickBox.Selected = val; + } +#endif + } + } + } + } private readonly List customInterfaceElementList = new List(); @@ -207,8 +231,10 @@ namespace Barotrauma.Items.Components } IsActive = true; InitProjSpecific(); + //load these here to ensure the UI elements (created in InitProjSpecific) are up-to-date Labels = element.GetAttributeString("labels", ""); Signals = element.GetAttributeString("signals", ""); + ElementStates = element.GetAttributeString("elementstates", ""); } private void UpdateLabels(string[] newLabels) @@ -386,6 +412,7 @@ namespace Barotrauma.Items.Components { labels = customInterfaceElementList.Select(ci => ci.Label).ToArray(); signals = customInterfaceElementList.Select(ci => ci.Signal).ToArray(); + elementStates = customInterfaceElementList.Select(ci => ci.State).ToArray(); return base.Save(parentElement); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index 5e4ba1095..32f151379 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -253,7 +253,13 @@ namespace Barotrauma.Items.Components public void CheckIfNeedsUpdate() { - if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null && IsOn && + if (!IsOn) + { + base.IsActive = false; + return; + } + + if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null && (statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) && (IsActiveConditionals == null || IsActiveConditionals.Count == 0)) { @@ -268,7 +274,7 @@ namespace Barotrauma.Items.Components } else { - IsActive = true; + base.IsActive = true; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RelayComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RelayComponent.cs index 0a0a3e691..5696c6280 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RelayComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RelayComponent.cs @@ -374,7 +374,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write(isOn); + msg.WriteBoolean(isOn); } public void ClientEventRead(IReadMessage msg, float sendingTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index d599a3bce..18ab28faf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.Linq; using Barotrauma.Extensions; using FarseerPhysics.Dynamics; +using System.Collections.Immutable; namespace Barotrauma.Items.Components { @@ -1151,7 +1152,7 @@ namespace Barotrauma.Items.Components if (objective.SubObjectives.None()) { var loadItemsObjective = AIContainItems(container, character, objective, usableProjectileCount + 1, equip: true, removeEmpty: true, dropItemOnDeselected: true); - loadItemsObjective.ignoredContainerIdentifiers = new Identifier[] { ((MapEntity)containerItem).Prefab.Identifier }; + loadItemsObjective.ignoredContainerIdentifiers = ((MapEntity)containerItem).Prefab.Identifier.ToEnumerable().ToImmutableHashSet(); if (character.IsOnPlayerTeam) { character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: FormatCapitals.Yes).Value, @@ -1340,7 +1341,7 @@ namespace Barotrauma.Items.Components { if (character.AIController.SelectedAiTarget == null && !hadCurrentTarget) { - if (CreatureMetrics.Instance.RecentlyEncountered.Contains(closestEnemy.SpeciesName)) + if (CreatureMetrics.Instance.RecentlyEncountered.Contains(closestEnemy.SpeciesName) || closestEnemy.IsHuman) { character.Speak(TextManager.Get("DialogNewTargetSpotted").Value, identifier: "newtargetspotted".ToIdentifier(), @@ -1611,6 +1612,7 @@ namespace Barotrauma.Items.Components targetRotation = rotation = (minRotation + maxRotation) / 2; UpdateTransformedBarrelPos(); + UpdateLightComponents(); } public override void FlipY(bool relativeToSub) @@ -1632,6 +1634,7 @@ namespace Barotrauma.Items.Components targetRotation = rotation = (minRotation + maxRotation) / 2; UpdateTransformedBarrelPos(); + UpdateLightComponents(); } public override void ReceiveSignal(Signal signal, Connection connection) @@ -1714,12 +1717,12 @@ namespace Barotrauma.Items.Components { if (TryExtractEventData(extraData, out EventData eventData)) { - msg.Write(eventData.Projectile.ID); + msg.WriteUInt16(eventData.Projectile.ID); msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16); } else { - msg.Write((ushort)0); + msg.WriteUInt16((ushort)0); float wrappedTargetRotation = targetRotation; while (wrappedTargetRotation < minRotation && MathUtils.IsValid(wrappedTargetRotation)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index ae4b5b1c6..57b4dc685 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -560,7 +560,7 @@ namespace Barotrauma.Items.Components } public override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { - msg.Write((byte)Variant); + msg.WriteByte((byte)Variant); base.ServerEventWrite(msg, c, extraData); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index a4281fe28..afb94878c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -959,14 +959,14 @@ namespace Barotrauma public void SharedWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { - msg.Write((byte)capacity); + msg.WriteByte((byte)capacity); for (int i = 0; i < capacity; i++) { msg.WriteRangedInteger(slots[i].Items.Count, 0, MaxStackSize); for (int j = 0; j < Math.Min(slots[i].Items.Count, MaxStackSize); j++) { var item = slots[i].Items[j]; - msg.Write(item?.ID ?? (ushort)0); + msg.WriteUInt16(item?.ID ?? (ushort)0); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 6411b4334..c8fc6f8a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1536,7 +1536,7 @@ namespace Barotrauma return false; } - private bool ConditionalMatches(PropertyConditional conditional) + public bool ConditionalMatches(PropertyConditional conditional) { if (string.IsNullOrEmpty(conditional.TargetItemComponentName)) { @@ -2861,77 +2861,77 @@ namespace Barotrauma var propertyOwner = allProperties.Find(p => p.property == property); if (allProperties.Count > 1) { - msg.Write((byte)allProperties.FindIndex(p => p.property == property)); + msg.WriteByte((byte)allProperties.FindIndex(p => p.property == property)); } object value = property.GetValue(propertyOwner.obj); if (value is string stringVal) { - msg.Write(stringVal); + msg.WriteString(stringVal); } else if (value is Identifier idValue) { - msg.Write(idValue); + msg.WriteIdentifier(idValue); } else if (value is float floatVal) { - msg.Write(floatVal); + msg.WriteSingle(floatVal); } else if (value is int intVal) { - msg.Write(intVal); + msg.WriteInt32(intVal); } else if (value is bool boolVal) { - msg.Write(boolVal); + msg.WriteBoolean(boolVal); } else if (value is Color color) { - msg.Write(color.R); - msg.Write(color.G); - msg.Write(color.B); - msg.Write(color.A); + msg.WriteByte(color.R); + msg.WriteByte(color.G); + msg.WriteByte(color.B); + msg.WriteByte(color.A); } else if (value is Vector2 vector2) { - msg.Write(vector2.X); - msg.Write(vector2.Y); + msg.WriteSingle(vector2.X); + msg.WriteSingle(vector2.Y); } else if (value is Vector3 vector3) { - msg.Write(vector3.X); - msg.Write(vector3.Y); - msg.Write(vector3.Z); + msg.WriteSingle(vector3.X); + msg.WriteSingle(vector3.Y); + msg.WriteSingle(vector3.Z); } else if (value is Vector4 vector4) { - msg.Write(vector4.X); - msg.Write(vector4.Y); - msg.Write(vector4.Z); - msg.Write(vector4.W); + msg.WriteSingle(vector4.X); + msg.WriteSingle(vector4.Y); + msg.WriteSingle(vector4.Z); + msg.WriteSingle(vector4.W); } else if (value is Point point) { - msg.Write(point.X); - msg.Write(point.Y); + msg.WriteInt32(point.X); + msg.WriteInt32(point.Y); } else if (value is Rectangle rect) { - msg.Write(rect.X); - msg.Write(rect.Y); - msg.Write(rect.Width); - msg.Write(rect.Height); + msg.WriteInt32(rect.X); + msg.WriteInt32(rect.Y); + msg.WriteInt32(rect.Width); + msg.WriteInt32(rect.Height); } else if (value is Enum) { - msg.Write((int)value); + msg.WriteInt32((int)value); } else if (value is string[] a) { - msg.Write(a.Length); + msg.WriteInt32(a.Length); for (int i = 0; i < a.Length; i++) { - msg.Write(a[i] ?? ""); + msg.WriteString(a[i] ?? ""); } } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 203d2dbac..ce8d957e2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -53,6 +53,11 @@ namespace Barotrauma InfoText = element.GetAttributeString("infotext", string.Empty); InfoTextOnOtherItemMissing = element.GetAttributeString("infotextonotheritemmissing", string.Empty); } + + public bool IsValidDeconstructor(Item deconstructor) + { + return RequiredDeconstructor.Length == 0 || RequiredDeconstructor.Any(r => deconstructor.HasTag(r) || deconstructor.Prefab.Identifier == r); + } } class FabricationRecipe @@ -62,6 +67,10 @@ namespace Barotrauma public abstract IEnumerable ItemPrefabs { get; } public abstract UInt32 UintIdentifier { get; } + public abstract bool MatchesItem(Item item); + + public abstract ItemPrefab FirstMatchingPrefab { get; } + public RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition) { Amount = amount; @@ -92,12 +101,21 @@ namespace Barotrauma public class RequiredItemByIdentifier : RequiredItem { public readonly Identifier ItemPrefabIdentifier; + public ItemPrefab ItemPrefab => ItemPrefab.Prefabs.TryGet(ItemPrefabIdentifier, out var prefab) ? prefab : MapEntityPrefab.FindByName(ItemPrefabIdentifier.Value) as ItemPrefab ?? throw new Exception($"No ItemPrefab with identifier or name \"{ItemPrefabIdentifier}\""); + public override UInt32 UintIdentifier { get; } public override IEnumerable ItemPrefabs => ItemPrefab.ToEnumerable(); + public override ItemPrefab FirstMatchingPrefab => ItemPrefab; + + public override bool MatchesItem(Item item) + { + return item?.Prefab.Identifier == ItemPrefabIdentifier; + } + public RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition) : base(amount, minCondition, maxCondition, useCondition) { ItemPrefabIdentifier = itemPrefab; @@ -109,10 +127,19 @@ namespace Barotrauma public class RequiredItemByTag : RequiredItem { public readonly Identifier Tag; + public override UInt32 UintIdentifier { get; } public override IEnumerable ItemPrefabs => ItemPrefab.Prefabs.Where(p => p.Tags.Contains(Tag)); + public override ItemPrefab FirstMatchingPrefab => ItemPrefab.Prefabs.FirstOrDefault(p => p.Tags.Contains(Tag)); + + public override bool MatchesItem(Item item) + { + if (item == null) { return false; } + return item.HasTag(Tag); + } + public RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition) : base(amount, minCondition, maxCondition, useCondition) { Tag = tag; @@ -208,10 +235,10 @@ namespace Barotrauma if (requiredItemIdentifier != Identifier.Empty) { var existing = requiredItems.FindIndex(r => - r is RequiredItemByIdentifier ri && - ri.ItemPrefabIdentifier == requiredItemIdentifier && - MathUtils.NearlyEqual(r.MinCondition, minCondition) && - MathUtils.NearlyEqual(r.MaxCondition, maxCondition)); + r is RequiredItemByIdentifier ri && + ri.ItemPrefabIdentifier == requiredItemIdentifier && + MathUtils.NearlyEqual(r.MinCondition, minCondition) && + MathUtils.NearlyEqual(r.MaxCondition, maxCondition)); if (existing >= 0) { amount += requiredItems[existing].Amount; @@ -222,10 +249,10 @@ namespace Barotrauma else { var existing = requiredItems.FindIndex(r => - r is RequiredItemByTag rt && - rt.Tag == requiredItemTag && - MathUtils.NearlyEqual(r.MinCondition, minCondition) && - MathUtils.NearlyEqual(r.MaxCondition, maxCondition)); + r is RequiredItemByTag rt && + rt.Tag == requiredItemTag && + MathUtils.NearlyEqual(r.MinCondition, minCondition) && + MathUtils.NearlyEqual(r.MaxCondition, maxCondition)); if (existing >= 0) { amount += requiredItems[existing].Amount; @@ -783,19 +810,12 @@ namespace Barotrauma //works the same as nameIdentifier, but just replaces the description Identifier descriptionIdentifier = ConfigElement.GetAttributeIdentifier("descriptionidentifier", ""); - if (string.IsNullOrEmpty(OriginalName)) - { - name = TextManager.Get(nameIdentifier.IsEmpty - ? $"EntityName.{Identifier}" - : $"EntityName.{nameIdentifier}", - $"EntityName.{fallbackNameIdentifier}"); - } - else if (Category.HasFlag(MapEntityCategory.Legacy)) - { - // Legacy items use names as identifiers, so we have to define them in the xml. But we also want to support the translations. Therefore - name = TextManager.Get(nameIdentifier.IsEmpty + name = TextManager.Get(nameIdentifier.IsEmpty ? $"EntityName.{Identifier}" - : $"EntityName.{nameIdentifier}"); + : $"EntityName.{nameIdentifier}", + $"EntityName.{fallbackNameIdentifier}"); + if (!string.IsNullOrEmpty(OriginalName)) + { name = name.Fallback(OriginalName); } @@ -838,20 +858,17 @@ namespace Barotrauma SerializableProperty.DeserializeProperties(this, ConfigElement); - if (Description.IsNullOrEmpty()) + if (descriptionIdentifier != Identifier.Empty) { - if (descriptionIdentifier != Identifier.Empty) - { - Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}"); - } - else if (nameIdentifier == Identifier.Empty) - { - Description = TextManager.Get($"EntityDescription.{Identifier}"); - } - else - { - Description = TextManager.Get($"EntityDescription.{nameIdentifier}"); - } + Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}").Fallback(Description); + } + else if (nameIdentifier == Identifier.Empty) + { + Description = TextManager.Get($"EntityDescription.{Identifier}").Fallback(Description); + } + else + { + Description = TextManager.Get($"EntityDescription.{nameIdentifier}").Fallback(Description); } var allowDroppingOnSwapWith = ConfigElement.GetAttributeIdentifierArray("allowdroppingonswapwith", Array.Empty()); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index a74a686d4..04351e385 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; @@ -22,7 +23,7 @@ namespace Barotrauma public bool IgnoreInEditor { get; set; } - private Identifier[] excludedIdentifiers; + private ImmutableHashSet excludedIdentifiers; private RelationType type; @@ -60,11 +61,11 @@ namespace Barotrauma { if (value == null) return; - Identifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToArray(); + Identifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToImmutableHashSet(); } } - public Identifier[] Identifiers { get; private set; } + public ImmutableHashSet Identifiers { get; private set; } public string JoinedExcludedIdentifiers { @@ -73,27 +74,53 @@ namespace Barotrauma { if (value == null) return; - excludedIdentifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToArray(); + excludedIdentifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToImmutableHashSet(); } } public bool MatchesItem(Item item) { if (item == null) { return false; } - if (excludedIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { return false; } - return Identifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && item.Prefab.VariantOf == id)); + if (excludedIdentifiers.Contains(item.Prefab.Identifier)) { return false; } + foreach (var excludedIdentifier in excludedIdentifiers) + { + if (item.HasTag(excludedIdentifier)) { return false; } + } + if (Identifiers.Contains(item.Prefab.Identifier)) { return true; } + foreach (var identifier in Identifiers) + { + if (item.HasTag(identifier)) { return true; } + } + if (AllowVariants && !item.Prefab.VariantOf.IsEmpty) + { + if (Identifiers.Contains(item.Prefab.VariantOf)) { return true; } + } + return false; } public bool MatchesItem(ItemPrefab itemPrefab) { if (itemPrefab == null) { return false; } - if (excludedIdentifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id))) { return false; } - return Identifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id) || (AllowVariants && !itemPrefab.VariantOf.IsEmpty && itemPrefab.VariantOf == id)); + if (excludedIdentifiers.Contains(itemPrefab.Identifier)) { return false; } + foreach (var excludedIdentifier in excludedIdentifiers) + { + if (itemPrefab.Tags.Contains(excludedIdentifier)) { return false; } + } + if (Identifiers.Contains(itemPrefab.Identifier)) { return true; } + foreach (var identifier in Identifiers) + { + if (itemPrefab.Tags.Contains(identifier)) { return true; } + } + if (AllowVariants && !itemPrefab.VariantOf.IsEmpty) + { + if (Identifiers.Contains(itemPrefab.VariantOf)) { return true; } + } + return false; } public RelatedItem(Identifier[] identifiers, Identifier[] excludedIdentifiers) { - this.Identifiers = identifiers.Select(id => id.Value.Trim().ToIdentifier()).ToArray(); - this.excludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToArray(); + this.Identifiers = identifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet(); + this.excludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet(); statusEffects = new List(); } @@ -161,7 +188,7 @@ namespace Barotrauma new XAttribute("targetslot", TargetSlot), new XAttribute("allowvariants", AllowVariants)); - if (excludedIdentifiers.Length > 0) + if (excludedIdentifiers.Count > 0) { element.Add(new XAttribute("excludedidentifiers", JoinedExcludedIdentifiers)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index 291737a4e..a226d5e6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -25,6 +25,12 @@ namespace Barotrauma private set; } + /// + /// "Diagonal" gaps are used on sloped walls to allow characters to pass through them either horizontally or vertically. + /// Water still flows through them only horizontally or vertically + /// + public bool IsDiagonal { get; } + //a value between 0.0f-1.0f (0.0 = closed, 1.0f = open) private float open; @@ -135,12 +141,13 @@ namespace Barotrauma : this(rect, rect.Width < rect.Height, submarine) { } - public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, ushort id = Entity.NullEntityID) + public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, bool isDiagonal = false, ushort id = Entity.NullEntityID) : base(CoreEntityPrefab.GapPrefab, submarine, id) { this.rect = rect; flowForce = Vector2.Zero; IsHorizontal = isHorizontal; + IsDiagonal = isDiagonal; open = 1.0f; FindHulls(); @@ -666,15 +673,15 @@ namespace Barotrauma { foreach (Gap gap in gaps) { - if (gap.Open == 0.0f || gap.IsRoomToRoom) continue; + if (gap.Open == 0.0f || gap.IsRoomToRoom) { continue; } if (gap.ConnectedWall != null) { int sectionIndex = gap.ConnectedWall.FindSectionIndex(gap.Position); - if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) continue; + if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) { continue; } } - if (gap.IsHorizontal) + if (gap.IsHorizontal || gap.IsDiagonal) { if (worldPos.Y < gap.WorldRect.Y && worldPos.Y > gap.WorldRect.Y - gap.WorldRect.Height && Math.Abs(gap.WorldRect.Center.X - worldPos.X) < allowedOrthogonalDist) @@ -682,7 +689,7 @@ namespace Barotrauma return gap; } } - else + if (!gap.IsHorizontal || gap.IsDiagonal) { if (worldPos.X > gap.WorldRect.X && worldPos.X < gap.WorldRect.Right && Math.Abs(gap.WorldRect.Y - gap.WorldRect.Height / 2 - worldPos.Y) < allowedOrthogonalDist) @@ -754,7 +761,7 @@ namespace Barotrauma isHorizontal = horizontalAttribute.Value.ToString() == "true"; } - Gap g = new Gap(rect, isHorizontal, submarine, idRemap.GetOffsetId(element)) + Gap g = new Gap(rect, isHorizontal, submarine, id: idRemap.GetOffsetId(element)) { linkedToID = new List(), }; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 9702e90be..3752320d8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -759,7 +759,7 @@ namespace Barotrauma for (int i = start; i < end; i++) { msg.WriteRangedSingle(BackgroundSections[i].ColorStrength, 0.0f, 1.0f, 8); - msg.Write(BackgroundSections[i].Color.PackedValue); + msg.WriteUInt32(BackgroundSections[i].Color.PackedValue); } } #endregion @@ -994,7 +994,11 @@ namespace Barotrauma foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0)) { var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position) / 1000, 1f); - item.body.ApplyForce((gap.LerpedFlowForce / distance) * deltaTime); + Vector2 force = (gap.LerpedFlowForce / distance) * deltaTime; + if (force.LengthSquared() > 0.01f) + { + item.body.ApplyForce(force); + } } } @@ -1545,7 +1549,7 @@ namespace Barotrauma var hull = new Hull(rect, submarine, idRemap.GetOffsetId(element)) { - WaterVolume = element.GetAttributeFloat("pressure", 0.0f) + WaterVolume = element.GetAttributeFloat("water", 0.0f) }; hull.linkedToID = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 6bb343533..181f60281 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -1972,7 +1972,7 @@ namespace Barotrauma List caveBranches = new List(); - var tunnel = new Tunnel(TunnelType.Cave, SegmentsToNodes(caveSegments), 100, parentTunnel); + var tunnel = new Tunnel(TunnelType.Cave, SegmentsToNodes(caveSegments), 150, parentTunnel); Tunnels.Add(tunnel); caveBranches.Add(tunnel); @@ -1989,7 +1989,7 @@ namespace Barotrauma bounds: caveArea); if (!branchSegments.Any()) { continue; } - var branch = new Tunnel(TunnelType.Cave, SegmentsToNodes(branchSegments), 0, parentBranch); + var branch = new Tunnel(TunnelType.Cave, SegmentsToNodes(branchSegments), 150, parentBranch); Tunnels.Add(branch); caveBranches.Add(branch); } @@ -2569,34 +2569,38 @@ namespace Barotrauma AbyssResources.Clear(); var abyssResourcePrefabs = levelResources.Where(r => r.commonnessInfo.AbyssCommonness > 0.0f); - int abyssClusterCount = (int)MathHelper.Lerp(GenerationParams.AbyssResourceClustersMin, GenerationParams.AbyssResourceClustersMax, MathUtils.InverseLerp(LevelData.Biome.MinDifficulty, LevelData.Biome.AdjustedMaxDifficulty, Difficulty)); - for (int i = 0; i < abyssClusterCount; i++) + if (abyssResourcePrefabs.Any()) { - var selectedPrefab = ToolBox.SelectWeightedRandom( - abyssResourcePrefabs.Select(r => r.itemPrefab).ToList(), - abyssResourcePrefabs.Select(r => r.commonnessInfo.AbyssCommonness).ToList(), - Rand.RandSync.ServerAndClient); - - var location = allValidLocations.GetRandom(l => + int abyssClusterCount = (int)MathHelper.Lerp(GenerationParams.AbyssResourceClustersMin, GenerationParams.AbyssResourceClustersMax, MathUtils.InverseLerp(LevelData.Biome.MinDifficulty, LevelData.Biome.AdjustedMaxDifficulty, Difficulty)); + for (int i = 0; i < abyssClusterCount; i++) { - if (l.Cell == null || l.Edge == null) { return false; } - if (l.EdgeCenter.Y > AbyssArea.Bottom) { return false; } - l.InitializeResources(); - return l.Resources.Count <= GetMaxResourcesOnEdge(selectedPrefab, l, out _); - }, randSync: Rand.RandSync.ServerAndClient); + var selectedPrefab = ToolBox.SelectWeightedRandom( + abyssResourcePrefabs.Select(r => r.itemPrefab).ToList(), + abyssResourcePrefabs.Select(r => r.commonnessInfo.AbyssCommonness).ToList(), + Rand.RandSync.ServerAndClient); - if (location.Cell == null || location.Edge == null) { break; } + var location = allValidLocations.GetRandom(l => + { + if (l.Cell == null || l.Edge == null) { return false; } + if (l.EdgeCenter.Y > AbyssArea.Bottom) { return false; } + l.InitializeResources(); + return l.Resources.Count <= GetMaxResourcesOnEdge(selectedPrefab, l, out _); + }, randSync: Rand.RandSync.ServerAndClient); - int clusterSize = Rand.Range(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y + 1, Rand.RandSync.ServerAndClient); - PlaceResources(selectedPrefab, clusterSize, location, out var placedResources, maxResourceOverlap: 0); - var abyssClusterLocation = new ClusterLocation(location.Cell, location.Edge, initializeResourceList: true); - abyssClusterLocation.Resources.AddRange(placedResources); - AbyssResources.Add(abyssClusterLocation); + if (location.Cell == null || location.Edge == null) { break; } - var locationIndex = allValidLocations.FindIndex(l => l.Equals(location)); - allValidLocations.RemoveAt(locationIndex); + int clusterSize = Rand.Range(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y + 1, Rand.RandSync.ServerAndClient); + PlaceResources(selectedPrefab, clusterSize, location, out var placedResources, maxResourceOverlap: 0); + var abyssClusterLocation = new ClusterLocation(location.Cell, location.Edge, initializeResourceList: true); + abyssClusterLocation.Resources.AddRange(placedResources); + AbyssResources.Add(abyssClusterLocation); + + var locationIndex = allValidLocations.FindIndex(l => l.Equals(location)); + allValidLocations.RemoveAt(locationIndex); + } } + PathPoints.Clear(); nextPathPointId = 0; @@ -2928,50 +2932,74 @@ namespace Barotrauma } /// Used by clients to set the rotation for the resources - public List GenerateMissionResources(ItemPrefab prefab, int requiredAmount, out float rotation) + public List GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, out float rotation) { var allValidLocations = GetAllValidClusterLocations(); var placedResources = new List(); rotation = 0.0f; + if (allValidLocations.None()) { return placedResources; } // TODO: WHAT?! + // Make sure not to pick a spot that already has other level resources for (int i = allValidLocations.Count - 1; i >= 0; i--) { - var location = allValidLocations[i]; - var locationHasResources = PathPoints.Any(p => - p.ClusterLocations.Any(c => - c.Equals(location) && - c.Resources.Any(r => r != null && !r.Removed && - (!(r.GetComponent() is Holdable h) || (h.Attachable && h.Attached))))); - if (locationHasResources) + if (HasResources(allValidLocations[i])) { allValidLocations.RemoveAt(i); } + + bool HasResources(ClusterLocation clusterLocation) + { + foreach (var p in PathPoints) + { + foreach (var c in p.ClusterLocations) + { + if (!c.Equals(clusterLocation)) { continue; } + foreach (var r in c.Resources) + { + if (r == null) { continue; } + if (r.Removed) { continue; } + if (!(r.GetComponent() is Holdable h) || (h.Attachable && h.Attached)) { return true; } + } + } + } + return false; + } } - var positionType = PositionType.MainPath; - if (PositionsOfInterest.Any(p => p.PositionType == PositionType.Cave)) + if (PositionsOfInterest.None(p => p.PositionType == positionType)) { - positionType = PositionType.Cave; - if (allValidLocations.Any(l => l.Edge.NextToCave)) + foreach (var validType in MineralMission.ValidPositionTypes) { - allValidLocations.RemoveAll(l => !l.Edge.NextToCave); + if (validType != positionType && PositionsOfInterest.Any(p => p.PositionType == validType)) + { + positionType = validType; + break; + } } } - else if (PositionsOfInterest.Any(p => p.PositionType == PositionType.SidePath)) + + try { - positionType = PositionType.SidePath; - if (allValidLocations.Any(l => l.Edge.NextToSidePath)) + RemoveInvalidLocations(positionType switch { - allValidLocations.RemoveAll(l => !l.Edge.NextToSidePath); - } + PositionType.MainPath => IsOnMainPath, + PositionType.SidePath => IsOnSidePath, + PositionType.Cave => IsInCave, + PositionType.AbyssCave => IsInAbyssCave, + _ => throw new NotImplementedException(), + }); + } + catch (NotImplementedException) + { + DebugConsole.ThrowError($"Unexpected PositionType (\"{positionType}\") for mineral mission resources: mineral spawning might not work as expected."); } var poi = PositionsOfInterest.GetRandom(p => p.PositionType == positionType, randSync: Rand.RandSync.ServerAndClient); - var poiPos = poi.Position.ToVector2(); + Vector2 poiPos = poi.Position.ToVector2(); allValidLocations.Sort((x, y) => Vector2.DistanceSquared(poiPos, x.EdgeCenter) .CompareTo(Vector2.DistanceSquared(poiPos, y.EdgeCenter))); - var maxResourceOverlap = 0.4f; + float maxResourceOverlap = 0.4f; var selectedLocation = allValidLocations.FirstOrDefault(l => Vector2.Distance(l.Edge.Point1, l.Edge.Point2) is float edgeLength && requiredAmount <= (int)Math.Floor(edgeLength / ((1.0f - maxResourceOverlap) * prefab.Size.X))); @@ -2993,9 +3021,18 @@ namespace Barotrauma throw new Exception("Failed to find a suitable level wall edge to place level resources on."); } PlaceResources(prefab, requiredAmount, selectedLocation, out placedResources); - var edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell); + Vector2 edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell); rotation = MathHelper.ToDegrees(-MathUtils.VectorToAngle(edgeNormal) + MathHelper.PiOver2); return placedResources; + + static bool IsOnMainPath(ClusterLocation location) => location.Edge.NextToMainPath; + static bool IsOnSidePath(ClusterLocation location) => location.Edge.NextToSidePath; + static bool IsInCave(ClusterLocation location) => location.Edge.NextToCave; + bool IsInAbyssCave(ClusterLocation location) => location.EdgeCenter.Y > AbyssArea.Bottom; + void RemoveInvalidLocations(Predicate match) + { + allValidLocations.RemoveAll(match); + } } private List GetAllValidClusterLocations() @@ -4015,17 +4052,21 @@ namespace Barotrauma GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg); } - float outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X; - //don't try to compensate if the port is very far from the outpost's center of mass - if (Math.Abs(outpostDockingPortOffset) > 5000.0f) + float? outpostDockingPortOffset = null; + if (outpostPort != null) { - outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset, -5000.0f, 5000.0f); - string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible."; - DebugConsole.NewMessage(warningMsg, Color.Orange); - GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg); + outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X; + //don't try to compensate if the port is very far from the outpost's center of mass + if (Math.Abs(outpostDockingPortOffset.Value) > 5000.0f) + { + outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset.Value, -5000.0f, 5000.0f); + string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible."; + DebugConsole.NewMessage(warningMsg, Color.Orange); + GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg); + } } - Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset, verticalMoveDir: 1); + Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, outpostDockingPortOffset != null ? subDockingPortOffset - outpostDockingPortOffset.Value : 0.0f, verticalMoveDir: 1); if (Type == LevelData.LevelType.Outpost) { spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs index 3b6aad96f..a1de6a144 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs @@ -357,7 +357,9 @@ namespace Barotrauma float closestDistance = 0.0f; foreach (DockingPort port in DockingPort.List) { - if (port.Item.Submarine != sub || port.IsHorizontal != linkedPort.IsHorizontal) { continue; } + if (port.Item.Submarine != sub) { continue; } + if (port.IsHorizontal != linkedPort.IsHorizontal) { continue; } + if (port.ForceDockingDirection != DockingPort.DirectionType.None && port.ForceDockingDirection == linkedPort.ForceDockingDirection) { continue; } float dist = Vector2.Distance(port.Item.WorldPosition, linkedPort.Item.WorldPosition); if (myPort == null || dist < closestDistance) { @@ -453,22 +455,22 @@ namespace Barotrauma saveElement.SetAttributeValue("pos", XMLExtensions.Vector2ToString(Position - Submarine.HiddenSubPosition)); - if (linkedTo.Any() || linkedToID.Any()) - { - var linkedPort = - linkedTo.FirstOrDefault(lt => (lt is Item item) && item.GetComponent() != null) ?? - FindEntityByID(linkedToID.First()) as MapEntity; - if (linkedPort != null) - { - saveElement.SetAttributeValue("linkedto", linkedPort.ID); - } - } } else { saveElement = new XElement("LinkedSubmarine"); sub.SaveToXElement(saveElement); } + if (linkedTo.Any() || linkedToID.Any()) + { + var linkedPort = + linkedTo.FirstOrDefault(lt => (lt is Item item) && item.GetComponent() != null) ?? + FindEntityByID(linkedToID.First()) as MapEntity; + if (linkedPort != null) + { + saveElement.SetAttributeValue("linkedto", linkedPort.ID); + } + } saveElement.SetAttributeValue("originallinkedto", originalLinkedPort != null ? originalLinkedPort.Item.ID : originalLinkedToID); saveElement.SetAttributeValue("originalmyport", originalMyPortID); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 7ab8451c7..e8489c131 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -338,7 +338,7 @@ namespace Barotrauma /// public virtual bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false) { - if (this is Item item && !upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(item, upgrade.Prefab))) + if (!upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(this, upgrade.Prefab))) { return false; } @@ -359,16 +359,6 @@ namespace Barotrauma Upgrades.Add(upgrade); } - // not used anymore -#if SERVER - // if (createNetworkEvent) - // { - // if (this is IServerSerializable serializable) - // { - // GameMain.Server.CreateEntityEvent(serializable, new object[] { NetEntityEvent.Type.Upgrade, upgrade }); - // } - // } -#endif return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index b973831de..600a2b731 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -168,6 +168,17 @@ namespace Barotrauma public ImmutableHashSet Tags => Prefab.Tags; +#if DEBUG + [Editable, Serialize("", IsPropertySaveable.Yes)] +#else + [Serialize("", IsPropertySaveable.Yes)] +#endif + public string SpecialTag + { + get; + set; + } + protected Color spriteColor; [Editable, Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes)] public Color SpriteColor @@ -574,6 +585,11 @@ namespace Barotrauma int xsections = 1, ysections = 1; int width = rect.Width, height = rect.Height; + WallSection[] prevSections = null; + if (Sections != null) + { + prevSections = Sections.ToArray(); + } if (!HasBody) { if (FlippedX && IsHorizontal) @@ -657,6 +673,14 @@ namespace Barotrauma } } } + + if (prevSections != null && Sections.Length == prevSections.Length) + { + for (int i = 0; i < Sections.Length; i++) + { + Sections[i].damage = prevSections[i].damage; + } + } } private Rectangle GenerateMergedRect(List mergedSections) @@ -829,27 +853,33 @@ namespace Barotrauma public WallSection GetSection(int sectionIndex) { - if (sectionIndex < 0 || sectionIndex >= Sections.Length) return null; - + if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return null; } return Sections[sectionIndex]; } public bool SectionBodyDisabled(int sectionIndex) { - if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false; - + if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return false; } return (Sections[sectionIndex].damage >= MaxHealth); } + public bool AllSectionBodiesDisabled() + { + for (int i = 0; i < Sections.Length; i++) + { + if (Sections[i].damage < MaxHealth) { return false; } + } + return true; + } + /// /// Sections that are leaking have a gap placed on them /// public bool SectionIsLeaking(int sectionIndex) { - if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false; - - return (Sections[sectionIndex].damage >= MaxHealth * LeakThreshold); + if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return false; } + return Sections[sectionIndex].damage >= MaxHealth * LeakThreshold; } public int SectionLength(int sectionIndex) @@ -1139,21 +1169,22 @@ namespace Barotrauma gapRect.Height += 20; bool horizontalGap = !IsHorizontal; + bool diagonalGap = false; if (Prefab.BodyRotation != 0.0f) { //rotation within a 90 deg sector (e.g. 100 -> 10, 190 -> 10, -10 -> 80) float sectorizedRotation = MathUtils.WrapAngleTwoPi(BodyRotation) % MathHelper.PiOver2; //diagonal if 30 < angle < 60 - bool diagonal = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3; + diagonalGap = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3; //gaps on the lower half of a diagonal wall are horizontal, ones on the upper half are vertical - if (diagonal) + if (diagonalGap) { horizontalGap = gapRect.Y - gapRect.Height / 2 < Position.Y; if (FlippedY) { horizontalGap = !horizontalGap; } } } - Sections[sectionIndex].gap = new Gap(gapRect, horizontalGap, Submarine); + Sections[sectionIndex].gap = new Gap(gapRect, horizontalGap, Submarine, isDiagonal: diagonalGap); //free the ID, because if we give gaps IDs we have to make sure they always match between the clients and the server and //that clients create them in the correct order along with every other entity created/removed during the round diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs index 30c8ae40b..d13d4216f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs @@ -271,20 +271,17 @@ namespace Barotrauma tags.Add("wall".ToIdentifier()); } - if (Description.IsNullOrEmpty()) + if (!descriptionIdentifier.IsEmpty) { - if (!descriptionIdentifier.IsEmpty) - { - Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}"); - } - else if (nameIdentifier.IsEmpty) - { - Description = TextManager.Get($"EntityDescription.{Identifier}"); - } - else - { - Description = TextManager.Get($"EntityDescription.{nameIdentifier}"); - } + Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}").Fallback(Description); + } + else if (nameIdentifier.IsEmpty) + { + Description = TextManager.Get($"EntityDescription.{Identifier}").Fallback(Description); + } + else + { + Description = TextManager.Get($"EntityDescription.{nameIdentifier}").Fallback(Description); } //backwards compatibility diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index b5f2d85d0..50e3f4507 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -187,7 +187,6 @@ namespace Barotrauma if (structure.Submarine != this || !structure.HasBody || structure.Indestructible) { continue; } realWorldCrushDepth = Math.Min(structure.CrushDepth, realWorldCrushDepth.Value); } - realWorldCrushDepth *= Info.GetRealWorldCrushDepthMultiplier(); } return realWorldCrushDepth.Value; } @@ -452,10 +451,27 @@ namespace Barotrauma verticalMoveDir = Math.Sign(verticalMoveDir); //do a raycast towards the top/bottom of the level depending on direction Vector2 potentialPos = new Vector2(spawnPos.X, verticalMoveDir > 0 ? Level.Loaded.Size.Y : 0); - if (PickBody(ConvertUnits.ToSimUnits(spawnPos), ConvertUnits.ToSimUnits(potentialPos), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null) + + //3 raycasts (left, middle and right side of the sub, so we don't accidentally raycast up a passage too narrow for the sub) + for (int x = -1; x <= 1; x++) { - //if the raycast hit a wall, attempt to place the spawnpos there - potentialPos.Y = ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) - 10; + Vector2 xOffset = Vector2.UnitX * minWidth / 2 * x; + if (PickBody( + ConvertUnits.ToSimUnits(spawnPos + xOffset), + ConvertUnits.ToSimUnits(potentialPos + xOffset), + collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null) + { + int offsetFromWall = 10 * -verticalMoveDir; + //if the raycast hit a wall, attempt to place the spawnpos there + if (verticalMoveDir > 0) + { + potentialPos.Y = Math.Min(potentialPos.Y, ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall); + } + else + { + potentialPos.Y = Math.Max(potentialPos.Y, ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall); + } + } } //step away from the top/bottom of the level, or from whatever wall the raycast hit, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index f201efb47..b8b2fd544 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -505,7 +505,6 @@ namespace Barotrauma if (wall.Submarine != submarine) { continue; } float wallCrushDepth = wall.CrushDepth; - if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; } float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth; if (pastCrushDepth > 0) { @@ -587,9 +586,13 @@ namespace Barotrauma newHull = Hull.FindHull(targetPos, null); } - var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine); - Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f); - if (adjacentGap == null) { return true; } + //if all the bodies of a wall have been disabled, we don't need to care about gaps (can always pass through) + if (!(contact.FixtureA.UserData is Structure wall) || !wall.AllSectionBodiesDisabled()) + { + var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine); + Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f); + if (adjacentGap == null) { return true; } + } if (newHull != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index 898f27b77..426941fe8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -24,7 +24,7 @@ namespace Barotrauma } public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine, Ruin } - public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver } + public enum SubmarineClass { Undefined, Scout, Attack, Transport } partial class SubmarineInfo : IDisposable { @@ -49,6 +49,12 @@ namespace Barotrauma } public CrewExperienceLevel RecommendedCrewExperience; + public int Tier + { + get; + set; + } + /// /// A random int that gets assigned when saving the sub. Used in mp campaign to verify that sub files match /// @@ -305,6 +311,7 @@ namespace Barotrauma RecommendedCrewExperience = original.RecommendedCrewExperience; RecommendedCrewSizeMin = original.RecommendedCrewSizeMin; RecommendedCrewSizeMax = original.RecommendedCrewSizeMax; + Tier = original.Tier; IsManuallyOutfitted = original.IsManuallyOutfitted; Tags = original.Tags; if (original.OutpostModuleInfo != null) @@ -386,6 +393,7 @@ namespace Barotrauma { Enum.TryParse(recommendedCrewExperience.Value, ignoreCase: true, out RecommendedCrewExperience); } + Tier = SubmarineElement.GetAttributeInt("tier", GetDefaultTier(Price)); if (SubmarineElement?.Attribute("type") != null) { @@ -407,7 +415,13 @@ namespace Barotrauma { if (SubmarineElement?.Attribute("class") != null) { - if (Enum.TryParse(SubmarineElement.GetAttributeString("class", "Undefined"), out SubmarineClass submarineClass)) + string classStr = SubmarineElement.GetAttributeString("class", "Undefined"); + if (classStr == "DeepDiver") + { + //backwards compatibility + SubmarineClass = SubmarineClass.Scout; + } + else if (Enum.TryParse(classStr, out SubmarineClass submarineClass)) { SubmarineClass = submarineClass; } @@ -538,25 +552,9 @@ namespace Barotrauma { realWorldCrushDepth = Level.DefaultRealWorldCrushDepth; } - realWorldCrushDepth *= GetRealWorldCrushDepthMultiplier(); return realWorldCrushDepth; } - /// - /// Based on - /// - public float GetRealWorldCrushDepthMultiplier() - { - if (SubmarineClass == SubmarineClass.DeepDiver) - { - return 1.2f; - } - else - { - return 1.0f; - } - } - //saving/loading ---------------------------------------------------- public void SaveAs(string filePath, System.IO.MemoryStream previewImage = null) { @@ -691,7 +689,7 @@ namespace Barotrauma System.IO.Stream stream; try { - stream = SaveUtil.DecompressFiletoStream(file); + stream = SaveUtil.DecompressFileToStream(file); } catch (System.IO.FileNotFoundException e) { @@ -748,5 +746,7 @@ namespace Barotrauma return doc; } + + public static int GetDefaultTier(int price) => price > 20000 ? 3 : price > 10000 ? 2 : 1; } -} \ No newline at end of file +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/NetStructBitField.cs b/Barotrauma/BarotraumaShared/SharedSource/NetStructBitField.cs index 964a4a9ca..4885dd27a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/NetStructBitField.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/NetStructBitField.cs @@ -83,7 +83,7 @@ namespace Barotrauma foreach (byte b in Buffer) { - msg.Write(b); + msg.WriteByte(b); } Dispose(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/BanList.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/BanList.cs index 12564a692..e08ff3ca1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/BanList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/BanList.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs index 144a567ca..8cb93bbb2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs @@ -98,7 +98,7 @@ namespace Barotrauma.Networking Task readTask = readStream?.ReadAsync(readTempBytes, 0, readTempBytes.Length, readCancellationToken.Token); if (readTask is null) { return -1; } - TimeSpan timeOut = TimeSpan.FromMilliseconds(100); + int timeOutMilliseconds = 100; for (int i = 0; i < 150; i++) { if (shutDown) @@ -106,12 +106,9 @@ namespace Barotrauma.Networking readCancellationToken?.Cancel(); return -1; } - - // BUG workaround for crash when closing the server under .NET 6.0, not sure if this is the proper way to fix it but it prevents it from crashing the client. - Markus -#if NET6_0 try { - if (readTask.IsCompleted || readTask.Wait(100, readCancellationToken.Token)) + if (readTask.IsCompleted || readTask.Wait(timeOutMilliseconds, readCancellationToken.Token)) { break; } @@ -120,12 +117,6 @@ namespace Barotrauma.Networking { return -1; } -#else - if (readTask.IsCompleted || readTask.Wait(timeOut)) - { - break; - } -#endif } if (readTask.Status != TaskStatus.RanToCompletion) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs index ef9030eea..1d7e30e5e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs @@ -177,8 +177,6 @@ namespace Barotrauma.Networking } public bool HasSpawned; //has the client spawned as a character during the current round - private readonly List kickVoters; - public HashSet GivenAchievements = new HashSet(); public ClientPermissions Permissions = ClientPermissions.None; @@ -186,11 +184,6 @@ namespace Barotrauma.Networking private readonly object[] votes; - public int KickVoteCount - { - get { return kickVoters.Count; } - } - partial void InitProjSpecific(); partial void DisposeProjSpecific(); public Client(string name, byte sessionId) @@ -198,8 +191,6 @@ namespace Barotrauma.Networking this.Name = name; this.SessionId = sessionId; - kickVoters = new List(); - votes = new object[Enum.GetNames(typeof(VoteType)).Length]; InitProjSpecific(); @@ -221,53 +212,22 @@ namespace Barotrauma.Networking { votes[i] = null; } - - kickVoters.Clear(); } - - public void AddKickVote(Client voter) - { - if (voter != null && !kickVoters.Contains(voter)) { kickVoters.Add(voter); } - } - - - public void RemoveKickVote(Client voter) - { - kickVoters.Remove(voter); - } - - public bool HasKickVoteFrom(Client voter) - { - return kickVoters.Contains(voter); - } - - public bool HasKickVoteFromSessionId(int id) - { - return kickVoters.Any(k => k.SessionId == id); - } - + public bool SessionOrAccountIdMatches(string userId) => (AccountId.IsSome() && Networking.AccountId.Parse(userId) == AccountId) || (byte.TryParse(userId, out byte sessionId) && SessionId == sessionId); - public static void UpdateKickVotes(IReadOnlyList connectedClients) - { - foreach (Client client in connectedClients) - { - client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter)); - } - } - public void WritePermissions(IWriteMessage msg) { - msg.Write(SessionId); + msg.WriteByte(SessionId); msg.WriteRangedInteger((int)Permissions, 0, (int)ClientPermissions.All); if (HasPermission(ClientPermissions.ConsoleCommands)) { - msg.Write((UInt16)PermittedConsoleCommands.Count); + msg.WriteUInt16((UInt16)PermittedConsoleCommands.Count); foreach (DebugConsole.Command command in PermittedConsoleCommands) { - msg.Write(command.names[0]); + msg.WriteString(command.names[0]); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs index 767c57723..b8e4460b7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/INetSerializableStruct.cs @@ -325,9 +325,14 @@ namespace Barotrauma Range range = GetEnumRange(type); int enumIndex = bitField.ReadInteger(range.Start, range.End); + if (typeof(T).GetCustomAttribute() != null) + { + return (T)(object)enumIndex; + } + foreach (T e in (T[])Enum.GetValues(type)) { - if ((int)Convert.ChangeType(e, e.GetTypeCode()) == enumIndex) { return e; } + if (((int)(object)e) == enumIndex) { return e; } } throw new InvalidOperationException($"An enum {type} with value {enumIndex} could not be found in {nameof(ReadEnum)}"); @@ -388,18 +393,18 @@ namespace Barotrauma private static bool ReadBoolean(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => bitField.ReadBoolean(); private static void WriteBoolean(bool b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { bitField.WriteBoolean(b); } - + private static byte ReadByte(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadByte(); - private static void WriteByte(byte b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteByte(byte b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteByte(b); } private static ushort ReadUInt16(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt16(); - private static void WriteUInt16(ushort b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteUInt16(ushort b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt16(b); } private static short ReadInt16(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadInt16(); - private static void WriteInt16(short b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteInt16(short b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteInt16(b); } private static uint ReadUInt32(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt32(); - private static void WriteUInt32(uint b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteUInt32(uint b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt32(b); } private static int ReadInt32(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) { @@ -421,14 +426,14 @@ namespace Barotrauma return; } - msg.Write(i); + msg.WriteInt32(i); } private static ulong ReadUInt64(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt64(); - private static void WriteUInt64(ulong b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteUInt64(ulong b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt64(b); } private static long ReadInt64(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadInt64(); - private static void WriteInt64(long b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteInt64(long b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteInt64(b); } private static float ReadSingle(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) { @@ -450,17 +455,17 @@ namespace Barotrauma return; } - msg.Write(f); + msg.WriteSingle(f); } private static double ReadDouble(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadDouble(); - private static void WriteDouble(double b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteDouble(double b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteDouble(b); } private static string ReadString(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadString(); - private static void WriteString(string b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteString(string b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteString(b); } private static Identifier ReadIdentifier(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadIdentifier(); - private static void WriteIdentifier(Identifier b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); } + private static void WriteIdentifier(Identifier b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteIdentifier(b); } private static AccountId ReadAccountId(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) { @@ -472,7 +477,7 @@ namespace Barotrauma private static void WriteAccountId(AccountId accountId, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { - msg.Write(accountId.StringRepresentation); + msg.WriteString(accountId.StringRepresentation); } private static Color ReadColor(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => attribute.IncludeColorAlpha ? inc.ReadColorR8G8B8A8() : inc.ReadColorR8G8B8(); @@ -519,7 +524,7 @@ namespace Barotrauma private static bool TryFindBehavior(out ReadWriteBehavior behavior) where T : notnull { bool found = TryFindBehavior(typeof(T), out var bhvr); - behavior = (ReadWriteBehavior)bhvr; + behavior = found ? (ReadWriteBehavior)bhvr : default; return found; } @@ -589,8 +594,8 @@ namespace Barotrauma return array; bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute() ?? type.GetCustomAttribute()) != null; - - bool NotStatic(MemberInfo info) + + static bool NotStatic(MemberInfo info) => info switch { PropertyInfo property => property.GetGetMethod() is { IsStatic: false }, @@ -656,7 +661,7 @@ namespace Barotrauma /// Using or will make the field or property optional. /// /// - interface INetSerializableStruct + internal interface INetSerializableStruct { /// /// Deserializes a network message into a struct. @@ -743,7 +748,7 @@ namespace Barotrauma IWriteMessage structWriteMsg = new WriteOnlyMessage(); WriteInternal(structWriteMsg, bitField); bitField.WriteToMessage(msg); - msg.Write(structWriteMsg.Buffer, 0, structWriteMsg.LengthBytes); + msg.WriteBytes(structWriteMsg.Buffer, 0, structWriteMsg.LengthBytes); } public void WriteInternal(IWriteMessage msg, IWritableBitField bitField) @@ -757,25 +762,4 @@ namespace Barotrauma } } } - - static class WriteOnlyMessageExtensions - { -#if CLIENT - public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header) - { - msg.Write((byte)header); - return msg; - } -#elif SERVER - public static IWriteMessage WithHeader(this IWriteMessage msg, ServerPacketHeader header) - { - msg.Write((byte)header); - return msg; - } -#endif - public static void Write(this IWriteMessage msg, INetSerializableStruct serializableStruct) - { - serializableStruct.Write(msg); - } - } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetBufferExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetBufferExtensions.cs deleted file mode 100644 index 0e407cc0c..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetBufferExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Barotrauma.Networking -{ - static class NetBufferExtensions - { - //public static void WriteEnum(this NetBuffer buffer, Enum value) - //{ - // buffer.WriteRangedInteger(0, Enum.GetValues(value.GetType()).Length - 1, Convert.ToInt32(value)); - //} - - //public static TEnum ReadEnum(this NetBuffer buffer) - //{ - // return (TEnum)(object)buffer.ReadRangedInteger(0, Enum.GetValues(typeof(TEnum)).Length - 1); - //} - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEventManager.cs index 0b154b322..c88d1c267 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEventManager.cs @@ -37,7 +37,7 @@ namespace Barotrauma.Networking //write an empty event to avoid messing up IDs //(otherwise the clients might read the next event in the message and think its ID //is consecutive to the previous one, even though we skipped over this broken event) - tempBuffer.Write(Entity.NullEntityID); + tempBuffer.WriteUInt16(Entity.NullEntityID); eventCount++; continue; } @@ -49,9 +49,9 @@ namespace Barotrauma.Networking break; } - tempBuffer.Write(e.EntityID); + tempBuffer.WriteUInt16(e.EntityID); tempBuffer.WriteVariableUInt32((uint)tempEventBuffer.LengthBytes); - tempBuffer.Write(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes); + tempBuffer.WriteBytes(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes); sentEvents.Add(e); eventCount++; @@ -60,9 +60,9 @@ namespace Barotrauma.Networking if (eventCount > 0) { msg.WritePadBits(); - msg.Write(eventsToSync[0].ID); - msg.Write((byte)eventCount); - msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); + msg.WriteUInt16(eventsToSync[0].ID); + msg.WriteByte((byte)eventCount); + msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs index be2a6d9df..e75202086 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs @@ -28,6 +28,19 @@ namespace Barotrauma.Networking public static bool IdMoreRecentOrMatches(ushort newId, ushort oldId) => !IdMoreRecent(oldId, newId); + /// + /// Returns some ID that is older than the input ID. There are no guarantees + /// regarding its relation to values other than the input. + /// + public static ushort GetIdOlderThan(ushort id) +#if DEBUG + // Debug implementation has some RNG to discourage bad assumptions about the return value + => unchecked((ushort)(id - 1 - Rand.Int(500, sync: Rand.RandSync.Unsynced))); +#else + // Release implementation favors performance + => unchecked((ushort)(id - 1)); +#endif + public static ushort Difference(ushort id1, ushort id2) { int diff = id2 > id1 ? id2 - id1 : id1 - id2; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs index af78cac5f..d4b897c9c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs @@ -47,32 +47,32 @@ namespace Barotrauma.Networking public static void WriteOrder(IWriteMessage msg, Order order, Character targetCharacter, bool isNewOrder) { - msg.Write(order.Prefab.Identifier); - msg.Write(targetCharacter == null ? (UInt16)0 : targetCharacter.ID); - msg.Write(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0); + msg.WriteIdentifier(order.Prefab.Identifier); + msg.WriteUInt16(targetCharacter == null ? (UInt16)0 : targetCharacter.ID); + msg.WriteUInt16(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0); // The option of a Dismiss order is written differently so we know what order we target // now that the game supports multiple current orders simultaneously if (!order.IsDismissal) { - msg.Write((byte)order.Options.IndexOf(order.Option)); + msg.WriteByte((byte)order.Options.IndexOf(order.Option)); } else { if (order.Option != Identifier.Empty) { - msg.Write(true); + msg.WriteBoolean(true); string[] dismissedOrder = order.Option.Value.Split('.'); - msg.Write((byte)dismissedOrder.Length); + msg.WriteByte((byte)dismissedOrder.Length); if (dismissedOrder.Length > 0) { Identifier dismissedOrderIdentifier = dismissedOrder[0].ToIdentifier(); var orderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier]; - msg.Write(dismissedOrderIdentifier); + msg.WriteIdentifier(dismissedOrderIdentifier); if (dismissedOrder.Length > 1) { Identifier dismissedOrderOption = dismissedOrder[1].ToIdentifier(); - msg.Write((byte)orderPrefab.Options.IndexOf(dismissedOrderOption)); + msg.WriteByte((byte)orderPrefab.Options.IndexOf(dismissedOrderOption)); } } } @@ -80,29 +80,29 @@ namespace Barotrauma.Networking { // If the order option is not specified for a Dismiss order, // we dismiss all current orders for the character - msg.Write(false); + msg.WriteBoolean(false); } } - msg.Write((byte)order.ManualPriority); - msg.Write((byte)order.TargetType); + msg.WriteByte((byte)order.ManualPriority); + msg.WriteByte((byte)order.TargetType); if (order.TargetType == Order.OrderTargetType.Position && order.TargetSpatialEntity is OrderTarget orderTarget) { - msg.Write(true); - msg.Write(orderTarget.Position.X); - msg.Write(orderTarget.Position.Y); - msg.Write(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID); + msg.WriteBoolean(true); + msg.WriteSingle(orderTarget.Position.X); + msg.WriteSingle(orderTarget.Position.Y); + msg.WriteUInt16(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID); } else { - msg.Write(false); + msg.WriteBoolean(false); if (order.TargetType == Order.OrderTargetType.WallSection) { - msg.Write((byte)(order.WallSectionIndex ?? 0)); + msg.WriteByte((byte)(order.WallSectionIndex ?? 0)); } } - msg.Write(isNewOrder); + msg.WriteBoolean(isNewOrder); } private void WriteOrder(IWriteMessage msg) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs index 65377a23f..b5721153d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs @@ -4,27 +4,27 @@ namespace Barotrauma.Networking { interface IWriteMessage { - void Write(bool val); + void WriteBoolean(bool val); void WritePadBits(); - void Write(byte val); - void Write(Int16 val); - void Write(UInt16 val); - void Write(Int32 val); - void Write(UInt32 val); - void Write(Int64 val); - void Write(UInt64 val); - void Write(Single val); - void Write(Double val); + void WriteByte(byte val); + void WriteInt16(Int16 val); + void WriteUInt16(UInt16 val); + void WriteInt32(Int32 val); + void WriteUInt32(UInt32 val); + void WriteInt64(Int64 val); + void WriteUInt64(UInt64 val); + void WriteSingle(Single val); + void WriteDouble(Double val); void WriteColorR8G8B8(Microsoft.Xna.Framework.Color val); void WriteColorR8G8B8A8(Microsoft.Xna.Framework.Color val); void WriteVariableUInt32(UInt32 val); - void Write(string val); - void Write(Identifier val); + void WriteString(string val); + void WriteIdentifier(Identifier val); void WriteRangedInteger(int val, int min, int max); void WriteRangedSingle(Single val, Single min, Single max, int bitCount); - void Write(byte[] val, int startIndex, int length); + void WriteBytes(byte[] val, int startIndex, int length); - void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int outLength); + byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength); int BitPosition { get; set; } int BytePosition { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs index a05e67a59..d4330e11e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs @@ -1,7 +1,6 @@ using Lidgren.Network; using System; -using System.Collections.Generic; -using Barotrauma.IO; +using System.IO; using System.IO.Compression; using System.Runtime.InteropServices; using System.Text; @@ -58,7 +57,7 @@ namespace Barotrauma.Networking bool testVal = MsgReader.ReadBoolean(buf, ref resetPos); if (testVal != val || resetPos != bitPos) { - DebugConsole.ThrowError("Boolean written incorrectly! " + testVal + ", " + val + "; " + resetPos + ", " + bitPos); + DebugConsole.ThrowError($"Boolean written incorrectly! {testVal}, {val}; {resetPos}, {bitPos}"); } #endif } @@ -125,7 +124,7 @@ namespace Barotrauma.Networking SingleUIntUnion su; su.UIntValue = 0; // must initialize every member of the union to avoid warning su.SingleValue = val; - + EnsureBufferSize(ref buf, bitPos + 32); NetBitWriter.WriteUInt32(su.UIntValue, 32, buf, bitPos); @@ -140,50 +139,48 @@ namespace Barotrauma.Networking WriteBytes(ref buf, ref bitPos, bytes, 0, 8); } - internal static void WriteColorR8G8B8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val) + internal static void WriteColorR8G8B8(ref byte[] buf, ref int bitPos, Color val) { EnsureBufferSize(ref buf, bitPos + 24); - + Write(ref buf, ref bitPos, val.R); Write(ref buf, ref bitPos, val.G); Write(ref buf, ref bitPos, val.B); } - - internal static void WriteColorR8G8B8A8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val) + + internal static void WriteColorR8G8B8A8(ref byte[] buf, ref int bitPos, Color val) { EnsureBufferSize(ref buf, bitPos + 32); - + Write(ref buf, ref bitPos, val.R); Write(ref buf, ref bitPos, val.G); Write(ref buf, ref bitPos, val.B); Write(ref buf, ref bitPos, val.A); } - + internal static void Write(ref byte[] buf, ref int bitPos, string val) { if (string.IsNullOrEmpty(val)) { - WriteVariableUInt32(ref buf, ref bitPos, (uint)0); + WriteVariableUInt32(ref buf, ref bitPos, 0u); return; } - + byte[] bytes = Encoding.UTF8.GetBytes(val); WriteVariableUInt32(ref buf, ref bitPos, (uint)bytes.Length); WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length); } - internal static int WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value) + internal static void WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value) { - int retval = 1; - uint remainingValue = (uint)value; + uint remainingValue = value; while (remainingValue >= 0x80) { Write(ref buf, ref bitPos, (byte)(remainingValue | 0x80)); - remainingValue = remainingValue >> 7; - retval++; + remainingValue >>= 7; } + Write(ref buf, ref bitPos, (byte)remainingValue); - return retval; } internal static void WriteRangedInteger(ref byte[] buf, ref int bitPos, int val, int min, int max) @@ -206,7 +203,7 @@ namespace Barotrauma.Networking EnsureBufferSize(ref buf, bitPos + numberOfBits); - NetBitWriter.WriteUInt32((UInt32)((float)maxVal * unit), numberOfBits, buf, bitPos); + NetBitWriter.WriteUInt32((UInt32)(maxVal * unit), numberOfBits, buf, bitPos); bitPos += numberOfBits; } @@ -225,9 +222,10 @@ namespace Barotrauma.Networking buf = new byte[byteLen + MsgConstants.BufferOverAllocateAmount]; return; } + if (buf.Length < byteLen) { - Array.Resize(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount); + Array.Resize(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount); } } } @@ -241,7 +239,7 @@ namespace Barotrauma.Networking return retval > 0; } - internal static void ReadPadBits(byte[] buf, ref int bitPos) + internal static void ReadPadBits(ref int bitPos) { int bitOffset = bitPos % 8; bitPos += (8 - bitOffset) % 8; @@ -326,15 +324,15 @@ namespace Barotrauma.Networking return BitConverter.ToDouble(bytes, 0); } - internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8(byte[] buf, ref int bitPos) + internal static Color ReadColorR8G8B8(byte[] buf, ref int bitPos) { byte r = ReadByte(buf, ref bitPos); byte g = ReadByte(buf, ref bitPos); byte b = ReadByte(buf, ref bitPos); return new Color(r, g, b, (byte)255); } - - internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8A8(byte[] buf, ref int bitPos) + + internal static Color ReadColorR8G8B8A8(byte[] buf, ref int bitPos) { byte r = ReadByte(buf, ref bitPos); byte g = ReadByte(buf, ref bitPos); @@ -354,8 +352,7 @@ namespace Barotrauma.Networking byte chunk = ReadByte(buf, ref bitPos); result |= (chunk & 0x7f) << shift; shift += 7; - if ((chunk & 0x80) == 0) - return (uint)result; + if ((chunk & 0x80) == 0) { return (uint)result; } } // ouch; failed to find enough bytes; malformed variable length number? @@ -378,23 +375,23 @@ namespace Barotrauma.Networking if ((bitPos & 7) == 0) { // read directly - string retval = System.Text.Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen); + string retval = Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen); bitPos += (8 * byteLen); return retval; } byte[] bytes = ReadBytes(buf, ref bitPos, byteLen); - return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); } internal static int ReadRangedInteger(byte[] buf, ref int bitPos, int min, int max) { - uint range = (uint)(max - min); - int numBits = NetUtility.BitsToHoldUInt(range); + uint range = (uint)(max - min); + int numBits = NetUtility.BitsToHoldUInt(range); - uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos); + uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos); bitPos += numBits; - + return (int)(min + rvalue); } @@ -403,51 +400,33 @@ namespace Barotrauma.Networking int maxInt = (1 << bitCount) - 1; int intVal = ReadRangedInteger(buf, ref bitPos, 0, maxInt); Single range = max - min; - return min + (range * ((Single)intVal) / ((Single)maxInt)); + return min + range * intVal / maxInt; } internal static byte[] ReadBytes(byte[] buf, ref int bitPos, int numberOfBytes) { byte[] retval = new byte[numberOfBytes]; NetBitWriter.ReadBytes(buf, numberOfBytes, bitPos, retval, 0); - bitPos += (8 * numberOfBytes); + bitPos += 8 * numberOfBytes; return retval; } } - class WriteOnlyMessage : IWriteMessage + internal sealed class WriteOnlyMessage : IWriteMessage { private byte[] buf = new byte[MsgConstants.InitialBufferSize]; - private int seekPos = 0; - private int lengthBits = 0; + private int seekPos; + private int lengthBits; public int BitPosition { - get - { - return seekPos; - } - set - { - seekPos = value; - } + get => seekPos; + set => seekPos = value; } - public int BytePosition - { - get - { - return seekPos / 8; - } - } + public int BytePosition => seekPos / 8; - public byte[] Buffer - { - get - { - return buf; - } - } + public byte[] Buffer => buf; public int LengthBits { @@ -464,15 +443,9 @@ namespace Barotrauma.Networking } } - public int LengthBytes - { - get - { - return (LengthBits + 7) / 8; - } - } + public int LengthBytes => (LengthBits + 7) / 8; - public void Write(bool val) + public void WriteBoolean(bool val) { MsgWriter.Write(ref buf, ref seekPos, val); } @@ -482,47 +455,47 @@ namespace Barotrauma.Networking MsgWriter.WritePadBits(ref buf, ref seekPos); } - public void Write(byte val) + public void WriteByte(byte val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(UInt16 val) + public void WriteUInt16(UInt16 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Int16 val) + public void WriteInt16(Int16 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(UInt32 val) + public void WriteUInt32(UInt32 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Int32 val) + public void WriteInt32(Int32 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(UInt64 val) + public void WriteUInt64(UInt64 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Int64 val) + public void WriteInt64(Int64 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Single val) + public void WriteSingle(Single val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Double val) + public void WriteDouble(Double val) { MsgWriter.Write(ref buf, ref seekPos, val); } @@ -531,7 +504,7 @@ namespace Barotrauma.Networking { MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val); } - + public void WriteColorR8G8B8A8(Color val) { MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val); @@ -542,14 +515,14 @@ namespace Barotrauma.Networking MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val); } - public void Write(String val) + public void WriteString(String val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Identifier val) + public void WriteIdentifier(Identifier val) { - Write(val.Value); + WriteString(val.Value); } public void WriteRangedInteger(int val, int min, int max) @@ -562,85 +535,67 @@ namespace Barotrauma.Networking MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount); } - public void Write(byte[] val, int startPos, int length) + public void WriteBytes(byte[] val, int startPos, int length) { MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length); } - - public void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int length) + + public byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int length) { + byte[] outBuf; if (LengthBytes <= MsgConstants.CompressionThreshold || !compressPastThreshold) { isCompressed = false; - if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); } + outBuf = new byte[LengthBytes]; Array.Copy(buf, outBuf, LengthBytes); length = LengthBytes; } else { - using (System.IO.MemoryStream output = new System.IO.MemoryStream()) + using MemoryStream output = new MemoryStream(); + + using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest)) { - using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest)) - { - dstream.Write(buf, 0, LengthBytes); - } - - byte[] compressedBuf = output.ToArray(); - //don't send the data as compressed if the data takes up more space after compression - //(which may happen when sending a sub/save file that's already been compressed with a better compression ratio) - if (compressedBuf.Length >= outBuf.Length) - { - isCompressed = false; - if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); } - Array.Copy(buf, outBuf, LengthBytes); - length = LengthBytes; - } - else - { - isCompressed = true; - if (compressedBuf.Length > outBuf.Length) { Array.Resize(ref outBuf, compressedBuf.Length); } - Array.Copy(compressedBuf, outBuf, compressedBuf.Length); - length = compressedBuf.Length; - DebugConsole.Log("Compressed message: " + LengthBytes + " to " + length); - } + dstream.Write(buf, 0, LengthBytes); + } + + byte[] compressedBuf = output.ToArray(); + //don't send the data as compressed if the data takes up more space after compression + //(which may happen when sending a sub/save file that's already been compressed with a better compression ratio) + if (compressedBuf.Length >= LengthBytes) + { + isCompressed = false; + outBuf = new byte[LengthBytes]; + Array.Copy(buf, outBuf, LengthBytes); + length = LengthBytes; + } + else + { + isCompressed = true; + outBuf = compressedBuf; + length = outBuf.Length; + DebugConsole.Log($"Compressed message: {LengthBytes} to {length}"); } } + + return outBuf; } } - class ReadOnlyMessage : IReadMessage + internal sealed class ReadOnlyMessage : IReadMessage { - private readonly byte[] buf; - private int seekPos = 0; - private int lengthBits = 0; + private int seekPos; + private int lengthBits; public int BitPosition { - get - { - return seekPos; - } - set - { - seekPos = value; - } + get => seekPos; + set => seekPos = value; } - public int BytePosition - { - get - { - return seekPos / 8; - } - } + public int BytePosition => seekPos / 8; - public byte[] Buffer - { - get - { - return buf; - } - } + public byte[] Buffer { get; } public int LengthBits { @@ -656,129 +611,126 @@ namespace Barotrauma.Networking } } - public int LengthBytes - { - get - { - return (LengthBits + 7) / 8; - } - } + public int LengthBytes => (LengthBits + 7) / 8; - public NetworkConnection Sender { get; private set; } - - public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int inLength, NetworkConnection sender) + public NetworkConnection Sender { get; } + + public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int byteLength, NetworkConnection sender) { Sender = sender; if (isCompressed) { byte[] decompressedData; - using (System.IO.MemoryStream input = new System.IO.MemoryStream(inBuf, startPos, inLength)) + using (MemoryStream input = new MemoryStream(inBuf, startPos, byteLength)) { - using (System.IO.MemoryStream output = new System.IO.MemoryStream()) + using (MemoryStream output = new MemoryStream()) { using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress)) { dstream.CopyTo(output); } + decompressedData = output.ToArray(); } } - buf = new byte[decompressedData.Length]; + + Buffer = new byte[decompressedData.Length]; try { - Array.Copy(decompressedData, 0, buf, 0, decompressedData.Length); + Array.Copy(decompressedData, 0, Buffer, 0, decompressedData.Length); } catch (ArgumentException e) { - throw new ArgumentException($"Failed to copy the incoming compressed buffer. Source buffer length: {decompressedData.Length}, start position: {0}, length: {decompressedData.Length}, destination buffer length: {buf.Length}.", e); + throw new ArgumentException( + $"Failed to copy the incoming compressed buffer. Source buffer length: {decompressedData.Length}, start position: {0}, length: {decompressedData.Length}, destination buffer length: {Buffer.Length}.", e); } + lengthBits = decompressedData.Length * 8; - DebugConsole.Log("Decompressing message: " + inLength + " to " + LengthBytes); + DebugConsole.Log("Decompressing message: " + byteLength + " to " + LengthBytes); } else { - buf = new byte[inBuf.Length]; + Buffer = new byte[inBuf.Length]; try { - Array.Copy(inBuf, startPos, buf, 0, inLength); + Array.Copy(inBuf, startPos, Buffer, 0, byteLength); } catch (ArgumentException e) { - throw new ArgumentException($"Failed to copy the incoming uncompressed buffer. Source buffer length: {inBuf.Length}, start position: {startPos}, length: {inLength}, destination buffer length: {buf.Length}.", e); + throw new ArgumentException($"Failed to copy the incoming uncompressed buffer. Source buffer length: {inBuf.Length}, start position: {startPos}, length: {byteLength}, destination buffer length: {Buffer.Length}.", e); } - lengthBits = inLength * 8; + + lengthBits = byteLength * 8; } + seekPos = 0; } public bool ReadBoolean() { - return MsgReader.ReadBoolean(buf, ref seekPos); + return MsgReader.ReadBoolean(Buffer, ref seekPos); } - public void ReadPadBits() - { - MsgReader.ReadPadBits(buf, ref seekPos); - } + public void ReadPadBits() { MsgReader.ReadPadBits(ref seekPos); } public byte ReadByte() { - return MsgReader.ReadByte(buf, ref seekPos); + return MsgReader.ReadByte(Buffer, ref seekPos); } public byte PeekByte() { - return MsgReader.PeekByte(buf, ref seekPos); + return MsgReader.PeekByte(Buffer, ref seekPos); } public UInt16 ReadUInt16() { - return MsgReader.ReadUInt16(buf, ref seekPos); + return MsgReader.ReadUInt16(Buffer, ref seekPos); } public Int16 ReadInt16() { - return MsgReader.ReadInt16(buf, ref seekPos); + return MsgReader.ReadInt16(Buffer, ref seekPos); } public UInt32 ReadUInt32() { - return MsgReader.ReadUInt32(buf, ref seekPos); + return MsgReader.ReadUInt32(Buffer, ref seekPos); } public Int32 ReadInt32() { - return MsgReader.ReadInt32(buf, ref seekPos); + return MsgReader.ReadInt32(Buffer, ref seekPos); } public UInt64 ReadUInt64() { - return MsgReader.ReadUInt64(buf, ref seekPos); + return MsgReader.ReadUInt64(Buffer, ref seekPos); } public Int64 ReadInt64() { - return MsgReader.ReadInt64(buf, ref seekPos); + return MsgReader.ReadInt64(Buffer, ref seekPos); } public Single ReadSingle() { - return MsgReader.ReadSingle(buf, ref seekPos); + return MsgReader.ReadSingle(Buffer, ref seekPos); } public Double ReadDouble() { - return MsgReader.ReadDouble(buf, ref seekPos); + return MsgReader.ReadDouble(Buffer, ref seekPos); } public UInt32 ReadVariableUInt32() { - return MsgReader.ReadVariableUInt32(buf, ref seekPos); + return MsgReader.ReadVariableUInt32(Buffer, ref seekPos); } public String ReadString() { - return MsgReader.ReadString(buf, ref seekPos); + return MsgReader.ReadString(Buffer, ref seekPos); } public Identifier ReadIdentifier() @@ -788,35 +740,35 @@ namespace Barotrauma.Networking public Color ReadColorR8G8B8() { - return MsgReader.ReadColorR8G8B8(buf, ref seekPos); + return MsgReader.ReadColorR8G8B8(Buffer, ref seekPos); } - + public Color ReadColorR8G8B8A8() { - return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos); + return MsgReader.ReadColorR8G8B8A8(Buffer, ref seekPos); } public int ReadRangedInteger(int min, int max) { - return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max); + return MsgReader.ReadRangedInteger(Buffer, ref seekPos, min, max); } public Single ReadRangedSingle(Single min, Single max, int bitCount) { - return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount); + return MsgReader.ReadRangedSingle(Buffer, ref seekPos, min, max, bitCount); } public byte[] ReadBytes(int numberOfBytes) { - return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes); + return MsgReader.ReadBytes(Buffer, ref seekPos, numberOfBytes); } } - class ReadWriteMessage : IWriteMessage, IReadMessage + internal sealed class ReadWriteMessage : IWriteMessage, IReadMessage { private byte[] buf; - private int seekPos = 0; - private int lengthBits = 0; + private int seekPos; + private int lengthBits; public ReadWriteMessage() { @@ -825,40 +777,22 @@ namespace Barotrauma.Networking lengthBits = 0; } - public ReadWriteMessage(byte[] b, int sPos, int lBits, bool copyBuf) + public ReadWriteMessage(byte[] b, int bitPos, int lBits, bool copyBuf) { buf = copyBuf ? (byte[])b.Clone() : b; - seekPos = sPos; + seekPos = bitPos; lengthBits = lBits; } public int BitPosition { - get - { - return seekPos; - } - set - { - seekPos = value; - } + get => seekPos; + set => seekPos = value; } - public int BytePosition - { - get - { - return seekPos / 8; - } - } + public int BytePosition => seekPos / 8; - public byte[] Buffer - { - get - { - return buf; - } - } + public byte[] Buffer => buf; public int LengthBits { @@ -874,17 +808,11 @@ namespace Barotrauma.Networking } } - public int LengthBytes - { - get - { - return (LengthBits + 7) / 8; - } - } + public int LengthBytes => (LengthBits + 7) / 8; - public NetworkConnection Sender { get { return null; } } + public NetworkConnection Sender => null; - public void Write(bool val) + public void WriteBoolean(bool val) { MsgWriter.Write(ref buf, ref seekPos, val); } @@ -894,47 +822,47 @@ namespace Barotrauma.Networking MsgWriter.WritePadBits(ref buf, ref seekPos); } - public void Write(byte val) + public void WriteByte(byte val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(UInt16 val) + public void WriteUInt16(UInt16 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Int16 val) + public void WriteInt16(Int16 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(UInt32 val) + public void WriteUInt32(UInt32 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Int32 val) + public void WriteInt32(Int32 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(UInt64 val) + public void WriteUInt64(UInt64 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Int64 val) + public void WriteInt64(Int64 val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Single val) + public void WriteSingle(Single val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Double val) + public void WriteDouble(Double val) { MsgWriter.Write(ref buf, ref seekPos, val); } @@ -943,7 +871,7 @@ namespace Barotrauma.Networking { MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val); } - + public void WriteColorR8G8B8A8(Color val) { MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val); @@ -954,14 +882,14 @@ namespace Barotrauma.Networking MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val); } - public void Write(String val) + public void WriteString(String val) { MsgWriter.Write(ref buf, ref seekPos, val); } - public void Write(Identifier val) + public void WriteIdentifier(Identifier val) { - Write(val.Value); + WriteString(val.Value); } public void WriteRangedInteger(int val, int min, int max) @@ -974,7 +902,7 @@ namespace Barotrauma.Networking MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount); } - public void Write(byte[] val, int startPos, int length) + public void WriteBytes(byte[] val, int startPos, int length) { MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length); } @@ -984,10 +912,7 @@ namespace Barotrauma.Networking return MsgReader.ReadBoolean(buf, ref seekPos); } - public void ReadPadBits() - { - MsgReader.ReadPadBits(buf, ref seekPos); - } + public void ReadPadBits() { MsgReader.ReadPadBits(ref seekPos); } public byte ReadByte() { @@ -1058,7 +983,7 @@ namespace Barotrauma.Networking { return MsgReader.ReadColorR8G8B8(buf, ref seekPos); } - + public Color ReadColorR8G8B8A8() { return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos); @@ -1079,10 +1004,10 @@ namespace Barotrauma.Networking return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes); } - public void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int outLength) + public byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength) { throw new InvalidOperationException("ReadWriteMessages are not to be sent"); } } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkEnums.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Enums.cs rename to Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkEnums.cs index 8f8e62d55..03712e66f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkEnums.cs @@ -40,6 +40,7 @@ namespace Barotrauma.Networking public static bool IsCompressed(this PacketHeader h) => h.HasFlag(PacketHeader.IsCompressed); + #warning TODO: remove? public static bool IsConnectionInitializationStep(this PacketHeader h) => h.HasFlag(PacketHeader.IsConnectionInitializationStep); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkExtensions.cs new file mode 100644 index 000000000..ee01e2197 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkExtensions.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.CompilerServices; +using Lidgren.Network; + +namespace Barotrauma.Networking +{ + internal static class WriteOnlyMessageExtensions + { +#if CLIENT + public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header) + { + msg.WriteByte((byte)header); + return msg; + } +#elif SERVER + public static IWriteMessage WithHeader(this IWriteMessage msg, ServerPacketHeader header) + { + msg.WriteByte((byte)header); + return msg; + } +#endif + public static void WriteNetSerializableStruct(this IWriteMessage msg, INetSerializableStruct serializableStruct) + { + serializableStruct.Write(msg); + } + + public static NetOutgoingMessage ToLidgren(this IWriteMessage msg, NetPeer peer) + { + NetOutgoingMessage outMsg = peer.CreateMessage(); + outMsg.Write(msg.Buffer, 0, msg.LengthBytes); + return outMsg; + } + } + + internal static class NetIncomingMessageExtensions + { + public static T ReadHeader(this NetIncomingMessage msg) where T : Enum + { + byte header = msg.ReadByte(); + return Unsafe.As(ref header); + } + + public static IReadMessage ToReadMessage(this NetIncomingMessage msg) + { + return new ReadWriteMessage(msg.Data, 0, msg.LengthBits, copyBuf: false); + } + } + + internal static class DeliveryMethodExtensions + { + public static NetDeliveryMethod ToLidgren(this DeliveryMethod deliveryMethod) => + deliveryMethod switch + { + DeliveryMethod.Unreliable => NetDeliveryMethod.Unreliable, + DeliveryMethod.Reliable => NetDeliveryMethod.ReliableUnordered, + DeliveryMethod.ReliableOrdered => NetDeliveryMethod.ReliableOrdered, + _ => NetDeliveryMethod.Unreliable + }; + + public static Steamworks.P2PSend ToSteam(this DeliveryMethod deliveryMethod) => + deliveryMethod switch + { + DeliveryMethod.Reliable => Steamworks.P2PSend.Reliable, + DeliveryMethod.ReliableOrdered => Steamworks.P2PSend.Unreliable, + _ => Steamworks.P2PSend.Unreliable + }; + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs new file mode 100644 index 000000000..6ae0d0e1b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs @@ -0,0 +1,142 @@ +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Linq; + +namespace Barotrauma.Networking +{ + [NetworkSerialize] + internal struct PeerPacketHeaders : INetSerializableStruct + { + public DeliveryMethod DeliveryMethod; + public PacketHeader PacketHeader; + public ConnectionInitialization? Initialization; + + public readonly void Deconstruct( + out DeliveryMethod deliveryMethod, + out PacketHeader packetHeader, + out ConnectionInitialization? initialization) + { + deliveryMethod = DeliveryMethod; + packetHeader = PacketHeader; + initialization = Initialization; + } + } + + [NetworkSerialize(ArrayMaxSize = ushort.MaxValue)] + internal struct ClientSteamTicketAndVersionPacket : INetSerializableStruct + { + public string Name; + public Option OwnerKey; + + #warning TODO: do something about the type of this + // It probably should be Option but we shouldn't build support for + // writing SteamIDs to INetSerializableStruct; we should consider adding + // attributes to give custom behaviors to specific members of a struct + public Option SteamId; + + public Option SteamAuthTicket; + public string GameVersion; + public Identifier Language; + } + + [NetworkSerialize] + internal struct SteamP2PInitializationRelayPacket : INetSerializableStruct + { + public ulong LobbyID; + public PeerPacketMessage Message; + } + + [NetworkSerialize] + internal struct SteamP2PInitializationOwnerPacket : INetSerializableStruct + { + public string OwnerName; + } + + + [NetworkSerialize(ArrayMaxSize = ushort.MaxValue)] + internal struct ServerPeerContentPackageOrderPacket : INetSerializableStruct + { + public string ServerName; + public ImmutableArray ContentPackages; + } + + [NetworkSerialize(ArrayMaxSize = ushort.MaxValue)] + internal struct PeerPacketMessage : INetSerializableStruct + { + public byte[] Buffer; + public readonly int Length => Buffer.Length; + + public readonly IReadMessage GetReadMessage() => new ReadWriteMessage(Buffer, 0, Length, copyBuf: false); + public readonly IReadMessage GetReadMessage(bool isCompressed, NetworkConnection conn) => new ReadOnlyMessage(Buffer, isCompressed, 0, Length, conn); + } + + [NetworkSerialize(ArrayMaxSize = byte.MaxValue)] + internal struct ClientPeerPasswordPacket : INetSerializableStruct + { + public byte[] Password; + } + + [NetworkSerialize] + internal struct ServerPeerPasswordPacket : INetSerializableStruct + { + public Option Salt; + public Option RetriesLeft; + } + + [NetworkSerialize] + internal struct PeerDisconnectPacket : INetSerializableStruct + { + public string Message; + } + + // ReSharper disable MemberCanBePrivate.Global, FieldCanBeMadeReadOnly.Global, UnassignedField.Global + public sealed class ServerContentPackage : INetSerializableStruct + { + [NetworkSerialize] + public string Name = ""; + + [NetworkSerialize(ArrayMaxSize = ushort.MaxValue)] + public byte[] HashBytes = Array.Empty(); + + [NetworkSerialize] + public ulong WorkshopId; + + [NetworkSerialize] + public uint InstallTimeDiffInSeconds; + + private Md5Hash? cachedHash; + private DateTime? cachedDateTime; + + public Md5Hash Hash + { + get => cachedHash ??= Md5Hash.BytesAsHash(HashBytes); + set + { + cachedHash = value; + HashBytes = value.ByteRepresentation; + } + } + + public DateTime InstallTime => cachedDateTime ??= DateTime.UtcNow + TimeSpan.FromSeconds(InstallTimeDiffInSeconds); + public RegularPackage? RegularPackage => ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash)); + public CorePackage? CorePackage => ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash)); + public ContentPackage? ContentPackage => (ContentPackage?)RegularPackage ?? CorePackage; + + public ServerContentPackage() { } + + public ServerContentPackage(ContentPackage contentPackage, DateTime referenceTime) + { + Name = contentPackage.Name; + Hash = contentPackage.Hash; + WorkshopId = contentPackage.SteamWorkshopId; + InstallTimeDiffInSeconds = + contentPackage.InstallTime is { } installTime + ? (uint)(installTime - referenceTime).TotalSeconds + : 0; + } + + public string GetPackageStr() => $"\"{Name}\" (hash {Hash.ShortRepresentation})"; + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs index 1f1c31596..5aad66b97 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs @@ -11,6 +11,11 @@ namespace Barotrauma.Networking { partial class RespawnManager : Entity, IServerSerializable { + /// + /// How much skills drop towards the job's default skill levels when dying + /// + const float SkillReductionOnDeath = 0.75f; + public enum State { Waiting, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index e626f1c23..74e9223ec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -209,48 +209,48 @@ namespace Barotrauma.Networking { case "float": msg.WriteVariableUInt32(4); - msg.Write((float)overrideValue); + msg.WriteSingle((float)overrideValue); break; case "int": msg.WriteVariableUInt32(4); - msg.Write((int)overrideValue); + msg.WriteInt32((int)overrideValue); break; case "vector2": msg.WriteVariableUInt32(8); - msg.Write(((Vector2)overrideValue).X); - msg.Write(((Vector2)overrideValue).Y); + msg.WriteSingle(((Vector2)overrideValue).X); + msg.WriteSingle(((Vector2)overrideValue).Y); break; case "vector3": msg.WriteVariableUInt32(12); - msg.Write(((Vector3)overrideValue).X); - msg.Write(((Vector3)overrideValue).Y); - msg.Write(((Vector3)overrideValue).Z); + msg.WriteSingle(((Vector3)overrideValue).X); + msg.WriteSingle(((Vector3)overrideValue).Y); + msg.WriteSingle(((Vector3)overrideValue).Z); break; case "vector4": msg.WriteVariableUInt32(16); - msg.Write(((Vector4)overrideValue).X); - msg.Write(((Vector4)overrideValue).Y); - msg.Write(((Vector4)overrideValue).Z); - msg.Write(((Vector4)overrideValue).W); + msg.WriteSingle(((Vector4)overrideValue).X); + msg.WriteSingle(((Vector4)overrideValue).Y); + msg.WriteSingle(((Vector4)overrideValue).Z); + msg.WriteSingle(((Vector4)overrideValue).W); break; case "color": msg.WriteVariableUInt32(4); - msg.Write(((Color)overrideValue).R); - msg.Write(((Color)overrideValue).G); - msg.Write(((Color)overrideValue).B); - msg.Write(((Color)overrideValue).A); + msg.WriteByte(((Color)overrideValue).R); + msg.WriteByte(((Color)overrideValue).G); + msg.WriteByte(((Color)overrideValue).B); + msg.WriteByte(((Color)overrideValue).A); break; case "rectangle": msg.WriteVariableUInt32(16); - msg.Write(((Rectangle)overrideValue).X); - msg.Write(((Rectangle)overrideValue).Y); - msg.Write(((Rectangle)overrideValue).Width); - msg.Write(((Rectangle)overrideValue).Height); + msg.WriteInt32(((Rectangle)overrideValue).X); + msg.WriteInt32(((Rectangle)overrideValue).Y); + msg.WriteInt32(((Rectangle)overrideValue).Width); + msg.WriteInt32(((Rectangle)overrideValue).Height); break; default: string strVal = overrideValue.ToString(); - msg.Write(strVal); + msg.WriteString(strVal); break; } } @@ -550,7 +550,7 @@ namespace Barotrauma.Networking public bool HasPassword { - get { return password != null; } + get { return !string.IsNullOrEmpty(password); } #if CLIENT set { @@ -953,14 +953,7 @@ namespace Barotrauma.Networking public void SetPassword(string password) { - if (string.IsNullOrEmpty(password)) - { - this.password = null; - } - else - { - this.password = password; - } + this.password = string.IsNullOrEmpty(password) ? null : password; } public static byte[] SaltPassword(byte[] password, int salt) @@ -977,14 +970,9 @@ namespace Barotrauma.Networking public bool IsPasswordCorrect(byte[] input, int salt) { - if (!HasPassword) return true; + if (!HasPassword) { return true; } byte[] saltedPw = SaltPassword(Encoding.UTF8.GetBytes(password), salt); - if (input.Length != saltedPw.Length) return false; - for (int i = 0; i < input.Length; i++) - { - if (input[i] != saltedPw[i]) return false; - } - return true; + return saltedPw.SequenceEqual(input); } /// @@ -1039,7 +1027,7 @@ namespace Barotrauma.Networking msg.WriteVariableUInt32((uint)monsterNames.Count); foreach (Identifier s in monsterNames) { - msg.Write(monsterEnabled[s]); + msg.WriteBoolean(monsterEnabled[s]); } msg.WritePadBits(); } @@ -1071,15 +1059,15 @@ namespace Barotrauma.Networking { if (ExtraCargo == null) { - msg.Write((UInt32)0); + msg.WriteUInt32((UInt32)0); return; } - msg.Write((UInt32)ExtraCargo.Count); + msg.WriteUInt32((UInt32)ExtraCargo.Count); foreach (KeyValuePair kvp in ExtraCargo) { - msg.Write(kvp.Key.Identifier); - msg.Write((byte)kvp.Value); + msg.WriteIdentifier(kvp.Key.Identifier); + msg.WriteByte((byte)kvp.Value); } } @@ -1109,7 +1097,7 @@ namespace Barotrauma.Networking msg.WriteVariableUInt32((uint)HiddenSubs.Count); foreach (string submarineName in HiddenSubs) { - msg.Write((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase))); + msg.WriteUInt16((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase))); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Voip/VoipQueue.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Voip/VoipQueue.cs index 29411143a..beafb895a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Voip/VoipQueue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Voip/VoipQueue.cs @@ -119,16 +119,16 @@ namespace Barotrauma.Networking { if (!CanSend) { throw new Exception("Called Write on a VoipQueue not set up for sending"); } - msg.Write((UInt16)LatestBufferID); - msg.Write(ForceLocal); msg.WritePadBits(); + msg.WriteUInt16((UInt16)LatestBufferID); + msg.WriteBoolean(ForceLocal); msg.WritePadBits(); lock (buffers) { for (int i = 0; i < BUFFER_COUNT; i++) { int index = (newestBufferInd + i + 1) % BUFFER_COUNT; - msg.Write((byte)bufferLengths[index]); - msg.Write(buffers[index], 0, bufferLengths[index]); + msg.WriteByte((byte)bufferLengths[index]); + msg.WriteBytes(buffers[index], 0, bufferLengths[index]); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/WhiteList.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/WhiteList.cs deleted file mode 100644 index cb197f3b9..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/WhiteList.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using Barotrauma.IO; -using System.Linq; - -namespace Barotrauma.Networking -{ - partial class WhiteListedPlayer - { - public string Name; - public string IP; - - public UInt16 UniqueIdentifier; - } - - partial class WhiteList - { - const string SavePath = "Data/whitelist.txt"; - - private List whitelistedPlayers; - public List WhiteListedPlayers - { - get { return whitelistedPlayers; } - } - - public bool Enabled; - - partial void InitProjSpecific(); - public WhiteList() - { - Enabled = false; - whitelistedPlayers = new List(); - - InitProjSpecific(); - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index 0cae07ba2..c78758bf3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -508,7 +508,14 @@ namespace Barotrauma try { - return (float)PropertyInfo.GetValue(parentObject, null); + if (PropertyType == typeof(int)) + { + return (int)PropertyInfo.GetValue(parentObject, null); + } + else + { + return (float)PropertyInfo.GetValue(parentObject, null); + } } catch (TargetInvocationException e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index ee27a296b..c2a73e504 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -308,7 +308,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in " + element + "! ", e); + DebugConsole.ThrowError($"Error when reading attribute \"{name}\" from {element}!", e); } } @@ -331,7 +331,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in " + element + "! ", e); + DebugConsole.ThrowError($"Error when reading attribute \"{name}\" from {element}!", e); } return val; @@ -350,7 +350,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in " + element + "! ", e); + DebugConsole.ThrowError($"Error when reading attribute \"{name}\" from {element}!", e); } return val; @@ -369,7 +369,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in " + element + "! ", e); + DebugConsole.ThrowError($"Error when reading attribute \"{name}\" from {element}!", e); } return val; @@ -388,7 +388,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in " + element + "! ", e); + DebugConsole.ThrowError($"Error when reading attribute \"{name}\" from {element}!", e); } return val; @@ -413,7 +413,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in " + element + "! ", e); + DebugConsole.ThrowError($"Error when reading attribute \"{name}\" from {element}!", e); } } @@ -438,7 +438,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in " + element + "! ", e); + DebugConsole.ThrowError($"Error when reading attribute \"{name}\" from {element}!", e); } } @@ -540,7 +540,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in " + element + "! ", e); + DebugConsole.ThrowError($"Error when reading attribute \"{name}\" from {element}!", e); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs index 4fc3665d5..4ba71d446 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs @@ -86,13 +86,6 @@ namespace Barotrauma return config; } - public static Config FromFile(string configFile, in Config? fallback = null) - { - XDocument doc = XMLExtensions.TryLoadXml(configFile); - - return FromElement(doc.Root ?? throw new InvalidOperationException("Unable to load config file: XML document is null."), fallback); - } - public static Config FromElement(XElement element, in Config? fallback = null) { Config retVal = fallback ?? GetDefault(); @@ -108,6 +101,7 @@ namespace Barotrauma #if CLIENT retVal.KeyMap = new KeyMapping(element.GetChildElements("keymapping"), retVal.KeyMap); retVal.InventoryKeyMap = new InventoryKeyMapping(element.GetChildElements("inventorykeymapping"), retVal.InventoryKeyMap); + LoadSubEditorImages(element); #endif return retVal; @@ -288,6 +282,7 @@ namespace Barotrauma { InputType.RadioChat, Keys.None }, { InputType.ActiveChat, Keys.T }, { InputType.CrewOrders, Keys.C }, + { InputType.ChatBox, Keys.B }, { InputType.Voice, Keys.V }, { InputType.RadioVoice, Keys.None }, @@ -331,9 +326,13 @@ namespace Barotrauma Dictionary bindings = fallback?.Bindings?.ToMutable() ?? defaultBindings.ToMutable(); foreach (InputType inputType in (InputType[])Enum.GetValues(typeof(InputType))) { - if (!bindings.ContainsKey(inputType)) { bindings.Add(inputType, defaultBindings[inputType]); } + if (!bindings.ContainsKey(inputType)) + { + bindings.Add(inputType, defaultBindings[inputType]); + } } + Dictionary savedBindings = new Dictionary(); bool playerConfigContainsNewChatBinds = false; bool playerConfigContainsRestoredVoipBinds = false; foreach (XElement element in elements) @@ -344,7 +343,34 @@ namespace Barotrauma { playerConfigContainsNewChatBinds |= result == InputType.ActiveChat; playerConfigContainsRestoredVoipBinds |= result == InputType.RadioVoice; - bindings[result] = element.GetAttributeKeyOrMouse(attribute.Name.LocalName, bindings[result]); + var keyOrMouse = element.GetAttributeKeyOrMouse(attribute.Name.LocalName, bindings[result]); + savedBindings.Add(result, keyOrMouse); + bindings[result] = keyOrMouse; + } + } + } + + // Check for duplicate binds when introducing new binds + foreach (var defaultBinding in defaultBindings) + { + if (!savedBindings.ContainsKey(defaultBinding.Key)) + { + foreach (var savedBinding in savedBindings) + { + if (savedBinding.Value == defaultBinding.Value) + { + OnGameMainHasLoaded += () => + { + (string, string)[] replacements = + { + ("[defaultbind]", $"\"{TextManager.Get($"inputtype.{defaultBinding.Key}")}\""), + ("[savedbind]", $"\"{TextManager.Get($"inputtype.{savedBinding.Key}")}\""), + ("[key]", $"\"{defaultBinding.Value.Name}\"") + }; + new GUIMessageBox(TextManager.Get("warning"), TextManager.GetWithVariables("duplicatebindwarning", replacements)); + }; + break; + } } } } @@ -442,6 +468,10 @@ namespace Barotrauma private static Config currentConfig; public static ref readonly Config CurrentConfig => ref currentConfig; +#if CLIENT + public static Action? OnGameMainHasLoaded; +#endif + public static void Init() { XDocument? currentConfigDoc = null; @@ -574,6 +604,8 @@ namespace Barotrauma .Select(kvp => new XAttribute($"slot{kvp.Index.ToString(CultureInfo.InvariantCulture)}", kvp.Bind.ToString()))); root.Add(inventoryKeyMappingElement); + + SubEditorScreen.ImageManager.Save(root); #endif configDoc.SaveSafe(PlayerConfigPath); @@ -600,5 +632,18 @@ namespace Barotrauma "Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); } } + +#if CLIENT + private static void LoadSubEditorImages(XElement configElement) + { + XElement? element = configElement?.Element("editorimages"); + if (element == null) + { + SubEditorScreen.ImageManager.Clear(alsoPending: true); + return; + } + SubEditorScreen.ImageManager.Load(element); + } +#endif } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 29f5fe5ba..9c1ce5c71 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1538,24 +1538,23 @@ namespace Barotrauma { Character targetCharacter = CharacterFromTarget(target); if (targetCharacter?.Info == null) { continue; } - if (!TalentTree.JobTalentTrees.TryGet(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; } - // for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well - IEnumerable disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))); + if (!TalentTree.JobTalentTrees.TryGet(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree characterTalentTree)) { continue; } foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos) { - IEnumerable viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s)); - if (viableTalents.None()) { continue; } - if (giveTalentInfo.GiveRandom) - { + { + // for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well + IEnumerable viableTalents = giveTalentInfo.TalentIdentifiers.Where(id => !targetCharacter.Info.UnlockedTalents.Contains(id) && !characterTalentTree.AllTalentIdentifiers.Contains(id)); + if (viableTalents.None()) { continue; } targetCharacter.GiveTalent(viableTalents.GetRandomUnsynced(), true); } else { - foreach (Identifier talent in viableTalents) + foreach (Identifier id in giveTalentInfo.TalentIdentifiers) { - targetCharacter.GiveTalent(talent, true); + if (targetCharacter.Info.UnlockedTalents.Contains(id) || characterTalentTree.AllTalentIdentifiers.Contains(id)) { continue; } + targetCharacter.GiveTalent(id, true); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Steam/SteamManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Steam/SteamManager.cs index 5eb17570d..f8815e014 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Steam/SteamManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Steam/SteamManager.cs @@ -64,7 +64,7 @@ namespace Barotrauma.Steam { if (!IsInitialized || !Steamworks.SteamClient.IsValid) { return false; } - return Steamworks.SteamApps.IsSubscribedFromFamilySharing; + return Steamworks.SteamApps.IsSubscribedFromFreeWeekend; } public static string GetUsername() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Text/LocalizedString/InputTypeLString.cs b/Barotrauma/BarotraumaShared/SharedSource/Text/LocalizedString/InputTypeLString.cs index cfd7c255b..3ae90e998 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Text/LocalizedString/InputTypeLString.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Text/LocalizedString/InputTypeLString.cs @@ -6,7 +6,13 @@ namespace Barotrauma public class InputTypeLString : LocalizedString { private readonly LocalizedString nestedStr; - public InputTypeLString(LocalizedString nStr) { nestedStr = nStr; } + private bool useColorHighlight; + + public InputTypeLString(LocalizedString nStr, bool useColorHighlight = false) + { + nestedStr = nStr; + this.useColorHighlight = useColorHighlight; + } protected override bool MustRetrieveValue() { @@ -23,8 +29,14 @@ namespace Barotrauma foreach (InputType? inputType in Enum.GetValues(typeof(InputType))) { if (!inputType.HasValue) { continue; } - cachedValue = cachedValue.Replace($"[{inputType}]", GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType.Value).Value, StringComparison.OrdinalIgnoreCase); - cachedValue = cachedValue.Replace($"[InputType.{inputType}]", GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType.Value).Value, StringComparison.OrdinalIgnoreCase); + + string keyBindText = GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType.Value).Value; + if (useColorHighlight) + { + keyBindText = $"‖color:gui.orange‖{keyBindText}‖end‖"; + } + cachedValue = cachedValue.Replace($"[{inputType}]", keyBindText, StringComparison.OrdinalIgnoreCase); + cachedValue = cachedValue.Replace($"[InputType.{inputType}]", keyBindText, StringComparison.OrdinalIgnoreCase); } #endif UpdateLanguage(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Text/TextManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Text/TextManager.cs index 2b1722674..823d7fd28 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Text/TextManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Text/TextManager.cs @@ -262,9 +262,9 @@ namespace Barotrauma string.Join(separator, parts.Select((part, index) => $"[{namePrefix}{index}]"))); } - public static LocalizedString ParseInputTypes(LocalizedString str) + public static LocalizedString ParseInputTypes(LocalizedString str, bool useColorHighlight = false) { - return new InputTypeLString(str); + return new InputTypeLString(str, useColorHighlight); } public static LocalizedString GetWithVariable(string tag, string varName, LocalizedString value, FormatCapitals formatCapitals = FormatCapitals.No) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs index c193ba8d7..fc64098bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs @@ -5,12 +5,11 @@ using System.Globalization; using System.Linq; using System.Xml.Linq; using Barotrauma.Items.Components; -using Barotrauma.Networking; // ReSharper disable ArrangeThisQualifier namespace Barotrauma { - internal class PropertyReference + internal sealed class PropertyReference { public object? OriginalValue { get; private set; } @@ -38,87 +37,63 @@ namespace Barotrauma /// Calculate the new value of the property /// /// level of the upgrade - /// Optional XElement reference, only used for error logging. /// - public float CalculateUpgrade(int level, XElement? sourceElement = null) + public object CalculateUpgrade(int level) { - if (OriginalValue is float || OriginalValue is int || OriginalValue is double) + switch (OriginalValue) { - var value = (float) OriginalValue; - - if (level == 0) { return value; } - - if (Multiplier[^1] != '%') + case float _: + case int _: + case double _: { - float multiplier = ParseValue(); - switch (Multiplier[0]) - { - case '*': - case 'x': - return value * (multiplier * level); - case '/': - return value / (multiplier * level); - case '-': - return value - (multiplier * level); - case '+': - return value + (multiplier * level); - case '=': - return multiplier; - } + var value = (float) OriginalValue; + return level == 0 ? value : CalculateUpgrade(value, level, Multiplier); } - else + case bool _ when bool.TryParse(Multiplier, out bool result): { - float multiplier = UpgradePrefab.ParsePercentage(Multiplier, Name, sourceElement, upgrade.Prefab.SuppressWarnings); - return ApplyPercentage(value, multiplier, level); + return result; + } + default: + { + DebugConsole.AddWarning($"Original value of \"{Name}\" in the upgrade \"{upgrade.Prefab.Name}\" is not a integer, float, double or boolean but {OriginalValue?.GetType()} with a value of ({OriginalValue}). \n" + + "The value has been assumed to be '0', did you forget a Convert.ChangeType()?"); + break; } - } - else - { - DebugConsole.AddWarning($"Original value of \"{Name}\" in the upgrade \"{upgrade.Prefab.Name}\" is not a integer, float or a double but {OriginalValue?.GetType()} with a value of ({OriginalValue}). \n" + - "The value has been assumed to be '0', did you forget a Convert.ChangeType()?"); } return 0; } - public static float CalculateUpgrade(object originalValue, int level, string Multiplier) + public static float CalculateUpgrade(float value, int level, string multiplier) { - if (originalValue is float || originalValue is int || originalValue is double) + if (multiplier[^1] != '%') { - var value = (float)originalValue; - - if (Multiplier[^1] != '%') - { - float multiplier = 1.0f; - if (Multiplier.Length > 1) - { - if (prefixCharacters.Contains(Multiplier[0])) - { - float.TryParse(Multiplier.Substring(1).Trim(), NumberStyles.Number, CultureInfo.InvariantCulture, out multiplier); - } - } - switch (Multiplier[0]) - { - case '*': - case 'x': - return value * (multiplier * level); - case '/': - return value / (multiplier * level); - case '-': - return value - (multiplier * level); - case '+': - return value + (multiplier * level); - case '=': - return multiplier; - } - } - else - { - float multiplier = UpgradePrefab.ParsePercentage(Multiplier, Identifier.Empty, suppressWarnings: true); - return ApplyPercentage(value, multiplier, level); - } + return CalculateUpgradeFloat(multiplier, value , level); } - return float.NaN; + + return ApplyPercentage(value, UpgradePrefab.ParsePercentage(multiplier, Identifier.Empty, suppressWarnings: true), level); + } + + private static float CalculateUpgradeFloat(string multiplier, float value, int level) + { + float multiplierFloat = ParseValue(multiplier, value); + + switch (multiplier[0]) + { + case '*': + case 'x': + return value * (multiplierFloat * level); + case '/': + return value / (multiplierFloat * level); + case '-': + return value - (multiplierFloat * level); + case '+': + return value + (multiplierFloat * level); + case '=': + return multiplierFloat; + } + + return 0; } /// @@ -133,7 +108,20 @@ namespace Barotrauma { if (savedValue.NameAsIdentifier() == Name) { - OriginalValue = savedValue.GetAttributeFloat("value", 0.0f); + string value = savedValue.GetAttributeString("value", string.Empty); + + if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out float floatValue)) + { + OriginalValue = floatValue; + } + else if (bool.TryParse(value, out bool boolValue)) + { + OriginalValue = boolValue; + } + else + { + OriginalValue = value; + } } } } @@ -155,30 +143,23 @@ namespace Barotrauma return attributes.Select(attribute => new PropertyReference(attribute.NameAsIdentifier(), attribute.Value, upgrade)).ToArray(); } - private float ParseValue() + private static float ParseValue(string multiplier, object? originalValue) { - if (Multiplier.Length > 1) + if (multiplier.Length > 1) { - if (prefixCharacters.Contains(Multiplier[0])) + if (prefixCharacters.Contains(multiplier[0])) { - if (float.TryParse(Multiplier.Substring(1).Trim(), NumberStyles.Number, CultureInfo.InvariantCulture, out float value)) { return value; } + if (float.TryParse(multiplier.Substring(1).Trim(), NumberStyles.Number, CultureInfo.InvariantCulture, out float value)) { return value; } - if (OriginalValue is float || OriginalValue is int || OriginalValue is double) { return (float) OriginalValue; } + if (originalValue is float || originalValue is int || originalValue is double) { return (float) originalValue; } } } - if (!upgrade.Prefab.SuppressWarnings) - { - DebugConsole.AddWarning($"Multiplier for {Name} is too short or does not contain proper prefix. \n" + - $"The value should start with {string.Join(",", prefixCharacters)} and contain a floating point value or another property. \n" + - "The value has been assumed to be '1'."); - } - return 1; } } - internal class Upgrade : IDisposable + internal sealed class Upgrade : IDisposable { private ISerializableEntity TargetEntity { get; } @@ -379,10 +360,10 @@ namespace Barotrauma { if (entity.SerializableProperties.TryGetValue(propertyReference.Name, out SerializableProperty? property) && property != null) { - object? originalValue = property!.GetValue(entity); + object? originalValue = property.GetValue(entity); propertyReference.SetOriginalValue(originalValue); - object newValue = Convert.ChangeType(propertyReference.CalculateUpgrade(Level, sourceElement), originalValue.GetType(), NumberFormatInfo.InvariantInfo); - property!.SetValue(entity, newValue); + object newValue = Convert.ChangeType(propertyReference.CalculateUpgrade(Level), originalValue.GetType(), NumberFormatInfo.InvariantInfo); + property.SetValue(entity, newValue); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs index bbb727d01..9ca176167 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs @@ -45,10 +45,14 @@ namespace Barotrauma public int GetBuyprice(int level, Location? location = null) { + int maxLevel = Prefab.MaxLevel; + + if (level > maxLevel) { maxLevel = level; } + int price = BasePrice; for (int i = 1; i <= level; i++) { - price += (int)(price * MathHelper.Lerp(IncreaseLow, IncreaseHigh, i / (float)Prefab.MaxLevel) / 100); + price += (int)(price * MathHelper.Lerp(IncreaseLow, IncreaseHigh, i / (float)maxLevel) / 100); } return location?.GetAdjustedMechanicalCost(price) ?? price; } @@ -108,7 +112,7 @@ namespace Barotrauma public readonly LocalizedString Name; public readonly IEnumerable ItemTags; - + public UpgradeCategory(ContentXElement element, UpgradeModulesFile file) : base(element, file) { selfItemTags = element.GetAttributeIdentifierArray("items", Array.Empty())?.ToImmutableHashSet() ?? ImmutableHashSet.Empty; @@ -137,13 +141,22 @@ namespace Barotrauma .Select(it => it.Identifier)); } - public bool CanBeApplied(Item item, UpgradePrefab? upgradePrefab) + public bool CanBeApplied(MapEntity item, UpgradePrefab? upgradePrefab) { - if (IsWallUpgrade) { return false; } + if (upgradePrefab != null && item.Submarine is { Info: var info } && !upgradePrefab.IsApplicable(info)) { return false; } + + bool isStructure = item is Structure; + switch (IsWallUpgrade) + { + case true: + return isStructure; + case false when isStructure: + return false; + } if (upgradePrefab != null && upgradePrefab.IsDisallowed(item)) { return false; } - return ((MapEntity)item).Prefab.GetAllowedUpgrades().Contains(Identifier) || + return item.Prefab.GetAllowedUpgrades().Contains(Identifier) || ItemTags.Any(tag => item.Prefab.Tags.Contains(tag) || item.Prefab.Identifier == tag); } @@ -173,6 +186,83 @@ namespace Barotrauma public override void Dispose() { } } + internal readonly struct UpgradeMaxLevelMod + { + private enum MaxLevelModType + { + Invalid, + Increase, + Set + } + + private readonly Either tierOrClass; + private readonly int value; + private readonly MaxLevelModType type; + + public int GetLevelAfter(int level) => + type switch + { + MaxLevelModType.Invalid => level, + MaxLevelModType.Increase => level + value, + MaxLevelModType.Set => value, + _ => throw new ArgumentOutOfRangeException() + }; + + public bool AppliesTo(SubmarineInfo sub) + { + if (type is MaxLevelModType.Invalid) { return false; } + + if (tierOrClass.TryGet(out int tier)) + { + return sub.Tier == tier; + } + + if (tierOrClass.TryGet(out SubmarineClass subClass)) + { + return sub.SubmarineClass == subClass; + } + + return false; + } + + public UpgradeMaxLevelMod(ContentXElement element) + { + bool isValid = true; + + SubmarineClass subClass = element.GetAttributeEnum("class", SubmarineClass.Undefined); + int tier = element.GetAttributeInt("tier", 0); + if (subClass != SubmarineClass.Undefined) + { + tierOrClass = subClass; + } + else + { + tierOrClass = tier; + } + + string stringValue = element.GetAttributeString("level", null) ?? string.Empty; + value = 0; + + if (string.IsNullOrWhiteSpace(stringValue)) { isValid = false; } + + char firstChar = stringValue[0]; + + if (!int.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var intValue)) { isValid = false; } + value = intValue; + + if (firstChar.Equals('+') || firstChar.Equals('-')) + { + type = MaxLevelModType.Increase; + } + else + { + type = MaxLevelModType.Set; + } + + if (!isValid) { type = MaxLevelModType.Invalid; } + } + } + internal partial class UpgradePrefab : UpgradeContentPrefab { public static readonly PrefabCollection Prefabs = new PrefabCollection( @@ -205,10 +295,19 @@ namespace Barotrauma onRemoveOverrideFile: null ); - public int MaxLevel { get; } + private readonly int maxLevel; + + public int MaxLevel + { + get + { + Submarine? sub = GameMain.GameSession?.Submarine ?? Submarine.MainSub; + return sub is { Info: var info } ? GetMaxLevel(info) : maxLevel; + } + } public LocalizedString Name { get; } - + public LocalizedString Description { get; } public float IncreaseOnTooltip { get; } @@ -243,17 +342,19 @@ namespace Barotrauma public bool IsWallUpgrade => UpgradeCategories.All(u => u.IsWallUpgrade); private Dictionary targetProperties { get; } + private readonly ImmutableArray MaxLevelsMods; public UpgradePrefab(ContentXElement element, UpgradeModulesFile file) : base(element, file) { Name = element.GetAttributeString("name", string.Empty)!; Description = element.GetAttributeString("description", string.Empty)!; - MaxLevel = element.GetAttributeInt("maxlevel", 1); + maxLevel = element.GetAttributeInt("maxlevel", 1); SuppressWarnings = element.GetAttributeBool("supresswarnings", false); HideInMenus = element.GetAttributeBool("hideinmenus", false); SourceElement = element; var targetProperties = new Dictionary(); + var maxLevels = new List(); Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", ""); if (!nameIdentifier.IsEmpty) @@ -291,6 +392,11 @@ namespace Barotrauma Price = new UpgradePrice(this, subElement); break; } + case "maxlevel": + { + maxLevels.Add(new UpgradeMaxLevelMod(subElement)); + break; + } #if CLIENT case "decorativesprite": { @@ -321,14 +427,35 @@ namespace Barotrauma #endif this.targetProperties = targetProperties; + MaxLevelsMods = maxLevels.ToImmutableArray(); upgradeCategoryIdentifiers = element.GetAttributeIdentifierArray("categories", Array.Empty())? .ToImmutableHashSet() ?? ImmutableHashSet.Empty; } - public bool IsDisallowed(Item item) + public int GetMaxLevel(SubmarineInfo info) { - return item.DisallowedUpgradeSet.Contains(Identifier) || UpgradeCategories.Any(c => item.DisallowedUpgradeSet.Contains(c.Identifier)); + int level = maxLevel; + + foreach (UpgradeMaxLevelMod mod in MaxLevelsMods) + { + if (mod.AppliesTo(info)) { level = mod.GetLevelAfter(level); } + } + + return level; + } + + public bool IsApplicable(SubmarineInfo? info) + { + if (info is null) { return false; } + + return GetMaxLevel(info) > 0; + } + + public bool IsDisallowed(MapEntity item) + { + return item.DisallowedUpgradeSet.Contains(Identifier) + || UpgradeCategories.Any(c => item.DisallowedUpgradeSet.Contains(c.Identifier)); } public static UpgradePrefab? Find(Identifier identifier) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/IPExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/IPExtensions.cs deleted file mode 100644 index e9934ec31..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/IPExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; - -namespace Barotrauma -{ - public static class IPExtensions - { - //TODO: remove? - //workaround for .NET Framework 4.5 bug; presumably fixed in later versions - //see https://stackoverflow.com/questions/23608829/why-does-ipaddress-maptoipv4-throw-argumentoutofrangeexception - public static IPAddress MapToIPv4NoThrow(this IPAddress address) - { - byte[] addressBytes = address.GetAddressBytes(); - - return new IPAddress(addressBytes.Skip(addressBytes.Length - 4).ToArray()); - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs index 1a2e8968c..49b231dfe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs @@ -231,7 +231,19 @@ namespace Barotrauma.IO public static bool IsPathRooted(string path) => System.IO.Path.IsPathRooted(path); - public static IEnumerable GetInvalidFileNameChars() => System.IO.Path.GetInvalidFileNameChars(); + private static readonly ImmutableHashSet invalidFileNameChars = ImmutableHashSet.Create + ( + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31, ':', '*', '?', '\\', '/' + ); + + /// + /// Returns file name characters that are invalid on any of our supported platforms (essentially the list of invalid characters on Windows) + /// + public static ImmutableHashSet GetInvalidFileNameCharsCrossPlatform() => invalidFileNameChars; } public static class Directory diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index 6ca456993..8df000e58 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -1,15 +1,13 @@ using System; -using System.Collections; using System.Collections.Generic; -using Barotrauma.IO; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading; using System.Xml.Linq; -using Steamworks.Data; -using Color = Microsoft.Xna.Framework.Color; using System.Text.RegularExpressions; +using Barotrauma.IO; +using Microsoft.Xna.Framework; namespace Barotrauma { @@ -20,7 +18,7 @@ namespace Barotrauma #if OSX //"/*user*/Library/Application Support/Daedalic Entertainment GmbH/" on Mac - public static string SaveFolder = Path.Combine( + public static readonly string SaveFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support", @@ -29,7 +27,7 @@ namespace Barotrauma #else //"C:/Users/*user*/AppData/Local/Daedalic Entertainment GmbH/" on Windows //"/home/*user*/.local/share/Daedalic Entertainment GmbH/" on Linux - public static string SaveFolder = Path.Combine( + public static readonly string SaveFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Daedalic Entertainment GmbH", "Barotrauma"); @@ -165,15 +163,6 @@ namespace Barotrauma return ownedSubmarines; } - /*public static void LoadMultiplayerCampaignState(string filePath, MultiPlayerCampaign multiplayerCampaign) - { - DebugConsole.Log("Loading save file for an existing game session (" + filePath + ")"); - DecompressToDirectory(filePath, TempPath, null); - XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml")); - if (doc == null) { return; } - gameSession.Load(doc.Root); - }*/ - public static XDocument LoadGameSessionDoc(string filePath) { DebugConsole.Log("Loading game session doc: " + filePath); @@ -391,73 +380,69 @@ namespace Barotrauma } - public static System.IO.Stream DecompressFiletoStream(string fileName) + public static System.IO.Stream DecompressFileToStream(string fileName) { - using (FileStream originalFileStream = File.Open(fileName, System.IO.FileMode.Open)) - { - System.IO.MemoryStream decompressedFileStream = new System.IO.MemoryStream(); + using FileStream originalFileStream = File.Open(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read); + System.IO.MemoryStream streamToReturn = new System.IO.MemoryStream(); - using (GZipStream decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress)) - { - decompressionStream.CopyTo(decompressedFileStream); - return decompressedFileStream; - } - } + using GZipStream gzipStream = new GZipStream(originalFileStream, CompressionMode.Decompress); + gzipStream.CopyTo(streamToReturn); + + streamToReturn.Position = 0; + return streamToReturn; } - private static bool DecompressFile(bool writeFile, string sDir, GZipStream zipStream, ProgressDelegate progress, out string fileName) + private static bool IsExtractionPathValid(string rootDir, string fileDir) + { + string getFullPath(string dir) + => (string.IsNullOrEmpty(dir) + ? Directory.GetCurrentDirectory() + : Path.GetFullPath(dir)) + .CleanUpPathCrossPlatform(correctFilenameCase: false); + + string rootDirFull = getFullPath(rootDir); + string fileDirFull = getFullPath(fileDir); + + return fileDirFull.StartsWith(rootDirFull, StringComparison.OrdinalIgnoreCase); + } + + private static bool DecompressFile(bool writeFile, string sDir, System.IO.BinaryReader reader, ProgressDelegate progress, out string fileName) { fileName = null; + if (reader.PeekChar() < 0) { return false; } + //Decompress file name - byte[] bytes = new byte[sizeof(int)]; - int Readed = Read(zipStream, bytes, sizeof(int)); - if (Readed < sizeof(int)) - return false; - - int iNameLen = BitConverter.ToInt32(bytes, 0); - if (iNameLen > 255) + int nameLen = reader.ReadInt32(); + if (nameLen > 255) { - throw new Exception("Failed to decompress \"" + sDir + "\" (file name length > 255). The file may be corrupted."); + throw new Exception( + $"Failed to decompress \"{sDir}\" (file name length > 255). The file may be corrupted."); } - bytes = new byte[sizeof(char)]; - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < iNameLen; i++) - { - Read(zipStream, bytes, sizeof(char)); - char c = BitConverter.ToChar(bytes, 0); - sb.Append(c); - } - string sFileName = sb.ToString().Replace('\\', '/'); + byte[] strBytes = reader.ReadBytes(nameLen * sizeof(char)); + string sFileName = Encoding.Unicode.GetString(strBytes) + .Replace('\\', '/'); fileName = sFileName; progress?.Invoke(sFileName); //Decompress file content - bytes = new byte[sizeof(int)]; - Read(zipStream, bytes, sizeof(int)); - int iFileLen = BitConverter.ToInt32(bytes, 0); - - bytes = new byte[iFileLen]; - Read(zipStream, bytes, bytes.Length); + int contentLen = reader.ReadInt32(); + byte[] contentBytes = reader.ReadBytes(contentLen); string sFilePath = Path.Combine(sDir, sFileName); string sFinalDir = Path.GetDirectoryName(sFilePath); - string sDirFull = (string.IsNullOrEmpty(sDir) ? Directory.GetCurrentDirectory() : Path.GetFullPath(sDir)).CleanUpPathCrossPlatform(correctFilenameCase: false); - string sFinalDirFull = (string.IsNullOrEmpty(sFinalDir) ? Directory.GetCurrentDirectory() : Path.GetFullPath(sFinalDir)).CleanUpPathCrossPlatform(correctFilenameCase: false); - - if (!sFinalDirFull.StartsWith(sDirFull, StringComparison.OrdinalIgnoreCase)) + if (!IsExtractionPathValid(sDir, sFinalDir)) { throw new InvalidOperationException( $"Error extracting \"{sFileName}\": cannot be extracted to parent directory"); } - + if (!writeFile) { return true; } - if (!Directory.Exists(sFinalDir)) - Directory.CreateDirectory(sFinalDir); + Directory.CreateDirectory(sFinalDir); int maxRetries = 4; for (int i = 0; i <= maxRetries; i++) { @@ -465,7 +450,7 @@ namespace Barotrauma { using (FileStream outFile = File.Open(sFilePath, System.IO.FileMode.Create, System.IO.FileAccess.Write)) { - outFile.Write(bytes, 0, iFileLen); + outFile.Write(contentBytes, 0, contentLen); } break; } @@ -479,26 +464,6 @@ namespace Barotrauma return true; } - private static int Read(GZipStream zipStream, byte[] bytes, int amount) - { - int read = 0; - - // BUG workaround for .NET6 causing save decompression to fail -#if NET6_0 - for (int i = 0; i < amount; i++) - { - int result = zipStream.ReadByte(); - if (result < 0) { break; } - - bytes[i] = (byte) result; - read++; - } -#else - read = zipStream.Read(bytes, 0, amount); -#endif - return read; - } - public static void DecompressToDirectory(string sCompressedFile, string sDir, ProgressDelegate progress) { DebugConsole.Log("Decompressing " + sCompressedFile + " to " + sDir + "..."); @@ -507,9 +472,9 @@ namespace Barotrauma { try { - using (FileStream inFile = File.Open(sCompressedFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)) - using (GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true)) - while (DecompressFile(true, sDir, zipStream, progress, out _)) { }; + using (var memStream = DecompressFileToStream(sCompressedFile)) + using (System.IO.BinaryReader reader = new System.IO.BinaryReader(memStream)) + while (DecompressFile(true, sDir, reader, progress, out _)) { }; break; } @@ -530,12 +495,12 @@ namespace Barotrauma { try { - using FileStream inFile = File.Open(sCompressedFile, System.IO.FileMode.Open, System.IO.FileAccess.Read); - using GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true); - while (DecompressFile(false, "", zipStream, null, out string fileName)) - { - paths.Add(fileName); - } + using (var memStream = DecompressFileToStream(sCompressedFile)) + using (System.IO.BinaryReader reader = new System.IO.BinaryReader(memStream)) + while (DecompressFile(false, "", reader, null, out string fileName)) + { + paths.Add(fileName); + } } catch (System.IO.IOException e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs index e7a38ef78..1f5b69a16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs @@ -3,13 +3,12 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Barotrauma.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; -using System.Runtime.CompilerServices; namespace Barotrauma { @@ -161,7 +160,7 @@ namespace Barotrauma public static string RemoveInvalidFileNameChars(string fileName) { - var invalidChars = Path.GetInvalidFileNameChars().Concat(new char[] {':', ';', '<', '>', '"', '/', '\\', '|', '?', '*'}); + var invalidChars = Path.GetInvalidFileNameCharsCrossPlatform().Concat(new char[] {';'}); foreach (char invalidChar in invalidChars) { fileName = fileName.Replace(invalidChar.ToString(), ""); @@ -424,7 +423,7 @@ namespace Barotrauma for (int i = 0; i < numberOfBits; i++) { bool bit = originalBuffer.ReadBoolean(); - buffer.Write(bit); + buffer.WriteBoolean(bit); } buffer.BitPosition = 0; @@ -713,9 +712,14 @@ namespace Barotrauma return e; } - public static void ThrowIfNull(T o) + public static void ThrowIfNull([NotNull] T o) { if (o is null) { throw new ArgumentNullException(); } } + + public static string GetFormattedPercentage(float v) + { + return TextManager.GetWithVariable("percentageformat", "[value]", ((int)MathF.Round(v * 100)).ToString()).Value; + } } } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 3b27b64c9..1e642870f 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,129 @@ +--------------------------------------------------------------------------------------------------------- +v0.19.3.0 +--------------------------------------------------------------------------------------------------------- + +Unstable only: +- Fixed deconstructors and research stations showing what unidentified genetic materials are. +- Fixed fabricator not showing the number of required items on recipes. +- Fixed a crash in Fabriactor.DrawInputOverLay. +- Visual improvements to draggable item UIs. +- Fixed reactor sliders adjusting to the received signals until the received value has been matched, leading to buggy-looking behavior when the sliders adjust by themselves after the signal wires have been disconnected. Now the reactor stops following the signals if nothing is received in 1 second. +- Misc fixes and improvements to Camel. +- Fixed vote-kicked players being unable to rejoin when they're unbanned. + +Changes: +- Updated our runtime to .NET 6, which should yield significant performance improvements. Do note that this unfortunately means we'll have to drop support for macOS versions older than 10.15, but we have taken some measures to help the affected Mac players continue having access to Barotrauma. More info here https://store.steampowered.com/news/app/602960/view/3367025204056277713. +- First version of reworked tutorials. Still very much a work in progress (only the 1st tutorial that teaches you the very basics is close to done), but would still appreciate feedback! +- Changed unit load device capacity to 12 (because the sprite has space for 12) and made them waterproof. +- Changed fabricator skill calculations: the most inadequate of the required skills determines the fabrication time (instead of the average). +- Made dying drop a characters' skills towards the maximum initial skill instead of minimum. +- Added a new keybind for opening and closing the chat box. The default bind is B. +- Added a warning if a new keybind overlaps with any of player's existing binds. +- Balanced existing mineral missions: adjusted rewards & required minerals and required some minerals to be handed over to the outposts as proof of their existence. +- Added new mining missions, including some in the abyss. + +Submarines: +- Added submarine tiers. Higher-tier submarines can be upgraded futher than lower-tier submarines. +- Overhauled and balanced submarine upgrades. +- Added an upgrade that adds a mineral scanner to nav terminals and sonar monitors. +- Submarine class now affects which upgrades are available for the sub. +- Removed the Deep Diver class: the way we see it, Deep Divers didn't have a clear enough role in the game, especially considering that hull upgrades served pretty much the same purpose. In practice, the only clear benefit of a Deep Diver was being able to get through the very last levels of the campaign, and having to switch to one just for that purpose wasn't fun. Now any submarine with full hull upgrades can get all the way to the end of the campaign. + +Fixes: +- Fixed a level generation issue that sometimes made the level impassable if there happened to be a cave right above the outpost. +- Fixed holes on sloped walls being impossible to pass through when you're swimming straight down/up (or straight right/left depending on the wall): the walls are technically considered either horizontal or vertical (depending on the angle of the slope), and you would have to swim in a direction perpendicular to this "technical" direction of the wall. +- Fixed retrying the Hognose mission making a new Hognose join your crew every time. +- Fixed idling NPCs sometimes getting stuck on ladders. +- Fixed mirrored turrets being displayed backwards on status monitor. +- Fixed character's hands getting "stuck" if you handcuff yourself while dragging someone. +- Fixed dragged character's arms not being pulled towards you, making it look like you're dragging them without touching if you run or walk away while dragging. +- Fixed dragged bots slowly moving constantly, preventing them from switching to the normal standing pose. +- Fixed bots having trouble fixing leaks in multi-hull rooms: they were required to be in the same hull as the leak, which prevented them from fixing leaks in e.g. R-29's bilge. +- Fixed combat missions not ending the round if both crews are dead. +- Fixed bots stating the name of the character they're firing at with turrets, making it seem like they know the name of every pirate they come across and magically recognize them through the walls of the enemy sub. +- Fixed chaingun rotation speed not being affected by the weapons skill. +- Fixed crashing when using ':' in item assembly names on Linux platforms. +- Fixed ImmuneToPressure ability flag being ignored on characters who don't need air (in practice meaning that you can get killed by pressure if you get huskified even if you have a talent that makes you immune to pressure). +- Disallow using the "reloadwearables" and "loadwearable" when a gamesession is active because it leads to a crash. These commands are intended to be used in the character editor. +- Fixed geneticmaterialcrawler_unresearched3 producing mudraptor genes. + +Submarine editor: +- Fixed sub editor background images not saving. + +Optimization: +- Optimized affliction that apply other afflictions on the character (e.g. radiation sickness, drunkenness, opiate withdrawal). +- Physics optimization: fixed submerged items' physics bodies staying active indefinitely even after they've come to rest due to buoyant forces being applied on them constantly. Now we stop updating bodies that have come to rest on the floor and aren't light enough to float. +- Optimized AI objectives that make the bots fetch items (combat, contain item, decontain item, get item). + +Multiplayer: +- Fixed "kick" button staying disabled indefinitely if you vote to kick someone and the vote doesn't go through. +- Fixed Steamworks publish tab showing the "free weekend" message when using Steam family sharing. + +Modding: +- Fixed inability to localize item names if the name is defined directly in the item config. +- Allowed defining where mineral mission resources are spawned using the "positiontype" attribute. The supported types are "MainPath", "SidePath", "Cave", and "AbyssCave". + +--------------------------------------------------------------------------------------------------------- +v0.19.2.0 +--------------------------------------------------------------------------------------------------------- + +Unstable only: +- Fixed an item getting placed in the sub editor when you select one from the entity list. +- Made flak cannon ammo proximity-triggered. +- Made flak cannon's sounds and visual effects more powerful. +- Fixed LightComponents turning themselves on when the round starts. +- Fixed "Input string was not in a correct format" when loading favorite/recent servers with an empty QueryPort. +- Fixed deconstructor showing contained items in the deconstruction output. +- Fixed double coilgun sometimes not playing a sound on every shot. +- Change the cursor to a hand on draggable item UIs. +- Fixed crashing during level generation if abyss resources have not been configured. +- Fixed hull & item repairs (and presumably replacing lost shuttles) not working in single player unless you save and reload. +- Visual improvements to Camel. +- Fixed timer that prevents recently joined players from voting to kick working the wrong way around. +- Fixed inability to resize a resizeable structure when placing it in the sub editor. + +Changes: +- Reintroduced separate local/radio voice chat keys as a legacy option. Now it's again possible to speak with voice activation by default and use a push-to-talk button for radio, the same way as before, by setting the chat mode to Local and using the new radio voice chat hotkey. +- Changed reactor temperature bar colors (from blue to red). +- Higher quality stun batons cause heavier stun. +- Disabled the autodocking prompt (which verifies whether you actually want to dock when docking is initiated by an automated circuit) in single player. +- Minor tweaks to the end of PvP missions to make them a little less underwhelming: instead of ending the round immediately when one team is dead (without even giving enough time to see the enemy die), there's a bried delay, a message box and a camera transition to let the players see what happened. + +Submarines: +- Fixed floating light component in Orca 2. +- Medical fabricator now consumes 500 power on all submarines, to be consistent with other fabricators +- Rebalanced Engine Force values to better match hull size. Most Scouts (Azimuth, Orca2, Remora, Winterhalter) are now faster. Humpback, Typhon and Orca slightly slower. +- Winterhalter and Remora are now Scout class ships, Deep Diver class will be removed. +- Introduced submarine tiers. Submarine tiers and class affect the max level of submarine upgrades that apply / can be bought. +- Updated prices of all submarines to match tiers. +- Gave Typhon 2 better stats and even more firepower, to outclass the original Typhon. +- Improved R-29 speed and gave a Flak cannon +- Added Large weapon hardpoints to Berilia to make it a Tier 3 transport. + +Optimization: +- Optimizations to the talent system, particularly when the talent menu is open and when there's a large number of talents (e.g. when using mods that make all talents available to every class). + +Multiplayer: +- Significantly sped up file transfers (mods, submarine files, campaign saves). +- The minimum kick vote counts are no longer rounded down. Previously if you had for example four players on the server and the minimum vote count set to 60%, kicking would require 2 votes, now it requires 3. + +Submarine editor: +- Fixed turret lightsource rotation not refreshing in the sub editor when flipping the item. +- Fixed turret lightsource rotation not refreshing in the sub editor when flipping the item. + +Fixes: +- Fixed linked subs still sometimes getting placed on the wrong side of the docking port when switching subs. +- Fixed PvP team assignment sometimes being wildly imbalanced, even if there's enough players with no preference to make the team sizes equal. +- Fixes to ruin door connections, wiring and connection panels. +- Fixed "insurance policy" giving the money to the dead character instead of the bank. +- Fixed damage to mirrored wall pieces resetting between rounds. +- Increased the minimum width of cave tunnels to prevent impassable paths. +- Fixed deconstructor input slots becoming unlocked when starting a new round while the deconstructor is running. + +Modding: +- Fixed console errors when trying to check int values with PropertyConditionals. +- Fixed melee weapon's StrikingPowerMultiplier only affecting the afflictions defined in the Attack, not ones defined in the status effect. + --------------------------------------------------------------------------------------------------------- v0.19.1.0 --------------------------------------------------------------------------------------------------------- @@ -15,9 +141,9 @@ Unstable only: - Fixed server trying to place all previously spawned respawn items into containers on every respawn, even if the items have already been removed. Happened because we never cleared respawnItems, and because we used that list when placing new respawn items into containers. Changes and additions: -- Overvoltage makes devices perform better, increasing the output of engines, making fabricators, deconstructors and fabricators operate faster, electrical discharge coils do more damage and oxygen generators generate more oxygen. Incentivizes operating the reactor manually and hopefully makes it a little more engaging. +- Overvoltage makes devices perform better, increasing the output of engines, making fabricators, deconstructors and pumps operate faster, electrical discharge coils do more damage, batteries recharge faster and oxygen generators generate more oxygen. Incentivizes operating the reactor manually and hopefully makes it a little more engaging. - Added more randomness to junction box overvoltage damage, and made partially damaged boxes take more damage from overvoltage. Prevents all boxes from breaking at the same time, making overvoltage less of a pain in the ass to deal with and intentionally overvolting the devices more worthwhile. -- Experimental: Added a way to immediately increase/decrease the temperatureof the reactor for a brief amount of time (atm bumps the gauge up/down by a fifth, and the boost fades out in 20 seconds). Allows reacting to load fluctuations very quickly, and to conserve fuel by operating the reactor at a lower fission rate = serves as another incentive to operate it manually. +- Experimental: Added a way to immediately increase/decrease the temperature of the reactor for a brief amount of time (atm bumps the gauge up/down by a fifth, and the boost fades out in 20 seconds). Allows reacting to load fluctuations very quickly, and to conserve fuel by operating the reactor at a lower fission rate = serves as another incentive to operate it manually. - Signals no longer set the fission and turbine rates of the reactor instantaneously, making automated reactor circuits less overpowered. They are still viable, but especially now with the addition of the extra incentives for operating the reactor manually, they're no longer as clearly the best and most efficient way to operate the reactor, making manual operation more worthwhile. - Added a Large Weapon Hardpoint. - Railgun is now considered a large weapon. @@ -188,6 +314,21 @@ Modding: - Added new properties to StatusEffect's SpawnCharacter feature: Stun, AfflictionOnSpawn, AfflictionStrength, TransferControl, RemovePreviousCharacter, TransferBuffs, TransferAfflictions, TransferInventory. - Fixed bots always choosing their "personality trait" from the first 6 even if more are modded in. +--------------------------------------------------------------------------------------------------------- +v0.18.15.2 (MacOS only) +--------------------------------------------------------------------------------------------------------- + +- Fixed crashes on MacOS 10.13 and 10.14. This seems to have happened because Microsoft quietly dropped support for these versions in .NET Core in late 2021, and we didn't realize until this hotfix when we deployed with a sufficiently new version of the technology. +- Fixed inability to use the voice chat on some MacOS versions, due to the game not having permissions to access the microphone as a result of the aforementioned .NET Core upgrade. + +--------------------------------------------------------------------------------------------------------- +v0.18.15.1 +--------------------------------------------------------------------------------------------------------- + +- Fixed frequent crashing in the submarine editor. The crashes were caused by modifying multiple entities at the same time, such as by selecting an entity of the same type as the last entity that was edited. +- Fixed dragged objects becoming invisible if you bring the cursor over a UI element in the submarine editor. +- Fixed a bug that sometimes caused radio voice chat to be muffled. + --------------------------------------------------------------------------------------------------------- v0.18.15.0 --------------------------------------------------------------------------------------------------------- @@ -259,6 +400,7 @@ Modding: v0.18.11.0 --------------------------------------------------------------------------------------------------------- +Bugfixes: - Disabled project-wide invariant globalization, which was meant to address "couldn't find a valid ICU package installed on the system" errors on some Linux distributions. The fix caused issues with case-insensitive comparisons and converting to upper or lower case in non-latin alphabets. - Fixed tutorial characters spawning without a headset. - Fixed inability to bind keys to LMB by clicking on the input box. diff --git a/Barotrauma/BarotraumaTest/CommonnessInfoTests.cs b/Barotrauma/BarotraumaTest/CommonnessInfoTests.cs new file mode 100644 index 000000000..ee85a5206 --- /dev/null +++ b/Barotrauma/BarotraumaTest/CommonnessInfoTests.cs @@ -0,0 +1,84 @@ +using Barotrauma; +using FluentAssertions; +using FsCheck; +using Xunit; +using static Barotrauma.ItemPrefab; + +namespace TestProject +{ + public class CommonnessInfoTests + { + private class CustomGenerators + { + + public static Arbitrary CommonnessInfoGeneratorOverride() + { + return Arb.From(from float commonness in Arb.Generate().Where(IsValid) + from float? abyssCommonness in Arb.Generate().Where(IsNullableValid) + from float? caveCommonness in Arb.Generate().Where(IsNullableValid) + select new CommonnessInfo(commonness, abyssCommonness, caveCommonness)); + + static bool IsValid(float commonness) => !float.IsNaN(commonness) && commonness > float.MinValue && commonness < float.MaxValue; + static bool IsNullableValid(float? commonness) => !commonness.HasValue || IsValid(commonness.Value); + } + } + + public CommonnessInfoTests() + { + Arb.Register(); + Arb.Register(); + } + + [Fact] + public void TestInheritedCommonness() + { + Prop.ForAll((child, parent) => + { + var info = child.WithInheritedCommonness(parent); + + info.Commonness.Should().Be(child.commonness); + + if (child.abyssCommonness.HasValue) + { + info.abyssCommonness.Should().HaveValue(); + info.abyssCommonness.Should().Be(child.abyssCommonness); + } + else if (parent.abyssCommonness.HasValue) + { + info.abyssCommonness.Should().HaveValue(); + info.abyssCommonness.Should().Be(parent.abyssCommonness); + } + else + { + info.abyssCommonness.Should().NotHaveValue(); + } + + if (child.caveCommonness.HasValue) + { + info.caveCommonness.Should().HaveValue(); + info.caveCommonness.Should().Be(child.caveCommonness); + } + else if (parent.caveCommonness.HasValue) + { + info.caveCommonness.Should().HaveValue(); + info.caveCommonness.Should().Be(parent.caveCommonness); + } + else + { + info.caveCommonness.Should().NotHaveValue(); + } + }).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestPathCommonness() + { + Prop.ForAll(info => + { + info.GetCommonness(Level.TunnelType.MainPath).Should().Be(info.Commonness); + info.GetCommonness(Level.TunnelType.SidePath).Should().Be(info.Commonness); + info.GetCommonness(Level.TunnelType.Cave).Should().Be(info.CaveCommonness); + }).QuickCheckThrowOnFailure(); + } + } +} diff --git a/Barotrauma/BarotraumaTest/EndpointComparisonTests.cs b/Barotrauma/BarotraumaTest/EndpointComparisonTests.cs new file mode 100644 index 000000000..6e588b4f3 --- /dev/null +++ b/Barotrauma/BarotraumaTest/EndpointComparisonTests.cs @@ -0,0 +1,15 @@ +#nullable enable +using System.Net; +using Barotrauma.Networking; +using Xunit; + +namespace TestProject; + +public class EndpointComparisonTests +{ + [Fact] + public void TestLidgrenAddress() + { + Assert.True(new LidgrenAddress(IPAddress.Loopback) == new LidgrenAddress(IPAddress.IPv6Loopback)); + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaTest/EndpointParseTests.cs b/Barotrauma/BarotraumaTest/EndpointParseTests.cs new file mode 100644 index 000000000..c8ccb4e43 --- /dev/null +++ b/Barotrauma/BarotraumaTest/EndpointParseTests.cs @@ -0,0 +1,67 @@ +#nullable enable +using System.Net; +using Barotrauma; +using Xunit; +using Barotrauma.Networking; +using FluentAssertions; + +namespace TestProject; + +public class EndpointParseTests +{ + [Fact] + public void TestLidgrenEndpoint() + { + Endpoint.Parse("127.0.0.1:27015") + .Should() + .BeOfType>() + .And.BeEquivalentTo( + Option.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)), + options => options.RespectingRuntimeTypes()); + } + + [Fact] + public void TestLidgrenEndpointHostName() + { + Endpoint.Parse("localhost:27015") + .Should() + .BeOfType>() + .And.BeEquivalentTo( + Option.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)), + options => options.RespectingRuntimeTypes()); + } + + [Fact] + public void TestLidgrenAddress() + { + Address.Parse("127.0.0.1") + .Should() + .BeOfType>() + .And.BeEquivalentTo( + Option
.Some(new LidgrenAddress(IPAddress.Loopback)), + options => options.RespectingRuntimeTypes()); + } + + [Fact] + public void TestSteamP2PEndpoint() + { + Endpoint.Parse("STEAM_1:1:508792388") + .Should() + .BeOfType>() + .And.BeEquivalentTo( + Option.Some(new SteamP2PEndpoint(new SteamId(76561198977850505))), + options => options.RespectingRuntimeTypes()); + } + + [Fact] + public void TestSteamP2PAddress() + { + Address.Parse("STEAM_1:1:508792388") + .Should() + .BeOfType>() + .And.BeEquivalentTo( + Option
.Some(new SteamP2PAddress(new SteamId(76561198977850505))), + options => options.RespectingRuntimeTypes()); + new SteamId(76561198977850505).StringRepresentation.Should().BeEquivalentTo("STEAM_1:1:508792388"); + } +} diff --git a/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs b/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs new file mode 100644 index 000000000..1e499dac0 --- /dev/null +++ b/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using System.Reflection; +using Barotrauma; +using Xunit; + +namespace TestProject; + +public class INetSerializableStructImplementationChecks +{ + private delegate bool TryFindBehaviorDelegate(Type type, out NetSerializableProperties.IReadWriteBehavior behavior); + + [Fact] + public void CheckStructMemberTypes() + { + var interfaceType = typeof(INetSerializableStruct); + var types = interfaceType.Assembly.GetTypes() + .Where(t => !t.IsAbstract && !t.IsInterface && t.IsAssignableTo(interfaceType)); + + //private static bool TryFindBehavior(Type type, out IReadWriteBehavior behavior) + TryFindBehaviorDelegate tryFindBehavior + = typeof(NetSerializableProperties) + .GetMethod("TryFindBehavior", BindingFlags.NonPublic | BindingFlags.Static, + typeof(TryFindBehaviorDelegate).GetMethod("Invoke")! + .GetParameters().Select(p => p.ParameterType).ToArray())! + .CreateDelegate(); + + foreach (var type in types) + { + var members = NetSerializableProperties.GetPropertiesAndFields(type); + foreach (var member in members) + { + void checkType(Type typeBeingChecked) + { + Assert.True(tryFindBehavior(typeBeingChecked, out _), $"{type}.{member.Name} of type {member.Type} is unsupported in {nameof(INetSerializableStruct)}"); + Type? nestedType = null; + if (typeBeingChecked.IsGenericType) + { + nestedType = typeBeingChecked.GetGenericArguments()[0]; + } + else if (typeBeingChecked.IsArray) + { + nestedType = typeBeingChecked.GetElementType(); + } + + if (nestedType != null) + { + checkType(nestedType); + } + } + checkType(member.Type); + } + } + } +} diff --git a/Barotrauma/BarotraumaTest/INetSerializableStructTests.cs b/Barotrauma/BarotraumaTest/INetSerializableStructTests.cs index 83cedcc37..e319baede 100644 --- a/Barotrauma/BarotraumaTest/INetSerializableStructTests.cs +++ b/Barotrauma/BarotraumaTest/INetSerializableStructTests.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Immutable; using Barotrauma; using Barotrauma.Networking; using FluentAssertions; @@ -11,7 +12,7 @@ using Xunit; namespace TestProject { // ReSharper disable UnusedMember.Local NotAccessedField.Local UnusedMember.Global - public class INetSerializableStructTests + public sealed class INetSerializableStructTests { private class CustomGenerators { @@ -25,6 +26,21 @@ namespace TestProject Arb.Register(); } + [Fact] + public void TestBitField() + { + Prop.ForAll(SerializeDeserializeBitField).VerboseCheckThrowOnFailure(); + } + + [Fact] + public void TestRanged() + { + Prop.ForAll( + Arb.Generate().Where(i => i <= 100 && i >= -100).ToArbitrary(), + Arb.Generate().Where(f => f <= 100f && f >= -100f).ToArbitrary(), + SerializeDeserializeRanged).QuickCheckThrowOnFailure(); + } + [Fact] public void TestOptional() { @@ -58,6 +74,12 @@ namespace TestProject { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } + + [Fact] + public void TestEnumFlags() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } [Fact] public void TestArray() @@ -67,6 +89,14 @@ namespace TestProject Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } + [Fact] + public void TestImmutableArray() + { + Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); + Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); + Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + [Fact] public void TestNullable() { @@ -147,6 +177,26 @@ namespace TestProject Thousand = 1000 } + [Flags] + private enum EnumFlagsTest + { + None = 0, + Bit0 = 1 << 0, + Bit1 = 1 << 1, + Bit2 = 1 << 2, + Bit3 = 1 << 3 + } + + private struct TestRangedStruct : INetSerializableStruct + { + [NetworkSerialize(MinValueInt = -100, MaxValueInt = 100)] + public int IntValue; + + [NetworkSerialize(MinValueFloat = -100, MaxValueFloat = 100, NumberOfBits = 16)] + public float FloatValue; + } + +#pragma warning disable CS0649 private struct TestStruct : INetSerializableStruct { [NetworkSerialize] @@ -168,6 +218,25 @@ namespace TestProject public (T, U) NotSerializedValue; public (T, U) NotSerializedFunction() => throw new NotImplementedException(); } +#pragma warning restore CS0649 + + private static void SerializeDeserializeRanged(int intValue, float floatValue) + { + ReadWriteMessage msg = new ReadWriteMessage(); + TestRangedStruct writeStruct = new TestRangedStruct + { + IntValue = intValue, + FloatValue = floatValue + }; + + msg.WriteNetSerializableStruct(writeStruct); + msg.BitPosition = 0; + + TestRangedStruct readStruct = INetSerializableStruct.Read(msg); + + readStruct.FloatValue.Should().BeApproximately(floatValue, 0.25f); // should be enough precision + readStruct.IntValue.Should().Be(intValue); + } private static void SerializeDeserialize(T arg) where T : notnull { @@ -177,12 +246,14 @@ namespace TestProject Value = arg }; - ((INetSerializableStruct)writeStruct).Write(msg); + msg.WriteNetSerializableStruct(writeStruct); msg.BitPosition = 0; TestStruct readStruct = INetSerializableStruct.Read>(msg); - readStruct.Should().BeEquivalentTo(writeStruct, options => options.ComparingByMembers>()); + readStruct.Should().BeEquivalentTo(writeStruct, options => options + .ComparingByMembers>() + .ComparingByMembers(typeof(Option<>))); } private static void SerializeDeserializeNullableTuple(T arg1, U arg2) @@ -194,12 +265,35 @@ namespace TestProject Two = arg2 }; - ((INetSerializableStruct)writeStruct).Write(msg); + msg.WriteNetSerializableStruct(writeStruct); msg.BitPosition = 0; TupleNullableStruct readStruct = INetSerializableStruct.Read>(msg); - readStruct.Should().BeEquivalentTo(writeStruct, options => options.ComparingByMembers>()); + readStruct.Should().BeEquivalentTo(writeStruct, options => options + .ComparingByMembers>() + .ComparingByMembers(typeof(Option<>))); + } + + private static void SerializeDeserializeBitField(bool[] arg) + { + ReadWriteMessage msg = new ReadWriteMessage(); + IWritableBitField bitFieldWrite = new WriteOnlyBitField(); + + foreach (bool b in arg) + { + bitFieldWrite.WriteBoolean(b); + } + + bitFieldWrite.WriteToMessage(msg); + msg.BitPosition = 0; + + IReadableBitField bitFieldRead = new ReadOnlyBitField(msg); + + foreach (bool b in arg) + { + bitFieldRead.ReadBoolean().Should().Be(b); + } } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaTest/NetIdUtilsTests.cs b/Barotrauma/BarotraumaTest/NetIdUtilsTests.cs new file mode 100644 index 000000000..90bd2dc50 --- /dev/null +++ b/Barotrauma/BarotraumaTest/NetIdUtilsTests.cs @@ -0,0 +1,20 @@ +using System; +using Barotrauma.Networking; +using FluentAssertions; +using FsCheck; +using Xunit; + +namespace TestProject; + +public class NetIdUtilsTests +{ + [Fact] + public void TestGetIdOlderThan() + { + Prop.ForAll(id => + { + var olderId = NetIdUtils.GetIdOlderThan(id); + Assert.True(NetIdUtils.IdMoreRecent(id, olderId)); + }).QuickCheckThrowOnFailure(); + } +} \ No newline at end of file diff --git a/BuildScripts/app_1026340.vdf b/BuildScripts/app_1026340.vdf new file mode 100644 index 000000000..70d3550a6 --- /dev/null +++ b/BuildScripts/app_1026340.vdf @@ -0,0 +1,15 @@ +"appbuild" +{ + "appid" "1026340" + "desc" "" + "buildoutput" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\output" + "contentroot" "" + "setlive" "" + "preview" "0" + "local" "" + "depots" + { + "1026341" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_1026341.vdf" + "1026342" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_1026342.vdf" + } +} diff --git a/BuildScripts/app_602960.vdf b/BuildScripts/app_602960.vdf new file mode 100644 index 000000000..f2fca8b7f --- /dev/null +++ b/BuildScripts/app_602960.vdf @@ -0,0 +1,16 @@ +"appbuild" +{ + "appid" "602960" + "desc" "" + "buildoutput" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\output" + "contentroot" "" + "setlive" "" + "preview" "0" + "local" "" + "depots" + { + "602961" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_602961.vdf" + "602962" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_602962.vdf" + "602963" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_602963.vdf" + } +} \ No newline at end of file diff --git a/Deploy/DeployAll.bat b/Deploy/DeployAll.bat new file mode 100644 index 000000000..b7f7b1ae7 --- /dev/null +++ b/Deploy/DeployAll.bat @@ -0,0 +1,4 @@ +@ECHO OFF + +cd DeployAll +dotnet run -v q diff --git a/Deploy/DeployAll.sh b/Deploy/DeployAll.sh new file mode 100644 index 000000000..511cf2276 --- /dev/null +++ b/Deploy/DeployAll.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd DeployAll +dotnet run -v q diff --git a/Deploy/DeployAll/DeployAll.csproj b/Deploy/DeployAll/DeployAll.csproj new file mode 100644 index 000000000..72669ffd0 --- /dev/null +++ b/Deploy/DeployAll/DeployAll.csproj @@ -0,0 +1,22 @@ + + + + Exe + net6.0 + disable + enable + + + + true + + + + true + + + + + + + diff --git a/Deploy/DeployAll/Deployables.cs b/Deploy/DeployAll/Deployables.cs new file mode 100644 index 000000000..a83936516 --- /dev/null +++ b/Deploy/DeployAll/Deployables.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using System.Text; +using System.Xml.Linq; + +namespace DeployAll; + +public static class Deployables +{ + public const string ResultPath = "Deploy/bin/content"; + + private const string clientProjFmt = "Barotrauma/BarotraumaClient/{0}Client.csproj"; + private const string serverProjFmt = "Barotrauma/BarotraumaServer/{0}Server.csproj"; + + private static readonly ImmutableArray<(string Project, string Runtime)> platforms = new[] + { + ("Windows", "win-x64"), + ("Mac", "osx-x64"), + ("Linux", "linux-x64") + }.ToImmutableArray(); + + public static void Generate(string configuration, Version version, string gitBranch, string gitRevision) + { + Util.RecreateDirectory(ResultPath); + + File.WriteAllText( + Path.Combine(ResultPath, "readme.txt"), + $"This is Barotrauma {configuration} v{version} ({gitBranch}, {gitRevision}) built on {DateTime.Now}"); + + foreach (var (project, runtime) in platforms) + { + string serverPath = Path.Combine(ResultPath, project, "Server"); + + void checkVersion(string projPath) + { + Version projVersion = Version.Parse( + XDocument.Load(projPath).Root? + .Element("PropertyGroup")? + .Element("Version")? + .Value ?? throw new Exception($"Version not found in {projPath}")); + if (projVersion != version) + { + throw new Exception($"Version mismatch in {projPath}: {projVersion} != {version}"); + } + } + + string serverProj = string.Format(serverProjFmt, project); + string clientProj = string.Format(clientProjFmt, project); + + checkVersion(serverProj); + checkVersion(clientProj); + + Console.WriteLine( + $"*** Building Barotrauma {configuration}{project} v{version} ({gitBranch}, {gitRevision}) to \"{Path.Combine(ResultPath, project)}\" ***"); + + DotnetCmd.Publish( + projPath: serverProj, + configuration: configuration, + runtime: runtime, + resultPath: serverPath); + Util.DeleteFiles(serverPath, + "*.png", "*.ogg", "*.webm", + "*.mp4", "*.otf", "*.ttf"); + + string clientPath = Path.Combine(ResultPath, project, "Client"); + string clientBundlePath = clientPath; + + if (project == "Mac") + { + clientPath = Path.Combine(clientPath, "Barotrauma.app", "Contents", "MacOS"); + Util.CopyDirectory("Deploy/DeployAll/macSkeleton", clientBundlePath); + + string infoPlistPath = Path.Combine(clientBundlePath, "Barotrauma.app", "Contents", "info.plist"); + string infoPlist = File.ReadAllText(infoPlistPath, Encoding.UTF8) + .Replace("{short_version_string}", $"{version.Major}.{version.Minor}.{version.Build}") + .Replace("{version}", version.ToString()) + .Replace("{current_year}", DateTime.Now.Year.ToString()); + File.WriteAllText(infoPlistPath, infoPlist, Encoding.UTF8); + } + + DotnetCmd.Publish( + projPath: serverProj, + configuration: configuration, + runtime: runtime, + resultPath: clientPath); + DotnetCmd.Publish( + projPath: clientProj, + configuration: configuration, + runtime: runtime, + resultPath: clientPath); + + if (!File.Exists(Path.Combine(clientPath, "GameAnalytics.NetStandard.dll"))) + { + throw new Exception($"GameAnalytics was not found in \"{clientPath}\""); + } + + if (project == "Mac") + { + Util.CopyDirectory(Path.Combine(clientPath, "Content", "Effects"), + Path.Combine( + clientBundlePath, "Barotrauma.app", "Contents", "Resources", "Content", "Effects")); + Util.CopyDirectory(Path.Combine(clientPath, "Content", "Lights"), + Path.Combine( + clientBundlePath, "Barotrauma.app", "Contents", "Resources", "Content", "Lights")); + } + + Console.WriteLine(""); + } + } +} \ No newline at end of file diff --git a/Deploy/DeployAll/DotnetCmd.cs b/Deploy/DeployAll/DotnetCmd.cs new file mode 100644 index 000000000..c6435e313 --- /dev/null +++ b/Deploy/DeployAll/DotnetCmd.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using AsmResolver.PE; +using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Win32Resources.Builder; + +namespace DeployAll; + +public static class DotnetCmd +{ + private const string DotnetAppName = "dotnet"; + + private const string desiredRuntimeVersion = "6.0.8"; + + public static void Publish(string projPath, string configuration, string runtime, string resultPath) + { + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = DotnetAppName, + ArgumentList = + { + "publish", + projPath, + "-c", + configuration, + "-clp:ErrorsOnly;Summary", + "--self-contained", + "-r", + runtime, + "/p:Platform=x64", + "/p:ErrorOnDuplicatePublishOutputFiles=false", //TODO: fix our duplicate files + "/p:RollForward=Disable", + $"/p:RuntimeFrameworkVersion={desiredRuntimeVersion}", + "-o", + resultPath + }, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + var process = Util.StartProcess(psi); + process.WaitForExit(); + string stdout = process.StandardOutput.ReadToEnd(); + string stderr = process.StandardError.ReadToEnd(); + + string errorLine = $"{stdout}\n{stderr}".Split('\n') + .First(ln => ln.Contains("Error(s)", StringComparison.OrdinalIgnoreCase)) + .Trim(); + + if (!errorLine.StartsWith("0 ", StringComparison.OrdinalIgnoreCase)) + { + throw new Exception($"Failed to build {projPath}, {errorLine}"); + } + + Console.WriteLine($" - Published \"{projPath}\" to \"{resultPath}\", {errorLine}"); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !runtime.StartsWith("win")) { return; } + + // You may be wondering, what is this crap? + // Cross-compiling is something that should work perfectly, because it's super convenient. + // However, thanks to the way .NET works, cross-compiling to Windows from *nix platforms + // results in an executable with basically no metadata, and the wrong subsystem! + // (see https://github.com/dotnet/sdk/blob/375955d3a9de213a01d70eb6180298000dee30ee/src/Tasks/Microsoft.NET.Build.Tasks/GenerateShims.cs#L127-L132) + // Does it look like we're about to modify the SDK itself to solve this problem? Yeah right. + // Instead let's just take the shim generated by the SDK and fix it ourselves. + + XElement firstPropertyGroup = XDocument.Load(projPath) + .Root? + .Element("PropertyGroup") + ?? throw new Exception("PropertyGroup not found"); + + string assemblyName = firstPropertyGroup.Element("AssemblyName")?.Value + ?? throw new Exception("AssemblyName not found"); + + // This is the shim that doesn't have the stuff we want. + var fileToChange = PEFile.FromFile(Path.Combine(resultPath, $"{assemblyName}.exe")); + // Luckily, the SDK does embed all of that data in the assembly with all of the IL! + // We can just yoink it from here. + var managedAssembly = PEImage.FromFile(Path.Combine(resultPath, $"{assemblyName}.dll")); + + // Here's a whole lot of magic to set up the resources section of the executable + var resourceSection = new PESection(".rsrc", SectionFlags.ContentInitializedData | SectionFlags.MemoryRead); + var resourceDirectoryBuffer = new ResourceDirectoryBuffer(); + resourceDirectoryBuffer.AddDirectory(managedAssembly.Resources ?? throw new Exception($"{assemblyName}.dll has no resources")); + resourceSection.Contents = resourceDirectoryBuffer; + fileToChange.Sections.Add(resourceSection); + fileToChange.AlignSections(); + var dataDirectories = fileToChange.OptionalHeader.DataDirectories; + dataDirectories[2] = new DataDirectory(resourceDirectoryBuffer.Rva, resourceDirectoryBuffer.GetPhysicalSize()); + + // And here's something a little less magical that fixes the subsystem + fileToChange.OptionalHeader.SubSystem = firstPropertyGroup.Element("OutputType")?.Value == "WinExe" + ? SubSystem.WindowsGui + : SubSystem.WindowsCui; + + using var writeStream = File.Open(Path.Combine(resultPath, $"{assemblyName}.exe"), FileMode.Create); + fileToChange.Write(writeStream); + } + + public static Version GetSdkVersion() + { + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = DotnetAppName, + ArgumentList = + { + "--version" + }, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + var process = Util.StartProcess(psi); + process.WaitForExit(); + string stdout = process.StandardOutput.ReadToEnd(); + + return Version.Parse(stdout.Trim()); + } +} diff --git a/Deploy/DeployAll/GitCmd.cs b/Deploy/DeployAll/GitCmd.cs new file mode 100644 index 000000000..624c418e3 --- /dev/null +++ b/Deploy/DeployAll/GitCmd.cs @@ -0,0 +1,86 @@ +using System; +using System.Diagnostics; + +namespace DeployAll; + +public static class GitCmd +{ + private const string gitCmdName = "git"; + + private static ProcessStartInfo MakePsi(params string[] args) + { + var psi = new ProcessStartInfo + { + FileName = gitCmdName, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + foreach (var arg in args) + { + psi.ArgumentList.Add(arg); + } + + return psi; + } + + private static void ExecCmd(out string stdOut, out string stdErr, params string[] args) + { + var process = Util.StartProcess(MakePsi(args)); + process.WaitForExit(); + stdOut = process.StandardOutput.ReadToEnd(); + stdErr = process.StandardError.ReadToEnd(); + } + + public static string GetRevision() + { + ExecCmd(out string stdOut, out _, + "rev-parse", + "--short", + "HEAD"); + + return stdOut.Trim(); + } + + public static string GetBranch() + { + ExecCmd(out string stdOut, out _, + "branch", + "--show-current"); + + return stdOut.Trim(); + } + + public static bool HasUncommittedChanges() + { + ExecCmd(out string stdOut, out _, + "status", + "--porcelain=1"); + + return !string.IsNullOrWhiteSpace(stdOut); + } + + public static bool IsRepoOutOfSync() + { + ExecCmd(out _, out _, + "fetch"); + + ExecCmd(out string remoteBranch, out _, + "status", + "-sb"); + + if (!remoteBranch.StartsWith("##")) { return true; } + if (!remoteBranch.Contains("...")) { return true; } + + remoteBranch = remoteBranch[(remoteBranch.IndexOf("...", StringComparison.InvariantCulture) + 3)..]; + remoteBranch = remoteBranch[..remoteBranch.IndexOf("\n", StringComparison.InvariantCulture)]; + + string localRevision = GetRevision(); + ExecCmd(out string remoteRevision, out _, + "rev-parse", + "--short", + remoteBranch); + remoteRevision = remoteRevision.Trim(); + + return localRevision != remoteRevision; + } +} \ No newline at end of file diff --git a/Deploy/DeployAll/Program.cs b/Deploy/DeployAll/Program.cs new file mode 100644 index 000000000..3de060435 --- /dev/null +++ b/Deploy/DeployAll/Program.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using DeployAll; + +while (!Directory.GetFiles(".").Any(f => f.EndsWith(".sln"))) +{ + Directory.SetCurrentDirectory(".."); +} + +const string windowsClientProj = "Barotrauma/BarotraumaClient/WindowsClient.csproj"; + +Version gameVersion = Version.Parse( + XDocument.Load(windowsClientProj).Root? + .Element("PropertyGroup")? + .Element("Version")? + .Value ?? throw new Exception($"Version not found in {windowsClientProj}")); + +string gitRevision = GitCmd.GetRevision(); +string gitBranch = GitCmd.GetBranch(); + +Console.WriteLine($"DEPLOYALL - Barotrauma v{gameVersion}, branch {gitBranch}, revision {gitRevision}"); + +if (GitCmd.HasUncommittedChanges()) +{ + if (Util.AskQuestion("The repo currently has some uncommitted changes. Do you still wish to proceed? [y/n]") + .AnsweredNo()) { return; } +} +else if (GitCmd.IsRepoOutOfSync()) +{ + if (Util.AskQuestion("The repo is currently out of sync. Do you still wish to proceed? [y/n]") + .AnsweredNo()) { return; } +} + +var sdkVersion = DotnetCmd.GetSdkVersion(); +Console.WriteLine($"Using .NET SDK {sdkVersion}"); + +string configuration = Util.AskQuestion("Type 1 for Release, 2 for Unstable, enter nothing to cancel") switch +{ + "1" => "Release", + "2" => "Unstable", + _ => "" +}; +if (string.IsNullOrWhiteSpace(configuration)) { return; } + +Deployables.Generate(configuration, gameVersion, gitBranch, gitRevision); + +if (Util.AskQuestion("Would you like to upload the generated builds to Steam? [y/n]") + .AnsweredNo()) { return; } + +SteamPipeAssistant.PrepareSteamCmd(); +SteamPipeAssistant.PrepareScripts(configuration, gameVersion, gitBranch, gitRevision); + +string userName = Util.AskQuestion("Type your Steam username to upload to Steamworks, enter nothing to skip uploading"); +if (string.IsNullOrWhiteSpace(userName)) { return; } + +SteamPipeAssistant.Upload(userName, configuration); diff --git a/Deploy/DeployAll/SteamPipeAssistant.cs b/Deploy/DeployAll/SteamPipeAssistant.cs new file mode 100644 index 000000000..e111af644 --- /dev/null +++ b/Deploy/DeployAll/SteamPipeAssistant.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Runtime.InteropServices; + +namespace DeployAll; + +public static class SteamPipeAssistant +{ + private abstract record ScriptItem(string Name) + { + public abstract override string ToString(); + } + + private record SingleItem(string Name, string Value) : ScriptItem(Name) + { + public override string ToString() => $"\"{Name}\" \"{Value}\""; + } + + private record AggregateItem(string Name, params ScriptItem[] SubItems) : ScriptItem(Name) + { + public override string ToString() + { + return $"\"{Name}\"\n" + + "{\n" + + string.Join("\n", + SubItems.Select(it => it.ToString()) + .SelectMany(s => s.Split("\n")) + .Select(s => $"\t{s}")) + + "\n}"; + } + } + + private static string steamCmdUrl + => true switch + { + _ when RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + => "https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", + _ when RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + => "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz", + _ when RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + => "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_osx.tar.gz", + _ => throw new Exception($"Unsupported host platform: {RuntimeInformation.OSDescription}") + }; + + private static string[] steamCmdFilenames + => true switch + { + _ when RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + => new[] { "steamcmd.exe" }, + _ when RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + => new[] { "steamcmd.sh", "linux32/steamcmd", "linux32/steamerrorreporter" }, + _ when RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + => new[] { "steamcmd.sh", "steamcmd" }, + _ => throw new Exception($"Unsupported host platform: {RuntimeInformation.OSDescription}") + }; + + private const string SteamCmdPath = "Deploy/bin/steamcmd"; + + public static void PrepareSteamCmd() + { + if (Directory.Exists(SteamCmdPath)) + { + Console.WriteLine($"SteamCMD found at {SteamCmdPath}, skipping download"); + return; + } + Console.WriteLine($"Downloading SteamCMD to {SteamCmdPath}"); + + Util.RecreateDirectory(SteamCmdPath); + + var steamCmdPkg = Util.DownloadFile(steamCmdUrl).ToArray(); + + if (Path.GetExtension(steamCmdUrl) == ".zip") + { + using var memStream = new MemoryStream(steamCmdPkg); + using ZipArchive archive = new ZipArchive(memStream, ZipArchiveMode.Read); + archive.ExtractToDirectory(SteamCmdPath); + } + else + { + string downloadResultPath = Path.Combine(SteamCmdPath, Path.GetFileName(steamCmdUrl)); + File.WriteAllBytes(downloadResultPath, steamCmdPkg); + + var psi = new ProcessStartInfo + { + FileName = "tar", + ArgumentList = + { + "-xf", + downloadResultPath, + "-C", + SteamCmdPath + }, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + var process = Util.StartProcess(psi); + process.WaitForExit(); + + File.Delete(downloadResultPath); + + foreach (var filename in steamCmdFilenames) + { + psi = new ProcessStartInfo + { + FileName = "chmod", + ArgumentList = + { + "+x", + Path.Combine(SteamCmdPath, filename) + }, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + process = Util.StartProcess(psi); + process.WaitForExit(); + } + } + + Console.WriteLine("SteamCMD downloaded and extracted"); + } + + private const string ScriptPath = "Deploy/bin/scripts"; + private const string BuildOutput = "Deploy/bin/output"; + + private const string appIdScriptFileFmt = "app_{0}.vdf"; + + private const ulong ClientAppId = 602960; + private const ulong ClientWindowsDepotId = 602961; + private const ulong ClientLinuxDepotId = 602962; + private const ulong ClientMacDepotId = 602963; + + private const ulong ServerAppId = 1026340; + private const ulong ServerWindowsDepotId = 1026341; + private const ulong ServerLinuxDepotId = 1026342; + + private static ScriptItem PrepareDepotScript(ulong depotId, string contentPath) + { + var childItems = new List + { + new SingleItem("DepotID", depotId.ToString()), + new SingleItem("contentroot", contentPath), + new AggregateItem("FileMapping", + new SingleItem("LocalPath", "*"), + new SingleItem("DepotPath", "."), + new SingleItem("recursive", "1")), + new SingleItem("FileExclusion", "config_player.xml"), + new SingleItem("FileExclusion", "Thumbs.db"), + new SingleItem("FileExclusion", ".DS_Store"), + new SingleItem("FileExclusion", "__MACOSX"), + }; + + if (depotId == ClientMacDepotId) + { + childItems.Add(new SingleItem("InstallScript", "Barotrauma.app/installscript.vdf")); + } + + var script = new AggregateItem("DepotBuildConfig", childItems.ToArray()); + var scriptFileName = Path.Combine(ScriptPath, $"depot_{depotId}.vdf"); + File.WriteAllText(scriptFileName, script.ToString()); + return new SingleItem(depotId.ToString(), Path.GetFullPath(scriptFileName)); + } + + private static void PrepareAppScript(ulong appId, string configuration, Version version, string gitBranch, string gitRevision) + { + var depotScripts = new AggregateItem("depots", appId switch + { + ClientAppId => new[] + { + PrepareDepotScript(ClientWindowsDepotId, + Path.Combine("Windows", "Client")), + PrepareDepotScript(ClientMacDepotId, + Path.Combine("Mac", "Client")), + PrepareDepotScript(ClientLinuxDepotId, + Path.Combine("Linux", "Client")) + }, + ServerAppId => new[] + { + PrepareDepotScript(ServerWindowsDepotId, + Path.Combine("Windows", "Server")), + PrepareDepotScript(ServerLinuxDepotId, + Path.Combine("Linux", "Server")) + }, + _ => throw new InvalidOperationException() + }); + + var script = new AggregateItem("appbuild", + new SingleItem("appid", appId.ToString()), + new SingleItem("desc", $"{configuration} v{version} ({gitBranch}, {gitRevision})"), + new SingleItem("buildoutput", Path.GetFullPath(BuildOutput)), + new SingleItem("contentroot", Path.GetFullPath(Deployables.ResultPath)), + new SingleItem("setlive", appId switch + { + ClientAppId => "experimental", + ServerAppId => "development", + _ => throw new InvalidOperationException() + }), + new SingleItem("preview", "0"), + depotScripts); + + var scriptFileName = Path.Combine(ScriptPath, string.Format(appIdScriptFileFmt, appId)); + File.WriteAllText(scriptFileName, script.ToString()); + } + + public static void PrepareScripts(string configuration, Version version, string gitBranch, string gitRevision) + { + Console.WriteLine($"Preparing SteamPipe scripts for {configuration} v{version} ({gitBranch}, {gitRevision})"); + + Util.RecreateDirectory(ScriptPath); + + PrepareAppScript(ClientAppId, configuration, version, gitBranch, gitRevision); + PrepareAppScript(ServerAppId, configuration, version, gitBranch, gitRevision); + + Console.WriteLine(""); + } + + public static void Upload(string userName, string configuration) + { + Util.RecreateDirectory(BuildOutput); + + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = Path.Combine(SteamCmdPath, steamCmdFilenames.First()), + ArgumentList = + { + "+login", + userName + }, + RedirectStandardOutput = false, + RedirectStandardError = false + }; + + void addScriptCmd(ulong appId) + { + psi.ArgumentList.Add("+run_app_build"); + psi.ArgumentList.Add(Path.GetFullPath(Path.Combine(ScriptPath, string.Format(appIdScriptFileFmt, appId)))); + } + addScriptCmd(ClientAppId); + if (configuration == "Release") { addScriptCmd(ServerAppId); } + + psi.ArgumentList.Add("+quit"); + var process = Util.StartProcess(psi); + process.WaitForExit(); + } +} \ No newline at end of file diff --git a/Deploy/DeployAll/Util.cs b/Deploy/DeployAll/Util.cs new file mode 100644 index 000000000..6962ac512 --- /dev/null +++ b/Deploy/DeployAll/Util.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; + +namespace DeployAll; + +public static class Util +{ + public static void DeleteFiles(string path, params string[] patterns) + { + foreach (var file in patterns.SelectMany(p => Directory.GetFiles(path, p, SearchOption.AllDirectories))) + { + File.Delete(file); + string dir = file; + do + { + dir = Path.GetDirectoryName(dir) ?? ""; + if (Directory.GetFiles(dir, "*", SearchOption.AllDirectories).Length == 0) + { + Directory.Delete(dir, recursive: false); + } + else + { + break; + } + } while (dir.LastIndexOf('/') > 0); + } + } + + public static void CopyDirectory(string sourceDir, string destinationDir) + { + var dir = new DirectoryInfo(sourceDir); + + DirectoryInfo[] dirs = dir.GetDirectories(); + + Directory.CreateDirectory(destinationDir); + + foreach (FileInfo file in dir.GetFiles()) + { + string targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath); + } + + foreach (DirectoryInfo subDir in dirs) + { + string newDestinationDir = Path.Combine(destinationDir, subDir.Name); + CopyDirectory(subDir.FullName, newDestinationDir); + } + } + + public static void DeleteDirectory(string path) + { + if (Directory.Exists(path)) + { + Directory.Delete(path, recursive: true); + } + } + + public static void RecreateDirectory(string path) + { + DeleteDirectory(path); + Directory.CreateDirectory(path); + } + + public static IReadOnlyList DownloadFile(string url) + { + var httpClient = new HttpClient(); + var response = httpClient.Send(new HttpRequestMessage( + HttpMethod.Get, + new Uri(url))); + using var stream = response.Content.ReadAsStream(); + + using var reader = new BinaryReader(stream); + var contents = new List(); + while (true) + { + byte[] bytesRead = reader.ReadBytes(1024); + if (bytesRead.Length == 0) { break; } + contents.AddRange(bytesRead); + } + + return contents; + } + + public static string AskQuestion(string question) + { + Console.WriteLine(question); + Console.Write("> "); + string answer = Console.ReadLine() ?? ""; + Console.WriteLine(""); + return answer; + } + + public static bool AnsweredYes(this string answer) + => answer.Equals("y", StringComparison.InvariantCulture); + + public static bool AnsweredNo(this string answer) + => !answer.AnsweredYes(); + + public static Process StartProcess(ProcessStartInfo info) + => Process.Start(info) + ?? throw new Exception($"Failed to start process \"{info.FileName}\""); +} \ No newline at end of file diff --git a/Deploy/DeployAll/macSkeleton/Barotrauma.app/Contents/Resources/barotrauma.icns b/Deploy/DeployAll/macSkeleton/Barotrauma.app/Contents/Resources/barotrauma.icns new file mode 100644 index 000000000..688e9ecef Binary files /dev/null and b/Deploy/DeployAll/macSkeleton/Barotrauma.app/Contents/Resources/barotrauma.icns differ diff --git a/Deploy/DeployAll/macSkeleton/Barotrauma.app/Contents/info.plist b/Deploy/DeployAll/macSkeleton/Barotrauma.app/Contents/info.plist new file mode 100644 index 000000000..d4a6b7a47 --- /dev/null +++ b/Deploy/DeployAll/macSkeleton/Barotrauma.app/Contents/info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + Barotrauma + CFBundleIconFile + barotrauma + CFBundleIdentifier + com.FakeFish.Barotrauma + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Barotrauma + CFBundlePackageType + APPL + CFBundleShortVersionString + {short_version_string} + CFBundleSignature + FONV + CFBundleVersion + {version} + LSApplicationCategoryType + public.app-category.games + LSMinimumSystemVersion + 10.13 + NSMicrophoneUsageDescription + Needs microphone access for in-game communications + NSHumanReadableCopyright + Copyright © 2017-{current_year} FakeFish. All rights reserved. + NSPrincipalClass + NSApplication + + diff --git a/Deploy/DeployAll/macSkeleton/Barotrauma.app/installscript.vdf b/Deploy/DeployAll/macSkeleton/Barotrauma.app/installscript.vdf new file mode 100644 index 000000000..e9e1eccb6 --- /dev/null +++ b/Deploy/DeployAll/macSkeleton/Barotrauma.app/installscript.vdf @@ -0,0 +1,31 @@ +"InstallScript" +{ + "version" "2" + "chmod" + { + "0" + { + "file" "Barotrauma.app/Contents/MacOS/Barotrauma" + "mode" "755" + } + "1" + { + "file" "Barotrauma.app/Contents/MacOS/Barotrauma.bin.osx" + "mode" "755" + } + "2" + { + "file" "Barotrauma.app/Contents/MacOS/DedicatedServer" + "mode" "755" + } + "3" + { + "file" "Barotrauma.app/Contents/MacOS/DedicatedServer.bin.osx" + "mode" "755" + } + } +} +"kvsignatures" +{ + "InstallScript" "2d0e72227a48d72bfda9810a7f5478af6670d8653f25a92974ceaa4d2e1009e0f5e8a5312a092a64790635d574b0adb84a265ee89df71470d7e8e15c915420da429eb4a15d4ee68840d19c8928a970ab25b8bbfb13f22ce3a061bbb604a94f92299d6e94d7543f3f7bd51170a4c31b3f9808f2f98e85ffd4bd074e88da44491e" +} diff --git a/Deploy/Linux/DeployLinux.sh b/Deploy/Linux/DeployLinux.sh deleted file mode 100755 index c9f7e721b..000000000 --- a/Deploy/Linux/DeployLinux.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish LinuxClient.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r linux-x64 \/p:Platform="x64" - -cd .. -cd BarotraumaServer -dotnet publish LinuxServer.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r linux-x64 \/p:Platform="x64" diff --git a/Deploy/Linux/DeployLinuxServer.sh b/Deploy/Linux/DeployLinuxServer.sh deleted file mode 100644 index 4d05a7814..000000000 --- a/Deploy/Linux/DeployLinuxServer.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -cd ../../Barotrauma/BarotraumaServer -dotnet publish LinuxServer.csproj -c Release --self-contained -r linux-x64 \/p:Platform="x64" diff --git a/Deploy/Linux/DeployLinuxUnstable.sh b/Deploy/Linux/DeployLinuxUnstable.sh deleted file mode 100755 index 015ae2cc7..000000000 --- a/Deploy/Linux/DeployLinuxUnstable.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish LinuxClient.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r linux-x64 \/p:Platform="x64" - -cd .. -cd BarotraumaServer -dotnet publish LinuxServer.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r linux-x64 \/p:Platform="x64" diff --git a/Deploy/Linux/DeployMac.sh b/Deploy/Linux/DeployMac.sh deleted file mode 100644 index 0bde38073..000000000 --- a/Deploy/Linux/DeployMac.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish MacClient.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r osx-x64 \/p:Platform="x64" - -cd .. -cd BarotraumaServer -dotnet publish MacServer.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r osx-x64 \/p:Platform="x64" diff --git a/Deploy/Linux/DeployMacUnstable.sh b/Deploy/Linux/DeployMacUnstable.sh deleted file mode 100644 index ecccfe3fc..000000000 --- a/Deploy/Linux/DeployMacUnstable.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish MacClient.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r osx-x64 \/p:Platform="x64" - -cd .. -cd BarotraumaServer -dotnet publish MacServer.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r osx-x64 \/p:Platform="x64" diff --git a/Deploy/Linux/DeployWindows.sh b/Deploy/Linux/DeployWindows.sh deleted file mode 100644 index 527841ed4..000000000 --- a/Deploy/Linux/DeployWindows.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish WindowsClient.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r win-x64 \/p:Platform="x64" - -cd .. -cd BarotraumaServer -dotnet publish WindowsServer.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r win-x64 \/p:Platform="x64" diff --git a/Deploy/Linux/DeployWindowsUnstable.sh b/Deploy/Linux/DeployWindowsUnstable.sh deleted file mode 100644 index ccabb7aee..000000000 --- a/Deploy/Linux/DeployWindowsUnstable.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish WindowsClient.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r win-x64 \/p:Platform="x64" - -cd .. -cd BarotraumaServer -dotnet publish WindowsServer.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r win-x64 \/p:Platform="x64" diff --git a/Deploy/ManualScripts/Linux/DeployLinux.sh b/Deploy/ManualScripts/Linux/DeployLinux.sh new file mode 100644 index 000000000..9ea5ba91f --- /dev/null +++ b/Deploy/ManualScripts/Linux/DeployLinux.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish LinuxClient.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r linux-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish LinuxServer.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r linux-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 diff --git a/Deploy/ManualScripts/Linux/DeployLinuxServer.sh b/Deploy/ManualScripts/Linux/DeployLinuxServer.sh new file mode 100644 index 000000000..5e49f9654 --- /dev/null +++ b/Deploy/ManualScripts/Linux/DeployLinuxServer.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd ../../../Barotrauma/BarotraumaServer +dotnet publish LinuxServer.csproj -c Release --self-contained -r linux-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 diff --git a/Deploy/ManualScripts/Linux/DeployLinuxUnstable.sh b/Deploy/ManualScripts/Linux/DeployLinuxUnstable.sh new file mode 100644 index 000000000..b2c270ee2 --- /dev/null +++ b/Deploy/ManualScripts/Linux/DeployLinuxUnstable.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish LinuxClient.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r linux-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish LinuxServer.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r linux-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 diff --git a/Deploy/ManualScripts/Linux/DeployMac.sh b/Deploy/ManualScripts/Linux/DeployMac.sh new file mode 100644 index 000000000..66592c074 --- /dev/null +++ b/Deploy/ManualScripts/Linux/DeployMac.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish MacClient.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r osx-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish MacServer.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r osx-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 diff --git a/Deploy/ManualScripts/Linux/DeployMacUnstable.sh b/Deploy/ManualScripts/Linux/DeployMacUnstable.sh new file mode 100644 index 000000000..1bea5df67 --- /dev/null +++ b/Deploy/ManualScripts/Linux/DeployMacUnstable.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish MacClient.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r osx-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish MacServer.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r osx-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 diff --git a/Deploy/ManualScripts/Linux/DeployWindows.sh b/Deploy/ManualScripts/Linux/DeployWindows.sh new file mode 100644 index 000000000..4cb05dc21 --- /dev/null +++ b/Deploy/ManualScripts/Linux/DeployWindows.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish WindowsClient.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r win-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish WindowsServer.csproj -c Release -clp:"ErrorsOnly;Summary" --self-contained -r win-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 diff --git a/Deploy/ManualScripts/Linux/DeployWindowsUnstable.sh b/Deploy/ManualScripts/Linux/DeployWindowsUnstable.sh new file mode 100644 index 000000000..f18b284f3 --- /dev/null +++ b/Deploy/ManualScripts/Linux/DeployWindowsUnstable.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish WindowsClient.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r win-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish WindowsServer.csproj -c Unstable -clp:"ErrorsOnly;Summary" --self-contained -r win-x64 \/p:Platform="x64" \/p:RollForward=Disable \/p:RuntimeFrameworkVersion=3.1.16 diff --git a/Deploy/ManualScripts/SteamPipeBuildScripts/app_1026340.vdf b/Deploy/ManualScripts/SteamPipeBuildScripts/app_1026340.vdf new file mode 100644 index 000000000..70d3550a6 --- /dev/null +++ b/Deploy/ManualScripts/SteamPipeBuildScripts/app_1026340.vdf @@ -0,0 +1,15 @@ +"appbuild" +{ + "appid" "1026340" + "desc" "" + "buildoutput" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\output" + "contentroot" "" + "setlive" "" + "preview" "0" + "local" "" + "depots" + { + "1026341" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_1026341.vdf" + "1026342" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_1026342.vdf" + } +} diff --git a/Deploy/ManualScripts/SteamPipeBuildScripts/app_602960.vdf b/Deploy/ManualScripts/SteamPipeBuildScripts/app_602960.vdf new file mode 100644 index 000000000..f2fca8b7f --- /dev/null +++ b/Deploy/ManualScripts/SteamPipeBuildScripts/app_602960.vdf @@ -0,0 +1,16 @@ +"appbuild" +{ + "appid" "602960" + "desc" "" + "buildoutput" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\output" + "contentroot" "" + "setlive" "" + "preview" "0" + "local" "" + "depots" + { + "602961" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_602961.vdf" + "602962" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_602962.vdf" + "602963" "[steamworks_sdk_install_folder]\sdk\tools\ContentBuilder\scripts\depot_602963.vdf" + } +} \ No newline at end of file diff --git a/BuildScripts/depot_1026341.vdf b/Deploy/ManualScripts/SteamPipeBuildScripts/depot_1026341.vdf similarity index 100% rename from BuildScripts/depot_1026341.vdf rename to Deploy/ManualScripts/SteamPipeBuildScripts/depot_1026341.vdf diff --git a/BuildScripts/depot_1026342.vdf b/Deploy/ManualScripts/SteamPipeBuildScripts/depot_1026342.vdf similarity index 100% rename from BuildScripts/depot_1026342.vdf rename to Deploy/ManualScripts/SteamPipeBuildScripts/depot_1026342.vdf diff --git a/BuildScripts/depot_602961.vdf b/Deploy/ManualScripts/SteamPipeBuildScripts/depot_602961.vdf similarity index 100% rename from BuildScripts/depot_602961.vdf rename to Deploy/ManualScripts/SteamPipeBuildScripts/depot_602961.vdf diff --git a/BuildScripts/depot_602962.vdf b/Deploy/ManualScripts/SteamPipeBuildScripts/depot_602962.vdf similarity index 100% rename from BuildScripts/depot_602962.vdf rename to Deploy/ManualScripts/SteamPipeBuildScripts/depot_602962.vdf diff --git a/BuildScripts/depot_602963.vdf b/Deploy/ManualScripts/SteamPipeBuildScripts/depot_602963.vdf similarity index 100% rename from BuildScripts/depot_602963.vdf rename to Deploy/ManualScripts/SteamPipeBuildScripts/depot_602963.vdf diff --git a/Deploy/ManualScripts/Windows/DeployLinux.bat b/Deploy/ManualScripts/Windows/DeployLinux.bat new file mode 100644 index 000000000..ee2f601e4 --- /dev/null +++ b/Deploy/ManualScripts/Windows/DeployLinux.bat @@ -0,0 +1,12 @@ +@ECHO OFF + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish LinuxClient.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r linux-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish LinuxServer.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r linux-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +PAUSE diff --git a/Deploy/ManualScripts/Windows/DeployLinuxUnstable.bat b/Deploy/ManualScripts/Windows/DeployLinuxUnstable.bat new file mode 100644 index 000000000..39d2e158f --- /dev/null +++ b/Deploy/ManualScripts/Windows/DeployLinuxUnstable.bat @@ -0,0 +1,12 @@ +@ECHO OFF + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish LinuxClient.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r linux-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish LinuxServer.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r linux-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +PAUSE diff --git a/Deploy/ManualScripts/Windows/DeployMac.bat b/Deploy/ManualScripts/Windows/DeployMac.bat new file mode 100644 index 000000000..7c182a920 --- /dev/null +++ b/Deploy/ManualScripts/Windows/DeployMac.bat @@ -0,0 +1,12 @@ +@ECHO OFF + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish MacClient.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r osx-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish MacServer.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r osx-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +PAUSE diff --git a/Deploy/ManualScripts/Windows/DeployMacUnstable.bat b/Deploy/ManualScripts/Windows/DeployMacUnstable.bat new file mode 100644 index 000000000..0ceea7daa --- /dev/null +++ b/Deploy/ManualScripts/Windows/DeployMacUnstable.bat @@ -0,0 +1,12 @@ +@ECHO OFF + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish MacClient.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r osx-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish MacServer.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r osx-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +PAUSE diff --git a/Deploy/ManualScripts/Windows/DeployWindows.bat b/Deploy/ManualScripts/Windows/DeployWindows.bat new file mode 100644 index 000000000..3bbdf4829 --- /dev/null +++ b/Deploy/ManualScripts/Windows/DeployWindows.bat @@ -0,0 +1,12 @@ +@ECHO OFF + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish WindowsClient.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r win-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish WindowsServer.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r win-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +PAUSE diff --git a/Deploy/ManualScripts/Windows/DeployWindowsServer.bat b/Deploy/ManualScripts/Windows/DeployWindowsServer.bat new file mode 100644 index 000000000..02beb909a --- /dev/null +++ b/Deploy/ManualScripts/Windows/DeployWindowsServer.bat @@ -0,0 +1,6 @@ +@ECHO OFF + +cd ../../../Barotrauma/BarotraumaServer +dotnet publish WindowsServer.csproj -c Release --self-contained -r win-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +PAUSE diff --git a/Deploy/ManualScripts/Windows/DeployWindowsUnstable.bat b/Deploy/ManualScripts/Windows/DeployWindowsUnstable.bat new file mode 100644 index 000000000..7864badb0 --- /dev/null +++ b/Deploy/ManualScripts/Windows/DeployWindowsUnstable.bat @@ -0,0 +1,12 @@ +@ECHO OFF + +cd ../../../Barotrauma + +cd BarotraumaClient +dotnet publish WindowsClient.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r win-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +cd .. +cd BarotraumaServer +dotnet publish WindowsServer.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r win-x64 /p:Platform=x64 /p:RollForward=Disable /p:RuntimeFrameworkVersion=3.1.16 + +PAUSE diff --git a/Deploy/Windows/DeployLinux.bat b/Deploy/Windows/DeployLinux.bat deleted file mode 100644 index 13cebab0c..000000000 --- a/Deploy/Windows/DeployLinux.bat +++ /dev/null @@ -1,12 +0,0 @@ -@ECHO OFF - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish LinuxClient.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r linux-x64 /p:Platform=x64 - -cd .. -cd BarotraumaServer -dotnet publish LinuxServer.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r linux-x64 /p:Platform=x64 - -PAUSE diff --git a/Deploy/Windows/DeployLinuxUnstable.bat b/Deploy/Windows/DeployLinuxUnstable.bat deleted file mode 100644 index ce7c60bc5..000000000 --- a/Deploy/Windows/DeployLinuxUnstable.bat +++ /dev/null @@ -1,12 +0,0 @@ -@ECHO OFF - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish LinuxClient.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r linux-x64 /p:Platform=x64 - -cd .. -cd BarotraumaServer -dotnet publish LinuxServer.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r linux-x64 /p:Platform=x64 - -PAUSE diff --git a/Deploy/Windows/DeployMac.bat b/Deploy/Windows/DeployMac.bat deleted file mode 100644 index c1488f7a3..000000000 --- a/Deploy/Windows/DeployMac.bat +++ /dev/null @@ -1,12 +0,0 @@ -@ECHO OFF - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish MacClient.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r osx-x64 /p:Platform=x64 - -cd .. -cd BarotraumaServer -dotnet publish MacServer.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r osx-x64 /p:Platform=x64 - -PAUSE diff --git a/Deploy/Windows/DeployMacUnstable.bat b/Deploy/Windows/DeployMacUnstable.bat deleted file mode 100644 index 771f83526..000000000 --- a/Deploy/Windows/DeployMacUnstable.bat +++ /dev/null @@ -1,12 +0,0 @@ -@ECHO OFF - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish MacClient.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r osx-x64 /p:Platform=x64 - -cd .. -cd BarotraumaServer -dotnet publish MacServer.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r osx-x64 /p:Platform=x64 - -PAUSE diff --git a/Deploy/Windows/DeployWindows.bat b/Deploy/Windows/DeployWindows.bat deleted file mode 100644 index cdd63185f..000000000 --- a/Deploy/Windows/DeployWindows.bat +++ /dev/null @@ -1,12 +0,0 @@ -@ECHO OFF - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish WindowsClient.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r win-x64 /p:Platform=x64 - -cd .. -cd BarotraumaServer -dotnet publish WindowsServer.csproj -c Release -clp:ErrorsOnly;Summary --self-contained -r win-x64 /p:Platform=x64 - -PAUSE diff --git a/Deploy/Windows/DeployWindowsServer.bat b/Deploy/Windows/DeployWindowsServer.bat deleted file mode 100644 index 865d61d03..000000000 --- a/Deploy/Windows/DeployWindowsServer.bat +++ /dev/null @@ -1,6 +0,0 @@ -@ECHO OFF - -cd ../../Barotrauma/BarotraumaServer -dotnet publish WindowsServer.csproj -c Release --self-contained -r win-x64 /p:Platform=x64 - -PAUSE diff --git a/Deploy/Windows/DeployWindowsUnstable.bat b/Deploy/Windows/DeployWindowsUnstable.bat deleted file mode 100644 index d8d4b4f7e..000000000 --- a/Deploy/Windows/DeployWindowsUnstable.bat +++ /dev/null @@ -1,12 +0,0 @@ -@ECHO OFF - -cd ../../Barotrauma - -cd BarotraumaClient -dotnet publish WindowsClient.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r win-x64 /p:Platform=x64 - -cd .. -cd BarotraumaServer -dotnet publish WindowsServer.csproj -c Unstable -clp:ErrorsOnly;Summary --self-contained -r win-x64 /p:Platform=x64 - -PAUSE