diff --git a/Barotrauma/BarotraumaClient/ClientCode.projitems b/Barotrauma/BarotraumaClient/ClientCode.projitems index ab5b8970a..1b79fd2eb 100644 --- a/Barotrauma/BarotraumaClient/ClientCode.projitems +++ b/Barotrauma/BarotraumaClient/ClientCode.projitems @@ -79,8 +79,8 @@ - + @@ -213,6 +213,7 @@ + Never diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index def51c503..49e4b1029 100644 --- a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.9.6")] -[assembly: AssemblyFileVersion("0.8.9.6")] +[assembly: AssemblyVersion("0.8.9.8")] +[assembly: AssemblyFileVersion("0.8.9.8")] diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/AITarget.cs index 6a7d9ec27..318d1d048 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/AITarget.cs @@ -13,13 +13,39 @@ namespace Barotrauma var pos = new Vector2(WorldPosition.X, -WorldPosition.Y); if (soundRange > 0.0f) { - Color color = Entity is Character ? Color.Yellow : Color.Orange; + Color color; + if (Entity is Character) + { + color = Color.Yellow; + } + else if (Entity is Item) + { + color = Color.Orange; + } + else + { + color = Color.OrangeRed; + } ShapeExtensions.DrawCircle(spriteBatch, pos, SoundRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom); + ShapeExtensions.DrawCircle(spriteBatch, pos, 3, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom); } if (sightRange > 0.0f) { - Color color = Entity is Character ? Color.CornflowerBlue : Color.CadetBlue; + Color color; + if (Entity is Character) + { + color = Color.CornflowerBlue; + } + else if (Entity is Item) + { + color = Color.CadetBlue; + } + else + { + color = Color.WhiteSmoke; + } ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom); + ShapeExtensions.DrawCircle(spriteBatch, pos, 6, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs index f80a46f5b..b20e0e7c8 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs @@ -16,18 +16,17 @@ namespace Barotrauma if (SelectedAiTarget?.Entity != null) { - GUI.DrawLine(spriteBatch, pos, new Vector2(SelectedAiTarget.WorldPosition.X, -SelectedAiTarget.WorldPosition.Y), Color.Red * 0.3f, 0, 5); + GUI.DrawLine(spriteBatch, pos, new Vector2(SelectedAiTarget.WorldPosition.X, -SelectedAiTarget.WorldPosition.Y), Color.Red * 0.5f, 0, 4); if (wallTarget != null) { Vector2 wallTargetPos = wallTarget.Position; - if (wallTarget.Structure.Submarine != null) wallTargetPos += wallTarget.Structure.Submarine.Position; + if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.Position; } wallTargetPos.Y = -wallTargetPos.Y; - GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Red, false); + GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false); GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5); } - - GUI.Font.DrawString(spriteBatch, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.ToString()})", pos - Vector2.UnitY * 20.0f, Color.Red); + GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.FormatZeroDecimal()})", Color.Red, Color.Black); } /*GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, Color.Red); @@ -52,24 +51,15 @@ namespace Barotrauma } GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black); - if (latchOntoAI != null) + if (LatchOntoAI != null) { - foreach (Joint attachJoint in latchOntoAI.AttachJoints) + foreach (Joint attachJoint in LatchOntoAI.AttachJoints) { GUI.DrawLine(spriteBatch, ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorA.X, -attachJoint.WorldAnchorA.Y)), - ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), Color.Orange * 0.6f, 0, 5); + ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), Color.Green, 0, 4); } - if (latchOntoAI.WallAttachPos.HasValue) - { - GUI.DrawLine(spriteBatch, pos, - ConvertUnits.ToDisplayUnits(new Vector2(latchOntoAI.WallAttachPos.Value.X, -latchOntoAI.WallAttachPos.Value.Y)), Color.Orange * 0.6f, 0, 3); - } - } - - GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); - if (LatchOntoAI.WallAttachPos.HasValue) { GUI.DrawLine(spriteBatch, pos, @@ -77,22 +67,34 @@ namespace Barotrauma } } - GUI.DrawLine(spriteBatch, - new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y), - new Vector2(pathSteering.CurrentPath.CurrentNode.DrawPosition.X, -pathSteering.CurrentPath.CurrentNode.DrawPosition.Y), - Color.Orange * 0.6f, 0, 3); - - for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++) + if (steeringManager is IndoorsSteeringManager pathSteering) { - GUI.DrawLine(spriteBatch, - new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y), - new Vector2(pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.Y), - Color.Orange * 0.6f, 0, 3); + var path = pathSteering.CurrentPath; + if (path != null) + { + if (path.CurrentNode != null) + { + GUI.DrawLine(spriteBatch, pos, + new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y), + Color.DarkViolet, 0, 3); - GUI.SmallFont.DrawString(spriteBatch, - pathSteering.CurrentPath.Nodes[i].ID.ToString(), - new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10), - Color.LightGreen); + GUI.DrawString(spriteBatch, pos - new Vector2(0, 100), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f); + } + for (int i = 1; i < path.Nodes.Count; i++) + { + var previousNode = path.Nodes[i - 1]; + var currentNode = path.Nodes[i]; + GUI.DrawLine(spriteBatch, + new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y), + new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y), + Color.Red * 0.5f, 0, 3); + + GUI.SmallFont.DrawString(spriteBatch, + currentNode.ID.ToString(), + new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), + Color.Red); + } + } } GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2); GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs index b588b93a4..28686501e 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs @@ -75,11 +75,12 @@ namespace Barotrauma GUI.SmallFont.DrawString(spriteBatch, currentNode.ID.ToString(), - new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y - 10), - Color.LightGreen); + new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), + Color.SkyBlue); } } } + GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2); GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); //if (Character.IsKeyDown(InputType.Aim)) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index c2761aeaf..c6f95eda7 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -88,13 +88,13 @@ namespace Barotrauma Collider.AngularVelocity = newAngularVelocity; float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); - float errorTolerance = character.AllowInput ? 0.01f : 0.2f; + float errorTolerance = character.AllowInput ? 0.01f : 0.1f; if (distSqrd > errorTolerance) { if (distSqrd > 10.0f || !character.AllowInput) { Collider.TargetRotation = newRotation; - SetPosition(newPosition, lerp: distSqrd < 5.0f); + SetPosition(newPosition, lerp: distSqrd < 1.0f); } else { @@ -108,15 +108,8 @@ namespace Barotrauma // -> we need to correct it manually if (!character.AllowInput) { - float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition); - float mainLimbErrorTolerance = 0.1f; - //if the main limb is roughly at the correct position and the collider isn't moving (much at least), - //don't attempt to correct the position. - if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f) - { - MainLimb.PullJointWorldAnchorB = Collider.SimPosition; - MainLimb.PullJointEnabled = true; - } + MainLimb.PullJointWorldAnchorB = Collider.SimPosition; + MainLimb.PullJointEnabled = true; } } character.MemLocalState.Clear(); diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index 615423b53..79abaed11 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -224,6 +224,20 @@ namespace Barotrauma } } + if (SelectedConstruction != null && SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect))) + { + if (GameMain.Client != null) + { + //emulate a Select input to get the character to deselect the item server-side + keys[(int)InputType.Select].Hit = true; + } + //reset focus to prevent us from accidentally interacting with another entity + focusedItem = null; + focusedCharacter = null; + findFocusedTimer = 0.2f; + SelectedConstruction = null; + } + DoInteractionUpdate(deltaTime, mouseSimPos); } @@ -327,6 +341,8 @@ namespace Barotrauma debugInteractablesAtCursor.Clear(); debugInteractablesNearCursor.Clear(); + bool draggingItemToWorld = CharacterInventory.DraggingItemToWorld; + //reduce the amount of aim assist if an item has been selected //= can't switch selection to another item without deselecting the current one first UNLESS the cursor is directly on the item //otherwise it would be too easy to accidentally switch the selected item when rewiring items @@ -348,6 +364,15 @@ namespace Barotrauma if (item.body != null && !item.body.Enabled) continue; if (item.ParentInventory != null) continue; if (ignoredItems != null && ignoredItems.Contains(item)) continue; + + if (draggingItemToWorld) + { + if (item.OwnInventory == null || + !item.OwnInventory.CanBePut(CharacterInventory.draggingItem)) + { + continue; + } + } float distanceToItem = float.PositiveInfinity; if (item.IsInsideTrigger(displayPosition, out Rectangle transformedTrigger)) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index fd0af8cf0..a2137e660 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -248,7 +248,7 @@ namespace Barotrauma scale: scale); } - if (!GUI.DisableItemHighlights) + if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld) { var hudTexts = focusedItem.GetHUDTexts(character); diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs index a4eb436a9..e01a3dd2b 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs @@ -241,10 +241,12 @@ namespace Barotrauma if (!string.IsNullOrEmpty(jobIdentifier)) { jobPrefab = JobPrefab.List.Find(jp => jp.Identifier == jobIdentifier); - for (int i = 0; i < jobPrefab.Skills.Count; i++) + byte skillCount = inc.ReadByte(); + for (int i = 0; i < skillCount; i++) { + string skillIdentifier = inc.ReadString(); float skillLevel = inc.ReadSingle(); - skillLevels.Add(jobPrefab.Skills[i].Identifier, skillLevel); + skillLevels.Add(skillIdentifier, skillLevel); } } @@ -254,7 +256,6 @@ namespace Barotrauma ID = infoID, }; ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); - System.Diagnostics.Debug.Assert(skillLevels.Count == ch.Job.Skills.Count); if (ch.Job != null) { foreach (KeyValuePair skill in skillLevels) @@ -262,11 +263,12 @@ namespace Barotrauma Skill matchingSkill = ch.Job.Skills.Find(s => s.Identifier == skill.Key); if (matchingSkill == null) { - DebugConsole.ThrowError("Skill \"" + skill.Key + "\" not found in character \"" + newName + "\""); + ch.Job.Skills.Add(new Skill(skill.Key, skill.Value)); continue; } matchingSkill.Level = skill.Value; } + ch.Job.Skills.RemoveAll(s => !skillLevels.ContainsKey(s.Identifier)); } return ch; } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index c53b261fa..5753c1c97 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs @@ -3,7 +3,6 @@ using Lidgren.Network; using Microsoft.Xna.Framework; using System; using System.Linq; -using System.Collections.Generic; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs index 04cb8c620..777bc7775 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs @@ -68,6 +68,14 @@ namespace Barotrauma private float dropItemAnimDuration = 0.5f; private float dropItemAnimTimer; + + public Item DroppedItem + { + get + { + return droppedItem; + } + } private Item droppedItem; private GUIComponent draggingMed; @@ -100,13 +108,15 @@ namespace Barotrauma if (openHealthWindow == value) return; if (value != null && !value.UseHealthWindow) return; + var prevOpenHealthWindow = openHealthWindow; + openHealthWindow = value; toggledThisFrame = true; if (Character.Controlled == null) { return; } if (value == null && Character.Controlled?.SelectedCharacter?.CharacterHealth != null && - Character.Controlled.SelectedCharacter.CharacterHealth == openHealthWindow && + Character.Controlled.SelectedCharacter.CharacterHealth == prevOpenHealthWindow && !Character.Controlled.SelectedCharacter.CanInventoryBeAccessed) { Character.Controlled.DeselectCharacter(); @@ -537,11 +547,13 @@ namespace Barotrauma } if (PlayerInput.KeyHit(InputType.Health) && GUI.KeyboardDispatcher.Subscriber == null && - Character.AllowInput && Character.FocusedCharacter == null && !toggledThisFrame) + Character.Controlled.AllowInput && !toggledThisFrame) { if (openHealthWindow != null) + { OpenHealthWindow = null; - else + } + else if (Character.Controlled == Character && Character.Controlled.FocusedCharacter == null) { OpenHealthWindow = this; forceAfflictionContainerUpdate = true; @@ -554,7 +566,10 @@ namespace Barotrauma HUD.CloseHUD(HUDLayoutSettings.HealthWindowAreaLeft)) { //emulate a Health input to get the character to deselect the item server-side - Character.Keys[(int)InputType.Health].Hit = true; + if (GameMain.Client != null) + { + Character.Controlled.Keys[(int)InputType.Health].Hit = true; + } OpenHealthWindow = null; } } diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 4d681d60b..b993f7deb 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -403,6 +403,11 @@ namespace Barotrauma AssignRelayToServer("help", false); AssignRelayToServer("verboselogging", false); AssignRelayToServer("freecam", false); +#if DEBUG + AssignRelayToServer("simulatedlatency", false); + AssignRelayToServer("simulatedloss", false); + AssignRelayToServer("simulatedduplicateschance", false); +#endif commands.Add(new Command("clientlist", "", (string[] args) => { })); AssignRelayToServer("clientlist", true); @@ -957,6 +962,8 @@ namespace Barotrauma } } } + element.Value = lines[i]; + i++; } }, isCheat: false)); #endif diff --git a/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs index 308aa869f..64fecb33b 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs @@ -178,10 +178,15 @@ namespace Barotrauma string displayedText = message.TranslatedText; string senderName = ""; + Color senderColor = Color.White; if (!string.IsNullOrWhiteSpace(message.SenderName)) { senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName; } + if (message.Sender?.Info?.Job != null) + { + senderColor = Color.Lerp(message.Sender.Info.Job.Prefab.UIColor, Color.White, 0.25f); + } var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null, color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f); @@ -191,7 +196,7 @@ namespace Barotrauma { senderNameBlock = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform) { AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) }, - senderName, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null) + senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null) { CanBeFocused = true }; @@ -230,7 +235,7 @@ namespace Barotrauma Visible = false }; var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), popupMsg.RectTransform, Anchor.TopRight), - senderName, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopRight) + senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopRight) { CanBeFocused = false }; @@ -328,6 +333,7 @@ namespace Barotrauma var popupMsg = popupMessages.Count > 0 ? popupMessages.Peek() : null; if (popupMsg != null) { + int offset = -popupMsg.Rect.Width - toggleButton.Rect.Width * 2 - (int)(50 * GUI.Scale) - (guiFrame.Rect.X - GameMain.GraphicsWidth); popupMsg.Visible = true; //popup messages appear and disappear faster when there's more pending messages popupMessageTimer += deltaTime * popupMessages.Count * popupMessages.Count; @@ -335,7 +341,7 @@ namespace Barotrauma { //move the message out of the screen and delete it popupMsg.RectTransform.ScreenSpaceOffset = - new Point((int)MathHelper.SmoothStep(-popupMsg.Rect.Width - toggleButton.Rect.Width * 2, 10, (popupMessageTimer - PopupMessageDuration) * 5.0f), 0); + new Point((int)MathHelper.SmoothStep(offset, 10, (popupMessageTimer - PopupMessageDuration) * 5.0f), 0); if (popupMessageTimer > PopupMessageDuration + 1.0f) { popupMessageTimer = 0.0f; @@ -347,7 +353,7 @@ namespace Barotrauma { //move the message on the screen popupMsg.RectTransform.ScreenSpaceOffset = new Point( - (int)MathHelper.SmoothStep(0, -popupMsg.Rect.Width - toggleButton.Rect.Width * 2 - (int)(35 * GUI.Scale), popupMessageTimer * 5.0f), 0); + (int)MathHelper.SmoothStep(0, offset, popupMessageTimer * 5.0f), 0); } } } diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs index cd8a792c0..ec5b88851 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs @@ -56,7 +56,7 @@ namespace Barotrauma private static List messages = new List(); private static Sound[] sounds; private static bool pauseMenuOpen, settingsMenuOpen; - private static GUIFrame pauseMenu; + public static GUIFrame PauseMenu { get; private set; } private static Sprite arrow, lockIcon, checkmarkIcon, timerIcon; public static KeyboardDispatcher KeyboardDispatcher { get; set; } @@ -69,6 +69,9 @@ namespace Barotrauma public static ScalableFont Font => Style?.Font; public static ScalableFont SmallFont => Style?.SmallFont; public static ScalableFont LargeFont => Style?.LargeFont; + public static ScalableFont VideoTitleFont => Style?.VideoTitleFont; + public static ScalableFont ObjectiveTitleFont => Style?.ObjectiveTitleFont; + public static ScalableFont ObjectiveNameFont => Style?.ObjectiveNameFont; public static UISprite UIGlow => Style.UIGlow; @@ -530,14 +533,19 @@ namespace Barotrauma if (list.Count == 0) { return; } foreach (var item in list) { - int i = updateList.Count - 1; - while (updateList[i].UpdateOrder > item.UpdateOrder) + int index = 0; + if (updateList.Count > 0) { - i--; + index = updateList.Count - 1; + while (updateList[index].UpdateOrder > item.UpdateOrder) + { + index--; + if (index == 0) { break; } + } } if (!updateListSet.Contains(item)) { - updateList.Insert(Math.Max(i, 0), item); + updateList.Insert(index, item); updateListSet.Add(item); } } @@ -553,7 +561,7 @@ namespace Barotrauma if (pauseMenuOpen) { - pauseMenu.AddToGUIUpdateList(); + PauseMenu.AddToGUIUpdateList(); } if (settingsMenuOpen) { @@ -648,6 +656,7 @@ namespace Barotrauma msg.Timer -= deltaTime; msg.Pos += msg.Velocity * deltaTime; } + } messages.RemoveAll(m => m.Timer <= 0.0f); } @@ -721,6 +730,10 @@ namespace Barotrauma Vector2 textSize = font.MeasureString(text); DrawRectangle(sb, pos - Vector2.One * backgroundPadding, textSize + Vector2.One * 2.0f * backgroundPadding, (Color)backgroundColor, true); } + else + { + sb.Draw(t, new Rectangle(rect.X + thickness, rect.Y, rect.Width - thickness * 2, thickness), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); + sb.Draw(t, new Rectangle(rect.X + thickness, rect.Y + rect.Height - thickness, rect.Width - thickness * 2, thickness), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); font.DrawString(sb, text, pos, color); } @@ -1412,9 +1425,9 @@ namespace Barotrauma if (pauseMenuOpen) { - pauseMenu = new GUIFrame(new RectTransform(Vector2.One, Canvas), style: null, color: Color.Black * 0.5f); + PauseMenu = new GUIFrame(new RectTransform(Vector2.One, Canvas), style: null, color: Color.Black * 0.5f); - var pauseMenuInner = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.3f), pauseMenu.RectTransform, Anchor.Center) { MinSize = new Point(200, 300) }); + var pauseMenuInner = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.3f), PauseMenu.RectTransform, Anchor.Center) { MinSize = new Point(200, 300) }); var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.85f, 0.85f), pauseMenuInner.RectTransform, Anchor.Center)) { diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs index c7f947b4e..2b3317682 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs @@ -12,6 +12,9 @@ namespace Barotrauma public ScalableFont Font { get; private set; } public ScalableFont SmallFont { get; private set; } public ScalableFont LargeFont { get; private set; } + public ScalableFont VideoTitleFont { get; private set; } + public ScalableFont ObjectiveTitleFont { get; private set; } + public ScalableFont ObjectiveNameFont { get; private set; } public Sprite CursorSprite { get; private set; } @@ -48,6 +51,15 @@ namespace Barotrauma case "largefont": LargeFont = new ScalableFont(subElement, graphicsDevice); break; + case "objectivetitle": + ObjectiveTitleFont = new ScalableFont(subElement, graphicsDevice); + break; + case "objectivename": + ObjectiveNameFont = new ScalableFont(subElement, graphicsDevice); + break; + case "videotitle": + VideoTitleFont = new ScalableFont(subElement, graphicsDevice); + break; case "cursor": CursorSprite = new Sprite(subElement); break; diff --git a/Barotrauma/BarotraumaClient/Source/GUI/HUDLayoutSettings.cs b/Barotrauma/BarotraumaClient/Source/GUI/HUDLayoutSettings.cs index 199cdb9d6..1a0b79cd9 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/HUDLayoutSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/HUDLayoutSettings.cs @@ -50,6 +50,11 @@ namespace Barotrauma get; private set; } + public static Rectangle ObjectiveAnchor + { + get; private set; + } + public static Rectangle InventoryAreaLower { get; private set; @@ -156,6 +161,10 @@ namespace Barotrauma new Rectangle(Padding, CrewArea.Y, chatBoxWidth, chatBoxHeight) : new Rectangle(GameMain.GraphicsWidth - Padding - chatBoxWidth, CrewArea.Y, chatBoxWidth, chatBoxHeight); + int objectiveAnchorWidth = (int)(250 * GUI.Scale); + int objectiveAnchorOffsetY = (int)(100 * GUI.Scale); + ObjectiveAnchor = new Rectangle(GameMain.GraphicsWidth - Padding - objectiveAnchorWidth, CrewArea.Y + crewAreaHeight + objectiveAnchorOffsetY, objectiveAnchorWidth, 0); + int lowerAreaHeight = (int)Math.Min(GameMain.GraphicsHeight * 0.25f, 280); InventoryAreaLower = new Rectangle(Padding, GameMain.GraphicsHeight - lowerAreaHeight, GameMain.GraphicsWidth - Padding * 2, lowerAreaHeight); @@ -191,6 +200,9 @@ namespace Barotrauma { public static bool CloseHUD(Rectangle rect) { + // Always close when hitting escape + if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) { return true; } + //don't close when the cursor is on a UI element if (GUI.MouseOn != null) return false; diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index ded8f0896..09c332ced 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -601,8 +601,10 @@ namespace Barotrauma { ((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); } - else // Otherwise toggle pausing. + else if ((Character.Controlled?.SelectedConstruction == null || !Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null)) + && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null) { + // Otherwise toggle pausing, unless another window/interface is open. GUI.TogglePauseMenu(); } } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index 1ef20b629..0762fbd00 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -130,7 +130,12 @@ namespace Barotrauma { OnEnterMessage = (textbox, text) => { - if (Character.Controlled == null) { return true; } + if (Character.Controlled?.Info == null) + { + textbox.Deselect(); + textbox.Text = ""; + return true; + } textbox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default]; @@ -976,7 +981,7 @@ namespace Barotrauma foreach (GUIComponent c in prevCharacterListBox.Content.Children) { Character character = c.UserData as Character; - if (character == null) continue; + if (character == null || character.IsDead || character.Removed) continue; AddCharacter(character); DisplayCharacterOrder(character, character.CurrentOrder); } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/SinglePlayerCampaign.cs index 01caedf9e..b4a7414e3 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/SinglePlayerCampaign.cs @@ -44,7 +44,7 @@ namespace Barotrauma ContextualTutorial = Tutorial.Tutorials.Find(t => t is ContextualTutorial) as ContextualTutorial; - if (ContextualTutorial.Selected && !ContextualTutorial.Initialized) // Selected when starting a new game -> initialize + if (ContextualTutorial.Selected) // Selected when starting a new game -> initialize { ContextualTutorial.Initialize(); } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs index 843b7566e..292f85b37 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Xml.Linq; using System; using Microsoft.Xna.Framework; using Barotrauma.Items.Components; using System.Linq; +using Microsoft.Xna.Framework.Input; namespace Barotrauma.Tutorials { @@ -13,38 +14,59 @@ namespace Barotrauma.Tutorials public static bool ContentRunning = false; public static bool Initialized = false; - private enum ContentTypes { None = 0, Video = 1, Text = 2 }; + private enum ContentTypes { None = 0, Video = 1, TextOnly = 2 }; private TutorialSegment activeSegment; private List segments; - private SpriteSheetPlayer spriteSheetPlayer; + private VideoPlayer videoPlayer; + private Steering navConsole; private Reactor reactor; private Sonar sonar; private Vector2 subStartingPosition; private List crew; + private Character mechanic; + private Character engineer; + private Character injuredMember = null; + private List> characterTimeOnSonar; private float requiredTimeOnSonar = 5f; private bool started = false; private string playableContentPath; - + private float tutorialTimer; - private float degrading2ActivationCountdown; private bool disableTutorialOnDeficiencyFound = true; + private GUIFrame holderFrame, objectiveFrame; + private List activeObjectives = new List(); + private string objectiveTranslated; + + private float floodTutorialTimer = 0.0f; + private const float floodTutorialDelay = 2.0f; + private float medicalTutorialTimer = 0.0f; + private const float medicalTutorialDelay = 2.0f; + + private Point screenResolution; + private float prevUIScale; + private class TutorialSegment { - public string Name; + public string Id; + public string Objective; public ContentTypes ContentType; - public XElement Content; + public XElement TextContent; + public XElement VideoContent; public bool IsTriggered; + public GUIButton ReplayButton; + public GUITextBlock LinkedTitle, LinkedText; public TutorialSegment(XElement config) { - Name = config.GetAttributeString("name", "Missing Name"); + Id = config.GetAttributeString("id", "Missing ID"); + Objective = TextManager.Get(config.GetAttributeString("objective", string.Empty), true); Enum.TryParse(config.GetAttributeString("contenttype", "None"), true, out ContentType); IsTriggered = config.GetAttributeBool("istriggered", false); @@ -53,10 +75,11 @@ namespace Barotrauma.Tutorials case ContentTypes.None: break; case ContentTypes.Video: - Content = config.Element("Video"); + VideoContent = config.Element("Video"); + TextContent = config.Element("Text"); break; - case ContentTypes.Text: - Content = config.Element("Text"); + case ContentTypes.TextOnly: + TextContent = config.Element("Text"); break; } } @@ -77,17 +100,17 @@ namespace Barotrauma.Tutorials public override void Initialize() { - if (Initialized) return; - Initialized = true; - - base.Initialize(); - spriteSheetPlayer = new SpriteSheetPlayer(); - characterTimeOnSonar = new List>(); - for (int i = 0; i < segments.Count; i++) { segments[i].IsTriggered = false; } + + if (Initialized) return; + Initialized = true; + + base.Initialize(); + videoPlayer = new VideoPlayer(); + characterTimeOnSonar = new List>(); } public void LoadPartiallyComplete(XElement element) @@ -111,15 +134,6 @@ namespace Barotrauma.Tutorials } } - private void PreloadVideoContent() - { - for (int i = 0; i < segments.Count; i++) - { - if (segments[i].ContentType != ContentTypes.Video || segments[i].IsTriggered) continue; - spriteSheetPlayer.PreloadContent(playableContentPath, "tutorial", segments[i].Name, segments[i].Content); - } - } - public void SavePartiallyComplete(XElement element) { XElement tutorialElement = new XElement("contextualtutorial"); @@ -151,13 +165,13 @@ namespace Barotrauma.Tutorials { if (!Initialized) return; - PreloadVideoContent(); - base.Start(); - + injuredMember = null; + activeObjectives.Clear(); + objectiveTranslated = TextManager.Get("Objective"); + CreateObjectiveFrame(); activeSegment = null; - tutorialTimer = 0.0f; - degrading2ActivationCountdown = -1; + tutorialTimer = floodTutorialTimer = medicalTutorialTimer = 0.0f; subStartingPosition = Vector2.Zero; characterTimeOnSonar.Clear(); @@ -193,6 +207,9 @@ namespace Barotrauma.Tutorials } crew = GameMain.GameSession.CrewManager.GetCharacters().ToList(); + mechanic = CrewMemberWithJob("mechanic"); + engineer = CrewMemberWithJob("engineer"); + Completed = true; // Trigger completed at start to prevent the contextual tutorial from automatically activating on starting new campaigns after this one started = true; } @@ -208,71 +225,185 @@ namespace Barotrauma.Tutorials public void Stop() { started = ContentRunning = Initialized = false; - spriteSheetPlayer.Remove(); - spriteSheetPlayer = null; + videoPlayer.Remove(); + videoPlayer = null; characterTimeOnSonar = null; } + 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++) + { + CreateObjectiveGUI(activeObjectives[i], i); + } + + screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + prevUIScale = GUI.Scale; + } + public override void AddToGUIUpdateList() { - base.AddToGUIUpdateList(); - if (spriteSheetPlayer != null) + if (videoPlayer != null) { - spriteSheetPlayer.AddToGUIUpdateList(); + videoPlayer.AddToGUIUpdateList(order: 100); } + + if (GUI.DisableHUD) return; + if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale) + { + CreateObjectiveFrame(); + } + + if (objectiveFrame != null && activeObjectives.Count > 0) + { + objectiveFrame.AddToGUIUpdateList(order: -1); + } + base.AddToGUIUpdateList(); } public override void Update(float deltaTime) { + if (videoPlayer != null) + { + videoPlayer.Update(); + } + + if (infoBox != null) + { + if (PlayerInput.KeyHit(Keys.Enter) || PlayerInput.KeyHit(Keys.Escape)) + { + CloseInfoFrame(null, null); + } + } + if (!started || ContentRunning) return; deltaTime *= 0.5f; - + for (int i = 0; i < segments.Count; i++) { - if (segments[i].IsTriggered) continue; + if (segments[i].IsTriggered || activeObjectives.Contains(segments[i])) continue; if (CheckContextualTutorials(i, deltaTime)) // Found a relevant tutorial, halt finding new ones { break; } } + + for (int i = 0; i < activeObjectives.Count; i++) + { + CheckActiveObjectives(activeObjectives[i], deltaTime); + } + } + + private void ClosePreTextAndTriggerVideoCallback() + { + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(activeSegment.VideoContent), new VideoPlayer.TextSettings(activeSegment.VideoContent), activeSegment.Id, true, activeSegment.Objective, CurrentSegmentStopCallback); } private void CurrentSegmentStopCallback() { + if (!string.IsNullOrEmpty(activeSegment.Objective)) + { + AddNewObjective(activeSegment); + } + activeSegment = null; ContentRunning = false; } + private void AddNewObjective(TutorialSegment segment) + { + activeObjectives.Add(segment); + CreateObjectiveGUI(segment, activeObjectives.Count - 1); + } + + private void CreateObjectiveGUI(TutorialSegment segment, int index) + { + Point replayButtonSize = new Point((int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).X * GUI.Scale), (int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).Y * 1.45f * GUI.Scale)); + + segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopRight, Pivot.TopRight) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null); + segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) => + { + ReplaySegmentVideo(segment); + return true; + }; + + int yOffset = (int)((GUI.ObjectiveNameFont.MeasureString(objectiveTranslated).Y / 2f + 5) * GUI.Scale); + segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point(replayButtonSize.X, yOffset), segment.ReplayButton.RectTransform, Anchor.Center, Pivot.BottomCenter) { AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }, objectiveTranslated, textColor: Color.White, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight); + segment.LinkedText = new GUITextBlock(new RectTransform(new Point(replayButtonSize.X, yOffset), segment.ReplayButton.RectTransform, Anchor.Center, Pivot.TopCenter) { AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }, segment.Objective, textColor: new Color(4, 180, 108), font: GUI.ObjectiveNameFont, textAlignment: Alignment.CenterRight); + + segment.LinkedTitle.TextScale = segment.LinkedText.TextScale = GUI.Scale; + + 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; + } + + private void RemoveCompletedObjective(TutorialSegment objective) + { + objective.IsTriggered = true; + + int checkMarkHeight = (int)(objective.ReplayButton.Rect.Height * 1.2f); + int checkMarkWidth = (int)(checkMarkHeight * 0.93f); + + Color color = new Color(4, 180, 108); + RectTransform rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), objective.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft); + rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - 5, 0); + GUIImage checkmark = new GUIImage(rectTA, "CheckMark"); + checkmark.Color = color; + + RectTransform rectTB = new RectTransform(new Vector2(1.1f, .8f), objective.LinkedText.RectTransform, Anchor.Center, Pivot.Center); + GUIImage stroke = new GUIImage(rectTB, "Stroke"); + stroke.Color = color; + + CoroutineManager.StartCoroutine(WaitForObjectiveEnd(objective)); + } + + private IEnumerable WaitForObjectiveEnd(TutorialSegment objective) + { + yield return new WaitForSeconds(2.0f); + objectiveFrame.RemoveChild(objective.ReplayButton); + activeObjectives.Remove(objective); + + for (int i = 0; i < activeObjectives.Count; i++) + { + activeObjectives[i].ReplayButton.RectTransform.AbsoluteOffset = new Point(0, (activeObjectives[i].ReplayButton.Rect.Height + 20) * i); + } + } + private bool CheckContextualTutorials(int index, float deltaTime) { switch (index) { case 0: // Welcome: Game Start [Text] - if (tutorialTimer < 0.5f) + if (tutorialTimer < 1.0f) { tutorialTimer += deltaTime; return false; } break; - case 1: // Command Reactor: 10 seconds after 'Welcome' dismissed and only if no command given to start reactor [Video] - if (tutorialTimer < 10.5f) + case 1: // Command Reactor: 2 seconds after 'Welcome' dismissed and only if no command given to start reactor [Video] + if (!segments[0].IsTriggered) return false; + if (tutorialTimer < 3.0f) { tutorialTimer += deltaTime; if (HasOrder("operatereactor")) { segments[index].IsTriggered = true; - tutorialTimer = 10.5f; + tutorialTimer = 2.5f; } return false; } break; - case 2: // Nav Console: 20 seconds after 'Command Reactor' dismissed or if nav console is activated [Video] + case 2: // Nav Console: 2 seconds after 'Command Reactor' dismissed or if nav console is activated [Video] + if (!IsReactorPoweredUp()) return false; // Do not advance tutorial based on this segment if reactor has not been powered up if (Character.Controlled?.SelectedConstruction != navConsole.Item) - { - if (!segments[1].IsTriggered) return false; // Do not advance tutorial timer based on this segment if reactor has not been powered up - if (tutorialTimer < 30.5f) + { + if (tutorialTimer < 4.5f) { tutorialTimer += deltaTime; return false; @@ -280,20 +411,13 @@ namespace Barotrauma.Tutorials } else { - if (!segments[1].IsTriggered || !HasOrder("operatereactor")) // If reactor has not been powered up or ordered to be, default to that one first - { - if (tutorialTimer < 10.5f) - { - tutorialTimer = 10.5f; - } - return false; - } - - tutorialTimer = 30.5f; + tutorialTimer = 4.5f; } - break; + + TriggerTutorialSegment(index, GameMain.GameSession.EndLocation.Name); + return true; case 3: // Objective: Travel ~150 meters and while sub is not flooding [Text] - if (Vector2.Distance(subStartingPosition, Submarine.MainSub.WorldPosition) < 12000f || IsFlooding()) + if (Vector2.Distance(subStartingPosition, Submarine.MainSub.WorldPosition) < 8000f || IsFlooding()) { return false; } @@ -307,6 +431,11 @@ namespace Barotrauma.Tutorials { return false; } + else if (floodTutorialTimer < floodTutorialDelay) + { + floodTutorialTimer += deltaTime; + return false; + } break; case 5: // Reactor: Player uses reactor for the first time [Video] if (Character.Controlled?.SelectedConstruction != reactor.Item) @@ -321,19 +450,23 @@ namespace Barotrauma.Tutorials } break; case 7: // Degrading1: Any equipment degrades to 50% health or less and player has not assigned any crew to perform maintenance [Text] + if ((mechanic == null || mechanic.IsDead) && (engineer == null || engineer.IsDead)) // Both engineer and mechanic are dead or do not exist -> do not display + { + return false; + } + bool degradedEquipmentFound = false; foreach (Item item in Item.ItemList) { - if (!item.Repairables.Any() || item.ConditionPercentage > 50) continue; + if (!item.Repairables.Any() || item.Condition > 50.0f) continue; degradedEquipmentFound = true; break; } if (degradedEquipmentFound) { - degrading2ActivationCountdown = 5f; - if (HasOrder("repairsystems")) + if (HasOrder("repairsystems", "jobspecific")) { segments[index].IsTriggered = true; return false; @@ -344,43 +477,44 @@ namespace Barotrauma.Tutorials return false; } break; - case 8: // Degrading2: 5 seconds after 'Degrading1' dismissed, and only if player has not assigned any crew to perform maintenance [Video] - if (degrading2ActivationCountdown == -1f) + case 8: // Medical: Crewmember is injured but not killed [Video] + + if (injuredMember == null) { - return false; - } - else if (degrading2ActivationCountdown > 0.0f) - { - degrading2ActivationCountdown -= deltaTime; - if (HasOrder("repairsystems")) + for (int i = 0; i < crew.Count; i++) { - segments[index].IsTriggered = true; + Character member = crew[i]; + if (member.Vitality < member.MaxVitality && !member.IsDead) + { + injuredMember = member; + break; + } } return false; } - break; - case 9: // Medical: Crewmember is injured but not killed [Video] - bool injuredFound = false; - for (int i = 0; i < crew.Count; i++) + else if (medicalTutorialTimer < medicalTutorialDelay) { - Character member = crew[i]; - if (member.Vitality < member.MaxVitality && !member.IsDead) - { - injuredFound = true; - break; - } + medicalTutorialTimer += deltaTime; + return false; } - - if (!injuredFound) return false; - break; - case 10: // Approach1: Destination is within ~100m [Video] + else + { + TriggerTutorialSegment(index, new string[] { injuredMember.Info.DisplayName, + (injuredMember.Info.Gender == Gender.Male) ? TextManager.Get("PronounPossessiveMale").ToLower() : TextManager.Get("PronounPossessiveFemale").ToLower() }); + return true; + } + case 9: // Approach1: Destination is within ~100m [Video] if (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 8000f) { return false; } - break; - case 11: // Approach2: Sub is docked [Text] + else + { + TriggerTutorialSegment(index, GameMain.GameSession.EndLocation.Name); + return true; + } + case 10: // Approach2: Sub is docked [Text] if (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0) { return false; @@ -392,11 +526,128 @@ namespace Barotrauma.Tutorials return true; } - private bool HasOrder(string aiTag) + private bool HasObjective(string objectiveName) + { + for (int i = 0; i < activeObjectives.Count; i++) + { + if (activeObjectives[i].Id == objectiveName) return true; + } + + return false; + } + + private void CheckActiveObjectives(TutorialSegment objective, float deltaTime) + { + switch(objective.Id) + { + case "ReactorCommand": // Reactor commanded + if (!IsReactorPoweredUp()) + { + if (!HasOrder("operatereactor")) return; + } + break; + case "NavConsole": // traveled 50 meters + if (Vector2.Distance(subStartingPosition, Submarine.MainSub.WorldPosition) < 4000f) + { + return; + } + break; + case "Flood": // Hull breaches repaired + if (IsFlooding()) return; + break; + case "Medical": + if (injuredMember != null && !injuredMember.IsDead) + { + if (injuredMember.CharacterHealth.DroppedItem == null) return; + } + break; + case "EnemyOnSonar": // Enemy dispatched + if (HasEnemyOnSonarForDuration(deltaTime)) + { + return; + } + break; + case "Degrading": // Fixed + if (mechanic != null && !mechanic.IsDead) + { + HumanAIController humanAI = mechanic.AIController as HumanAIController; + if (mechanic.CurrentOrder?.AITag != "repairsystems" || humanAI.CurrentOrderOption != "jobspecific") + { + return; + } + } + + if (engineer != null && !engineer.IsDead) + { + HumanAIController humanAI = engineer.AIController as HumanAIController; + if (engineer.CurrentOrder?.AITag != "repairsystems" || humanAI.CurrentOrderOption != "jobspecific") + { + return; + } + } + + break; + case "Approach1": // Wait until docked + if (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0) + { + return; + } + break; + } + + RemoveCompletedObjective(objective); + } + + private bool IsReactorPoweredUp() + { + 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) < 10; + } + + private Character CrewMemberWithJob(string job) { for (int i = 0; i < crew.Count; i++) { - if (crew[i].CurrentOrder?.AITag == aiTag) return true; + if (crew[i].Info.Job.Name == job) return crew[i]; + } + + return null; + } + + private bool HasOrder(string aiTag, string option = null) + { + for (int i = 0; i < crew.Count; i++) + { + if (crew[i].CurrentOrder?.AITag == aiTag) + { + if (option == null) + { + return true; + } + else + { + HumanAIController humanAI = crew[i].AIController as HumanAIController; + return humanAI.CurrentOrderOption == option; + } + } } return false; @@ -406,10 +657,12 @@ namespace Barotrauma.Tutorials { foreach (Gap gap in Gap.GapList) { - if (gap.ConnectedWall == null) continue; + if (gap.ConnectedWall == null || gap.IsRoomToRoom) continue; if (gap.ConnectedDoor != null || gap.Open <= 0.0f) continue; if (gap.Submarine == null) continue; + if (gap.Submarine.IsOutpost) continue; if (gap.Submarine != Submarine.MainSub) continue; + if (gap.FlowTargetHull == null || gap.FlowTargetHull.WaterPercentage <= 0.0f) continue; return true; } @@ -447,27 +700,51 @@ namespace Barotrauma.Tutorials } } - return characterTimeOnSonar.Find(ct => ct.Second >= requiredTimeOnSonar) != null; + return characterTimeOnSonar.Find(ct => ct.Second >= requiredTimeOnSonar && !ct.First.IsDead) != null; } private void TriggerTutorialSegment(int index, params object[] args) { + Inventory.draggingItem = null; ContentRunning = true; activeSegment = segments[index]; - activeSegment.IsTriggered = true; + + string tutorialText = TextManager.GetFormatted(activeSegment.TextContent.GetAttributeString("tag", ""), true, args); + string objectiveText = string.Empty; + + if (!string.IsNullOrEmpty(activeSegment.Objective)) + { + if (args.Length == 0) + { + objectiveText = activeSegment.Objective; + } + else + { + objectiveText = string.Format(activeSegment.Objective, args); + } + + activeSegment.Objective = objectiveText; + } + else + { + activeSegment.IsTriggered = true; // Complete at this stage only if no related objective + } switch (activeSegment.ContentType) { case ContentTypes.None: break; case ContentTypes.Video: - spriteSheetPlayer.LoadContent(playableContentPath, activeSegment.Content, activeSegment.Name, true, true, CurrentSegmentStopCallback); + infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Id), tutorialText, + activeSegment.TextContent.GetAttributeInt("width", 300), + activeSegment.TextContent.GetAttributeInt("height", 80), + activeSegment.TextContent.GetAttributeString("anchor", "Center"), true, ClosePreTextAndTriggerVideoCallback); break; - case ContentTypes.Text: - infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Name), TextManager.GetFormatted(activeSegment.Content.GetAttributeString("tag", ""), false, args), - activeSegment.Content.GetAttributeInt("width", 300), - activeSegment.Content.GetAttributeInt("height", 80), - activeSegment.Content.GetAttributeString("anchor", "Center"), true, CurrentSegmentStopCallback); + case ContentTypes.TextOnly: + infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Id), tutorialText, + activeSegment.TextContent.GetAttributeInt("width", 300), + activeSegment.TextContent.GetAttributeInt("height", 80), + activeSegment.TextContent.GetAttributeString("anchor", "Center"), true, CurrentSegmentStopCallback); break; } @@ -479,6 +756,13 @@ namespace Barotrauma.Tutorials CoroutineManager.StartCoroutine(WaitToStop()); // Completed } + private void ReplaySegmentVideo(TutorialSegment segment) + { + if (ContentRunning) return; + ContentRunning = true; + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false); + } + private IEnumerable WaitToStop() { while (ContentRunning) yield return null; diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs index e2c38e71b..69a5e98c9 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs @@ -121,7 +121,7 @@ namespace Barotrauma.Tutorials public virtual void AddToGUIUpdateList() { - if (infoBox != null) infoBox.AddToGUIUpdateList(); + if (infoBox != null) infoBox.AddToGUIUpdateList(order: 100); } public virtual void Update(float deltaTime) @@ -188,17 +188,19 @@ namespace Barotrauma.Tutorials Anchor anchor = Anchor.TopRight; Enum.TryParse(anchorStr, out anchor); - var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), GUI.Canvas, anchor) { AbsoluteOffset = new Point(20) }); + var infoBlock = new GUIFrame(new RectTransform(new Point((int)(width * GUI.Scale), (int)(height * GUI.Scale)), GUI.Canvas, anchor) { AbsoluteOffset = new Point(20) }); infoBlock.Flash(Color.Green); if (title.Length > 0) { var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1f, .35f), infoBlock.RectTransform, Anchor.TopCenter, - Pivot.TopCenter), title, font: GUI.LargeFont, textAlignment: Alignment.Center); + Pivot.TopCenter), title, font: GUI.VideoTitleFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0)); + titleBlock.TextScale = GUI.Scale; } var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.9f, 1f), infoBlock.RectTransform, Anchor.BottomCenter), text, wrap: true); + textBlock.TextScale = GUI.Scale; infoBoxClosedCallback = callback; diff --git a/Barotrauma/BarotraumaClient/Source/GameSettings.cs b/Barotrauma/BarotraumaClient/Source/GameSettings.cs index 22e8604d9..e8d6e8cfe 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSettings.cs @@ -14,12 +14,13 @@ namespace Barotrauma { private enum Tab { - General, Graphics, Audio, Controls, } + private readonly Point MinSupportedResolution = new Point(1024, 540); + private GUIFrame settingsFrame; private GUIButton applyButton; @@ -71,16 +72,79 @@ namespace Barotrauma private void CreateSettingsFrame() { - settingsFrame = new GUIFrame(new RectTransform(new Point(500, 500), GUI.Canvas, Anchor.Center)); + settingsFrame = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.8f), GUI.Canvas, Anchor.Center)); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), settingsFrame.RectTransform), - TextManager.Get("Settings"), textAlignment: Alignment.Center, font: GUI.LargeFont); + var settingsFramePadding = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), settingsFrame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.05f) }, style: null); - var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.8f), settingsFrame.RectTransform, Anchor.Center) - { RelativeOffset = new Vector2(0.0f, 0.06f) }, style: null); + /// General tab -------------------------------------------------------------- + + var leftPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 1.0f), settingsFramePadding.RectTransform, Anchor.TopLeft)); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform), + TextManager.Get("Settings"), textAlignment: Alignment.TopLeft, font: GUI.LargeFont); + + var generalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), leftPanel.RectTransform, Anchor.TopLeft)); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), generalLayoutGroup.RectTransform), TextManager.Get("ContentPackages")); + var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.75f), generalLayoutGroup.RectTransform)) + { + CanBeFocused = false + }; + + foreach (ContentPackage contentPackage in ContentPackage.List) + { + var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.067f), contentPackageList.Content.RectTransform, minSize: new Point(0, 15)), contentPackage.Name) + { + UserData = contentPackage, + OnSelected = SelectContentPackage, + Selected = SelectedContentPackages.Contains(contentPackage) + }; + if (!contentPackage.IsCompatible()) + { + tickBox.TextColor = Color.Red; + tickBox.Enabled = false; + tickBox.ToolTip = TextManager.Get(contentPackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage") + .Replace("[packagename]", contentPackage.Name) + .Replace("[packageversion]", contentPackage.GameVersion.ToString()) + .Replace("[gameversion]", GameMain.Version.ToString()); + } + else if (contentPackage.CorePackage && !contentPackage.ContainsRequiredCorePackageFiles(out List missingContentTypes)) + { + tickBox.TextColor = Color.Red; + tickBox.Enabled = false; + tickBox.ToolTip = TextManager.Get("ContentPackageMissingCoreFiles") + .Replace("[packagename]", contentPackage.Name) + .Replace("[missingfiletypes]", string.Join(", ", missingContentTypes)); + } + } + + new GUITextBlock(new RectTransform(new Vector2(0.92f, 0.05f), generalLayoutGroup.RectTransform), TextManager.Get("Language")); + var languageDD = new GUIDropDown(new RectTransform(new Vector2(0.92f, 0.05f), generalLayoutGroup.RectTransform)); + foreach (string language in TextManager.AvailableLanguages) + { + languageDD.AddItem(TextManager.Get("Language." + language), language); + } + languageDD.SelectItem(TextManager.Language); + languageDD.OnSelected = (guiComponent, obj) => + { + string newLanguage = obj as string; + if (newLanguage == Language) return true; + + UnsavedSettings = true; + Language = newLanguage; + + new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage")); + + return true; + }; + + var rightPanel = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - leftPanel.RectTransform.RelativeSize.X, 0.95f), + settingsFramePadding.RectTransform, Anchor.TopRight)); + + var tabButtonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), rightPanel.RectTransform, Anchor.TopCenter), isHorizontal: true); + + var paddedFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), rightPanel.RectTransform, Anchor.Center), style: null); - var tabButtonHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.05f), settingsFrame.RectTransform, Anchor.TopCenter) - { RelativeOffset = new Vector2(0.0f, 0.11f) }, isHorizontal: true); tabs = new GUIFrame[Enum.GetValues(typeof(Tab)).Length]; tabButtons = new GUIButton[tabs.Length]; @@ -104,10 +168,10 @@ namespace Barotrauma var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.46f, 0.95f), tabs[(int)Tab.Graphics].RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.02f, 0.0f) }) - { RelativeSpacing = 0.01f, Stretch = true }; + { RelativeSpacing = 0.01f }; var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.46f, 0.95f), tabs[(int)Tab.Graphics].RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.02f, 0.0f) }) - { RelativeSpacing = 0.01f, Stretch = true }; + { RelativeSpacing = 0.01f }; var supportedDisplayModes = new List(); foreach (DisplayMode mode in GraphicsAdapter.DefaultAdapter.SupportedDisplayModes) @@ -135,6 +199,7 @@ namespace Barotrauma foreach (DisplayMode mode in supportedDisplayModes) { + if (mode.Width < MinSupportedResolution.X || mode.Height < MinSupportedResolution.Y) { continue; } resolutionDD.AddItem(mode.Width + "x" + mode.Height, mode); if (GraphicsWidth == mode.Width && GraphicsHeight == mode.Height) resolutionDD.SelectItem(mode); } @@ -173,9 +238,6 @@ namespace Barotrauma return true; }; - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform), style: null); - GUITickBox vsyncTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), TextManager.Get("EnableVSync")) { ToolTip = TextManager.Get("EnableVSyncToolTip"), @@ -191,8 +253,6 @@ namespace Barotrauma Selected = VSyncEnabled }; - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), leftColumn.RectTransform), style: null); GUITextBlock particleLimitText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("ParticleLimit")); GUIScrollBar particleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), barSize: 0.1f) @@ -209,8 +269,6 @@ namespace Barotrauma }; particleScrollBar.OnMoved(particleScrollBar, particleScrollBar.BarScroll); - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), rightColumn.RectTransform), style: null); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("LosEffect")); var losModeDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform)); losModeDD.AddItem(TextManager.Get("LosModeNone"), LosMode.None); @@ -229,8 +287,6 @@ namespace Barotrauma return true; }; - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: null); GUITextBlock LightText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("LightMapScale")) { ToolTip = TextManager.Get("LightMapScaleToolTip") @@ -251,8 +307,6 @@ namespace Barotrauma }; lightScrollBar.OnMoved(lightScrollBar, lightScrollBar.BarScroll); - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: null); new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("SpecularLighting")) { ToolTip = TextManager.Get("SpecularLightingToolTip"), @@ -265,8 +319,6 @@ namespace Barotrauma } }; - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), rightColumn.RectTransform), style: null); new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("ChromaticAberration")) { ToolTip = TextManager.Get("ChromaticAberrationToolTip"), @@ -279,9 +331,6 @@ namespace Barotrauma } }; - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: null); - GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("HUDScale")); GUIScrollBar HUDScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), barSize: 0.1f) @@ -300,9 +349,6 @@ namespace Barotrauma }; HUDScaleScrollBar.OnMoved(HUDScaleScrollBar, HUDScaleScrollBar.BarScroll); - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), rightColumn.RectTransform), style: null); - GUITextBlock inventoryScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("InventoryScale")); GUIScrollBar inventoryScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), barSize: 0.1f) { @@ -319,9 +365,6 @@ namespace Barotrauma }; inventoryScaleScrollBar.OnMoved(inventoryScaleScrollBar, inventoryScaleScrollBar.BarScroll); - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), rightColumn.RectTransform), style: null); - /// Audio tab ---------------------------------------------------------------- var audioSliders = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.4f), tabs[(int)Tab.Audio].RectTransform, Anchor.TopCenter) @@ -567,9 +610,10 @@ namespace Barotrauma var inputNames = Enum.GetValues(typeof(InputType)); for (int i = 0; i < inputNames.Length; i++) { - var inputContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), inputFrame.RectTransform), style: null); - new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), inputContainer.RectTransform), TextManager.Get("InputType." + ((InputType)i)) + ": ", font: GUI.SmallFont); - var keyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), inputContainer.RectTransform, Anchor.TopRight), + var inputContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.06f), inputFrame.RectTransform)) { Stretch = true, IsHorizontal = true, RelativeSpacing = 0.05f }; + new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), inputContainer.RectTransform, Anchor.TopLeft) { MinSize = new Point(150, 0) }, + TextManager.Get("InputType." + ((InputType)i)) + ": ", font: GUI.SmallFont) { ForceUpperCase = true }; + var keyBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), inputContainer.RectTransform), text: keyMapping[i].ToString(), font: GUI.SmallFont) { UserData = i @@ -593,88 +637,11 @@ namespace Barotrauma }; aimAssistSlider.OnMoved(aimAssistSlider, aimAssistSlider.BarScroll); - /// General tab -------------------------------------------------------------- - - var generalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), tabs[(int)Tab.General].RectTransform, Anchor.Center) - { RelativeOffset = new Vector2(0.0f, 0.0f) }) { RelativeSpacing = 0.01f, Stretch = true }; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), generalLayoutGroup.RectTransform), TextManager.Get("ContentPackages")); - var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), generalLayoutGroup.RectTransform)) - { - CanBeFocused = false - }; - - - foreach (ContentPackage contentPackage in ContentPackage.List) - { - var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), contentPackageList.Content.RectTransform, minSize: new Point(0, 15)), contentPackage.Name) - { - UserData = contentPackage, - OnSelected = SelectContentPackage, - Selected = SelectedContentPackages.Contains(contentPackage) - }; - if (!contentPackage.IsCompatible()) - { - tickBox.TextColor = Color.Red; - tickBox.Enabled = false; - tickBox.ToolTip = TextManager.Get(contentPackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage") - .Replace("[packagename]", contentPackage.Name) - .Replace("[packageversion]", contentPackage.GameVersion.ToString()) - .Replace("[gameversion]", GameMain.Version.ToString()); - } - else if (contentPackage.CorePackage && !contentPackage.ContainsRequiredCorePackageFiles(out List missingContentTypes)) - { - tickBox.TextColor = Color.Red; - tickBox.Enabled = false; - tickBox.ToolTip = TextManager.Get("ContentPackageMissingCoreFiles") - .Replace("[packagename]", contentPackage.Name) - .Replace("[missingfiletypes]", string.Join(", ", missingContentTypes)); - } - } - GUITextBlock aimAssistText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), controlsLayoutGroup.RectTransform), TextManager.Get("AimAssist")); - GUIScrollBar aimAssistSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), controlsLayoutGroup.RectTransform), - barSize: 0.1f) - { - UserData = aimAssistText, - BarScroll = MathUtils.InverseLerp(0.0f, 5.0f, AimAssistAmount), - OnMoved = (scrollBar, scroll) => - { - ChangeSliderText(scrollBar, scroll); - AimAssistAmount = MathHelper.Lerp(0.0f, 5.0f, scroll); - return true; - }, - Step = 0.1f - }; - aimAssistSlider.OnMoved(aimAssistSlider, aimAssistSlider.BarScroll); - - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null); - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), generalLayoutGroup.RectTransform), TextManager.Get("Language")); - var languageDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), generalLayoutGroup.RectTransform)); - foreach (string language in TextManager.AvailableLanguages) - { - languageDD.AddItem(TextManager.Get("Language." + language), language); - } - languageDD.SelectItem(TextManager.Language); - languageDD.OnSelected = (guiComponent, obj) => - { - string newLanguage = obj as string; - if (newLanguage == Language) return true; - - UnsavedSettings = true; - Language = newLanguage; - - new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage")); - - return true; - }; - //spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null); new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft), - TextManager.Get("Cancel")) + TextManager.Get("Cancel"), style: "GUIButtonLarge") { IgnoreLayoutGroups = true, OnClicked = (x, y) => @@ -690,7 +657,7 @@ namespace Barotrauma }; applyButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonArea.RectTransform, Anchor.BottomRight), - TextManager.Get("ApplySettingsButton")) + TextManager.Get("ApplySettingsButton"), style: "GUIButtonLarge") { IgnoreLayoutGroups = true, Enabled = false @@ -698,7 +665,7 @@ namespace Barotrauma applyButton.OnClicked = ApplyClicked; UnsavedSettings = false; // Reset unsaved settings to false once the UI has been created - SelectTab(Tab.General); + SelectTab(Tab.Graphics); } private void SelectTab(Tab tab) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs index d536e9fe5..8a1858095 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs @@ -93,7 +93,7 @@ namespace Barotrauma.Items.Components public void Draw(SpriteBatch spriteBatch, bool editing) { - Color color = Color.White; + Color color = item.SpriteColor; if (brokenSprite == null) { //broken doors turn black if no broken sprite has been configured @@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components weldSpritePos.Y = -weldSpritePos.Y; weldedSprite.Draw(spriteBatch, - weldSpritePos, Color.White * (stuck / 100.0f), scale: item.Scale); + weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale); } if (openState == 1.0f) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs index c136956f9..cc9829018 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs @@ -175,63 +175,6 @@ namespace Barotrauma.Items.Components } } } - - public void ApplyTo(RectTransform target) - { - if (RelativeOffset.HasValue) - { - target.RelativeOffset = RelativeOffset.Value; - } - else if (AbsoluteOffset.HasValue) - { - target.AbsoluteOffset = AbsoluteOffset.Value; - } - if (RelativeSize.HasValue) - { - target.RelativeSize = RelativeSize.Value; - } - else if (AbsoluteSize.HasValue) - { - target.NonScaledSize = AbsoluteSize.Value; - } - if (Anchor.HasValue) - { - target.Anchor = Anchor.Value; - } - if (Pivot.HasValue) - { - target.Pivot = Pivot.Value; - } - else - { - target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor); - } - target.RecalculateChildren(true, true); - } - } - - public GUIFrame GuiFrame { get; protected set; } - - [Serialize(false, false)] - public bool AllowUIOverlap - { - get; - set; - } - - private ItemComponent linkToUIComponent; - [Serialize("", false)] - public string LinkUIToComponent - { - get; - set; - } - - [Serialize(0, false)] - public int HudPriority - { - get; - private set; } private bool shouldMuffleLooping; @@ -553,7 +496,11 @@ namespace Barotrauma.Items.Components { msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString()); } - Msg = msg; + DisplayMsg = msg; + } + else + { + DisplayMsg = Msg; } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/LevelResource.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/LevelResource.cs index 8a73e7e2e..b7951ce7d 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/LevelResource.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/LevelResource.cs @@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components { public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) { - DeattachTimer = msg.ReadSingle(); + deattachTimer = msg.ReadSingle(); if (deattachTimer >= DeattachDuration) { holdable.DeattachFromWall(); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs index b0ad89489..cd5c74d3e 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs @@ -25,6 +25,12 @@ namespace Barotrauma.Items.Components DrawHUDBack, null); submarineContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.85f), GuiFrame.RectTransform, Anchor.Center), style: null); + new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.85f), GuiFrame.RectTransform, Anchor.Center), + DrawHUDFront, null) + { + CanBeFocused = false + }; + hullInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.13f), GUI.Canvas, minSize: new Point(250, 150)), style: "InnerFrame") { @@ -47,7 +53,7 @@ namespace Barotrauma.Items.Components public override void AddToGUIUpdateList() { base.AddToGUIUpdateList(); - if (hasPower) hullInfoFrame.AddToGUIUpdateList(); + hullInfoFrame.AddToGUIUpdateList(); } public override void OnMapLoaded() @@ -96,6 +102,27 @@ namespace Barotrauma.Items.Components } } + private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container) + { + foreach (GUIComponent child in submarineContainer.Children.First().Children) + { + if (child.UserData is Hull hull) + { + if (hull.Submarine == null || !hull.Submarine.IsOutpost) { continue; } + string text = TextManager.Get("MiniMapOutpostDockingInfo").Replace("[outpost]", hull.Submarine.Name); + Vector2 textSize = GUI.Font.MeasureString(text); + Vector2 textPos = child.Center; + if (textPos.X + textSize.X / 2 > submarineContainer.Rect.Right) + textPos.X -= ((textPos.X + textSize.X / 2) - submarineContainer.Rect.Right) + 10; + if (textPos.X - textSize.X / 2 < submarineContainer.Rect.X) + textPos.X += (submarineContainer.Rect.X - (textPos.X - textSize.X / 2)) + 10; + GUI.DrawString(spriteBatch, textPos - textSize / 2, text, + Color.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)), Color.Black * 0.8f); + break; + } + } + } + private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container) { Hull mouseOnHull = null; @@ -221,7 +248,7 @@ namespace Barotrauma.Items.Components foreach (Submarine sub in subs) { if (sub.HullVertices == null) { continue; } - + Rectangle worldBorders = sub.GetDockedBorders(); worldBorders.Location += sub.WorldPosition.ToPoint(); @@ -242,6 +269,8 @@ namespace Barotrauma.Items.Components GUI.DrawLine(spriteBatch, center + start, center + end, Color.DarkCyan * Rand.Range(0.3f, 0.35f), width: 10); } } + + } private void GetLinkedHulls(Hull hull, List linkedHulls) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs index d0e98ead9..d46b2029a 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs @@ -107,7 +107,10 @@ namespace Barotrauma.Items.Components { var warningBtn = new GUIButton(new RectTransform(new Point(buttonWidth, buttonHeight), columnLeft.RectTransform) { AbsoluteOffset = new Point((i % buttonsPerRow) * (buttonWidth + spacing), (int)Math.Floor(i / (float)buttonsPerRow) * (buttonHeight + spacing)) }, - TextManager.Get(warningTexts[i]), style: "IndicatorButton"); + TextManager.Get(warningTexts[i]), style: "IndicatorButton") + { + CanBeFocused = false + }; var btnText = warningBtn.GetChild(); btnText.Font = GUI.SmallFont; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs index da6fe39d0..a4935fd62 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs @@ -785,8 +785,9 @@ namespace Barotrauma.Items.Components foreach (Limb limb in c.AnimController.Limbs) { - float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared(); + if (!limb.body.Enabled) { continue; } + float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared(); if (limb.SimPosition == Vector2.Zero || pointDist > DisplayRadius * DisplayRadius) continue; if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs index 7fc554b01..6ff7cb7f7 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs @@ -127,6 +127,18 @@ namespace Barotrauma.Items.Components Stretch = true, RelativeSpacing = 0.03f }; + autopilotTickBox = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.3f), paddedControlContainer.RectTransform), + TextManager.Get("SteeringAutoPilot"), style: "GUIRadioButton") + { + OnSelected = (GUITickBox box) => + { + AutoPilot = box.Selected; + if (AutoPilot && MaintainPos) + { + posToMaintain = controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition; + } + unsentChanges = true; + user = Character.Controlled; maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(0.2f, 0.2f), paddedAutoPilotControls.RectTransform), TextManager.Get("SteeringMaintainPos"), font: GUI.SmallFont) @@ -484,26 +496,14 @@ namespace Barotrauma.Items.Components user = Character.Controlled; } } - if (!AutoPilot && Character.DisableControls) + if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null) { steeringAdjustSpeed = character == null ? 0.2f : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f); Vector2 input = Vector2.Zero; - if (PlayerInput.KeyDown(InputType.Left)) - { - input -= Vector2.UnitX; - } - if (PlayerInput.KeyDown(InputType.Right)) - { - input += Vector2.UnitX; - } - if (PlayerInput.KeyDown(InputType.Up)) - { - input += Vector2.UnitY; - } - if (PlayerInput.KeyDown(InputType.Down)) - { - input -= Vector2.UnitY; - } + if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; } + if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; } + if (PlayerInput.KeyDown(InputType.Up)) { input += Vector2.UnitY; } + if (PlayerInput.KeyDown(InputType.Down)) { input -= Vector2.UnitY; } if (PlayerInput.KeyDown(Keys.LeftShift)) { SteeringInput += input * deltaTime * 200; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs index 9b14effac..f8f82df50 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs @@ -1,6 +1,7 @@ using Barotrauma.Particles; using FarseerPhysics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; @@ -11,12 +12,21 @@ using System.Xml.Linq; namespace Barotrauma.Items.Components { partial class RepairTool +#if DEBUG + : IDrawableComponent +#endif { public ParticleEmitter ParticleEmitter { get; private set; } +#if DEBUG + public Vector2 DrawSize + { + get { return GameMain.DebugDraw ? Vector2.One * Range : Vector2.Zero; } + } +#endif private List ParticleEmitterHitStructure = new List(); private List ParticleEmitterHitCharacter = new List(); @@ -122,5 +132,17 @@ namespace Barotrauma.Items.Components emitter.Second.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi); } } +#if DEBUG + public void Draw(SpriteBatch spriteBatch, bool editing) + { + if (GameMain.DebugDraw && IsActive) + { + GUI.DrawLine(spriteBatch, + new Vector2(debugRayStartPos.X, -debugRayStartPos.Y), + new Vector2(debugRayEndPos.X, -debugRayEndPos.Y), + Color.Yellow); + } + } +#endif } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs index b1871cfe0..7e8fdc706 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs @@ -1,4 +1,6 @@ -using Barotrauma.Particles; +using Barotrauma.Networking; +using Barotrauma.Particles; +using Lidgren.Network; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Collections.Generic; @@ -112,8 +114,7 @@ namespace Barotrauma.Items.Components System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts"); foreach (GUIComponent c in GuiFrame.GetChild(0).Children) { - Skill skill = c.UserData as Skill; - if (skill == null) continue; + if (!(c.UserData is Skill skill)) continue; GUITextBlock textBlock = (GUITextBlock)c; if (character.GetSkillLevel(skill.Identifier) < skill.Level) @@ -126,5 +127,15 @@ namespace Barotrauma.Items.Components } } } + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + deteriorationTimer = msg.ReadSingle(); + } + + public void ClientWrite(NetBuffer msg, object[] extraData = null) + { + //no need to write anything, just letting the server know we started repairing + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs index e1d57fa4b..53c247abe 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs @@ -169,20 +169,6 @@ namespace Barotrauma.Items.Components if (draggingConnected.Connect(this, !alreadyConnected, true)) { var otherConnection = draggingConnected.OtherConnection(this); -#if SERVER - //TODO: ffs - if (otherConnection == null) - { - GameServer.Log(Character.Controlled.LogName + " connected a wire to " + - Item.Name + " (" + Name + ")", ServerLog.MessageType.ItemInteraction); - } - else - { - GameServer.Log(Character.Controlled.LogName + " connected a wire from " + - Item.Name + " (" + Name + ") to " + otherConnection.item.Name + " (" + otherConnection.Name + ")", ServerLog.MessageType.ItemInteraction); - } -#endif - SetWire(index, draggingConnected); } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/StatusHUD.cs index 8b6b5ea03..b56dd8629 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/StatusHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/StatusHUD.cs @@ -181,7 +181,7 @@ namespace Barotrauma.Items.Components Dictionary combinedAfflictionStrengths = new Dictionary(); foreach (Affliction affliction in allAfflictions) { - if (affliction.Strength < affliction.Prefab.ActivationThreshold || affliction.Strength <= 0.0f) continue; + if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold || affliction.Strength <= 0.0f) continue; if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab)) { combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs index 4ed31820f..6e42a2949 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs @@ -237,13 +237,13 @@ namespace Barotrauma.Items.Components railSprite?.Draw(spriteBatch, drawPos, - Color.White, + item.SpriteColor, rotation + MathHelper.PiOver2, item.Scale, SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth)); barrelSprite?.Draw(spriteBatch, - drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale, - Color.White, + drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale, + item.SpriteColor, rotation + MathHelper.PiOver2, item.Scale, SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth)); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index f9e043913..7944ddf9d 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -301,7 +301,7 @@ namespace Barotrauma protected virtual void ControlInput(Camera cam) { // Note that these targets are static. Therefore the outcome is the same if this method is called multiple times or only once. - if (selectedSlot != null || draggingItem != null) + if (selectedSlot != null && !DraggingItemToWorld) { cam.Freeze = true; } @@ -609,8 +609,17 @@ namespace Barotrauma if (selectedSlot == null) { - draggingItem.Drop(Character.Controlled); - GUI.PlayUISound(GUISoundType.DropItem); + if (DraggingItemToWorld && + Character.Controlled.FocusedItem?.OwnInventory != null && + Character.Controlled.FocusedItem.OwnInventory.TryPutItem(draggingItem, Character.Controlled)) + { + GUI.PlayUISound(GUISoundType.PickItem); + } + else + { + GUI.PlayUISound(GUISoundType.DropItem); + draggingItem.Drop(Character.Controlled); + } } else if (selectedSlot.ParentInventory.Items[selectedSlot.SlotIndex] != draggingItem) { @@ -702,10 +711,27 @@ namespace Barotrauma int iconSize = (int)(64 * GUI.Scale); float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f); Vector2 itemPos = PlayerInput.MousePosition; + + if (GUI.MouseOn == null && selectedSlot == null) + { + var shadowSprite = GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0]; + string toolTip = Character.Controlled.FocusedItem != null ? + TextManager.Get("PutItemIn").Replace("[itemname]", Character.Controlled.FocusedItem.Name) : + TextManager.Get("DropItem"); + int textWidth = (int)Math.Max(GUI.Font.MeasureString(draggingItem.Name).X, GUI.SmallFont.MeasureString(toolTip).X); + int textSpacing = (int)(15 * GUI.Scale); + Point shadowBorders = (new Point(40, 10)).Multiply(GUI.Scale); + shadowSprite.Draw(spriteBatch, + new Rectangle(itemPos.ToPoint() - new Point(iconSize / 2) - shadowBorders, new Point(iconSize + textWidth + textSpacing, iconSize) + shadowBorders.Multiply(2)), Color.Black * 0.8f); + GUI.DrawString(spriteBatch, new Vector2(itemPos.X + iconSize / 2 + textSpacing, itemPos.Y - iconSize / 2), draggingItem.Name, Color.White); + GUI.DrawString(spriteBatch, new Vector2(itemPos.X + iconSize / 2 + textSpacing, itemPos.Y), toolTip, + color: Character.Controlled.FocusedItem == null ? Color.Red : Color.LightGreen, + font: GUI.SmallFont); + } sprite.Draw(spriteBatch, itemPos + Vector2.One * 2, Color.Black, scale: scale); - sprite.Draw(spriteBatch, - itemPos, - sprite == draggingItem.Sprite ? draggingItem.GetSpriteColor() : draggingItem.GetInventoryIconColor(), + sprite.Draw(spriteBatch, + itemPos, + sprite == draggingItem.Sprite ? draggingItem.GetSpriteColor() : draggingItem.GetInventoryIconColor(), scale: scale); } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/ItemInventory.cs b/Barotrauma/BarotraumaClient/Source/Items/ItemInventory.cs index 8c126470f..8f2aa6359 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/ItemInventory.cs @@ -10,12 +10,6 @@ namespace Barotrauma protected override void ControlInput(Camera cam) { - if (draggingItem == null && HUD.CloseHUD(BackgroundFrame)) - { - // TODO: fix so that works with the server side - Character.Controlled.SelectedConstruction = null; - return; - } base.ControlInput(cam); if (BackgroundFrame.Contains(PlayerInput.MousePosition)) { diff --git a/Barotrauma/BarotraumaClient/Source/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/Source/Items/ItemPrefab.cs index d96f135ca..307e86801 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/ItemPrefab.cs @@ -232,11 +232,11 @@ namespace Barotrauma { if (!ResizeHorizontal && !ResizeVertical) { - sprite.Draw(spriteBatch, new Vector2(placeRect.Center.X, -(placeRect.Y - placeRect.Height / 2)), SpriteColor, scale: Scale * scale); + sprite.Draw(spriteBatch, new Vector2(placeRect.Center.X, -(placeRect.Y - placeRect.Height / 2)), SpriteColor * 0.8f, scale: Scale * scale); } else { - if (sprite != null) sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), null, SpriteColor); + if (sprite != null) sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), null, SpriteColor * 0.8f); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs index f295160c6..8dc7700f0 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs @@ -1,14 +1,13 @@ using Barotrauma.Networking; using Barotrauma.Particles; using Barotrauma.Sounds; +using Lidgren.Network; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Input; using System.Linq; -using Lidgren.Network; namespace Barotrauma { @@ -19,6 +18,8 @@ namespace Barotrauma private List decals = new List(); private float serverUpdateDelay; + private float remoteWaterVolume, remoteOxygenPercentage; + private List remoteFireSources; private bool networkUpdatePending; private float networkUpdateTimer; @@ -139,6 +140,10 @@ namespace Barotrauma partial void UpdateProjSpecific(float deltaTime, Camera cam) { serverUpdateDelay -= deltaTime; + if (serverUpdateDelay <= 0.0f) + { + ApplyRemoteState(); + } if (networkUpdatePending) { @@ -547,18 +552,18 @@ namespace Barotrauma public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime) { - float newWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; - float newOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8); + remoteWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; + remoteOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8); bool hasFireSources = message.ReadBoolean(); int fireSourceCount = 0; - List newFireSources = new List(); + remoteFireSources = new List(); if (hasFireSources) { fireSourceCount = message.ReadRangedInteger(0, 16); for (int i = 0; i < fireSourceCount; i++) { - newFireSources.Add(new Vector3( + remoteFireSources.Add(new Vector3( MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f), MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f), message.ReadRangedSingle(0.0f, 1.0f, 8))); @@ -567,41 +572,6 @@ namespace Barotrauma if (serverUpdateDelay > 0.0f) { return; } - WaterVolume = newWaterVolume; - OxygenPercentage = newOxygenPercentage; - - for (int i = 0; i < fireSourceCount; i++) - { - Vector2 pos = new Vector2( - rect.X + rect.Width * newFireSources[i].X, - rect.Y - rect.Height + (rect.Height * newFireSources[i].Y)); - float size = newFireSources[i].Z * rect.Width; - - var newFire = i < FireSources.Count ? - FireSources[i] : - new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true); - newFire.Position = pos; - newFire.Size = new Vector2(size, newFire.Size.Y); - - //ignore if the fire wasn't added to this room (invalid position)? - if (!FireSources.Contains(newFire)) - { - newFire.Remove(); - continue; - } - } - - for (int i = FireSources.Count - 1; i >= fireSourceCount; i--) - { - FireSources[i].Remove(); - if (i < FireSources.Count) - { - FireSources.RemoveAt(i); - } - } - - if (serverUpdateDelay > 0.0f) { return; } - ApplyRemoteState(); } diff --git a/Barotrauma/BarotraumaClient/Source/Map/Lights/ConvexHull.cs b/Barotrauma/BarotraumaClient/Source/Map/Lights/ConvexHull.cs index e7467fec3..90e8517a7 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Lights/ConvexHull.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Lights/ConvexHull.cs @@ -247,15 +247,11 @@ namespace Barotrauma.Lights public void Rotate(Vector2 origin, float amount) { - Matrix rotationMatrix = Matrix.CreateRotationZ(amount); - - Vector2[] newVerts = new Vector2[vertices.Length]; - for (int i = 0; i < vertices.Length; i++) - { - newVerts[i] = Vector2.Transform(vertices[i].Pos - origin, rotationMatrix) + origin; - } - - SetVertices(newVerts); + Matrix rotationMatrix = + Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) * + Matrix.CreateRotationZ(amount) * + Matrix.CreateTranslation(origin.X, origin.Y, 0.0f); + SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix); } private void CalculateDimensions() diff --git a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs index c61e3821d..f8d12f957 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs @@ -453,7 +453,10 @@ namespace Barotrauma.Lights //raster pattern on top of everything spriteBatch.Begin(blendState: BlendState.AlphaBlend, samplerState: SamplerState.LinearWrap); - spriteBatch.Draw(highlightRaster, new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), Color.White * 0.5f); + spriteBatch.Draw(highlightRaster, + new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), + new Rectangle(0, 0, (int)(HighlightMap.Width / currLightMapScale * 0.5f), (int)(HighlightMap.Height / currLightMapScale * 0.5f)), + Color.White * 0.5f); spriteBatch.End(); DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"]; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/Source/Map/Map/Map.cs index 83b5d1622..4abd51d76 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Map/Map.cs @@ -364,15 +364,23 @@ namespace Barotrauma } } + if (GUI.KeyboardDispatcher.Subscriber == null) + { + float moveSpeed = 1000.0f; + Vector2 moveAmount = Vector2.Zero; + if (PlayerInput.KeyDown(InputType.Left)) { moveAmount += Vector2.UnitX; } + if (PlayerInput.KeyDown(InputType.Right)) { moveAmount -= Vector2.UnitX; } + if (PlayerInput.KeyDown(InputType.Up)) { moveAmount += Vector2.UnitY; } + if (PlayerInput.KeyDown(InputType.Down)) { moveAmount -= Vector2.UnitY; } + drawOffset += moveAmount * moveSpeed / zoom * deltaTime; + } + if (GUI.MouseOn == mapContainer) { zoom += PlayerInput.ScrollWheelSpeed / 1000.0f; zoom = MathHelper.Clamp(zoom, 1.0f, 4.0f); - if (PlayerInput.MidButtonHeld()) - { - drawOffset += PlayerInput.MouseSpeed / zoom; - } + if (PlayerInput.MidButtonHeld()) { drawOffset += PlayerInput.MouseSpeed / zoom; } #if DEBUG if (PlayerInput.DoubleClicked() && highlightedLocation != null) { @@ -551,11 +559,6 @@ namespace Barotrauma null, connectionColor * MathHelper.Clamp(a, 0.1f, 0.5f), MathUtils.VectorToAngle(end - start), new Vector2(0, 16), SpriteEffects.None, 0.01f); } - } - - rect.Inflate(8, 8); - GUI.DrawRectangle(spriteBatch, rect, Color.Black, false, 0.0f, 8); - GUI.DrawRectangle(spriteBatch, rect, Color.LightGray); if (GameMain.DebugDraw && zoom > 1.0f && generationParams.ShowLevelTypeNames) { diff --git a/Barotrauma/BarotraumaClient/Source/Map/MapEntityPrefab.cs b/Barotrauma/BarotraumaClient/Source/Map/MapEntityPrefab.cs index f8ab3faa8..2d2a03e2b 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/MapEntityPrefab.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/MapEntityPrefab.cs @@ -44,7 +44,7 @@ namespace Barotrauma drawRect.Location -= Submarine.MainSub.Position.ToPoint(); } drawRect.Y = -drawRect.Y; - GUI.DrawRectangle(spriteBatch, drawRect, Color.DarkBlue); + GUI.DrawRectangle(spriteBatch, drawRect, Color.White); } public void DrawListLine(SpriteBatch spriteBatch, Vector2 pos, Color color) { diff --git a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs index 02545e1e0..e97d93cac 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs @@ -75,7 +75,10 @@ namespace Barotrauma }; var h = new ConvexHull(verts, Color.Black, this); - h.Rotate(position, rotation); + if (Math.Abs(rotation) > 0.001f) + { + h.Rotate(position, rotation); + } convexHulls.Add(h); } @@ -292,8 +295,7 @@ namespace Barotrauma { if (damageEffect != null) { - float newCutoff = Sections[i].damage > 0 ? - MathHelper.Lerp(0.2f, 0.65f, Sections[i].damage / Prefab.Health) : 0.0f; + float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / Prefab.Health); if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor) { @@ -360,6 +362,7 @@ namespace Barotrauma -Bodies[i].Rotation, Color.White); } } + AiTarget?.Draw(spriteBatch); } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/StructurePrefab.cs b/Barotrauma/BarotraumaClient/Source/Map/StructurePrefab.cs index 5e818059f..4863ea754 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/StructurePrefab.cs @@ -35,7 +35,7 @@ namespace Barotrauma public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f) { // TODO: the scale property is not used - sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), new Vector2(placeRect.Width, placeRect.Height), textureScale: TextureScale * Scale); + sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), new Vector2(placeRect.Width, placeRect.Height), color: Color.White * 0.8f, textureScale: TextureScale * Scale); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 70aa20dfe..fb6837e05 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -141,8 +141,23 @@ namespace Barotrauma.Networking { OnClicked = (btn, userdata) => { - if (!permissions.HasFlag(ClientPermissions.ManageRound)) return false; - RequestRoundEnd(); + if (!permissions.HasFlag(ClientPermissions.ManageRound)) { return false; } + if (!Submarine.MainSub.AtStartPosition && !Submarine.MainSub.AtEndPosition) + { + var msgBox = new GUIMessageBox("", TextManager.Get("EndRoundSubNotAtLevelEnd"), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); + msgBox.Buttons[0].OnClicked = (_, __) => + { + GameMain.Client.RequestRoundEnd(); + return true; + }; + msgBox.Buttons[0].OnClicked += msgBox.Close; + msgBox.Buttons[1].OnClicked += msgBox.Close; + } + else + { + RequestRoundEnd(); + } return true; }, Visible = false @@ -622,7 +637,7 @@ namespace Barotrauma.Networking if (gameStarted) SetRadioButtonColor(); - if (ShowNetStats) + if (ShowNetStats && client?.ServerConnection != null) { netStats.AddValue(NetStats.NetStatType.ReceivedBytes, client.ServerConnection.Statistics.ReceivedBytes); netStats.AddValue(NetStats.NetStatType.SentBytes, client.ServerConnection.Statistics.SentBytes); @@ -1109,7 +1124,7 @@ namespace Barotrauma.Networking { string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ")."; DebugConsole.ThrowError(errorMsg, createMessageBox: true); - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch"+levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); CoroutineManager.StartCoroutine(EndGame("")); yield return CoroutineStatus.Failure; } @@ -1381,9 +1396,6 @@ namespace Barotrauma.Networking case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; - case ServerNetObject.CLIENT_LIST: - ReadClientList(inc); - break; case ServerNetObject.CHAT_MESSAGE: ChatMessage.ClientRead(inc); break; @@ -1439,7 +1451,11 @@ namespace Barotrauma.Networking break; case ServerNetObject.ENTITY_EVENT: case ServerNetObject.ENTITY_EVENT_INITIAL: - if (!entityEventManager.Read(objHeader, inc, sendingTime, entities)) { break; } + if (!entityEventManager.Read(objHeader, inc, sendingTime, entities)) + { + eventReadFailed = true; + break; + } break; case ServerNetObject.CHAT_MESSAGE: ChatMessage.ClientRead(inc); @@ -1703,6 +1719,8 @@ namespace Barotrauma.Networking SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession); campaign.LastSaveID = campaign.PendingSaveID; + + DebugConsole.Log("Campaign save received, save ID " + campaign.LastSaveID); //decrement campaign update ID so the server will send us the latest data //(as there may have been campaign updates after the save file was created) campaign.LastUpdateID--; @@ -1720,7 +1738,7 @@ namespace Barotrauma.Networking public override void CreateEntityEvent(INetSerializable entity, object[] extraData) { - if (!(entity is IClientSerializable)) throw new InvalidCastException("entity is not IClientSerializable"); + if (!(entity is IClientSerializable)) throw new InvalidCastException("Entity is not IClientSerializable"); entityEventManager.CreateEvent(entity as IClientSerializable, extraData); } @@ -1989,7 +2007,8 @@ namespace Barotrauma.Networking msg.Write(true); msg.WritePadBits(); msg.Write(savePath); msg.Write(mapSeed); - msg.Write(sub.FilePath); + msg.Write(sub.Name); + msg.Write(sub.MD5Hash.Hash); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index 51ed62071..b173ae194 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -62,7 +62,7 @@ namespace Barotrauma UpdateSubList(submarines); - // New game right side + // New game right sideon new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), TextManager.Get("SaveName") + ":", textAlignment: Alignment.BottomLeft); saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), string.Empty); @@ -262,11 +262,6 @@ namespace Barotrauma saveFiles = SaveUtil.GetSaveFiles(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); } - saveList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform, Anchor.CenterLeft)) - { - OnSelected = SelectSaveFile - }; - saveList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform, Anchor.CenterLeft)) { OnSelected = SelectSaveFile @@ -274,7 +269,9 @@ namespace Barotrauma foreach (string saveFile in saveFiles) { - XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile); + string fileName = saveFile; + string subName = ""; + string saveTime = ""; var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform), style: "ListBoxElement") { UserData = saveFile @@ -282,25 +279,38 @@ namespace Barotrauma var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), text: Path.GetFileNameWithoutExtension(saveFile)); - if (doc?.Root == null) - { - DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted."); - nameText.Color = Color.Red; - continue; - } - string submarineName = doc.Root.GetAttributeString("submarine", ""); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), - text: submarineName, font: GUI.SmallFont) + if (!isMultiplayer) { - UserData = saveFile + XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile); + if (doc?.Root == null) + { + DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted."); + nameText.Color = Color.Red; + continue; + } + subName = doc.Root.GetAttributeString("submarine", ""); + saveTime = doc.Root.GetAttributeString("savetime", ""); + } + else + { + string[] splitSaveFile = saveFile.Split(';'); + saveFrame.UserData = splitSaveFile[0]; + fileName = nameText.Text = Path.GetFileNameWithoutExtension(splitSaveFile[0]); + if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; } + if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; } + } + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), + text: subName, font: GUI.SmallFont) + { + UserData = fileName }; - string saveTime = doc.Root.GetAttributeString("savetime", ""); new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), text: saveTime, textAlignment: Alignment.Right, font: GUI.SmallFont) { - UserData = saveFile + UserData = fileName }; } @@ -345,13 +355,6 @@ namespace Barotrauma }; } - public void UpdateTutorialSelection() - { - if (isMultiplayer) return; - Tutorial contextualTutorial = Tutorial.Tutorials.Find(t => t is ContextualTutorial); - contextualTutorialBox.Selected = (contextualTutorial != null) ? !GameMain.Config.CompletedTutorialNames.Contains(contextualTutorial.Name) : true; - } - public void UpdateTutorialSelection() { if (isMultiplayer) return; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index 827b8af57..3c322d12d 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs @@ -457,11 +457,14 @@ namespace Barotrauma { IgnoreLayoutGroups = true, OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }, - Enabled = true, - Visible = GameMain.Client == null || - GameMain.Client.HasPermission(Networking.ClientPermissions.ManageRound) || - GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign) + Enabled = true }; + if (GameMain.Client != null) + { + startButton.Visible = !GameMain.Client.GameStarted && + (GameMain.Client.HasPermission(Networking.ClientPermissions.ManageRound) || + GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)); + } } OnLocationSelected?.Invoke(location, connection); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs index 48be1e5a0..8dfbbdd0b 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs @@ -118,6 +118,19 @@ namespace Barotrauma instance = this; } + private void Reset() + { + AnimParams.ForEach(a => a.Reset(true)); + RagdollParams.Reset(true); + RagdollParams.ClearHistory(); + CurrentAnimation.ClearHistory(); + if (!character.Removed) + { + character.Remove(); + } + character = null; + } + public override void Deselect() { base.Deselect(); @@ -128,15 +141,7 @@ namespace Barotrauma isEndlessRunner = false; if (character != null) { - AnimParams.ForEach(a => a.Reset(true)); - RagdollParams.Reset(true); - RagdollParams.ClearHistory(); - CurrentAnimation.ClearHistory(); - if (!character.Removed) - { - character.Remove(); - } - character = null; + Reset(); } GameMain.World.ProcessChanges(); } @@ -393,6 +398,12 @@ namespace Barotrauma } if (!isFreezed) { + if (character.AnimController.Invalid) + { + Reset(); + SpawnCharacter(currentCharacterConfig); + } + Submarine.MainSub.SetPrevTransform(Submarine.MainSub.Position); Submarine.MainSub.Update((float)deltaTime); @@ -1215,7 +1226,7 @@ namespace Barotrauma Cam.Position = character.WorldPosition; } - private bool CreateCharacter(string name, bool isHumanoid, params object[] ragdollConfig) + private bool CreateCharacter(string name, string mainFolder, bool isHumanoid, params object[] ragdollConfig) { var contentPackage = GameMain.Config.SelectedContentPackages.LastOrDefault(); if (contentPackage == null) @@ -1228,23 +1239,22 @@ namespace Barotrauma #if !DEBUG if (vanilla != null && contentPackage == vanilla) { - GUI.AddMessage($"Cannot edit the Vanilla content!", Color.Red, font: GUI.LargeFont); + GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont); return false; } #endif string speciesName = name; - string mainFolder = $"Content/Characters/{speciesName}"; // Config file - string configFilePath = $"{mainFolder}/{speciesName}.xml"; + string configFilePath = Path.Combine(mainFolder, $"{speciesName}.xml").Replace(@"\", @"/"); if (ContentPackage.GetFilesOfType(GameMain.SelectedPackages, ContentType.Character).None(path => path.Contains(speciesName))) { // Create the config file XElement mainElement = new XElement("Character", new XAttribute("name", speciesName), new XAttribute("humanoid", isHumanoid), - new XElement("ragdolls"), - new XElement("animations"), + new XElement("ragdolls", new XAttribute("folder", Path.Combine(mainFolder, $"Ragdolls/").Replace(@"\", @"/"))), + new XElement("animations", new XAttribute("folder", Path.Combine(mainFolder, $"Animations/").Replace(@"\", @"/"))), new XElement("health"), new XElement("ai")); XDocument doc = new XDocument(mainElement); @@ -1259,13 +1269,13 @@ namespace Barotrauma DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path)); } // Ragdoll - string ragdollFolder = RagdollParams.GetDefaultFolder(speciesName); + string ragdollFolder = RagdollParams.GetFolder(speciesName); string ragdollPath = RagdollParams.GetDefaultFile(speciesName); RagdollParams ragdollParams = isHumanoid ? RagdollParams.CreateDefault(ragdollPath, speciesName, ragdollConfig) : RagdollParams.CreateDefault(ragdollPath, speciesName, ragdollConfig) as RagdollParams; // Animations - string animFolder = AnimationParams.GetDefaultFolder(speciesName); + string animFolder = AnimationParams.GetFolder(speciesName); foreach (AnimationType animType in Enum.GetValues(typeof(AnimationType))) { if (animType != AnimationType.NotDefined) @@ -1869,7 +1879,7 @@ namespace Barotrauma #if !DEBUG if (VanillaCharacters != null && VanillaCharacters.Contains(currentCharacterConfig)) { - GUI.AddMessage(GetCharacterEditorTranslation("CantEditVanillaContent"), Color.Red, font: GUI.LargeFont); + GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont); return false; } #endif @@ -1884,7 +1894,7 @@ namespace Barotrauma #if !DEBUG if (VanillaCharacters != null && VanillaCharacters.Contains(currentCharacterConfig)) { - GUI.AddMessage(GetCharacterEditorTranslation("CantEditVanillaContent"), Color.Red, font: GUI.LargeFont); + GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont); return false; } #endif @@ -1956,7 +1966,7 @@ namespace Barotrauma #if !DEBUG if (VanillaCharacters != null && VanillaCharacters.Contains(currentCharacterConfig)) { - GUI.AddMessage(GetCharacterEditorTranslation("CantEditVanillaContent"), Color.Red, font: GUI.LargeFont); + GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont); box.Close(); return false; } @@ -2089,7 +2099,7 @@ namespace Barotrauma #if !DEBUG if (VanillaCharacters != null && VanillaCharacters.Contains(currentCharacterConfig)) { - GUI.AddMessage(GetCharacterEditorTranslation("CantEditVanillaContent"), Color.Red, font: GUI.LargeFont); + GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont); box.Close(); return false; } @@ -3918,7 +3928,7 @@ namespace Barotrauma private void DrawJointLimitWidgets(SpriteBatch spriteBatch, Limb limb, LimbJoint joint, Vector2 drawPos, bool autoFreeze, bool allowPairEditing, float rotationOffset = 0) { - rotationOffset -= MathHelper.ToRadians(RagdollParams.SpritesheetOrientation); + rotationOffset += MathHelper.ToRadians(RagdollParams.SpritesheetOrientation); Color angleColor = joint.UpperLimit - joint.LowerLimit > 0 ? Color.LightGreen * 0.5f : Color.Red; DrawRadialWidget(spriteBatch, drawPos, MathHelper.ToDegrees(joint.UpperLimit), $"joint.jointParams.Name {GetCharacterEditorTranslation("UpperLimit")}", Color.Cyan, angle => { @@ -4652,7 +4662,7 @@ namespace Barotrauma LimbXElements.Values, JointXElements }; - if (CharacterEditorScreen.instance.CreateCharacter(Name, IsHumanoid, ragdollParams)) + if (CharacterEditorScreen.instance.CreateCharacter(Name, Path.GetDirectoryName(XMLPath), IsHumanoid, ragdollParams)) { GUI.AddMessage(GetCharacterEditorTranslation("CharacterCreated").Replace("[name]", Name), Color.Green, font: GUI.Font); } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index c46668705..01fba6b49 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -51,7 +51,7 @@ namespace Barotrauma new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.05f, 0.05f) }, style: "TitleText"); - buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.BottomLeft, pivot: Pivot.BottomLeft) + buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.BottomLeft, pivot: Pivot.BottomLeft) { AbsoluteOffset = new Point(50, 0) }) @@ -61,7 +61,7 @@ namespace Barotrauma }; // === CAMPAIGN - var campaignHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, isHorizontal: true); + var campaignHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, isHorizontal: true); new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), campaignHolder.RectTransform), "MainMenuCampaignIcon") { @@ -107,7 +107,7 @@ namespace Barotrauma }; // === MULTIPLAYER - var multiplayerHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) }, isHorizontal: true); + var multiplayerHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) }, isHorizontal: true); new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), multiplayerHolder.RectTransform), "MainMenuMultiplayerIcon") { @@ -152,7 +152,7 @@ namespace Barotrauma }; // === CUSTOMIZE - var customizeHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.15f, 0.0f) }, isHorizontal: true); + var customizeHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.15f, 0.0f) }, isHorizontal: true); new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), customizeHolder.RectTransform), "MainMenuCustomizeIcon") { @@ -209,7 +209,7 @@ namespace Barotrauma } // === OPTION - var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.5f), parent: buttonsParent.RectTransform), isHorizontal: true); + var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.5f), parent: buttonsParent.RectTransform), isHorizontal: true); new GUIImage(new RectTransform(new Vector2(0.15f, 0.6f), optionHolder.RectTransform), "MainMenuOptionIcon") { @@ -219,7 +219,7 @@ namespace Barotrauma //spacing new GUIFrame(new RectTransform(new Vector2(0.01f, 0.0f), optionHolder.RectTransform), style: null); - var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.55f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.15f) }); + var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.15f) }); var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), parent: optionButtons.RectTransform)) { @@ -241,7 +241,7 @@ namespace Barotrauma //debug button for quickly starting a new round #if DEBUG - new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), buttonsParent.RectTransform, Anchor.TopLeft, Pivot.BottomLeft) { AbsoluteOffset = new Point(0, -40) }, + new GUIButton(new RectTransform(new Vector2(0.8f, 0.1f), buttonsParent.RectTransform, Anchor.TopLeft, Pivot.BottomLeft) { AbsoluteOffset = new Point(0, -40) }, "Quickstart (dev)", style: "GUIButtonLarge", color: Color.Red) { IgnoreLayoutGroups = true, @@ -255,7 +255,7 @@ namespace Barotrauma #endif var minButtonSize = new Point(120, 20); - var maxButtonSize = new Point(240, 40); + var maxButtonSize = new Point(480, 80); /*new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("TutorialButton"), style: "GUIButtonLarge") { @@ -270,16 +270,21 @@ namespace Barotrauma SetupButtons(buttons); buttons.ForEach(b => b.TextBlock.SetTextPos());*/ - var relativeSize = new Vector2(0.5f, 0.5f); + var relativeSize = new Vector2(0.6f, 0.5f); var minSize = new Point(600, 400); - var maxSize = new Point(900, 600); - var anchor = Anchor.Center; - var pivot = Pivot.Center; - menuTabs = new GUIFrame[Enum.GetValues(typeof(Tab)).Length + 1]; + var maxSize = new Point(2000, 1500); + var anchor = Anchor.CenterRight; + var pivot = Pivot.CenterRight; + Vector2 relativeSpacing = new Vector2(0.05f, 0.0f); - menuTabs[(int)Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize)); + menuTabs = new GUIFrame[Enum.GetValues(typeof(Tab)).Length + 1]; + + menuTabs[(int)Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }, + style: null); + + menuTabs[(int)Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }); var paddedNewGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.NewGame].RectTransform, Anchor.Center), style: null); - menuTabs[(int)Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize)); + menuTabs[(int)Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }); var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.LoadGame].RectTransform, Anchor.Center), style: null); campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame, Submarine.SavedSubmarines) @@ -290,13 +295,13 @@ namespace Barotrauma var hostServerScale = new Vector2(0.7f, 1.0f); menuTabs[(int)Tab.HostServer] = new GUIFrame(new RectTransform( - Vector2.Multiply(relativeSize, hostServerScale), GUI.Canvas, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))); + Vector2.Multiply(relativeSize, hostServerScale), GUI.Canvas, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale)) { RelativeOffset = relativeSpacing }); CreateHostServerFields(); //---------------------------------------------------------------------- - menuTabs[(int)Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize)); + menuTabs[(int)Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }); //PLACEHOLDER var tutorialList = new GUIListBox( @@ -377,6 +382,7 @@ namespace Barotrauma return false; } + GameMain.Config.ResetSettingsFrame(); selectedTab = (Tab)obj; switch (selectedTab) @@ -389,8 +395,9 @@ namespace Barotrauma campaignSetupUI.UpdateLoadMenu(); break; case Tab.Settings: - GameMain.Config.ResetSettingsFrame(); - menuTabs[(int)Tab.Settings] = GameMain.Config.SettingsFrame; + menuTabs[(int)Tab.Settings].RectTransform.ClearChildren(); + GameMain.Config.SettingsFrame.RectTransform.Parent = menuTabs[(int)Tab.Settings].RectTransform; + GameMain.Config.SettingsFrame.RectTransform.RelativeSize = Vector2.One; break; case Tab.JoinServer: GameMain.ServerListScreen.Select(); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index e249c088f..a79601b8b 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -233,7 +233,8 @@ namespace Barotrauma levelSeed = value; - backgroundSprite = LocationType.Random(levelSeed)?.GetPortrait(ToolBox.StringToInt(levelSeed)); + int intSeed = ToolBox.StringToInt(levelSeed); + backgroundSprite = LocationType.Random(new MTRandom(intSeed))?.GetPortrait(intSeed); seedBox.Text = levelSeed; //lastUpdateID++; @@ -726,6 +727,12 @@ namespace Barotrauma spectateButton.Visible = GameMain.Client.GameStarted; ReadyToStartBox.Visible = !GameMain.Client.GameStarted; ReadyToStartBox.Selected = false; + if (campaignUI?.StartButton != null) + { + campaignUI.StartButton.Visible = !GameMain.Client.GameStarted && + (GameMain.Client.HasPermission(ClientPermissions.ManageRound) || + GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)); + } GameMain.Client.SetReadyToStart(ReadyToStartBox); } else @@ -847,9 +854,9 @@ namespace Barotrauma if (campaignUI?.StartButton != null) { - campaignUI.StartButton.Visible = - GameMain.Client.HasPermission(ClientPermissions.ManageRound) || - GameMain.Client.HasPermission(ClientPermissions.ManageCampaign); + campaignUI.StartButton.Visible = !GameMain.Client.GameStarted && + (GameMain.Client.HasPermission(ClientPermissions.ManageRound) || + GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)); } } @@ -860,18 +867,21 @@ namespace Barotrauma spectateButton.Enabled = true; } - public void SetCampaignCharacterInfo(CharacterInfo characterInfo) - { - if (CampaignCharacterDiscarded) return; - - campaignCharacterInfo = characterInfo; - if (campaignCharacterInfo != null) + public void SetCampaignCharacterInfo(CharacterInfo newCampaignCharacterInfo) + { + if (newCampaignCharacterInfo != null) { - UpdatePlayerFrame(campaignCharacterInfo, false); + if (CampaignCharacterDiscarded) { return; } + if (campaignCharacterInfo != newCampaignCharacterInfo) + { + campaignCharacterInfo = newCampaignCharacterInfo; + UpdatePlayerFrame(campaignCharacterInfo, false); + } } - else + else if (campaignCharacterInfo != null) { - UpdatePlayerFrame(null, true); + campaignCharacterInfo = null; + UpdatePlayerFrame(campaignCharacterInfo, false); } } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index 7ac15f8d0..ae3a090d2 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -337,7 +337,24 @@ namespace Barotrauma var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.03f), paddedLeftPanel.RectTransform), TextManager.Get("ShowLighting")) { Selected = lightingEnabled, - OnSelected = (GUITickBox obj) => { lightingEnabled = obj.Selected; return true; } + OnSelected = (GUITickBox obj) => + { + lightingEnabled = obj.Selected; + if (lightingEnabled) + { + //turn off lights that are inside containers + foreach (Item item in Item.ItemList) + { + foreach (LightComponent lightComponent in item.GetComponents()) + { + lightComponent.Light.Color = item.Container != null || (item.body != null && !item.body.Enabled) ? + Color.Transparent : + lightComponent.LightColor; + } + } + } + return true; + } }; tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.03f), paddedLeftPanel.RectTransform), TextManager.Get("ShowWalls")) { @@ -564,6 +581,9 @@ namespace Barotrauma MapEntityPrefab.Selected = null; + saveFrame = null; + loadFrame = null; + MapEntity.DeselectAll(); MapEntity.SelectionGroups.Clear(); @@ -766,6 +786,20 @@ namespace Barotrauma savePath = Path.Combine(Submarine.SavePath, savePath); } +#if !DEBUG + var vanilla = GameMain.VanillaContent; + if (vanilla != null) + { + var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine); + string pathToCompare = savePath.Replace(@"\", @"/").ToLowerInvariant(); + if (vanillaSubs.Any(sub => sub.Replace(@"\", @"/").ToLowerInvariant() == pathToCompare)) + { + GUI.AddMessage(TextManager.Get("CannotEditVanillaSubs"), Color.Red, font: GUI.LargeFont); + return false; + } + } +#endif + /*foreach (var contentPackage in GameMain.Config.SelectedContentPackages) { Submarine.MainSub.RequiredContentPackages.Add(contentPackage.Name); @@ -2040,6 +2074,10 @@ namespace Barotrauma dummyCharacter.SelectedConstruction = null; } } + else if (MapEntity.SelectedList.Count == 1) + { + (MapEntity.SelectedList[0] as Item)?.UpdateHUD(cam, dummyCharacter, (float)deltaTime); + } CharacterHUD.Update((float)deltaTime, dummyCharacter, cam); } diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index eb0a60828..8eae69a5b 100644 --- a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.9.6")] -[assembly: AssemblyFileVersion("0.8.9.6")] +[assembly: AssemblyVersion("0.8.9.8")] +[assembly: AssemblyFileVersion("0.8.9.8")] diff --git a/Barotrauma/BarotraumaServer/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/Source/Characters/CharacterInfo.cs index 29f5bade5..8a75d42e0 100644 --- a/Barotrauma/BarotraumaServer/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/Source/Characters/CharacterInfo.cs @@ -20,8 +20,10 @@ namespace Barotrauma if (Job != null) { msg.Write(Job.Prefab.Identifier); + msg.Write((byte)Job.Skills.Count); foreach (Skill skill in Job.Skills) { + msg.Write(skill.Identifier); msg.Write(skill.Level); } } diff --git a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs index 95f9d1e53..d9831f872 100644 --- a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs @@ -15,6 +15,8 @@ namespace Barotrauma private bool networkUpdateSent; + private double LastInputTime; + public float GetPositionUpdateInterval(Client recipient) { if (!Enabled) { return 1000.0f; } @@ -61,6 +63,15 @@ namespace Barotrauma else if (memInput.Count == 0) { AnimController.Frozen = true; + if (Timing.TotalTime > LastInputTime + 0.5) + { + //no inputs have been received in 0.5 seconds, reset input + //(if there's a temporary network hiccup that prevents us from receiving inputs, we assume the inputs haven't changed, + //but if it takes too long, for example due to a client crashing/disconnecting, we don't want to keep the character + //firing a welding tool or whatever else they were doing until the kill disconnect timer kicks in) + prevDequeuedInput = dequeuedInput = + dequeuedInput.HasFlag(InputNetFlags.FacingLeft) ? InputNetFlags.FacingLeft : InputNetFlags.None; + } } else { @@ -190,6 +201,7 @@ namespace Barotrauma networkUpdateID = (ushort)(networkUpdateID - i) }; memInput.Insert(i, newMem); + LastInputTime = Timing.TotalTime; } } diff --git a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs index b94525388..0e1667db4 100644 --- a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -11,11 +11,11 @@ namespace Barotrauma { private List characterData = new List(); - public static void StartNewCampaign(string savePath, string subName, string seed) + public static void StartNewCampaign(string savePath, string subPath, string seed) { if (string.IsNullOrWhiteSpace(savePath)) return; - GameMain.GameSession = new GameSession(new Submarine(subName, ""), savePath, + GameMain.GameSession = new GameSession(new Submarine(subPath, ""), savePath, GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign")); var campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode); campaign.GenerateMap(seed); @@ -251,6 +251,7 @@ namespace Barotrauma } lastSaveID++; + DebugConsole.Log("Campaign saved, save ID " + lastSaveID); } } } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs index 04ca33c0d..7bf36f0f8 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs @@ -1,9 +1,5 @@ using Barotrauma.Networking; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Lidgren.Network; namespace Barotrauma.Items.Components { @@ -14,5 +10,16 @@ namespace Barotrauma.Items.Components //let the clients know the initial deterioration delay item.CreateServerEvent(this); } + + public void ServerRead(ClientNetObject type, NetBuffer msg, Client c) + { + if (c.Character == null) return; + StartRepairing(c.Character); + } + + public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) + { + msg.Write(deteriorationTimer); + } } } diff --git a/Barotrauma/BarotraumaServer/Source/Map/Hull.cs b/Barotrauma/BarotraumaServer/Source/Map/Hull.cs index 907ba6f45..3b10a7a24 100644 --- a/Barotrauma/BarotraumaServer/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaServer/Source/Map/Hull.cs @@ -20,6 +20,16 @@ namespace Barotrauma partial void UpdateProjSpecific(float deltaTime, Camera cam) { if (IdFreed) { return; } + + //don't create updates if all clients are very far from the hull + float hullUpdateDistanceSqr = NetConfig.HullUpdateDistance * NetConfig.HullUpdateDistance; + if (!GameMain.Server.ConnectedClients.Any(c => + c.Character != null && + Vector2.DistanceSquared(c.Character.WorldPosition, WorldPosition) < hullUpdateDistanceSqr)) + { + return; + } + //update client hulls if the amount of water has changed by >10% //or if oxygen percentage has changed by 5% if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f || @@ -31,8 +41,8 @@ namespace Barotrauma GameMain.NetworkMember.CreateEntityEvent(this); lastSentVolume = waterVolume; lastSentOxygen = OxygenPercentage; - sendUpdateTimer = NetworkUpdateInterval; - } + sendUpdateTimer = NetConfig.HullUpdateInterval; + } } } diff --git a/Barotrauma/BarotraumaServer/Source/Networking/Client.cs b/Barotrauma/BarotraumaServer/Source/Networking/Client.cs index 78ff44d7a..28e291e39 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/Client.cs @@ -46,7 +46,7 @@ namespace Barotrauma.Networking //when was a specific entity event last sent to the client // key = event id, value = NetTime.Now when sending - public readonly Dictionary EntityEventLastSent = new Dictionary(); + public readonly Dictionary EntityEventLastSent = new Dictionary(); //when was a position update for a given entity last sent to the client // key = entity id, value = NetTime.Now when sending diff --git a/Barotrauma/BarotraumaServer/Source/Networking/FileTransfer/FileSender.cs b/Barotrauma/BarotraumaServer/Source/Networking/FileTransfer/FileSender.cs index 2303577e9..88ad64ba2 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/FileTransfer/FileSender.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/FileTransfer/FileSender.cs @@ -187,7 +187,7 @@ namespace Barotrauma.Networking transfer.WaitTimer -= deltaTime; if (transfer.WaitTimer > 0.0f) continue; - if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) continue; + if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel)) continue; transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime; @@ -202,15 +202,6 @@ namespace Barotrauma.Networking { message = peer.CreateMessage(); message.Write((byte)ServerPacketHeader.FILE_TRANSFER); - message.Write((byte)FileTransferMessageType.Initiate); - message.Write((byte)transfer.FileType); - message.Write((ushort)chunkLen); - message.Write((ulong)transfer.Data.Length); - message.Write(transfer.FileName); - GameMain.Server.CompressOutgoingMessage(message); - transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel); - - transfer.Status = FileTransferStatus.Sending; //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 diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs index 4ea71904f..d4c20614b 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs @@ -10,6 +10,7 @@ using System.Text; using System.IO.Compression; using System.IO; using Barotrauma.Steam; +using System.Xml.Linq; namespace Barotrauma.Networking { @@ -715,23 +716,25 @@ namespace Barotrauma.Networking { string savePath = inc.ReadString(); string seed = inc.ReadString(); - string subPath = inc.ReadString(); + string subName = inc.ReadString(); + string subHash = inc.ReadString(); - if (!File.Exists(subPath)) + var matchingSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash); + + if (matchingSub == null) { SendDirectChatMessage( - TextManager.Get("CampaignStartFailedSubNotFound").Replace("[subpath]", subPath), + TextManager.Get("CampaignStartFailedSubNotFound").Replace("[subname]", subName), connectedClient, ChatMessageType.MessageBox); } else { - if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.StartNewCampaign(savePath, subPath, seed); + if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.StartNewCampaign(savePath, matchingSub.FilePath, seed); } } else { string saveName = inc.ReadString(); - if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.LoadCampaign(saveName); } break; @@ -811,7 +814,16 @@ namespace Barotrauma.Networking Log(c.Name + " has reported an error: " + errorStr, ServerLog.MessageType.Error); GameAnalyticsManager.AddErrorEventOnce("GameServer.HandleClientError:LevelsDontMatch" + error, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorStr); - KickClient(c, errorStr); + + if (c.Connection == OwnerConnection) + { + SendDirectChatMessage(errorStr, c, ChatMessageType.MessageBox); + EndGame(); + } + else + { + KickClient(c, errorStr); + } } public override void CreateEntityEvent(INetSerializable entity, object[] extraData = null) @@ -1028,7 +1040,7 @@ namespace Barotrauma.Networking if (command == ClientPermissions.ManageRound && inc.PeekBoolean() && GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign) { - if (!mpCampaign.AllowedToEndRound(sender.Character)) + if (!mpCampaign.AllowedToEndRound(sender.Character) && !sender.HasPermission(command)) { return; } @@ -1114,9 +1126,22 @@ namespace Barotrauma.Networking UInt16 modeIndex = inc.ReadUInt16(); if (GameMain.NetLobbyScreen.GameModes[modeIndex].Identifier.ToLowerInvariant() == "multiplayercampaign") { + string[] saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer); + for (int i = 0; i < saveFiles.Length; i++) + { + XDocument doc = SaveUtil.LoadGameSessionDoc(saveFiles[i]); + if (doc?.Root != null) + { + saveFiles[i] = + string.Join(";", + saveFiles[i].Replace(';', ' '), + doc.Root.GetAttributeString("submarine", ""), + doc.Root.GetAttributeString("savetime", "")); + } + } + NetOutgoingMessage msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO); - string[] saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer); msg.Write((UInt16)saveFiles.Count()); foreach (string saveFile in saveFiles) { @@ -1201,6 +1226,7 @@ namespace Barotrauma.Networking ClientWriteLobby(c); if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && + GameMain.NetLobbyScreen.SelectedMode == campaign.Preset && NetIdUtils.IdMoreRecent(campaign.LastSaveID, c.LastRecvCampaignSave)) { //already sent an up-to-date campaign save @@ -1335,10 +1361,6 @@ namespace Barotrauma.Networking WriteClientList(c, outmsg); clientListBytes = outmsg.LengthBytes - clientListBytes; - int eventManagerBytes = outmsg.LengthBytes; - entityEventManager.Write(c, outmsg, out List sentEvents); - eventManagerBytes = outmsg.LengthBytes - eventManagerBytes; - int chatMessageBytes = outmsg.LengthBytes; WriteChatMessages(outmsg, c); chatMessageBytes = outmsg.LengthBytes - chatMessageBytes; @@ -1348,7 +1370,8 @@ namespace Barotrauma.Networking while (!c.NeedsMidRoundSync && c.PendingPositionUpdates.Count > 0) { var entity = c.PendingPositionUpdates.Peek(); - if (entity == null || entity.Removed) + if (entity == null || entity.Removed || + (entity is Item item && item.PositionUpdateInterval == float.PositiveInfinity)) { c.PendingPositionUpdates.Dequeue(); continue; @@ -1387,25 +1410,55 @@ namespace Barotrauma.Networking errorMsg += " Client list size: " + clientListBytes + " bytes\n" + " Chat message size: " + chatMessageBytes + " bytes\n" + - " Event size: " + eventManagerBytes + " bytes\n" + " Position update size: " + positionUpdateBytes + " bytes\n\n"; - - if (sentEvents != null && sentEvents.Count > 0) - { - errorMsg += "Sent events: \n"; - foreach (var entityEvent in sentEvents) - { - errorMsg += " - " + (entityEvent.Entity?.ToString() ?? "null") + "\n"; - } - } - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); } CompressOutgoingMessage(outmsg); - server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable); + + //--------------------------------------------------------------------------- + + for (int i = 0; i < NetConfig.MaxEventPacketsPerUpdate; i++) + { + outmsg = server.CreateMessage(); + outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME); + outmsg.Write((float)NetTime.Now); + + int eventManagerBytes = outmsg.LengthBytes; + entityEventManager.Write(c, outmsg, out List sentEvents); + eventManagerBytes = outmsg.LengthBytes - eventManagerBytes; + + if (sentEvents.Count == 0) + { + break; + } + + outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); + + if (outmsg.LengthBytes > NetPeerConfiguration.MaximumTransmissionUnit) + { + string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + NetPeerConfiguration.MaximumTransmissionUnit + ")\n"; + errorMsg += + " Event size: " + eventManagerBytes + " bytes\n"; + + if (sentEvents != null && sentEvents.Count > 0) + { + errorMsg += "Sent events: \n"; + foreach (var entityEvent in sentEvents) + { + errorMsg += " - " + (entityEvent.Entity?.ToString() ?? "null") + "\n"; + } + } + + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + } + + CompressOutgoingMessage(outmsg); + server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable); + } } private void WriteClientList(Client c, NetOutgoingMessage outmsg) @@ -1492,7 +1545,8 @@ namespace Barotrauma.Networking } var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; - if (campaign != null && NetIdUtils.IdMoreRecent(campaign.LastUpdateID, c.LastRecvCampaignUpdate)) + if (campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode && + NetIdUtils.IdMoreRecent(campaign.LastUpdateID, c.LastRecvCampaignUpdate)) { outmsg.Write(true); outmsg.WritePadBits(); diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs index 630c09a08..2d720f969 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs @@ -164,7 +164,7 @@ namespace Barotrauma.Networking if (connectedClient != null) { Log("Disconnecting client " + connectedClient.Name + " (Steam ID: " + steamID + "). Steam authentication no longer valid (" + status + ").", ServerLog.MessageType.ServerMessage); - KickClient(connectedClient, $"DisconnectMessage.SteamAuthNoLongerValid_[status]={status.ToString()}"); + KickClient(connectedClient, $"DisconnectMessage.SteamAuthNoLongerValid~[status]={status.ToString()}"); } }*/ } @@ -342,7 +342,7 @@ namespace Barotrauma.Networking if (clVersion != GameMain.Version.ToString()) { DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidVersion, - $"DisconnectMessage.InvalidVersion_[version]={GameMain.Version.ToString()}_[clientversion]={clVersion}"); + $"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={clVersion}"); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", ServerLog.MessageType.Error); DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", Color.Red); @@ -368,7 +368,7 @@ namespace Barotrauma.Networking if (missingPackages.Count == 1) { - DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackage_[missingcontentpackage]={GetPackageStr(missingPackages[0])}"); + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}"); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error); return; } @@ -376,7 +376,7 @@ namespace Barotrauma.Networking { List packageStrs = new List(); missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp))); - DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackages_[missingcontentpackages]={string.Join(", ", packageStrs)}"); + DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}"); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error); return; } @@ -399,7 +399,7 @@ namespace Barotrauma.Networking if (incompatiblePackages.Count == 1) { DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage, - $"DisconnectMessage.IncompatibleContentPackage_[incompatiblecontentpackage]={GetPackageStr2(incompatiblePackages[0])}"); + $"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr2(incompatiblePackages[0])}"); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content package " + GetPackageStr2(incompatiblePackages[0]) + ")", ServerLog.MessageType.Error); return; } @@ -408,7 +408,7 @@ namespace Barotrauma.Networking List packageStrs = new List(); incompatiblePackages.ForEach(cp => packageStrs.Add(GetPackageStr2(cp))); DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage, - $"DisconnectMessage.IncompatibleContentPackages_[incompatiblecontentpackages]={string.Join(", ", packageStrs)}"); + $"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}"); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error); return; } @@ -494,13 +494,21 @@ namespace Barotrauma.Networking } else { - newClient.SetPermissions(ClientPermissions.None, new List()); + var defaultPerms = PermissionPreset.List.Find(p => p.Name == "None"); + if (defaultPerms != null) + { + newClient.SetPermissions(defaultPerms.Permissions, defaultPerms.PermittedCommands); + } + else + { + newClient.SetPermissions(ClientPermissions.None, new List()); + } } } private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, DisconnectReason reason, string message) { - inc.SenderConnection.Disconnect(reason.ToString() + "/ " + message); + inc.SenderConnection.Disconnect(reason.ToString() + "/ " + TextManager.GetServerMessage(message)); if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); } if (unauthClient != null) { diff --git a/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs index 83e59c6cc..d7c3f4ea0 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -228,7 +228,7 @@ namespace Barotrauma.Networking GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected old event " + (c.LastRecvEntityEventID + 1).ToString() + " (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago)" + - " Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.ServerMessage); + " Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error); server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncOldEvent"); } ); @@ -242,7 +242,7 @@ namespace Barotrauma.Networking toKick.ForEach(c => { DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red); - GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.ServerMessage); + GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error); server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncRemovedEvent"); }); } @@ -251,7 +251,7 @@ namespace Barotrauma.Networking var timedOutClients = clients.FindAll(c => c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut); foreach (Client timedOutClient in timedOutClients) { - GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.ServerMessage); + GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error); GameMain.Server.DisconnectClient(timedOutClient, "", "ServerMessage.SyncTimeout"); } @@ -305,19 +305,7 @@ namespace Barotrauma.Networking } //too many events for one packet - if (eventsToSync.Count > MaxEventsPerWrite) - { - if (eventsToSync.Count > MaxEventsPerWrite * 3 && !client.NeedsMidRoundSync) - { - Color color = eventsToSync.Count > MaxEventsPerWrite * 20 ? Color.Red : Color.Orange; - if (eventsToSync.Count < MaxEventsPerWrite * 5) { color = Color.Yellow; } - DebugConsole.NewMessage("WARNING: event count very high: " + eventsToSync.Count + "/" + MaxEventsPerWrite, color); - } - - eventsToSync.RemoveRange(MaxEventsPerWrite, eventsToSync.Count - MaxEventsPerWrite); - } - - foreach (NetEntityEvent entityEvent in eventsToSync) + if (eventsToSync.Count > 200) { if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync) { @@ -350,7 +338,7 @@ namespace Barotrauma.Networking msg.Write(client.UnreceivedEntityEventCount); msg.Write(client.FirstNewEventID); - Write(msg, eventsToSync, client); + Write(msg, eventsToSync, out sentEvents, client); } else { @@ -387,10 +375,10 @@ namespace Barotrauma.Networking for (int i = startIndex; i < eventList.Count; i++) { - //find the first event that hasn't been sent in 1.5 * roundtriptime or at all - client.EntityEventLastSent.TryGetValue(eventList[i].ID, out float lastSent); + //find the first event that hasn't been sent in roundtriptime or at all + client.EntityEventLastSent.TryGetValue(eventList[i].ID, out double lastSent); - float minInterval = Math.Max(client.Connection.AverageRoundtripTime * 1.5f, (float)server.UpdateInterval.TotalSeconds * 2); + float minInterval = Math.Max(client.Connection.AverageRoundtripTime, (float)server.UpdateInterval.TotalSeconds * 2); if (lastSent > NetTime.Now - Math.Min(minInterval, 0.5f)) { @@ -416,7 +404,7 @@ namespace Barotrauma.Networking } else { - double midRoundSyncTimeOut = uniqueEvents.Count / MaxEventsPerWrite * server.UpdateInterval.TotalSeconds; + double midRoundSyncTimeOut = uniqueEvents.Count / 100 * server.UpdateInterval.TotalSeconds; midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 10.0f); client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count; diff --git a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs index a9a354d94..ec80a70d9 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs @@ -337,37 +337,56 @@ namespace Barotrauma.Networking continue; } - string permissionsStr = clientElement.GetAttributeString("permissions", ""); ClientPermissions permissions = Networking.ClientPermissions.None; - if (permissionsStr.ToLowerInvariant() == "all") + List permittedCommands = new List(); + + if (clientElement.Attribute("preset") == null) { - foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + string permissionsStr = clientElement.GetAttributeString("permissions", ""); + if (permissionsStr.ToLowerInvariant() == "all") { - permissions |= permission; + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + permissions |= permission; + } + } + else if (!Enum.TryParse(permissionsStr, out permissions)) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + permissionsStr + "\" is not a valid client permission."); + continue; + } + + if (permissions.HasFlag(Networking.ClientPermissions.ConsoleCommands)) + { + foreach (XElement commandElement in clientElement.Elements()) + { + if (commandElement.Name.ToString().ToLowerInvariant() != "command") continue; + + string commandName = commandElement.GetAttributeString("name", ""); + DebugConsole.Command command = DebugConsole.FindCommand(commandName); + if (command == null) + { + DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + commandName + "\" is not a valid console command."); + continue; + } + + permittedCommands.Add(command); + } } } - else if (!Enum.TryParse(permissionsStr, out permissions)) + else { - DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + permissionsStr + "\" is not a valid client permission."); - continue; - } - - List permittedCommands = new List(); - if (permissions.HasFlag(Networking.ClientPermissions.ConsoleCommands)) - { - foreach (XElement commandElement in clientElement.Elements()) + string presetName = clientElement.GetAttributeString("preset", ""); + PermissionPreset preset = PermissionPreset.List.Find(p => p.Name == presetName); + if (preset == null) { - if (commandElement.Name.ToString().ToLowerInvariant() != "command") continue; - - string commandName = commandElement.GetAttributeString("name", ""); - DebugConsole.Command command = DebugConsole.FindCommand(commandName); - if (command == null) - { - DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + commandName + "\" is not a valid console command."); - continue; - } - - permittedCommands.Add(command); + DebugConsole.ThrowError("Failed to restore saved permissions to the client \"" + clientName + "\". Permission preset \"" + presetName + "\" not found."); + return; + } + else + { + permissions = preset.Permissions; + permittedCommands = preset.PermittedCommands.ToList(); } } @@ -440,9 +459,14 @@ namespace Barotrauma.Networking foreach (SavedClientPermission clientPermission in ClientPermissions) { + var matchingPreset = PermissionPreset.List.Find(p => p.MatchesPermissions(clientPermission.Permissions, clientPermission.PermittedCommands)); + if (matchingPreset != null && matchingPreset.Name == "None") + { + continue; + } + XElement clientElement = new XElement("Client", - new XAttribute("name", clientPermission.Name), - new XAttribute("permissions", clientPermission.Permissions.ToString())); + new XAttribute("name", clientPermission.Name)); if (clientPermission.SteamID > 0) { @@ -453,14 +477,21 @@ namespace Barotrauma.Networking clientElement.Add(new XAttribute("ip", clientPermission.IP)); } - if (clientPermission.Permissions.HasFlag(Barotrauma.Networking.ClientPermissions.ConsoleCommands)) + if (matchingPreset == null) { - foreach (DebugConsole.Command command in clientPermission.PermittedCommands) + clientElement.Add(new XAttribute("permissions", clientPermission.Permissions.ToString())); + if (clientPermission.Permissions.HasFlag(Networking.ClientPermissions.ConsoleCommands)) { - clientElement.Add(new XElement("command", new XAttribute("name", command.names[0]))); + foreach (DebugConsole.Command command in clientPermission.PermittedCommands) + { + clientElement.Add(new XElement("command", new XAttribute("name", command.names[0]))); + } } } - + else + { + clientElement.Add(new XAttribute("preset", matchingPreset.Name)); + } doc.Root.Add(clientElement); } diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 4b478cc34..98144c0a5 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -56,6 +56,7 @@ + @@ -70,10 +71,11 @@ - + + diff --git a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml index 49e953433..3187fe94a 100644 --- a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml +++ b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml @@ -13,6 +13,13 @@ + + + + + + + @@ -28,6 +35,8 @@ + + diff --git a/Barotrauma/BarotraumaShared/SharedContent.projitems b/Barotrauma/BarotraumaShared/SharedContent.projitems index 619c6b810..85b8d8b4d 100644 --- a/Barotrauma/BarotraumaShared/SharedContent.projitems +++ b/Barotrauma/BarotraumaShared/SharedContent.projitems @@ -10,7 +10,7 @@ - + @@ -45,12 +45,27 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -342,6 +357,36 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -897,13 +942,13 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -1955,6 +2000,9 @@ PreserveNewest + + PreserveNewest + @@ -2356,7 +2404,7 @@ PreserveNewest - + PreserveNewest @@ -3114,7 +3162,7 @@ PreserveNewest - PreserveNewest + Never PreserveNewest @@ -3134,6 +3182,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs index 84f39ee7a..42f9d6515 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs @@ -115,7 +115,7 @@ namespace Barotrauma SonarLabel = element.GetAttributeString("sonarlabel", ""); } - public AITarget(Entity e) + public AITarget(Entity e, float sightRange = -1, float soundRange = 0) { Entity = e; if (sightRange < 0) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 6702051d9..8ff638c05 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -26,7 +26,6 @@ namespace Barotrauma } } - public class TargetingPriority { public string TargetTag; @@ -71,12 +70,10 @@ namespace Barotrauma private float raycastTimer; - private bool IsCoolDownRunning => attackingLimb != null && attackingLimb.attack.CoolDownTimer > 0; + private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0; private bool aggressiveBoarding; - private LatchOntoAI latchOntoAI; - //a point in a wall which the Character is currently targeting private WallTarget wallTarget; @@ -113,6 +110,11 @@ namespace Barotrauma private readonly float aggressiongreed; private readonly float aggressionhurt; + // TODO: expose? + private readonly float priorityFearIncreasement = 2; + private readonly float memoryFadeTime = 0.5f; + + public LatchOntoAI LatchOntoAI { get; private set; } public bool AttackHumans { @@ -132,11 +134,6 @@ namespace Barotrauma } } - public Limb AttackingLimb - { - get { return attackingLimb; } - } - public float CombatStrength { get { return combatStrength; } @@ -147,7 +144,7 @@ namespace Barotrauma get { //can't enter a submarine when attached to something - return latchOntoAI == null || !latchOntoAI.IsAttached; + return LatchOntoAI == null || !LatchOntoAI.IsAttached; } } @@ -155,11 +152,13 @@ namespace Barotrauma { get { - //can't flip when attached to something - return latchOntoAI == null || !latchOntoAI.IsAttached; + //can't flip when attached to something or when reversing + return !Reverse && (LatchOntoAI == null || !LatchOntoAI.IsAttached); } } + public bool Reverse { get; private set; } + public EnemyAIController(Character c, string file, string seed) : base(c) { targetMemories = new Dictionary(); @@ -207,7 +206,7 @@ namespace Barotrauma switch (subElement.Name.ToString().ToLowerInvariant()) { case "latchonto": - latchOntoAI = new LatchOntoAI(subElement, this); + LatchOntoAI = new LatchOntoAI(subElement, this); break; case "targetpriority": targetingPriorities.Add(subElement.GetAttributeString("tag", "").ToLowerInvariant(), new TargetingPriority(subElement)); @@ -262,8 +261,7 @@ namespace Barotrauma public override void SelectTarget(AITarget target) { SelectedAiTarget = target; - selectedTargetMemory = FindTargetMemory(target); - + selectedTargetMemory = GetTargetMemory(target); targetValue = 100.0f; } @@ -300,8 +298,7 @@ namespace Barotrauma } else { - TargetingPriority targetingPriority = null; - UpdateTargets(Character, out targetingPriority); + UpdateTargets(Character, out TargetingPriority targetingPriority); updateTargetsTimer = UpdateTargetsInterval; if (SelectedAiTarget == null) @@ -318,7 +315,7 @@ namespace Barotrauma } } - latchOntoAI?.Update(this, deltaTime); + LatchOntoAI?.Update(this, deltaTime); if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)) { @@ -408,13 +405,44 @@ namespace Barotrauma State = AIState.Idle; return; } - - Vector2 escapeDir = Vector2.Normalize(SimPosition - SelectedAiTarget.SimPosition); - if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY; - SteeringManager.SteeringManual(deltaTime, escapeDir); - SteeringManager.SteeringWander(); - if (Character.CurrentHull == null) + else if (selectedTargetMemory != null) { + selectedTargetMemory.Priority += deltaTime * priorityFearIncreasement; + } + if (Character.CurrentHull != null) + { + // Seek exit, if inside + if (SteeringManager is IndoorsSteeringManager indoorSteering && escapePoint == Vector2.Zero) + { + foreach (Gap gap in Gap.GapList) + { + if (gap.Submarine != Character.Submarine) { continue; } + if (gap.Open < 1 || gap.IsRoomToRoom) { continue; } + var path = indoorSteering.PathFinder.FindPath(Character.SimPosition, gap.SimPosition); + if (!path.Unreachable) + { + if (escapePoint != Vector2.Zero) + { + // Ignore the gap if it's further away than the previously assigned escape point + if (Vector2.DistanceSquared(Character.SimPosition, gap.SimPosition) > Vector2.DistanceSquared(Character.SimPosition, escapePoint)) { continue; } + } + escapePoint = gap.SimPosition; + } + } + } + } + if (escapePoint != Vector2.Zero && Vector2.DistanceSquared(Character.SimPosition, escapePoint) > 1) + { + SteeringManager.SteeringSeek(escapePoint); + } + else + { + // If outside or near enough the escapePoint, steer away + escapePoint = Vector2.Zero; + Vector2 escapeDir = Vector2.Normalize(WorldPosition - SelectedAiTarget.WorldPosition); + if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY; + SteeringManager.SteeringManual(deltaTime, escapeDir); + SteeringManager.SteeringWander(); SteeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f); } } @@ -431,9 +459,8 @@ namespace Barotrauma return; } - selectedTargetMemory.Priority -= deltaTime * 0.1f; - - Vector2 attackPos = SelectedAiTarget.WorldPosition; + Vector2 attackWorldPos = SelectedAiTarget.WorldPosition; + Vector2 attackSimPos = SelectedAiTarget.SimPosition; if (SelectedAiTarget.Entity is Item item) { @@ -455,19 +482,14 @@ namespace Barotrauma } else { - UpdateWallTarget(); + if (!IsProperlyLatched) + { + UpdateWallTarget(); + } raycastTimer = RaycastInterval; } - if (wallTarget != null) - { - attackPos = wallTarget.Position; - if (Character.Submarine == null && wallTarget.Structure.Submarine != null) - { - attackPos += wallTarget.Structure.Submarine.Position; - } - } - else if (SelectedAiTarget.Entity is Character c) + if (SelectedAiTarget.Entity is Character c) { //target the closest limb if the target is a character float closestDist = Vector2.DistanceSquared(SelectedAiTarget.WorldPosition, WorldPosition) * 10.0f; @@ -478,14 +500,37 @@ namespace Barotrauma if (dist < closestDist) { closestDist = dist; - attackPos = limb.WorldPosition; + attackWorldPos = limb.WorldPosition; + attackSimPos = limb.SimPosition; } } } if (wallTarget != null) { - Character.AnimController.TargetDir = Character.WorldPosition.X < attackPos.X ? Direction.Right : Direction.Left; + attackWorldPos = wallTarget.Position; + if (wallTarget.Structure.Submarine != null) + { + attackWorldPos += wallTarget.Structure.Submarine.Position; + } + attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos); + } + else + { + // Take the sub position into account in the sim pos + if (Character.Submarine == null && SelectedAiTarget.Entity.Submarine != null) + { + attackSimPos += SelectedAiTarget.Entity.Submarine.SimPosition; + } + else if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine == null) + { + attackSimPos -= Character.Submarine.SimPosition; + } + } + + if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater) + { + Character.AnimController.TargetDir = Character.WorldPosition.X < attackWorldPos.X ? Direction.Right : Direction.Left; } if (aggressiveBoarding) @@ -494,32 +539,26 @@ namespace Barotrauma if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex)) { WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex); - Hull targetHull = section.gap?.FlowTargetHull; - if (targetHull != null && !section.gap.IsRoomToRoom) + Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true); + if (section?.gap != null && section.gap.IsRoomToRoom && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime)) { - Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true); - if (wallTarget.Structure.IsHorizontal) - { - targetPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2; - } - else - { - targetPos.X = targetHull.WorldRect.Center.X; - } - - latchOntoAI?.DeattachFromBody(); - Character.AnimController.ReleaseStuckLimbs(); - if (steeringManager is IndoorsSteeringManager) - { - steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetPos - Character.WorldPosition)); - } - else - { - steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetPos)); - } return; } } + else if (SelectedAiTarget.Entity is Structure wall) + { + for (int i = 0; i < wall.Sections.Length; i++) + { + WallSection section = wall.Sections[i]; + if (CanPassThroughHole(wall, i) && section?.gap != null) + { + if (SteerThroughGap(wall, section, section.gap.WorldPosition, deltaTime)) + { + return; + } + } + } + } else if (SelectedAiTarget.Entity is Item i) { var door = i.GetComponent(); @@ -532,6 +571,8 @@ namespace Barotrauma if (Character.WorldPosition.Y < door.Item.WorldRect.Y && Character.WorldPosition.Y > door.Item.WorldRect.Y - door.Item.Rect.Height) { velocity.Y = 0; + LatchOntoAI?.DeattachFromBody(); + Character.AnimController.ReleaseStuckLimbs(); steeringManager.SteeringManual(deltaTime, velocity); return; } @@ -541,6 +582,8 @@ namespace Barotrauma if (Character.WorldPosition.X < door.Item.WorldRect.X && Character.WorldPosition.X > door.Item.WorldRect.Right) { velocity.X = 0; + LatchOntoAI?.DeattachFromBody(); + Character.AnimController.ReleaseStuckLimbs(); steeringManager.SteeringManual(deltaTime, velocity); return; } @@ -552,58 +595,58 @@ namespace Barotrauma bool canAttack = true; if (IsCoolDownRunning) { - switch (attackingLimb.attack.AfterAttack) + switch (AttackingLimb.attack.AfterAttack) { case AIBehaviorAfterAttack.Pursue: case AIBehaviorAfterAttack.PursueIfCanAttack: - if (attackingLimb.attack.SecondaryCoolDown <= 0) + if (AttackingLimb.attack.SecondaryCoolDown <= 0) { // No (valid) secondary cooldown defined. - if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue) + if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue) { canAttack = false; } else { - UpdateFallBack(attackPos, deltaTime); + UpdateFallBack(attackWorldPos, deltaTime); return; } } else { - if (attackingLimb.attack.SecondaryCoolDownTimer <= 0) + if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0) { // Don't allow attacking when the attack target has just changed. if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget) { canAttack = false; - if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack) + if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack) { // Fall back if cannot attack. - UpdateFallBack(attackPos, deltaTime); + UpdateFallBack(attackWorldPos, deltaTime); return; } - attackingLimb = null; + AttackingLimb = null; } else { // If the secondary cooldown is defined and expired, check if we can switch the attack - var previousLimb = attackingLimb; - var newLimb = GetAttackLimb(attackPos, previousLimb); + var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb); if (newLimb != null) { - attackingLimb = newLimb; + // Attack with the new limb + AttackingLimb = newLimb; } else { // No new limb was found. - if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue) + if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue) { canAttack = false; } else { - UpdateFallBack(attackPos, deltaTime); + UpdateFallBack(attackWorldPos, deltaTime); return; } } @@ -616,44 +659,106 @@ namespace Barotrauma } } break; + case AIBehaviorAfterAttack.FallBackUntilCanAttack: + if (AttackingLimb.attack.SecondaryCoolDown <= 0) + { + // No (valid) secondary cooldown defined. + UpdateFallBack(attackWorldPos, deltaTime); + return; + } + else + { + if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0) + { + // Don't allow attacking when the attack target has just changed. + if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget) + { + UpdateFallBack(attackWorldPos, deltaTime); + return; + } + else + { + // If the secondary cooldown is defined and expired, check if we can switch the attack + var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb); + if (newLimb != null) + { + // Attack with the new limb + AttackingLimb = newLimb; + } + else + { + // No new limb was found. + UpdateFallBack(attackWorldPos, deltaTime); + return; + } + } + } + else + { + // Cooldown not yet expired -> steer away from the target + UpdateFallBack(attackWorldPos, deltaTime); + return; + } + } + break; case AIBehaviorAfterAttack.FallBack: default: - UpdateFallBack(attackPos, deltaTime); + UpdateFallBack(attackWorldPos, deltaTime); return; } } - if (attackingLimb == null) + if (AttackingLimb == null || _previousAiTarget != SelectedAiTarget) { - attackingLimb = GetAttackLimb(attackPos); + AttackingLimb = GetAttackLimb(attackWorldPos); } if (canAttack) { - canAttack = attackingLimb != null && attackingLimb.attack.CoolDownTimer <= 0; + canAttack = AttackingLimb != null && AttackingLimb.attack.CoolDownTimer <= 0; } float distance = 0; if (canAttack) { // Check that we can reach the target - distance = Vector2.Distance(attackingLimb.WorldPosition, attackPos); - canAttack = distance < attackingLimb.attack.Range; + distance = Vector2.Distance(AttackingLimb.WorldPosition, attackWorldPos); + canAttack = distance < AttackingLimb.attack.Range; + if (!canAttack && !IsCoolDownRunning) + { + // If not, reset the attacking limb, if the cooldown is not running + AttackingLimb = null; + } } - Limb steeringLimb = Character.AnimController.MainLimb; + // If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target. For example the Hammerhead steering with the claws when it should use the torso. + // If we always use the main limb, this causes the character to seek the target with it's torso/head, when it should not. For example Mudraptor steering with it's belly, when it should use it's head. + // So let's use the one that's closer to the attacking limb. + Limb steeringLimb; + var torso = Character.AnimController.GetLimb(LimbType.Torso); + var head = Character.AnimController.GetLimb(LimbType.Head); + if (AttackingLimb == null) + { + steeringLimb = head ?? torso; + } + else + { + if (head != null && torso != null) + { + steeringLimb = Vector2.DistanceSquared(AttackingLimb.SimPosition, head.SimPosition) < Vector2.DistanceSquared(AttackingLimb.SimPosition, torso.SimPosition) ? head : torso; + } + else + { + steeringLimb = head ?? torso; + } + } if (steeringLimb != null) { - Vector2 toTarget = Vector2.Normalize(attackPos - steeringLimb.WorldPosition); - Vector2 targetingVector = toTarget * attackingLimb.attack.Range; - // Offset the position a bit so that we don't overshoot the movement. - Vector2 steerPos = attackPos + targetingVector; - steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(attackPos), 10); - if (Character.CurrentHull == null) - { - SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f); - } + Vector2 offset = Character.SimPosition - steeringLimb.SimPosition; + // Offset so that we don't overshoot the movement + Vector2 steerPos = attackSimPos + offset; + SteeringManager.SteeringSeek(steerPos, 10); - if (steeringManager is IndoorsSteeringManager indoorsSteering) + if (SteeringManager is IndoorsSteeringManager indoorsSteering) { if (indoorsSteering.CurrentPath != null && !indoorsSteering.IsPathDirty) { @@ -668,7 +773,7 @@ namespace Barotrauma } else if (indoorsSteering.CurrentPath.Finished) { - steeringManager.SteeringManual(deltaTime, toTarget); + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition)); } else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null) { @@ -682,50 +787,97 @@ namespace Barotrauma } } } + else if (Character.CurrentHull == null) + { + SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f); + } } if (canAttack) { - UpdateLimbAttack(deltaTime, attackingLimb, ConvertUnits.ToSimUnits(attackPos), distance); + UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance); } } + private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime) + { + Hull targetHull = section.gap?.FlowTargetHull; + if (targetHull != null) + { + if (wall.IsHorizontal) + { + targetWorldPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2; + } + else + { + targetWorldPos.X = targetHull.WorldRect.Center.X; + } + LatchOntoAI?.DeattachFromBody(); + Character.AnimController.ReleaseStuckLimbs(); + if (steeringManager is IndoorsSteeringManager) + { + steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition)); + } + else + { + steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetWorldPos)); + } + return true; + } + return false; + } + private Limb GetAttackLimb(Vector2 attackWorldPos, Limb ignoredLimb = null) { AttackContext currentContext = Character.GetAttackContext(); var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity; - var limbs = Character.AnimController.Limbs - .Where(l => - l != ignoredLimb && - l.attack != null && - !l.IsSevered && - !l.IsStuck && - l.attack.IsValidContext(currentContext) && - l.attack.IsValidTarget(target) && - l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character))) - .OrderByDescending(l => l.attack.Priority) - .ThenBy(l => Vector2.Distance(l.WorldPosition, attackWorldPos)); - // TODO: priority should probably not override the distance -> use values instead of booleans - return limbs.FirstOrDefault(); + Limb selectedLimb = null; + float currentPriority = 0; + foreach (Limb limb in Character.AnimController.Limbs) + { + if (limb == ignoredLimb) { continue; } + if (limb.IsSevered || limb.IsStuck) { continue; } + var attack = limb.attack; + if (attack == null) { continue; } + if (attack.CoolDownTimer > 0) { continue; } + if (!attack.IsValidContext(currentContext)) { continue; } + if (!attack.IsValidTarget(target)) { continue; } + if (target is ISerializableEntity se && target is Character) + { + // TODO: allow conditionals of which matching any is enough instead of having to fulfill all + if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; } + } + float priority = CalculatePriority(limb, attackWorldPos); + if (priority > currentPriority) + { + currentPriority = priority; + selectedLimb = limb; + } + } + return selectedLimb; + + float CalculatePriority(Limb limb, Vector2 attackPos) + { + float dist = Vector2.Distance(limb.WorldPosition, attackPos); + // The limb is ignored if the target is not close. Prevents character going in reverse if very far away from it. + // We also need a max value that is more than the actual range. + float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, limb.attack.Range * 3, dist)); + return (1 + limb.attack.Priority) * distanceFactor; + } } private void UpdateWallTarget() { wallTarget = null; - if (Character.AnimController.CurrentHull != null) - { - return; - } - //check if there's a wall between the target and the Character - Vector2 rayStart = Character.SimPosition; + Vector2 rayStart = SimPosition; Vector2 rayEnd = SelectedAiTarget.SimPosition; bool offset = SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null; if (offset) { - rayStart -= ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position); + rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition; } Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true); @@ -745,13 +897,15 @@ namespace Barotrauma { if (wall.SectionBodyDisabled(i)) { - if (aggressiveBoarding && CanPassThroughHole(wall, i)) //aggressive boarders always target holes they can pass through + if (aggressiveBoarding && CanPassThroughHole(wall, i)) { + //aggressive boarders always target holes they can pass through sectionIndex = i; break; } - else //otherwise ignore and keep breaking other sections + else { + //otherwise ignore and keep breaking other sections continue; } } @@ -762,17 +916,16 @@ namespace Barotrauma Vector2 attachTargetNormal; if (wall.IsHorizontal) { - attachTargetNormal = new Vector2(0.0f, Math.Sign(Character.WorldPosition.Y - wall.WorldPosition.Y)); + attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y)); sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y; } else { - attachTargetNormal = new Vector2(Math.Sign(Character.WorldPosition.X - wall.WorldPosition.X), 0.0f); + attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f); sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X; } - + LatchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal); wallTarget = new WallTarget(sectionPos, wall, sectionIndex); - latchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal); } } @@ -789,11 +942,11 @@ namespace Barotrauma } } - latchOntoAI?.DeattachFromBody(); + LatchOntoAI?.DeattachFromBody(); Character.AnimController.ReleaseStuckLimbs(); if (attacker == null || attacker.AiTarget == null) return; - AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget); + AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget); targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt; // Reduce the cooldown so that the character can react @@ -815,23 +968,37 @@ namespace Barotrauma private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackSimPos, float distance = -1) { - var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable; - if (damageTarget == null) return; - - float prevHealth = damageTarget.Health; - if (limb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance)) + if (SelectedAiTarget == null) { return; } + if (wallTarget != null) { - if (damageTarget.Health > 0) + // If the selected target is not the wall target, make the wall target the selected target. + var aiTarget = wallTarget.Structure.AiTarget; + if (aiTarget != null && SelectedAiTarget != aiTarget) { - // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon - selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed; + SelectTarget(aiTarget); + } + } + if (SelectedAiTarget.Entity is IDamageable damageTarget) + { + float prevHealth = damageTarget.Health; + if (limb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance)) + { + if (damageTarget.Health > 0) + { + // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon + selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed; + } + else + { + selectedTargetMemory.Priority = 0; + } } } } - private void UpdateFallBack(Vector2 attackPosition, float deltaTime) + private void UpdateFallBack(Vector2 attackWorldPos, float deltaTime) { - Vector2 attackVector = attackPosition - Character.WorldPosition; + Vector2 attackVector = attackWorldPos - WorldPosition; float dist = attackVector.Length(); float desiredDist = colliderSize * 2.0f; if (dist < desiredDist) @@ -890,15 +1057,20 @@ namespace Barotrauma //goes through all the AItargets, evaluates how preferable it is to attack the target, //whether the Character can see/hear the target and chooses the most preferable target within //sight/hearing range - public void UpdateTargets(Character character, out TargetingPriority targetingPriority) + public AITarget UpdateTargets(Character character, out TargetingPriority priority) { + if (IsProperlyLatched) + { + // If attached to a valid target, just keep the target. + // Priority not used in this case. + priority = null; + return SelectedAiTarget; + } AITarget newTarget = null; - targetingPriority = null; + priority = null; selectedTargetMemory = null; targetValue = 0.0f; - UpdateTargetMemories(); - foreach (AITarget target in AITarget.List) { if (!target.Enabled) continue; @@ -907,26 +1079,63 @@ namespace Barotrauma continue; } - float valueModifier = 1.0f; - float dist = 0.0f; - Character targetCharacter = target.Entity as Character; //ignore the aitarget if it is the Character itself if (targetCharacter == character) continue; + float valueModifier = 1; string targetingTag = null; if (targetCharacter != null) { if (targetCharacter.Submarine != null && Character.Submarine == null) + { + targetingTag = "dead"; + if (targetCharacter.Submarine != Character.Submarine) + { + // In a different sub or the target is outside when we are inside or vice versa -> Ignore the target + continue; + } + else if (targetCharacter.CurrentHull != Character.CurrentHull) + { + // In the same sub, halve the priority, if not in the same hull. + valueModifier = 0.5f; + } + } + else if (targetCharacter.AIController is EnemyAIController enemy) + { + if (enemy.combatStrength > combatStrength) + { + targetingTag = "stronger"; + } + else if (enemy.combatStrength < combatStrength) + { + targetingTag = "weaker"; + } + if (State == AIState.Escape && targetingTag == "stronger") + { + // Frightened + valueModifier = 2; + } + else + { + if (targetCharacter.Submarine != Character.Submarine) + { + // In a different sub or the target is outside when we are inside or vice versa -> Ignore the target + continue; + } + else if (targetCharacter.CurrentHull != Character.CurrentHull) + { + // In the same sub, halve the priority, if not in the same hull. + valueModifier = 0.5f; + } + } + } + else if (targetCharacter.Submarine != null && Character.Submarine == null) { //target inside, AI outside -> we'll be attacking a wall between the characters so use the priority for attacking rooms targetingTag = "room"; } - else if (targetCharacter.IsDead) - { - targetingTag = "dead"; - } else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant())) { targetingTag = targetCharacter.SpeciesName.ToLowerInvariant(); @@ -937,42 +1146,6 @@ namespace Barotrauma //skip the target if it's a room and the character is already inside a sub if (character.CurrentHull != null && target.Entity is Hull) continue; - Door door = null; - if (target.Entity is Item item) - { - if (targetCharacter.AIController is EnemyAIController enemy) - { - targetingTag = "room"; - } - - door = item.GetComponent(); - foreach (TargetingPriority prio in targetingPriorities.Values) - { - if (item.HasTag(prio.TargetTag)) - { - targetingTag = "stronger"; - } - } - } - else if (target.Entity is Structure s) - { - targetingTag = "wall"; - if (aggressiveBoarding) - { - // Ignore walls when inside. - valueModifier = character.CurrentHull == null ? 2 : 0; - if (valueModifier > 0) - { - targetingTag = "weaker"; - } - } - } - } - else if (target.Entity != null) - { - //skip the target if it's a room and the character is already inside a sub - if (character.CurrentHull != null && target.Entity is Hull) continue; - Door door = null; if (target.Entity is Item item) { @@ -992,19 +1165,72 @@ namespace Barotrauma } } } + else if (target.Entity is Structure s) + { + targetingTag = "wall"; + if (aggressiveBoarding) + { + // Ignore walls when inside. + valueModifier = character.CurrentHull == null ? 2 : 0; + if (valueModifier > 0) + { + // Ignore structures that doesn't have a body (not walls) + valueModifier *= s.HasBody ? 1 : 0; + } + for (int i = 0; i < s.Sections.Length; i++) + { + var section = s.Sections[i]; + if (CanPassThroughHole(s, i)) + { + // Ignore walls that can be passed through + valueModifier = 0; + break; + } + else if (section.gap != null) + { + // up to 100% priority increase for every gap in the wall + valueModifier *= 1 + section.gap.Open; + } + } + } + } else { targetingTag = "room"; } - if (door != null) { - //increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside - if (character.CurrentHull == null && aggressiveBoarding && !door.LinkedGap.IsRoomToRoom) + // If there's not a more specific tag for the door + if (string.IsNullOrEmpty(targetingTag) || targetingTag == "room") { - valueModifier = door.IsOpen ? 10 : 5; + targetingTag = "door"; } - else if (door.IsOpen || door.Item.Condition <= 0.0f) //ignore broken and open doors + bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom; + bool isOpen = door.IsOpen || door.Item.Condition <= 0.0f; + //increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside + if (aggressiveBoarding) + { + if (character.CurrentHull == null) + { + valueModifier = isOutdoor ? 1 : 0; + valueModifier *= isOpen ? 5 : 1; + } + } + } + else if (target.Entity is Structure s) + { + targetingTag = "wall"; + if (aggressiveBoarding) + { + // Ignore walls when inside. + valueModifier = character.CurrentHull == null ? 2 : 0; + if (valueModifier > 0) + { + valueModifier = isOutdoor ? 0 : 1; + valueModifier *= isOpen ? 0 : 1; + } + } + else if (isOpen) //ignore broken and open doors { continue; } @@ -1020,10 +1246,15 @@ namespace Barotrauma valueModifier *= targetingPriorities[targetingTag].Priority; + if (targetingTag == null) continue; + if (!targetingPriorities.ContainsKey(targetingTag)) continue; + + valueModifier *= targetingPriorities[targetingTag].Priority; + if (valueModifier == 0.0f) continue; Vector2 toTarget = target.WorldPosition - character.WorldPosition; - dist = toTarget.Length(); + float dist = toTarget.Length(); //if the target has been within range earlier, the character will notice it more easily //(i.e. remember where the target was) @@ -1037,30 +1268,29 @@ namespace Barotrauma // -> just ignore the distance and attack whatever has the highest priority dist = Math.Max(dist, 100.0f); - AITargetMemory targetMemory = FindTargetMemory(target); + AITargetMemory targetMemory = GetTargetMemory(target); if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y) { // Inside the sub, treat objects that are up or down, as they were farther away. dist *= 3; } - valueModifier = valueModifier * targetMemory.Priority / (float)Math.Sqrt(dist); + valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist); if (valueModifier > targetValue) { newTarget = target; selectedTargetMemory = targetMemory; - targetingPriority = targetingPriorities[targetingTag]; + priority = targetingPriorities[targetingTag]; targetValue = valueModifier; } } SelectedAiTarget = newTarget; - if (SelectedAiTarget != _previousAiTarget) { wallTarget = null; } - _previousAiTarget = SelectedAiTarget; + return SelectedAiTarget; } private AITargetMemory GetTargetMemory(AITarget target) @@ -1070,24 +1300,21 @@ namespace Barotrauma memory = new AITargetMemory(10); targetMemories.Add(target, memory); } - - memory = new AITargetMemory(10.0f); - targetMemories.Add(target, memory); - return memory; } private List removals = new List(); private void UpdateTargetMemories(float deltaTime) { - List toBeRemoved = null; - foreach (KeyValuePair memory in targetMemories) + removals.Clear(); + foreach (var memory in targetMemories) { - memory.Value.Priority += 0.1f; - if (Math.Abs(memory.Value.Priority) < 1.0f || !AITarget.List.Contains(memory.Key)) + // Slowly decrease all memories + memory.Value.Priority -= memoryFadeTime * deltaTime; + // Remove targets that have no priority or have been removed + if (memory.Value.Priority <= 1 || !AITarget.List.Contains(memory.Key)) { - if (toBeRemoved == null) toBeRemoved = new List(); - toBeRemoved.Add(memory.Key); + removals.Add(memory.Key); } } removals.ForEach(r => targetMemories.Remove(r)); @@ -1104,25 +1331,9 @@ namespace Barotrauma AttackingLimb = null; } - if (toBeRemoved != null) - { - foreach (AITarget target in toBeRemoved) - { - targetMemories.Remove(target); - } - } - - #endregion - - protected override void OnStateChanged(AIState from, AIState to) - { - latchOntoAI?.DeattachFromBody(); - Character.AnimController.ReleaseStuckLimbs(); - } - private int GetMinimumPassableHoleCount() { - return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize); + return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize); } private bool CanPassThroughHole(Structure wall, int sectionIndex) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs index 200e20a5a..fe5304a4c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs @@ -193,7 +193,7 @@ namespace Barotrauma // is not attached or is attached to something else if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody) { - if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.Range * enemyAI.AttackingLimb.attack.Range) + if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange) { AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, transformedAttachPos); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index f3afac468..12c490f57 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -115,7 +115,8 @@ namespace Barotrauma unreachable.Add(goToObjective.Target as Hull); } goToObjective = null; - SteeringManager.SteeringWander(); + HumanAIController.ObjectiveManager.GetObjective().Wander(deltaTime); + //SteeringManager.SteeringWander(); } } else if (currentHull != null) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 7f20924c1..d0c519da9 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -99,7 +99,8 @@ namespace Barotrauma FindTargetItem(); if (targetItem == null || moveToTarget == null) { - SteeringManager.SteeringWander(); + HumanAIController.ObjectiveManager.GetObjective().Wander(deltaTime); + //SteeringManager.SteeringWander(); return; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index bba2a53e5..378a71008 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -98,63 +98,67 @@ namespace Barotrauma PathSteering.Reset(); return; } - if (standStillTimer < -walkDuration) { standStillTimer = Rand.Range(standStillMin, standStillMax); } - - //steer away from edges of the hull - if (character.AnimController.CurrentHull != null && !character.IsClimbing) - { - standStillTimer = Rand.Range(standStillMin, standStillMax); - } Wander(deltaTime); return; } - if (leftDist < WallAvoidDistance && rightDist < WallAvoidDistance) - { - if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2) - { - PathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist)); - } - else - { - PathSteering.Reset(); - return; - } - } - else if (leftDist < WallAvoidDistance) - { - PathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance-leftDist) / WallAvoidDistance); - PathSteering.WanderAngle = 0.0f; - return; - } - else - { - PathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance-rightDist) / WallAvoidDistance); - PathSteering.WanderAngle = MathHelper.Pi; - return; - } - } - - character.AIController.SteeringManager.SteeringWander(); - if (!character.IsClimbing && !character.AnimController.InWater) - { - //reset vertical steering to prevent dropping down from platforms etc - character.AIController.SteeringManager.ResetY(); - } - return; - } - if (currentTarget != null) { character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition); } } + public void Wander(float deltaTime) + { + //steer away from edges of the hull + if (character.AnimController.CurrentHull != null && !character.IsClimbing) + { + float leftDist = character.Position.X - character.AnimController.CurrentHull.Rect.X; + float rightDist = character.AnimController.CurrentHull.Rect.Right - character.Position.X; + if (leftDist < WallAvoidDistance && rightDist < WallAvoidDistance) + { + if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2) + { + PathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist)); + } + else + { + PathSteering.Reset(); + } + } + else if (leftDist < WallAvoidDistance) + { + //PathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance - leftDist) / WallAvoidDistance); + PathSteering.SteeringManual(deltaTime, Vector2.UnitX); + PathSteering.WanderAngle = 0.0f; + } + else if (rightDist < WallAvoidDistance) + { + //PathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance - rightDist) / WallAvoidDistance); + PathSteering.SteeringManual(deltaTime, -Vector2.UnitX); + PathSteering.WanderAngle = MathHelper.Pi; + } + else + { + SteeringManager.SteeringWander(); + } + } + else + { + SteeringManager.SteeringWander(); + } + if (!character.IsClimbing && !character.AnimController.InWater) + { + //reset vertical steering to prevent dropping down from platforms etc + character.AIController.SteeringManager.ResetY(); + } + } + private readonly List targetHulls = new List(20); private readonly List hullWeights = new List(20); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs index 05d777513..7faa82a97 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs @@ -369,9 +369,18 @@ namespace Barotrauma return; } - float movementAngle = MathUtils.VectorToAngle(movement) - MathHelper.PiOver2; - - float mainLimbAngle = (MainLimb.type == LimbType.Torso ? TorsoAngle.Value : HeadAngle.Value) * Dir; + Vector2 transformedMovement = reverse ? -movement : movement; + float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2; + float mainLimbAngle = 0; + if (MainLimb.type == LimbType.Torso && TorsoAngle.HasValue) + { + mainLimbAngle = TorsoAngle.Value; + } + else if (MainLimb.type == LimbType.Head && HeadAngle.HasValue) + { + mainLimbAngle = HeadAngle.Value; + } + mainLimbAngle *= Dir; while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi) { movementAngle += MathHelper.TwoPi; @@ -379,7 +388,7 @@ namespace Barotrauma while (MainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi) { movementAngle -= MathHelper.TwoPi; - } + } if (CurrentSwimParams.RotateTowardsMovement) { @@ -403,7 +412,6 @@ namespace Barotrauma if (TailAngle.HasValue) { Limb tail = GetLimb(LimbType.Tail); - //tail?.body.SmoothRotate(movementAngle + TailAngle.Value * Dir, TailTorque); if (tail != null) { SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, MainLimb, TailTorque); @@ -413,6 +421,10 @@ namespace Barotrauma else { movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2; + if (reverse) + { + movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi); + } if (MainLimb.type == LimbType.Head && HeadAngle.HasValue) { Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque); @@ -442,7 +454,7 @@ namespace Barotrauma var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude); if (waveLength > 0 && waveAmplitude > 0) { - WalkPos -= movement.Length() / Math.Abs(waveLength); + WalkPos -= transformedMovement.Length() / Math.Abs(waveLength); WalkPos = MathUtils.WrapAngleTwoPi(WalkPos); } @@ -685,12 +697,6 @@ namespace Barotrauma limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength); } - while (referenceLimb.Rotation - angle < -MathHelper.TwoPi) - { - angle -= MathHelper.TwoPi; - } - - limb?.body.SmoothRotate(angle, torque, wrapAngle: false); } private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs index 4c8d56679..0d3b77f8a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs @@ -99,7 +99,7 @@ namespace Barotrauma public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Animations/"; public static string GetDefaultFile(string speciesName, AnimationType animType) => $"{GetFolder(speciesName)}{GetDefaultFileName(speciesName, animType)}.xml"; - protected static string GetFolder(string speciesName) + public static string GetFolder(string speciesName) { var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty); if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default") diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs index bb2a4d0b4..3f32ca43d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs @@ -79,7 +79,7 @@ namespace Barotrauma new XAttribute("sourcerect", $"0, 0, 1, 1"))) }; - protected static string GetFolder(string speciesName) + public static string GetFolder(string speciesName) { var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty); if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default") diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index c060cbb92..0de17eaf4 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -1035,8 +1035,6 @@ namespace Barotrauma CheckValidity(); - CheckValidity(); - UpdateNetPlayerPosition(deltaTime); CheckDistFromCollider(); UpdateCollisionCategories(); @@ -1297,17 +1295,42 @@ namespace Barotrauma UpdateProjSpecific(deltaTime); } - private void CheckValidity() + public bool Invalid { get; private set; } + private int validityResets; + private bool CheckValidity() { - CheckValidity(Collider); + bool isColliderValid = CheckValidity(Collider); + bool limbsValid = true; foreach (Limb limb in limbs) { if (limb.body == null || !limb.body.Enabled) { continue; } - CheckValidity(limb.body); + if (!CheckValidity(limb.body)) + { + limbsValid = false; + break; + } } + bool isValid = isColliderValid && limbsValid; + if (!isValid) + { + validityResets++; + if (validityResets > 1) + { + Invalid = true; + DebugConsole.ThrowError("Invalid ragdoll physics. Ragdoll freezed to prevent crashes."); + Collider.SetTransform(Vector2.Zero, 0.0f); + foreach (Limb limb in Limbs) + { + limb.body.SetTransform(Collider.SimPosition, 0.0f); + limb.body.ResetDynamics(); + } + Frozen = true; + } + } + return isValid; } - private void CheckValidity(PhysicsBody body) + private bool CheckValidity(PhysicsBody body) { string errorMsg = null; string bodyName = body.UserData is Limb ? "Limb" : "Collider"; @@ -1359,7 +1382,7 @@ namespace Barotrauma limb.body.ResetDynamics(); } SetInitialLimbPositions(); - return; + return false; } return true; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs index d815d3771..0e84aa580 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs @@ -28,6 +28,7 @@ namespace Barotrauma public enum AIBehaviorAfterAttack { FallBack, + FallBackUntilCanAttack, PursueIfCanAttack, Pursue } @@ -81,6 +82,9 @@ namespace Barotrauma [Serialize(AIBehaviorAfterAttack.FallBack, true), Editable(ToolTip = "The preferred AI behavior after the attack.")] public AIBehaviorAfterAttack AfterAttack { get; private set; } + [Serialize(false, true), Editable(ToolTip = "Should the ai try to reverse when aiming with this attack?")] + public bool Reverse { get; private set; } + [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target before the AI tries to attack.")] public float Range { get; private set; } @@ -96,6 +100,9 @@ namespace Barotrauma [Serialize(0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "Used as the attack cooldown between different kind of attacks. Does not have effect, if set to 0.")] public float SecondaryCoolDown { get; private set; } = 0; + [Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "Random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases).")] + public float CoolDownRandomFactor { get; private set; } = 0; + [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)] public float StructureDamage { get; private set; } @@ -182,7 +189,7 @@ namespace Barotrauma public readonly List Afflictions = new List(); /// - /// Only affects ai decision making. + /// Only affects ai decision making. All the conditionals has to be met in order to select the attack. TODO: allow to define conditionals using any (implemented in StatusEffect -> move from there to PropertyConditional?) /// public List Conditionals { get; private set; } = new List(); @@ -444,8 +451,10 @@ namespace Barotrauma public void SetCoolDown() { - CoolDownTimer = CoolDown; - SecondaryCoolDownTimer = SecondaryCoolDown; + float randomFraction = CoolDown * CoolDownRandomFactor; + CoolDownTimer = CoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server)); + randomFraction = SecondaryCoolDown * CoolDownRandomFactor; + SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server)); } public void ResetCoolDown() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 39e60ac07..19c905b3f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -810,6 +810,7 @@ namespace Barotrauma public void LoadHeadAttachments() { + if (Info == null) { return; } if (AnimController == null) { return; } var head = AnimController.GetLimb(LimbType.Head); if (head == null) { return; } @@ -1112,13 +1113,15 @@ namespace Barotrauma ViewTarget = null; if (!AllowInput) return; - Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition; - if (Controlled == this) + if (Controlled == this || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)) { SmoothedCursorPosition = cursorPosition; } else { + //apply some smoothing to the cursor positions of remote players when playing as a client + //to make aiming look a little less choppy + Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition; smoothedCursorDiff = NetConfig.InterpolateCursorPositionError(smoothedCursorDiff); SmoothedCursorPosition = cursorPosition - smoothedCursorDiff; } @@ -1185,6 +1188,10 @@ namespace Barotrauma if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.F)) { AnimController.ReleaseStuckLimbs(); + if (AIController != null && AIController is EnemyAIController enemyAI) + { + enemyAI.LatchOntoAI?.DeattachFromBody(); + } } #endif @@ -1264,15 +1271,7 @@ namespace Barotrauma if (IsKeyDown(InputType.Aim) && selectedItems[i] != null) selectedItems[i].SecondaryUse(deltaTime, this); } } - -#if CLIENT - if (SelectedConstruction != null && SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect))) - { - //emulate a Select input to get the character to deselect the item server-side - keys[(int)InputType.Select].Hit = true; - SelectedConstruction = null; - } -#endif + if (SelectedConstruction != null) { if (IsKeyDown(InputType.Use)) SelectedConstruction.Use(deltaTime, this); @@ -1650,7 +1649,8 @@ namespace Barotrauma #if CLIENT if (isLocalPlayer) { - if (GUI.MouseOn == null && !CharacterInventory.IsMouseOnInventory()) + if (GUI.MouseOn == null && + (!CharacterInventory.IsMouseOnInventory() || CharacterInventory.DraggingItemToWorld)) { if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen) { @@ -1665,10 +1665,12 @@ namespace Barotrauma focusedItem = null; } findFocusedTimer -= deltaTime; - } + } #endif //climb ladders automatically when pressing up/down inside their trigger area - if (SelectedConstruction == null && !AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen) + Ladder currentLadder = SelectedConstruction?.GetComponent(); + if ((SelectedConstruction == null || currentLadder != null) && + !AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen) { bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down); bool isControlled = Controlled == this; @@ -1679,6 +1681,19 @@ namespace Barotrauma float minDist = float.PositiveInfinity; foreach (Ladder ladder in Ladder.List) { + if (ladder == currentLadder) + { + continue; + } + else if (currentLadder != null) + { + //only switch from ladder to another if the ladders are above the current ladders and pressing up, or vice versa + if (ladder.Item.WorldPosition.Y > currentLadder.Item.WorldPosition.Y != IsKeyDown(InputType.Up)) + { + continue; + } + } + if (CanInteractWith(ladder.Item, out float dist, checkLinked: false) && dist < minDist) { minDist = dist; @@ -1695,7 +1710,7 @@ namespace Barotrauma } } - if (SelectedCharacter != null && IsKeyHit(InputType.Grab)) //Let people use ladders and buttons and stuff when dragging chars + if (SelectedCharacter != null && (IsKeyHit(InputType.Grab) || IsKeyHit(InputType.Health))) //Let people use ladders and buttons and stuff when dragging chars { DeselectCharacter(); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs index c5aee6f70..8dcf98fce 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs @@ -804,6 +804,12 @@ namespace Barotrauma var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true); if (newItem == null) { continue; } + if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) && + GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + GameMain.NetworkMember.CreateEntityEvent(newItem, new object[] { NetEntityEvent.Type.Status }); + } + int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 }); if (!slotIndices.Any()) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs index aea153f53..708e62bee 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -153,7 +153,10 @@ namespace Barotrauma //how high the strength has to be for the affliction icon to be shown in the UI public readonly float ShowIconThreshold = 0.05f; public readonly float MaxStrength = 100.0f; - + + //how high the strength has to be for the affliction icon to be shown with a health scanner + public readonly float ShowInHealthScannerThreshold = 0.05f; + public float BurnOverlayAlpha; public float DamageOverlayAlpha; @@ -257,6 +260,8 @@ namespace Barotrauma ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f)); MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f); + ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f)); + DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f); BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs index dff106cfb..5d6f70a83 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs @@ -460,6 +460,7 @@ namespace Barotrauma { affliction.Strength = 0.0f; } + CalculateVitality(); } private void AddLimbAffliction(Limb limb, Affliction newAffliction) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs index 0c1d42432..b19684c74 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs @@ -520,14 +520,15 @@ namespace Barotrauma // Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example. wasHit = true; } - else if (damageTarget is Structure && structureBody?.UserData is Structure) + else if (damageTarget is Structure wall && structureBody != null && + (structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine))) { - // If the attack is aimed to a structure and hits a structure, it's successful + // If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful wasHit = true; } else { - // If the attack is aimed to a character but hits a structure, the hit is blocked. + // If there is nothing between, the hit is successful wasHit = structureBody == null; } } diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs index adc0b0d8d..e8f5e87fd 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs @@ -103,12 +103,6 @@ namespace Barotrauma if (missionType == MissionType.Random) { allowedMissions.AddRange(MissionPrefab.List); -#if SERVER - if (GameMain.Server != null) - { - allowedMissions.RemoveAll(mission => !GameMain.Server.ServerSettings.AllowedRandomMissionTypes.Contains(mission.type)); - } -#endif } else if (missionType == MissionType.None) { @@ -124,6 +118,11 @@ namespace Barotrauma { allowedMissions.RemoveAll(m => !m.IsAllowed(locations[0], locations[1])); } + + if (allowedMissions.Count == 0) + { + return null; + } int probabilitySum = allowedMissions.Sum(m => m.Commonness); int randomNumber = rand.NextInt32() % probabilitySum; diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs index d5778c9cc..9e00659fe 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs @@ -139,7 +139,7 @@ namespace Barotrauma MTRandom rand = new MTRandom(ToolBox.StringToInt(seed)); for (int i = 0; i < 2; i++) { - dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null); + dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand); } } @@ -208,7 +208,7 @@ namespace Barotrauma DockingPort myPort = null, outPostPort = null; foreach (DockingPort port in DockingPort.List) { - if (port.IsHorizontal) { continue; } + if (port.IsHorizontal || port.Docked) { continue; } if (port.Item.Submarine == level.StartOutpost) { outPostPort = port; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index 89ee1b073..189a94651 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -880,8 +880,7 @@ namespace Barotrauma.Items.Components List linked = new List(item.linkedTo); foreach (MapEntity entity in linked) { - Item linkedItem = entity as Item; - if (linkedItem == null) { continue; } + if (!(entity is Item linkedItem)) { continue; } var dockingPort = linkedItem.GetComponent(); if (dockingPort != null) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index dc355a5c6..e5171e5c8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -426,11 +426,11 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character character = null) { - if (!attachable || item.body == null) return (character == null || character.IsKeyDown(InputType.Aim)); + if (!attachable || item.body == null) { return character == null || character.IsKeyDown(InputType.Aim); } if (character != null) { - if (!character.IsKeyDown(InputType.Aim)) return false; - if (!CanBeAttached()) return false; + if (!character.IsKeyDown(InputType.Aim)) { return false; } + if (!CanBeAttached()) { return false; } #if SERVER if (GameMain.Server != null) { @@ -438,7 +438,12 @@ namespace Barotrauma.Items.Components GameServer.Log(character.LogName + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction); } #endif - item.Drop(character); + } + + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) + { + if (character != null) { item.Drop(character); } + AttachToWall(); } return true; @@ -550,7 +555,7 @@ namespace Barotrauma.Items.Components public override void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { base.ServerWrite(msg, c, extraData); - if (!attachable || body == null) return; + if (!attachable || body == null) { return; } msg.Write(Attached); msg.Write(body.SimPosition.X); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs index 458bf0c57..90a7a0665 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs @@ -2,12 +2,15 @@ using Lidgren.Network; using Microsoft.Xna.Framework; using System; +using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components { partial class LevelResource : ItemComponent, IServerSerializable { + private float lastSentDeattachTimer; + [Serialize(1.0f, false)] public float DeattachDuration { @@ -21,12 +24,29 @@ namespace Barotrauma.Items.Components get { return deattachTimer; } set { - deattachTimer = Math.Max(0.0f, value); //clients don't deattach the item until the server says so (handled in ClientRead) - if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && deattachTimer >= DeattachDuration) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) + { + return; + } + deattachTimer = Math.Max(0.0f, value); +#if SERVER + if (deattachTimer >= DeattachDuration) + { + if (holdable.Attached){ item.CreateServerEvent(this); } + holdable.DeattachFromWall(); + } + else if (Math.Abs(lastSentDeattachTimer - deattachTimer) > 0.1f) + { + item.CreateServerEvent(this); + lastSentDeattachTimer = deattachTimer; + } +#else + if (deattachTimer >= DeattachDuration) { holdable.DeattachFromWall(); } +#endif } } @@ -67,7 +87,10 @@ namespace Barotrauma.Items.Components return; } holdable.Reattachable = false; - holdable.PickingTime = float.MaxValue; + if (requiredItems.Any()) + { + holdable.PickingTime = float.MaxValue; + } var body = item.body ?? holdable.Body; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index 160efdf9b..153e694ef 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -17,6 +17,8 @@ namespace Barotrauma.Items.Components private readonly List fixableEntities; private Vector2 pickedPosition; private float activeTimer; + + private Vector2 debugRayStartPos, debugRayEndPos; [Serialize(0.0f, false)] public float Range { get; set; } @@ -156,7 +158,7 @@ namespace Barotrauma.Items.Components var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair; if (RepairThroughWalls) { - var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false); + var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false, allowInsideFixture: true); foreach (Body body in bodies) { FixBody(user, deltaTime, degreeOfSuccess, body); @@ -164,7 +166,7 @@ namespace Barotrauma.Items.Components } else { - FixBody(user, deltaTime, degreeOfSuccess, Submarine.PickBody(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false)); + FixBody(user, deltaTime, degreeOfSuccess, Submarine.PickBody(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false, allowInsideFixture: true)); } if (ExtinguishAmount > 0.0f && item.CurrentHull != null) @@ -247,6 +249,7 @@ namespace Barotrauma.Items.Components var levelResource = targetItem.GetComponent(); if (levelResource != null && levelResource.IsActive && + levelResource.requiredItems.Any() && levelResource.HasRequiredItems(user, addMessage: false)) { levelResource.DeattachTimer += deltaTime; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs index 46c994f66..b1634a6b7 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs @@ -104,8 +104,8 @@ namespace Barotrauma.Items.Components #if SERVER GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction); #endif - - item.Drop(picker, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer); + Character thrower = picker; + item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer); item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f); ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index 5807a6e7f..21fd14e74 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -189,7 +189,7 @@ namespace Barotrauma.Items.Components { get { return name; } } - + [Editable, Serialize("", true)] public string Msg { @@ -203,12 +203,6 @@ namespace Barotrauma.Items.Components set; } - public AITarget AITarget - { - get; - private set; - } - public AITarget AITarget { get; @@ -253,7 +247,7 @@ namespace Barotrauma.Items.Components { DebugConsole.ThrowError("Invalid pick key in " + element + "!", e); } - + SerializableProperties = SerializableProperty.DeserializeProperties(this, element); ParseMsg(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs index 11a3ea4e0..d9abe0511 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs @@ -155,13 +155,14 @@ namespace Barotrauma.Items.Components { if (item.Container != null) { return false; } - if (AutoInteractWithContained) + if (AutoInteractWithContained && character.SelectedConstruction == null) { foreach (Item contained in Inventory.Items) { if (contained == null) continue; if (contained.TryInteract(character)) { + character.FocusedItem = contained; return false; } } @@ -178,6 +179,7 @@ namespace Barotrauma.Items.Components if (contained == null) continue; if (contained.TryInteract(picker)) { + picker.FocusedItem = contained; return true; } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs index 010f24173..5df72ff59 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs @@ -90,7 +90,7 @@ namespace Barotrauma.Items.Components Force = MathHelper.Lerp(force, (voltage < minVoltage) ? 0.0f : targetForce, 0.1f); if (Math.Abs(Force) > 1.0f) { - Vector2 currForce = new Vector2((force / 100.0f) * maxForce * Math.Min(voltage / minVoltage, 1.0f), 0.0f); + Vector2 currForce = new Vector2((force / 10.0f) * maxForce * Math.Min(voltage / minVoltage, 1.0f), 0.0f); //less effective when in a bad condition currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index 101439383..a26222b4b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -49,7 +49,7 @@ namespace Barotrauma.Items.Components private bool shutDown; - const float AIUpdateInterval = 1.0f; + const float AIUpdateInterval = 0.2f; private float aiUpdateTimer; private Character lastUser; @@ -209,10 +209,10 @@ namespace Barotrauma.Items.Components allowedFissionRate = Vector2.Lerp(new Vector2(20, AvailableFuel), new Vector2(10, AvailableFuel), degreeOfSuccess); allowedFissionRate.X = Math.Min(allowedFissionRate.X, allowedFissionRate.Y - 10); - float heatAmount = fissionRate * (AvailableFuel / 100.0f) * 2.0f; + float heatAmount = GetGeneratedHeat(fissionRate); float temperatureDiff = (heatAmount - turbineOutput) - Temperature; Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff)); - if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime; + //if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime; FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime); TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime); @@ -313,6 +313,48 @@ namespace Barotrauma.Items.Components } } + private float GetGeneratedHeat(float fissionRate) + { + return fissionRate * (prevAvailableFuel / 100.0f) * 2.0f; + } + + /// + /// Do we need more fuel to generate enough power to match the current load. + /// + /// How low we allow the output/load ratio to go before loading more fuel. + /// 1.0 = always load more fuel when maximum output is too low, 0.5 = load more if max output is 50% of the load + private bool NeedMoreFuel(float minimumOutputRatio) + { + if (prevAvailableFuel <= 0.0f && load > 0.0f) + { + return true; + } + + //fission rate is clamped to the amount of available fuel + float maxFissionRate = Math.Min(prevAvailableFuel, 100.0f); + float maxTurbineOutput = 100.0f; + + //calculate the maximum output if the fission rate is cranked as high as it goes and turbine output is at max + float theoreticalMaxHeat = GetGeneratedHeat(fissionRate: maxFissionRate); + float temperatureFactor = Math.Min(theoreticalMaxHeat / 50.0f, 1.0f); + float theoreticalMaxOutput = Math.Min(maxTurbineOutput / 100.0f, temperatureFactor) * MaxPowerOutput; + + //maximum output not enough, we need more fuel + return theoreticalMaxOutput < load * minimumOutputRatio; + } + + private bool TooMuchFuel() + { + var containedItems = item.ContainedItems; + if (containedItems != null && containedItems.Count() <= 1) { return false; } + + //get the amount of heat we'd generate if the fission rate was at the low end of the optimal range + float minimumHeat = GetGeneratedHeat(optimalFissionRate.X); + + //if we need a very high turbine output to keep the engine from overheating, there's too much fuel + return minimumHeat > Math.Min(correctTurbineOutput * 1.5f, 90); + } + private void UpdateFailures(float deltaTime) { if (temperature > allowedTemperature.Y) @@ -367,6 +409,11 @@ namespace Barotrauma.Items.Components targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, 100.0f); } targetFissionRate = MathHelper.Clamp(targetFissionRate, 0.0f, 100.0f); + + //don't push the target too far from the current fission rate + //otherwise we may "overshoot", cranking the target fission rate all the way up because it takes a while + //for the actual fission rate and temperature to follow + targetFissionRate = MathHelper.Clamp(targetFissionRate, FissionRate - 5, FissionRate + 5); } public override void UpdateBroken(float deltaTime, Camera cam) @@ -441,12 +488,18 @@ namespace Barotrauma.Items.Components } } - //we need more fuel - if (-currPowerConsumption < load * 0.5f && prevAvailableFuel <= 0.0f) + if (aiUpdateTimer > 0.0f) + { + aiUpdateTimer -= deltaTime; + return false; + } + + //load more fuel if the current maximum output is only 50% of the current load + if (NeedMoreFuel(minimumOutputRatio: 0.5f)) { var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent()) { - MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1, + MinContainedAmount = item.ContainedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1, GetItemPriority = (Item fuelItem) => { if (fuelItem.ParentInventory?.Owner is Item) @@ -461,6 +514,7 @@ namespace Barotrauma.Items.Components character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f); + aiUpdateTimer = AIUpdateInterval; return false; } else if (TooMuchFuel()) @@ -479,12 +533,6 @@ namespace Barotrauma.Items.Components } } - if (aiUpdateTimer > 0.0f) - { - aiUpdateTimer -= deltaTime; - return false; - } - if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item) { character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f); @@ -506,7 +554,7 @@ namespace Barotrauma.Items.Components { AutoTemp = false; unsentChanges = true; - UpdateAutoTemp(2.0f + degreeOfSuccess * 5.0f, 1.0f); + UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f); } #if CLIENT onOffSwitch.BarScroll = 0.0f; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index 542f66087..b5d448e79 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs @@ -153,7 +153,7 @@ namespace Barotrauma.Items.Components } continue; } - if (!pt.IsActive) { continue; } + if (!pt.IsActive || !pt.CanTransfer) { continue; } gridLoad += pt.PowerLoad; gridPower -= pt.CurrPowerConsumption; @@ -209,9 +209,9 @@ namespace Barotrauma.Items.Components Charge -= CurrPowerOutput / 3600.0f; } - item.SendSignal(0, Charge.ToString(), "charge", null); - item.SendSignal(0, ((Charge / capacity) * 100).ToString(), "charge_%", null); - item.SendSignal(0, ((RechargeSpeed / maxRechargeSpeed) * 100).ToString(), "charge_rate", null); + item.SendSignal(0, ((int)Charge).ToString(), "charge", null); + item.SendSignal(0, ((int)((Charge / capacity) * 100)).ToString(), "charge_%", null); + item.SendSignal(0, ((int)((RechargeSpeed / maxRechargeSpeed) * 100)).ToString(), "charge_rate", null); foreach (Pair connected in directlyConnected) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs index f15e8dc8c..3bd5416bb 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs @@ -180,7 +180,8 @@ namespace Barotrauma.Items.Components #endif //items in a bad condition are more sensitive to overvoltage - float maxOverVoltage = MathHelper.Lerp(Math.Min(OverloadVoltage, 1.0f), OverloadVoltage, item.Condition / item.MaxCondition); + float maxOverVoltage = MathHelper.Lerp(OverloadVoltage * 0.75f, OverloadVoltage, item.Condition / item.MaxCondition); + maxOverVoltage = Math.Max(OverloadVoltage, 1.0f); //if the item can't be fixed, don't allow it to break if (!item.Repairables.Any() || !CanBeOverloaded) continue; @@ -319,7 +320,13 @@ namespace Barotrauma.Items.Components ApplyStatusEffects(ActionType.OnActive, deltaTime, null); - float maxPower = this is RelayComponent relayComponent ? relayComponent.MaxPower : float.PositiveInfinity; + //float maxPower = this is RelayComponent relayComponent ? relayComponent.MaxPower : float.PositiveInfinity; + RelayComponent thisRelayComponent = this as RelayComponent; + if (thisRelayComponent != null) + { + clampPower = Math.Min(Math.Min(clampPower, thisRelayComponent.MaxPower), powerLoad); + clampLoad = Math.Min(clampLoad, thisRelayComponent.MaxPower); + } foreach (Connection c in PowerConnections) { @@ -357,6 +364,8 @@ namespace Barotrauma.Items.Components continue; } + float addLoad = 0.0f; + float addPower = 0.0f; if (powered is PowerContainer powerContainer) { if (recipient.Name == "power_in") @@ -365,7 +374,7 @@ namespace Barotrauma.Items.Components } else { - fullPower += Math.Min(powerContainer.CurrPowerOutput, maxPower); + addPower = powerContainer.CurrPowerOutput; } } else @@ -380,10 +389,16 @@ namespace Barotrauma.Items.Components //negative power consumption = the construction is a //generator/battery or another junction box { - fullPower -= Math.Max(powered.CurrPowerConsumption, -maxPower); + addPower -= powered.CurrPowerConsumption; } } - } + + if (addPower + fullPower > clampPower) { addPower -= (addPower + fullPower) - clampPower; }; + if (addPower > 0) { fullPower += addPower; } + + if (addLoad + fullLoad > clampLoad) { addLoad -= (addLoad + fullLoad) - clampLoad; }; + if (addLoad > 0) { fullLoad += addLoad; } + } } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs index 3f33c4df2..8d3baec86 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs @@ -12,8 +12,6 @@ namespace Barotrauma.Items.Components public static float SkillIncreaseMultiplier = 0.4f; private string header; - - private float fixDurationLowSkill, fixDurationHighSkill; private float deteriorationTimer; @@ -52,17 +50,20 @@ namespace Barotrauma.Items.Components set; } - /*private float repairProgress; - public float RepairProgress + [Serialize(100.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The amount of time it takes to fix the item with insufficient skill levels.")] + public float FixDurationLowSkill { - get { return repairProgress; } - set - { - repairProgress = MathHelper.Clamp(value, 0.0f, 1.0f); - if (repairProgress >= 1.0f && currentFixer != null) currentFixer.AnimController.Anim = AnimController.Animation.None; - } - }*/ - + get; + set; + } + + [Serialize(10.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The amount of time it takes to fix the item with sufficient skill levels.")] + public float FixDurationHighSkill + { + get; + set; + } + private Character currentFixer; public Character CurrentFixer { @@ -83,8 +84,6 @@ namespace Barotrauma.Items.Components this.item = item; header = element.GetAttributeString("name", ""); - fixDurationLowSkill = element.GetAttributeFloat("fixdurationlowskill", 100.0f); - fixDurationHighSkill = element.GetAttributeFloat("fixdurationhighskill", 5.0f); InitProjSpecific(element); } @@ -160,7 +159,7 @@ namespace Barotrauma.Items.Components } bool wasBroken = !item.IsFullCondition; - float fixDuration = MathHelper.Lerp(fixDurationLowSkill, fixDurationHighSkill, successFactor); + float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor); if (fixDuration <= 0.0f) { item.Condition = item.MaxCondition; @@ -186,26 +185,5 @@ namespace Barotrauma.Items.Components { character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f)); } - - public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) - { - msg.Write(deteriorationTimer); - } - - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - deteriorationTimer = msg.ReadSingle(); - } - - public void ClientWrite(NetBuffer msg, object[] extraData = null) - { - //no need to write anything, just letting the server know we started repairing - } - - public void ServerRead(ClientNetObject type, NetBuffer msg, Client c) - { - if (c.Character == null) return; - StartRepairing(c.Character); - } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs index 49a620e56..27335ef0c 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs @@ -163,6 +163,14 @@ namespace Barotrauma.Items.Components { wires[index] = wire; recipientsDirty = true; + if (wire != null) + { + var otherConnection = wire.OtherConnection(this); + if (otherConnection != null) + { + otherConnection.recipientsDirty = true; + } + } } public void SendSignal(int stepsTaken, string signal, Item source, Character sender, float power, float signalStrength = 1.0f) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 3dfbf16d9..91255ae23 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -356,7 +356,7 @@ namespace Barotrauma.Items.Components projectile.body.ResetDynamics(); projectile.body.Enabled = true; projectile.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)), -rotation); - projectile.FindHull(); + projectile.UpdateTransform(); projectile.Submarine = projectile.body.Submarine; Projectile projectileComponent = projectile.GetComponent(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 191e78a43..09f0991c3 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -59,7 +59,7 @@ namespace Barotrauma public PhysicsBody body; public readonly XElement StaticBodyConfig; - + private float lastSentCondition; private float sendConditionUpdateTimer; private bool conditionUpdatePending; @@ -211,14 +211,14 @@ namespace Barotrauma set { spriteColor = value; } } - [Serialize("1.0,1.0,1.0,1.0", false), Editable] + [Serialize("1.0,1.0,1.0,1.0", true), Editable] public Color InventoryIconColor { get; protected set; } - [Serialize("1.0,1.0,1.0,1.0", false), Editable(ToolTip = "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")] + [Serialize("1.0,1.0,1.0,1.0", true), Editable(ToolTip = "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")] public Color ContainerColor { get; @@ -274,12 +274,11 @@ namespace Barotrauma SetActiveSprite(); - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && lastSentCondition != condition) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !MathUtils.NearlyEqual(lastSentCondition, condition)) { if (Math.Abs(lastSentCondition - condition) > 1.0f || condition == 0.0f || condition == Prefab.Health) { - GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status }); - lastSentCondition = condition; + conditionUpdatePending = true; } } } @@ -996,6 +995,21 @@ namespace Barotrauma aiTarget.SightRange -= deltaTime * 1000.0f; aiTarget.SoundRange -= deltaTime * 1000.0f; } + + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + sendConditionUpdateTimer -= deltaTime; + if (conditionUpdatePending) + { + if (sendConditionUpdateTimer <= 0.0f) + { + GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status }); + lastSentCondition = condition; + sendConditionUpdateTimer = NetConfig.ItemConditionUpdateInterval; + conditionUpdatePending = false; + } + } + } ApplyStatusEffects(ActionType.Always, deltaTime, null); @@ -1272,7 +1286,11 @@ namespace Barotrauma LastSentSignalRecipients.Clear(); if (connections == null) return; - stepsTaken++; + public List GetConnectedComponentsRecursive(Connection c) where T : ItemComponent + { + List connectedComponents = new List(); + List alreadySearched = new List() { this }; + GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents); if (!connections.TryGetValue(connectionName, out Connection c)) return; diff --git a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs index 105a87746..25e9155a7 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs @@ -554,6 +554,39 @@ namespace Barotrauma AllowedLinks = element.GetAttributeStringArray("allowedlinks", new string[0], convertToLowerInvariant: true).ToList(); + if (sprite == null) + { + DebugConsole.ThrowError("Item \"" + Name + "\" has no sprite!"); +#if SERVER + sprite = new Sprite("", Vector2.Zero); + sprite.SourceRect = new Rectangle(0, 0, 32, 32); +#else + sprite = new Sprite(TextureLoader.PlaceHolderTexture, null, null) + { + Origin = TextureLoader.PlaceHolderTexture.Bounds.Size.ToVector2() / 2 + }; +#endif + size = sprite.size; + sprite.EntityID = identifier; + } + + if (!category.HasFlag(MapEntityCategory.Legacy) && string.IsNullOrEmpty(identifier)) + { + DebugConsole.ThrowError( + "Item prefab \"" + name + "\" has no identifier. All item prefabs have a unique identifier string that's used to differentiate between items during saving and loading."); + } + if (!string.IsNullOrEmpty(identifier)) + { + MapEntityPrefab existingPrefab = List.Find(e => e.Identifier == identifier); + if (existingPrefab != null) + { + DebugConsole.ThrowError( + "Map entity prefabs \"" + name + "\" and \"" + existingPrefab.Name + "\" have the same identifier!"); + } + } + + AllowedLinks = element.GetAttributeStringArray("allowedlinks", new string[0], convertToLowerInvariant: true).ToList(); + List.Add(this); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs index c94dffa0f..13c6965e9 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs @@ -28,8 +28,10 @@ namespace Barotrauma public Explosion(float range, float force, float damage, float structureDamage, float empStrength = 0.0f) { - attack = new Attack(damage, 0.0f, 0.0f, structureDamage, range); - attack.SeverLimbsProbability = 1.0f; + attack = new Attack(damage, 0.0f, 0.0f, structureDamage, range) + { + SeverLimbsProbability = 1.0f + }; this.force = force; this.empStrength = empStrength; sparks = true; @@ -183,9 +185,6 @@ namespace Barotrauma Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false); bool underWater = hull == null || explosionPos.Y < hull.Surface; - Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false); - bool underWater = hull == null || explosionPos.Y < hull.Surface; - explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary distFactors = new Dictionary(); diff --git a/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs b/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs index 83ea002c0..c252c399a 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs @@ -175,12 +175,11 @@ namespace Barotrauma LimitSize(); UpdateProjSpecific(growModifier); - -#if CLIENT - if (GameMain.Client != null) return; -#endif - - if (size.X < 1.0f) Remove(); + + if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)) + { + Remove(); + } } partial void UpdateProjSpecific(float growModifier); @@ -293,10 +292,6 @@ namespace Barotrauma //evaporate some of the water hull.WaterVolume -= extinguishAmount; -#if CLIENT - if (GameMain.Client != null) return; -#endif - if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)) { Remove(); @@ -325,12 +320,11 @@ namespace Barotrauma size.X -= extinguishAmount; hull.WaterVolume -= extinguishAmount; - -#if CLIENT - if (GameMain.Client != null) return; -#endif - - if (size.X < 1.0f) Remove(); + + if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)) + { + Remove(); + } } public void Extinguish(float deltaTime, float amount, Vector2 worldPosition) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index 485a7b2bc..883c85f65 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -88,6 +88,7 @@ namespace Barotrauma Gap.UpdateHulls(); } + OxygenPercentage = prevOxygenPercentage; surface = drawSurface = rect.Y - rect.Height + WaterVolume / rect.Width; Pressure = surface; } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 8ffc5630e..5bace8352 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -231,7 +231,7 @@ namespace Barotrauma public static Level CreateRandom(LocationConnection locationConnection) { - string seed = locationConnection.Locations[0].Name + locationConnection.Locations[1].Name; + string seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName; float sizeFactor = MathUtils.InverseLerp( MapGenerationParams.Instance.SmallLevelConnectionLength, @@ -1522,21 +1522,49 @@ namespace Barotrauma outpost.MakeOutpost(); Point? minSize = null; + DockingPort subPort = null; if (Submarine.MainSub != null) { Point subSize = Submarine.MainSub.GetDockedBorders().Size; Point outpostSize = outpost.GetDockedBorders().Size; minSize = new Point(Math.Max(subSize.X, outpostSize.X), subSize.Y + outpostSize.Y); + + float closestDistance = float.MaxValue; + foreach (DockingPort port in DockingPort.List) + { + if (port.IsHorizontal || port.Docked) { continue; } + if (port.Item.Submarine != Submarine.MainSub) { continue; } + //the submarine port has to be at the top of the sub + if (port.Item.WorldPosition.Y < Submarine.MainSub.WorldPosition.Y) { continue; } + float dist = Math.Abs(port.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X); + if (dist < closestDistance) + { + subPort = port; + closestDistance = dist; + } + } } - outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize)); + float subDockingPortOffset = subPort == null ? 0.0f : subPort.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X; + //don't try to compensate if the port is very far from the sub's center of mass + if (Math.Abs(subDockingPortOffset) > 2000.0f) + { + subDockingPortOffset = MathHelper.Clamp(subDockingPortOffset, -2000.0f, 2000.0f); + string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Name + ", dist: " + subDockingPortOffset + "). 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:DockingPortVeryFar" + Submarine.MainSub.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg); + } + + outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset)); if ((i == 0) == !Mirrored) { StartOutpost = outpost; + if (GameMain.GameSession?.StartLocation != null) { outpost.Name = GameMain.GameSession.StartLocation.Name; } } else { EndOutpost = outpost; + if (GameMain.GameSession?.EndLocation != null) { outpost.Name = GameMain.GameSession.EndLocation.Name; } } } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs index d96da098c..3874415b6 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs @@ -15,7 +15,6 @@ namespace Barotrauma public Vector3 Position; public float NetworkUpdateTimer; - public const float NetworkUpdateInterval = 0.2f; public float Scale; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs index 40469cea9..b75965a40 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -343,7 +343,7 @@ namespace Barotrauma { GameMain.NetworkMember.CreateEntityEvent(this, new object[] { obj }); obj.NeedsNetworkSyncing = false; - obj.NetworkUpdateTimer = LevelObject.NetworkUpdateInterval; + obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval; } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelTrigger.cs index 83033e604..1c3ec2339 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelTrigger.cs @@ -432,12 +432,16 @@ namespace Barotrauma { if (ForceFluctuationStrength > 0.0f) { - forceFluctuationTimer += deltaTime; - if (forceFluctuationTimer > ForceFluctuationInterval) + //no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers + if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any()) { - NeedsNetworkSyncing = true; - currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f); - forceFluctuationTimer = 0.0f; + forceFluctuationTimer += deltaTime; + if (forceFluctuationTimer > ForceFluctuationInterval) + { + NeedsNetworkSyncing = true; + currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f); + forceFluctuationTimer = 0.0f; + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/Location.cs index ff9172eb8..04a108a94 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/Location.cs @@ -16,6 +16,8 @@ namespace Barotrauma public int TypeChangeTimer; + public string BaseName { get => baseName; } + public string Name { get; private set; } public Vector2 MapPosition { get; private set; } @@ -32,10 +34,10 @@ namespace Barotrauma get { CheckMissionCompleted(); - + for (int i = availableMissions.Count; i < Connections.Count * 2; i++) { - int seed = (ToolBox.StringToInt(Name) + MissionsCompleted * 10 + i) % int.MaxValue; + int seed = (ToolBox.StringToInt(BaseName) + MissionsCompleted * 10 + i) % int.MaxValue; MTRandom rand = new MTRandom(seed); LocationConnection connection = Connections[(MissionsCompleted + i) % Connections.Count]; @@ -46,7 +48,7 @@ namespace Barotrauma if (availableMissions.Any(m => m.Prefab == mission.Prefab)) { continue; } if (GameSettings.VerboseLogging && mission != null) { - DebugConsole.NewMessage("Generated a new mission for a location connection (seed: " + seed.ToString("X") + ", type: " + mission.Name + ")", Color.White); + DebugConsole.NewMessage("Generated a new mission for a location (location: " + Name + ", seed: " + seed.ToString("X") + ", missions completed: " + MissionsCompleted + ", type: " + mission.Name + ")", Color.White); } availableMissions.Add(mission); } @@ -75,10 +77,10 @@ namespace Barotrauma } } - public Location(Vector2 mapPosition, int? zone) + public Location(Vector2 mapPosition, int? zone, Random rand) { - this.Type = LocationType.Random("", zone); - this.Name = RandomName(Type); + this.Type = LocationType.Random(rand, zone); + this.Name = RandomName(Type, rand); this.MapPosition = mapPosition; PortraitId = ToolBox.StringToInt(Name); @@ -86,9 +88,9 @@ namespace Barotrauma Connections = new List(); } - public static Location CreateRandom(Vector2 position, int? zone) + public static Location CreateRandom(Vector2 position, int? zone , Random rand) { - return new Location(position, zone); + return new Location(position, zone, rand); } public IEnumerable GetMissionsInConnection(LocationConnection connection) @@ -99,7 +101,16 @@ namespace Barotrauma public void ChangeType(LocationType newType) { - if (newType == Type) return; + if (newType == Type) { return; } + + //clear missions from this and adjacent locations (they may be invalid now) + availableMissions.Clear(); + foreach (LocationConnection connection in Connections) + { + connection.OtherLocation(this)?.availableMissions.Clear(); + } + + DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType); Type = newType; Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName); @@ -111,6 +122,7 @@ namespace Barotrauma { if (mission.Completed) { + DebugConsole.Log("Mission \"" + mission.Name + "\" completed in \"" + Name + "\"."); MissionsCompleted++; } } @@ -118,10 +130,10 @@ namespace Barotrauma availableMissions.RemoveAll(m => m.Completed); } - private string RandomName(LocationType type) + private string RandomName(LocationType type, Random rand) { - baseName = type.GetRandomName(); - nameFormatIndex = Rand.Int(type.NameFormats.Count, Rand.RandSync.Server); + baseName = type.GetRandomName(rand); + nameFormatIndex = rand.Next() % type.NameFormats.Count; return type.NameFormats[nameFormatIndex].Replace("[name]", baseName); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs index 09483cc25..aecfd8149 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs @@ -155,20 +155,15 @@ namespace Barotrauma return portraits[Math.Abs(portraitId) % portraits.Count]; } - public string GetRandomName() - { - return names[Rand.Int(names.Count, Rand.RandSync.Server)]; - } - - public static LocationType Random(string seed = "", int? zone = null) - { - Debug.Assert(List.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes"); - public string GetRandomName(Random rand) { return names[rand.Next() % names.Count]; } + public static LocationType Random(Random rand, int? zone = null) + { + Debug.Assert(List.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes"); + List allowedLocationTypes = zone.HasValue ? List.FindAll(lt => lt.CommonnessPerZone.ContainsKey(zone.Value)) : List; if (allowedLocationTypes.Count == 0) @@ -180,12 +175,12 @@ namespace Barotrauma { return ToolBox.SelectWeightedRandom( allowedLocationTypes, - allowedLocationTypes.Select(a => a.CommonnessPerZone[zone.Value]).ToList(), - Rand.RandSync.Server); + allowedLocationTypes.Select(a => a.CommonnessPerZone[zone.Value]).ToList(), + rand); } else { - return allowedLocationTypes[Rand.Int(allowedLocationTypes.Count, Rand.RandSync.Server)]; + return allowedLocationTypes[rand.Next() % allowedLocationTypes.Count]; } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs index ba0ba04d5..eedceada2 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs @@ -196,7 +196,7 @@ namespace Barotrauma Vector2 position = points[positionIndex]; if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex]; int zone = MathHelper.Clamp(generationParams.DifficultyZones - (int)Math.Floor(Vector2.Distance(position, mapCenter) / zoneRadius), 1, generationParams.DifficultyZones); - newLocations[i] = Location.CreateRandom(position, zone); + newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server)); Locations.Add(newLocations[i]); } @@ -578,10 +578,12 @@ namespace Barotrauma location.MissionsCompleted = missionsCompleted; if (showNotifications && prevLocationType != location.Type) { - ChangeLocationType( - location, - prevLocationName, - prevLocationType.CanChangeTo.Find(c => c.ChangeToType.ToLowerInvariant() == location.Type.Identifier.ToLowerInvariant())); + var change = prevLocationType.CanChangeTo.Find(c => + c.ChangeToType.ToLowerInvariant() == location.Type.Identifier.ToLowerInvariant()); + if (change != null) + { + ChangeLocationType(location, prevLocationName, change); + } } break; case "connection": diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index 6e4fe089b..82d0dad3e 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -297,7 +297,13 @@ namespace Barotrauma CreateStairBodies(); } } - + + // Only add ai targets automatically to walls + if (aiTarget == null && HasBody && Tags.Contains("wall")) + { + aiTarget = new AITarget(this); + } + InsertToList(); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs index a4077554a..037364597 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs @@ -217,6 +217,10 @@ namespace Barotrauma } SerializableProperty.DeserializeProperties(sp, element); + if (sp.Body) + { + sp.Tags.Add("wall"); + } string translatedDescription = TextManager.Get("EntityDescription." + sp.identifier, true); if (!string.IsNullOrEmpty(translatedDescription)) sp.Description = translatedDescription; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index e78224c15..9a413cd79 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; +using System.Threading; using System.Xml.Linq; using Voronoi2; @@ -493,7 +494,7 @@ namespace Barotrauma } } - public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null) + public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null, float subDockingPortOffset = 0.0f) { Rectangle dockedBorders = GetDockedBorders(); Vector2 diffFromDockedBorders = @@ -532,6 +533,10 @@ namespace Barotrauma { maxX = Math.Min(maxX, ruin.Area.X - 100.0f); } + else + { + maxX = Math.Min(maxX, ruin.Area.X - 100.0f); + } } if (minX < 0.0f && maxX > Level.Loaded.Size.X) @@ -554,6 +559,26 @@ namespace Barotrauma spawnPos.X = (minX + maxX) / 2; } + if (minX < 0.0f && maxX > Level.Loaded.Size.X) + { + //no walls found at either side, just use the initial spawnpos and hope for the best + } + else if (minX < 0) + { + //no wall found at the left side, spawn to the left from the right-side wall + spawnPos.X = maxX - minWidth - 100.0f + subDockingPortOffset; + } + else if (maxX > Level.Loaded.Size.X) + { + //no wall found at right side, spawn to the right from the left-side wall + spawnPos.X = minX + minWidth + 100.0f + subDockingPortOffset; + } + else + { + //walls found at both sides, use their midpoint + spawnPos.X = (minX + maxX) / 2 + subDockingPortOffset; + } + spawnPos.Y = Math.Min(spawnPos.Y, Level.Loaded.Size.Y - dockedBorders.Height / 2 - 10); return spawnPos - diffFromDockedBorders; } @@ -665,7 +690,7 @@ namespace Barotrauma } } - public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null) + public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null, bool allowInsideFixture = false) { if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f) { @@ -677,21 +702,20 @@ namespace Barotrauma Body closestBody = null; if (allowInsideFixture) { - if (fixture == null || - (ignoreSensors && fixture.IsSensor) || - fixture.CollisionCategories == Category.None || - fixture.CollisionCategories == Physics.CollisionItem) return -1; - - if (customPredicate != null && !customPredicate(fixture)) return -1; - - if (collisionCategory != null && - !fixture.CollisionCategories.HasFlag((Category)collisionCategory) && - !((Category)collisionCategory).HasFlag(fixture.CollisionCategories)) return -1; + var aabb = new FarseerPhysics.Collision.AABB(rayStart - Vector2.One * 0.001f, rayStart + Vector2.One * 0.001f); + GameMain.World.QueryAABB((fixture) => + { + if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) { return true; } fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform); if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; } - if (fixture.Body.UserData is Structure structure) + closestFraction = 0.0f; + closestNormal = Vector2.Normalize(rayEnd - rayStart); + if (fixture.Body != null) closestBody = fixture.Body; + return false; + }, ref aabb); + if (closestFraction <= 0.0f) { lastPickedPosition = rayStart; lastPickedFraction = closestFraction; @@ -721,7 +745,7 @@ namespace Barotrauma return closestBody; } - public static List PickBodies(Vector2 rayStart, Vector2 rayEnd, List ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null) + public static List PickBodies(Vector2 rayStart, Vector2 rayEnd, IEnumerable ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null, bool allowInsideFixture = false) { if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f) { @@ -732,25 +756,9 @@ namespace Barotrauma List bodies = new List(); GameMain.World.RayCast((fixture, point, normal, fraction) => { - if (fixture == null || - (ignoreSensors && fixture.IsSensor) || - fixture.CollisionCategories == Category.None || - fixture.CollisionCategories == Physics.CollisionItem) return -1; + if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) { return -1; } - if (customPredicate != null && !customPredicate(fixture)) return -1; - - if (collisionCategory != null && - !fixture.CollisionCategories.HasFlag((Category)collisionCategory) && - !((Category)collisionCategory).HasFlag(fixture.CollisionCategories)) return -1; - - if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) return -1; - - if (fixture.Body.UserData is Structure structure) - { - if (structure.IsPlatform && collisionCategory != null && !((Category)collisionCategory).HasFlag(Physics.CollisionPlatform)) return -1; - } - - bodies.Add(fixture.Body); + if (fixture.Body != null) { bodies.Add(fixture.Body); } if (fraction < closestFraction) { lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction; @@ -760,10 +768,68 @@ namespace Barotrauma return fraction; }, rayStart, rayEnd); - + + if (allowInsideFixture) + { + var aabb = new FarseerPhysics.Collision.AABB(rayStart - Vector2.One * 0.001f, rayStart + Vector2.One * 0.001f); + GameMain.World.QueryAABB((fixture) => + { + if (bodies.Contains(fixture.Body) || fixture.Body == null) { return true; } + if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) { return true; } + + fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform); + if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; } + + closestFraction = 0.0f; + lastPickedPosition = rayStart; + lastPickedFraction = 0.0f; + lastPickedNormal = Vector2.Normalize(rayEnd - rayStart); + bodies.Add(fixture.Body); + return false; + }, ref aabb); + } + return bodies; } + private static bool CheckFixtureCollision(Fixture fixture, IEnumerable ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null) + { + if (fixture == null || + (ignoreSensors && fixture.IsSensor) || + fixture.CollisionCategories == Category.None || + fixture.CollisionCategories == Physics.CollisionItem) + { + return false; + } + + if (customPredicate != null && !customPredicate(fixture)) + { + return false; + } + + if (collisionCategory != null && + !fixture.CollisionCategories.HasFlag((Category)collisionCategory) && + !((Category)collisionCategory).HasFlag(fixture.CollisionCategories)) + { + return false; + } + + if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) + { + return false; + } + + if (fixture.Body.UserData is Structure structure) + { + if (structure.IsPlatform && collisionCategory != null && !((Category)collisionCategory).HasFlag(Physics.CollisionPlatform)) + { + return false; + } + } + + return true; + } + /// /// check visibility between two points (in sim units) /// diff --git a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs index bb1a5556f..f932497b4 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs @@ -615,7 +615,7 @@ namespace Barotrauma ID = (ushort)int.Parse(element.Attribute("ID").Value) }; - Enum.TryParse(element.GetAttributeString("spawn", "Path"), out w.spawnType); + w.spawnType = spawnType; string idCardDescString = element.GetAttributeString("idcarddesc", ""); if (!string.IsNullOrWhiteSpace(idCardDescString)) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/FileTransfer/FileTransfer.cs b/Barotrauma/BarotraumaShared/Source/Networking/FileTransfer/FileTransfer.cs index 51a489698..a5d48f5eb 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/FileTransfer/FileTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/FileTransfer/FileTransfer.cs @@ -7,7 +7,7 @@ enum FileTransferMessageType { - Unknown, Initiate, Data, Cancel + Unknown, Initiate, Data, TransferOnSameMachine, Cancel } enum FileTransferType diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetConfig.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetConfig.cs index d7a36e28c..74b6afcb2 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetConfig.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetConfig.cs @@ -32,11 +32,14 @@ namespace Barotrauma.Networking public const float HighPrioCharacterPositionUpdateInterval = 0.0f; public const float LowPrioCharacterPositionUpdateInterval = 1.0f; - //how much the physics body of an item has to move until the server - //send a position update to clients (in sim units) - public const float ItemPosUpdateDistance = 2.0f; - - public const float DeleteDisconnectedTime = 10.0f; + public const float DeleteDisconnectedTime = 20.0f; + + public const float ItemConditionUpdateInterval = 0.15f; + public const float LevelObjectUpdateInterval = 0.5f; + public const float HullUpdateInterval = 0.5f; + public const float HullUpdateDistance = 20000.0f; + + public const int MaxEventPacketsPerUpdate = 4; /// /// Interpolates the positional error of a physics body towards zero. diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs index 03e28f3d4..20cb9b39a 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs @@ -58,15 +58,8 @@ namespace Barotrauma.Networking eventCount++; continue; } - //the ID has been taken by another entity (the original entity has been removed) -> write an empty event - /*else if (Entity.FindEntityByID(e.Entity.ID) != e.Entity || e.Entity.IdFreed) - { - //technically the clients don't have any use for these, but removing events and shifting the IDs of all - //consecutive ones is so error-prone that I think this is a safer option - tempBuffer.Write(Entity.NullEntityID); - tempBuffer.WritePadBits(); - }*/ - else + + if (msg.LengthBytes + tempBuffer.LengthBytes + tempEventBuffer.LengthBytes > MaxEventBufferLength) { //no more room in this packet break; diff --git a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs index e03708d6e..dfe562c88 100644 --- a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs @@ -4,11 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; -using Barotrauma.Items.Components; -#if CLIENT -using Barotrauma.Particles; -using Barotrauma.Sounds; -#endif namespace Barotrauma { @@ -91,52 +86,10 @@ namespace Barotrauma } } + private TargetType targetTypes; protected HashSet targetIdentifiers; - public readonly ItemPrefab ItemPrefab; - public readonly SpawnPositionType SpawnPosition; - public readonly float Speed; - public readonly float Rotation; - - public ItemSpawnInfo(XElement element, string parentDebugName) - { - if (element.Attribute("name") != null) - { - //backwards compatibility - DebugConsole.ThrowError("Error in StatusEffect config (" + element.ToString() + ") - use item identifier instead of the name."); - string itemPrefabName = element.GetAttributeString("name", ""); - ItemPrefab = MapEntityPrefab.List.Find(m => m is ItemPrefab && (m.NameMatches(itemPrefabName) || m.Tags.Contains(itemPrefabName))) as ItemPrefab; - if (ItemPrefab == null) - { - DebugConsole.ThrowError("Error in StatusEffect \""+ parentDebugName + "\" - item prefab \"" + itemPrefabName + "\" not found."); - } - } - else - { - string itemPrefabIdentifier = element.GetAttributeString("identifier", ""); - if (string.IsNullOrEmpty(itemPrefabIdentifier)) itemPrefabIdentifier = element.GetAttributeString("identifiers", ""); - if (string.IsNullOrEmpty(itemPrefabIdentifier)) - { - DebugConsole.ThrowError("Invalid item spawn in StatusEffect \"" + parentDebugName + "\" - identifier not found in the element \"" + element.ToString() + "\""); - } - ItemPrefab = MapEntityPrefab.List.Find(m => m is ItemPrefab && m.Identifier == itemPrefabIdentifier) as ItemPrefab; - if (ItemPrefab == null) - { - DebugConsole.ThrowError("Error in StatusEffect config - item prefab with the identifier \"" + itemPrefabIdentifier + "\" not found."); - return; - } - } - - Speed = element.GetAttributeFloat("speed", 0.0f); - Rotation = MathHelper.ToRadians(element.GetAttributeFloat("rotation", 0.0f)); - - private List sounds = new List(); - private SoundSelectionMode soundSelectionMode; - private SoundChannel soundChannel; - private bool loopSound; -#endif - private List requiredItems; public string[] propertyNames; @@ -228,10 +181,6 @@ namespace Barotrauma Range = element.GetAttributeFloat("range", 0.0f); -#if CLIENT - particleEmitters = new List(); -#endif - IEnumerable attributes = element.Attributes(); List propertyAttributes = new List(); propertyConditionals = new List(); @@ -408,25 +357,6 @@ namespace Barotrauma var newSpawnItem = new ItemSpawnInfo(subElement, parentDebugName); if (newSpawnItem.ItemPrefab != null) spawnItems.Add(newSpawnItem); break; -#if CLIENT - case "particleemitter": - particleEmitters.Add(new ParticleEmitter(subElement)); - break; - case "sound": - var sound = Submarine.LoadRoundSound(subElement); - if (sound != null) - { - loopSound = subElement.GetAttributeBool("loop", false); - if (subElement.Attribute("selectionmode") != null) - { - if (Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out SoundSelectionMode selectionMode)) - { - soundSelectionMode = selectionMode; - } - } - sounds.Add(sound); - } - break; } } InitProjSpecific(element, parentDebugName); @@ -439,11 +369,6 @@ namespace Barotrauma return (targetTypes & targetType) != 0; } - public bool HasTargetType(TargetType targetType) - { - return (targetTypes & targetType) != 0; - } - public virtual bool HasRequiredItems(Entity entity) { if (requiredItems == null) return true; @@ -643,46 +568,10 @@ namespace Barotrauma { hull = ((Item)entity).CurrentHull; } -#if CLIENT - if (entity != null && sounds.Count > 0) - { - if (soundChannel == null || !soundChannel.IsPlaying) - { - if (soundSelectionMode == SoundSelectionMode.All) - { - foreach (RoundSound sound in sounds) - { - soundChannel = SoundPlayer.PlaySound(sound.Sound, sound.Volume, sound.Range, entity.WorldPosition, hull); - if (soundChannel != null) soundChannel.Looping = loopSound; - } - } - else - { - int selectedSoundIndex = 0; - if (soundSelectionMode == SoundSelectionMode.ItemSpecific && entity is Item item) - { - selectedSoundIndex = item.ID % sounds.Count; - } - else if (soundSelectionMode == SoundSelectionMode.CharacterSpecific && entity is Character user) - { - selectedSoundIndex = user.ID % sounds.Count; - } - else - { - selectedSoundIndex = Rand.Int(sounds.Count); - } - var selectedSound = sounds[selectedSoundIndex]; - soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, selectedSound.Volume, selectedSound.Range, entity.WorldPosition, hull); - if (soundChannel != null) soundChannel.Looping = loopSound; - } - } - } -#endif foreach (ISerializableEntity serializableEntity in targets) { - Item item = serializableEntity as Item; - if (item == null) continue; + if (!(serializableEntity is Item item)) continue; Character targetCharacter = targets.FirstOrDefault(t => t is Character character && !character.Removed) as Character; if (targetCharacter == null) @@ -782,11 +671,7 @@ namespace Barotrauma fire.Size = new Vector2(FireSize, fire.Size.Y); } - bool isNotClient = true; -#if CLIENT - isNotClient = GameMain.Client == null; -#endif - + bool isNotClient = GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient; if (isNotClient && entity != null && Entity.Spawner != null) //clients are not allowed to spawn items { foreach (ItemSpawnInfo itemSpawnInfo in spawnItems) @@ -843,27 +728,11 @@ namespace Barotrauma } } -#if CLIENT - if (entity != null) - { - foreach (ParticleEmitter emitter in particleEmitters) - { - float angle = 0.0f; - if (emitter.Prefab.CopyEntityAngle) - { - if (entity is Item it) - { - angle = it.body == null ? 0.0f : it.body.Rotation; - } - } - - emitter.Emit(deltaTime, entity.WorldPosition, hull, angle); - } - } - ApplyProjSpecific(deltaTime, entity, targets, hull); } + partial void ApplyProjSpecific(float deltaTime, Entity entity, List targets, Hull currentHull); + private void ApplyToProperty(ISerializableEntity target, SerializableProperty property, object value, float deltaTime) { if (disableDeltaTime || setValue) deltaTime = 1.0f; diff --git a/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs index 5ad818e21..3fbf744cc 100644 --- a/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs @@ -403,18 +403,18 @@ namespace Barotrauma } } - public static void ClearFolder(string FolderName, string[] ignoredFiles = null) + public static void ClearFolder(string FolderName, string[] ignoredFileNames = null) { DirectoryInfo dir = new DirectoryInfo(FolderName); foreach (FileInfo fi in dir.GetFiles()) { - if (ignoredFiles != null) + if (ignoredFileNames != null) { bool ignore = false; - foreach (string ignoredFile in ignoredFiles) + foreach (string ignoredFile in ignoredFileNames) { - if (Path.GetFullPath(fi.FullName).Equals(Path.GetFullPath(ignoredFile))) + if (Path.GetFileName(fi.FullName).Equals(Path.GetFileName(ignoredFile))) { ignore = true; break; diff --git a/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub b/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub index 62696becf..513c14cde 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub and b/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub index d2cd1ff19..6403c9fd9 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index aacc41a31..7b714e62c 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index 3d9685698..7ac100e81 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/PAX.sub b/Barotrauma/BarotraumaShared/Submarines/PAX.sub index 58e7cc9a0..13fefe7ba 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/PAX.sub and b/Barotrauma/BarotraumaShared/Submarines/PAX.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index 74c05a493..ede107e94 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub index 57f8fc56f..f8378ae05 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index d3c065fe6..0f4a06fc3 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Venture.sub b/Barotrauma/BarotraumaShared/Submarines/Venture.sub index 8cb2b6a3d..2ed054c24 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Venture.sub and b/Barotrauma/BarotraumaShared/Submarines/Venture.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 15722f9ce..54b1c3eac 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,109 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.9.8 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- Improved tutorial - better videos, instructional texts, objective list that suggest what you should do +next, option to rewatch the videos and re-read the instructions. +- Overhauled charybdis (still a work in progress though). +- Automatically grab adjacent ladders when the top/bottom of the current ladder is reached. Makes moving +through docking ports a little less confusing. +- Option to configure when afflictions become visible with the health scanner by adding +a "ShowInHealthScannerThreshold" attribute to the affliction. +- Added labels next to periscopes in Humpback and Dugong. +- Modified Humpback's bilge to make it easier for AI characters to fix. +- Enemies can now navigate out from the sub when escaping. +- Bunch of tweaks to monster AIs (targeting priorities, attack cooldowns, etc). +- Made InventoryIconColor and ContainerColor properties saveable. +- Removed descriptive name from alien artifacts (so be extra careful when bringing them on board). +- Allow panning the campaign map with WASD. +- Allow editing selected wires in wiring mode (so it's possible to, for example, modify the colors of +the wires without having to disconnect them). +- Decreased structure damage done by frag grenades and made them disappear after they've exploded. +- Batteries output charge values as integers. +- Made damaged junction boxes less sensitive to overvoltage. Nearly broken junction boxes were barely able +to handle any overvoltage, leading to chain reaction where one junction box breaking causes the grid to be +overloaded, and the rest of the boxes start taking damage at an increasing speed. +- Reactors don't cool down when underwater anymore. +- Removed minimum conditions from battery deconstruction output (= deconstructing an empty battery still +gives the materials used to craft the battery). +- Made a bunch of ItemContainer UI panels larger. +- Items can be dragged and dropped directly from the inventory into containers without having to select +the container first. +- Plants can be picked up from the environment without any tools. +- Added more help texts to highlighted items ("[E] Interact", "[E] Climb"...) + +Multiplayer fixes: +- Changes to event syncing logic to prevent cases where clients fall behind the server and get kicked out +due to the server not being able to send out network events to the clients fast enough. +- Networking optimizations that prevent level objects and continuously deteriorating items from creating +excessive amounts of network events. +- Fixed windows clients being unable to start a campaign in servers running on Linux. +- Fixed clients being unable to start a campaign using a submarine that's not in the default Submarine +folder at the server's side. +- Fixed loading submarine files and campaign saves occasionally failing when running multiple instances +of the game from the same install location (for example, a dedicated server executable and a client +executable). +- Don't transfer files through the network when sending them to the owner of the server (i.e. a client +hosting directly from the main executable). +- Fixed fires and water occasionally getting out of sync between a client using the fire/water console +commands and the server. +- Fixed clients disconnecting with an "unknown object header" error if they fail to read a network event +(when they should instead report the error to the server and wait for a message that contains a more +descriptive error). +- Campaign fix: clear missions from locations that change their type, and all adjacent locations. Not +clearing them caused missions to still be available when they logically shouldn't be (e.g. a transport +mission from an uninhabited location to another) and syncing issues in multiplayer. +- Disable campaign start button if a round is already running when joining. +- Fixed clients being unable to end campaign rounds at all if the sub isn't at the start/end outpost +(regardless if they have the permission to end the round or not). +- Fixed campaign characters still being displayed in the server lobby after the game mode has been +changed to something else. +- Fixed items in the characters inventory always starting at 100% condition client-side even if they had +deteriorated during the previous round. +- Fixed LevelResource (mineral, plant, etc) deattach timers not being synced with clients. +- AI characters can take out excess fuel rods from the reactor when needed. +- Item interfaces and the health interface can be closed with Esc. + +Bugfixes: +- Fixed almost all items using default repair duration values (10 seconds with high skills, 100 seconds +with low skills) instead of the ones configured in the item XMLs. +- Nuclear shells and nuclear depth charges disappear after they've exploded. +- Fixed "trying to add a dead character to crewmanager" errors when attempting to revive a character +killed by some other affliction than internal damage, bleeding or burns. +- Take the position of a sub's docking port into account when determining where to place outposts. +Previously the outposts were simply placed midway between the adjacent walls, which occasionally caused +problems with submarines whose docking port is close to the bow or tail. +- Fixed a bug in relay components that caused a bunch of issues in power grids that utilize relays: +Relays would receive the full amount of power from the grid regardless of the load of the devices +connected to the power_out connection, causing unnecessary overloads and fires. +- Fixed batteries being able to draw power through relay components that are connected directly to +a power source, even if the relay isn't on. +- Don't allow steering the sub with WASD when a textbox is selected. +- Use the SpriteColor of the item when drawing the moving parts of turrets and doors. +- Fixed steering issues that caused monsters to swim in an incorrect direction when they're targeting +something inside the sub. +- Fixed a crash due when scaling the Mudraptor. +- Fixed crashing if a character has neither a head angle or a torso angle configured. +- Fixed characters always being created in the default folder in the character editor. +- Monsters don't target doors/hatches at the exterior of the sub when inside or inner doors when outside. +- Don't display disabled limbs on sonar (i.e. severed limbs that have "faded out"). +- Close the save/load dialogs when leaving the sub editor. Otherwise they'll still be visible when +re-entering the editor, and saving at that point will overwrite the previously loaded sub with an empty one. +- Removing an item after it's been combined doesn't trigger the OnBroken StatusEffects (e.g. combining two +half-full flash powder jars doesn't cause them to explode). +- Fixed welding tools and plasma cutters not hitting targets if the barrel is inside the target (e.g. if +trying to weld a completely broken wall with the cutter partially inside the wall). +- Fixed very small mineral colliders that made them extremely hard to hit with the plasma cutter. +- Fixed items with no sprite crashing the game (now they just cause a console error). +- Don't allow autointeracting with contained items (e.g. picking up an ammunition box from a loader) +if another item is currently selected. Makes it less likely for players to accidentally pick up items +from containers when they deselect another item. +- Fixed characters not letting go of the character they're grabbing when the health interface is closed +by clicking outside the window. +- Fixed CrewManager throwing errors if changing resolutions when a crew member is dead. +- Fixed items emitting light from inside containers in the sub editor. + --------------------------------------------------------------------------------------------------------- v0.8.9.7 --------------------------------------------------------------------------------------------------------- @@ -9,6 +115,8 @@ Additions and changes: - Clients communicate syncing errors to the server, and the server logs a more descriptive error about what went wrong. Should make it easier to diagnose disconnection issues from now on. - Ending a multiplayer campaign round by talking to watchman doesn't require any special permissions. +- Server automatically ends rounds if there have been no players alive in 60 seconds and respawning +is not allowed during the round. - Added a button for resetting an entity's properties to the default values to the sub editor. - Updated handheld sonar UI graphics.