diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs index 73c1c27ad..a237a6a1e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs @@ -28,6 +28,7 @@ namespace Barotrauma } 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); + GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SoundRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1); } if (sightRange > 0.0f) { @@ -39,8 +40,6 @@ namespace Barotrauma else if (Entity is Item) { color = Color.CadetBlue; - // disable the indicators for items, because they clutter the debug view - return; } else { @@ -50,6 +49,7 @@ namespace Barotrauma } 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); + GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SightRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs index d904ca184..726985bb1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs @@ -16,9 +16,9 @@ namespace Barotrauma }*/ } - partial void SetOrderProjSpecific(Order order) + partial void SetOrderProjSpecific(Order order, string option) { - GameMain.GameSession.CrewManager.DisplayCharacterOrder(Character, order); + GameMain.GameSession.CrewManager.DisplayCharacterOrder(Character, order, option); } public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index adeb499d2..62ef87c61 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -171,10 +171,10 @@ namespace Barotrauma if (GUI.DisableHUD) { return; } character.CharacterHealth.Alignment = Alignment.Right; - if (Screen.Selected == GameMain.GameScreen) + /*if (Screen.Selected == GameMain.GameScreen) { GUI.InfoAreaBackground.Draw(spriteBatch, Vector2.Zero, scale: GUI.Scale); - } + }*/ if (GameMain.GameSession?.CrewManager != null) { @@ -318,7 +318,7 @@ namespace Barotrauma if (ShouldDrawInventory(character)) { character.Inventory.Locked = LockInventory(character); - character.Inventory.DrawOwn(spriteBatch); + character.Inventory.DrawThis(spriteBatch); character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ? CharacterInventory.Layout.Default : CharacterInventory.Layout.Right; @@ -333,7 +333,7 @@ namespace Barotrauma { ///character.Inventory.CurrentLayout = Alignment.Left; character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left; - character.SelectedCharacter.Inventory.DrawOwn(spriteBatch); + character.SelectedCharacter.Inventory.DrawThis(spriteBatch); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 761d082b3..c28055df8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -134,7 +134,8 @@ namespace Barotrauma private float heartbeatTimer; private Texture2D heartrateFade; - private HeartratePosition[] heartbeatPattern = { + private readonly HeartratePosition[] heartbeatPattern = + { new HeartratePosition() { Time = 0.0f, Height = 0.0f }, new HeartratePosition() { Time = 0.15f, Height = 0.2f }, new HeartratePosition() { Time = 0.2f, Height = -0.2f }, @@ -174,7 +175,7 @@ namespace Barotrauma // 0-1 private float damageIntensity; - private float damageIntensityDropdownRate = 0.1f; + private readonly float damageIntensityDropdownRate = 0.1f; public float DamageOverlayTimer { get; private set; } @@ -259,14 +260,14 @@ namespace Barotrauma bool horizontal = true; healthBar = new GUIProgressBar(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAreaLeft, GUI.Canvas), - barSize: 1.0f, color: GUIColorSettings.HealthBarColorHigh, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical", false) + barSize: 1.0f, color: GUIColorSettings.HealthBarColorHigh, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical") { Enabled = true, HoverCursor = CursorState.Hand, IsHorizontal = horizontal }; healthBarShadow = new GUIProgressBar(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAreaLeft, GUI.Canvas), - barSize: 1.0f, color: Color.Green, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical", false) + barSize: 1.0f, color: Color.Green, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical", showFrame: false) { IsHorizontal = horizontal }; @@ -281,9 +282,13 @@ namespace Barotrauma //limb selection frame healthWindow = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), healthWindowContainer.RectTransform, Anchor.CenterRight, Pivot.CenterRight), style: "GUIFrameListBox"); - var healthWindowVerticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, healthWindow.RectTransform, Anchor.Center)); + var healthWindowVerticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, healthWindow.RectTransform, Anchor.Center)) + { + Stretch = true, + RelativeSpacing = 0.03f + }; - var paddedHealthWindow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.95f), healthWindowVerticalLayout.RectTransform), true) + var paddedHealthWindow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.95f), healthWindowVerticalLayout.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.03f @@ -292,7 +297,7 @@ namespace Barotrauma var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.6f, 1.0f), paddedHealthWindow.RectTransform), (spriteBatch, component) => { - DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true, false); + DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true); }, (deltaTime, component) => { @@ -332,11 +337,15 @@ namespace Barotrauma GUILayoutGroup selectedLimbLayout = new GUILayoutGroup(new RectTransform(Vector2.One, rightSide.RectTransform)); - selectedLimbText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.08f), selectedLimbLayout.RectTransform), "", font: GUI.LargeFont); - selectedLimbText.AutoScaleHorizontal = true; + selectedLimbText = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.08f), selectedLimbLayout.RectTransform), "", font: GUI.LargeFont, textAlignment: Alignment.Center) + { + AutoScaleHorizontal = true + }; - afflictionIconContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.92f), selectedLimbLayout.RectTransform), style: null); - afflictionIconContainer.KeepSpaceForScrollBar = true; + afflictionIconContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.92f), selectedLimbLayout.RectTransform), style: null) + { + KeepSpaceForScrollBar = true + }; var healthBarContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.07f), healthWindowVerticalLayout.RectTransform), style: null); @@ -393,7 +402,7 @@ namespace Barotrauma new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), textLayout.RectTransform), style: "HorizontalLine"); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textLayout.RectTransform, Anchor.TopLeft), TextManager.Get("SuitableTreatments"), textAlignment: Alignment.TopLeft); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textLayout.RectTransform, Anchor.TopLeft), TextManager.Get("SuitableTreatments"), font: GUI.SubHeadingFont); treatmentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), textLayout.RectTransform), true) { @@ -431,9 +440,11 @@ namespace Barotrauma heartbeatTimer = 0.46f; - heartratePositions = new List(); - heartratePositions.Add(heartbeatPattern.First()); - heartratePositions.Add(heartbeatPattern.Last()); + heartratePositions = new List + { + heartbeatPattern.First(), + heartbeatPattern.Last() + }; cprButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), cprLayout.RectTransform, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton") { @@ -718,16 +729,17 @@ namespace Barotrauma if (afflictionTooltip == null || afflictionTooltip.UserData != affliction) { - afflictionTooltip = new GUIListBox(new RectTransform(new Vector2(0.4f, 0.2f), GUI.Canvas, scaleBasis: ScaleBasis.Smallest)); - afflictionTooltip.UserData = affliction; - - afflictionTooltip.CanBeFocused = false; + afflictionTooltip = new GUIListBox(new RectTransform(new Vector2(0.4f, 0.2f), GUI.Canvas, scaleBasis: ScaleBasis.Smallest)) + { + UserData = affliction, + CanBeFocused = false + }; CreateAfflictionInfoElements(afflictionTooltip.Content, affliction); int height = afflictionTooltip.Content.Children.Sum(c => c.Rect.Height) + 10; afflictionTooltip.RectTransform.Resize(new Point(afflictionTooltip.Rect.Width, height), true); - afflictionTooltip.RectTransform.AbsoluteOffset = GUI.MouseOn.Rect.Center; + afflictionTooltip.RectTransform.AbsoluteOffset = new Point(GUI.MouseOn.Rect.Right, GUI.MouseOn.Rect.Y); afflictionTooltip.ScrollBarVisible = false; var labelContainer = afflictionTooltip.Content.GetChildByUserData("label"); @@ -1105,6 +1117,9 @@ namespace Barotrauma Dictionary treatmentSuitability = new Dictionary(); GetSuitableTreatments(treatmentSuitability, normalize: true, randomization: randomVariance); + Affliction mostSevereAffliction = afflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !afflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? afflictions.FirstOrDefault(); + GUIButton buttonToSelect = null; + foreach (Affliction affliction in afflictions) { var child = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconContainer.Content.RectTransform, Anchor.TopCenter)) @@ -1117,16 +1132,24 @@ namespace Barotrauma Color = Color.Gray.Multiply(0.1f).Opaque(), HoverColor = Color.Gray.Multiply(0.4f).Opaque(), SelectedColor = Color.Gray.Multiply(0.25f).Opaque(), - PressedColor = Color.Black, + PressedColor = Color.Gray.Multiply(0.2f).Opaque(), UserData = "selectaffliction", OnClicked = SelectAffliction }; - new GUIImage(new RectTransform(Vector2.One * 0.9f, button.RectTransform, Anchor.Center), affliction.Prefab.Icon, scaleToFit: true) + if (affliction == mostSevereAffliction) + { + buttonToSelect = button; + } + + var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.9f, button.RectTransform, Anchor.Center), affliction.Prefab.Icon, scaleToFit: true) { Color = GetAfflictionIconColor(affliction.Prefab, affliction), CanBeFocused = false }; + afflictionIcon.PressedColor = afflictionIcon.Color; + afflictionIcon.HoverColor = Color.Lerp(afflictionIcon.Color, Color.White, 0.6f); + afflictionIcon.SelectedColor = Color.Lerp(afflictionIcon.Color, Color.White, 0.5f); float afflictionVitalityDecrease = affliction.GetVitalityDecrease(this); @@ -1140,7 +1163,7 @@ namespace Barotrauma afflictionEffectColor = GUI.Style.Green; } - new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.1f), child.RectTransform), 0.0f, afflictionEffectColor) + new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.1f), child.RectTransform), 0.0f, afflictionEffectColor, style: "CharacterHealthBar") { UserData = "afflictionstrength" }; @@ -1148,6 +1171,8 @@ namespace Barotrauma child.Recalculate(); } + if (buttonToSelect != null) { buttonToSelect.OnClicked(buttonToSelect, "selectaffliction"); } + afflictionIconContainer.RecalculateChildren(); List> treatmentSuitabilities = treatmentSuitability.OrderByDescending(t => t.Value).ToList(); @@ -1157,8 +1182,7 @@ namespace Barotrauma { count++; if (count > 5) { break; } - ItemPrefab item = MapEntityPrefab.Find(name: null, identifier: treatment.Key, showErrorMessages: false) as ItemPrefab; - if (item == null) continue; + if (!(MapEntityPrefab.Find(name: null, identifier: treatment.Key, showErrorMessages: false) is ItemPrefab item)) { continue; } var itemSlot = new GUIFrame(new RectTransform(new Vector2(1.0f / 7.0f, 1.0f), recommendedTreatmentContainer.Content.RectTransform, Anchor.TopLeft), style: null) @@ -1299,7 +1323,9 @@ namespace Barotrauma private void UpdateHeartrate(float deltaTime, GUICustomComponent component) { - heartbeatTimer -= deltaTime * 0.75f; + if (GameMain.Instance.Paused) { return; } + + heartbeatTimer -= deltaTime * 0.5f; if (heartbeatTimer <= 0.0f) { @@ -1335,7 +1361,7 @@ namespace Barotrauma } } - currentHeartrateTime += deltaTime * 0.75f; + currentHeartrateTime += deltaTime * 0.5f; while (currentHeartrateTime >= 1.0f) { currentHeartrateTime -= 1.0f; @@ -1377,9 +1403,9 @@ namespace Barotrauma Rectangle sourceRect = heartrateFade.Bounds; - Rectangle destinationRectangle = new Rectangle(); - destinationRectangle.Location = new Point((int)(currentHeartrateTime * targetRect.Width) + targetRect.Left - targetRect.Height, targetRect.Top); - destinationRectangle.Size = new Point((int)(targetRect.Height * ((float)sourceRect.Width / (float)sourceRect.Height)), targetRect.Height); + Rectangle destinationRectangle = new Rectangle( + new Point((int)(currentHeartrateTime * targetRect.Width) + targetRect.Left - targetRect.Height, targetRect.Top), + new Point((int)(targetRect.Height * ((float)sourceRect.Width / (float)sourceRect.Height)), targetRect.Height)); if (destinationRectangle.Left < targetRect.Left) { @@ -1557,7 +1583,7 @@ namespace Barotrauma } } - private void DrawHealthWindow(SpriteBatch spriteBatch, Rectangle drawArea, bool allowHighlight, bool highlightAll) + private void DrawHealthWindow(SpriteBatch spriteBatch, Rectangle drawArea, bool allowHighlight) { if (Character.Removed) { return; } @@ -1586,16 +1612,16 @@ namespace Barotrauma float midPoint = (float)limbEffectiveArea.Center.Y / (float)limbHealth.IndicatorSprite.Texture.Height; float fadeDist = 0.6f * (float)limbEffectiveArea.Height / (float)limbHealth.IndicatorSprite.Texture.Height; - if (negativeEffect > 0.0f && negativeEffect < 5.0f) { negativeEffect = 5.0f; } - if (positiveEffect > 0.0f && positiveEffect < 5.0f) { positiveEffect = 5.0f; } + if (negativeEffect > 0.0f && negativeEffect < 5.0f) { negativeEffect = 10.0f; } + if (positiveEffect > 0.0f && positiveEffect < 5.0f) { positiveEffect = 10.0f; } - Color positiveColor = Color.Lerp(Color.Orange, Color.Lime, Math.Min(positiveEffect / 15.0f, 1.0f)); - Color negativeColor = Color.Lerp(Color.Orange, Color.Red, Math.Min(negativeEffect / 15.0f, 1.0f)); + Color positiveColor = Color.Lerp(Color.Orange, Color.Lime, Math.Min(positiveEffect / 25.0f, 1.0f)); + Color negativeColor = Color.Lerp(Color.Orange, Color.Red, Math.Min(negativeEffect / 25.0f, 1.0f)); Color color1 = Color.Orange; Color color2 = Color.Orange; - if (negativeEffect+positiveEffect > 0.0f) + if (negativeEffect + positiveEffect > 0.0f) { if (negativeEffect >= positiveEffect) { @@ -1614,11 +1640,6 @@ namespace Barotrauma color1 = Color.Lerp(color1, Color.Black, 0.75f); color2 = Color.Lerp(color2, Color.Black, 0.75f); } - if (((i == highlightedLimbIndex || i == selectedLimbIndex) && allowHighlight) || highlightAll) - { - color1 = Color.Lerp(color1, Color.White, 0.5f); - color2 = Color.Lerp(color2, Color.White, 0.5f); - } GameMain.GameScreen.GradientEffect.Parameters["color1"].SetValue(color1.ToVector4()); GameMain.GameScreen.GradientEffect.Parameters["color2"].SetValue(color2.ToVector4()); @@ -1666,36 +1687,75 @@ namespace Barotrauma limbIndicatorOverlay.Draw(spriteBatch, frame, drawArea.Center.ToVector2(), Color.Gray, origin: limbIndicatorOverlay.FrameSize.ToVector2() / 2, rotate: 0.0f, scale: Vector2.One * overlayScale); + if (allowHighlight) + { + i = 0; + foreach (LimbHealth limbHealth in limbHealths) + { + if (limbHealth.HighlightSprite == null) { continue; } + + float scale = Math.Min(drawArea.Width / (float)limbHealth.HighlightSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.HighlightSprite.SourceRect.Height); + + int drawCount = 0; + if (i == highlightedLimbIndex) { drawCount++; } + if (i == selectedLimbIndex) { drawCount++; } + for (int j = 0; j < drawCount; j++) + { + limbHealth.HighlightSprite.Draw(spriteBatch, + drawArea.Center.ToVector2(), Color.White, + limbHealth.HighlightSprite.Origin, + 0, scale); + } + i++; + } + } spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, blendState: BlendState.NonPremultiplied, rasterizerState: GameMain.ScissorTestEnable); i = 0; foreach (LimbHealth limbHealth in limbHealths) { - if (limbHealth.IndicatorSprite == null) continue; + IEnumerable thisAfflictions = limbHealth.Afflictions.Where(a => a.Strength >= a.Prefab.ShowIconThreshold); + thisAfflictions = thisAfflictions.Concat(afflictions.Where(a => + { + Limb indicatorLimb = Character.AnimController.GetLimb(a.Prefab.IndicatorLimb); + return (indicatorLimb != null && indicatorLimb.HealthIndex == i && a.Strength >= a.Prefab.ShowIconThreshold); + })); + if (thisAfflictions.Count() <= 0) { i++; continue; } + if (limbHealth.IndicatorSprite == null) { continue; } + + float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height); + Rectangle highlightArea = GetLimbHighlightArea(limbHealth, drawArea); - float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height); - - float iconScale = 0.3f * scale; + float iconScale = 0.25f * scale; Vector2 iconPos = highlightArea.Center.ToVector2(); - foreach (Affliction affliction in limbHealth.Afflictions) + + Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault(); + + if (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); } + + if (thisAfflictions.Count() > 1) { - DrawLimbAfflictionIcon(spriteBatch, affliction, iconScale, ref iconPos); + string additionalAfflictionCount = $"+{thisAfflictions.Count() - 1}"; + Vector2 displace = GUI.SubHeadingFont.MeasureString(additionalAfflictionCount); + GUI.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X * 1.1f, -displace.Y * 0.45f), Color.Black * 0.75f); + GUI.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X, -displace.Y * 0.5f), Color.White); } - foreach (Affliction affliction in afflictions) - { - Limb indicatorLimb = Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb); - if (indicatorLimb != null && indicatorLimb.HealthIndex == i) - { - DrawLimbAfflictionIcon(spriteBatch, affliction, iconScale, ref iconPos); - } - } i++; } - + + if (selectedLimbIndex > -1) + { + var selectedLimbArea = GetLimbHighlightArea(limbHealths[selectedLimbIndex], drawArea); + GUI.DrawLine(spriteBatch, + new Vector2(selectedLimbText.Rect.X, selectedLimbText.Rect.Center.Y), + selectedLimbArea.Center.ToVector2(), + Color.LightGray * 0.5f, width: 4); + } + if (draggingMed != null) { GUIImage itemImage = draggingMed.GetChild(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs index d07677347..767b91ebc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs @@ -32,7 +32,7 @@ namespace Barotrauma font: GUI.SmallFont); } - if (!ItemNames.TryGetValue(variant, out var itemNames)) { return backFrame; } + if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; } var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) }) { @@ -40,11 +40,12 @@ namespace Barotrauma }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform), TextManager.Get("Items", fallBackTag: "mapentitycategory.equipment"), font: GUI.LargeFont); - foreach (string itemName in itemNames.Distinct()) + foreach (string identifier in itemIdentifiers.Distinct()) { - int count = itemNames.Count(i => i == itemName); + if (!(MapEntityPrefab.Find(name: null, identifier: identifier) is ItemPrefab itemPrefab)) { continue; } + int count = itemIdentifiers.Count(i => i == identifier); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform), - " - " + (count == 1 ? itemName : itemName + " x" + count), + " - " + (count == 1 ? itemPrefab.Name : itemPrefab.Name + " x" + count), font: GUI.SmallFont); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs new file mode 100644 index 000000000..267103d4f --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs @@ -0,0 +1,25 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class CargoMission : Mission + { + public override void ClientReadInitial(IReadMessage msg) + { + ushort itemCount = msg.ReadUInt16(); + for (int i = 0; i < itemCount; i++) + { + items.Add(Item.ReadSpawnData(msg)); + } + if (items.Contains(null)) + { + throw new System.Exception("Error in CargoMission.ClientReadInitial: item list contains null (mission: " + Prefab.Identifier + ")"); + } + if (items.Count != itemCount) + { + throw new System.Exception("Error in CargoMission.ClientReadInitial: item count does not match the server count (" + itemCount + " != " + items.Count + "mission: " + Prefab.Identifier + ")"); + } + if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs index 8a69ad19e..8bda0040d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs @@ -1,4 +1,6 @@ -namespace Barotrauma +using Barotrauma.Networking; + +namespace Barotrauma { partial class CombatMission { @@ -18,5 +20,10 @@ return descriptions[GameMain.Client.Character.TeamID == Character.TeamType.Team1 ? 1 : 2]; } } + + public override void ClientReadInitial(IReadMessage msg) + { + //do nothing + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs index 05e283c08..b5f44164c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs @@ -23,5 +23,7 @@ namespace Barotrauma { State = msg.ReadInt16(); } + + public abstract void ClientReadInitial(IReadMessage msg); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs new file mode 100644 index 000000000..162968040 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs @@ -0,0 +1,25 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class MonsterMission : Mission + { + public override void ClientReadInitial(IReadMessage msg) + { + byte monsterCount = msg.ReadByte(); + for (int i = 0; i < monsterCount; i++) + { + monsters.Add(Character.ReadSpawnData(msg)); + } + if (monsters.Contains(null)) + { + throw new System.Exception("Error in MonsterMission.ClientReadInitial: monster list contains null (mission: " + Prefab.Identifier + ")"); + } + if (monsters.Count != monsterCount) + { + throw new System.Exception("Error in MonsterMission.ClientReadInitial: monster count does not match the server count (" + monsterCount + " != " + monsters.Count + "mission: " + Prefab.Identifier + ")"); + } + InitializeMonsters(monsters); + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs new file mode 100644 index 000000000..b455fea48 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs @@ -0,0 +1,19 @@ +using Barotrauma.Networking; +using FarseerPhysics; + +namespace Barotrauma +{ + partial class SalvageMission : Mission + { + public override void ClientReadInitial(IReadMessage msg) + { + item = Item.ReadSpawnData(msg); + if (item == null) + { + throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")"); + } + + item.body.FarseerBody.BodyType = BodyType.Kinematic; + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs index e6dc30273..820c4bc9d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs @@ -37,6 +37,8 @@ namespace Barotrauma } private float openState; + public bool CloseAfterMessageSent; + private float prevUIScale; //individual message texts that pop up when the chatbox is hidden @@ -286,6 +288,10 @@ namespace Barotrauma public void SetVisibility(bool visible) { GUIFrame.Parent.Visible = visible; + if (GameMain.GameSession?.CrewManager?.ReportButtonFrame != null) + { + GameMain.GameSession.CrewManager.ReportButtonFrame.Visible = visible; + } } private IEnumerable UpdateMessageAnimation(GUIComponent message, float animDuration) @@ -330,6 +336,17 @@ namespace Barotrauma prevUIScale = GUI.Scale; } + //hide chatbox when accessing the inventory of another character to prevent overlaps + if (Character.Controlled?.SelectedCharacter?.Inventory != null && + Character.Controlled.SelectedCharacter.CanInventoryBeAccessed) + { + SetVisibility(false); + } + else + { + SetVisibility(true); + } + if (showNewMessagesButton.Visible && chatBox.ScrollBar.BarScroll == 1f) { showNewMessagesButton.Visible = false; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs index 7da77c269..ec39f671c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs @@ -59,11 +59,15 @@ namespace Barotrauma public readonly GUIStyle Style; + public readonly string Name; + public int? Width { get; private set; } public int? Height { get; private set; } public GUIComponentStyle(XElement element, GUIStyle style) { + Name = element.Name.LocalName; + Style = style; Element = element; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index 033c40afd..01387431c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -133,6 +133,7 @@ namespace Barotrauma public static ScalableFont LargeFont => Style?.LargeFont; public static ScalableFont SubHeadingFont => Style?.SubHeadingFont; public static ScalableFont DigitalFont => Style?.DigitalFont; + public static ScalableFont HotkeyFont => Style?.HotkeyFont; public static ScalableFont CJKFont { get; private set; } @@ -988,6 +989,7 @@ namespace Barotrauma Debug.Assert(updateList.Count == updateListSet.Count); updateList.ForEach(c => c.UpdateAuto(deltaTime)); UpdateMessages(deltaTime); + UpdateInput(); } private static void UpdateMessages(float deltaTime) @@ -1027,6 +1029,32 @@ namespace Barotrauma messages.RemoveAll(m => m.Timer <= 0.0f); } + private static void UpdateInput() + { + if (PlayerInput.KeyHit(InputType.ToggleInventory)) + { + if (Character.Controlled?.Inventory != null) + { + Character.Controlled.Inventory.ToggleInventory(); + } + } + + if (PlayerInput.KeyHit(Keys.Escape) && GameMain.WindowActive) + { + HandleEscFunctionality(); + } + +#if DEBUG + if (GameMain.NetworkMember == null) + { + if (PlayerInput.KeyHit(Keys.P) && !(KeyboardDispatcher.Subscriber is GUITextBox)) + { + DebugConsole.Paused = !DebugConsole.Paused; + } + } +#endif + } + #region Element drawing public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color) @@ -1760,32 +1788,30 @@ namespace Barotrauma if (!rect1.Intersects(rect2)) continue; intersections = true; - int rect1Area = rect1.Width * rect1.Height; - int rect2Area = rect2.Width * rect2.Height; Point centerDiff = rect1.Center - rect2.Center; //move the interfaces away from each other, in a random direction if they're at the same position Vector2 moveAmount = centerDiff == Point.Zero ? Rand.Vector(1.0f) : Vector2.Normalize(centerDiff.ToVector2()); //if the horizontal move amount is much larger than vertical, only move horizontally //(= attempt to place the elements side-by-side if they're more apart horizontally than vertically) - if (Math.Abs(moveAmount.X) > Math.Abs(moveAmount.Y) * 5.0f) + if (Math.Abs(moveAmount.X) > Math.Abs(moveAmount.Y) * 8.0f) { moveAmount.Y = 0.0f; } //same for the y-axis - else if (Math.Abs(moveAmount.Y) > Math.Abs(moveAmount.X) * 5.0f) + else if (Math.Abs(moveAmount.Y) > Math.Abs(moveAmount.X) * 8.0f) { moveAmount.X = 0.0f; } //make sure we don't move the interfaces out of the screen - Vector2 moveAmount1 = ClampMoveAmount(rect1, area, moveAmount * 20.0f * rect1Area / (rect1Area + rect2Area)); - Vector2 moveAmount2 = ClampMoveAmount(rect2, area, -moveAmount * 20.0f * rect1Area / (rect1Area + rect2Area)); + Vector2 moveAmount1 = ClampMoveAmount(rect1, area, moveAmount * 10.0f); + Vector2 moveAmount2 = ClampMoveAmount(rect2, area, -moveAmount * 10.0f); //move by 10 units in the desired direction and repeat until nothing overlaps //(or after 100 iterations, in which case we'll just give up and let them overlap) - elements[i].RectTransform.ScreenSpaceOffset += (moveAmount1).ToPoint(); - elements[j].RectTransform.ScreenSpaceOffset += (moveAmount2).ToPoint(); + elements[i].RectTransform.ScreenSpaceOffset += moveAmount1.ToPoint(); + elements[j].RectTransform.ScreenSpaceOffset += moveAmount2.ToPoint(); } if (disallowedAreas == null) continue; @@ -1834,6 +1860,50 @@ namespace Barotrauma #endregion #region Misc + private static void HandleEscFunctionality() + { + // Check if a text input is selected. + if (KeyboardDispatcher.Subscriber != null) + { + if (KeyboardDispatcher.Subscriber is GUITextBox textBox) + { + textBox.Deselect(); + } + KeyboardDispatcher.Subscriber = null; + } + //if a verification prompt (are you sure you want to x) is open, close it + else if (GUIMessageBox.VisibleBox as GUIMessageBox != null && + GUIMessageBox.VisibleBox.UserData as string == "verificationprompt") + { + ((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); + } + else if (Tutorials.Tutorial.Initialized && Tutorials.Tutorial.ContentRunning) + { + (GameMain.GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI(); + } + else if (PauseMenuOpen) + { + TogglePauseMenu(); + } + //open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC + else if ((Character.Controlled == null || !itemHudActive()) + //TODO: do we need to check Inventory.SelectedSlot? + && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null + && !CrewManager.IsCommandInterfaceOpen) + { + // Otherwise toggle pausing, unless another window/interface is open. + TogglePauseMenu(); + } + + bool itemHudActive() + { + if (Character.Controlled?.SelectedConstruction == null) { return false; } + return + Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null) || + ((Character.Controlled.ViewTarget as Item)?.Prefab?.FocusOnSelected ?? false); + } + } + public static void TogglePauseMenu() { if (Screen.Selected == GameMain.MainMenuScreen) return; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIColorSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIColorSettings.cs index 4f509c3ee..dd7f2765f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIColorSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIColorSettings.cs @@ -5,22 +5,23 @@ namespace Barotrauma public class GUIColorSettings { // Inventory - public static Color InventorySlotColor = new Color(78, 114, 88); - public static Color EquipmentSlotColor = new Color(72, 58, 25); - public static Color EquipmentSlotIconColor = new Color(99, 70, 64); + public static readonly Color InventorySlotColor = new Color(27, 140, 132); + public static readonly Color InventorySlotEquippedColor = new Color(211, 227, 217); + public static readonly Color EquipmentSlotEmptyColor = new Color(152, 148, 128); + public static readonly Color EquipmentSlotColor = new Color(225, 211, 189); + public static readonly Color EquipmentSlotIconColor = new Color(99, 70, 64); // Health HUD - public static Color BuffColorLow = Color.LightGreen; - public static Color BuffColorMedium = Color.Green; - public static Color BuffColorHigh = Color.DarkGreen; + public static readonly Color BuffColorLow = Color.LightGreen; + public static readonly Color BuffColorMedium = Color.Green; + public static readonly Color BuffColorHigh = Color.DarkGreen; - public static Color DebuffColorLow = Color.DarkSalmon; - public static Color DebuffColorMedium = Color.Red; - public static Color DebuffColorHigh = Color.DarkRed; - - public static Color HealthBarColorLow = Color.Red; - public static Color HealthBarColorMedium = Color.Orange; - public static Color HealthBarColorHigh = new Color(78, 114, 88); + public static readonly Color DebuffColorLow = Color.DarkSalmon; + public static readonly Color DebuffColorMedium = Color.Red; + public static readonly Color DebuffColorHigh = Color.DarkRed; + public static readonly Color HealthBarColorLow = Color.Red; + public static readonly Color HealthBarColorMedium = Color.Orange; + public static readonly Color HealthBarColorHigh = new Color(78, 114, 88); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs index 6e1b33015..c291a99bc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs @@ -517,6 +517,7 @@ namespace Barotrauma return state switch { ComponentState.Hover => HoverColor, + ComponentState.HoverSelected => HoverColor, ComponentState.Pressed => PressedColor, ComponentState.Selected => SelectedColor, _ => Color, diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs index 1dc280621..b084cbd7f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs @@ -83,7 +83,7 @@ namespace Barotrauma get { return floatValue; } set { - if (value == floatValue) return; + if (MathUtils.NearlyEqual(value, floatValue)) return; floatValue = value; ClampFloatValue(); float newValue = floatValue; @@ -134,7 +134,6 @@ namespace Barotrauma if (value == intValue) return; intValue = value; UpdateText(); - OnValueChanged?.Invoke(this); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index 983b32e6c..531281f4e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -24,6 +24,7 @@ namespace Barotrauma public ScalableFont LargeFont { get; private set; } public ScalableFont SubHeadingFont { get; private set; } public ScalableFont DigitalFont { get; private set; } + public ScalableFont HotkeyFont { get; private set; } public Dictionary ForceFontUpperCase { @@ -63,8 +64,8 @@ namespace Barotrauma public Color TextColorDark { get; private set; } = Color.Black * 0.9f; public Color TextColorDim { get; private set; } = Color.White * 0.6f; - public static Point ItemFrameMargin = new Point(50, 56).Multiply(GUI.SlicedSpriteScale); - public static Point ItemFrameOffset = new Point(0, 3).Multiply(GUI.SlicedSpriteScale); + public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale); + public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale); public GUIStyle(XElement element, GraphicsDevice graphicsDevice) { @@ -148,6 +149,10 @@ namespace Barotrauma DigitalFont = LoadFont(subElement, graphicsDevice); ForceFontUpperCase[DigitalFont] = subElement.GetAttributeBool("forceuppercase", false); break; + case "hotkeyfont": + HotkeyFont = LoadFont(subElement, graphicsDevice); + ForceFontUpperCase[HotkeyFont] = subElement.GetAttributeBool("forceuppercase", false); + break; case "objectivetitle": case "subheading": SubHeadingFont = LoadFont(subElement, graphicsDevice); @@ -236,8 +241,20 @@ namespace Barotrauma return new ScalableFont(file, size, graphicsDevice, dynamicLoading, isCJK); } - private uint GetFontSize(XElement element) + private uint GetFontSize(XElement element, uint defaultSize = 14) { + //check if any of the language override fonts want to override the font size as well + foreach (XElement subElement in element.Elements()) + { + if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; } + string language = subElement.GetAttributeString("language", "").ToLowerInvariant(); + if (GameMain.Config.Language.ToLowerInvariant() == language) + { + uint overrideFontSize = GetFontSize(subElement, 0); + if (overrideFontSize > 0) { return overrideFontSize; } + } + } + foreach (XElement subElement in element.Elements()) { if (subElement.Name.ToString().ToLowerInvariant() != "size") { continue; } @@ -247,7 +264,7 @@ namespace Barotrauma return (uint)subElement.GetAttributeInt("size", 14); } } - return 14; + return defaultSize; } private string GetFontFilePath(XElement element) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs index e513b7ba4..f730a55f2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs @@ -6,14 +6,18 @@ using System.Collections.Generic; using System.Xml.Linq; using Barotrauma.Media; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { class LoadingScreen { - private Texture2D backgroundTexture; + private readonly Texture2D defaultBackgroundTexture, overlay; + private readonly SpriteSheet decorativeGraph, decorativeMap; + private Texture2D currentBackgroundTexture; + private Sprite noiseSprite; - private RenderTarget2D renderTarget; + private string randText = ""; private Sprite languageSelectionCursor; private ScalableFont languageSelectionFont, languageSelectionFontCJK; @@ -65,15 +69,9 @@ namespace Barotrauma } } - private float state; - private string selectedTip; - public Vector2 BackgroundPosition; - - public Vector2 TitlePosition; - - private object loadMutex = new object(); + private readonly object loadMutex = new object(); private float? loadState; public float? LoadState @@ -109,15 +107,13 @@ namespace Barotrauma public LoadingScreen(GraphicsDevice graphics) { - backgroundTexture = TextureLoader.FromFile("Content/UI/titleBackground.png"); + defaultBackgroundTexture = TextureLoader.FromFile("Content/Map/LocationPortraits/AlienRuins.png"); - renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight); - GameMain.Instance.OnResolutionChanged += () => - { - renderTarget?.Dispose(); - renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight); - }; + decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640)); + decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732)); + overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png"); + noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero); DrawLoadingText = true; selectedTip = TextManager.Get("LoadingScreenTip", true); } @@ -147,41 +143,35 @@ namespace Barotrauma drawn = true; - graphics.SetRenderTarget(renderTarget); - - float backgroundScale = GameMain.GraphicsHeight / 1500.0f; - float titleScale = MathHelper.SmoothStep(0.8f, 1.0f, state / 10.0f) * GameMain.GraphicsHeight / 1000.0f; - - state += deltaTime; - - if (DrawLoadingText) - { - BackgroundPosition = new Vector2(GameMain.GraphicsWidth * 0.3f, GameMain.GraphicsHeight * 0.45f); - TitlePosition = new Vector2(GameMain.GraphicsWidth * 0.5f, GameMain.GraphicsHeight * 0.45f); - } - - spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState); - graphics.Clear(Color.Black); - - spriteBatch.Draw(backgroundTexture, BackgroundPosition, null, Color.White * Math.Min(state / 5.0f, 1.0f), 0.0f, - new Vector2(backgroundTexture.Width / 2.0f, backgroundTexture.Height / 2.0f), - backgroundScale * 1.5f, SpriteEffects.None, 0.2f); - - titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale); - - spriteBatch.End(); - - graphics.SetRenderTarget(null); - - if (WaterRenderer.Instance != null) - { - WaterRenderer.Instance.ScrollWater(Vector2.One * 10.0f, deltaTime); - WaterRenderer.Instance.RenderWater(spriteBatch, renderTarget, null); - } + currentBackgroundTexture ??= defaultBackgroundTexture; spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState); - titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale); + float scale = (GameMain.GraphicsWidth / (float)currentBackgroundTexture.Width) * 1.2f; + float paddingX = currentBackgroundTexture.Width * scale - GameMain.GraphicsWidth; + float paddingY = currentBackgroundTexture.Height * scale - GameMain.GraphicsHeight; + + double noiseT = (Timing.TotalTime * 0.02f); + Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f); + pos = new Vector2(pos.X * paddingX, pos.Y * paddingY); + + spriteBatch.Draw(currentBackgroundTexture, + new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos, + null, Color.White, 0.0f, new Vector2(currentBackgroundTexture.Width / 2, currentBackgroundTexture.Height / 2), + scale, SpriteEffects.None, 0.0f); + + spriteBatch.Draw(overlay, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), null, Color.White, 0.0f, Vector2.Zero, SpriteEffects.None, 0.0f); + + float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0); + float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f; + noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight), + startOffset: new Point(Rand.Range(0, noiseSprite.SourceRect.Width), Rand.Range(0, noiseSprite.SourceRect.Height)), + color: Color.White * noiseStrength * 0.1f, + textureScale: Vector2.One * noiseScale); + + titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f), + Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f), + scale: GameMain.GraphicsHeight / 2000.0f); if (WaitForLanguageSelection) { @@ -218,7 +208,7 @@ namespace Barotrauma if (GUI.LargeFont != null) { GUI.LargeFont.DrawString(spriteBatch, loadText.ToUpper(), - new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText).X / 2.0f, GameMain.GraphicsHeight * 0.7f), + new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText.ToUpper()).X / 2.0f, GameMain.GraphicsHeight * 0.75f), Color.White); } } @@ -232,12 +222,49 @@ namespace Barotrauma for (int i = 0; i < lines.Length; i++) { GUI.Font.DrawString(spriteBatch, lines[i], - new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.78f + i * lineHeight)), Color.White); + new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White); } } } spriteBatch.End(); + + spriteBatch.Begin(blendState: BlendState.Additive); + + Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f); + + float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0); + decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal), + new Vector2(GameMain.GraphicsWidth * 0.001f, GameMain.GraphicsHeight * 0.24f), + Color.White, Vector2.Zero, 0.0f, decorativeScale, SpriteEffects.FlipVertically); + + decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal), + new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.66f), + Color.White, decorativeMap.FrameSize.ToVector2(), 0.0f, decorativeScale); + + if (noiseVal < 0.2f) + { + //SCP-CB reference + randText = (new string[] { "NIL", "black white gray", "Sometimes we would have had time to scream", "e8m106]af", "NO" }).GetRandom(); + } + else if (noiseVal < 0.3f) + { + randText = ToolBox.RandomSeed(9); + } + else if (noiseVal < 0.5f) + { + randText = + Rand.Int(100).ToString().PadLeft(2, '0') + " " + + Rand.Int(100).ToString().PadLeft(2, '0') + " " + + Rand.Int(100).ToString().PadLeft(2, '0') + " " + + Rand.Int(100).ToString().PadLeft(2, '0'); + } + + GUI.LargeFont?.DrawString(spriteBatch, randText, + new Vector2(GameMain.GraphicsWidth - decorativeMap.FrameSize.X * decorativeScale.X * 0.8f, GameMain.GraphicsHeight * 0.57f), + Color.White * (1.0f - noiseVal)); + + spriteBatch.End(); } private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice) @@ -336,6 +363,7 @@ namespace Barotrauma drawn = false; LoadState = null; selectedTip = TextManager.Get("LoadingScreenTip", true); + currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture; while (!drawn) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/RectTransform.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/RectTransform.cs index 0049ea9e5..feb3bb001 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/RectTransform.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/RectTransform.cs @@ -701,8 +701,16 @@ namespace Barotrauma public void MatchPivotToAnchor() => MatchPivotToAnchor(Anchor); + + private Point? animTargetPos; + public Point AnimTargetPos + { + get { return animTargetPos ?? AbsoluteOffset; } + } + public void MoveOverTime(Point targetPos, float duration) { + animTargetPos = targetPos; CoroutineManager.StartCoroutine(DoMoveAnimation(targetPos, duration)); } public void ScaleOverTime(Point targetSize, float duration) @@ -721,6 +729,7 @@ namespace Barotrauma yield return CoroutineStatus.Running; } AbsoluteOffset = targetPos; + animTargetPos = null; yield return CoroutineStatus.Success; } private IEnumerable DoScaleAnimation(Point targetSize, float duration) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs index 3d7f9c843..0186b5160 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs @@ -37,6 +37,12 @@ namespace Barotrauma private set; } + /// + /// How much the borders of a sliced sprite are allowed to scale + /// You may for example want to prevent a 1-pixel border from scaling down (and disappearing) on small resolutions + /// + private float minBorderScale = 0.1f, maxBorderScale = 10.0f; + public bool CrossFadeIn { get; private set; } = true; public bool CrossFadeOut { get; private set; } = true; @@ -58,6 +64,9 @@ namespace Barotrauma Vector4 sliceVec = element.GetAttributeVector4("slice", Vector4.Zero); if (sliceVec != Vector4.Zero) { + minBorderScale = element.GetAttributeFloat("minborderscale", 0.1f); + maxBorderScale = element.GetAttributeFloat("minborderscale", 10.0f); + Rectangle slice = new Rectangle((int)sliceVec.X, (int)sliceVec.Y, (int)(sliceVec.Z - sliceVec.X), (int)(sliceVec.W - sliceVec.Y)); Slice = true; @@ -103,7 +112,8 @@ namespace Barotrauma scale.Y = MathHelper.Clamp((float)rect.Height / (Slices[0].Height + Slices[6].Height), 0, 1); scale.X = MathHelper.Clamp((float)rect.Width / (Slices[0].Width + Slices[2].Width), 0, 1); - scale.X = scale.Y = Math.Min(Math.Min(scale.X, scale.Y), GUI.SlicedSpriteScale); + scale.X = scale.Y = + MathHelper.Clamp(Math.Min(Math.Min(scale.X, scale.Y), GUI.SlicedSpriteScale), minBorderScale, maxBorderScale); int centerHeight = rect.Height - (int)((Slices[0].Height + Slices[6].Height) * scale.Y); int centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale.X); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index a0df47916..72b0b2208 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -764,60 +764,6 @@ namespace Barotrauma SoundPlayer.Update((float)Timing.Step); - if (PlayerInput.KeyHit(Keys.Escape) && WindowActive) - { - // Check if a text input is selected. - if (GUI.KeyboardDispatcher.Subscriber != null) - { - if (GUI.KeyboardDispatcher.Subscriber is GUITextBox textBox) - { - textBox.Deselect(); - } - GUI.KeyboardDispatcher.Subscriber = null; - } - //if a verification prompt (are you sure you want to x) is open, close it - else if (GUIMessageBox.VisibleBox as GUIMessageBox != null && - GUIMessageBox.VisibleBox.UserData as string == "verificationprompt") - { - ((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); - } - else if (Tutorial.Initialized && Tutorial.ContentRunning) - { - (GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI(); - } - else if (GUI.PauseMenuOpen) - { - GUI.TogglePauseMenu(); - } - //open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC - else if ((Character.Controlled == null || !itemHudActive()) - //TODO: do we need to check Inventory.SelectedSlot? - && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null - && !CrewManager.IsCommandInterfaceOpen) - { - // Otherwise toggle pausing, unless another window/interface is open. - GUI.TogglePauseMenu(); - } - - bool itemHudActive() - { - if (Character.Controlled?.SelectedConstruction == null) { return false; } - return - Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null) || - ((Character.Controlled.ViewTarget as Item)?.Prefab?.FocusOnSelected ?? false); - } - } - -#if DEBUG - if (GameMain.NetworkMember == null) - { - if (PlayerInput.KeyHit(Keys.P) && !(GUI.KeyboardDispatcher.Subscriber is GUITextBox)) - { - DebugConsole.Paused = !DebugConsole.Paused; - } - } -#endif - GUI.ClearUpdateList(); Paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen || Tutorial.ContentRunning || DebugConsole.Paused) && (NetworkMember == null || !NetworkMember.GameStarted); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 3a3635029..1dc1cedbd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -4,6 +4,7 @@ using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; using System.Linq; @@ -58,7 +59,7 @@ namespace Barotrauma public List OrderOptionButtons = new List(); - private Sprite jobIndicatorBackground, previousOrderArrow; + private Sprite jobIndicatorBackground, previousOrderArrow, cancelIcon, crewMemberBackground; #endregion @@ -121,7 +122,6 @@ namespace Barotrauma return true; } }; - new GUIImage( new RectTransform(Vector2.One, commandButton.RectTransform), new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(551, 1, 79, 126)), @@ -134,6 +134,20 @@ namespace Barotrauma ToolTip = TextManager.Get("inputtype.command") }; + var keybindText = new GUITextBlock(new RectTransform(Vector2.One, commandButton.RectTransform), "", font: GUI.SmallFont, + textAlignment: Alignment.TopLeft, color: GUI.Style.TextColorBright) + { + TextGetter = () => + { + //hide the text if using a long non-default keybind + string txt = GameMain.Config.KeyBindText(InputType.CrewOrders); + return txt.Length > 2 ? "" : txt; + }, + HoverTextColor = GUI.Style.TextColorBright, + Padding = Vector4.One * 3, + CanBeFocused = false + }; + // AbsoluteOffset is set in UpdateProjectSpecific based on crewListOpenState crewList = new GUIListBox( new RectTransform( @@ -164,6 +178,8 @@ namespace Barotrauma jobIndicatorBackground = new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(0, 512, 128, 128)); previousOrderArrow = new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(128, 512, 128, 128)); + cancelIcon = new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(512, 384, 128, 128)); + crewMemberBackground = new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(0, 728, 320, 40)); #region Chatbox @@ -200,6 +216,11 @@ namespace Barotrauma } textbox.Deselect(); textbox.Text = ""; + if (ChatBox.CloseAfterMessageSent) + { + ChatBox.ToggleOpen = false; + ChatBox.CloseAfterMessageSent = false; + } return true; } }; @@ -271,6 +292,7 @@ namespace Barotrauma screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); prevUIScale = GUI.Scale; ToggleCrewListOpen = GameMain.Config.CrewMenuOpen; + dismissedOrderPrefab ??= Order.GetPrefab("dismissed"); } #endregion @@ -368,31 +390,22 @@ namespace Barotrauma { int width = crewList.Content.Rect.Width - HUDLayoutSettings.Padding; int height = Math.Max(45, (int)((1.0f / 8.0f) * width)); - Color backgroundColor = new Color(36, 37, 34) * 0.8f; - var characterButton = new GUIButton(new RectTransform(new Point(width, height), parent: crewList.Content.RectTransform, anchor: Anchor.TopRight), style: null, color: backgroundColor) + var background = new GUIImage( + new RectTransform(new Point(width, height), parent: crewList.Content.RectTransform, anchor: Anchor.TopRight), + crewMemberBackground, + scaleToFit: true) { UserData = character }; - characterButton.HoverColor = backgroundColor; - characterButton.PressedColor = backgroundColor; - characterButton.SelectedColor = backgroundColor; - if (IsSinglePlayer) - { - characterButton.OnClicked = CharacterClicked; - } - else - { - characterButton.CanBeSelected = false; - } - - var iconRelativeSize = (float)height / characterButton.Rect.Width; + var iconRelativeSize = (float)height / background.Rect.Width; var layoutGroup = new GUILayoutGroup( - new RectTransform(Vector2.One, parent: characterButton.RectTransform), + new RectTransform(Vector2.One, parent: background.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { + CanBeFocused = false, RelativeSpacing = 0.1f * iconRelativeSize, UserData = character }; @@ -421,22 +434,45 @@ namespace Barotrauma }; } + var relativeWidth = 1.0f - 4.5f * iconRelativeSize - 5 * layoutGroup.RelativeSpacing; new GUITextBlock( new RectTransform( - new Vector2(1.0f - 4.5f * iconRelativeSize - 5 * layoutGroup.RelativeSpacing, 1.0f), + new Vector2(relativeWidth, 1.0f), layoutGroup.RectTransform), - character.Name, + ToolBox.LimitString(character.Name, GUI.SubHeadingFont, (int)(relativeWidth * layoutGroup.Rect.Width)), textColor: character.Info?.Job?.Prefab?.UIColor) { - OverflowClip = true + CanBeFocused = false }; + var characterButton = new GUIButton( + new RectTransform( + new Vector2(iconRelativeSize + layoutGroup.RelativeSpacing + relativeWidth, 1.0f), + background.RectTransform), + style: null) + { + UserData = character + }; + if (IsSinglePlayer) + { + characterButton.OnClicked = CharacterClicked; + } + else + { + characterButton.CanBeFocused = false; + characterButton.CanBeSelected = false; + } + new GUIImage( new RectTransform(new Vector2(0.5f * iconRelativeSize, 0.5f), layoutGroup.RectTransform), - style: "VerticalLine"); + style: "VerticalLine") + { + CanBeFocused = false + }; var soundIcons = new GUIFrame(new RectTransform(new Vector2(iconRelativeSize, 0.8f), layoutGroup.RectTransform), style: null) { + CanBeFocused = false, UserData = "soundicons" }; new GUIImage( @@ -444,8 +480,8 @@ namespace Barotrauma GUI.Style.GetComponentStyle("GUISoundIcon").Sprites[GUIComponent.ComponentState.None].FirstOrDefault().Sprite, scaleToFit: true) { - UserData = new Pair("soundicon", 0.0f), CanBeFocused = false, + UserData = new Pair("soundicon", 0.0f), Visible = true }; new GUIImage( @@ -453,8 +489,8 @@ namespace Barotrauma "GUISoundIconDisabled", scaleToFit: true) { - UserData = "soundicondisabled", CanBeFocused = true, + UserData = "soundicondisabled", Visible = false }; } @@ -577,11 +613,9 @@ namespace Barotrauma { if (client?.Character == null) { return; } - var playerFrame = crewList.Content.GetChildByUserData(client.Character); - - if (playerFrame == null) { return; } - - if (playerFrame.FindChild(c => c is GUILayoutGroup).GetChildByUserData("soundicons") is GUIComponent soundIcons) + if (crewList.Content.GetChildByUserData(client.Character)? + .FindChild(c => c is GUILayoutGroup)? + .GetChildByUserData("soundicons") is GUIComponent soundIcons) { var soundIcon = soundIcons.FindChild(c => c.UserData is Pair pair && pair.First == "soundicon"); var soundIconDisabled = soundIcons.FindChild("soundicondisabled"); @@ -617,6 +651,12 @@ namespace Barotrauma /// public void SetCharacterOrder(Character character, Order order, string option, Character orderGiver) { + if (character == null) + { + //can't issue an order if no characters are available + return; + } + if (order != null && order.TargetAllCharacters) { if (orderGiver == null || orderGiver.CurrentHull == null) { return; } @@ -635,26 +675,24 @@ namespace Barotrauma } else { - DisplayPreviousCharacterOrder(character); - character.SetOrder(order, option, orderGiver, speak: orderGiver != character); if (IsSinglePlayer) { + character.SetOrder(order, option, orderGiver, speak: orderGiver != character); orderGiver?.Speak( order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null); } else if (orderGiver != null) { - OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver); + OrderChatMessage msg = new OrderChatMessage(order, option, order?.TargetItemComponent?.Item, character, orderGiver); GameMain.Client?.SendChatMessage(msg); } - DisplayCharacterOrder(character, order); } } /// /// Displays the specified order in the crew UI next to the character. /// - public void DisplayCharacterOrder(Character character, Order order) + public void DisplayCharacterOrder(Character character, Order order, string option) { if (character == null) { return; } @@ -663,15 +701,23 @@ namespace Barotrauma if (characterFrame == null) { return; } GUILayoutGroup layoutGroup = (GUILayoutGroup)characterFrame.FindChild(c => c is GUILayoutGroup); + layoutGroup.RemoveChild(GetPreviousOrderComponent(layoutGroup)); + var currentOrderComponent = GetCurrentOrderComponent(layoutGroup); - if (layoutGroup.GetChildByUserData("order") is GUIComponent existingOrderFrame) + if (order != null && currentOrderComponent != null) { - characterFrame.RemoveChild(existingOrderFrame); + var currentOrderInfo = (OrderInfo)currentOrderComponent.UserData; + + if (order.Identifier == currentOrderInfo.Order.Identifier && + option == currentOrderInfo.OrderOption && + order.TargetEntity == currentOrderInfo.Order.TargetEntity) { return; } + + DisplayPreviousCharacterOrder(character, layoutGroup, currentOrderInfo); } - if (order == null || order == dismissedOrder) { return; } + layoutGroup.RemoveChild(currentOrderComponent); - // TODO: Move the character to the top + if (order == null || order.Identifier == dismissedOrderPrefab.Identifier) { return; } var orderFrame = new GUIButton( new RectTransform( @@ -679,59 +725,46 @@ namespace Barotrauma layoutGroup.RectTransform), style: null) { - UserData = "order", + UserData = new OrderInfo(order, option), OnClicked = (button, userData) => { - if (GameMain.IsMultiplayer && Character.Controlled == null) { return true; } - SetCharacterOrder(character, dismissedOrder, null, Character.Controlled); + if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) { return false; } + + SetCharacterOrder(character, dismissedOrderPrefab, null, Character.Controlled); return true; } }; CreateNodeIcon(orderFrame.RectTransform, order.SymbolSprite, order.Color, tooltip: order.Name); new GUIImage( - new RectTransform(new Vector2(0.4f), orderFrame.RectTransform, anchor: Anchor.TopRight, pivot: Pivot.Center), - "GUICancelButton", + new RectTransform(Vector2.One, orderFrame.RectTransform), + cancelIcon, scaleToFit: true) { + CanBeFocused = false, UserData = "cancel", Visible = false }; orderFrame.RectTransform.RepositionChildInHierarchy(3); + characterFrame.SetAsFirstChild(); } - private void DisplayPreviousCharacterOrder(Character character) + private void DisplayPreviousCharacterOrder(Character character, GUILayoutGroup characterComponent, OrderInfo currentOrderInfo) { - if (character == null) { return; } - - var characterFrame = crewList.Content.GetChildByUserData(character); - - if (characterFrame == null) { return; } - - GUILayoutGroup layoutGroup = (GUILayoutGroup)characterFrame.FindChild(c => c is GUILayoutGroup); - - if (layoutGroup.GetChildByUserData("prevorder") is GUIComponent existingPrevOrderFrame) - { - characterFrame.RemoveChild(existingPrevOrderFrame); - } - - var order = character.CurrentOrder; - - if (order == null || order == dismissedOrder) { return; } - - - var orderOption = (character.AIController as HumanAIController)?.CurrentOrderOption; + if (currentOrderInfo.Order == null || currentOrderInfo.Order.Identifier == dismissedOrderPrefab.Identifier) { return; } + var previousOrderInfo = new OrderInfo(currentOrderInfo); var prevOrderFrame = new GUIButton( new RectTransform( - new Vector2(layoutGroup.GetChildByUserData("job").RectTransform.RelativeSize.X, 0.8f), - layoutGroup.RectTransform), + new Vector2(characterComponent.GetChildByUserData("job").RectTransform.RelativeSize.X, 0.8f), + characterComponent.RectTransform), style: null) { - UserData = "prevorder", + UserData = previousOrderInfo, OnClicked = (button, userData) => { - if (GameMain.IsMultiplayer && Character.Controlled == null) { return true; } - SetCharacterOrder(character, order, orderOption, Character.Controlled); + if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) { return false; } + var orderInfo = (OrderInfo)userData; + SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, Character.Controlled); return true; } }; @@ -739,7 +772,11 @@ namespace Barotrauma var prevOrderIconFrame = new GUIFrame( new RectTransform(new Vector2(0.8f), prevOrderFrame.RectTransform, anchor: Anchor.BottomLeft), style: null); - CreateNodeIcon(prevOrderIconFrame.RectTransform, order.SymbolSprite, order.Color, tooltip: order.Name); + CreateNodeIcon( + prevOrderIconFrame.RectTransform, + previousOrderInfo.Order.SymbolSprite, + previousOrderInfo.Order.Color, + tooltip: previousOrderInfo.Order.Name); foreach (GUIComponent c in prevOrderIconFrame.Children) { c.HoverColor = c.Color; @@ -751,9 +788,40 @@ namespace Barotrauma previousOrderArrow, scaleToFit: true) { - ToolTip = order.Name + CanBeFocused = false }; - prevOrderFrame.RectTransform.RepositionChildInHierarchy(layoutGroup.GetChildByUserData("order") != null ? 4 : 3); + prevOrderFrame.RectTransform.RepositionChildInHierarchy(GetCurrentOrderComponent(characterComponent) != null ? 4 : 3); + } + + private GUIComponent GetCurrentOrderComponent(GUILayoutGroup characterComponent) + { + return characterComponent.FindChild(c => c.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "currentorder"); + } + + private GUIComponent GetPreviousOrderComponent(GUILayoutGroup characterComponent) + { + return characterComponent.FindChild(c => c.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder"); + } + + private struct OrderInfo + { + public string ComponentIdentifier { get; set; } + public Order Order { get; private set; } + public string OrderOption { get; private set; } + + public OrderInfo(Order order, string orderOption) + { + ComponentIdentifier = "currentorder"; + Order = order; + OrderOption = orderOption; + } + + public OrderInfo(OrderInfo orderInfo) + { + ComponentIdentifier = "previousorder"; + Order = orderInfo.Order; + OrderOption = orderInfo.OrderOption; + } } #region Updating and drawing the UI @@ -802,7 +870,7 @@ namespace Barotrauma { if (!(c.UserData is Character character) || character.IsDead || character.Removed) { continue; } AddCharacter(character); - DisplayCharacterOrder(character, character.CurrentOrder); + DisplayCharacterOrder(character, character.CurrentOrder, (character.AIController as HumanAIController)?.CurrentOrderOption); } } @@ -871,7 +939,7 @@ namespace Barotrauma #region Command UI - if (PlayerInput.KeyDown(InputType.Command) && GUI.KeyboardDispatcher.Subscriber == null && + if (PlayerInput.KeyDown(InputType.Command) && (GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber == crewList) && (!GameMain.IsMultiplayer || (GameMain.IsMultiplayer && (Character.Controlled != null || DebugConsole.CheatsEnabled))) && commandFrame == null && !clicklessSelectionActive) { @@ -884,21 +952,29 @@ namespace Barotrauma if (canIssueOrders) { - CreateCommandUI(); + CreateCommandUI(GUI.MouseOn?.UserData as Character); clicklessSelectionActive = isOpeningClick = true; } } if (commandFrame != null) { - if ((GameMain.IsMultiplayer && !DebugConsole.CheatsEnabled && Character.Controlled == null)) + void ResetNodeSelection(GUIButton newSelectedNode = null) + { + selectedNode?.Children.ForEach(c => c.Color = c.HoverColor * nodeColorMultiplier); + selectedNode = newSelectedNode; + timeSelected = 0; + isSelectionHighlighted = false; + } + + if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) { DisableCommandUI(); } - else if (PlayerInput.RightButtonClicked() && - (optionNodes.Any(n => GUI.IsMouseOn(n)) || shortcutNodes.Any(n => GUI.IsMouseOn(n)))) + else if (PlayerInput.RightButtonClicked() && characterContext == null && + (optionNodes.Any(n => GUI.IsMouseOn(n.Item1)) || shortcutNodes.Any(n => GUI.IsMouseOn(n)))) { - var node = optionNodes.Find(n => GUI.IsMouseOn(n)); + var node = optionNodes.Find(n => GUI.IsMouseOn(n.Item1))?.Item1; if (node == null) { node = shortcutNodes.Find(n => GUI.IsMouseOn(n)); @@ -932,9 +1008,7 @@ namespace Barotrauma clicklessSelectionActive = isOpeningClick = false; if (selectedNode != null) { - selectedNode.Children.ForEach(c => c.Color = c.HoverColor * nodeColorMultiplier); - selectedNode = null; - timeSelected = 0; + ResetNodeSelection(); } } } @@ -949,7 +1023,7 @@ namespace Barotrauma GUIComponent closestNode = null; float closestBearing = 0; - optionNodes.ForEach(n => CheckIfClosest(n)); + optionNodes.ForEach(n => CheckIfClosest(n.Item1)); CheckIfClosest(returnNode); void CheckIfClosest(GUIComponent comp) @@ -978,27 +1052,49 @@ namespace Barotrauma timeSelected += deltaTime; if (timeSelected >= selectionTime) { - selectedNode.OnClicked.Invoke(selectedNode, selectedNode.UserData); - selectedNode = null; - timeSelected = 0; + selectedNode.OnClicked?.Invoke(selectedNode, selectedNode.UserData); + ResetNodeSelection(); + } + else if (timeSelected >= 0.15f && !isSelectionHighlighted) + { + selectedNode.Children.ForEach(c => c.Color = c.HoverColor); + isSelectionHighlighted = true; } } else { - if (selectedNode != null) - { - selectedNode.Children.ForEach(c => c.Color = c.HoverColor * nodeColorMultiplier); - } - selectedNode = closestNode as GUIButton; - selectedNode.Children.ForEach(c => c.Color = c.HoverColor); - timeSelected = 0; + ResetNodeSelection(closestNode as GUIButton); } } else if (selectedNode != null) { - selectedNode.Children.ForEach(c => c.Color = c.HoverColor * nodeColorMultiplier); - selectedNode = null; - timeSelected = 0; + ResetNodeSelection(); + } + } + + var hotkeyHit = false; + foreach (Tuple node in optionNodes) + { + if (node.Item2 != Keys.None && PlayerInput.KeyHit(node.Item2)) + { + (node.Item1 as GUIButton)?.OnClicked?.Invoke(node.Item1 as GUIButton, node.Item1.UserData); + ResetNodeSelection(); + hotkeyHit = true; + break; + } + } + + if (!hotkeyHit) + { + if (returnNodeHotkey != Keys.None && PlayerInput.KeyHit(returnNodeHotkey)) + { + returnNode?.OnClicked?.Invoke(returnNode, returnNode.UserData); + ResetNodeSelection(); + } + else if (expandNodeHotkey != Keys.None && PlayerInput.KeyHit(expandNodeHotkey)) + { + expandNode?.OnClicked?.Invoke(expandNode, expandNode.UserData); + ResetNodeSelection(); } } } @@ -1022,6 +1118,7 @@ namespace Barotrauma { ChatBox.InputBox.AddToGUIUpdateList(); ChatBox.GUIFrame.Flash(Color.DarkGreen, 0.5f); + ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen; ChatBox.ToggleOpen = true; ChatBox.InputBox.Select(ChatBox.InputBox.Text.Length); } @@ -1030,6 +1127,7 @@ namespace Barotrauma { ChatBox.InputBox.AddToGUIUpdateList(); ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f); + ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen; ChatBox.ToggleOpen = true; if (!ChatBox.InputBox.Text.StartsWith(ChatBox.RadioChatString)) @@ -1050,7 +1148,7 @@ namespace Barotrauma child.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID; if (child.Visible && child.FindChild(c => c is GUILayoutGroup) is GUILayoutGroup layoutGroup) { - if (layoutGroup.GetChildByUserData("order") is GUIComponent orderButton && + if (GetCurrentOrderComponent(layoutGroup) is GUIComponent orderButton && orderButton.GetChildByUserData("colorsource") is GUIComponent orderIcon && orderButton.GetChildByUserData("cancel") is GUIComponent cancelIcon) { @@ -1096,66 +1194,101 @@ namespace Barotrauma { get { - return commandFrame != null; + return GameMain.GameSession?.CrewManager?.commandFrame != null; } } - private static GUIFrame commandFrame, targetFrame; - private static GUIButton centerNode, returnNode, expandNode, shortcutCenterNode; - private static List optionNodes = new List(); - private static List shortcutNodes = new List(); - private static List extraOptionNodes = new List(); - private static GUICustomComponent nodeConnectors; - private static GUIImage background; + private GUIFrame commandFrame, targetFrame; + private GUIButton centerNode, returnNode, expandNode, shortcutCenterNode; + private List> optionNodes = new List>(); + private Keys returnNodeHotkey = Keys.None, expandNodeHotkey = Keys.None; + private List shortcutNodes = new List(); + private List extraOptionNodes = new List(); + private GUICustomComponent nodeConnectors; + private GUIImage background; - private static GUIButton selectedNode; - private static float selectionTime = 0.75f, timeSelected = 0.0f; - private static bool clicklessSelectionActive, isOpeningClick; + private GUIButton selectedNode; + private float selectionTime = 0.75f, timeSelected = 0.0f; + private bool clicklessSelectionActive, isOpeningClick, isSelectionHighlighted; private Point centerNodeSize, nodeSize, shortcutCenterNodeSize, shortcutNodeSize, returnNodeSize; private float centerNodeMargin, optionNodeMargin, shortcutCenterNodeMargin, shortcutNodeMargin, returnNodeMargin; private List availableCategories; - private static Stack historyNodes = new Stack(); - private static List extraOptionCharacters = new List(); + private Stack historyNodes = new Stack(); + private List extraOptionCharacters = new List(); /// /// node.Color = node.HighlightColor * nodeColorMultiplier /// private const float nodeColorMultiplier = 0.75f; - private const int assignmentNodeMaxCount = 9; + private const int assignmentNodeMaxCount = 8; private int nodeDistance = (int)(GUI.Scale * 250); private float returnNodeDistanceModifier = 0.65f; - private Order dismissedOrder; + private Order dismissedOrderPrefab; + private Character characterContext; + private Point shorcutCenterNodeOffset; - private void CreateCommandUI() + private void CreateCommandUI(Character characterContext = null) { + CharacterHealth.OpenHealthWindow = null; ScaleCommandUI(); commandFrame = new GUIFrame( - new RectTransform(Vector2.Zero, GUICanvas.Instance, anchor: Anchor.Center), + new RectTransform(Vector2.One, GUICanvas.Instance, anchor: Anchor.Center), style: null, color: Color.Transparent); background = new GUIImage( new RectTransform(Vector2.One, commandFrame.RectTransform, anchor: Anchor.Center), Order.CommandBackground); background.Color = background.Color * 0.8f; - var startNode = new GUIButton( - new RectTransform(centerNodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center), - style: null); - CreateNodeIcon(startNode.RectTransform, Order.StartNode, Color.White); + + this.characterContext = characterContext; + GUIButton startNode = null; + if (characterContext == null) + { + startNode = new GUIButton( + new RectTransform(centerNodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center), + style: null); + CreateNodeIcon(startNode.RectTransform, Order.StartNode, Color.White); + } + else + { + // Button + startNode = new GUIButton( + new RectTransform(centerNodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center), + style: null); + // Container + new GUIImage( + new RectTransform(Vector2.One, startNode.RectTransform, anchor: Anchor.Center), + Order.NodeContainer, + scaleToFit: true) + { + Color = characterContext.Info.Job.Prefab.UIColor * nodeColorMultiplier, + HoverColor = characterContext.Info.Job.Prefab.UIColor, + UserData = "colorsource" + }; + // Character icon + new GUICustomComponent( + new RectTransform(Vector2.One, startNode.RectTransform, anchor: Anchor.Center), + (spriteBatch, _) => + { + characterContext.Info.DrawIcon(spriteBatch, startNode.Center, startNode.Rect.Size.ToVector2() * 0.6f); + }) + { + ToolTip = characterContext.Info.DisplayName + " (" + characterContext.Info.Job.Name + ")" + }; + } SetCenterNode(startNode); - if (availableCategories == null) - { - GetAvailableCategories(); - } - if (dismissedOrder == null) - { - dismissedOrder = Order.GetPrefab("dismissed"); - } + availableCategories ??= GetAvailableCategories(); + dismissedOrderPrefab ??= Order.GetPrefab("dismissed"); CreateShortcutNodes(); CreateOrderCategoryNodes(); CreateNodeConnectors(); + if (Character.Controlled != null) + { + Character.Controlled.dontFollowCursor = true; + } } private void ToggleCommandUI() @@ -1195,9 +1328,10 @@ namespace Barotrauma shortcutNodeMargin = shortcutNodeSize.X * 0.5f; returnNodeMargin = returnNodeSize.X * 0.5f; nodeDistance = (int)(150 * GUI.Scale); + shorcutCenterNodeOffset = new Point(0, (int)(1.25f * nodeDistance)); } - private void GetAvailableCategories() + private List GetAvailableCategories() { availableCategories = new List(); foreach (OrderCategory category in Enum.GetValues(typeof(OrderCategory))) @@ -1208,6 +1342,7 @@ namespace Barotrauma availableCategories.Add(category); } } + return availableCategories; } private void CreateNodeConnectors() @@ -1225,7 +1360,7 @@ namespace Barotrauma var startNodePos = centerNode.Rect.Center.ToVector2(); if (targetFrame == null || !targetFrame.Visible) { - optionNodes.ForEach(n => DrawNodeConnector(startNodePos, centerNodeMargin, n, optionNodeMargin, spriteBatch)); + optionNodes.ForEach(n => DrawNodeConnector(startNodePos, centerNodeMargin, n.Item1, optionNodeMargin, spriteBatch)); } DrawNodeConnector(startNodePos, centerNodeMargin, returnNode, returnNodeMargin, spriteBatch); DrawNodeConnector(startNodePos, centerNodeMargin, expandNode, optionNodeMargin, spriteBatch); @@ -1244,7 +1379,7 @@ namespace Barotrauma var end = endNodePos - direction * endNodeMargin; var colorSource = endNode.GetChildByUserData("colorsource"); if ((selectedNode == null && endNode != shortcutCenterNode && GUI.IsMouseOn(endNode)) || - endNode == selectedNode || endNode == shortcutCenterNode && shortcutNodes.Any(n => GUI.IsMouseOn(n))) + (isSelectionHighlighted && (endNode == selectedNode || (endNode == shortcutCenterNode && shortcutNodes.Any(n => GUI.IsMouseOn(n)))))) { GUI.DrawLine(spriteBatch, start, end, colorSource != null ? colorSource.HoverColor : Color.White, width: 4); } @@ -1254,7 +1389,7 @@ namespace Barotrauma } } - public static void DisableCommandUI() + public void DisableCommandUI() { if (commandFrame == null) { return; } RemoveOptionNodes(); @@ -1270,15 +1405,25 @@ namespace Barotrauma background = null; commandFrame = null; extraOptionCharacters.Clear(); - clicklessSelectionActive = isOpeningClick = false; + clicklessSelectionActive = isOpeningClick = isSelectionHighlighted = false; + characterContext = null; + returnNodeHotkey = expandNodeHotkey = Keys.None; + if (Character.Controlled != null) + { + Character.Controlled.dontFollowCursor = false; + } } private bool NavigateForward(GUIButton node, object userData) { - if (!optionNodes.Remove(node)) { shortcutNodes.Remove(node); }; + if (!(optionNodes.Find(n => n.Item1 == node) is Tuple optionNode) || !optionNodes.Remove(optionNode)) + { + shortcutNodes.Remove(node); + }; RemoveOptionNodes(); if (returnNode != null) { + returnNode.RemoveChild(returnNode.GetChildByUserData("hotkey")); returnNode.Children.ForEach(child => child.Visible = false); returnNode.Visible = false; historyNodes.Push(returnNode); @@ -1293,6 +1438,17 @@ namespace Barotrauma shortcutCenterNode = null; } CreateNodes(userData); + if (returnNode != null && returnNode.Visible) + { + var hotkey = optionNodes.Count + 1; + if (expandNode != null && expandNode.Visible) { hotkey += 1; } + CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true); + returnNodeHotkey = Keys.D0 + hotkey % 10; + } + else + { + returnNodeHotkey = Keys.None; + } return true; } @@ -1308,6 +1464,7 @@ namespace Barotrauma var historyNode = historyNodes.Pop(); SetReturnNode(historyNode, historyNode.RectTransform.AbsoluteOffset); historyNode.Visible = true; + historyNode.RemoveChild(historyNode.GetChildByUserData("hotkey")); historyNode.Children.ForEach(child => child.Visible = true); } else @@ -1315,6 +1472,17 @@ namespace Barotrauma returnNode = null; } CreateNodes(userData); + if (returnNode != null && returnNode.Visible) + { + var hotkey = optionNodes.Count + 1; + if (expandNode != null && expandNode.Visible) { hotkey += 1; } + CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true); + returnNodeHotkey = Keys.D0 + hotkey % 10; + } + else + { + returnNodeHotkey = Keys.None; + } return true; } @@ -1323,13 +1491,14 @@ namespace Barotrauma node.RectTransform.Parent = commandFrame.RectTransform; node.RectTransform.MoveOverTime(Point.Zero, CommandNodeAnimDuration); node.RectTransform.ScaleOverTime(centerNodeSize, CommandNodeAnimDuration); + node.RemoveChild(node.GetChildByUserData("hotkey")); foreach (GUIComponent c in node.Children) { c.Color = c.HoverColor * nodeColorMultiplier; c.HoverColor = c.Color; c.PressedColor = c.Color; c.SelectedColor = c.Color; - c.ToolTip = null; + c.ToolTip = characterContext != null ? characterContext.Info.DisplayName + " (" + characterContext.Info.Job.Name + ")" : null; } node.OnClicked = null; centerNode = node; @@ -1368,17 +1537,19 @@ namespace Barotrauma return true; } - private static void RemoveOptionNodes() + private void RemoveOptionNodes() { - optionNodes.ForEach(node => commandFrame.RemoveChild(node)); + optionNodes.ForEach(node => commandFrame.RemoveChild(node.Item1)); optionNodes.Clear(); shortcutNodes.ForEach(node => commandFrame.RemoveChild(node)); shortcutNodes.Clear(); commandFrame.RemoveChild(expandNode); + expandNode = null; + expandNodeHotkey = Keys.None; RemoveExtraOptionNodes(); } - private static void RemoveExtraOptionNodes() + private void RemoveExtraOptionNodes() { extraOptionNodes.ForEach(node => commandFrame.RemoveChild(node)); extraOptionNodes.Clear(); @@ -1394,10 +1565,10 @@ namespace Barotrauma 0.0f; var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, points, firstAngle); var offsetIndex = 0; - availableCategories.ForEach(oc => CreateOrderCategoryNode(oc, offsets[offsetIndex++].ToPoint())); + availableCategories.ForEach(oc => CreateOrderCategoryNode(oc, offsets[offsetIndex++].ToPoint(), offsetIndex)); } - private void CreateOrderCategoryNode(OrderCategory category, Point offset) + private void CreateOrderCategoryNode(OrderCategory category, Point offset, int hotkey) { var node = new GUIButton( new RectTransform(nodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center), style: null) @@ -1414,26 +1585,32 @@ namespace Barotrauma if (!string.IsNullOrWhiteSpace(categoryDescription)) { tooltip += "\n" + categoryDescription; } CreateNodeIcon(node.RectTransform, sprite, Color.White, tooltip: tooltip); } - optionNodes.Add(node); + CreateHotkeyIcon(node.RectTransform, hotkey % 10); + optionNodes.Add(new Tuple(node, Keys.D0 + hotkey % 10)); } private void CreateShortcutNodes() { - shortcutNodes.Clear(); - var sub = Character.Controlled != null && Character.Controlled.TeamID == Character.TeamType.Team2 && Submarine.MainSubs.Length > 1 ? Submarine.MainSubs[1] : Submarine.MainSub; - var reactor = sub.GetItems(false).Find(i => i.HasTag("reactor")).GetComponent(); - var reactorOutput = -reactor.CurrPowerConsumption; - // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor - // ---> Create shortcut node for "Operate Reactor" order's "Power Up" option - if ((Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("engineer")) && - reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item)) + if (sub == null) { return; } + + shortcutNodes.Clear(); + + var reactor = sub.GetItems(false).Find(i => i.HasTag("reactor"))?.GetComponent(); + if (reactor != null) { - var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled); - shortcutNodes.Add( - CreateOrderOptionNode(shortcutNodeSize, null, Point.Zero, order, order.Prefab.Options[0], order.Prefab.OptionNames[0])); + var reactorOutput = -reactor.CurrPowerConsumption; + // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor + // ---> Create shortcut node for "Operate Reactor" order's "Power Up" option + if ((Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("engineer")) && + reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item)) + { + var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled); + shortcutNodes.Add( + CreateOrderOptionNode(shortcutNodeSize, null, Point.Zero, order, order.Prefab.Options[0], order.Prefab.OptionNames[0], -1)); + } } // TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it @@ -1444,7 +1621,7 @@ namespace Barotrauma nav.GetComponent() is Steering steering && steering.Voltage > steering.MinVoltage) { shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("steer"))); + CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("steer"), -1)); } // If player is not a security officer AND invaders are reported @@ -1453,7 +1630,7 @@ namespace Barotrauma (Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders))) { shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fightintruders"))); + CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fightintruders"), -1)); } // If player is not a mechanic AND a breach has been reported @@ -1462,7 +1639,7 @@ namespace Barotrauma (Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach))) { shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fixleaks"))); + CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fixleaks"), -1)); } // If player is not an engineer AND broken devices have been reported @@ -1471,7 +1648,7 @@ namespace Barotrauma (Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices))) { shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("repairsystems"))); + CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("repairsystems"), -1)); } // If fire is reported @@ -1479,16 +1656,13 @@ namespace Barotrauma if (ActiveOrders.Any(o=> o.First.Prefab == Order.GetPrefab("reportfire"))) { shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("extinguishfires"))); + CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("extinguishfires"), -1)); } if (shortcutNodes.Count < 1) { return; } shortcutCenterNode = new GUIButton( - new RectTransform(shortcutCenterNodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center) - { - AbsoluteOffset = new Point(0, (int)(1.25f * nodeDistance)) - }, + new RectTransform(shortcutCenterNodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center), style: null); CreateNodeIcon(shortcutCenterNode.RectTransform, Order.ShortcutNode, Color.Red); foreach (GUIComponent c in shortcutCenterNode.Children) @@ -1497,13 +1671,14 @@ namespace Barotrauma c.PressedColor = c.Color; c.SelectedColor = c.Color; } + shortcutCenterNode.RectTransform.MoveOverTime(shorcutCenterNodeOffset, CommandNodeAnimDuration); var nodeCountForCalculations = shortcutNodes.Count * 2 + 2; var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 0.75f * nodeDistance, nodeCountForCalculations); for (int i = 0; i < shortcutNodes.Count; i++) { shortcutNodes[i].RectTransform.Parent = commandFrame.RectTransform; - shortcutNodes[i].RectTransform.AbsoluteOffset = shortcutCenterNode.RectTransform.AbsoluteOffset + offsets[i + 1].ToPoint(); + shortcutNodes[i].RectTransform.MoveOverTime(shorcutCenterNodeOffset + offsets[i + 1].ToPoint(), CommandNodeAnimDuration); } } @@ -1514,11 +1689,13 @@ namespace Barotrauma GetCircumferencePointCount(orders.Count), GetFirstNodeAngle(orders.Count)); for(int i = 0; i < orders.Count; i++) { - optionNodes.Add(CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), orders[i])); + optionNodes.Add(new Tuple( + CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), orders[i], (i + 1) % 10), + Keys.D0 + (i + 1) % 10)); } } - private GUIButton CreateOrderNode(Point size, RectTransform parent, Point offset, Order order) + private GUIButton CreateOrderNode(Point size, RectTransform parent, Point offset, Order order, int hotkey) { var node = new GUIButton( new RectTransform(size, parent: parent, anchor: Anchor.Center), style: null) @@ -1531,10 +1708,10 @@ namespace Barotrauma var hasOptions = order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1; node.OnClicked = (button, userData) => { - if (Character.Controlled != null && Character.Controlled.SpeechImpediment >= 100.0f) { return false; } + if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) { return false; } var o = userData as Order; // TODO: Consider defining orders' or order categories' quick-assignment possibility in the XML - if (o.Category == OrderCategory.Movement) + if (o.Category == OrderCategory.Movement && characterContext == null) { CreateAssignmentNodes(node); } @@ -1543,15 +1720,16 @@ namespace Barotrauma NavigateForward(button, userData); } else - { - SetCharacterOrder(GetBestCharacterForOrder(o), o, null, Character.Controlled); + { + SetCharacterOrder(characterContext ?? GetBestCharacterForOrder(o), o, null, Character.Controlled); DisableCommandUI(); } return true; }; CreateNodeIcon(node.RectTransform, order.SymbolSprite, order.Color, - tooltip: hasOptions ? order.Name : + tooltip: hasOptions || characterContext != null ? order.Name : order.Name + "\nLMB: " + TextManager.Get("commandui.quickassigntooltip") + "\nRMB: " + TextManager.Get("commandui.manualassigntooltip")); + if (hotkey >= 0) { CreateHotkeyIcon(node.RectTransform, hotkey); } return node; } @@ -1654,24 +1832,26 @@ namespace Barotrauma for (int i = 0; i < order.Options.Length; i++) { - optionNodes.Add(new GUIButton( - new RectTransform(new Vector2(1.0f, 0.2f), optionContainer.RectTransform), - text: order.OptionNames[i], - style: "GUITextBox") - { - UserData = new Tuple( - item == null ? order : new Order(order, item, item.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType)), - order.Options[i]), - Font = GUI.SmallFont, - OnClicked = (_, userData) => + optionNodes.Add(new Tuple( + new GUIButton( + new RectTransform(new Vector2(1.0f, 0.2f), optionContainer.RectTransform), + text: order.OptionNames[i], + style: "GUITextBox") { - if (GameMain.Client != null && Character.Controlled == null) { return false; } - var o = userData as Tuple; - SetCharacterOrder(GetBestCharacterForOrder(o.Item1), o.Item1, o.Item2, Character.Controlled); - DisableCommandUI(); - return true; - } - }); + UserData = new Tuple( + item == null ? order : new Order(order, item, item.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType)), + order.Options[i]), + Font = GUI.SmallFont, + OnClicked = (_, userData) => + { + if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) { return false; } + var o = userData as Tuple; + SetCharacterOrder(characterContext ?? GetBestCharacterForOrder(o.Item1), o.Item1, o.Item2, Character.Controlled); + DisableCommandUI(); + return true; + } + }, + Keys.None)); } optionFrames.Add(optionFrame); } @@ -1694,13 +1874,14 @@ namespace Barotrauma var offsetIndex = 0; for (int i = 0; i < order.Options.Length; i++) { - optionNodes.Add( - CreateOrderOptionNode(nodeSize, commandFrame.RectTransform, offsets[offsetIndex++].ToPoint(), o, order.Options[i], order.OptionNames[i])); + optionNodes.Add(new Tuple( + CreateOrderOptionNode(nodeSize, commandFrame.RectTransform, offsets[offsetIndex++].ToPoint(), o, order.Options[i], order.OptionNames[i], (i + 1) % 10), + Keys.D0 + (i + 1) % 10)); } } } - private GUIButton CreateOrderOptionNode(Point size, RectTransform parent, Point offset, Order order, string option, string optionName) + private GUIButton CreateOrderOptionNode(Point size, RectTransform parent, Point offset, Order order, string option, string optionName, int hotkey) { var node = new GUIButton( new RectTransform(size, parent: parent, anchor: Anchor.Center) @@ -1712,9 +1893,9 @@ namespace Barotrauma UserData = new Tuple(order, option), OnClicked = (_, userData) => { - if (GameMain.Client != null && Character.Controlled == null) { return false; } + if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) { return false; } var o = userData as Tuple; - SetCharacterOrder(GetBestCharacterForOrder(o.Item1), o.Item1, o.Item2, Character.Controlled); + SetCharacterOrder(characterContext ?? GetBestCharacterForOrder(o.Item1), o.Item1, o.Item2, Character.Controlled); DisableCommandUI(); return true; } @@ -1722,8 +1903,10 @@ namespace Barotrauma if (order.Prefab.OptionSprites.TryGetValue(option, out Sprite sprite)) { CreateNodeIcon(node.RectTransform, sprite, order.Color, - tooltip: optionName + "\nLMB: " + TextManager.Get("commandui.quickassigntooltip") + "\nRMB: " + TextManager.Get("commandui.manualassigntooltip")); + tooltip: characterContext != null ? optionName : + optionName + "\nLMB: " + TextManager.Get("commandui.quickassigntooltip") + "\nRMB: " + TextManager.Get("commandui.manualassigntooltip")); } + if (hotkey >= 0) { CreateHotkeyIcon(node.RectTransform, hotkey); } return node; } @@ -1735,7 +1918,10 @@ namespace Barotrauma var characters = GetCharactersSortedForOrder(order.Item1); if (characters.Count < 1) { return; } - if (!optionNodes.Remove(node)) { shortcutNodes.Remove(node); }; + if (!(optionNodes.Find(n => n.Item1 == node) is Tuple optionNode) || !optionNodes.Remove(optionNode)) + { + shortcutNodes.Remove(node); + }; RemoveOptionNodes(); if (returnNode != null) { @@ -1752,7 +1938,7 @@ namespace Barotrauma } else { - var optionNode = new GUIButton( + var clickedOptionNode = new GUIButton( new RectTransform(centerNodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center), style: null) { @@ -1760,9 +1946,9 @@ namespace Barotrauma }; if (order.Item1.Prefab.OptionSprites.TryGetValue(order.Item2, out Sprite sprite)) { - CreateNodeIcon(optionNode.RectTransform, sprite, order.Item1.Color, tooltip: order.Item2); + CreateNodeIcon(clickedOptionNode.RectTransform, sprite, order.Item1.Color, tooltip: order.Item2); } - SetCenterNode(optionNode); + SetCenterNode(clickedOptionNode); node = null; targetFrame.Visible = false; } @@ -1783,10 +1969,18 @@ namespace Barotrauma var assignmentNodeCount = (needToExpand ? nodeCount - 1 : nodeCount); for (; i < assignmentNodeCount; i++) { - CreateAssignmentNode(order, characters[i], offsets[i].ToPoint()); + CreateAssignmentNode(order, characters[i], offsets[i].ToPoint(), (i + 1) % 10); } - if (!needToExpand) { return; } + int hotkey; + if (!needToExpand) + { + hotkey = optionNodes.Count + 1; + CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true); + returnNodeHotkey = Keys.D0 + hotkey % 10; + expandNodeHotkey = Keys.None; + return; + } extraOptionCharacters.Clear(); extraOptionCharacters.AddRange(characters.GetRange(i, characters.Count - i)); @@ -1802,6 +1996,12 @@ namespace Barotrauma OnClicked = ExpandAssignmentNodes }; CreateNodeIcon(expandNode.RectTransform, Order.ExpandNode, order.Item1.Color, tooltip: TextManager.Get("commandui.expand")); + + hotkey = optionNodes.Count + 1; + CreateHotkeyIcon(expandNode.RectTransform, hotkey % 10); + expandNodeHotkey = Keys.D0 + hotkey % 10; + CreateHotkeyIcon(returnNode.RectTransform, ++hotkey % 10, true); + returnNodeHotkey = Keys.D0 + hotkey % 10; } private bool ExpandAssignmentNodes(GUIButton node, object userData) @@ -1820,12 +2020,12 @@ namespace Barotrauma GetFirstNodeAngle(extraOptionCharacters.Count)); for (int i = 0; i < extraOptionCharacters.Count; i++) { - CreateAssignmentNode(order, extraOptionCharacters[i], offsets[i].ToPoint(), extraOption: true); + CreateAssignmentNode(order, extraOptionCharacters[i], offsets[i].ToPoint(), -1); } return true; } - private void CreateAssignmentNode(Tuple order, Character character, Point offset, bool extraOption = false) + private void CreateAssignmentNode(Tuple order, Character character, Point offset, int hotkey) { // Button var node = new GUIButton( @@ -1834,23 +2034,15 @@ namespace Barotrauma { OnClicked = (button, userData) => { + if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) { return false; } SetCharacterOrder(character, order.Item1, order.Item2, Character.Controlled); DisableCommandUI(); return true; } }; - node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration); - - // Character icon - new GUICustomComponent( - new RectTransform(Vector2.One, node.RectTransform), - (spriteBatch, _) => - { - character.Info.DrawIcon(spriteBatch, node.Center, node.Rect.Size.ToVector2() * 0.75f); - }); - // Smaller container - new GUIImage( + // Container + var icon = new GUIImage( new RectTransform(new Vector2(1.2f), node.RectTransform, anchor: Anchor.Center), Order.NodeContainer, scaleToFit: true) @@ -1859,13 +2051,41 @@ namespace Barotrauma HoverColor = character.Info.Job.Prefab.UIColor, PressedColor = character.Info.Job.Prefab.UIColor, SelectedColor = character.Info.Job.Prefab.UIColor, - ToolTip = character.Info.DisplayName + " (" + character.Info.Job.Name + ")", UserData = "colorsource" }; - (extraOption ? extraOptionNodes : optionNodes).Add(node); + // Character icon + new GUICustomComponent( + new RectTransform(Vector2.One, node.RectTransform), + (spriteBatch, _) => + { + character.Info.DrawIcon(spriteBatch, node.Center, node.Rect.Size.ToVector2() * 0.75f); + }) + { + ToolTip = character.Info.DisplayName + " (" + character.Info.Job.Name + ")" + }; + + bool canHear = character.CanHearCharacter(Character.Controlled); + + if (!canHear) + { + node.CanBeFocused = icon.CanBeFocused = false; + new GUIImage(new RectTransform(Vector2.One, node.RectTransform, Anchor.Center), cancelIcon, scaleToFit: true) + { + Color = GUI.Style.Red * nodeColorMultiplier + }; + } + if (canHear && hotkey >= 0) + { + CreateHotkeyIcon(node.RectTransform, hotkey); + optionNodes.Add(new Tuple(node, Keys.D0 + hotkey)); + } + else + { + extraOptionNodes.Add(node); + } } - private void CreateNodeIcon(RectTransform parent, Sprite sprite, Color iconColor, string tooltip = null) + private void CreateNodeIcon(RectTransform parent, Sprite sprite, Color color, string tooltip = null) { // Icon new GUIImage( @@ -1873,15 +2093,35 @@ namespace Barotrauma sprite, scaleToFit: true) { - Color = iconColor * nodeColorMultiplier, - HoverColor = iconColor, - PressedColor = iconColor, - SelectedColor = iconColor, + Color = color * nodeColorMultiplier, + HoverColor = color, + PressedColor = color, + SelectedColor = color, ToolTip = tooltip, UserData = "colorsource" }; } + private void CreateHotkeyIcon(RectTransform parent, int hotkey, bool enlargeIcon = false) + { + var bg = new GUIImage( + new RectTransform(new Vector2(enlargeIcon ? 0.4f : 0.25f), parent, anchor: Anchor.BottomCenter, pivot: Pivot.Center), + Order.HotkeyContainer, + scaleToFit: true) + { + CanBeFocused = false, + UserData = "hotkey" + }; + new GUITextBlock( + new RectTransform(Vector2.One, bg.RectTransform, anchor: Anchor.Center), + hotkey.ToString(), + textColor: Color.Black, + textAlignment: Alignment.Center) + { + CanBeFocused = false + }; + } + private int GetCircumferencePointCount(int nodes) { return nodes % 2 > 0 ? nodes : nodes + 1; @@ -1893,14 +2133,14 @@ namespace Barotrauma if (returnNode != null) { bearing = GetBearing( - centerNode.RectTransform.AbsoluteOffset.ToVector2(), - returnNode.RectTransform.AbsoluteOffset.ToVector2()); + centerNode.RectTransform.AnimTargetPos.ToVector2(), + returnNode.RectTransform.AnimTargetPos.ToVector2()); } else if (shortcutCenterNode != null) { bearing = GetBearing( centerNode.RectTransform.AbsoluteOffset.ToVector2(), - shortcutCenterNode.RectTransform.AbsoluteOffset.ToVector2()); + shorcutCenterNodeOffset.ToVector2()); } return nodeCount % 2 > 0 ? MathHelper.ToRadians(bearing + 360.0f / nodeCount / 2) : @@ -1921,7 +2161,7 @@ namespace Barotrauma private Character GetBestCharacterForOrder(Order order) { return characters.FindAll(c => c != Character.Controlled) - .OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder == dismissedOrder) + .OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier) .ThenByDescending(c => order.HasAppropriateJob(c)) .ThenBy(c => c.CurrentOrder?.Weight) .FirstOrDefault(); @@ -1932,12 +2172,12 @@ namespace Barotrauma if (order.Identifier == "follow") { return characters.FindAll(c => c != Character.Controlled) - .OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder == dismissedOrder) + .OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier) .ToList(); } else { - return characters.OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder == dismissedOrder) + return characters.OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier) .ThenByDescending(c => order.HasAppropriateJob(c)) .ThenBy(c => c.CurrentOrder?.Weight) .ToList(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index fdc9bef8c..c13b7150a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -560,7 +560,7 @@ namespace Barotrauma } }; - GUITextBlock particleLimitText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("ParticleLimit"), font: GUI.SubHeadingFont); + GUITextBlock particleLimitText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("ParticleLimit"), font: GUI.SubHeadingFont, wrap: true); GUIScrollBar particleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: "GUISlider", barSize: 0.1f) { @@ -576,7 +576,7 @@ namespace Barotrauma }; particleScrollBar.OnMoved(particleScrollBar, particleScrollBar.BarScroll); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("LosEffect"), font: GUI.SubHeadingFont); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("LosEffect"), font: GUI.SubHeadingFont, wrap: true); var losModeDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform)); losModeDD.AddItem(TextManager.Get("LosModeNone"), LosMode.None); losModeDD.AddItem(TextManager.Get("LosModeTransparent"), LosMode.Transparent); @@ -594,7 +594,7 @@ namespace Barotrauma return true; }; - GUITextBlock LightText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("LightMapScale"), font: GUI.SubHeadingFont) + GUITextBlock LightText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("LightMapScale"), font: GUI.SubHeadingFont, wrap: true) { ToolTip = TextManager.Get("LightMapScaleToolTip") }; @@ -638,7 +638,7 @@ namespace Barotrauma } }; - GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("HUDScale"), font: GUI.SubHeadingFont); + GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("HUDScale"), font: GUI.SubHeadingFont, wrap: true); GUIScrollBar HUDScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: "GUISlider", barSize: 0.1f) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index a520f11cd..9a4f0eab3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -11,7 +11,7 @@ using System.Xml.Linq; namespace Barotrauma { partial class CharacterInventory : Inventory - { + { public enum Layout { Default, @@ -35,13 +35,18 @@ namespace Barotrauma } private static Dictionary limbSlotIcons; - + private static Sprite inventoryBackgroundSprite, inventoryExtendButton, inventoryExtendUpArrow, inventoryExtendDownArrow; + + public Rectangle InventoryToggleArea; + public bool InventoryToggleContains = false; + private Vector2 inventoryExtendButtonOffset, inventoryArrowOffset; + private int inventoryOpeningOffset; + public const InvSlotType PersonalSlots = InvSlotType.Card | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.OuterClothes | InvSlotType.Head; - private Point screenResolution; - public Vector2[] SlotPositions; - + private Vector2 bgScale; + private Layout layout; public Layout CurrentLayout { @@ -50,7 +55,7 @@ namespace Barotrauma { if (layout == value) return; layout = value; - SetSlotPositions(layout); + SetSlotPositions(); } } public bool Hidden { get; set; } @@ -59,6 +64,8 @@ namespace Barotrauma private float hidePersonalSlotsState; private GUIButton hideButton; private Rectangle personalSlotArea; + private bool inventoryOpen = false; + private bool wasInventoryToggledAutomatically = false; public bool HidePersonalSlots { @@ -103,10 +110,17 @@ namespace Barotrauma limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128))); limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128))); limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); + + inventoryBackgroundSprite = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(252, 317, 263, 197)); + inventoryExtendButton = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(533, 300, 96, 19)); + inventoryExtendUpArrow = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(640, 310, 10, 10)); + inventoryExtendDownArrow = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(668, 310, 10, 10)); } + SlotPositions = new Vector2[SlotTypes.Length]; CurrentLayout = Layout.Default; - SetSlotPositions(layout); + SetSlotPositions(); + GameMain.Instance.OnResolutionChanged += SetSlotPositions; } protected override ItemInventory GetActiveEquippedSubInventory(int slotIndex) @@ -193,31 +207,54 @@ namespace Barotrauma } } - screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); CalculateBackgroundFrame(); } protected override void CalculateBackgroundFrame() { Rectangle frame = Rectangle.Empty; + int firstSlotLocationY = 0; + int lastSlotLocationY = 0; + for (int i = 0; i < capacity; i++) { if (HideSlot(i)) continue; + if (PersonalSlots.HasFlag(SlotTypes[i])) continue; + if (IsHandSlot(SlotTypes[i])) continue; if (frame == Rectangle.Empty) { + firstSlotLocationY = slots[i].Rect.Location.Y; frame = slots[i].Rect; continue; } frame = Rectangle.Union(frame, slots[i].Rect); + lastSlotLocationY = slots[i].Rect.Location.Y; } - frame.Inflate(10, 30); - frame.Location -= new Point(0, 25); + + frame.Inflate(25f * UIScale, 25f * UIScale); + + if (layout == Layout.Default) + { + inventoryOpeningOffset = firstSlotLocationY - lastSlotLocationY; + + bgScale = new Vector2((float)frame.Width / (float)inventoryBackgroundSprite.SourceRect.Width, (float)frame.Height / (float)inventoryBackgroundSprite.SourceRect.Height); + + inventoryExtendButtonOffset = new Vector2(inventoryExtendButton.size.X * UIScale * 1.5f / 2f, inventoryExtendButton.size.Y * UIScale * 1.5f / 2f + UIScale * 6); + inventoryArrowOffset = new Vector2(inventoryExtendUpArrow.size.X * UIScale * 1.25f / 2f, inventoryExtendUpArrow.size.Y * UIScale * 1.25f / 2f); + InventoryToggleArea = new Rectangle(new Point(frame.Center.X, frame.Top) - inventoryExtendButtonOffset.ToPoint(), inventoryExtendButton.size.ToPoint() + new Point(0, (int)(UIScale * 6f))); + if (!inventoryOpen) + { + frame.Offset(0, inventoryOpeningOffset); + InventoryToggleArea.Offset(0, inventoryOpeningOffset); + } + } + BackgroundFrame = frame; } protected override bool HideSlot(int i) { - if (slots[i].Disabled || (hideEmptySlot[i] && Items[i] == null)) return true; + if (slots[i].Disabled) return true; if (layout == Layout.Default) { @@ -230,19 +267,17 @@ namespace Barotrauma return true; } - //don't show the equip slot if the item is also in the default inventory - if (SlotTypes[i] != InvSlotType.Any && Items[i] != null) - { - for (int j = 0; j < capacity; j++) - { - if (SlotTypes[j] == InvSlotType.Any && Items[j] == Items[i]) return true; - } - } - return false; } - private void SetSlotPositions(Layout layout) + + protected override bool IsSlotHiddenDueToToggleState(int i) { + return layout == Layout.Default && !inventoryOpen && slots[i].QuickUseKey == Keys.None && SlotTypes[i] == InvSlotType.Any; + } + + private void SetSlotPositions() + { + Layout layout = CurrentLayout; int spacing = (int)(10 * UIScale); Point slotSize = (SlotSpriteSmall.size * UIScale).ToPoint(); int bottomOffset = slotSize.Y + spacing * 2 + ContainedIndicatorHeight; @@ -258,27 +293,47 @@ namespace Barotrauma int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s)); int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s)); - int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (slotSize.X + spacing) / 2; - int upperX = GameMain.GraphicsWidth - slotSize.X * 2; + int firstRowSlotCount = hotkeyCount > normalSlotCount ? normalSlotCount : hotkeyCount; + + int startX = GameMain.GraphicsWidth / 2 - firstRowSlotCount * (slotSize.X + spacing) / 2; + int x = startX; + int equipmentX = GameMain.GraphicsWidth - slotSize.X * 2; + int buttonIndex = 0; + int startY = GameMain.GraphicsHeight - bottomOffset; + int y = startY; + int handIndex = 1; //make sure the rightmost normal slot doesn't overlap with the personal slots - x -= Math.Max((x + normalSlotCount * (slotSize.X + spacing)) - (upperX - personalSlotCount * (slotSize.X + spacing)), 0); + x -= Math.Max(x + firstRowSlotCount * (slotSize.X + spacing) - (equipmentX - personalSlotCount * (slotSize.X + spacing)), 0); int hideButtonSlotIndex = -1; for (int i = 0; i < SlotPositions.Length; i++) { if (PersonalSlots.HasFlag(SlotTypes[i])) { - SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset); - upperX -= slotSize.X + spacing; + SlotPositions[i] = new Vector2(equipmentX, startY); + equipmentX -= slotSize.X + spacing; personalSlotArea = (hideButtonSlotIndex == -1) ? new Rectangle(SlotPositions[i].ToPoint(), slotSize) : Rectangle.Union(personalSlotArea, new Rectangle(SlotPositions[i].ToPoint(), slotSize)); hideButtonSlotIndex = i; } + else if (IsHandSlot(SlotTypes[i])) + { + SlotPositions[i] = new Vector2(startX - (slotSize.X + spacing * 4) - (slotSize.X + spacing) * handIndex, startY); + handIndex--; + } else { - SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); + if (buttonIndex >= hotkeyCount) + { + y -= bottomOffset; + buttonIndex = 0; + x = startX; + } + + buttonIndex++; + SlotPositions[i] = new Vector2(x, y); x += slotSize.X + spacing; } } @@ -296,7 +351,6 @@ namespace Barotrauma break; case Layout.Right: { - int extraOffset = 0; int x = HUDLayoutSettings.InventoryAreaLower.Right; int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - slotSize.X - spacing; for (int i = 0; i < slots.Length; i++) @@ -307,24 +361,35 @@ namespace Barotrauma //upperX -= slotSize.X + spacing; } else - { - x -= slotSize.X + spacing; + { + if (i < slots.Length - 5) + { + x -= slotSize.X + spacing; + } } } int lowerX = x; + int y = GameMain.GraphicsHeight - bottomOffset; + for (int i = 0; i < SlotPositions.Length; i++) { if (HideSlot(i)) continue; if (PersonalSlots.HasFlag(SlotTypes[i])) { - SlotPositions[i] = new Vector2(personalSlotX, GameMain.GraphicsHeight - bottomOffset * 2 - extraOffset - spacing * 2); + SlotPositions[i] = new Vector2(personalSlotX, y - bottomOffset); personalSlotX -= slots[i].Rect.Width + spacing; } else { - SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset - extraOffset); + SlotPositions[i] = new Vector2(x, y); x += slots[i].Rect.Width + spacing; + + if (i == SlotPositions.Length - 6) + { + x = lowerX + (slots[i].Rect.Width + spacing) * 2; + y -= bottomOffset; + } } } @@ -333,28 +398,37 @@ namespace Barotrauma { if (!HideSlot(i)) continue; x -= slots[i].Rect.Width + spacing; - SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset - extraOffset); + SlotPositions[i] = new Vector2(x, y); } } break; case Layout.Left: { - int x = HUDLayoutSettings.InventoryAreaLower.X; + int x = HUDLayoutSettings.InventoryAreaLower.Left; + int y = GameMain.GraphicsHeight - bottomOffset; int personalSlotX = x; + for (int i = 0; i < SlotPositions.Length; i++) { if (HideSlot(i)) continue; if (PersonalSlots.HasFlag(SlotTypes[i])) { - SlotPositions[i] = new Vector2(personalSlotX, GameMain.GraphicsHeight - bottomOffset * 2 - spacing * 2); + SlotPositions[i] = new Vector2(personalSlotX, y - bottomOffset); personalSlotX += slots[i].Rect.Width + spacing; } else { - SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); + SlotPositions[i] = new Vector2(x, y); x += slots[i].Rect.Width + spacing; + + if (i == SlotPositions.Length - 7) + { + x -= (slots[i].Rect.Width + spacing) * 6; + y -= bottomOffset; + } } } + for (int i = 0; i < SlotPositions.Length; i++) { if (!HideSlot(i)) continue; @@ -406,7 +480,11 @@ namespace Barotrauma { HUDLayoutSettings.InventoryTopY = slots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale); } - + } + + private bool IsHandSlot(InvSlotType slotType) + { + return slotType == InvSlotType.LeftHand || slotType == InvSlotType.RightHand; } protected override void ControlInput(Camera cam) @@ -419,6 +497,32 @@ namespace Barotrauma } } + public void ToggleInventory(bool automaticToggle = false) + { + if (Character.Controlled == null || !Character.Controlled.IsHuman || wasInventoryToggledAutomatically || inventoryOpen && automaticToggle) return; + inventoryOpen = !inventoryOpen; + if (inventoryOpen) + { + wasInventoryToggledAutomatically = automaticToggle; + BackgroundFrame.Offset(0, -inventoryOpeningOffset); + InventoryToggleArea.Offset(0, -inventoryOpeningOffset); + } + else + { + BackgroundFrame.Offset(0, inventoryOpeningOffset); + InventoryToggleArea.Offset(0, inventoryOpeningOffset); + } + } + + private void HandleAutomaticInventoryState() + { + if (wasInventoryToggledAutomatically && inventoryOpen && Character.Controlled.SelectedConstruction == null && Character.Controlled.SelectedCharacter == null) + { + wasInventoryToggledAutomatically = false; + ToggleInventory(); + } + } + public override void Update(float deltaTime, Camera cam, bool isSubInventory = false) { if (!AccessibleWhenAlive && !character.IsDead) @@ -427,6 +531,8 @@ namespace Barotrauma return; } + HandleAutomaticInventoryState(); + base.Update(deltaTime, cam); bool hoverOnInventory = GUI.MouseOn == null && @@ -460,7 +566,7 @@ namespace Barotrauma for (int i = 0; i < capacity; i++) { if (Items[i] != null && Items[i] != draggingItem && Character.Controlled?.Inventory == this && - GUI.KeyboardDispatcher.Subscriber == null && + GUI.KeyboardDispatcher.Subscriber == null && !CrewManager.IsCommandInterfaceOpen && slots[i].QuickUseKey != Keys.None && PlayerInput.KeyHit(slots[i].QuickUseKey)) { QuickUseItem(Items[i], true, false, true); @@ -608,7 +714,10 @@ namespace Barotrauma private void HandleButtonEquipStates(Item item, InventorySlot slot, float deltaTime) { - slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ? + Rectangle modifiedRect = slot.EquipButtonRect; + modifiedRect.Width = slot.InteractRect.Width; + + slot.EquipButtonState = modifiedRect.Contains(PlayerInput.MousePosition) ? GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None; if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld()) { @@ -682,6 +791,8 @@ namespace Barotrauma continue; } + if (num > hotkeyCount) break; + if (SlotTypes[i] == InvSlotType.Any) { slots[i].QuickUseKey = Keys.D0 + num % 10; @@ -827,7 +938,7 @@ namespace Barotrauma if (character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) { //player has selected the inventory of another character -> attempt to move the item there - success = character.SelectedCharacter.Inventory.TryPutItem(item, Character.Controlled, item.AllowedSlots, true); + success = character.SelectedCharacter.Inventory.TryPutItem(item, Character.Controlled, item.AllowedSlots, true, true); } break; case QuickUseAction.PutToContainer: @@ -843,7 +954,7 @@ namespace Barotrauma character.SelectedBy.Inventory != null) { //item is in the inventory of another character -> attempt to get the item from there - success = character.SelectedBy.Inventory.TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true); + success = character.SelectedBy.Inventory.TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true, true); } break; case QuickUseAction.TakeFromContainer: @@ -862,7 +973,7 @@ namespace Barotrauma // No subinventory found or placing unsuccessful -> attempt to put in the character's inventory if (!success) { - success = TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true); + success = TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true, true); } break; case QuickUseAction.PutToEquippedItem: @@ -894,107 +1005,87 @@ namespace Barotrauma GUI.PlayUISound(success ? GUISoundType.PickItem : GUISoundType.PickItemFail); } - public void DrawOwn(SpriteBatch spriteBatch) + public void DrawThis(SpriteBatch spriteBatch) { if (!AccessibleWhenAlive && !character.IsDead) return; if (slots == null) CreateSlots(); - if (GameMain.GraphicsWidth != screenResolution.X || - GameMain.GraphicsHeight != screenResolution.Y || - prevUIScale != UIScale || - prevHUDScale != GUI.Scale) - { - SetSlotPositions(layout); - prevUIScale = UIScale; - prevHUDScale = GUI.Scale; - } - - if (layout == Layout.Center) - { - CalculateBackgroundFrame(); - GUI.DrawRectangle(spriteBatch, BackgroundFrame, Color.Black * 0.8f, true); - GUI.DrawString(spriteBatch, - new Vector2((int)(BackgroundFrame.Center.X - GUI.Font.MeasureString(character.Name).X / 2), (int)BackgroundFrame.Y + 5), - character.Name, Color.White * 0.9f); - } - - for (int i = 0; i < capacity; i++) - { - if (HideSlot(i)) continue; - - Rectangle interactRect = slots[i].InteractRect; - interactRect.Location += slots[i].DrawOffset.ToPoint(); - - //don't draw the item if it's being dragged out of the slot - bool drawItem = draggingItem == null || draggingItem != Items[i] || interactRect.Contains(PlayerInput.MousePosition); - DrawSlot(spriteBatch, this, slots[i], Items[i], i, drawItem, SlotTypes[i]); - } if (hideButton != null && hideButton.Visible && !Locked) { hideButton.DrawManually(spriteBatch, alsoChildren: true); } + + if (layout == Layout.Default) + { + inventoryBackgroundSprite.Draw(spriteBatch, BackgroundFrame.Location.ToVector2(), Color.White, Vector2.Zero, rotate: 0, scale: bgScale); + + Vector2 backgroundFrameCenter = new Vector2(BackgroundFrame.Center.X, BackgroundFrame.Top); + inventoryExtendButton.Draw(spriteBatch, backgroundFrameCenter - inventoryExtendButtonOffset, Color.White, scale: UIScale * 1.5f); + + Vector2 arrowPosition = backgroundFrameCenter - inventoryArrowOffset; + if (inventoryOpen) + { + inventoryExtendDownArrow.Draw(spriteBatch, arrowPosition, Color.White, scale: UIScale * 1.25f); + } + else + { + inventoryExtendUpArrow.Draw(spriteBatch, arrowPosition, Color.White, scale: UIScale * 1.25f); + } + + GUI.DrawString(spriteBatch, arrowPosition + new Vector2(UIScale * 25, -3 * UIScale), GameMain.Config.KeyBindText(InputType.ToggleInventory), Color.White, font: GUI.HotkeyFont); + + InventoryToggleContains = InventoryToggleArea.Contains(PlayerInput.MousePosition); + if (InventoryToggleContains && PlayerInput.PrimaryMouseButtonClicked()) + { + ToggleInventory(); + } + } InventorySlot highlightedQuickUseSlot = null; for (int i = 0; i < capacity; i++) { if (HideSlot(i)) continue; + if (IsSlotHiddenDueToToggleState(i)) continue; - if (Items[i] == null || - (draggingItem == Items[i] && !slots[i].InteractRect.Contains(PlayerInput.MousePosition)) || - !Items[i].AllowedSlots.Any(a => a != InvSlotType.Any)) + InventorySlot slot = slots[i]; + + Item item = Items[i]; + InvSlotType slotType = SlotTypes[i]; + + Rectangle interactRect = slot.InteractRect; + interactRect.Location += slot.DrawOffset.ToPoint(); + + //don't draw the item if it's being dragged out of the slot + bool drawItem = draggingItem == null || draggingItem != item || interactRect.Contains(PlayerInput.MousePosition); + DrawSlot(spriteBatch, this, slot, item, i, drawItem, slotType); + + if (item == null || (draggingItem == item && !slot.InteractRect.Contains(PlayerInput.MousePosition)) || !item.AllowedSlots.Any(a => a != InvSlotType.Any)) { //draw limb icons on empty slots - if (limbSlotIcons.ContainsKey(SlotTypes[i])) + if (limbSlotIcons.ContainsKey(slotType)) { - var icon = limbSlotIcons[SlotTypes[i]]; - icon.Draw(spriteBatch, slots[i].Rect.Center.ToVector2() + slots[i].DrawOffset, GUIColorSettings.EquipmentSlotIconColor, origin: icon.size / 2, scale: slots[i].Rect.Width / icon.size.X); + var icon = limbSlotIcons[slotType]; + icon.Draw(spriteBatch, slot.Rect.Center.ToVector2() + slot.DrawOffset, GUIColorSettings.EquipmentSlotIconColor, origin: icon.size / 2, scale: slot.Rect.Width / icon.size.X); } - continue; - } - if (draggingItem == Items[i] && !slots[i].IsHighlighted) continue; - - //draw hand icons if the item is equipped in a hand slot - if (IsInLimbSlot(Items[i], InvSlotType.LeftHand)) - { - var icon = limbSlotIcons[InvSlotType.LeftHand]; - icon.Draw(spriteBatch, new Vector2(slots[i].Rect.X, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.35f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); - } - if (IsInLimbSlot(Items[i], InvSlotType.RightHand)) - { - var icon = limbSlotIcons[InvSlotType.RightHand]; - icon.Draw(spriteBatch, new Vector2(slots[i].Rect.Right, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.65f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); - } - Color color = slots[i].EquipButtonState == GUIComponent.ComponentState.Pressed ? Color.Gray : Color.White * 0.8f; - if (slots[i].EquipButtonState == GUIComponent.ComponentState.Hover) - { - color = Color.White; - highlightedQuickUseSlot = slots[i]; - } - if (Locked) { color *= 0.3f; } + // Draw always on hotkeys + if (slot.QuickUseKey != Keys.None) + { + DrawIndicatorAndHotkey(spriteBatch, slot, slot.EquipButtonState == GUIComponent.ComponentState.Hover); + } - if (!Items[i].AllowedSlots.Any(a => a == InvSlotType.Any)) - { continue; } - EquipIndicator.Draw(spriteBatch, slots[i].EquipButtonRect.Center.ToVector2(), color, EquipIndicator.Origin, 0, UIScale); - /*slots[i].QuickUseTimer = Math.Min(slots[i].QuickUseTimer, 1.0f); - if (slots[i].QuickUseTimer > 0.0f) - { - float indicatorFillAmount = character.HasEquippedItem(Items[i]) ? 1.0f - slots[i].QuickUseTimer : slots[i].QuickUseTimer; - quickUseHighlight.DrawTiled(spriteBatch, - slots[i].EquipButtonRect.Center.ToVector2() - quickUseHighlight.Origin * UIScale * 0.85f, - new Vector2(quickUseIndicator.SourceRect.Width * indicatorFillAmount, quickUseIndicator.SourceRect.Height) * UIScale * 0.85f, - null, - color * 0.9f, - null, - Vector2.One * UIScale * 0.85f); - } - else*/ if (character.HasEquippedItem(Items[i])) - { - EquipIndicatorHighlight.Draw(spriteBatch, slots[i].EquipButtonRect.Center.ToVector2(), color * 0.9f, EquipIndicatorHighlight.Origin, 0, UIScale * 0.85f); - } + if (draggingItem == item && !slot.IsHighlighted) continue; + + bool hover = slot.EquipButtonState == GUIComponent.ComponentState.Hover; + if (hover) highlightedQuickUseSlot = slot; + + if (!item.AllowedSlots.Any(a => a == InvSlotType.Any)) continue; + + DrawIndicatorAndHotkey(spriteBatch, slot, hover); + } if (highlightedQuickUseSlot != null && !string.IsNullOrEmpty(highlightedQuickUseSlot.QuickUseButtonToolTip)) @@ -1002,5 +1093,30 @@ namespace Barotrauma GUIComponent.DrawToolTip(spriteBatch, highlightedQuickUseSlot.QuickUseButtonToolTip, highlightedQuickUseSlot.EquipButtonRect); } } + + private void DrawIndicatorAndHotkey(SpriteBatch spriteBatch, InventorySlot slot, bool hover) + { + + Color indicatorColor = (hover) ? Color.White : Color.White * 0.8f; + + /*if (character.HasEquippedItem(item)) + { + indicatorColor = hover ? GUIColorSettings.InventorySlotEquippedColor : GUIColorSettings.InventorySlotEquippedColor * 0.8f; + } + else + { + indicatorColor = hover ? GUIColorSettings.InventorySlotColor : GUIColorSettings.InventorySlotColor * 0.8f; + }*/ + + if (Locked) indicatorColor *= 0.3f; + + Rectangle equipRect = slot.EquipButtonRect; + EquipIndicator.Draw(spriteBatch, new Vector2(equipRect.Center.X, equipRect.Bottom), indicatorColor, EquipIndicator.Origin, 0, UIScale * 0.6f); + + if (slot.QuickUseKey != Keys.None) + { + GUI.DrawString(spriteBatch, new Vector2(slot.Rect.Center.X - 2, slot.Rect.Top), slot.QuickUseKey.ToString().Substring(1, 1), Color.Black, font: GUI.HotkeyFont); + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs index 5eaeafcfe..9e751d1df 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs @@ -114,7 +114,7 @@ namespace Barotrauma.Items.Components shakeTimer -= deltaTime; Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0.5f) - 0.5f); shakePos = noisePos * shake * 2.0f; - shake = Math.Min(shake, shakeTimer); + shake = Math.Min(shake, shakeTimer * 10.0f); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index b3955b14a..85958687f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.90f, 0.80f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) { Stretch = true, - RelativeSpacing = 0.02f + RelativeSpacing = 0.08f }; var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null); @@ -51,15 +51,15 @@ namespace Barotrauma.Items.Components inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height)); new GUIFrame(new RectTransform(Vector2.One, inputLabelArea.RectTransform), style: "HorizontalLine"); - var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1.2f), topFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; + var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), topFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; // === INPUT SLOTS === // inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null); inputInventoryOverlay = new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false }; // === ACTIVATE BUTTON === // - var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.75f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft); - activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.65f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.7f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft); + activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") { TextBlock = { AutoScaleHorizontal = true }, OnClicked = ToggleActive @@ -87,7 +87,7 @@ namespace Barotrauma.Items.Components new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine"); // === OUTPUT SLOTS === // - outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), bottomFrame.RectTransform, Anchor.CenterLeft), style: null); + outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), style: null); } public override bool Select(Character character) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 4f7b83564..4f418c79f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -299,6 +299,11 @@ namespace Barotrauma.Items.Components foreach (FabricationRecipe.RequiredItem requiredItem in missingItems) { + while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.Items[slotIndex] != null) + { + slotIndex++; + } + //highlight suitable ingredients in linked inventories foreach (Item item in availableIngredients) { @@ -311,26 +316,24 @@ namespace Barotrauma.Items.Components { if (item.ParentInventory.slots[availableSlotIndex].HighlightTimer <= 0.0f) { - item.ParentInventory.slots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green * 0.5f, 0.5f, 0.5f); + item.ParentInventory.slots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f); + if (slotIndex < inputContainer.Capacity) + { + inputContainer.Inventory.slots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f); + } } } } } - while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.Items[slotIndex] != null) - { - slotIndex++; - } if (slotIndex >= inputContainer.Capacity) { break; } - + var itemIcon = requiredItem.ItemPrefab.InventoryIcon ?? requiredItem.ItemPrefab.sprite; - Rectangle slotRect = inputContainer.Inventory.slots[slotIndex].Rect; - itemIcon.Draw( spriteBatch, slotRect.Center.ToVector2(), - color: requiredItem.ItemPrefab.InventoryIconColor * (availableIngredients.Any(i => IsItemValidIngredient(i, requiredItem)) ? 1.0f : 0.3f), + color: requiredItem.ItemPrefab.InventoryIconColor * 0.3f, scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y)); if (slotRect.Contains(PlayerInput.MousePosition)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index cb72b3f01..746fef710 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -59,7 +59,7 @@ namespace Barotrauma.Items.Components public override void AddToGUIUpdateList() { base.AddToGUIUpdateList(); - hullInfoFrame.AddToGUIUpdateList(); + hullInfoFrame.AddToGUIUpdateList(order: 1); } public override void OnMapLoaded() @@ -257,8 +257,11 @@ namespace Barotrauma.Items.Components } if (mouseOnHull == hull) - { + { hullInfoFrame.RectTransform.ScreenSpaceOffset = hullFrame.Rect.Center; + if (hullInfoFrame.Rect.Right > GameMain.GraphicsWidth) { hullInfoFrame.RectTransform.ScreenSpaceOffset -= new Point(hullInfoFrame.Rect.Width, 0); } + if (hullInfoFrame.Rect.Bottom > GameMain.GraphicsHeight) { hullInfoFrame.RectTransform.ScreenSpaceOffset -= new Point(0, hullInfoFrame.Rect.Height); } + hullInfoFrame.Visible = true; hullNameText.Text = hull.DisplayName; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index e72a1048d..ab89225c5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -236,7 +236,6 @@ namespace Barotrauma.Items.Components var directionalModeSwitchText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1), directionalModeFrame.RectTransform, Anchor.CenterRight), TextManager.Get("SonarDirectionalPing"), GUI.Style.TextColor, GUI.SubHeadingFont, Alignment.CenterLeft); - signalWarningText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), paddedControlContainer.RectTransform), "", warningColor, textAlignment: Alignment.Center); GuiFrame.CanBeFocused = false; @@ -245,11 +244,14 @@ namespace Barotrauma.Items.Components sonarView = new GUICustomComponent(new RectTransform(Vector2.One * 0.7f, GuiFrame.RectTransform, Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight), (spriteBatch, guiCustomComponent) => { DrawSonar(spriteBatch, guiCustomComponent.Rect); }, null); + signalWarningText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), sonarView.RectTransform, Anchor.Center, Pivot.BottomCenter), + "", warningColor, GUI.LargeFont, Alignment.Center); + // Setup layout for nav terminal if (isConnectedToSteering) { - controlContainer.RectTransform.SetPosition(Anchor.TopLeft); controlContainer.RectTransform.RelativeOffset = controlBoxOffset; + controlContainer.RectTransform.SetPosition(Anchor.TopLeft); sonarView.RectTransform.ScaleBasis = ScaleBasis.Smallest; sonarView.RectTransform.SetPosition(Anchor.CenterRight); sonarView.RectTransform.Resize(Vector2.One * GUI.RelativeHorizontalAspectRatio * sonarAreaSize); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs index 700028d02..2fa79e267 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs @@ -108,10 +108,7 @@ namespace Barotrauma.Items.Components private void CreateGUI() { - controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, Sonar.controlBoxSize.Y + 0.02f), GuiFrame.RectTransform, Anchor.CenterLeft) - { - RelativeOffset = new Vector2(0, 0) // The y offset should be based on the relative size difference of the steering and the status windows - }, "ItemUI"); + controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterLeft), "ItemUI"); var paddedControlContainer = new GUIFrame(new RectTransform(controlContainer.Rect.Size - GUIStyle.ItemFrameMargin, controlContainer.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset @@ -120,7 +117,7 @@ namespace Barotrauma.Items.Components var steeringModeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), paddedControlContainer.RectTransform, Anchor.TopLeft), style: null); steeringModeSwitch = new GUIButton(new RectTransform(new Vector2(0.2f, 1), steeringModeArea.RectTransform), string.Empty, style: "SwitchVertical") { - Selected = false, + Selected = autoPilot, Enabled = true, OnClicked = (button, data) => { @@ -141,34 +138,26 @@ namespace Barotrauma.Items.Components manualPilotIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), steeringModeRightSide.RectTransform, Anchor.TopLeft), TextManager.Get("SteeringManual"), font: GUI.SubHeadingFont, style: "IndicatorLightRedSmall") { - Selected = true, + Selected = !autoPilot, Enabled = false }; autopilotIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), steeringModeRightSide.RectTransform, Anchor.BottomLeft), TextManager.Get("SteeringAutoPilot"), font: GUI.SubHeadingFont, style: "IndicatorLightRedSmall") { - Selected = false, + Selected = autoPilot, Enabled = false }; manualPilotIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor); autopilotIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor); GUITextBlock.AutoScaleAndNormalize(manualPilotIndicator.TextBlock, autopilotIndicator.TextBlock); - var autoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.6f), paddedControlContainer.RectTransform, Anchor.BottomCenter) - { - RelativeOffset = new Vector2(0, 0.02f) - }, "OutlineFrame"); - var paddedAutoPilotControls = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), autoPilotControls.RectTransform, Anchor.Center)) - { - Stretch = true, - RelativeSpacing = 0.03f, - ChildAnchor = Anchor.TopLeft - }; + var autoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.62f), paddedControlContainer.RectTransform, Anchor.BottomCenter), "OutlineFrame"); + var paddedAutoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.92f, 0.88f), autoPilotControls.RectTransform, Anchor.Center), style: null); - maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.3f), paddedAutoPilotControls.RectTransform), + maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.TopCenter), TextManager.Get("SteeringMaintainPos"), font: GUI.SmallFont, style: "GUIRadioButton") { - Enabled = false, + Enabled = autoPilot, Selected = maintainPos, OnSelected = tickBox => { @@ -200,12 +189,12 @@ namespace Barotrauma.Items.Components return true; } }; - - levelStartTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.3f), paddedAutoPilotControls.RectTransform), - GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, 30), + int textLimit = (int)(MathHelper.Clamp(25 * GUI.xScale, 15, 35)); + levelStartTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.Center), + GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, textLimit), font: GUI.SmallFont, style: "GUIRadioButton") { - Enabled = false, + Enabled = autoPilot, Selected = levelStartSelected, OnSelected = tickBox => { @@ -228,11 +217,11 @@ namespace Barotrauma.Items.Components } }; - levelEndTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.3f), paddedAutoPilotControls.RectTransform), - GameMain.GameSession?.EndLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, 30), + levelEndTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.BottomCenter), + GameMain.GameSession?.EndLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, textLimit), font: GUI.SmallFont, style: "GUIRadioButton") { - Enabled = false, + Enabled = autoPilot, Selected = levelEndSelected, OnSelected = tickBox => { @@ -254,14 +243,14 @@ namespace Barotrauma.Items.Components return true; } }; - - GUITextBlock.AutoScaleAndNormalize(maintainPosTickBox.TextBlock, levelStartTickBox.TextBlock, levelEndTickBox.TextBlock); maintainPosTickBox.RectTransform.IsFixedSize = levelStartTickBox.RectTransform.IsFixedSize = levelEndTickBox.RectTransform.IsFixedSize = false; maintainPosTickBox.RectTransform.MaxSize = levelStartTickBox.RectTransform.MaxSize = levelEndTickBox.RectTransform.MaxSize = new Point(int.MaxValue, paddedAutoPilotControls.Rect.Height / 3); maintainPosTickBox.RectTransform.MinSize = levelStartTickBox.RectTransform.MinSize = levelEndTickBox.RectTransform.MinSize = Point.Zero; + GUITextBlock.AutoScaleAndNormalize(scaleHorizontal: false, scaleVertical: true, maintainPosTickBox.TextBlock, levelStartTickBox.TextBlock, levelEndTickBox.TextBlock); + GUIRadioButtonGroup destinations = new GUIRadioButtonGroup(); destinations.AddRadioButton((int)Destination.MaintainPos, maintainPosTickBox); destinations.AddRadioButton((int)Destination.LevelStart, levelStartTickBox); @@ -401,11 +390,12 @@ namespace Barotrauma.Items.Components (spriteBatch, guiCustomComponent) => { DrawHUD(spriteBatch, guiCustomComponent.Rect); }, null); steerRadius = steerArea.Rect.Width / 2; - // Tooltip/helper text - pressureWarningText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), paddedStatusContainer.RectTransform), TextManager.Get("SteeringDepthWarning"), GUI.Style.Red) + pressureWarningText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), steerArea.RectTransform, Anchor.Center, Pivot.TopCenter), + TextManager.Get("SteeringDepthWarning"), Color.Red, GUI.LargeFont, Alignment.Center) { Visible = false }; + // Tooltip/helper text tipContainer = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.1f), steerArea.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) , "", font: GUI.Font, wrap: true, style: "GUIToolTip", textAlignment: Alignment.Center) { @@ -423,6 +413,7 @@ namespace Barotrauma.Items.Components { GuiFrame.ClearChildren(); CreateGUI(); + UpdateGUIElements(); } /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index 0486d3c53..6bba6a561 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -152,7 +152,10 @@ namespace Barotrauma.Items.Components DraggingConnected.Connections[1]?.ConnectionPanel == panel) { DraggingConnected.RemoveConnection(panel.Item); - panel.DisconnectedWires.Add(DraggingConnected); + if (DraggingConnected.Item.ParentInventory == null) + { + panel.DisconnectedWires.Add(DraggingConnected); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs index 674ebad69..7deb2705e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs @@ -134,46 +134,57 @@ namespace Barotrauma.Items.Components } if (IsActive && item.ParentInventory?.Owner is Character user && user == Character.Controlled)// && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) { - Vector2 gridPos = Character.Controlled.Position; - Vector2 roundedGridPos = new Vector2( - MathUtils.RoundTowardsClosest(Character.Controlled.Position.X, Submarine.GridSize.X), - MathUtils.RoundTowardsClosest(Character.Controlled.Position.Y, Submarine.GridSize.Y)); - //Vector2 attachPos = GetAttachPosition(user); - - if (item.Submarine == null) + if (user.CanInteract) { - Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition); - if (attachTarget != null) + Vector2 gridPos = Character.Controlled.Position; + Vector2 roundedGridPos = new Vector2( + MathUtils.RoundTowardsClosest(Character.Controlled.Position.X, Submarine.GridSize.X), + MathUtils.RoundTowardsClosest(Character.Controlled.Position.Y, Submarine.GridSize.Y)); + //Vector2 attachPos = GetAttachPosition(user); + + if (item.Submarine == null) { - if (attachTarget.Submarine != null) + Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition); + if (attachTarget != null) { - //set to submarine-relative position - gridPos += attachTarget.Submarine.Position; - roundedGridPos += attachTarget.Submarine.Position; + if (attachTarget.Submarine != null) + { + //set to submarine-relative position + gridPos += attachTarget.Submarine.Position; + roundedGridPos += attachTarget.Submarine.Position; + } } } + else + { + gridPos += item.Submarine.Position; + roundedGridPos += item.Submarine.Position; + } + + Submarine.DrawGrid(spriteBatch, 14, gridPos, roundedGridPos, alpha: 0.7f); + + WireSection.Draw( + spriteBatch, this, + new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, + new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, + item.Color, 0.0f, 0.3f); + + WireSection.Draw( + spriteBatch, this, + new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, + item.DrawPosition, + item.Color, itemDepth, 0.3f); + + GUI.DrawRectangle(spriteBatch, new Vector2(newNodePos.X + drawOffset.X, -(newNodePos.Y + drawOffset.Y)) - Vector2.One * 3, Vector2.One * 6, item.Color); } else { - gridPos += item.Submarine.Position; - roundedGridPos += item.Submarine.Position; + WireSection.Draw( + spriteBatch, this, + new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, + item.DrawPosition, + item.Color, 0.0f, 0.3f); } - - Submarine.DrawGrid(spriteBatch, 14, gridPos, roundedGridPos, alpha: 0.7f); - - WireSection.Draw( - spriteBatch, this, - new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, - new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, - item.Color, 0.0f, 0.3f); - - WireSection.Draw( - spriteBatch, this, - new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, - item.DrawPosition, - item.Color, itemDepth, 0.3f); - - GUI.DrawRectangle(spriteBatch, new Vector2(newNodePos.X + drawOffset.X, -(newNodePos.Y + drawOffset.Y)) - Vector2.One * 3, Vector2.One * 6, item.Color); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 55eee788e..9adbe6230 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -53,14 +53,10 @@ namespace Barotrauma { int buttonDir = Math.Sign(SubInventoryDir); - Vector2 equipIndicatorPos = new Vector2( - Rect.Center.X - Inventory.EquipIndicator.size.X / 2 * Inventory.UIScale, - Rect.Center.Y + (Rect.Height / 2 + 25 * Inventory.UIScale) * buttonDir - Inventory.EquipIndicator.size.Y / 2 * Inventory.UIScale); + Vector2 equipIndicatorPos = new Vector2(Rect.Left, Rect.Top); equipIndicatorPos += DrawOffset; - return new Rectangle( - (int)(equipIndicatorPos.X), (int)(equipIndicatorPos.Y), - (int)(Inventory.EquipIndicator.size.X * Inventory.UIScale), (int)(Inventory.EquipIndicator.size.Y * Inventory.UIScale)); + return new Rectangle((int)equipIndicatorPos.X, (int)equipIndicatorPos.Y, Rect.Width, (int)(Inventory.EquipIndicator.size.Y * Inventory.UIScale)); } } @@ -132,11 +128,27 @@ namespace Barotrauma protected Point prevScreenResolution; protected static Sprite slotHotkeySprite; - public static Sprite SlotSpriteSmall; - public static Sprite EquipIndicator, EquipIndicatorHighlight; + + private static Sprite slotSpriteSmall; + public static Sprite SlotSpriteSmall + { + get + { + if (slotSpriteSmall == null) + { + //TODO: define these in xml + slotSpriteSmall = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(12, 9, 115, 115), null, 0); + slotSpriteSmall.size = new Vector2(SlotSpriteSmall.SourceRect.Width * 0.682f, SlotSpriteSmall.SourceRect.Height * 0.682f); + } + return slotSpriteSmall; + } + } + public static Sprite DraggableIndicator; + public static Sprite EquipIndicator; + public static Inventory DraggingInventory; - public Rectangle BackgroundFrame { get; protected set; } + public Rectangle BackgroundFrame; private ushort[] receivedItemIDs; private CoroutineHandle syncItemsCoroutine; @@ -151,6 +163,8 @@ namespace Barotrauma private Point savedPosition, originalPos; private bool canMove = false; private bool positionUpdateQueued = false; + private Vector2 draggableIndicatorOffset; + private float draggableIndicatorScale; public class SlotReference { @@ -291,7 +305,7 @@ namespace Barotrauma int columns = Math.Min(slotsPerRow, capacity); Vector2 spacing = new Vector2(5.0f * UIScale); - spacing.Y += (this is CharacterInventory) ? EquipIndicator.size.Y * UIScale : ContainedIndicatorHeight; + spacing.Y += (this is CharacterInventory) ? EquipIndicator.size.Y : ContainedIndicatorHeight; Vector2 rectSize = new Vector2(60.0f * UIScale); padding = new Vector4(spacing.X, spacing.Y, spacing.X, spacing.X); @@ -370,7 +384,12 @@ namespace Barotrauma protected virtual bool HideSlot(int i) { - return slots[i].Disabled || (hideEmptySlot[i] && Items[i] == null); + return slots[i].Disabled; + } + + protected virtual bool IsSlotHiddenDueToToggleState(int i) + { + return false; } public virtual void Update(float deltaTime, Camera cam, bool subInventory = false) @@ -386,7 +405,7 @@ namespace Barotrauma { for (int i = 0; i < capacity; i++) { - if (HideSlot(i)) { continue; } + if (HideSlot(i) || IsSlotHiddenDueToToggleState(i)) { continue; } UpdateSlot(slots[i], i, Items[i], subInventory); } if (!isSubInventory) @@ -516,6 +535,9 @@ namespace Barotrauma { if (PlayerInput.PrimaryMouseButtonDown()) { + // Prevent us from dragging an item + draggingItem = null; + draggingSlot = null; DraggingInventory = subInventory; } } @@ -535,7 +557,7 @@ namespace Barotrauma var slot = slots[slotIndex]; int dir = slot.SubInventoryDir; Rectangle subRect = slot.Rect; - Vector2 spacing = new Vector2(10 * UIScale, (10 + EquipIndicator.size.Y) * UIScale); + Vector2 spacing = new Vector2(10 * UIScale, (10 * UIScale + EquipIndicator.size.Y)); int columns = (int)Math.Max(Math.Floor(Math.Sqrt(itemCapacity)), 1); while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f) @@ -631,18 +653,24 @@ namespace Barotrauma /// Is the mouse on any inventory element (slot, equip button, subinventory...) /// /// - public static bool IsMouseOnInventory() + public static bool IsMouseOnInventory(bool ignoreDrag = false) { if (Character.Controlled == null) return false; - if (draggingItem != null || DraggingInventory != null) return true; + if (!ignoreDrag && draggingItem != null || DraggingInventory != null) return true; if (Character.Controlled.Inventory != null) { var inv = Character.Controlled.Inventory; + + if (inv.BackgroundFrame.Contains(PlayerInput.MousePosition) || inv.InventoryToggleContains) return true; + for (var i = 0; i < inv.slots.Length; i++) { var slot = inv.slots[i]; + + if (inv.HideSlot(i) || inv.IsSlotHiddenDueToToggleState(i)) continue; + if (slot.InteractRect.Contains(PlayerInput.MousePosition)) { return true; @@ -715,6 +743,11 @@ namespace Barotrauma if (inv == null) { return CursorState.Default; } + if (inv.InventoryToggleContains) + { + return CursorState.Hand; + } + foreach (var item in inv.Items) { var container = item?.GetComponent(); @@ -765,8 +798,12 @@ namespace Barotrauma } } - foreach (var slot in inv.slots) + for (int i = 0; i < inv.slots.Length; i++) { + InventorySlot slot = inv.slots[i]; + + if (inv.IsSlotHiddenDueToToggleState(i)) continue; + if (slot.EquipButtonRect.Contains(PlayerInput.MousePosition)) { return CursorState.Hand; @@ -783,6 +820,7 @@ namespace Barotrauma } } } + return CursorState.Default; } @@ -850,6 +888,12 @@ namespace Barotrauma { if (positionUpdateQueued) // Wait a frame before updating the positioning of the container after a resolution change to have everything working { + int height = (int)(movableFrameRectHeight * UIScale); + CreateSlots(); + container.Inventory.movableFrameRect = new Rectangle(container.Inventory.BackgroundFrame.X, container.Inventory.BackgroundFrame.Y - height, container.Inventory.BackgroundFrame.Width, height); + draggableIndicatorScale = 1.25f * UIScale; + draggableIndicatorOffset = DraggableIndicator.size * draggableIndicatorScale / 2f; + draggableIndicatorOffset += new Vector2(height / 2f - draggableIndicatorOffset.Y); container.Inventory.originalPos = container.Inventory.savedPosition = container.Inventory.movableFrameRect.Center; positionUpdateQueued = false; } @@ -862,12 +906,13 @@ namespace Barotrauma prevScreenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); prevUIScale = UIScale; prevHUDScale = GUI.Scale; - int height = (int)(movableFrameRectHeight * UIScale); - container.Inventory.movableFrameRect = new Rectangle(container.Inventory.BackgroundFrame.X, container.Inventory.BackgroundFrame.Y - height, container.Inventory.BackgroundFrame.Width, height); positionUpdateQueued = true; } - - GUI.DrawRectangle(spriteBatch, container.Inventory.movableFrameRect, movableFrameRectColor, true); + else + { + GUI.DrawRectangle(spriteBatch, container.Inventory.movableFrameRect, movableFrameRectColor, true); + DraggableIndicator.Draw(spriteBatch, container.Inventory.movableFrameRect.Location.ToVector2() + draggableIndicatorOffset, 0, draggableIndicatorScale); + } } } @@ -886,17 +931,20 @@ namespace Barotrauma if (selectedSlot == null) { - if (DraggingItemToWorld && - Character.Controlled.FocusedItem?.OwnInventory != null && - Character.Controlled.FocusedItem.OwnInventory.CanBePut(draggingItem) && - Character.Controlled.FocusedItem.OwnInventory.TryPutItem(draggingItem, Character.Controlled)) + if (!IsMouseOnInventory(true)) { - GUI.PlayUISound(GUISoundType.PickItem); - } - else - { - GUI.PlayUISound(GUISoundType.DropItem); - draggingItem.Drop(Character.Controlled); + if (DraggingItemToWorld && + Character.Controlled.FocusedItem?.OwnInventory != null && + Character.Controlled.FocusedItem.OwnInventory.CanBePut(draggingItem) && + 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) @@ -1028,7 +1076,7 @@ namespace Barotrauma bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement; - if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null) + if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null && !IsMouseOnInventory(true)) { var shadowSprite = GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0]; string toolTip = mouseOnHealthInterface ? TextManager.Get("QuickUseAction.UseTreatment") : @@ -1053,7 +1101,7 @@ namespace Barotrauma } } - if (selectedSlot != null && selectedSlot.Item != null) + if (selectedSlot != null && selectedSlot.Item != null && selectedSlot.Slot.EquipButtonState != GUIComponent.ComponentState.Hover) { Rectangle slotRect = selectedSlot.Slot.Rect; slotRect.Location += selectedSlot.Slot.DrawOffset.ToPoint(); @@ -1091,11 +1139,26 @@ namespace Barotrauma if (inventory != null && (CharacterInventory.PersonalSlots.HasFlag(type) || (inventory.isSubInventory && (inventory.Owner as Item) != null && (inventory.Owner as Item).AllowedSlots.Any(a => CharacterInventory.PersonalSlots.HasFlag(a))))) { - slotColor = slot.IsHighlighted ? GUIColorSettings.EquipmentSlotColor : GUIColorSettings.EquipmentSlotColor * 0.8f; + if (item == null) + { + slotColor = slot.IsHighlighted ? GUIColorSettings.EquipmentSlotEmptyColor : GUIColorSettings.EquipmentSlotEmptyColor * 0.8f; + } + else + { + slotColor = slot.IsHighlighted ? GUIColorSettings.EquipmentSlotColor : GUIColorSettings.EquipmentSlotColor * 0.8f; + } + } else { - slotColor = slot.IsHighlighted ? GUIColorSettings.InventorySlotColor : GUIColorSettings.InventorySlotColor * 0.8f; + if (item != null && Character.Controlled.HasEquippedItem(item)) + { + slotColor = slot.IsHighlighted ? GUIColorSettings.InventorySlotEquippedColor : GUIColorSettings.InventorySlotEquippedColor * 0.8f; + } + else + { + slotColor = slot.IsHighlighted ? GUIColorSettings.InventorySlotColor : GUIColorSettings.InventorySlotColor * 0.8f; + } } if (inventory != null && inventory.Locked) { slotColor = Color.Gray * 0.5f; } @@ -1210,7 +1273,7 @@ namespace Barotrauma if (GameMain.DebugDraw) { GUI.DrawRectangle(spriteBatch, rect, Color.White, false, 0, 1); - GUI.DrawRectangle(spriteBatch, slot.EquipButtonRect, Color.White, false, 0, 1); + GUI.DrawRectangle(spriteBatch, slot.EquipButtonRect, Color.Red, false, 0, 1); } if (slot.HighlightColor != Color.Transparent) @@ -1221,8 +1284,11 @@ namespace Barotrauma if (item != null && drawItem) { Sprite sprite = item.Prefab.InventoryIcon ?? item.Sprite; - float scale = Math.Min(Math.Min((rect.Width - 10) / sprite.size.X, (rect.Height - 10) / sprite.size.Y), 2.0f); - Vector2 itemPos = rect.Center.ToVector2(); + + float equipButtonHeightAdjustment = inventory == Character.Controlled?.Inventory ? slot.EquipButtonRect.Height : 0; + + float scale = Math.Min(Math.Min((rect.Width - 10) / sprite.size.X, (rect.Height - 5 - equipButtonHeightAdjustment) / sprite.size.Y), 2.0f); + Vector2 itemPos = rect.Center.ToVector2() + new Vector2(0, equipButtonHeightAdjustment / 2f); if (itemPos.Y > GameMain.GraphicsHeight) { itemPos.Y -= Math.Min( @@ -1248,15 +1314,6 @@ namespace Barotrauma } sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale); } - - if (inventory != null && - !inventory.Locked && - Character.Controlled?.Inventory == inventory && - slot.QuickUseKey != Keys.None) - { - spriteBatch.Draw(slotHotkeySprite.Texture, rect.ScaleSize(1.25f), slotHotkeySprite.SourceRect, slotColor); - GUI.DrawString(spriteBatch, rect.Location.ToVector2() + new Vector2(1, -2), slot.QuickUseKey.ToString().Substring(1, 1), Color.Black, font: GUI.SmallFont); - } } public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) @@ -1273,7 +1330,7 @@ namespace Barotrauma //prevents the inventory from briefly reverting to an old state if items are moved around in quick succession //also delay if we're still midround syncing, some of the items in the inventory may not exist yet - if (syncItemsDelay > 0.0f || GameMain.Client.MidRoundSyncing) + if (syncItemsDelay > 0.0f || GameMain.Client.MidRoundSyncing || NetIdUtils.IdMoreRecent(lastEventID, GameMain.Client.EntityEventManager.LastReceivedID)) { if (syncItemsCoroutine != null) CoroutineManager.StopCoroutines(syncItemsCoroutine); syncItemsCoroutine = CoroutineManager.StartCoroutine(SyncItemsAfterDelay(lastEventID)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index b0634c374..21fa69741 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -698,16 +698,19 @@ namespace Barotrauma //reset positions first List elementsToMove = new List(); - if (editingHUD != null && editingHUD.UserData == this) + if (editingHUD != null && editingHUD.UserData == this && + ((HasInGameEditableProperties && Character.Controlled?.SelectedConstruction == this) || Screen.Selected == GameMain.SubEditorScreen)) { elementsToMove.Add(editingHUD); - } - + } + + debugInitialHudPositions.Clear(); foreach (ItemComponent ic in activeHUDs) { - if (ic.GuiFrame == null || ic.AllowUIOverlap || ic.GetLinkUIToComponent() != null) continue; + if (ic.GuiFrame == null || ic.AllowUIOverlap || ic.GetLinkUIToComponent() != null) { continue; } ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero; elementsToMove.Add(ic.GuiFrame); + debugInitialHudPositions.Add(ic.GuiFrame.Rect); } List disallowedAreas = new List(); @@ -728,18 +731,22 @@ namespace Barotrauma foreach (ItemComponent ic in activeHUDs) { - if (ic.GuiFrame == null) continue; + if (ic.GuiFrame == null) { continue; } + + var linkUIToComponent = ic.GetLinkUIToComponent(); - if (linkUIToComponent == null) continue; - + if (linkUIToComponent == null) { continue; } + ic.GuiFrame.RectTransform.ScreenSpaceOffset = linkUIToComponent.GuiFrame.RectTransform.ScreenSpaceOffset; } } + private readonly List debugInitialHudPositions = new List(); + public void UpdateHUD(Camera cam, Character character, float deltaTime) { bool editingHUDCreated = false; - if (HasInGameEditableProperties || + if ((HasInGameEditableProperties && character.SelectedConstruction == this) || Screen.Selected == GameMain.SubEditorScreen) { GUIComponent prevEditingHUD = editingHUD; @@ -850,6 +857,23 @@ namespace Barotrauma ic.DrawHUD(spriteBatch, character); } } + + if (GameMain.DebugDraw) + { + int i = 0; + foreach (ItemComponent ic in activeHUDs) + { + if (i >= debugInitialHudPositions.Count) { break; } + if (activeHUDs[i].GuiFrame == null) { continue; } + if (ic.GuiFrame == null || ic.AllowUIOverlap || ic.GetLinkUIToComponent() != null) { continue; } + + GUI.DrawRectangle(spriteBatch, debugInitialHudPositions[i], Color.Orange); + GUI.DrawRectangle(spriteBatch, ic.GuiFrame.Rect, Color.LightGreen); + GUI.DrawLine(spriteBatch, debugInitialHudPositions[i].Location.ToVector2(), ic.GuiFrame.Rect.Location.ToVector2(), Color.Orange); + + i++; + } + } } readonly List texts = new List(); @@ -1249,10 +1273,9 @@ namespace Barotrauma { inventory = container.Inventory; } - } + } } - var item = new Item(itemPrefab, pos, sub) { ID = itemId @@ -1262,8 +1285,8 @@ namespace Barotrauma { wifiComponent.TeamID = (Character.TeamType)teamID; } - if (descriptionChanged) item.Description = itemDesc; - if (tagsChanged) item.Tags = tags; + if (descriptionChanged) { item.Description = itemDesc; } + if (tagsChanged) { item.Tags = tags; } if (sub != null) { @@ -1276,7 +1299,7 @@ namespace Barotrauma if (inventorySlotIndex >= 0 && inventorySlotIndex < 255 && inventory.TryPutItem(item, inventorySlotIndex, false, false, null, false)) { - return null; + return item; } inventory.TryPutItem(item, null, item.AllowedSlots, false); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemInventory.cs index d637d1aa6..686a91e48 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemInventory.cs @@ -11,6 +11,12 @@ namespace Barotrauma { base.ControlInput(cam); cam.OffsetAmount = 0; + + if (Character.Controlled?.Inventory != null) + { + Character.Controlled.Inventory.ToggleInventory(true); + } + //if this is used, we need to implement syncing this inventory with the server /*Character.DisableControls = true; if (Character.Controlled != null) @@ -19,7 +25,7 @@ namespace Barotrauma { Character.Controlled.SelectedConstruction = null; } - }*/ + }*/ } protected override void CalculateBackgroundFrame() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index b290632f0..c29f8c331 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -21,7 +21,7 @@ namespace Barotrauma public string Filename { - get { return Sound.Filename; } + get { return Sound?.Filename; } } public RoundSound(XElement element, Sound sound) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs index abdf9555a..76d3e90a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs @@ -307,6 +307,8 @@ namespace Barotrauma.Networking Status = FileTransferStatus.Finished, FileSize = 0 }; + + Md5Hash.RemoveFromCache(directTransfer.FilePath); OnFinished(directTransfer); } break; @@ -376,6 +378,7 @@ namespace Barotrauma.Networking { finishedTransfers.Add(new Pair(transferId, Timing.TotalTime)); StopTransfer(activeTransfer); + Md5Hash.RemoveFromCache(activeTransfer.FilePath); OnFinished(activeTransfer); } else diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 8161724be..299285eff 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -364,15 +364,7 @@ namespace Barotrauma.Networking updateInterval = new TimeSpan(0, 0, 0, 0, 150); CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo"); - } - - private bool RetryConnection(GUIButton button, object obj) - { - if (clientPeer != null) { clientPeer.Close(); } - clientPeer = null; - ConnectToServer(serverEndpoint, serverName); - return true; - } + } private bool ReturnToPreviousMenu(GUIButton button, object obj) { @@ -850,13 +842,14 @@ namespace Barotrauma.Networking disconnectReason == DisconnectReason.ExcessiveDesyncOldEvent || disconnectReason == DisconnectReason.ExcessiveDesyncRemovedEvent || disconnectReason == DisconnectReason.SyncTimeout; - + if (allowReconnect && (disconnectReason == DisconnectReason.Unknown || eventSyncError)) { if (eventSyncError) { GameMain.NetLobbyScreen.Select(); + GameMain.GameSession?.EndRound(""); gameStarted = false; myCharacter = null; } @@ -1244,6 +1237,8 @@ namespace Barotrauma.Networking mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); } + GameMain.GameSession.Mission?.ClientReadInitial(inc); + if (GameMain.GameSession.Submarine.IsFileCorrupted) { DebugConsole.ThrowError($"Failed to start a round. Could not load the submarine \"{GameMain.GameSession.Submarine.Name}\"."); @@ -1635,9 +1630,9 @@ namespace Barotrauma.Networking break; case ServerNetObject.ENTITY_POSITION: UInt16 id = inc.ReadUInt16(); - byte msgLength = inc.ReadByte(); + uint msgLength = inc.ReadVariableUInt32(); - int msgEndPos = inc.BitPosition + msgLength * 8; + int msgEndPos = (int)(inc.BitPosition + msgLength * 8); var entity = Entity.FindEntityByID(id) as IServerSerializable; if (entity != null) @@ -2367,9 +2362,14 @@ namespace Barotrauma.Networking { textBox.Deselect(); } - textBox.Text = ""; + if (ChatBox.CloseAfterMessageSent) + { + ChatBox.ToggleOpen = false; + ChatBox.CloseAfterMessageSent = false; + } + return true; } @@ -2443,13 +2443,16 @@ namespace Barotrauma.Networking { msgBox.AddToGUIUpdateList(); ChatBox.GUIFrame.Flash(Color.DarkGreen, 0.5f); + ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen; ChatBox.ToggleOpen = true; + ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen; } if (radioKeyHit) { msgBox.AddToGUIUpdateList(); ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f); + ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen; ChatBox.ToggleOpen = true; if (!msgBox.Text.StartsWith(ChatBox.RadioChatString)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs index a8844d9b7..14d2dc31d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs @@ -195,7 +195,10 @@ namespace Barotrauma.Networking new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null); var serverMsg = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) { ScrollBarVisible = true }; - var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage, font: GUI.SmallFont, wrap: true) { CanBeFocused = true }; + var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage, font: GUI.SmallFont, wrap: true) + { + CanBeFocused = false + }; serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); }; msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs index 9a4b5b7f9..556265ebf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -35,12 +36,13 @@ namespace Barotrauma GameMain.NetLobbyScreen.Frame.FindChild("modevotes", true).Visible = value; - //gray out modes that can't be voted - foreach (GUITextBlock comp in GameMain.NetLobbyScreen.ModeList.Content.Children) + // Disable modes that cannot be voted on + foreach (var guiComponent in GameMain.NetLobbyScreen.ModeList.Content.Children) { - comp.TextColor = - new Color(comp.TextColor.R, comp.TextColor.G, comp.TextColor.B, - !allowModeVoting || ((GameModePreset)comp.UserData).Votable ? (byte)255 : (byte)100); + if (guiComponent is GUIFrame frame) + { + frame.CanBeFocused = !allowModeVoting || ((GameModePreset) frame.UserData).Votable; + } } UpdateVoteTexts(null, VoteType.Mode); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index 3adb061d8..1eb384919 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -234,7 +234,7 @@ namespace Barotrauma !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought)); foreach (MapEntityCategory category in itemCategories) { - var categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), + var categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), "", style: "ItemCategory" + category.ToString()) { UserData = category, @@ -253,7 +253,14 @@ namespace Barotrauma }; itemCategoryButtons.Add(categoryButton); - new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.25f), categoryButton.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.02f) }, + categoryButton.RectTransform.SizeChanged += () => + { + var sprite = categoryButton.Frame.sprites[GUIComponent.ComponentState.None].First(); + categoryButton.RectTransform.NonScaledSize = + new Point(categoryButton.Rect.Width, (int)(categoryButton.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width))); + }; + + new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.256f), categoryButton.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.02f) }, TextManager.Get("MapEntityCategory." + category), textAlignment: Alignment.Center, textColor: categoryButton.TextColor) { Padding = Vector4.Zero, @@ -262,7 +269,7 @@ namespace Barotrauma HoverColor = Color.Transparent, PressedColor = Color.Transparent, SelectedColor = Color.Transparent, - CanBeFocused = false + CanBeFocused = true }; } FillStoreItemList(); @@ -715,11 +722,12 @@ namespace Barotrauma for (int i = 0; i < availableMissions.Count; i++) { var mission = availableMissions[i]; - var tickBox = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.1f), missionContent.RectTransform), + var tickBox = new GUITickBox(new RectTransform(new Vector2(0.65f, 0.1f), missionContent.RectTransform), mission?.Name ?? TextManager.Get("NoMission"), style: "GUIRadioButton") { Enabled = GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign) }; + tickBox.TextBlock.Wrap = true; missionTickBoxes.Add(tickBox); missionRadioButtonGroup.AddRadioButton(i, tickBox); } @@ -833,7 +841,7 @@ namespace Barotrauma var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 1.0f), frame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft) { - RelativeSpacing = 0.02f, + AbsoluteSpacing = (int)(5 * GUI.Scale), Stretch = true }; @@ -874,8 +882,11 @@ namespace Barotrauma UserData = pi, IntValue = pi.Quantity }; + amountInput.TextBox.OnSelected += (sender, key) => { suppressBuySell = true; }; + amountInput.TextBox.OnDeselected += (sender, key) => { suppressBuySell = false; amountInput.OnValueChanged?.Invoke(amountInput); }; amountInput.OnValueChanged += (numberInput) => { + if (suppressBuySell) { return; } PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem; //Attempting to buy @@ -889,7 +900,6 @@ namespace Barotrauma { BuyItem(numberInput, purchasedItem); } - numberInput.IntValue = purchasedItem.Quantity; } //Attempting to sell else @@ -901,14 +911,15 @@ namespace Barotrauma } } }; + frame.HoverColor = frame.SelectedColor = Color.Transparent; } listBox.RecalculateChildren(); content.Recalculate(); content.RectTransform.RecalculateChildren(true, true); amountInput?.LayoutGroup.Recalculate(); textBlock.Text = ToolBox.LimitString(textBlock.Text, textBlock.Font, textBlock.Rect.Width); - /*content.RectTransform.IsFixedSize = true; - content.RectTransform.Children.ForEach(c => c.IsFixedSize = true);*/ + //content.RectTransform.IsFixedSize = true; + content.RectTransform.Children.ForEach(c => c.IsFixedSize = true); return frame; } @@ -946,21 +957,33 @@ namespace Barotrauma return false; } + private bool suppressBuySell; + private void RefreshMyItems() { HashSet existingItemFrames = new HashSet(); foreach (PurchasedItem pi in Campaign.CargoManager.PurchasedItems) { - var itemFrame = myItemList.Content.GetChildByUserData(pi); + var itemFrame = myItemList.Content.Children.FirstOrDefault(c => + c.UserData is PurchasedItem pi2 && pi.ItemPrefab == pi2.ItemPrefab); if (itemFrame == null) { var priceInfo = pi.ItemPrefab.GetPrice(Campaign.Map.CurrentLocation); if (priceInfo == null) { continue; } itemFrame = CreateItemFrame(pi, priceInfo, myItemList); - itemFrame.Flash(GUI.Style.Green); + itemFrame.Flash(GUI.Style.Green); + } + else + { + itemFrame.UserData = itemFrame.GetChild(0).GetChild().UserData = pi; } - itemFrame.GetChild(0).GetChild().IntValue = pi.Quantity; existingItemFrames.Add(itemFrame); + + suppressBuySell = true; + var numInput = itemFrame.GetChild(0).GetChild(); + if (numInput.IntValue != pi.Quantity) { itemFrame.Flash(GUI.Style.Green); } + numInput.IntValue = (itemFrame.UserData as PurchasedItem).Quantity = pi.Quantity; + suppressBuySell = false; } var removedItemFrames = myItemList.Content.Children.Except(existingItemFrames).ToList(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index 78577f46d..3eeb3be8a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -204,7 +204,7 @@ namespace Barotrauma Submarine.DrawBack(spriteBatch, false, e => !(e is Structure) || e.SpriteDepth < 0.9f); foreach (Character c in Character.CharacterList) { - if (c.AnimController.Limbs.Any(l => l.DeformSprite != null) || !c.IsVisible) { continue; } + if (!c.IsVisible || c.AnimController.Limbs.Any(l => l.DeformSprite != null)) { continue; } c.Draw(spriteBatch, Cam); } spriteBatch.End(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index 267908311..d8144f7e3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -1111,7 +1111,7 @@ namespace Barotrauma Alignment textAlignment = Alignment.CenterLeft; Vector2 textFieldSize = new Vector2(0.5f, 1.0f); Vector2 tickBoxSize = new Vector2(0.4f, 0.07f); - var content = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 0.9f), menuTabs[(int)Tab.HostServer].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) + var content = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.9f), menuTabs[(int)Tab.HostServer].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) { RelativeSpacing = 0.02f, Stretch = true @@ -1122,7 +1122,7 @@ namespace Barotrauma //play style ----------------------------------------------------- - var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.5f, 0.1f), parent.RectTransform), style: null, color: Color.Black); + var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.35f, 0.1f), parent.RectTransform), style: null, color: Color.Black); playstyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 0.1f), playstyleContainer.RectTransform), ServerListScreen.PlayStyleBanners[0], scaleToFit: true) @@ -1263,7 +1263,7 @@ namespace Barotrauma var tickboxAreaLower = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true); - karmaEnabledBox = new GUITickBox(new RectTransform(new Vector2(0.4f, 1.0f), tickboxAreaLower.RectTransform), TextManager.Get("ServerSettingsUseKarma")) + karmaEnabledBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), TextManager.Get("ServerSettingsUseKarma")) { ToolTip = TextManager.Get("karmaexplanation"), OnSelected = (tb) => @@ -1272,7 +1272,7 @@ namespace Barotrauma return true; } }; - karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(0.6f, 1.0f), tickboxAreaLower.RectTransform)) + karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform)) { ButtonEnabled = false, Enabled = false diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index e7f67856f..f3506cfd6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -24,6 +24,8 @@ namespace Barotrauma private GUIListBox chatBox, playerList; private GUIListBox serverLogBox, serverLogFilterTicks; + private GUIComponent jobVariantTooltip; + private GUITextBox chatInput; private GUITextBox serverLogFilter; public GUITextBox ChatInput @@ -419,14 +421,14 @@ namespace Barotrauma RelativeSpacing = 0.05f }; FileTransferTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), fileTransferContent.RectTransform), "", font: GUI.SmallFont); - var fileTransferBottom = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), fileTransferContent.RectTransform), isHorizontal: true) + var fileTransferBottom = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), fileTransferContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; FileTransferProgressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), fileTransferBottom.RectTransform), 0.0f, Color.DarkGreen); FileTransferProgressText = new GUITextBlock(new RectTransform(Vector2.One, FileTransferProgressBar.RectTransform), "", font: GUI.SmallFont, textAlignment: Alignment.CenterLeft); - new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), fileTransferBottom.RectTransform), TextManager.Get("cancel")) + new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), fileTransferBottom.RectTransform), TextManager.Get("cancel"), style: "GUIButtonSmall") { OnClicked = (btn, userdata) => { @@ -458,7 +460,7 @@ namespace Barotrauma myCharacterFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), sideBar.RectTransform)); playerInfoContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), myCharacterFrame.RectTransform, Anchor.Center), style: null); - spectateBox = new GUITickBox(new RectTransform(new Vector2(0.06f, 0.06f), myCharacterFrame.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.05f) }, + spectateBox = new GUITickBox(new RectTransform(new Vector2(0.4f, 0.06f), myCharacterFrame.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.05f) }, TextManager.Get("spectatebutton")) { Selected = false, @@ -1477,6 +1479,39 @@ namespace Barotrauma } } + private void CreateJobVariantTooltip(JobPrefab jobPrefab, int variant, GUIComponent parentSlot) + { + jobVariantTooltip = new GUIFrame(new RectTransform(new Point((int)(500 * GUI.Scale), (int)(200 * GUI.Scale)), GUI.Canvas, pivot: Pivot.TopRight), + style: "GUIToolTip") + { + UserData = new Pair(jobPrefab, variant) + }; + jobVariantTooltip.RectTransform.AbsoluteOffset = new Point(parentSlot.Rect.Right, parentSlot.Rect.Bottom); + + var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), jobVariantTooltip.RectTransform, Anchor.Center)) + { + Stretch = true, + RelativeSpacing = 0.02f + }; + + string name = + TextManager.Get("jobname." + jobPrefab.Identifier + (variant + 1), returnNull: true) ?? + jobPrefab.Name; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), name, font: GUI.SubHeadingFont); + + string description = + TextManager.Get("jobdescription." + jobPrefab.Identifier + (variant + 1), returnNull: true) ?? + TextManager.Get("jobdescription." + jobPrefab.Identifier, returnNull: true) ?? + ""; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), description, wrap: true, font: GUI.SmallFont); + new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform, Anchor.BottomLeft), + onDraw: (sb, component) => { DrawJobVariantItems(sb, component, new Pair(jobPrefab, variant)); }); + + jobVariantTooltip.RectTransform.MinSize = new Point(0, content.RectTransform.Children.Sum(c => c.Rect.Height)); + } + public bool ToggleSpectate(GUITickBox tickBox) { SetSpectate(tickBox.Selected); @@ -2210,6 +2245,22 @@ namespace Barotrauma JobList.Deselect(); JobSelectionFrame.Visible = false; } + + if (GUI.MouseOn?.UserData is Pair jobPrefab && GUI.MouseOn.Style?.Name == "JobVariantButton") + { + var prevVisibleVariant = jobVariantTooltip?.UserData as Pair; + if (jobVariantTooltip == null || prevVisibleVariant.First != jobPrefab.First || prevVisibleVariant.Second != jobPrefab.Second) + { + CreateJobVariantTooltip(jobPrefab.First, jobPrefab.Second, GUI.MouseOn.Parent); + } + } + if (jobVariantTooltip != null) + { + jobVariantTooltip?.AddToGUIUpdateList(); + Rectangle mouseRect = jobVariantTooltip.MouseRect; + mouseRect.Inflate(60 * GUI.Scale, 60 * GUI.Scale); + if (!mouseRect.Contains(PlayerInput.MousePosition)) { jobVariantTooltip = null; } + } } public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) { @@ -2251,6 +2302,52 @@ namespace Barotrauma } } + private void DrawJobVariantItems(SpriteBatch spriteBatch, GUICustomComponent component, Pair jobPrefab) + { + var itemIdentifiers = jobPrefab.First.ItemIdentifiers[jobPrefab.Second] + .Distinct() + .Where(id => jobPrefab.First.ShowItemPreview[jobPrefab.Second][id]); + + Point slotSize = new Point(component.Rect.Height); + int spacing = (int)(5 * GUI.Scale); + int slotCount = itemIdentifiers.Count(); + + float totalWidth = slotSize.X * slotCount + spacing * (slotCount - 1); + if (totalWidth > component.Rect.Width) + { + slotSize = slotSize.Multiply(component.Rect.Width / totalWidth); + } + int i = 0; + foreach (string itemIdentifier in itemIdentifiers) + { + if (!(MapEntityPrefab.Find(null, identifier: itemIdentifier, showErrorMessages: false) is ItemPrefab itemPrefab)) { continue; } + + Vector2 slotPos = new Vector2(component.Rect.X + (slotSize.X + spacing) * i, component.Rect.Center.Y - slotSize.Y / 2); + + Rectangle slotRect = new Rectangle(slotPos.ToPoint(), slotSize); + Inventory.SlotSpriteSmall.Draw(spriteBatch, slotPos, + scale: slotSize.X / (float)Inventory.SlotSpriteSmall.SourceRect.Width, + color: slotRect.Contains(PlayerInput.MousePosition) ? Color.White : Color.White * 0.6f); + + Sprite icon = itemPrefab.InventoryIcon ?? itemPrefab.sprite; + float iconScale = Math.Min(Math.Min(slotSize.X / icon.size.X, slotSize.Y / icon.size.Y), 2.0f) * 0.9f; + icon.Draw(spriteBatch, slotPos + slotSize.ToVector2() * 0.5f, scale: iconScale); + + int count = jobPrefab.First.ItemIdentifiers[jobPrefab.Second].Count(id => id == itemIdentifier); + if (count > 1) + { + string itemCountText = "x" + count; + GUI.Font.DrawString(spriteBatch, itemCountText, slotPos + slotSize.ToVector2() - GUI.Font.MeasureString(itemCountText) - Vector2.UnitX * 5, Color.White); + } + + if (slotRect.Contains(PlayerInput.MousePosition)) + { + GUIComponent.DrawToolTip(spriteBatch, itemPrefab.Name+'\n'+itemPrefab.Description, slotRect); + } + i++; + } + } + public void NewChatMessage(ChatMessage message) { float prevSize = chatBox.BarSize; @@ -3012,8 +3109,10 @@ namespace Barotrauma (variantIndex + 1).ToString(), style: "JobVariantButton") { Selected = jobPrefab.Second == variantIndex, + //ToolTip = TextManager.Get("jobdescription." + jobPrefab.First.Identifier + (variantIndex + 1), returnNull: true) ?? "", UserData = new Pair(jobPrefab.First, variantIndex), }; + return btn; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 7cd362f63..f8e71d755 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1102,6 +1102,8 @@ namespace Barotrauma return true; }; + nameBox.Text = subNameLabel?.Text ?? ""; + submarineNameCharacterCount.Text = nameBox.Text.Length + " / " + submarineNameLimit; var descriptionHeaderGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.03f), leftColumn.RectTransform), true); diff --git a/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs index 00806fe99..c1fab4f3a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs @@ -66,7 +66,7 @@ namespace Barotrauma { if (sound?.Sound == null) { - string errorMsg = $"Error in StatusEffect.ApplyProjSpecific1 (sound \"{sound.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace; + string errorMsg = $"Error in StatusEffect.ApplyProjSpecific1 (sound \"{sound?.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace; GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull1" + Environment.StackTrace, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } @@ -92,7 +92,7 @@ namespace Barotrauma var selectedSound = sounds[selectedSoundIndex]; if (selectedSound?.Sound == null) { - string errorMsg = $"Error in StatusEffect.ApplyProjSpecific2 (sound \"{selectedSound.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace; + string errorMsg = $"Error in StatusEffect.ApplyProjSpecific2 (sound \"{selectedSound?.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace; GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index eb66cd697..11aa2d8fc 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.9.703.0 + 0.9.704.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index de5014235..695d03891 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.9.702.0 + 0.9.704.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index ecced590d..c4fda1ec6 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.9.703.0 + 0.9.704.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index b40af5df1..b0d2b4c3f 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.9.703.0 + 0.9.704.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 1e2c5358f..ac3f4a5fb 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.9.703.0 + 0.9.704.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 21e81d07c..b9bb39875 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -394,7 +394,7 @@ namespace Barotrauma tempBuffer.WritePadBits(); - msg.Write((byte)tempBuffer.LengthBytes); + msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes); msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 8497f6b7f..8fecc00ab 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -439,7 +439,7 @@ namespace Barotrauma client.GivePermission(permission); GameMain.Server.UpdateClientPermissions(client); NewMessage("Granted " + perm + " permissions to " + client.Name + ".", Color.White); - }, args, 2); + }, args, 1); }); AssignOnExecute("revokeperm", (string[] args) => @@ -474,7 +474,7 @@ namespace Barotrauma client.RemovePermission(permission); GameMain.Server.UpdateClientPermissions(client); NewMessage("Revoked " + perm + " permissions from " + client.Name + ".", Color.White); - }, args, 2); + }, args, 1); }); AssignOnExecute("giverank", (string[] args) => @@ -511,7 +511,7 @@ namespace Barotrauma client.SetPermissions(preset.Permissions, preset.PermittedCommands); GameMain.Server.UpdateClientPermissions(client); NewMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White); - }, args, 2); + }, args, 1); }); AssignOnExecute("givecommandperm", (string[] args) => @@ -552,7 +552,7 @@ namespace Barotrauma client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Union(grantedCommands).Distinct().ToList()); GameMain.Server.UpdateClientPermissions(client); NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White); - }, args, 2); + }, args, 1); }); AssignOnExecute("revokecommandperm", (string[] args) => @@ -592,7 +592,7 @@ namespace Barotrauma client.SetPermissions(client.Permissions, client.PermittedConsoleCommands.Except(revokedCommands).ToList()); GameMain.Server.UpdateClientPermissions(client); NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White); - }, args, 2); + }, args, 1); }); AssignOnExecute("showperm", (string[] args) => diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs new file mode 100644 index 000000000..7c3cd5b32 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs @@ -0,0 +1,16 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class CargoMission : Mission + { + public override void ServerWriteInitial(IWriteMessage msg, Client c) + { + msg.Write((ushort)items.Count); + foreach (Item item in items) + { + item.WriteSpawnData(msg, item.ID); + } + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs index 01c123b44..92697eac9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs @@ -109,5 +109,10 @@ namespace Barotrauma } } } + + public override void ServerWriteInitial(IWriteMessage msg, Client c) + { + //do nothing + } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs index 907bd5cb3..afbd78dbe 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs @@ -13,5 +13,7 @@ namespace Barotrauma GameServer.Log(TextManager.Get("MissionInfo") + ": " + header + " - " + message, ServerLog.MessageType.ServerMessage); } + + public abstract void ServerWriteInitial(IWriteMessage msg, Client c); } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs new file mode 100644 index 000000000..ef0be14e2 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs @@ -0,0 +1,22 @@ +using Barotrauma.Networking; +using System; + +namespace Barotrauma +{ + partial class MonsterMission : Mission + { + public override void ServerWriteInitial(IWriteMessage msg, Client c) + { + if (monsters.Count == 0 && monsterFiles.Count > 0) + { + throw new InvalidOperationException("Server attempted to write monster mission data when no monsters had been spawned."); + } + + msg.Write((byte)monsters.Count); + foreach (Character monster in monsters) + { + monster.WriteSpawnData(msg, monster.ID); + } + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs new file mode 100644 index 000000000..09a323ee8 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs @@ -0,0 +1,12 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class SalvageMission : Mission + { + public override void ServerWriteInitial(IWriteMessage msg, Client c) + { + item.WriteSpawnData(msg, item.ID); + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 08f605600..caff80bd3 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -273,10 +273,8 @@ namespace Barotrauma } Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); - if (Map.SelectedConnection != null) - { - Map.SelectMission(selectedMissionIndex); - } + if (Map.SelectedLocation == null) { Map.SelectRandomLocation(preferUndiscovered: true); } + if (Map.SelectedConnection != null) { Map.SelectMission(selectedMissionIndex); } List currentItems = new List(CargoManager.PurchasedItems); foreach (PurchasedItem pi in currentItems) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index ec8bab21b..8f1a873a2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -345,7 +345,7 @@ namespace Barotrauma IWriteMessage tempBuffer = new WriteOnlyMessage(); body.ServerWrite(tempBuffer, c, extraData); - msg.Write((byte)tempBuffer.LengthBytes); + msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes); msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); msg.WritePadBits(); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 0dd7868f6..94b7e95a5 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -1783,6 +1783,17 @@ namespace Barotrauma.Networking if (campaign != null) { + if (campaign.Map == null) + { + throw new Exception("Campaign map was null."); + } + if (campaign.Map.SelectedConnection == null) + { + //this should not happen, there should always be some destination selected + DebugConsole.ThrowError("No connection between locations was selected when starting the round. Choosing a random location..."); + campaign.Map.SelectRandomLocation(preferUndiscovered: true); + } + GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, reloadSub: true, loadSecondSub: teamCount > 1, @@ -2007,6 +2018,8 @@ namespace Barotrauma.Networking serverSettings.WriteMonsterEnabled(msg); + GameMain.GameSession.Mission?.ServerWriteInitial(msg, client); + serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs index e687b9e70..95768f14e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -20,6 +20,11 @@ namespace Barotrauma.Networking get { return createTime; } } + public void ResetCreateTime() + { + createTime = Timing.TotalTime; + } + public ServerEntityEvent(IServerSerializable serializableEntity, UInt16 id) : base(serializableEntity, id) { @@ -223,6 +228,7 @@ namespace Barotrauma.Networking { lastWarningTime = Timing.TotalTime; GameServer.Log("WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() + ", latest create id: " + ID.ToString(), ServerLog.MessageType.ServerMessage); + events.ForEach(e => e.ResetCreateTime()); //TODO: reset clients if this happens, maybe do it if a majority are behind rather than all of them? } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 01d687ea4..0eca5efc2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -175,6 +175,7 @@ namespace Barotrauma.Networking GameMain.NetworkMember?.KarmaManager?.SaveCustomPreset(); GameMain.NetworkMember?.KarmaManager?.Save(); } + SaveSettings(); GameMain.NetLobbyScreen.LastUpdateID++; } } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 8ed3c8daf..5d1251a78 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.9.703.0 + 0.9.704.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs index 167119088..c9036fbe4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs @@ -142,16 +142,8 @@ namespace Barotrauma } } - public AITarget(Entity e, XElement element) : this(e) + public void Reset() { - SightRange = element.GetAttributeFloat("sightrange", 0.0f); - SoundRange = element.GetAttributeFloat("soundrange", 0.0f); - MinSightRange = element.GetAttributeFloat("minsightrange", 0f); - MinSoundRange = element.GetAttributeFloat("minsoundrange", 0f); - MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange); - MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange); - FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime); - Static = element.GetAttributeBool("static", Static); if (Static) { SightRange = MaxSightRange; @@ -163,7 +155,18 @@ namespace Barotrauma SightRange = MinSightRange; SoundRange = MinSoundRange; } + } + public AITarget(Entity e, XElement element) : this(e) + { + SightRange = element.GetAttributeFloat("sightrange", 0.0f); + SoundRange = element.GetAttributeFloat("soundrange", 0.0f); + MinSightRange = element.GetAttributeFloat("minsightrange", 0f); + MinSoundRange = element.GetAttributeFloat("minsoundrange", 0f); + MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange); + MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange); + FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime); + Static = element.GetAttributeBool("static", Static); SonarDisruption = element.GetAttributeFloat("sonardisruption", 0.0f); SonarLabel = element.GetAttributeString("sonarlabel", ""); SonarIconIdentifier = element.GetAttributeString("sonaricon", ""); @@ -172,6 +175,7 @@ namespace Barotrauma { Type = t; } + Reset(); } public AITarget(Entity e) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 792fd6ae3..184ef4d2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -270,6 +270,17 @@ namespace Barotrauma } } + if (isStateChanged) + { + if (State == AIState.Idle) + { + stateResetTimer -= deltaTime; + if (stateResetTimer <= 0) + { + ResetOriginalState(); + } + } + } if (targetIgnoreTimer > 0) { targetIgnoreTimer -= deltaTime; @@ -662,7 +673,7 @@ namespace Barotrauma attackSimPos = Character.GetRelativeSimPosition(SelectedAiTarget.Entity); } - if (CanEnterSubmarine) + if (Character.AnimController.CanEnterSubmarine) { //targeting a wall section that can be passed through -> steer manually through the hole if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex)) @@ -674,21 +685,20 @@ namespace Barotrauma return; } } - // Don't think we need this - //else if (wallTarget == null && 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 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(); @@ -1024,7 +1034,7 @@ namespace Barotrauma var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor; if (door != null && !door.IsOpen) { - if (SelectedAiTarget != door.Item.AiTarget) + if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget) { SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority); return; @@ -1076,15 +1086,15 @@ namespace Barotrauma LatchOntoAI?.DeattachFromBody(); Character.AnimController.ReleaseStuckLimbs(); Hull targetHull = section.gap?.FlowTargetHull; - SelectedAiTarget = targetHull != null ? targetHull.AiTarget : wall.AiTarget; - float distance = Vector2.DistanceSquared(Character.WorldPosition, targetWorldPos); float maxDistance = Math.Min(wall.Rect.Width, wall.Rect.Height); - if (distance * distance > maxDistance * maxDistance) + if (Vector2.DistanceSquared(Character.WorldPosition, targetWorldPos) > maxDistance * maxDistance) { return false; } if (targetHull != null) { + // If already inside, target the hull, else target the wall. + SelectedAiTarget = Character.CurrentHull != null ? targetHull.AiTarget : wall.AiTarget; if (wall.IsHorizontal) { targetWorldPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2; @@ -1094,6 +1104,7 @@ namespace Barotrauma targetWorldPos.X = targetHull.WorldRect.Center.X; } steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition)); + SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15); return true; } return false; @@ -1102,9 +1113,16 @@ namespace Barotrauma private bool CanAttack(Entity target) { if (target == null) { return false; } - if (target is Character ch) + if (target is Character c) { - if (Character.CurrentHull == null && ch.CurrentHull != null || Character.CurrentHull != null && ch.CurrentHull == null) + if (Character.CurrentHull == null && c.CurrentHull != null || Character.CurrentHull != null && c.CurrentHull == null) + { + return false; + } + } + else if (target is Item i && i.GetComponent() == null) + { + if (Character.CurrentHull == null && i.CurrentHull != null || Character.CurrentHull != null && i.CurrentHull == null) { return false; } @@ -1175,7 +1193,7 @@ namespace Barotrauma { if (wall.SectionBodyDisabled(i)) { - if (CanEnterSubmarine && CanPassThroughHole(wall, i)) + if (Character.AnimController.CanEnterSubmarine && CanPassThroughHole(wall, i)) { sectionIndex = i; break; @@ -1204,12 +1222,12 @@ namespace Barotrauma 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); - if (CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall)) + if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall)) { wallTarget = new WallTarget(sectionPos, wall, sectionIndex); } } - if (!CanEnterSubmarine && wallTarget == null) + if (!Character.AnimController.CanEnterSubmarine && wallTarget == null) { if (closestBody.UserData is Structure w && w.Submarine != null || closestBody.UserData is Item i && i.Submarine != null) { @@ -1251,12 +1269,18 @@ namespace Barotrauma return; } - if (attackResult.Damage > 0.0f && Character.Params.AI.AttackWhenProvoked) + if (attackResult.Damage > 0.0f) { - if (attacker.Submarine == Character.Submarine && canAttackCharacters || - attacker.Submarine != null && canAttackSub) + if (Character.Params.AI.AttackWhenProvoked) { - ChangeTargetState(attacker, AIState.Attack, 100); + if (attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub) + { + ChangeTargetState(attacker, AIState.Attack, 100); + } + } + else if (!AIParams.HasTag(attacker.SpeciesName)) + { + ChangeTargetState(attacker, AIState.Flee, 100); } } @@ -1306,7 +1330,8 @@ namespace Barotrauma SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget).Priority); } } - if (SelectedAiTarget.Entity is IDamageable damageTarget) + IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable; + if (damageTarget != null) { if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb)) { @@ -1480,9 +1505,9 @@ namespace Barotrauma } else { + // Don't attack targets that are not in the same submarine continue; } - } if (targetCharacter.IsDead) { @@ -1578,7 +1603,7 @@ namespace Barotrauma continue; } valueModifier = 1; - if (!CanEnterSubmarine && IsWallDisabled(s)) + if (!Character.AnimController.CanEnterSubmarine && IsWallDisabled(s)) { continue; } @@ -1587,7 +1612,7 @@ namespace Barotrauma var section = s.Sections[i]; if (section.gap == null) { continue; } bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null; - if (CanEnterSubmarine) + if (Character.AnimController.CanEnterSubmarine) { if (CanPassThroughHole(s, i)) { @@ -1766,7 +1791,7 @@ namespace Barotrauma removals.ForEach(r => targetMemories.Remove(r)); } - private const float targetIgnoreTime = 5; + private readonly float targetIgnoreTime = 5; private float targetIgnoreTimer; private readonly HashSet ignoredTargets = new HashSet(); public void IgnoreTarget(AITarget target) @@ -1775,25 +1800,16 @@ namespace Barotrauma ignoredTargets.Add(target); targetIgnoreTimer = targetIgnoreTime * Rand.Range(0.75f, 1.25f); } - - protected override void OnTargetChanged(AITarget previousTarget, AITarget newTarget) - { - if (previousTarget == null || newTarget == null) { return; } - var previousCharacter = previousTarget.Entity as Character; - var newCharacter = newTarget.Entity as Character; - if (previousCharacter == null && newCharacter == null) - { - return; - } - if (previousCharacter != null && newCharacter != null && previousCharacter.SpeciesName == newCharacter.SpeciesName) - { - return; - } - modifiedParams.Keys.ForEachMod(tag => TryResetOriginalState(tag)); - } #endregion #region State switching + /// + /// How long do we hold on to the current state after losing a target before we reset back to the original state. + /// In other words, how long do we have to idle before the original state is restored. + /// + private readonly float stateResetCooldown = 10; + private float stateResetTimer; + private bool isStateChanged; /// /// Resets the target's state to the original value defined in the xml. @@ -1806,6 +1822,7 @@ namespace Barotrauma modifiedParams.Remove(tag); if (tempParams.ContainsKey(tag)) { + tempParams.Values.ForEach(t => AIParams.RemoveTarget(t)); tempParams.Remove(tag); } targetParams.Reset(); @@ -1829,23 +1846,39 @@ namespace Barotrauma /// private void ChangeTargetState(Character target, AIState state, float? priority = null) { + isStateChanged = true; + SetStateResetTimer(); ChangeParams(target.SpeciesName); - // If the target is shooting from the submarine, we might not perceive it because it doesn't move. - // --> Target the submarine too. - if (target.Submarine != null && state == AIState.Attack && canAttackSub) + // Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items + if (state == AIState.Attack || state == AIState.Flee) { - ChangeParams("room"); - ChangeParams("wall"); - ChangeParams("door"); + ChangeParams("weapon"); + ChangeParams("tool"); + } + if (state == AIState.Attack) + { + // If the target is shooting from the submarine, we might not perceive it because it doesn't move. + // --> Target the submarine too. + if (target.Submarine != null && canAttackSub) + { + ChangeParams("room"); + ChangeParams("wall"); + ChangeParams("door"); + } + ChangeParams("provocative", onlyExisting: true); + ChangeParams("light", onlyExisting: true); } - void ChangeParams(string tag) + void ChangeParams(string tag, bool onlyExisting = false) { if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams)) { - if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams)) + if (!onlyExisting && !tempParams.ContainsKey(tag)) { - tempParams.Add(tag, targetParams); + if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams)) + { + tempParams.Add(tag, targetParams); + } } } if (targetParams != null) @@ -1862,6 +1895,12 @@ namespace Barotrauma } } } + + private void ResetOriginalState() + { + isStateChanged = false; + modifiedParams.Keys.ForEachMod(tag => TryResetOriginalState(tag)); + } #endregion protected override void OnStateChanged(AIState from, AIState to) @@ -1873,8 +1912,14 @@ namespace Barotrauma escapeMargin = 0; allGapsSearched = false; unreachableGaps.Clear(); + if (isStateChanged && to == AIState.Idle && from != to) + { + SetStateResetTimer(); + } } + private void SetStateResetTimer() => stateResetTimer = stateResetCooldown * Rand.Range(0.75f, 1.25f); + private float GetPerceivingRange(AITarget target) => Math.Max(target.SightRange * Sight, target.SoundRange * Hearing); private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index b6e1d2025..0d685c7cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -711,6 +711,7 @@ namespace Barotrauma public void SetOrder(Order order, string option, Character orderGiver, bool speak = true) { + SetOrderProjSpecific(order, option); CurrentOrderOption = option; CurrentOrder = order; objectiveManager.SetOrder(order, option, orderGiver); @@ -749,10 +750,9 @@ namespace Barotrauma Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f); } } - SetOrderProjSpecific(order); } - partial void SetOrderProjSpecific(Order order); + partial void SetOrderProjSpecific(Order order, string option); public override void SelectTarget(AITarget target) { @@ -970,8 +970,8 @@ namespace Barotrauma { bool isValidTarget(Character e) => IsActive(e) && !IsFriendly(character, e); int enemyCount = visibleHulls == null ? - Character.CharacterList.Count(e => e.CurrentHull == hull && isValidTarget(e)) : - Character.CharacterList.Count(e => visibleHulls.Contains(e.CurrentHull) && isValidTarget(e)); + Character.CharacterList.Count(e => isValidTarget(e) && e.CurrentHull == hull) : + Character.CharacterList.Count(e => isValidTarget(e) && visibleHulls.Contains(e.CurrentHull)); // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages) enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); } @@ -988,7 +988,7 @@ namespace Barotrauma return sameSpecies && !differentTeam; } - public static bool IsActive(Character other) => !other.Removed && !other.IsDead && !other.IsUnconscious; + public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious; public static bool IsTrueForAllCrewMembers(Character character, Func predicate) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 99619b889..1676bffc4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -145,9 +145,14 @@ namespace Barotrauma }, onAbandon: () => { + // Don't ignore any hulls if outside, because apparently it happens that we can't find a path, in which case we just want to try again. + // If we ignore the hull, it might be the only airlock in the target sub, which ignores the whole sub. if (currentHull != null && goToObjective != null) { - HumanAIController.UnreachableHulls.Add(goToObjective.Target as Hull); + if (goToObjective.Target is Hull hull) + { + HumanAIController.UnreachableHulls.Add(hull); + } } RemoveSubObjective(ref goToObjective); }); @@ -172,7 +177,7 @@ namespace Barotrauma } foreach (Character enemy in Character.CharacterList) { - if (HumanAIController.IsFriendly(enemy) || !HumanAIController.IsActive(enemy)) { continue; } + if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy)) { continue; } if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull)) { Vector2 dir = character.Position - enemy.Position; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index d4fb8469e..f76dbdd71 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -265,6 +265,14 @@ namespace Barotrauma { get { + if (SteeringManager == PathSteering && PathSteering.CurrentPath?.CurrentNode?.Ladders != null) + { + //don't consider the character to be close enough to the target while climbing ladders, + //UNLESS the last node in the path has been reached + //otherwise characters can let go of the ladders too soon once they're close enough to the target + if (PathSteering.CurrentPath.NextNode != null) { return false; } + } + bool closeEnough = Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough; if (closeEnough) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index 14bb6e791..aa52360d2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -25,6 +25,7 @@ namespace Barotrauma public static Sprite ShortcutNode { get; private set; } public static Sprite ExpandNode { get; private set; } public static Sprite NodeContainer { get; private set; } + public static Sprite HotkeyContainer { get; private set; } public static Sprite CommandBackground { get; private set; } public static List PrefabList { get; private set; } public static Order GetPrefab(string identifier) @@ -163,6 +164,9 @@ namespace Barotrauma case "nodecontainer": NodeContainer = new Sprite(spriteElement, lazyLoad: true); break; + case "hotkeycontainer": + HotkeyContainer = new Sprite(spriteElement, lazyLoad: true); + break; case "commandbackground": CommandBackground = new Sprite(spriteElement, lazyLoad: true); break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 287e0704b..05f292559 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -312,22 +312,15 @@ namespace Barotrauma selectedCharacter.selectedBy = null; selectedCharacter = value; if (selectedCharacter != null) + { selectedCharacter.selectedBy = this; - #if CLIENT - if (GameMain.GameSession == null) return; - // Quick & dirty hiding of the chat whenever a character with an accessible inventory is selected to prevent overlaps - if (GameMain.GameSession.CrewManager.IsSinglePlayer) - { - if (GameMain.GameSession.CrewManager.ChatBox == null) return; - GameMain.GameSession.CrewManager.ChatBox.SetVisibility(!(IsHumanoid && value != null && value.Inventory != null && value.CanInventoryBeAccessed)); - } - else - { - if (GameMain.Client?.ChatBox == null) return; - GameMain.Client.ChatBox.SetVisibility(!(IsHumanoid && value != null && value.Inventory != null && value.CanInventoryBeAccessed)); - } + if (Inventory != null) + { + Inventory.ToggleInventory(true); + } #endif + } } } @@ -2315,14 +2308,21 @@ namespace Barotrauma aiTarget.SoundRange = MathHelper.Clamp(range, 0, 10000); } + public bool CanHearCharacter(Character speaker) + { + if (speaker == null || speaker.SpeechImpediment > 100.0f) { return false; } + ChatMessageType messageType = ChatMessage.CanUseRadio(speaker) && ChatMessage.CanUseRadio(this) ? + ChatMessageType.Radio : + ChatMessageType.Default; + return !string.IsNullOrEmpty(ChatMessage.ApplyDistanceEffect("message", messageType, speaker, this)); + } + public void SetOrder(Order order, string orderOption, Character orderGiver, bool speak = true) { if (orderGiver != null) { //set the character order only if the character is close enough to hear the message - ChatMessageType messageType = ChatMessage.CanUseRadio(orderGiver) && ChatMessage.CanUseRadio(this) ? - ChatMessageType.Radio : ChatMessageType.Default; - if (string.IsNullOrEmpty(ChatMessage.ApplyDistanceEffect("message", messageType, orderGiver, this))) return; + if (!CanHearCharacter(orderGiver)) { return; } } HumanAIController humanAI = AIController as HumanAIController; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 093de4d3b..cd23e345b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -13,6 +13,7 @@ namespace Barotrauma class LimbHealth { public Sprite IndicatorSprite; + public Sprite HighlightSprite; public Rectangle HighlightArea; @@ -44,6 +45,9 @@ namespace Barotrauma IndicatorSprite = new Sprite(subElement); HighlightArea = subElement.GetAttributeRect("highlightarea", new Rectangle(0, 0, (int)IndicatorSprite.size.X, (int)IndicatorSprite.size.Y)); break; + case "highlightsprite": + HighlightSprite = new Sprite(subElement); + break; case "vitalitymultiplier": if (subElement.Attribute("name") != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index f3c5d4b13..b6ffe5ed0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs @@ -61,7 +61,8 @@ namespace Barotrauma } public readonly Dictionary ItemSets = new Dictionary(); - public readonly Dictionary> ItemNames = new Dictionary>(); + public readonly Dictionary> ItemIdentifiers = new Dictionary>(); + public readonly Dictionary> ShowItemPreview = new Dictionary>(); public readonly List Skills = new List(); public readonly List AutomaticOrders = new List(); public readonly List AppropriateOrders = new List(); @@ -184,9 +185,10 @@ namespace Barotrauma { case "itemset": ItemSets.Add(variant, subElement); - var itemNames = new List(); - loadItemNames(subElement, itemNames); - ItemNames.Add(variant++, itemNames); + ItemIdentifiers[variant] = new List(); + ShowItemPreview[variant] = new Dictionary(); + loadItemIdentifiers(subElement, variant); + variant++; break; case "skills": foreach (XElement skillElement in subElement.Elements()) @@ -201,20 +203,19 @@ namespace Barotrauma case "appropriateorders": subElement.Elements().ForEach(order => AppropriateOrders.Add(order.GetAttributeString("identifier", "").ToLowerInvariant())); break; - case "icon": + case "jobicon": Icon = new Sprite(subElement.FirstElement()); break; } } - void loadItemNames(XElement parentElement, List itemNames) + void loadItemIdentifiers(XElement parentElement, int variant) { foreach (XElement itemElement in parentElement.GetChildElements("Item")) { if (itemElement.Element("name") != null) { DebugConsole.ThrowError("Error in job config \"" + Name + "\" - use identifiers instead of names to configure the items."); - itemNames.Add(itemElement.GetAttributeString("name", "")); continue; } @@ -222,22 +223,13 @@ namespace Barotrauma if (string.IsNullOrWhiteSpace(itemIdentifier)) { DebugConsole.ThrowError("Error in job config \"" + Name + "\" - item with no identifier."); - itemNames.Add(""); } else { - var prefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; - if (prefab == null) - { - DebugConsole.ThrowError("Error in job config \"" + Name + "\" - item prefab \"" + itemIdentifier + "\" not found."); - itemNames.Add(""); - } - else - { - itemNames.Add(prefab.Name); - } + ItemIdentifiers[variant].Add(itemIdentifier); + ShowItemPreview[variant][itemIdentifier] = itemElement.GetAttributeBool("showpreview", true); } - loadItemNames(itemElement, itemNames); + loadItemIdentifiers(itemElement, variant); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index 6753b8149..1feb334d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -536,7 +536,6 @@ namespace Barotrauma private readonly List contactBodies = new List(); - private List ignoredBodies; /// /// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated. /// @@ -556,20 +555,11 @@ namespace Barotrauma case HitDetection.Distance: if (dist < attack.DamageRange) { - if (ignoredBodies == null) - { - ignoredBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList(); - ignoredBodies.Add(character.AnimController.Collider.FarseerBody); - } - - structureBody = Submarine.PickBody( - SimPosition, attackSimPos, - ignoredBodies, Physics.CollisionWall); - - if (damageTarget is Item) + structureBody = Submarine.PickBody(SimPosition, attackSimPos, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true); + if (damageTarget is Item i && i.GetComponent() != null) { // If the attack is aimed to an item and hits an item, it's successful. - // Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example. + // Ignore blocking checks on doors, because it causes cases where a Mudraptor cannot hit the hatch, for example. wasHit = true; } else if (damageTarget is Structure wall && structureBody != null && diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 259144325..8f5194ccc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -464,7 +464,7 @@ namespace Barotrauma private bool TryAddTarget(XElement targetElement, out TargetParams target) { string tag = targetElement.GetAttributeString("tag", null); - if (!CheckTag(tag)) + if (!HasTag(tag)) { target = null; DebugConsole.ThrowError($"Multiple targets with the same tag ('{tag}') defined! Only the first will be used!"); @@ -487,15 +487,18 @@ namespace Barotrauma if (TryAddTarget(element, out targetParams)) { Element.Add(element); + return true; + } + else + { + return false; } - return targetParams != null; } - private bool CheckTag(string tag) + public bool HasTag(string tag) { if (tag == null) { return false; } - tag = tag.ToLowerInvariant(); - return targets.None(t => t.Tag == tag); + return targets.None(t => t.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase)); } public bool RemoveTarget(TargetParams target) => RemoveSubParam(target, targets); diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 90ff507be..adcb12ba4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -511,7 +511,7 @@ namespace Barotrauma try { int count = args.Length == 0 ? 10 : int.Parse(args[0]); - Entity.DumpIds(count); + Entity.DumpIds(count, args.Length >= 2 ? args[1] : null); } catch (Exception e) { @@ -1550,6 +1550,7 @@ namespace Barotrauma if (args != null && args.Length > argCount) { onAnswered(args[argCount]); + return; } #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index 4059a95c6..cb4973991 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -5,11 +5,11 @@ using System.Xml.Linq; namespace Barotrauma { - class CargoMission : Mission + partial class CargoMission : Mission { - private XElement itemConfig; + private readonly XElement itemConfig; - private List items; + private readonly List items = new List(); private int requiredDeliveryAmount; @@ -22,7 +22,7 @@ namespace Barotrauma private void InitItems() { - items = new List(); + items.Clear(); if (itemConfig == null) { @@ -35,7 +35,7 @@ namespace Barotrauma LoadItemAsChild(subElement, null); } - if (requiredDeliveryAmount == 0) requiredDeliveryAmount = items.Count; + if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } } private void LoadItemAsChild(XElement element, Item parent) @@ -90,8 +90,6 @@ namespace Barotrauma var item = new Item(itemPrefab, position, cargoRoom.Submarine); item.FindHull(); - - items.Add(item); if (parent != null) parent.Combine(item, user: null); @@ -108,7 +106,10 @@ namespace Barotrauma public override void Start(Level level) { - InitItems(); + if (!IsClient) + { + InitItems(); + } } public override void End() @@ -127,8 +128,9 @@ namespace Barotrauma foreach (Item item in items) { - if (!item.Removed) item.Remove(); + if (!item.Removed) { item.Remove(); } } + items.Clear(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 3699f4324..3386a00ec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -1,11 +1,12 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Barotrauma { - partial class Mission + abstract partial class Mission { public readonly MissionPrefab Prefab; protected bool completed; @@ -192,9 +193,7 @@ namespace Barotrauma public void GiveReward() { - var mode = GameMain.GameSession.GameMode as CampaignMode; - if (mode == null) return; - + if (!(GameMain.GameSession.GameMode is CampaignMode mode)) { return; } mode.Money += Reward; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs index 6588014fc..06a19a7c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs @@ -3,14 +3,18 @@ using System.Collections.Generic; using System.Linq; using System; using System.Xml.Linq; +using Barotrauma.Extensions; +using Barotrauma.Networking; namespace Barotrauma { - class MonsterMission : Mission + partial class MonsterMission : Mission { private readonly string monsterFile; private readonly int monsterCount; - private readonly HashSet> monsterFiles = new HashSet>(); + + //string = filename, point = min,max + private readonly HashSet> monsterFiles = new HashSet>(); private readonly List monsters = new List(); private readonly List sonarPositions = new List(); @@ -50,7 +54,7 @@ namespace Barotrauma maxSonarMarkerDistance = prefab.ConfigElement.GetAttributeFloat("maxsonarmarkerdistance", 10000.0f); - monsterCount = prefab.ConfigElement.GetAttributeInt("monstercount", 1); + monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255); string monsterFileName = monsterFile; foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster")) { @@ -64,9 +68,9 @@ namespace Barotrauma { defaultCount = monsterElement.GetAttributeInt("amount", 1); } - int min = monsterElement.GetAttributeInt("min", defaultCount); - int max = Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)); - monsterFiles.Add(new Tuple(monster, Rand.Range(min, max + 1, Rand.RandSync.Server))); + int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255); + int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255); + monsterFiles.Add(new Tuple(monster, new Point(min, max))); } description = description.Replace("[monster]", TextManager.Get("character." + System.IO.Path.GetFileNameWithoutExtension(monsterFileName))); @@ -74,45 +78,50 @@ namespace Barotrauma public override void Start(Level level) { - Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos); - - bool isClient = IsClient; - if (monsters.Count > 0) { throw new Exception($"monsters.Count > 0 ({monsters.Count})"); } - if (!string.IsNullOrEmpty(monsterFile)) - { - for (int i = 0; i < monsterCount; i++) - { - monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false)); - } - } - foreach (var monster in monsterFiles) - { - for (int i = 0; i < monster.Item2; i++) - { - monsters.Add(Character.Create(monster.Item1, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false)); - } - } - if (tempSonarPositions.Count > 0) { throw new Exception($"tempSonarPositions.Count > 0 ({tempSonarPositions.Count})"); } + if (!IsClient) + { + Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos); + if (!string.IsNullOrEmpty(monsterFile)) + { + for (int i = 0; i < monsterCount; i++) + { + monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false)); + } + } + foreach (var monster in monsterFiles) + { + int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1); + for (int i = 0; i < amount; i++) + { + monsters.Add(Character.Create(monster.Item1, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false)); + } + } + + InitializeMonsters(monsters); + } + } + + private void InitializeMonsters(IEnumerable monsters) + { monsters.ForEach(m => m.Enabled = false); SwarmBehavior.CreateSwarm(monsters.Cast()); - for (int i = 0; i < monsters.Count; i++) + foreach (Character monster in monsters) { - tempSonarPositions.Add(spawnPos + Rand.Vector(maxSonarMarkerDistance)); + tempSonarPositions.Add(monster.WorldPosition + Rand.Vector(maxSonarMarkerDistance)); } - - if (monsters.Count != tempSonarPositions.Count) + if (monsters.Count() != tempSonarPositions.Count) { - throw new Exception($"monsters.Count != tempSonarPositions.Count ({monsters.Count} != {tempSonarPositions.Count})"); + throw new Exception($"monsters.Count != tempSonarPositions.Count ({monsters.Count()} != {tempSonarPositions.Count})"); } } @@ -183,5 +192,6 @@ namespace Barotrauma } public bool IsEliminated(Character enemy) => enemy.Removed || enemy.IsDead || enemy.AIController is EnemyAIController ai && ai.State == AIState.Flee; + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 6908e57d0..e3691f34e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -6,13 +6,13 @@ using System.Linq; namespace Barotrauma { - class SalvageMission : Mission + partial class SalvageMission : Mission { private readonly ItemPrefab itemPrefab; private Item item; - private Level.PositionType spawnPositionType; + private readonly Level.PositionType spawnPositionType; public override IEnumerable SonarPositions { @@ -62,23 +62,26 @@ namespace Barotrauma public override void Start(Level level) { - //ruin items are allowed to spawn close to the sub - float minDistance = spawnPositionType == Level.PositionType.Ruin ? 0.0f : Level.Loaded.Size.X * 0.3f; - Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f); - - item = new Item(itemPrefab, position, null); - item.body.FarseerBody.BodyType = BodyType.Kinematic; - - if (item.HasTag("alien")) + if (!IsClient) { - //try to find an artifact holder and place the artifact inside it - foreach (Item it in Item.ItemList) - { - if (it.Submarine != null || !it.HasTag("artifactholder")) continue; + //ruin items are allowed to spawn close to the sub + float minDistance = spawnPositionType == Level.PositionType.Ruin ? 0.0f : Level.Loaded.Size.X * 0.3f; + Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f); + + item = new Item(itemPrefab, position, null); + item.body.FarseerBody.BodyType = BodyType.Kinematic; - var itemContainer = it.GetComponent(); - if (itemContainer == null) continue; - if (itemContainer.Combine(item, user: null)) break; // Placement successful + if (item.HasTag("alien")) + { + //try to find an artifact holder and place the artifact inside it + foreach (Item it in Item.ItemList) + { + if (it.Submarine != null || !it.HasTag("artifactholder")) continue; + + var itemContainer = it.GetComponent(); + if (itemContainer == null) { continue; } + if (itemContainer.Combine(item, user: null)) { break; } // Placement successful + } } } } @@ -108,7 +111,8 @@ namespace Barotrauma { if (item.CurrentHull?.Submarine == null || !item.CurrentHull.Submarine.AtEndPosition || item.Removed) { return; } - item.Remove(); + item?.Remove(); + item = null; GiveReward(); completed = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index e705bb006..ad7a1b428 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -49,14 +49,13 @@ namespace Barotrauma { PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.ItemPrefab == item); - if (purchasedItem != null && quantity == 1) + campaign.Money -= item.GetPrice(campaign.Map.CurrentLocation).BuyPrice * quantity; + if (purchasedItem != null) { - campaign.Money -= item.GetPrice(campaign.Map.CurrentLocation).BuyPrice; - purchasedItem.Quantity += 1; + purchasedItem.Quantity += quantity; } else { - campaign.Money -= item.GetPrice(campaign.Map.CurrentLocation).BuyPrice * quantity; purchasedItem = new PurchasedItem(item, quantity); purchasedItems.Add(purchasedItem); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 1eb7ccc2e..563793bda 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -159,6 +159,10 @@ namespace Barotrauma public void StartRound(Level level, bool reloadSub = true, bool loadSecondSub = false, bool mirrorLevel = false) { + //make sure no status effects have been carried on from the next round + //(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully) + StatusEffect.StopAll(); + #if CLIENT GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null; if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode; @@ -260,7 +264,18 @@ namespace Barotrauma if (GameMode.Mission != null) { Mission = GameMode.Mission; } if (GameMode != null) { GameMode.Start(); } - if (GameMode.Mission != null) { Mission.Start(Level.Loaded); } + if (GameMode.Mission != null) + { + int prevEntityCount = Entity.GetEntityList().Count; + Mission.Start(Level.Loaded); + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Entity.GetEntityList().Count != prevEntityCount) + { + DebugConsole.ThrowError( + "Entity count has changed after starting a mission as a client. " + + "The clients should not instantiate entities themselves when starting the mission," + + " but instead the server should inform the client of the spawned entities using Mission.ServerWriteInitial."); + } + } EventManager.StartRound(level); SteamAchievementManager.OnStartRound(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 869e90cb8..cc6ea8d41 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -16,6 +16,7 @@ namespace Barotrauma partial class CharacterInventory : Inventory { + private const int hotkeyCount = 5; private Character character; public InvSlotType[] SlotTypes @@ -52,16 +53,7 @@ namespace Barotrauma { DebugConsole.ThrowError("Error in the inventory config of \"" + character.SpeciesName + "\" - " + slotTypeNames[i] + " is not a valid inventory slot type."); } - SlotTypes[i] = parsedSlotType; - switch (SlotTypes[i]) - { - //case InvSlotType.Head: - //case InvSlotType.OuterClothes: - case InvSlotType.LeftHand: - case InvSlotType.RightHand: - hideEmptySlot[i] = true; - break; - } + SlotTypes[i] = parsedSlotType; } InitProjSpecific(element); @@ -130,25 +122,25 @@ namespace Barotrauma /// /// If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equipped into its respective limbslot /// - public bool TryPutItemWithAutoEquipCheck(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true) + public bool TryPutItemWithAutoEquipCheck(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true, bool preferNonHotkeys = false) { // Does not auto-equip the item if specified and no suitable any slot found (for example handcuffs are not auto-equipped) if (item.AllowedSlots.Contains(InvSlotType.Any)) { var wearable = item.GetComponent(); - if (wearable != null && !wearable.AutoEquipWhenFull && CheckIfAnySlotAvailable(item, false) == -1) + if (wearable != null && !wearable.AutoEquipWhenFull && CheckIfAnySlotAvailable(item, false, preferNonHotkeys) == -1) { return false; } } - return TryPutItem(item, user, allowedSlots, createNetworkEvent); + return TryPutItem(item, user, allowedSlots, createNetworkEvent, preferNonHotkeys); } /// /// If there is room, puts the item in the inventory and returns true, otherwise returns false /// - public override bool TryPutItem(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true) + public override bool TryPutItem(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true, bool preferNonHotkeys = false) { if (allowedSlots == null || !allowedSlots.Any()) return false; @@ -172,7 +164,7 @@ namespace Barotrauma //try to place the item in a LimbSlot.Any slot if that's allowed if (allowedSlots.Contains(InvSlotType.Any) && item.AllowedSlots.Contains(InvSlotType.Any)) { - int freeIndex = CheckIfAnySlotAvailable(item, inWrongSlot); + int freeIndex = CheckIfAnySlotAvailable(item, inWrongSlot, preferNonHotkeys); if (freeIndex > -1) { PutItem(item, freeIndex, user, true, createNetworkEvent); @@ -215,60 +207,30 @@ namespace Barotrauma #if CLIENT if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; } #endif - bool removeFromOtherSlots = item.ParentInventory != this; - if (placedInSlot == -1 && inWrongSlot) - { - if (!hideEmptySlot[i] || SlotTypes[currentSlot] != InvSlotType.Any) removeFromOtherSlots = true; - } - + bool removeFromOtherSlots = item.ParentInventory != this || (placedInSlot == -1 && inWrongSlot); PutItem(item, i, user, removeFromOtherSlots, createNetworkEvent); item.Equip(character); placedInSlot = i; } - } - - if (placedInSlot > -1) - { - if (item.AllowedSlots.Contains(InvSlotType.Any) && hideEmptySlot[placedInSlot]) - { - bool isInAnySlot = false; - for (int i = 0; i < capacity; i++) - { - if (SlotTypes[i] == InvSlotType.Any && Items[i]==item) - { - isInAnySlot = true; - break; - } - } - if (!isInAnySlot) - { - for (int i = 0; i < capacity; i++) - { - if (SlotTypes[i] == InvSlotType.Any && Items[i] == null) - { - Items[i] = item; - break; - } - } - } - } - return true; - } + } } return placedInSlot > -1; } - public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot) + public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot, bool preferNonHotkeys) { - for (int i = 0; i < capacity; i++) + for (int i = 0; i < capacity; i++) + { + if (SlotTypes[i] != InvSlotType.Any) continue; + if (Items[i] == item) { - if (SlotTypes[i] != InvSlotType.Any) continue; - if (Items[i] == item) - { - return i; - } + return i; } + } + + if (!preferNonHotkeys) + { for (int i = 0; i < capacity; i++) { if (SlotTypes[i] != InvSlotType.Any) continue; @@ -283,11 +245,45 @@ namespace Barotrauma return i; } + } + else + { + int hotkeysCounted = 0; + // First go through non-hotkeyed slots + for (int i = 0; i < capacity; i++) + { + if (SlotTypes[i] != InvSlotType.Any) continue; + hotkeysCounted++; + + if (hotkeysCounted <= hotkeyCount) continue; + + if (inWrongSlot) + { + if (Items[i] != item && Items[i] != null) continue; + } + else + { + if (Items[i] != null) continue; + } + +#if CLIENT + if (!inventoryOpen) + { + ToggleInventory(); + } +#endif + + return i; + } + + // Then redo with no preference + return CheckIfAnySlotAvailable(item, inWrongSlot, false); + } return -1; } - public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true) + public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool avoidHotkeys = false) { if (index < 0 || index >= Items.Length) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index 7e01ff6c9..f2a6bf1f6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -110,6 +110,12 @@ namespace Barotrauma.Items.Components hitTargets.Clear(); IsActive = true; + + if (item.AiTarget != null) + { + item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange; + item.AiTarget.SightRange = item.AiTarget.MaxSightRange; + } return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index 1c7ab61cf..bfd1cc524 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -82,7 +82,7 @@ namespace Barotrauma.Items.Components public virtual bool OnPicked(Character picker) { - if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots)) + if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots, true, true)) { if (!picker.HasSelectedItem(item) && item.body != null) item.body.Enabled = false; this.picker = picker; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs index 023d590cf..e8060e3ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs @@ -36,7 +36,6 @@ namespace Barotrauma.Items.Components public Propulsion(Item item, XElement element) : base(item,element) { - ResetSoundRange(); } public override bool Use(float deltaTime, Character character = null) @@ -106,19 +105,5 @@ namespace Barotrauma.Items.Components item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange; } } - - public override void Unequip(Character character) - { - base.Unequip(character); - ResetSoundRange(); - } - - private void ResetSoundRange() - { - if (item.AiTarget != null) - { - item.AiTarget.SoundRange = item.AiTarget.MinSoundRange; - } - } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index dee73bc0f..94f4d3e38 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -82,14 +82,21 @@ namespace Barotrauma.Items.Components degreeOfFailure *= degreeOfFailure; return MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure)); } - + public override bool Use(float deltaTime, Character character = null) { - if (character == null || character.Removed) return false; - if ((item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) || reloadTimer > 0.0f) return false; + if (character == null || character.Removed) { return false; } + if ((item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) || reloadTimer > 0.0f) { return false; } + IsActive = true; reloadTimer = reload; + if (item.AiTarget != null) + { + item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange; + item.AiTarget.SightRange = item.AiTarget.MaxSightRange; + } + List limbBodies = new List(); foreach (Limb l in character.AnimController.Limbs) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs index 63d16e4b6..19bd4533e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs @@ -95,8 +95,10 @@ namespace Barotrauma.Items.Components Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, 0.1f); if (Math.Abs(Force) > 1.0f) { + //arbitrary multiplier that was added to changes in submarine mass without having to readjust all engines + float forceMultiplier = 0.1f; float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage / MinVoltage, 1.0f); - Vector2 currForce = new Vector2((force / 10.0f) * maxForce * voltageFactor, 0.0f); + Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f); //less effective when in a bad condition currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition); @@ -107,12 +109,12 @@ namespace Barotrauma.Items.Components if (item.AiTarget != null) { var aiTarget = item.AiTarget; - aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, currForce.Length() / maxForce); + aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, Math.Min(currForce.Length() * forceMultiplier / maxForce, 1.0f)); } if (item.CurrentHull != null) { var aiTarget = item.CurrentHull.AiTarget; - float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, currForce.Length() / maxForce); + float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, Math.Min(currForce.Length() * forceMultiplier / maxForce, 1.0f)); aiTarget.SoundRange = Math.Max(noise, aiTarget.SoundRange); } #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs index 830f8e5a0..fa849b44d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs @@ -79,7 +79,14 @@ namespace Barotrauma.Items.Components public float Range { get { return range; } - set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); } + set + { + range = MathHelper.Clamp(value, 0.0f, 100000.0f); + if (item?.AiTarget != null && item.AiTarget.MaxSoundRange <= 0) + { + item.AiTarget.MaxSoundRange = range; + } + } } [Serialize(false, false, description: "Should the sonar display the walls of the submarine it is inside.")] diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs index 4342bb427..15b450a98 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs @@ -84,13 +84,10 @@ namespace Barotrauma.Items.Components { if (sender == null || sender.channel != channel) { return false; } - if (sender.TeamID != Character.TeamType.None && TeamID != Character.TeamType.None) + if (sender.TeamID != TeamID) { - if (sender.TeamID != TeamID) - { - return false; - } - } + return false; + } if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index efdb0d0e6..7ad737a90 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -264,7 +264,7 @@ namespace Barotrauma.Items.Components Character user = item.ParentInventory?.Owner as Character; removeNodeDelay = (user?.SelectedConstruction == null) ? removeNodeDelay - deltaTime : 0.5f; - Submarine sub = null; + Submarine sub = item.Submarine; if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; } if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index e55d25536..17353c018 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -14,7 +14,6 @@ namespace Barotrauma protected readonly int capacity; public Item[] Items; - protected bool[] hideEmptySlot; public bool Locked; @@ -32,21 +31,16 @@ namespace Barotrauma this.Owner = owner; Items = new Item[capacity]; - hideEmptySlot = new bool[capacity]; #if CLIENT this.slotsPerRow = slotsPerRow; - if (SlotSpriteSmall == null) + if (DraggableIndicator == null) { - //TODO: define these in xml - SlotSpriteSmall = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(0, 0, 128, 128), null, 0); - // Adjustment to match the old size of 75,71 - SlotSpriteSmall.size = new Vector2(SlotSpriteSmall.SourceRect.Width * 0.5859375f, SlotSpriteSmall.SourceRect.Height * 0.5546875f); + DraggableIndicator = GUI.Style.GetComponentStyle("GUIDragIndicator").Sprites[GUIComponent.ComponentState.None][0].Sprite; - slotHotkeySprite = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(128, 0, 128, 128), null, 0); - EquipIndicator = new Sprite("Content/UI/inventoryAtlas.png", new Rectangle(673, 182, 73, 27), new Vector2(0.5f, 0.5f), 0); - EquipIndicatorHighlight = new Sprite("Content/UI/inventoryAtlas.png", new Rectangle(679, 108, 67, 21), new Vector2(0.5f, 0.5f), 0); + EquipIndicator = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(137, 10, 112, 25), new Vector2(0.5f, 1f), 0); + EquipIndicator.size = new Vector2(EquipIndicator.SourceRect.Width * 0.682f, EquipIndicator.SourceRect.Height * 0.682f); } #endif } @@ -108,7 +102,7 @@ namespace Barotrauma /// /// If there is room, puts the item in the inventory and returns true, otherwise returns false /// - public virtual bool TryPutItem(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true) + public virtual bool TryPutItem(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true, bool avoidHotkeys = false) { int slot = FindAllowedSlot(item); if (slot < 0) return false; @@ -117,7 +111,7 @@ namespace Barotrauma return true; } - public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true) + public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool avoidHotkeys = false) { if (i < 0 || i >= Items.Length) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index 793062786..831d85aa3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -47,9 +47,9 @@ namespace Barotrauma return (item != null && Items[i] == null && container.CanBeContained(item)); } - public override bool TryPutItem(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true) + public override bool TryPutItem(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true, bool preferNonHotkeys = false) { - bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent); + bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent, preferNonHotkeys); if (wasPut) { @@ -68,9 +68,9 @@ namespace Barotrauma return wasPut; } - public override bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true) + public override bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool preferNonHotkeys = false) { - bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent); + bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, preferNonHotkeys); if (wasPut) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs index 7ecc8e61b..1ab304ba9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs @@ -1,6 +1,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; @@ -283,16 +284,23 @@ namespace Barotrauma Removed = true; } - public static void DumpIds(int count) + public static void DumpIds(int count, string filename) { List entities = dictionary.Values.OrderByDescending(e => e.id).ToList(); count = Math.Min(entities.Count, count); + List lines = new List(); for (int i = 0; i < count; i++) { + lines.Add(entities[i].id + ": " + entities[i].ToString()); DebugConsole.ThrowError(entities[i].id + ": " + entities[i].ToString()); } + + if (!string.IsNullOrWhiteSpace(filename)) + { + File.WriteAllLines(filename, lines); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs index 6a5cfc962..fa13b2d48 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs @@ -70,7 +70,7 @@ namespace Barotrauma return false; } - public void SaveToCache(string filename, long? time=null) + public void SaveToCache(string filename, long? time = null) { if (!string.IsNullOrWhiteSpace(filename)) { @@ -191,9 +191,26 @@ namespace Barotrauma } public static string GetShortHash(string fullHash) - { + { if (string.IsNullOrEmpty(fullHash)) { return ""; } return fullHash.Length < 7 ? fullHash : fullHash.Substring(0, 7); } + + public static bool RemoveFromCache(string filename) + { + if (!string.IsNullOrWhiteSpace(filename)) + { + filename = filename.CleanUpPath(); + lock (cache) + { + if (cache.ContainsKey(filename)) + { + cache.Remove(filename); + return true; + } + } + } + return false; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 09e5fda56..d30787186 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -173,6 +173,7 @@ namespace Barotrauma XDocument doc = OpenFile(filePath); StartHashDocTask(doc); hashTask.Wait(); + hashTask = null; } return hash; @@ -343,7 +344,10 @@ namespace Barotrauma public Submarine(string filePath, string hash = "", bool tryLoad = true) : base(null) { this.filePath = filePath; - LastModifiedTime = File.GetLastWriteTime(filePath); + if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) + { + LastModifiedTime = File.GetLastWriteTime(filePath); + } try { name = displayName = Path.GetFileNameWithoutExtension(filePath); @@ -1631,6 +1635,10 @@ namespace Barotrauma return false; } + hash = null; + hashTask = null; + Md5Hash.RemoveFromCache(filePath); + return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs index bf838f108..f6e856946 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Pipes; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -81,43 +82,67 @@ namespace Barotrauma.Networking readCancellationToken = null; readStream?.Dispose(); readStream = null; writeStream?.Dispose(); writeStream = null; + msgsToRead?.Clear(); msgsToWrite?.Clear(); } + + private static int ReadIncomingMsgs() + { + Task readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token); + TimeSpan ts = TimeSpan.FromMilliseconds(100); + for (int i = 0; i < 150; i++) + { + if (shutDown) + { + readCancellationToken?.Cancel(); + shutDown = true; + return -1; + } + + if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(ts) ?? true)) + { + break; + } + } + + if (readTask == null || !readTask.IsCompleted) + { + readCancellationToken?.Cancel(); + shutDown = true; + return -1; + } + + if (readTask.Status != TaskStatus.RanToCompletion) + { + shutDown = true; + return -1; + } + + return readTask.Result; + } + + private static void UpdateRead() { while (!shutDown) { - Task readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token); - TimeSpan ts = TimeSpan.FromMilliseconds(100); - for (int i=0;i<150;i++) - { - if (shutDown) - { - readCancellationToken?.Cancel(); - shutDown = true; - return; - } - - if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(ts) ?? true)) - { - break; - } - } - - if (readTask == null || !readTask.IsCompleted) - { - readCancellationToken?.Cancel(); - shutDown = true; - return; - } - - if (readTask.Status != TaskStatus.RanToCompletion) +#if SERVER + if (!((readStream as AnonymousPipeClientStream)?.IsConnected ?? false)) { shutDown = true; return; } +#else + if (!((readStream as AnonymousPipeServerStream)?.IsConnected ?? false)) + { + shutDown = true; + return; + } +#endif - int readLen = readTask.Result; + int readLen = ReadIncomingMsgs(); + + if (readLen < 0) { shutDown = true; return; } int procIndex = 0; @@ -125,8 +150,20 @@ namespace Barotrauma.Networking { if (readState == ReadState.WaitingForPacketStart) { - readIncTotal = tempBytes[procIndex] | (tempBytes[procIndex + 1] << 8); - procIndex += 2; + readIncTotal = tempBytes[procIndex]; + procIndex++; + + if (procIndex >= readLen) + { + readLen = ReadIncomingMsgs(); + + if (readLen < 0) { shutDown = true; return; } + + procIndex = 0; + } + + readIncTotal |= (tempBytes[procIndex] << 8); + procIndex++; if (readIncTotal <= 0) { continue; } @@ -166,6 +203,19 @@ namespace Barotrauma.Networking { while (!shutDown) { +#if SERVER + if (!((writeStream as AnonymousPipeClientStream)?.IsConnected ?? false)) + { + shutDown = true; + return; + } +#else + if (!((writeStream as AnonymousPipeServerStream)?.IsConnected ?? false)) + { + shutDown = true; + return; + } +#endif bool msgAvailable; byte[] msg; lock (msgsToWrite) { @@ -176,9 +226,11 @@ namespace Barotrauma.Networking byte[] lengthBytes = new byte[2]; lengthBytes[0] = (byte)(msg.Length & 0xFF); lengthBytes[1] = (byte)((msg.Length >> 8) & 0xFF); + + msg = lengthBytes.Concat(msg).ToArray(); + try { - writeStream?.Write(lengthBytes, 0, 2); writeStream?.Write(msg, 0, msg.Length); } catch (IOException e) @@ -199,11 +251,23 @@ namespace Barotrauma.Networking writeManualResetEvent.Reset(); if (!writeManualResetEvent.WaitOne(1000)) { - //heartbeat to keep the other end alive - byte[] lengthBytes = new byte[2]; - lengthBytes[0] = (byte)0; - lengthBytes[1] = (byte)0; - writeStream?.Write(lengthBytes, 0, 2); + if (shutDown) + { + return; + } + try + { + //heartbeat to keep the other end alive + byte[] lengthBytes = new byte[2]; + lengthBytes[0] = (byte)0; + lengthBytes[1] = (byte)0; + writeStream?.Write(lengthBytes, 0, 2); + } + catch (IOException e) + { + shutDown = true; + break; + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs index 9892bb379..32e4d483b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs @@ -19,7 +19,7 @@ namespace Barotrauma.Networking public OrderChatMessage(Order order, string orderOption, Entity targetEntity, Character targetCharacter, Character sender) : this(order, orderOption, - order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption), + order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption), targetEntity, targetCharacter, sender) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Sprite/SpriteSheet.cs b/Barotrauma/BarotraumaShared/SharedSource/Sprite/SpriteSheet.cs index 26d0e204e..afeaaf19d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Sprite/SpriteSheet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Sprite/SpriteSheet.cs @@ -30,10 +30,11 @@ namespace Barotrauma Init(columnCount, rowCount); } - public SpriteSheet(string filePath, int columnCount, int rowCount, Vector2 origin) + public SpriteSheet(string filePath, int columnCount, int rowCount, Vector2 origin, Rectangle? sourceRect = null) : base(filePath, origin) { this.origin = origin; + if (sourceRect.HasValue) { SourceRect = sourceRect.Value; } Init(columnCount, rowCount); } diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 4ce13e394..c653ca6cc 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 4983a2c43..4769c75fe 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub index 1221d0161..f18887fe6 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 11dff8d22..120ee3d62 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,47 @@ +--------------------------------------------------------------------------------------------------------- +v0.9.704.0 (Unstable) +--------------------------------------------------------------------------------------------------------- + +- Numerous UI fixes again. +- Inventory remake. +- Fixed yet another bug that caused inventory items to get mixed up in the campaign. +- Fixed a bug that caused clients to get disconnected with an "invalid object header" error when a character has a large amount of different afflictions. +- Fixed monster missions occasionally causing "entity not found" errors in the multiplayer campaign. +- Rebalanced medical items: stat-boosting effects last much longer. +- Modified vigor buffs from steroids and hyperzine to be intantaneous instead of delayed. +- Nerfed opiate spawning. +- Updated localizations (new texts are now translated to all languages). +- Reduced the number of monsters inside ruins in high-difficulty levels, improving performance in those levels. +- New loading screen. +- Health interface improvements. +- Improvements to the command interface. +- Fixed clients failing to select a submarine file downloaded from the server if it replaced an older version of the sub. +- Fixed performance issues when two items with editable properties are in the inventory (for example a headset and a diving suit). +- Fixed header texts not being visible in Russian. +- Skill gain values are now moddable (see Content/SkillSettings.xml). +- Balanced skill levels and skill gains. +- Job variant descriptions and items are now shown when hovering the cursor over the job variant button. +- Fix bots occasionally getting stuck on water waypoints. +- Fixed bots occasionally letting go of ladders too soon when going to operate an item, causing them to fall down. +- The filter in the sub editor only searches from the currently selected category. +- Made Unstable watermark less intrusive in sub editor. +- Fixed chatbox disappearing when any character is accessing another character's inventory. +- Chatbox is hidden after the message has been sent if it was hidden when starting to enter the message. +- Fixed wifi components in the respawn shuttle being able to communicate with the main sub in non-combat missions. +- Fixed engine sound range being up to 20 times larger than it should be. +- Prevent dragging items while dragging inventories. +- Fixed monsters occasionally being able to attack characters inside the sub. +- Screwdrivers can be used as shivs! +- Fixed last wire node appearing to be outside the submarine if a character gets electrocuted while connecting the first end of the wire. +- Fixes to bugs that occasionally caused the client hosting a server to get silently disconnected from the server, getting stuck in a "limbo" where they can't interact with anything. +- Fix crash with command UI when there is no reactor on the sub. +- Fixed all items being highlighted in the multiplayer campaign store menu when another player buys something. +- Fixed setting the number of items to buy by typing a number in the text box being practically impossible in the multiplayer campaign store. +- Fixed grenades exploding multiple times if you throw one, pick it up before it explodes and rethrow it. +- Fixed equipped wire being shown twice in the wiring interface if you connect and disconnect it when the other end of the wire is not connected to anything. +- Fixed antibiotic glue being marked as a suitable treatment for gunshot wounds and internal damage, even though it only treats burns and bleeding. +- Fixed a waypoint issue in Orca's engineering compartment that prevented the bots from reaching the oxygen generator. + --------------------------------------------------------------------------------------------------------- v0.9.703.0 (Unstable) --------------------------------------------------------------------------------------------------------- @@ -11,7 +55,7 @@ v0.9.703.0 (Unstable) - Fixed engines causing crashes if MinVoltage is set to 0. - Fixed multiplayer campaign saves appearing in the single player "load game" menu if they're placed in the singleplayer save folder (leading to a crash if a player starts to load the save). - Fixed fabricator cancellation failing to be communicated under certain circumstances. -- Fixed fabricator an deconstructor operating faster when run on overvoltage, making it possible to fabricate/deconstruct things almost instantaneously by using relays. +- Fixed fabricator and deconstructor operating faster when run on overvoltage, making it possible to fabricate/deconstruct things almost instantaneously by using relays. - Fixed campaign store filter doing nothing. - Potentially a fixed race condition when autoupdating Workshop items during loading screen. - Fixed subinventories not opening when trying to heal an unconscious character. @@ -59,7 +103,7 @@ v0.9.701.0 (Unstable) --------------------------------------------------------------------------------------------------------- - Fixed SteamP2P server hosting. -- Fixed a bug that caused "missing entity" errors when joining mid-round. Occurred if entities were created or removed while the client was in the process of getting in sync with the server, for example if the other players were firing turrets while the client was joining. However, there may still other bugs left that can cause the same "missing entity" error message. +- Fixed a bug that caused "missing entity" errors when joining mid-round. Occurred if entities were created or removed while the client was in the process of getting in sync with the server, for example if the other players were firing turrets while the client was joining. However, there may still be other bugs left that can cause the same "missing entity" error message. - Cap the framerate to 200 FPS when VSync is off to prevent overloading the GPU. The cap can be adjusted by changing the "framelimit" attribute in config_player.xml. - Rebalanced random events. - Numerous fixes and improvements to Berilia. @@ -136,7 +180,7 @@ Additions and changes: - The job gear variants are not just visually different versions of the same item, but completely separate items. The job variants now allow the players to choose what kind of gear they want to spawn with, not just the look of the uniform. - Attachable items and wire nodes can now be freely placed around the character instead of always being placed at the position of the character. When attaching items/wires, there's a placement grid that makes it much easier to neatly attach/wire things mid-round. - The submarines now get automatically outfitted with a semi-random selection of supplies when starting a campaign. The items that have been manually placed in the submarine editor are kept as-is. -- Splitted internal damage into multiple subtypes: blunt force trauma, lacerations and bite wounds. The new afflictions are functionally identical to the default internal damage affliction, but can be used to identify the source of the injuries. +- Split internal damage into multiple subtypes: blunt force trauma, lacerations and bite wounds. The new afflictions are functionally identical to the default internal damage affliction, but can be used to identify the source of the injuries. - Humans are more resistant to gunshot wounds, lacerations and blunt force trauma than monsters. The intention is to allow making weapons more effective towards monsters without making killing your crewmates with them too easy. - Added a "terminal" item that can be used to send and display textual signals. Could be used for things such as terminals that send commands to devices or display some data received from devices. - Added muzzle flashes to small firearms. @@ -221,7 +265,7 @@ Monsters: - Fixed Tigerthresher and other creatures that can't attack the submarine bumping on the doors. - Fixed characters occasionally getting stuck while trying to reach the last known position of the previous target. - Fixed a crash when the creature is set to attack when provoked and when the damage source is null. -- Fixed extra creature being spawned when using the elements in the monster misson definitions. +- Fixed extra creature being spawned when using the elements in the monster mission definitions. - Fixed Hammerhead Matriarch's skirt going throught the walls. - Fixed performance issues when creatures are trying to find a path out from the submarine while escaping. Also improved the escaping behavior in general. - Fixed Mudraptors sometimes squeezing themselves towards doors without being able to attack them. @@ -231,7 +275,7 @@ Monsters: Multiplayer: - Fixed players not getting notified in any way when their connection to the server has timed out, allowing them to keep playing without being able to interact with anything. - Fixed clients only being informed of the reason for their ban the moment they're banned, but not if they try to rejoin. -- Fixed clients not attempting to reconnect to the server automatically when if the connection is lost, forcing the client to rejoin the server manually. +- Fixed clients not attempting to reconnect to the server automatically when the connection is lost, forcing the client to rejoin the server manually. - Karma system can be enabled/disabled in the "host server" menu. - Option to set the number of password retries before a ban. - Fixed voice chat indicators not working in the in-game crew list. diff --git a/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj b/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj index 452e484b8..69fbafa2f 100644 --- a/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj +++ b/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj @@ -11,6 +11,12 @@ AnyCPU;x64 + + TRACE + portable + true + +