diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs index ad368d0aa..e994f5954 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs @@ -39,10 +39,7 @@ namespace Barotrauma partial void DamageParticles(float deltaTime, Vector2 worldPosition) { - if (particleEmitter != null) - { - particleEmitter.Emit(deltaTime, worldPosition); - } + particleEmitter?.Emit(deltaTime, worldPosition); if (sound != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 09a5ea3b3..a84241519 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -201,7 +201,7 @@ namespace Barotrauma /// public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true) { - if (DisableControls || GUI.PauseMenuOpen || GUI.SettingsMenuOpen) + if (DisableControls || GUI.InputBlockingMenuOpen) { foreach (Key key in keys) { @@ -323,7 +323,7 @@ namespace Barotrauma DoInteractionUpdate(deltaTime, mouseSimPos); } - if (!GUI.PauseMenuOpen && !GUI.SettingsMenuOpen) + if (!GUI.InputBlockingMenuOpen) { if (SelectedConstruction != null && (SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) || @@ -515,6 +515,7 @@ namespace Barotrauma if (draggingItemToWorld) { if (item.OwnInventory == null || + !item.OwnInventory.Container.AllowDragAndDrop || !item.OwnInventory.CanBePut(CharacterInventory.DraggingItems.First()) || !CanAccessInventory(item.OwnInventory)) { @@ -679,7 +680,7 @@ namespace Barotrauma else { //Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm - hudInfoVisible = controlled.CanSeeCharacter(this, controlled.ViewTarget == null ? controlled.WorldPosition : controlled.ViewTarget.WorldPosition); + hudInfoVisible = controlled.CanSeeTarget(this, controlled.ViewTarget); } hudInfoTimer = Rand.Range(0.5f, 1.0f); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 70a61e811..8d590a191 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -302,9 +302,9 @@ namespace Barotrauma GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); //read anyway to prevent messing up reading the rest of the message - UInt16 lastEventID = msg.ReadUInt16(); - byte itemCount = msg.ReadByte(); - for (int i = 0; i < itemCount; i++) + _ = msg.ReadUInt16(); + byte inventoryItemCount = msg.ReadByte(); + for (int i = 0; i < inventoryItemCount; i++) { msg.ReadUInt16(); } @@ -411,8 +411,11 @@ namespace Barotrauma string option = null; if (orderPrefab.HasOptions) { - int optionIndex = msg.ReadRangedInteger(0, orderPrefab.Options.Length); - option = orderPrefab.Options[optionIndex]; + int optionIndex = msg.ReadRangedInteger(-1, orderPrefab.AllOptions.Length); + if (optionIndex > -1) + { + option = orderPrefab.Options[optionIndex]; + } } GameMain.GameSession?.CrewManager?.SetOrderHighlight(this, orderPrefab.Identifier, option); } @@ -431,9 +434,18 @@ namespace Barotrauma break; case 9: //NetEntityEvent.Type.AddToCrew GameMain.GameSession.CrewManager.AddCharacter(this); - foreach (Item item in Inventory.AllItems) + CharacterTeamType teamID = (CharacterTeamType)msg.ReadByte(); + ushort itemCount = msg.ReadUInt16(); + for (int i = 0; i < itemCount; i++) { + ushort itemID = msg.ReadUInt16(); + if (!(Entity.FindEntityByID(itemID) is Item item)) { continue; } item.AllowStealing = true; + var wifiComponent = item.GetComponent(); + if (wifiComponent != null) + { + wifiComponent.TeamID = teamID; + } } break; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index f8bfcc7f0..21a86f985 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -676,8 +676,8 @@ namespace Barotrauma bool inWater = Character.AnimController.InWater; var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air; var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab.DrawTarget == drawTarget || e.Prefab.ParticlePrefab.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both); - float particleMinScale = emitter != null ? emitter.Prefab.ScaleMin : 0.5f; - float particleMaxScale = emitter != null ? emitter.Prefab.ScaleMax : 1; + float particleMinScale = emitter?.Prefab.Properties.ScaleMin ?? 0.5f; + float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1; float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1); float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity); if (!inWater) @@ -1326,7 +1326,7 @@ namespace Barotrauma child.Recalculate(); } - if (buttonToSelect != null) { buttonToSelect.OnClicked(buttonToSelect, "selectaffliction"); } + buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction"); afflictionIconContainer.RecalculateChildren(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 47a0ddbad..fbe2dc370 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -1395,9 +1395,9 @@ namespace Barotrauma // omega nesting incoming if (fabricationRecipe != null) { - foreach (Tuple itemLocationPrice in itemPrefab.GetSellPricesOver(0)) + foreach (KeyValuePair itemLocationPrice in itemPrefab.GetSellPricesOver(0)) { - NewMessage(" If bought at " + itemLocationPrice.Item1 + " it costs " + itemLocationPrice.Item2.Price); + NewMessage(" If bought at " + itemLocationPrice.Key + " it costs " + itemLocationPrice.Value.Price); int totalPrice = 0; int? totalBestPrice = 0; foreach (var ingredient in fabricationRecipe.RequiredItems) @@ -1408,15 +1408,15 @@ namespace Barotrauma totalPrice += ingredientItemPrefab.DefaultPrice.Price; totalBestPrice += ingredientItemPrefab.GetMinPrice(); int basePrice = ingredientItemPrefab.DefaultPrice.Price; - foreach (Tuple ingredientItemLocationPrice in ingredientItemPrefab.GetBuyPricesUnder()) + foreach (KeyValuePair ingredientItemLocationPrice in ingredientItemPrefab.GetBuyPricesUnder()) { - if (basePrice > ingredientItemLocationPrice.Item2.Price) + if (basePrice > ingredientItemLocationPrice.Value.Price) { - NewMessage(" Location " + ingredientItemLocationPrice.Item1 + " sells ingredient " + ingredientItemPrefab.Name + " for cheaper, " + ingredientItemLocationPrice.Item2.Price, Color.Yellow); + NewMessage(" Location " + ingredientItemLocationPrice.Key + " sells ingredient " + ingredientItemPrefab.Name + " for cheaper, " + ingredientItemLocationPrice.Value.Price, Color.Yellow); } else { - NewMessage(" Location " + ingredientItemLocationPrice.Item1 + " sells ingredient " + ingredientItemPrefab.Name + " for more, " + ingredientItemLocationPrice.Item2.Price, Color.Teal); + NewMessage(" Location " + ingredientItemLocationPrice.Key + " sells ingredient " + ingredientItemPrefab.Name + " for more, " + ingredientItemLocationPrice.Value.Price, Color.Teal); } } } @@ -1426,11 +1426,15 @@ namespace Barotrauma if (totalBestPrice.HasValue) { - int? bestDifference = itemLocationPrice.Item2.Price - totalBestPrice; + int? bestDifference = itemLocationPrice.Value.Price - totalBestPrice; NewMessage(" Constructing the item from store-bought items provides " + bestDifference + " profit with best-case scenario values."); } } } + }, + () => + { + return new string[][] { ItemPrefab.Prefabs.SelectMany(p => p.Aliases).Concat(ItemPrefab.Prefabs.Select(p => p.Identifier)).ToArray() }; }, isCheat: false)); commands.Add(new Command("checkcraftingexploits", "checkcraftingexploits: Finds outright item exploits created by buying store-bought ingredients and constructing them into sellable items.", (string[] args) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs index fe31bb320..781d2c3ba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs @@ -55,6 +55,12 @@ namespace Barotrauma { Debug.Assert(actionInstance == null || actionId == null); + if (GUI.InputBlockingMenuOpen) + { + if (actionId.HasValue) { SendIgnore(actionId.Value); } + return; + } + shouldFadeToBlack = fadeToBlack; if (lastMessageBox != null && !lastMessageBox.Closed && GUIMessageBox.MessageBoxes.Contains(lastMessageBox)) @@ -368,6 +374,15 @@ namespace Barotrauma GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable); } + private static void SendIgnore(UInt16 actionId) + { + IWriteMessage outmsg = new WriteOnlyMessage(); + outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE); + outmsg.Write(actionId); + outmsg.Write(byte.MaxValue); + GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable); + } + // Too broken, left it here if I ever want to come back to it private static List GetQuoteHighlights(string text, Color color) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs index d4d0afdd6..55191b4b4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs @@ -22,6 +22,13 @@ namespace Barotrauma public override void ClientReadInitial(IReadMessage msg) { + ushort targetItemCount = msg.ReadUInt16(); + for (int i = 0; i < targetItemCount; i++) + { + var item = Item.ReadSpawnData(msg); + items.Add(item); + } + byte characterCount = msg.ReadByte(); for (int i = 0; i < characterCount; i++) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs index 4134996da..5fbdc15a5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs @@ -10,7 +10,12 @@ namespace Barotrauma for (int i = 0; i < characterCount; i++) { - characters.Add(Character.ReadSpawnData(msg)); + Character character = Character.ReadSpawnData(msg); + characters.Add(character); + if (msg.ReadBoolean()) + { + terroristCharacters.Add(character); + } ushort itemCount = msg.ReadUInt16(); for (int j = 0; j < itemCount; j++) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/OutpostDestroyMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/OutpostDestroyMission.cs deleted file mode 100644 index e12fc3691..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/OutpostDestroyMission.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Barotrauma.Networking; - -namespace Barotrauma -{ - partial class OutpostDestroyMission : AbandonedOutpostMission - { - public override void ClientReadInitial(IReadMessage msg) - { - base.ClientReadInitial(msg); - ushort itemCount = msg.ReadUInt16(); - for (int i = 0; i < itemCount; i++) - { - var item = Item.ReadSpawnData(msg); - items.Add(item); - } - } - } -} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index e7d1b227d..1285c03b8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -208,6 +208,19 @@ namespace Barotrauma get { return pauseMenuOpen; } } + public static bool InputBlockingMenuOpen + { + get + { + return PauseMenuOpen || + SettingsMenuOpen || + DebugConsole.IsOpen || + GameSession.IsTabMenuOpen || + (GameMain.GameSession?.GameMode?.Paused ?? false) || + CharacterHUD.IsCampaignInterfaceOpen; + } + } + public static bool PreventPauseMenuToggle = false; public static Color ScreenOverlayColor @@ -2144,7 +2157,7 @@ namespace Barotrauma for (int j = i + 1; j < elements.Count; j++) { Rectangle rect2 = elements[j].Rect; - if (!rect1.Intersects(rect2)) continue; + if (!rect1.Intersects(rect2)) { continue; } intersections = true; Point centerDiff = rect1.Center - rect2.Center; @@ -2173,10 +2186,10 @@ namespace Barotrauma elements[j].RectTransform.ScreenSpaceOffset += moveAmount2.ToPoint(); } - if (disallowedAreas == null) continue; + if (disallowedAreas == null) { continue; } foreach (Rectangle rect2 in disallowedAreas) { - if (!rect1.Intersects(rect2)) continue; + if (!rect1.Intersects(rect2)) { continue; } intersections = true; Point centerDiff = rect1.Center - rect2.Center; @@ -2194,7 +2207,7 @@ namespace Barotrauma iterations++; } - Vector2 ClampMoveAmount(Rectangle Rect, Rectangle clampTo, Vector2 moveAmount) + static Vector2 ClampMoveAmount(Rectangle Rect, Rectangle clampTo, Vector2 moveAmount) { if (Rect.Y < clampTo.Y) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs index 337e98bc8..94a503f1e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs @@ -201,7 +201,7 @@ namespace Barotrauma { base.ApplyStyle(style); - if (frame != null) { frame.ApplyStyle(style); } + frame?.ApplyStyle(style); } public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs index 0d7d27fd5..232c13c6a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs @@ -551,7 +551,7 @@ namespace Barotrauma } if (openState >= 2.0f) { - if (Parent != null) { Parent.RemoveChild(this); } + Parent?.RemoveChild(this); if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); } } } @@ -604,7 +604,7 @@ namespace Barotrauma } else { - if (Parent != null) { Parent.RemoveChild(this); } + Parent?.RemoveChild(this); if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs index 952cded4d..5d89dcaa0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs @@ -658,10 +658,7 @@ namespace Barotrauma currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData); } - if (Strikethrough != null) - { - Strikethrough.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X, ForceUpperCase ? pos.Y : pos.Y + GUI.Scale * 2f); - } + Strikethrough?.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X, ForceUpperCase ? pos.Y : pos.Y + GUI.Scale * 2f); } if (overflowClipActive) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs index 838f4c534..d4c1d980d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs @@ -626,7 +626,6 @@ namespace Barotrauma if (GameMain.Client == null) { SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee); - GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(newSub); RefreshSubmarineDisplay(true); } else @@ -661,7 +660,6 @@ namespace Barotrauma { GameMain.GameSession.PurchaseSubmarine(selectedSubmarine); SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0); - GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(newSub); RefreshSubmarineDisplay(true); } else diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index fac0477eb..a5239b1b6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -228,7 +228,10 @@ namespace Barotrauma var crewButton = createTabButton(InfoFrameTab.Crew, "crew"); - var missionButton = createTabButton(InfoFrameTab.Mission, "mission"); + if (!(GameMain.GameSession?.GameMode is TestGameMode)) + { + createTabButton(InfoFrameTab.Mission, "mission"); + } if (GameMain.GameSession?.GameMode is CampaignMode campaignMode) { @@ -903,44 +906,61 @@ namespace Barotrauma infoFrame.ClearChildren(); GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); int padding = (int)(0.0245f * missionFrame.Rect.Height); - Location location = GameMain.GameSession.EndLocation != null ? GameMain.GameSession.EndLocation : GameMain.GameSession.StartLocation; + GUIFrame missionFrameContent = new GUIFrame(new RectTransform(new Point(missionFrame.Rect.Width - padding * 2, missionFrame.Rect.Height - padding * 2), infoFrame.RectTransform, Anchor.Center), style: null); + Location location = GameMain.GameSession.EndLocation ?? GameMain.GameSession.StartLocation; + + GUILayoutGroup locationInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), missionFrameContent.RectTransform)) + { + AbsoluteSpacing = GUI.IntScale(10) + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Name, font: GUI.LargeFont); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUI.SubHeadingFont); + + var biomeLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform), + TextManager.Get("Biome", fallBackTag: "location"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), biomeLabel.RectTransform), Level.Loaded.LevelData.Biome.DisplayName, textAlignment: Alignment.CenterRight); + var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform), + TextManager.Get("LevelDifficulty"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), ((int)Level.Loaded.LevelData.Difficulty) + " %", textAlignment: Alignment.CenterRight); + + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), missionFrameContent.RectTransform) { AbsoluteOffset = new Point(0, locationInfoContainer.Rect.Height + padding) }, style: "HorizontalLine") + { + CanBeFocused = false + }; + + int locationInfoYOffset = locationInfoContainer.Rect.Height + padding * 2; + Sprite portrait = location.Type.GetPortrait(location.PortraitId); bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0; - int contentWidth = hasPortrait ? (int)(missionFrame.Rect.Width * 0.951f) : missionFrame.Rect.Width - padding * 2; - - Vector2 locationNameSize = GUI.LargeFont.MeasureString(location.Name); - Vector2 locationTypeSize = GUI.SubHeadingFont.MeasureString(location.Name); - GUITextBlock locationNameText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationNameSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, location.Name, font: GUI.LargeFont); - GUITextBlock locationTypeText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationTypeSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationNameText.Rect.Height + padding) }, location.Type.Name, font: GUI.SubHeadingFont); - - int locationInfoYOffset = locationNameText.Rect.Height + locationTypeText.Rect.Height + padding * 2; - - GUIListBox missionList; + int contentWidth = missionFrameContent.Rect.Width; if (hasPortrait) { - GUIFrame portraitHolder = new GUIFrame(new RectTransform(new Point(contentWidth, (int)(missionFrame.Rect.Height * 0.588f)), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) }); float portraitAspectRatio = portrait.SourceRect.Width / portrait.SourceRect.Height; - GUIImage portraitImage = new GUIImage(new RectTransform(new Vector2(1.0f, 1f), portraitHolder.RectTransform), portrait, scaleToFit: true); - portraitHolder.RectTransform.NonScaledSize = new Point(portraitImage.Rect.Size.X, (int)(portraitImage.Rect.Size.X / portraitAspectRatio)); + GUIImage portraitImage = new GUIImage(new RectTransform(new Vector2(0.5f, 1f), locationInfoContainer.RectTransform, Anchor.CenterRight), portrait, scaleToFit: true) + { + IgnoreLayoutGroups = true + }; + locationInfoContainer.Recalculate(); + portraitImage.RectTransform.NonScaledSize = new Point(Math.Min((int)(portraitImage.Rect.Size.Y * portraitAspectRatio), portraitImage.Rect.Width), portraitImage.Rect.Size.Y); + } - missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrame.Rect.Bottom - portraitHolder.Rect.Bottom - padding), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, portraitHolder.RectTransform.AbsoluteOffset.Y + portraitHolder.Rect.Height + padding) }); - } - else - { - missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrame.Rect.Height - locationInfoYOffset - padding), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) }); - } + GUIListBox missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrameContent.Rect.Height - locationInfoYOffset), missionFrameContent.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) }); missionList.ContentBackground.Color = Color.Transparent; missionList.Spacing = GUI.IntScale(15); if (GameMain.GameSession?.Missions != null) { + int spacing = GUI.IntScale(5); + int iconSize = (int)(GUI.LargeFont.MeasureChar('T').Y + GUI.Font.MeasureChar('T').Y * 4 + spacing * 4); + foreach (Mission mission in GameMain.GameSession.Missions) { GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null); - GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.225f, 0f) }, false, childAnchor: Anchor.TopLeft) + GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(iconSize + spacing, 0) }, false, childAnchor: Anchor.TopLeft) { - AbsoluteSpacing = GUI.IntScale(5) + AbsoluteSpacing = spacing }; string descriptionText = mission.Description; foreach (string missionMessage in mission.ShownMessages) @@ -974,12 +994,12 @@ namespace Barotrauma if (mission.Prefab.Icon != null) { - float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height; + /*float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height; int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X); int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); - Point iconSize = new Point(iconWidth, iconHeight); + Point iconSize = new Point(iconWidth, iconHeight);*/ - new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) + new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor, HoverColor = mission.Prefab.IconColor, diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index 73ce812e5..5f4ccab30 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -43,7 +43,7 @@ namespace Barotrauma private readonly CampaignUI campaignUI; private CampaignMode? Campaign => campaignUI.Campaign; private int AvailableMoney => Campaign?.Money ?? 0; - private UpgradeTab selectedUpgradTab = UpgradeTab.Upgrade; + private UpgradeTab selectedUpgradeTab = UpgradeTab.Upgrade; private GUIMessageBox? currectConfirmation; @@ -59,7 +59,7 @@ namespace Barotrauma private GUIFrame? subPreviewFrame; private Submarine? drawnSubmarine; private readonly List applicableCategories = new List(); - private Vector2[][] subHullVerticies = new Vector2[0][]; + private Vector2[][] subHullVertices = new Vector2[0][]; private List submarineWalls = new List(); public MapEntity? HoveredItem; @@ -110,7 +110,7 @@ namespace Barotrauma public void RefreshAll() { - switch (selectedUpgradTab) + switch (selectedUpgradeTab) { case UpgradeTab.Repairs: SelectTab(UpgradeTab.Repairs); @@ -215,7 +215,7 @@ namespace Barotrauma */ private void CreateUI(GUIComponent parent) { - selectedUpgradTab = UpgradeTab.Upgrade; + selectedUpgradeTab = UpgradeTab.Upgrade; parent.ClearChildren(); ItemInfoFrame.ClearChildren(); @@ -259,8 +259,8 @@ namespace Barotrauma GUIImage submarineIcon = new GUIImage(rectT(new Point(locationLayout.Rect.Height, locationLayout.Rect.Height), locationLayout), style: "SubmarineIcon", scaleToFit: true); new GUITextBlock(rectT(1.0f - submarineIcon.RectTransform.RelativeSize.X, 1, locationLayout), TextManager.Get("UpgradeUI.Title"), font: GUI.LargeFont); categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true }; - GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradTab == UpgradeTab.Upgrade }; - GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradTab == UpgradeTab.Repairs }; + GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradeTab == UpgradeTab.Upgrade }; + GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs }; /* RIGHT HEADER LAYOUT * |---------------------------------------------------------------------------------------------------| @@ -282,15 +282,15 @@ namespace Barotrauma { if (o is UpgradeTab upgradeTab) { - if (upgradeTab != selectedUpgradTab || currentStoreLayout == null || currentStoreLayout.Parent != storeLayout) + if (upgradeTab != selectedUpgradeTab || currentStoreLayout == null || currentStoreLayout.Parent != storeLayout) { - selectedUpgradTab = upgradeTab; - SelectTab(selectedUpgradTab); + selectedUpgradeTab = upgradeTab; + SelectTab(selectedUpgradeTab); storeLayout?.Recalculate(); } - repairButton.Selected = (UpgradeTab) repairButton.UserData == selectedUpgradTab; - upgradeButton.Selected = (UpgradeTab) upgradeButton.UserData == selectedUpgradTab; + repairButton.Selected = (UpgradeTab) repairButton.UserData == selectedUpgradeTab; + upgradeButton.Selected = (UpgradeTab) upgradeButton.UserData == selectedUpgradeTab; return true; } @@ -306,6 +306,12 @@ namespace Barotrauma SelectTab(UpgradeTab.Upgrade); + var itemSwapPreview = new GUICustomComponent(new RectTransform(new Vector2(0.27f, 0.4f), mainStoreLayout.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(GUI.IsFourByThree() ? 0.5f : 0.47f, 0.0f) }, DrawItemSwapPreview) + { + IgnoreLayoutGroups = true, + CanBeFocused = true + }; + #if DEBUG // creates a button that re-creates the UI CreateRefreshButton(); @@ -323,6 +329,35 @@ namespace Barotrauma #endif } + private void DrawItemSwapPreview(SpriteBatch spriteBatch, GUICustomComponent component) + { + var selectedItem = customizeTabOpen ? + activeItemSwapSlideDown?.UserData as Item ?? HoveredItem as Item : + HoveredItem as Item; + if (selectedItem?.Prefab.SwappableItem == null) { return; } + + Sprite schematicsSprite = selectedItem.Prefab.SwappableItem.SchematicSprite; + if (schematicsSprite == null) { return; } + float schematicsScale = Math.Min(component.Rect.Width / 2 / schematicsSprite.size.X, component.Rect.Height / schematicsSprite.size.Y); + Vector2 center = new Vector2(component.Rect.Center.X, component.Rect.Center.Y); + schematicsSprite.Draw(spriteBatch, new Vector2(component.Rect.X, center.Y), GUI.Style.Green, new Vector2(0, schematicsSprite.size.Y / 2), + scale: schematicsScale); + + var swappableItemList = selectedUpgradeCategoryLayout?.FindChild("prefablist", true) as GUIListBox; + var highlightedElement = swappableItemList?.Content.FindChild(c => c.UserData is ItemPrefab && c.IsParentOf(GUI.MouseOn)) ?? GUI.MouseOn; + ItemPrefab swapTo = highlightedElement?.UserData as ItemPrefab ?? selectedItem.PendingItemSwap; + if (swapTo?.SwappableItem == null) { return; } + Sprite? schematicsSprite2 = swapTo.SwappableItem?.SchematicSprite; + schematicsSprite2?.Draw(spriteBatch, new Vector2(component.Rect.Right, center.Y), GUI.Style.Orange, new Vector2(schematicsSprite2.size.X, schematicsSprite2.size.Y / 2), + scale: Math.Min(component.Rect.Width / 2 / schematicsSprite2.size.X, component.Rect.Height / schematicsSprite2.size.Y)); + + var arrowSprite = GUI.Style?.GetComponentStyle("GUIButtonToggleRight")?.GetDefaultSprite(); + if (arrowSprite != null) + { + arrowSprite.Draw(spriteBatch, center, scale: GUI.Scale); + } + } + private void SelectTab(UpgradeTab tab) { if (currentStoreLayout != null) @@ -672,7 +707,9 @@ namespace Barotrauma GUIFrame paddedFrame = new GUIFrame(rectT(0.93f, 0.9f, frame, Anchor.Center), style: null); bool hasSwappableItems = Submarine.MainSub.GetItems(true).Any(i => - i.Prefab.SwappableItem != null && (i.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == i.Prefab.Identifier)) && + i.Prefab.SwappableItem != null && + !i.HiddenInGame && i.AllowSwapping && + (i.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == i.Prefab.Identifier)) && Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && category.ItemTags.Any(t => i.HasTag(t))); float listHeight = hasSwappableItems ? 0.9f : 1.0f; @@ -746,7 +783,7 @@ namespace Barotrauma p is ItemPrefab itemPrefab && category.ItemTags.Any(t => itemPrefab.Tags.Contains(t)) && (itemPrefab.SwappableItem?.CanBeBought ?? false)).Cast(); - var entitiesOnSub = submarine.GetItems(true).Where(i => submarine.IsEntityFoundOnThisSub(i, true) && category.ItemTags.Any(t => i.HasTag(t))).ToList(); + var entitiesOnSub = submarine.GetItems(true).Where(i => submarine.IsEntityFoundOnThisSub(i, true) && !i.HiddenInGame && i.AllowSwapping && category.ItemTags.Any(t => i.HasTag(t))).ToList(); int slotIndex = 0; foreach (Item item in entitiesOnSub) @@ -836,7 +873,7 @@ namespace Barotrauma int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation); frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description, - price, null, + price, replacement, addBuyButton: true, addProgressBar: false, buttonStyle: isPurchased ? "UpgradeBuyButton" : "StoreAddToCrateButton")); @@ -1191,7 +1228,7 @@ namespace Barotrauma { if (HoveredItem != item) { CreateItemTooltip(item); } HoveredItem = item; - if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradTab == UpgradeTab.Upgrade && currentStoreLayout != null) + if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradeTab == UpgradeTab.Upgrade && currentStoreLayout != null) { if (customizeTabOpen) { @@ -1221,14 +1258,14 @@ namespace Barotrauma // Every wall should have the same upgrades so we can just display the first one in the tooltip Structure? firstStructure = submarineWalls.FirstOrDefault(); // use pnpoly algorithm to detect if our mouse is within any of the hull polygons - if (subHullVerticies.Any(hullVertex => ToolBox.PointIntersectsWithPolygon(PlayerInput.MousePosition, hullVertex))) + if (subHullVertices.Any(hullVertex => ToolBox.PointIntersectsWithPolygon(PlayerInput.MousePosition, hullVertex))) { if (HoveredItem != firstStructure && !(firstStructure is null)) { CreateItemTooltip(firstStructure); } HoveredItem = firstStructure; isMouseOnStructure = true; GUI.MouseCursor = CursorState.Hand; - if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradTab == UpgradeTab.Upgrade && currentStoreLayout != null) + if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradeTab == UpgradeTab.Upgrade && currentStoreLayout != null) { ScrollToCategory(data => data.Category.IsWallUpgrade); } @@ -1353,7 +1390,7 @@ namespace Barotrauma Vector2 offset = (sub.WorldPosition - new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)) * scale; Vector2 center = parent.Rect.Center.ToVector2(); - subHullVerticies = new Vector2[sub.HullVertices.Count][]; + subHullVertices = new Vector2[sub.HullVertices.Count][]; for (int i = 0; i < sub.HullVertices.Count; i++) { @@ -1367,7 +1404,7 @@ namespace Barotrauma float angle = (float)Math.Atan2(edge.Y, edge.X); Matrix rotate = Matrix.CreateRotationZ(angle); - subHullVerticies[i] = new[] + subHullVertices[i] = new[] { center + start + Vector2.Transform(new Vector2(length, -lineWidth), rotate), center + end + Vector2.Transform(new Vector2(-length, -lineWidth), rotate), @@ -1379,7 +1416,7 @@ namespace Barotrauma private void DrawSubmarine(SpriteBatch spriteBatch, GUICustomComponent component) { - foreach (Vector2[] hullVertex in subHullVerticies) + foreach (Vector2[] hullVertex in subHullVertices) { // calculate the center point so we can draw a line from X to Y instead of drawing a rotated rectangle that is filled Vector2 point1 = hullVertex[1] + (hullVertex[2] - hullVertex[1]) / 2; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 25ba13501..0dbbe81f8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -951,10 +951,7 @@ namespace Barotrauma Screen.Selected.AddToGUIUpdateList(); - if (Client != null) - { - Client.AddToGUIUpdateList(); - } + Client?.AddToGUIUpdateList(); SubmarinePreview.AddToGUIUpdateList(); @@ -984,10 +981,7 @@ namespace Barotrauma } } - if (NetworkMember != null) - { - NetworkMember.Update((float)Timing.Step); - } + NetworkMember?.Update((float)Timing.Step); GUI.Update((float)Timing.Step); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 5fef339b4..6672be5c8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -1279,10 +1279,7 @@ namespace Barotrauma //make the previously selected character wait in place for some time //(so they don't immediately start idling and walking away from their station) var aiController = Character.Controlled?.AIController; - if (aiController != null) - { - aiController.Reset(); - } + aiController?.Reset(); DisableCommandUI(); Character.Controlled = character; HintManager.OnChangeCharacter(); @@ -2009,7 +2006,10 @@ namespace Barotrauma { nodeConnectors = new GUICustomComponent( new RectTransform(Vector2.One, commandFrame.RectTransform), - onDraw: DrawNodeConnectors); + onDraw: DrawNodeConnectors) + { + CanBeFocused = false + }; nodeConnectors.SetAsFirstChild(); background.SetAsFirstChild(); } @@ -2018,10 +2018,27 @@ namespace Barotrauma { if (centerNode == null || optionNodes == null) { return; } var startNodePos = centerNode.Rect.Center.ToVector2(); - // Don't draw connectors for mini map options or assignment nodes - if ((targetFrame == null || !targetFrame.Visible) && !(optionNodes.FirstOrDefault()?.Item1.UserData is Character)) + // Don't draw connectors for assignment nodes + if (!(optionNodes.FirstOrDefault()?.Item1.UserData is Character)) { - optionNodes.ForEach(n => DrawNodeConnector(startNodePos, centerNodeMargin, n.Item1, optionNodeMargin, spriteBatch)); + // Regular option nodes + if (targetFrame == null || !targetFrame.Visible) + { + optionNodes.ForEach(n => DrawNodeConnector(startNodePos, centerNodeMargin, n.Item1, optionNodeMargin, spriteBatch)); + } + // Minimap item nodes for single-option orders + else if(optionNodes.FirstOrDefault()?.Item1?.UserData is Tuple userData && string.IsNullOrEmpty(userData.Item2)) + { + foreach (var node in optionNodes) + { + float iconRadius = 0.5f * optionNodeMargin; + Vector2 itemPosition = node.Item1.Parent.Rect.Center.ToVector2(); + if (Vector2.Distance(node.Item1.Center, itemPosition) <= iconRadius) { continue; } + DrawNodeConnector(itemPosition, 0.0f, node.Item1, iconRadius, spriteBatch, widthMultiplier: 0.5f); + GUI.DrawFilledRectangle(spriteBatch, itemPosition - Vector2.One, new Vector2(3), + node.Item1.GetChildByUserData("colorsource")?.Color ?? Color.White); + } + } } DrawNodeConnector(startNodePos, centerNodeMargin, returnNode, returnNodeMargin, spriteBatch); if (shortcutCenterNode == null || !shortcutCenterNode.Visible) { return; } @@ -2030,7 +2047,7 @@ namespace Barotrauma shortcutNodes.ForEach(n => DrawNodeConnector(startNodePos, shortcutCenterNodeMargin, n, shortcutNodeMargin, spriteBatch)); } - private void DrawNodeConnector(Vector2 startNodePos, float startNodeMargin, GUIComponent endNode, float endNodeMargin, SpriteBatch spriteBatch) + private void DrawNodeConnector(Vector2 startNodePos, float startNodeMargin, GUIComponent endNode, float endNodeMargin, SpriteBatch spriteBatch, float widthMultiplier = 1.0f) { if (endNode == null || !endNode.Visible) { return; } var endNodePos = endNode.Rect.Center.ToVector2(); @@ -2041,11 +2058,11 @@ namespace Barotrauma if ((selectedNode == null && endNode != shortcutCenterNode && GUI.IsMouseOn(endNode)) || (isSelectionHighlighted && (endNode == selectedNode || (endNode == shortcutCenterNode && shortcutNodes.Any(n => GUI.IsMouseOn(n)))))) { - GUI.DrawLine(spriteBatch, start, end, colorSource != null ? colorSource.HoverColor : Color.White, width: 4); + GUI.DrawLine(spriteBatch, start, end, colorSource?.HoverColor ?? Color.White, width: Math.Max(widthMultiplier * 4.0f, 1.0f)); } else { - GUI.DrawLine(spriteBatch, start, end, colorSource != null ? colorSource.Color : Color.White * nodeColorMultiplier, width: 2); + GUI.DrawLine(spriteBatch, start, end, colorSource?.Color ?? Color.White * nodeColorMultiplier, width: Math.Max(widthMultiplier * 2.0f, 1.0f)); } } @@ -2122,7 +2139,12 @@ namespace Barotrauma { if (commandFrame == null) { return false; } RemoveOptionNodes(); - if (targetFrame != null) { targetFrame.Visible = false; } + if (targetFrame != null) + { + targetFrame.Visible = false; + nodeConnectors.RectTransform.Parent = commandFrame.RectTransform; + nodeConnectors.RectTransform.RepositionChildInHierarchy(1); + } // TODO: Center node could move to option node instead of being removed commandFrame.RemoveChild(centerNode); SetCenterNode(node); @@ -2731,20 +2753,22 @@ namespace Barotrauma if (itemTargetFrame == null) { continue; } var anchor = Anchor.TopLeft; - if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f) + if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f) { - anchor = Anchor.BottomRight; + if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f) + { + anchor = Anchor.BottomRight; + } + else + { + anchor = Anchor.TopRight; + } } - else if (itemTargetFrame.RectTransform.RelativeOffset.X > 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f) + else if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f) { anchor = Anchor.BottomLeft; } - else if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y > 0.5f) - { - anchor = Anchor.TopRight; - } - GUIComponent optionElement; if (order.Options.Length > 1) { @@ -2807,7 +2831,6 @@ namespace Barotrauma { UserData = userData, Font = GUI.SmallFont, - ToolTip = item?.Name ?? GetOrderNameBasedOnContextuality(order), OnClicked = (_, userData) => { if (!CanIssueOrders) { return false; } @@ -2824,12 +2847,28 @@ namespace Barotrauma var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o.Order != null && o.Order.Identifier == userData.Item1.Identifier && o.Order.TargetEntity == userData.Item1.TargetEntity)) ? 0.5f : 1f; - CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier); + CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier, tooltip: item.Name); optionNodes.Add(new Tuple(optionElement, Keys.None)); } optionElements.Add(optionElement); } - GUI.PreventElementOverlap(optionElements, clampArea: new Rectangle(10, 10, GameMain.GraphicsWidth - 20, GameMain.GraphicsHeight - 20)); + + Rectangle clampArea = new Rectangle(10, 10, GameMain.GraphicsWidth - 20, GameMain.GraphicsHeight - 20); + if (order.Identifier == "operateweapons") + { + Rectangle disallowedArea = targetFrame.GetChild().Rect; + Point originalSize = disallowedArea.Size; + disallowedArea.Size = disallowedArea.MultiplySize(0.9f); + disallowedArea.X += (originalSize.X - disallowedArea.Size.X) / 2; + disallowedArea.Y += (originalSize.Y - disallowedArea.Size.Y) / 2; + GUI.PreventElementOverlap(optionElements, new List() { disallowedArea }, clampArea); + nodeConnectors.RectTransform.Parent = targetFrame.RectTransform; + nodeConnectors.RectTransform.SetAsFirstChild(); + } + else + { + GUI.PreventElementOverlap(optionElements, clampArea: clampArea); + } var shadow = new GUIFrame( new RectTransform(targetFrame.Rect.Size + new Point((int)(200 * GUI.Scale)), targetFrame.RectTransform, anchor: Anchor.Center), @@ -2901,6 +2940,12 @@ namespace Barotrauma private bool CreateAssignmentNodes(GUIComponent node) { + if (centerNode == null) + { + DisableCommandUI(); + return false; + } + var order = (node.UserData is Order) ? new Tuple(node.UserData as Order, null) : node.UserData as Tuple; @@ -2947,6 +2992,8 @@ namespace Barotrauma node = null; } targetFrame.Visible = false; + nodeConnectors.RectTransform.Parent = commandFrame.RectTransform; + nodeConnectors.RectTransform.RepositionChildInHierarchy(1); } if (shortcutCenterNode != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index 84952177c..e2a74ba9a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -211,10 +211,7 @@ namespace Barotrauma { Character.Controlled = null; - if (prevControlled != null) - { - prevControlled.ClearInputs(); - } + prevControlled?.ClearInputs(); overlayColor = Color.LightGray; overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId); @@ -535,7 +532,13 @@ namespace Barotrauma msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); - msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex); + + var selectedMissionIndices = map.GetSelectedMissionIndices(); + msg.Write((byte)selectedMissionIndices.Count()); + foreach (int selectedMissionIndex in selectedMissionIndices) + { + msg.Write((byte)selectedMissionIndex); + } msg.Write(PurchasedHullRepairs); msg.Write(PurchasedItemRepairs); msg.Write(PurchasedLostShuttles); @@ -589,8 +592,15 @@ namespace Barotrauma string mapSeed = msg.ReadString(); UInt16 currentLocIndex = msg.ReadUInt16(); UInt16 selectedLocIndex = msg.ReadUInt16(); - byte selectedMissionIndex = msg.ReadByte(); - bool allowDebugTeleport = msg.ReadBoolean(); + + byte selectedMissionCount = msg.ReadByte(); + List selectedMissionIndices = new List(); + for (int i = 0; i < selectedMissionCount; i++) + { + selectedMissionIndices.Add(msg.ReadByte()); + } + + bool allowDebugTeleport = msg.ReadBoolean(); float? reputation = null; if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); } @@ -717,7 +727,7 @@ namespace Barotrauma campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex); campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); - campaign.Map.SelectMission(selectedMissionIndex); + campaign.Map.SelectMission(selectedMissionIndices); campaign.Map.AllowDebugTeleport = allowDebugTeleport; campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems); campaign.CargoManager.SetPurchasedItems(purchasedItems); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index 469790d11..5c65ea608 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -266,10 +266,7 @@ namespace Barotrauma prevControlled.AIController.Enabled = false; } Character.Controlled = null; - if (prevControlled != null) - { - prevControlled.ClearInputs(); - } + prevControlled?.ClearInputs(); GUI.DisableHUD = true; while (GameMain.Instance.LoadingScreenOpen) @@ -305,7 +302,7 @@ namespace Barotrauma yield return CoroutineStatus.Success; } overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration); - timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); + timer = Math.Min(timer + CoroutineManager.UnscaledDeltaTime, textDuration); yield return CoroutineStatus.Running; } var outpost = GameMain.GameSession.Level.StartOutpost; @@ -333,7 +330,7 @@ namespace Barotrauma while (timer < fadeInDuration) { overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration); - timer += CoroutineManager.DeltaTime; + timer += CoroutineManager.UnscaledDeltaTime; yield return CoroutineStatus.Running; } overlayColor = Color.Transparent; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs index 6d880f5ef..989889b19 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs @@ -224,10 +224,7 @@ namespace Barotrauma.Tutorials public virtual void Update(float deltaTime) { - if (videoPlayer != null) - { - videoPlayer.Update(); - } + videoPlayer?.Update(); if (activeObjectives != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs index 97b6dc4e7..e69e11bae 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs @@ -218,12 +218,15 @@ namespace Barotrauma }; List missionsToDisplay = new List(selectedMissions); - if (!selectedMissions.Any() && startLocation?.SelectedMission != null) - { - if (startLocation.SelectedMission.Locations[0] == startLocation.SelectedMission.Locations[1] || - startLocation.SelectedMission.Locations.Contains(campaignMode?.Map.SelectedLocation)) + if (!selectedMissions.Any() && startLocation != null) + { + foreach (Mission mission in startLocation.SelectedMissions) { - missionsToDisplay.Add(startLocation.SelectedMission); + if (mission.Locations[0] == mission.Locations[1] || + mission.Locations.Contains(campaignMode?.Map.SelectedLocation)) + { + missionsToDisplay.Add(mission); + } } } @@ -284,10 +287,11 @@ namespace Barotrauma new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), displayedMission.Completed ? "MissionCompletedIcon" : "MissionFailedIcon", scaleToFit: true); } - var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), missionContentHorizontal.RectTransform)) + var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), missionContentHorizontal.RectTransform)) { - RelativeSpacing = 0.05f + AbsoluteSpacing = GUI.IntScale(5) }; + missionContentHorizontal.Recalculate(); var missionNameTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), displayedMission.Name, font: GUI.SubHeadingFont); if (displayedMission.Difficulty.HasValue) @@ -309,7 +313,7 @@ namespace Barotrauma }; } } - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), + var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), missionMessage, wrap: true, parseRichText: true); int reward = displayedMission.GetReward(Submarine.MainSub); if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && reward > 0) @@ -323,6 +327,13 @@ namespace Barotrauma var spacing = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), missionList.Content.RectTransform) { MaxSize = new Point(int.MaxValue, GUI.IntScale(15)) }, style: null); new GUIFrame(new RectTransform(new Vector2(0.8f, 1.0f), spacing.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.1f, 0.0f) }, "HorizontalLine"); } + + foreach (GUIComponent child in missionTextContent.Children) + { + child.RectTransform.IsFixedSize = true; + } + missionTextContent.RectTransform.MinSize = new Point(0, missionTextContent.Children.Sum(c => c.Rect.Height + missionTextContent.AbsoluteSpacing)); + missionContentHorizontal.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Rect.Height / missionTextContent.RectTransform.RelativeSize.Y)); } if (!missionsToDisplay.Any()) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/UpgradeManager.cs index e66fb012d..073893bb2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/UpgradeManager.cs @@ -39,43 +39,5 @@ namespace Barotrauma } } } - - /// - /// Server has notified us that upgrades were reset. - /// - /// - /// - public void ClientRead(IReadMessage inc) - { - bool shouldReset = inc.ReadBoolean(); - int money = inc.ReadInt32(); - // uint length = inc.ReadUInt32(); - // - // for (int i = 0; i < length; i++) - // { - // string key = inc.ReadString(); - // byte value = inc.ReadByte(); - // Metadata.SetValue(key, value); - // } - - Campaign.Money = money; - - if (shouldReset) - { - ResetUpgrades(); - } - - // spentMoney is local, so this message box should only appear for those who have spent money on upgrades - if (spentMoney > 0) - { - GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("UpgradeRefundTitle"), TextManager.Get("UpgradeRefundBody"), new [] { TextManager.Get("Ok") }); - msgBox.Buttons[0].OnClicked += msgBox.Close; - } - - spentMoney = 0; - PendingUpgrades.Clear(); - PurchasedUpgrades.Clear(); - CanUpgrade = false; - } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index fbeca0143..650369933 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -1326,24 +1326,41 @@ namespace Barotrauma } }; - GUITickBox disableInGameHintsBox = new GUITickBox(new RectTransform(tickBoxScale, gameplaySettingsGroup.RectTransform), - TextManager.Get("DisableInGameHints")) + new GUITickBox(new RectTransform(tickBoxScale, gameplaySettingsGroup.RectTransform), TextManager.Get("DisableInGameHints")) { Selected = DisableInGameHints, ToolTip = TextManager.Get("DisableInGameHintsToolTip"), OnSelected = (tickBox) => { DisableInGameHints = tickBox.Selected; - if (!DisableInGameHints && GameMain.Config?.IgnoredHints != null) - { - // Reset the ignored hints when the hints are re-enabled (to-be-replaced by a separate button) - GameMain.Config.IgnoredHints.Clear(); - } UnsavedSettings = true; return true; } }; + new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), + text: TextManager.Get("ResetInGameHints"), + style: "GUIButtonSmall") + { + OnClicked = (button, userData) => + { + var msgBox = new GUIMessageBox(TextManager.Get("ResetInGameHints"), + TextManager.Get("ResetInGameHintsTooltip"), + new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }) + { + UserData = "verificationprompt" + }; + msgBox.Buttons[0].OnClicked = (button, userData) => + { + GameMain.Config.IgnoredHints.Clear(); + return true; + }; + msgBox.Buttons[0].OnClicked += msgBox.Close; + msgBox.Buttons[1].OnClicked = msgBox.Close; + return false; + } + }; + GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), TextManager.Get("HUDScale"), font: GUI.SubHeadingFont, wrap: true); GUIScrollBar HUDScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), style: "GUISlider", barSize: 0.1f) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs index c9c4fcb7a..d57f1ee61 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs @@ -281,10 +281,10 @@ namespace Barotrauma.Items.Components foreach (ParticleEmitter particleEmitter in particleEmitters) { float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); - float particleRange = particleEmitter.Prefab.VelocityMax * particleEmitter.Prefab.ParticlePrefab.LifeTime; + float particleRange = particleEmitter.Prefab.Properties.VelocityMax * particleEmitter.Prefab.ParticlePrefab.LifeTime; particleEmitter.Emit( deltaTime, particleStartPos, - item.CurrentHull, particleAngle, particleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0, velocityMultiplier: dist / particleRange * 1.5f, + item.CurrentHull, particleAngle, particleEmitter.Prefab.Properties.CopyEntityAngle ? -particleAngle : 0, velocityMultiplier: dist / particleRange * 1.5f, colorMultiplier: new Color(color.R, color.G, color.B, (byte)255)); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs index b3a228c25..f58aee17b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs @@ -16,44 +16,43 @@ namespace Barotrauma.Items.Components public Vector2 DrawSize { - get { return new Vector2(light.Range * 2, light.Range * 2); } + get { return new Vector2(Light.Range * 2, Light.Range * 2); } } - private LightSource light; - public LightSource Light - { - get { return light; } - } + public LightSource Light { get; } public override void OnScaleChanged() { - light.SpriteScale = Vector2.One * item.Scale; - light.Position = ParentBody != null ? ParentBody.Position : item.Position; + Light.SpriteScale = Vector2.One * item.Scale; + Light.Position = ParentBody != null ? ParentBody.Position : item.Position; } partial void SetLightSourceState(bool enabled, float brightness) { - if (light == null) { return; } - light.Enabled = enabled; - light.Color = LightColor.Multiply(brightness); + if (Light == null) { return; } + Light.Enabled = enabled; + if (enabled) + { + Light.Color = LightColor.Multiply(brightness); + } } public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) { - if (light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn) + if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn) { - Vector2 origin = light.LightSprite.Origin; - if ((light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = light.LightSprite.SourceRect.Width - origin.X; } - if ((light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = light.LightSprite.SourceRect.Height - origin.Y; } - light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, origin, -light.Rotation, item.Scale, light.LightSpriteEffect, itemDepth - 0.0001f); + Vector2 origin = Light.LightSprite.Origin; + if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; } + if ((Light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = Light.LightSprite.SourceRect.Height - origin.Y; } + Light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, origin, -Light.Rotation, item.Scale, Light.LightSpriteEffect, itemDepth - 0.0001f); } } public override void FlipX(bool relativeToSub) { - if (light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null) + if (Light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null) { - light.LightSpriteEffect = light.LightSpriteEffect == SpriteEffects.None ? + Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None; } } @@ -93,7 +92,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { base.RemoveComponentSpecific(); - light.Remove(); + Light.Remove(); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index ecc4b8b83..4d7ee5ea1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -178,26 +178,26 @@ namespace Barotrauma.Items.Components ToolTip = fi.TargetItem.Description }; - var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform), - childAnchor: Anchor.CenterLeft, isHorizontal: true) { RelativeSpacing = 0.02f }; + var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform), + childAnchor: Anchor.CenterLeft, isHorizontal: true) { RelativeSpacing = 0.02f }; - var itemIcon = fi.TargetItem.InventoryIcon ?? fi.TargetItem.sprite; - if (itemIcon != null) - { - new GUIImage(new RectTransform(new Point(frame.Rect.Height,frame.Rect.Height), container.RectTransform), - itemIcon, scaleToFit: true) - { - Color = fi.TargetItem.InventoryIconColor, - ToolTip = fi.TargetItem.Description - }; - } + var itemIcon = fi.TargetItem.InventoryIcon ?? fi.TargetItem.sprite; + if (itemIcon != null) + { + new GUIImage(new RectTransform(new Point(frame.Rect.Height,frame.Rect.Height), container.RectTransform), + itemIcon, scaleToFit: true) + { + Color = fi.TargetItem.InventoryIconColor, + ToolTip = fi.TargetItem.Description + }; + } - new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform), GetRecipeNameAndAmount(fi)) - { - Padding = Vector4.Zero, - AutoScaleVertical = true, - ToolTip = fi.TargetItem.Description - }; + new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform), GetRecipeNameAndAmount(fi)) + { + Padding = Vector4.Zero, + AutoScaleVertical = true, + ToolTip = fi.TargetItem.Description + }; } } @@ -367,6 +367,10 @@ namespace Barotrauma.Items.Components { toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%"; } + else if (requiredItem.MaxCondition <= 0.0f) + { + toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText); + } if (!string.IsNullOrEmpty(requiredItem.ItemPrefabs.First().Description)) { toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs index 7d9ff5175..80e5671a5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs @@ -33,10 +33,7 @@ namespace Barotrauma.Items.Components base.Update(deltaTime, cam); - if (selectionUI != null) - { - selectionUI.Update(); - } + selectionUI?.Update(); } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index c5a72e0f7..a92ec14dd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -436,7 +436,9 @@ namespace Barotrauma.Items.Components connectedSubUpdateTimer = ConnectedSubUpdateInterval; } - if (sonarView.Rect.Contains(PlayerInput.MousePosition)) + Steering steering = item.GetComponent(); + if (sonarView.Rect.Contains(PlayerInput.MousePosition) && + (GUI.MouseOn == null || GUI.MouseOn == sonarView || sonarView.IsParentOf(GUI.MouseOn) || GUI.MouseOn == steering?.GuiFrame || (steering?.GuiFrame?.IsParentOf(GUI.MouseOn) ?? false))) { float scrollSpeed = PlayerInput.ScrollWheelSpeed / 1000.0f; if (Math.Abs(scrollSpeed) > 0.0001f) @@ -613,7 +615,6 @@ namespace Barotrauma.Items.Components } } - Steering steering = item.GetComponent(); if (steering != null && steering.DockingModeEnabled && steering.ActiveDockingSource != null) { float dockingDist = Vector2.Distance(steering.ActiveDockingSource.Item.WorldPosition, steering.DockingTarget.Item.WorldPosition); @@ -780,10 +781,7 @@ namespace Barotrauma.Items.Components DisplayRadius = (rect.Width / 2.0f) * (1.0f - displayBorderSize); displayScale = DisplayRadius / range * zoom; - if (screenBackground != null) - { - screenBackground.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X); - } + screenBackground?.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X); if (useDirectionalPing) { @@ -871,10 +869,7 @@ namespace Barotrauma.Items.Components GUI.DrawString(spriteBatch, rect.Location.ToVector2(), sonarBlips.Count.ToString(), Color.White); } - if (screenOverlay != null) - { - screenOverlay.Draw(spriteBatch, center, 0.0f, rect.Width / screenOverlay.size.X); - } + screenOverlay?.Draw(spriteBatch, center, 0.0f, rect.Width / screenOverlay.size.X); if (signalStrength <= 0.5f) { @@ -937,21 +932,25 @@ namespace Barotrauma.Items.Components cave.StartPos.ToVector2(), transducerCenter, displayScale, center, DisplayRadius); } - + + int missionIndex = 0; foreach (Mission mission in GameMain.GameSession.Missions) { if (!string.IsNullOrWhiteSpace(mission.SonarLabel)) { + int i = 0; foreach (Vector2 sonarPosition in mission.SonarPositions) { DrawMarker(spriteBatch, mission.SonarLabel, mission.SonarIconIdentifier, - mission, - sonarPosition, transducerCenter, + "mission" + missionIndex + ":" + i, + sonarPosition, transducerCenter, displayScale, center, DisplayRadius * 0.95f); + i++; } } + missionIndex++; } if (AllowUsingMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null) @@ -964,7 +963,7 @@ namespace Barotrauma.Items.Components var i = unobtainedMinerals.FirstOrDefault(); if (i == null) { continue; } DrawMarker(spriteBatch, - i.Name, "mineral", i, + i.Name, "mineral", "mineralcluster" + i, c.center, transducerCenter, displayScale, center, DisplayRadius * 0.95f, onlyShowTextOnMouseOver: true); @@ -977,14 +976,14 @@ namespace Barotrauma.Items.Components if (connectedSubs.Contains(sub)) { continue; } if (sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } - if (item.Submarine != null) + if (item.Submarine != null || Character.Controlled != null) { //hide enemy team - if (sub.TeamID == CharacterTeamType.Team1 && (item.Submarine.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2)) + if (sub.TeamID == CharacterTeamType.Team1 && (item.Submarine?.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2)) { continue; } - else if (sub.TeamID == CharacterTeamType.Team2 && (item.Submarine.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1)) + else if (sub.TeamID == CharacterTeamType.Team2 && (item.Submarine?.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1)) { continue; } @@ -1095,7 +1094,7 @@ namespace Barotrauma.Items.Components if (!dockingPort.Item.Submarine.ShowSonarMarker && dockingPort.Item.Submarine != item.Submarine && !dockingPort.Item.Submarine.Info.IsOutpost) { continue; } //don't show the docking ports of the opposing team on the sonar - if (item.Submarine != null) + if (item.Submarine != null && item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle && dockingPort.Item.Submarine.Info.Type != SubmarineType.Outpost) { // specifically checking for friendlyNPC seems more logical here if (dockingPort.Item.Submarine.TeamID != item.Submarine.TeamID && dockingPort.Item.Submarine.TeamID != CharacterTeamType.FriendlyNPC) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs index 0cf62bd4b..6fce23838 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs @@ -111,7 +111,7 @@ namespace Barotrauma.Items.Components Tuple tracerPoints = new Tuple(startLocation, endLocation); foreach (ParticleEmitter emitter in particleEmitters) { - emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation, colorMultiplier: emitter.Prefab.ColorMultiplier, tracerPoints: tracerPoints); + emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier, tracerPoints: tracerPoints); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs index a57f4abfa..93af5d8f2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs @@ -73,7 +73,7 @@ namespace Barotrauma.Items.Components } particleEmitter.Emit( deltaTime, ConvertUnits.ToDisplayUnits(raystart), - item.CurrentHull, particleAngle, particleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0); + item.CurrentHull, particleAngle, particleEmitter.Prefab.Properties.CopyEntityAngle ? -particleAngle : 0); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index 963b5b9c7..1bedbf2d6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components Wire equippedWire = null; bool allowRewiring = GameMain.NetworkMember?.ServerSettings == null || GameMain.NetworkMember.ServerSettings.AllowRewiring || panel.AlwaysAllowRewiring; - if (allowRewiring && (!panel.Locked || Screen.Selected == GameMain.SubEditorScreen)) + if (allowRewiring && (!panel.Locked && !panel.TemporarilyLocked || Screen.Selected == GameMain.SubEditorScreen)) { //if the Character using the panel has a wire item equipped //and the wire hasn't been connected yet, draw it on the panel @@ -365,7 +365,7 @@ namespace Barotrauma.Items.Components ConnectionPanel.HighlightedWire = wire; bool allowRewiring = GameMain.NetworkMember?.ServerSettings == null || GameMain.NetworkMember.ServerSettings.AllowRewiring || panel.AlwaysAllowRewiring; - if (allowRewiring && (!wire.Locked && !panel.Locked || Screen.Selected == GameMain.SubEditorScreen)) + if (allowRewiring && (!wire.Locked && !panel.Locked && !panel.TemporarilyLocked || Screen.Selected == GameMain.SubEditorScreen)) { //start dragging the wire if (PlayerInput.PrimaryMouseButtonHeld()) { DraggingConnected = wire; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs index c5806925f..8b9e7cf42 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs @@ -46,6 +46,8 @@ namespace Barotrauma.Items.Components private Character equipper; + private bool isEquippable; + public IEnumerable VisibleCharacters { get @@ -55,14 +57,28 @@ namespace Barotrauma.Items.Components } } + public override void OnItemLoaded() + { + isEquippable = item.GetComponent() != null; + if (!isEquippable) { IsActive = true; } + } + public override void Update(float deltaTime, Camera cam) { base.Update(deltaTime, cam); - if (equipper == null || equipper.Removed) + Entity refEntity = equipper; + if (isEquippable) { - IsActive = false; - return; + if (equipper == null || equipper.Removed) + { + IsActive = false; + return; + } + } + else + { + refEntity = item; } if (updateTimer > 0.0f) @@ -76,11 +92,11 @@ namespace Barotrauma.Items.Components { if (c == equipper || !c.Enabled || c.Removed) { continue; } - float dist = Vector2.DistanceSquared(equipper.WorldPosition, c.WorldPosition); + float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition); if (dist < Range * Range) { - Vector2 diff = c.WorldPosition - equipper.WorldPosition; - if (Submarine.CheckVisibility(equipper.SimPosition, equipper.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) + Vector2 diff = c.WorldPosition - refEntity.WorldPosition; + if (Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) { visibleCharacters.Add(c); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index b7e45630f..744067877 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -254,7 +254,7 @@ namespace Barotrauma.Items.Components foreach (ParticleEmitter emitter in particleEmitterCharges) { // color is currently not connected to ammo type, should be updated when ammo is changed - emitter.Emit(deltaTime, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.ColorMultiplier); + emitter.Emit(deltaTime, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier); } if (chargeSoundChannel == null || !chargeSoundChannel.IsPlaying) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 99960ef81..ce48fbe7c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -1125,9 +1125,10 @@ namespace Barotrauma if (selectedSlot == null) { if (DraggingItemToWorld && - Character.Controlled.FocusedItem?.OwnInventory != null && - (Character.Controlled.FocusedItem.GetComponent()?.HasRequiredItems(Character.Controlled, addMessage: false) ?? false) && - Character.Controlled.FocusedItem.OwnInventory.CanBePut(DraggingItems.FirstOrDefault())) + Character.Controlled.FocusedItem is { OwnInventory: { } inventory } item && item.GetComponent() is { } container && + container.HasRequiredItems(Character.Controlled, addMessage: false) && + container.AllowDragAndDrop && + inventory.CanBePut(DraggingItems.FirstOrDefault())) { bool anySuccess = false; foreach (Item it in DraggingItems) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 751dbda41..0e99ca9d8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -462,10 +462,7 @@ namespace Barotrauma if (GameMain.DebugDraw) { - if (body != null) - { - body.DebugDraw(spriteBatch, Color.White); - } + body?.DebugDraw(spriteBatch, Color.White); } if (editing && IsSelected && PlayerInput.KeyDown(Keys.Space)) @@ -722,7 +719,7 @@ namespace Barotrauma reloadTextureButton.OnClicked += (button, data) => { Sprite.ReloadXML(); - Sprite.ReloadTexture(); + Sprite.ReloadTexture(updateAllSprites: true); return true; }; } @@ -768,7 +765,7 @@ namespace Barotrauma { if (!ic.AllowInGameEditing) { continue; } if (SerializableProperty.GetProperties(ic).Count == 0 && - !SerializableProperty.GetProperties(ic).Any(p => p.GetAttribute().IsEditable())) + !SerializableProperty.GetProperties(ic).Any(p => p.GetAttribute().IsEditable(ic))) { continue; } @@ -1133,31 +1130,35 @@ namespace Barotrauma texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false)); bool noComponentText = true; - foreach (ItemComponent ic in components) - { - if (string.IsNullOrEmpty(ic.DisplayMsg)) { continue; } - if (!ic.CanBePicked && !ic.CanBeSelected) { continue; } - if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; } - Color color = Color.Gray; - if (ic.HasRequiredItems(character, false)) - { - if (ic is Repairable) - { - if (!IsFullCondition) { color = Color.Cyan; } - } - else - { - color = Color.Cyan; - } - } - texts.Add(new ColoredText(ic.DisplayMsg, color, false, false)); - noComponentText = false; - } - if (noComponentText && CampaignInteractionType != CampaignMode.InteractionType.None) + if (CampaignInteractionType != CampaignMode.InteractionType.None) { texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameMain.Config.KeyBindText(InputType.Use)), Color.Cyan, false, false)); } + else + { + foreach (ItemComponent ic in components) + { + if (string.IsNullOrEmpty(ic.DisplayMsg)) { continue; } + if (!ic.CanBePicked && !ic.CanBeSelected) { continue; } + if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; } + + Color color = Color.Gray; + if (ic.HasRequiredItems(character, false)) + { + if (ic is Repairable) + { + if (!IsFullCondition) { color = Color.Cyan; } + } + else + { + color = Color.Cyan; + } + } + texts.Add(new ColoredText(ic.DisplayMsg, color, false, false)); + noComponentText = false; + } + } if (PlayerInput.IsShiftDown() && CrewManager.DoesItemHaveContextualOrders(this)) { texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")), Color.Cyan, false, false)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs index e899eb482..f8a8e97cb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs @@ -77,9 +77,9 @@ namespace Barotrauma if (flash) { - float displayRange = flashRange.HasValue ? flashRange.Value : Attack.Range; + float displayRange = flashRange ?? Attack.Range; if (displayRange < 0.1f) { return; } - var light = new LightSource(worldPosition, displayRange, Color.LightYellow, null); + var light = new LightSource(worldPosition, displayRange, flashColor, null); CoroutineManager.StartCoroutine(DimLight(light)); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 2d8875d30..cd5668d22 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -629,9 +629,9 @@ namespace Barotrauma PowerConsumptionTimer = message.ReadSingle() }; } - else if (BallastFlora != null) + else { - BallastFlora.ClientRead(message, header); + BallastFlora?.ClientRead(message, header); } return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs index 743c367f5..e2584cf07 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs @@ -210,7 +210,7 @@ namespace Barotrauma if (ParticleEmitterTriggers[i] != null && !ParticleEmitterTriggers[i].IsTriggered) { continue; } Vector2 emitterPos = LocalToWorld(Prefab.EmitterPositions[i]); ParticleEmitters[i].Emit(deltaTime, emitterPos, hullGuess: null, - angle: ParticleEmitters[i].Prefab.CopyEntityAngle ? -CurrentRotation + MathHelper.PiOver2 : 0.0f); + angle: ParticleEmitters[i].Prefab.Properties.CopyEntityAngle ? -CurrentRotation + MathHelper.PiOver2 : 0.0f); } } @@ -293,6 +293,12 @@ namespace Barotrauma public void ClientRead(IReadMessage msg) { if (Triggers == null) { return; } + + if (Prefab.TakeLevelWallDamage) + { + float newHealth = msg.ReadRangedSingle(0.0f, Prefab.Health, 8); + AddDamage(Health - newHealth, 1.0f, null, isNetworkEvent: true); + } for (int i = 0; i < Triggers.Count; i++) { if (!Triggers[i].UseNetworkSyncing) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs index a25d03711..f4ecea93d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -20,6 +20,8 @@ namespace Barotrauma const int MaxVisibleObjects = 500; private Rectangle currentGridIndices; + + public bool ForceRefreshVisibleObjects; partial void UpdateProjSpecific(float deltaTime) { @@ -60,6 +62,8 @@ namespace Barotrauma if (objectGrid[x, y] == null) { continue; } foreach (LevelObject obj in objectGrid[x, y]) { + if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; } + if (zoom < 0.05f) { //hide if the sprite is very small when zoomed this far out @@ -154,9 +158,10 @@ namespace Barotrauma indices.Height = Math.Min(indices.Height, objectGrid.GetLength(1) - 1); float z = 0.0f; - if (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime) + if (ForceRefreshVisibleObjects || (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime)) { RefreshVisibleObjects(indices, cam.Zoom); + ForceRefreshVisibleObjects = false; if (cam.Zoom < 0.1f) { //when zoomed very far out, refresh a little less often diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index 150a4bfc4..bb8e93052 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs @@ -361,6 +361,7 @@ namespace Barotrauma.Lights void DrawHalo(Character character) { + if (character == null || character.Removed) { return; } Vector2 haloDrawPos = character.DrawPosition; haloDrawPos.Y = -haloDrawPos.Y; @@ -404,7 +405,7 @@ namespace Barotrauma.Lights } foreach (Item item in Item.ItemList) { - if (item.IsHighlighted && !highlightedEntities.Contains(item)) + if ((item.IsHighlighted || item.IconStyle != null) && !highlightedEntities.Contains(item)) { highlightedEntities.Add(item); } @@ -425,7 +426,14 @@ namespace Barotrauma.Lights { if (highlighted is Item item) { - item.Draw(spriteBatch, false, true); + if (item.IconStyle != null && (item != Character.Controlled.FocusedItem || Character.Controlled.FocusedItem == null)) + { + //wait until next pass + } + else + { + item.Draw(spriteBatch, false, true); + } } else if (highlighted is Character character) { @@ -434,6 +442,22 @@ namespace Barotrauma.Lights } spriteBatch.End(); + //draw items with iconstyles in the style's color + spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform); + foreach (Entity highlighted in highlightedEntities) + { + if (highlighted is Item item) + { + if (item.IconStyle != null && (item != Character.Controlled.FocusedItem || Character.Controlled.FocusedItem == null)) + { + SolidColorEffect.Parameters["color"].SetValue(item.IconStyle.Color.ToVector4()); + SolidColorEffect.CurrentTechnique.Passes[0].Apply(); + item.Draw(spriteBatch, false, true); + } + } + } + spriteBatch.End(); + //draw characters in black with a bit of blur, leaving the white edges visible float phase = (float)(Math.Sin(Timing.TotalTime * 3.0f) + 1.0f) / 2.0f; //phase oscillates between 0 and 1 Vector4 overlayColor = Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs index 28a0f056c..7a29743ad 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs @@ -607,15 +607,18 @@ namespace Barotrauma tooltip = (new Rectangle(typeChangeIconPos.ToPoint(), new Point(30)), location.LastTypeChangeMessage); } } - if (location != CurrentLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location)) && generationParams.MissionIcon != null) + if (location != CurrentLocation && generationParams.MissionIcon != null) { - Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom; - float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width; - generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom); - if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos)) + if ((CurrentLocation == currentDisplayLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location))) || location.AvailableMissions.Any(m => m.Prefab.Type == MissionType.GoTo)) { - var availableMissions = CurrentLocation.AvailableMissions.Where(m => m.Locations.Contains(location)); - tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name))); + Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom; + float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width; + generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom); + if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos)) + { + var availableMissions = CurrentLocation.AvailableMissions.Where(m => m.Locations.Contains(location)).Concat(location.AvailableMissions.Where(m => m.Prefab.Type == MissionType.GoTo)).Distinct(); + tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name))); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index 8dfdd6452..16fedb53c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -153,7 +153,7 @@ namespace Barotrauma OnClicked = (button, data) => { Sprite.ReloadXML(); - Sprite.ReloadTexture(); + Sprite.ReloadTexture(updateAllSprites: true); return true; } }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs index 990db04c4..feee46eb0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs @@ -89,9 +89,9 @@ namespace Barotrauma.Networking var fadeOutTime = !orderPrefab.IsIgnoreOrder ? (float?)orderPrefab.FadeOutTime : null; GameMain.GameSession?.CrewManager?.AddOrder(order, fadeOutTime); } - else if (orderMessageInfo.TargetCharacter != null) + else { - orderMessageInfo.TargetCharacter.SetOrder(order, orderOption, orderMessageInfo.Priority, senderCharacter); + orderMessageInfo.TargetCharacter?.SetOrder(order, orderOption, orderMessageInfo.Priority, senderCharacter); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index bd4317a2a..354129a86 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -643,7 +643,7 @@ namespace Barotrauma.Networking DebugConsole.ThrowError("Error while reading a message from server.", e); new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", new string[2] { "[message]", "[targetsite]" }, new string[2] { e.Message, e.TargetSite.ToString() })); Disconnect(); - GameMain.MainMenuScreen.Select(); + GameMain.ServerListScreen.Select(); return; } @@ -659,10 +659,7 @@ namespace Barotrauma.Networking { EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode); - if (respawnManager != null) - { - respawnManager.Update(deltaTime); - } + respawnManager?.Update(deltaTime); if (updateTimer <= DateTime.Now) { @@ -936,9 +933,6 @@ namespace Barotrauma.Networking } } break; - case ServerPacketHeader.RESET_UPGRADES: - campaign?.UpgradeManager.ClientRead(inc); - break; case ServerPacketHeader.CREW: campaign?.ClientReadCrew(inc); break; @@ -1313,10 +1307,7 @@ namespace Barotrauma.Networking Client.ReadPermissions(inc, out permissions, out permittedCommands); Client targetClient = ConnectedClients.Find(c => c.ID == clientID); - if (targetClient != null) - { - targetClient.SetPermissions(permissions, permittedCommands); - } + targetClient?.SetPermissions(permissions, permittedCommands); if (clientID == myID) { SetMyPermissions(permissions, permittedCommands.Select(command => command.names[0])); @@ -1427,7 +1418,7 @@ namespace Barotrauma.Networking while (CoroutineManager.IsCoroutineRunning("EndGame")) { - if (EndCinematic != null) { EndCinematic.Stop(); } + EndCinematic?.Stop(); yield return CoroutineStatus.Running; } @@ -2759,6 +2750,8 @@ namespace Barotrauma.Networking public void Vote(VoteType voteType, object data) { + if (clientPeer == null) return; + IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY); msg.Write((byte)ClientNetObject.VOTE); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs index db7168738..4808fa93c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs @@ -7,7 +7,102 @@ namespace Barotrauma.Particles { class ParticleEmitterProperties : ISerializableEntity { - public string Name => nameof(ParticleEmitterProperties); + private const float MinValue = int.MinValue, + MaxValue = int.MaxValue; + + public string Name => nameof(ParticleEmitter); + + private float angleMin, angleMax; + + public float AngleMinRad { get; private set; } + public float AngleMaxRad { get; private set; } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 360, MinValueFloat = -360f), Serialize(0f, true)] + public float AngleMin + { + get => angleMin; + set + { + angleMin = value; + AngleMinRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f)); + } + } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 360, MinValueFloat = -360f), Serialize(0f, true)] + public float AngleMax + { + get => angleMax; + set + { + angleMax = value; + AngleMaxRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f)); + } + } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)] + public float DistanceMin { get; set; } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)] + public float DistanceMax { get; set; } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)] + public float VelocityMin { get; set; } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)] + public float VelocityMax { get; set; } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(1f, true)] + public float ScaleMin { get; set; } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(1f, true)] + public float ScaleMax { get; set; } + + + [Editable(), Serialize("1,1", true)] + public Vector2 ScaleMultiplier { get; set; } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, true)] + public float EmitInterval { get; set; } + + [Editable, Serialize(0, true)] + public int ParticleAmount { get; set; } + + [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = 0), Serialize(0f, true)] + public float ParticlesPerSecond { get; set; } + + [Editable, Serialize(false, true)] + public bool HighQualityCollisionDetection { get; set; } + + [Editable, Serialize(false, true)] + public bool CopyEntityAngle { get; set; } + + [Editable, Serialize("1,1,1,1", true)] + public Color ColorMultiplier { get; set; } + + [Editable, Serialize(false, true)] + public bool DrawOnTop { get; set; } + + [Serialize(0f, true)] + public float Angle + { + get => AngleMin; + set => AngleMin = AngleMax = value; + } + + [Serialize(0f, true)] + public float Distance + { + get => DistanceMin; + set => DistanceMin = DistanceMax = value; + } + + [Serialize(0f, true)] + public float Velocity + { + get => VelocityMin; + set => VelocityMin = VelocityMax = value; + } + public Dictionary SerializableProperties { get; } public ParticleEmitterProperties(XElement element) @@ -39,9 +134,9 @@ namespace Barotrauma.Particles emitTimer += deltaTime * amountMultiplier; burstEmitTimer -= deltaTime; - if (Prefab.ParticlesPerSecond > 0) + if (Prefab.Properties.ParticlesPerSecond > 0) { - float emitInterval = 1.0f / Prefab.ParticlesPerSecond; + float emitInterval = 1.0f / Prefab.Properties.ParticlesPerSecond; while (emitTimer > emitInterval) { Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints); @@ -50,9 +145,9 @@ namespace Barotrauma.Particles } if (burstEmitTimer > 0.0f) { return; } - - burstEmitTimer = Prefab.EmitInterval; - for (int i = 0; i < Prefab.ParticleAmount * amountMultiplier; i++) + + burstEmitTimer = Prefab.Properties.EmitInterval; + for (int i = 0; i < Prefab.Properties.ParticleAmount * amountMultiplier; i++) { Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints); } @@ -60,26 +155,29 @@ namespace Barotrauma.Particles private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple tracerPoints = null) { - angle += Rand.Range(Prefab.AngleMin, Prefab.AngleMax); + var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab; + if (particlePrefab == null) { return; } + + angle += Rand.Range(Prefab.Properties.AngleMinRad, Prefab.Properties.AngleMaxRad); Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); - Vector2 velocity = dir * Rand.Range(Prefab.VelocityMin, Prefab.VelocityMax) * velocityMultiplier; - position += dir * Rand.Range(Prefab.DistanceMin, Prefab.DistanceMax); + Vector2 velocity = dir * Rand.Range(Prefab.Properties.VelocityMin, Prefab.Properties.VelocityMax) * velocityMultiplier; + position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax); - var particle = GameMain.ParticleManager.CreateParticle(overrideParticle ?? Prefab.ParticlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, tracerPoints: tracerPoints); + var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, tracerPoints: tracerPoints); if (particle != null) { - particle.Size *= Rand.Range(Prefab.ScaleMin, Prefab.ScaleMax) * sizeMultiplier; - particle.Size *= Prefab.ScaleMultiplier; - particle.HighQualityCollisionDetection = Prefab.HighQualityCollisionDetection; - if (colorMultiplier.HasValue) - { - particle.ColorMultiplier = colorMultiplier.Value.ToVector4(); - } - else if (Prefab.ColorMultiplier != Color.White) + particle.Size *= Rand.Range(Prefab.Properties.ScaleMin, Prefab.Properties.ScaleMax) * sizeMultiplier; + particle.Size *= Prefab.Properties.ScaleMultiplier; + particle.HighQualityCollisionDetection = Prefab.Properties.HighQualityCollisionDetection; + if (colorMultiplier.HasValue) { - particle.ColorMultiplier = Prefab.ColorMultiplier.ToVector4(); + particle.ColorMultiplier = colorMultiplier.Value.ToVector4(); + } + else if (Prefab.Properties.ColorMultiplier != Color.White) + { + particle.ColorMultiplier = Prefab.Properties.ColorMultiplier.ToVector4(); } } } @@ -88,9 +186,9 @@ namespace Barotrauma.Particles { Rectangle bounds = new Rectangle((int)startPosition.X, (int)startPosition.Y, (int)startPosition.X, (int)startPosition.Y); - for (float angle = Prefab.AngleMin; angle <= Prefab.AngleMax; angle += 0.1f) + for (float angle = Prefab.Properties.AngleMinRad; angle <= Prefab.Properties.AngleMaxRad; angle += 0.1f) { - Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Prefab.VelocityMax; + Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Prefab.Properties.VelocityMax; Vector2 endPosition = Prefab.ParticlePrefab.CalculateEndPosition(startPosition, velocity); Vector2 endSize = Prefab.ParticlePrefab.CalculateEndSize(); @@ -103,15 +201,15 @@ namespace Barotrauma.Particles } else { - spriteExtent = Math.Max(spriteExtent, Math.Max(sprite.size.X * endSize.X, sprite.size.Y * endSize.Y)); + spriteExtent = Math.Max(spriteExtent, Math.Max(sprite.size.X * endSize.X, sprite.size.Y * endSize.Y)); } } bounds = new Rectangle( - (int)Math.Min(bounds.X, endPosition.X - Prefab.DistanceMax - spriteExtent / 2), - (int)Math.Min(bounds.Y, endPosition.Y - Prefab.DistanceMax - spriteExtent / 2), - (int)Math.Max(bounds.X, endPosition.X + Prefab.DistanceMax + spriteExtent / 2), - (int)Math.Max(bounds.Y, endPosition.Y + Prefab.DistanceMax + spriteExtent / 2)); + (int)Math.Min(bounds.X, endPosition.X - Prefab.Properties.DistanceMax - spriteExtent / 2), + (int)Math.Min(bounds.Y, endPosition.Y - Prefab.Properties.DistanceMax - spriteExtent / 2), + (int)Math.Max(bounds.X, endPosition.X + Prefab.Properties.DistanceMax + spriteExtent / 2), + (int)Math.Max(bounds.Y, endPosition.Y + Prefab.Properties.DistanceMax + spriteExtent / 2)); } bounds = new Rectangle(bounds.X, bounds.Y, bounds.Width - bounds.X, bounds.Height - bounds.Y); @@ -121,9 +219,7 @@ namespace Barotrauma.Particles } class ParticleEmitterPrefab - { - public readonly string Name; - + { private string particlePrefabName; private ParticlePrefab particlePrefab; @@ -134,105 +230,30 @@ namespace Barotrauma.Particles if (particlePrefab == null && particlePrefabName != null) { particlePrefab = GameMain.ParticleManager?.FindPrefab(particlePrefabName); - if (particlePrefab == null) { particlePrefabName = null; } + if (particlePrefab == null) + { + DebugConsole.ThrowError($"Failed to find particle prefab \"{particlePrefabName}\"."); + particlePrefabName = null; + } } return particlePrefab; } } - public readonly float AngleMin, AngleMax; + public readonly ParticleEmitterProperties Properties; - public readonly float DistanceMin, DistanceMax; - - public readonly float VelocityMin, VelocityMax; - - public readonly float ScaleMin, ScaleMax; - public readonly Vector2 ScaleMultiplier; - - public readonly float EmitInterval; - public readonly int ParticleAmount; - - public readonly float ParticlesPerSecond; - - public readonly bool HighQualityCollisionDetection; - - public readonly bool CopyEntityAngle; - - public readonly Color ColorMultiplier; - - public bool DrawOnTop => forceDrawOnTop || ParticlePrefab.DrawOnTop; - private readonly bool forceDrawOnTop; + public bool DrawOnTop => Properties.DrawOnTop || ParticlePrefab.DrawOnTop; public ParticleEmitterPrefab(XElement element) { - Name = element.Name.ToString(); + Properties = new ParticleEmitterProperties(element); particlePrefabName = element.GetAttributeString("particle", ""); + } - if (element.Attribute("startrotation") == null) - { - AngleMin = element.GetAttributeFloat("anglemin", 0.0f); - AngleMax = element.GetAttributeFloat("anglemax", 0.0f); - } - else - { - AngleMin = element.GetAttributeFloat("angle", 0.0f); - AngleMax = AngleMin; - } - - AngleMin = MathHelper.ToRadians(MathHelper.Clamp(AngleMin, -360.0f, 360.0f)); - AngleMax = MathHelper.ToRadians(MathHelper.Clamp(AngleMax, -360.0f, 360.0f)); - - if (element.Attribute("scalemin") == null) - { - ScaleMin = 1.0f; - ScaleMax = 1.0f; - } - else - { - ScaleMin = element.GetAttributeFloat("scalemin", 1.0f); - ScaleMax = Math.Max(ScaleMin, element.GetAttributeFloat("scalemax", 1.0f)); - } - ScaleMultiplier = element.GetAttributeVector2("scalemultiplier", Vector2.One); - - if (element.Attribute("distance") == null) - { - DistanceMin = element.GetAttributeFloat("distancemin", 0.0f); - DistanceMax = element.GetAttributeFloat("distancemax", 0.0f); - } - else - { - DistanceMin = DistanceMax = element.GetAttributeFloat("distance", 0.0f); - } - if (DistanceMax < DistanceMin) - { - var temp = DistanceMin; - DistanceMin = DistanceMax; - DistanceMax = temp; - } - - if (element.Attribute("velocity") == null) - { - VelocityMin = element.GetAttributeFloat("velocitymin", 0.0f); - VelocityMax = element.GetAttributeFloat("velocitymax", 0.0f); - } - else - { - VelocityMin = VelocityMax = element.GetAttributeFloat("velocity", 0.0f); - } - if (VelocityMax < VelocityMin) - { - var temp = VelocityMin; - VelocityMin = VelocityMax; - VelocityMax = temp; - } - - EmitInterval = element.GetAttributeFloat("emitinterval", 0.0f); - ParticlesPerSecond = element.GetAttributeFloat("particlespersecond", 0); - ParticleAmount = element.GetAttributeInt("particleamount", 0); - HighQualityCollisionDetection = element.GetAttributeBool("highqualitycollisiondetection", false); - CopyEntityAngle = element.GetAttributeBool("copyentityangle", false); - forceDrawOnTop = element.GetAttributeBool("drawontop", false); - ColorMultiplier = element.GetAttributeColor("colormultiplier", Color.White); + public ParticleEmitterPrefab(ParticlePrefab prefab, ParticleEmitterProperties properties) + { + Properties = properties; + particlePrefab = prefab; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index 234b8fce8..945fc9afc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -51,9 +51,18 @@ namespace Barotrauma CreateUI(container); campaign.Map.OnLocationSelected += SelectLocation; - campaign.Map.OnMissionSelected += (connection, mission) => + campaign.Map.OnMissionsSelected += (connection, missions) => { - missionList?.Select(mission); + if (missionList?.Content != null) + { + foreach (GUIComponent missionElement in missionList.Content.Children) + { + if (missionElement.FindChild(c => c is GUITickBox, recursive: true) is GUITickBox tickBox) + { + tickBox.Selected = missions.Contains(tickBox.UserData as Mission); + } + } + } }; } @@ -436,6 +445,16 @@ namespace Barotrauma { Spacing = (int)(5 * GUI.yScale) }; + missionList.OnSelected = (GUIComponent selected, object userdata) => + { + var tickBox = selected.FindChild(c => c is GUITickBox, recursive: true) as GUITickBox; + if (GUI.MouseOn == tickBox) { return false; } + if (tickBox != null) + { + tickBox.Selected = !tickBox.Selected; + } + return true; + }; SelectedLevel = connection?.LevelData; Location currentDisplayLocation = Campaign.GetCurrentDisplayLocation(); @@ -444,9 +463,6 @@ namespace Barotrauma List availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).ToList(); if (!availableMissions.Contains(null)) { availableMissions.Insert(0, null); } - Mission selectedMission = currentDisplayLocation.SelectedMission != null && availableMissions.Contains(currentDisplayLocation.SelectedMission) ? - currentDisplayLocation.SelectedMission : null; - missionList.Content.ClearChildren(); foreach (Mission mission in availableMissions) @@ -458,53 +474,74 @@ namespace Barotrauma var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionPanel.RectTransform, Anchor.Center)) { Stretch = true, - CanBeFocused = true + CanBeFocused = true, + AbsoluteSpacing = GUI.IntScale(5) }; var missionName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission?.Name ?? TextManager.Get("NoMission"), font: GUI.SubHeadingFont, wrap: true); + // missionName.RectTransform.MinSize = new Point(0, (int)(missionName.Rect.Height * 1.5f)); if (mission != null) - { - if (MapGenerationParams.Instance?.MissionIcon != null) + { + var tickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) }, label: string.Empty) { - var icon = new GUIImage(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) }, - MapGenerationParams.Instance.MissionIcon, scaleToFit: true) + UserData = mission, + Selected = Campaign.Map.CurrentLocation?.SelectedMissions.Contains(mission) ?? false + }; + tickBox.RectTransform.MinSize = new Point(tickBox.Rect.Height, 0); + tickBox.RectTransform.IsFixedSize = true; + if (Campaign.AllowedToManageCampaign()) + { + tickBox.OnSelected += (GUITickBox tb) => { - Color = MapGenerationParams.Instance.IndicatorColor * 0.5f, - SelectedColor = MapGenerationParams.Instance.IndicatorColor, - HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f) - }; - icon.RectTransform.IsFixedSize = true; - - GUILayoutGroup difficultyIndicatorGroup = null; - if (mission.Difficulty.HasValue) - { - difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) }, - isHorizontal: true, childAnchor: Anchor.CenterRight) + if (tb.Selected) { - AbsoluteSpacing = 1, - UserData = "difficulty" - }; - var difficultyColor = mission.GetDifficultyColor(); - for (int i = 0; i < mission.Difficulty; i++) - { - new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true) - { - Color = difficultyColor * 0.5f, - SelectedColor = difficultyColor, - HoverColor = Color.Lerp(difficultyColor, Color.White, 0.5f) - }; + Campaign.Map.CurrentLocation.SelectMission(mission); } - } - - float extraPadding = 0.5f * icon.Rect.Width; - float extraZPadding = difficultyIndicatorGroup != null ? mission.Difficulty.Value * (difficultyIndicatorGroup.Children.First().Rect.Width + difficultyIndicatorGroup.AbsoluteSpacing) : 0; - missionName.Padding = new Vector4(missionName.Padding.X + icon.Rect.Width + extraPadding, - missionName.Padding.Y, - missionName.Padding.Z + extraZPadding + extraPadding, - missionName.Padding.W); - missionName.CalculateHeightFromText(); + else + { + Campaign.Map.CurrentLocation.DeselectMission(mission); + } + if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending && + Campaign.AllowedToManageCampaign()) + { + GameMain.Client?.SendCampaignState(); + } + return true; + }; } + GUILayoutGroup difficultyIndicatorGroup = null; + if (mission.Difficulty.HasValue) + { + difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) }, + isHorizontal: true, childAnchor: Anchor.CenterRight) + { + AbsoluteSpacing = 1, + UserData = "difficulty" + }; + var difficultyColor = mission.GetDifficultyColor(); + for (int i = 0; i < mission.Difficulty; i++) + { + new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true) + { + Color = difficultyColor, + SelectedColor = difficultyColor, + HoverColor = difficultyColor + }; + } + } + + float extraPadding = 0;// 0.8f * tickBox.Rect.Width; + float extraZPadding = difficultyIndicatorGroup != null ? mission.Difficulty.Value * (difficultyIndicatorGroup.Children.First().Rect.Width + difficultyIndicatorGroup.AbsoluteSpacing) : 0; + missionName.Padding = new Vector4(missionName.Padding.X + tickBox.Rect.Width * 1.2f + extraPadding, + missionName.Padding.Y, + missionName.Padding.Z + extraZPadding + extraPadding, + missionName.Padding.W); + missionName.CalculateHeightFromText(); + + //spacing + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform) { MinSize = new Point(0, GUI.IntScale(10)) }, style: null); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(Submarine.MainSub), wrap: true, parseRichText: true); string reputationText = mission.GetReputationRewardText(mission.Locations[0]); @@ -512,14 +549,14 @@ namespace Barotrauma new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.Description, wrap: true, parseRichText: true); } - missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(20)); + missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height + missionTextContent.AbsoluteSpacing) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(0)); foreach (GUIComponent child in missionTextContent.Children) { - var textBlock = child as GUITextBlock; - textBlock.Color = textBlock.SelectedColor = textBlock.HoverColor = Color.Transparent; - textBlock.SelectedTextColor = textBlock.TextColor; - textBlock.TextColor *= 0.5f; - textBlock.HoverTextColor = textBlock.TextColor; + if (child is GUITextBlock textBlock) + { + textBlock.Color = textBlock.SelectedColor = textBlock.HoverColor = Color.Transparent; + textBlock.SelectedTextColor = textBlock.HoverTextColor = textBlock.TextColor; + } } missionPanel.OnAddedToGUIUpdateList = (c) => { @@ -538,28 +575,10 @@ namespace Barotrauma }; } } - missionList.Select(selectedMission); if (prevSelectedLocation == selectedLocation) { missionList.BarScroll = prevMissionListScroll; } - - if (Campaign.AllowedToManageCampaign()) - { - missionList.OnSelected = (component, userdata) => - { - Mission mission = userdata as Mission; - if (Campaign.Map.CurrentLocation.SelectedMission == mission) { return false; } - Campaign.Map.CurrentLocation.SelectedMission = mission; - //RefreshMissionInfo(mission); - if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending && - Campaign.AllowedToManageCampaign()) - { - GameMain.Client?.SendCampaignState(); - } - return true; - }; - } } StartButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), content.RectTransform), @@ -567,7 +586,8 @@ namespace Barotrauma { OnClicked = (GUIButton btn, object obj) => { - if (missionList.Content.Children.Any(c => c.UserData is Mission) && !(missionList.SelectedData is Mission)) + if (missionList.Content.FindChild(c => c is GUITickBox tickBox && tickBox.Selected, recursive: true) == null && + missionList.Content.Children.Any(c => c.UserData is Mission)) { var noMissionVerification = new GUIMessageBox(string.Empty, TextManager.Get("nomissionprompt"), new string[] { TextManager.Get("yes"), TextManager.Get("no") }); noMissionVerification.Buttons[0].OnClicked = (btn, userdata) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 9bb7804e2..192630005 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -969,6 +969,16 @@ namespace Barotrauma UserData = missionType, }; + if (MissionPrefab.HiddenMissionClasses.Contains(missionType)) + { + missionTypeTickBoxes[index] = new GUITickBox(new RectTransform(Vector2.One, frame.RectTransform), string.Empty) + { + UserData = (int)missionType, + Visible = false, + CanBeFocused = false + }; + continue; + } missionTypeTickBoxes[index] = new GUITickBox(new RectTransform(Vector2.One, frame.RectTransform), TextManager.Get("MissionType." + missionType.ToString())) { @@ -3214,7 +3224,12 @@ namespace Barotrauma { for (int i = 0; i < missionTypeTickBoxes.Length; i++) { - MissionType missionType = (MissionType)((int)missionTypeTickBoxes[i].UserData); + MissionType missionType = (MissionType)(int)missionTypeTickBoxes[i].UserData; + if (MissionPrefab.HiddenMissionClasses.Contains(missionType)) + { + missionTypeTickBoxes[i].Parent.Visible = false; + continue; + } if (SelectedMode == GameModePreset.Mission) { missionTypeTickBoxes[i].Parent.Visible = MissionPrefab.CoOpMissionClasses.ContainsKey(missionType); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs index ef47d565e..9b778246a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs @@ -18,55 +18,6 @@ namespace Barotrauma { class ParticleEditorScreen : EditorScreen { - class Emitter : ISerializableEntity - { - public float EmitTimer; - - public float BurstTimer; - - [Editable, Serialize("0.0,360.0", false)] - public Vector2 AngleRange { get; private set; } - - [Editable, Serialize("0.0,0.0", false)] - public Vector2 VelocityRange { get; private set; } - - [Editable, Serialize("1.0,1.0", false)] - public Vector2 ScaleRange { get; private set; } - - [Editable, Serialize(0, false)] - public int ParticleBurstAmount { get; private set; } - - [Editable, Serialize(1.0f, false)] - public float ParticleBurstInterval { get; private set; } - - [Editable, Serialize(1.0f, false)] - public float ParticlesPerSecond { get; private set; } - - public string Name - { - get - { - return TextManager.Get("particleeditor.emitter"); - } - } - - public Dictionary SerializableProperties - { - get; - private set; - } - - public Emitter() - { - ScaleRange = Vector2.One; - AngleRange = new Vector2(0.0f, 360.0f); - ParticleBurstAmount = 1; - ParticleBurstInterval = 1.0f; - - SerializableProperties = SerializableProperty.GetProperties(this); - } - } - private GUIComponent rightPanel, leftPanel; private GUIListBox prefabList; private GUITextBox filterBox; @@ -74,7 +25,17 @@ namespace Barotrauma private ParticlePrefab selectedPrefab; - private Emitter emitter; + private readonly ParticleEmitterProperties emitterProperties = new ParticleEmitterProperties(null) + { + ScaleMax = 1f, + ScaleMin = 1f, + AngleMax = 360f, + AngleMin = 0, + ParticlesPerSecond = 1f + }; + + private ParticleEmitterPrefab emitterPrefab; + private ParticleEmitter emitter; private readonly Camera cam; @@ -128,8 +89,8 @@ namespace Barotrauma } }; - var serializeToClipBoardButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.03f), paddedRightPanel.RectTransform), - TextManager.Get("editor.copytoclipboard")) + new GUIButton(new RectTransform(new Vector2(1.0f, 0.03f), paddedRightPanel.RectTransform), + TextManager.Get("ParticleEditor.CopyPrefabToClipboard")) { OnClicked = (btn, obj) => { @@ -138,11 +99,18 @@ namespace Barotrauma } }; - emitter = new Emitter(); - var emitterEditorContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), paddedRightPanel.RectTransform), style: null); - var emitterEditor = new SerializableEntityEditor(emitterEditorContainer.RectTransform, emitter, false, true, elementHeight: 20, titleFont: GUI.SubHeadingFont); - emitterEditor.RectTransform.RelativeSize = Vector2.One; - emitterEditorContainer.RectTransform.Resize(new Point(emitterEditorContainer.RectTransform.NonScaledSize.X, emitterEditor.ContentHeight), false); + new GUIButton(new RectTransform(new Vector2(1.0f, 0.03f), paddedRightPanel.RectTransform), + TextManager.Get("ParticleEditor.CopyEmitterToClipboard")) + { + OnClicked = (btn, obj) => + { + SerializeEmitterToClipboard(); + return true; + } + }; + + var emitterListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.25f), paddedRightPanel.RectTransform)); + new SerializableEntityEditor(emitterListBox.Content.RectTransform, emitterProperties, false, true, elementHeight: 20, titleFont: GUI.SubHeadingFont); var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.6f), paddedRightPanel.RectTransform)); @@ -163,7 +131,10 @@ namespace Barotrauma prefabList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), paddedLeftPanel.RectTransform)); prefabList.OnSelected += (GUIComponent component, object obj) => { + cam.Position = Vector2.Zero; selectedPrefab = obj as ParticlePrefab; + emitterPrefab = new ParticleEmitterPrefab(selectedPrefab, emitterProperties); + emitter = new ParticleEmitter(emitterPrefab); listBox.ClearChildren(); new SerializableEntityEditor(listBox.Content.RectTransform, selectedPrefab, false, true, elementHeight: 20, titleFont: GUI.SubHeadingFont); //listBox.Content.RectTransform.NonScaledSize = particlePrefabEditor.RectTransform.NonScaledSize; @@ -204,19 +175,6 @@ namespace Barotrauma } } - private void Emit(Vector2 position) - { - float angle = MathHelper.ToRadians(Rand.Range(emitter.AngleRange.X, emitter.AngleRange.Y)); - Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Rand.Range(emitter.VelocityRange.X, emitter.VelocityRange.Y); - - var particle = GameMain.ParticleManager.CreateParticle(selectedPrefab, position, velocity, 0.0f); - - if (particle != null) - { - particle.Size *= Rand.Range(emitter.ScaleRange.X, emitter.ScaleRange.Y); - } - } - private void FilterEmitters(string text) { if (string.IsNullOrWhiteSpace(text)) @@ -269,9 +227,34 @@ namespace Barotrauma Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false; } + private void SerializeEmitterToClipboard() + { + XElement element = new XElement(nameof(ParticleEmitter)); + if (selectedPrefab is { } prefab) + { + element.Add(new XAttribute("particle", prefab.Identifier)); + } + + SerializableProperty.SerializeProperties(emitterProperties, element, saveIfDefault: false); + + StringBuilder sb = new StringBuilder(); + + System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings + { + OmitXmlDeclaration = true + }; + + using (var writer = System.Xml.XmlWriter.Create(sb, settings)) + { + element.WriteTo(writer); + writer.Flush(); + } + + Clipboard.SetText(sb.ToString()); + } + private void SerializeToClipboard(ParticlePrefab prefab) { -#if WINDOWS if (prefab == null) { return; } System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings @@ -314,7 +297,6 @@ namespace Barotrauma } Clipboard.SetText(sb.ToString()); -#endif } public override void Update(double deltaTime) @@ -331,31 +313,9 @@ namespace Barotrauma CreateContextMenu(); } - if (selectedPrefab != null) + if (selectedPrefab != null && emitter != null) { - emitter.EmitTimer += (float)deltaTime; - emitter.BurstTimer += (float)deltaTime; - - - if (emitter.ParticlesPerSecond > 0) - { - float emitInterval = 1.0f / emitter.ParticlesPerSecond; - while (emitter.EmitTimer > emitInterval) - { - Emit(Vector2.Zero); - emitter.EmitTimer -= emitInterval; - } - } - - if (emitter.BurstTimer > emitter.ParticleBurstInterval) - { - for (int i = 0; i < emitter.ParticleBurstAmount; i++) - { - Emit(Vector2.Zero); - } - emitter.BurstTimer = 0.0f; - } - + emitter.Emit((float) deltaTime, Vector2.Zero); } GameMain.ParticleManager.Update((float)deltaTime); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SpriteEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SpriteEditorScreen.cs index 4f77b1dff..13936faa4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SpriteEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SpriteEditorScreen.cs @@ -365,7 +365,8 @@ namespace Barotrauma "FlowerSprite", "DecorativeSprite", "BarrelSprite", - "RailSprite" + "RailSprite", + "SchematicSprite" }; foreach (string spriteElementName in spriteElementNames) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 7a9da1ca0..8f83974cd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -3200,6 +3200,8 @@ namespace Barotrauma Color newColor = SetColor(null); + if (!IsSubEditor()) { return true; } + Dictionary> oldProperties = new Dictionary>(); foreach (var (sEntity, color, _) in entities) @@ -3969,9 +3971,9 @@ namespace Barotrauma { loadFrame.AddToGUIUpdateList(); } - else if (saveFrame != null) + else { - saveFrame.AddToGUIUpdateList(); + saveFrame?.AddToGUIUpdateList(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index ebc8e2705..19a61d2a6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -273,8 +273,8 @@ namespace Barotrauma public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, bool inGame, bool showName, string style = "", int elementHeight = 24, ScalableFont titleFont = null) : this(parent, entity, inGame ? - SerializableProperty.GetProperties(entity).Union(SerializableProperty.GetProperties(entity).Where(p => p.GetAttribute()?.IsEditable() ?? false)) - : SerializableProperty.GetProperties(entity), showName, style, elementHeight, titleFont) + SerializableProperty.GetProperties(entity).Union(SerializableProperty.GetProperties(entity).Where(p => p.GetAttribute()?.IsEditable(entity) ?? false)) + : SerializableProperty.GetProperties(entity).Where(p => p.GetAttribute()?.IsEditable(entity) ?? true), showName, style, elementHeight, titleFont) { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index 57d79287a..d99335015 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -826,36 +826,58 @@ namespace Barotrauma IEnumerable suitableMusic = GetSuitableMusicClips(currentMusicType, currentIntensity); + int mainTrackIndex = 0; if (suitableMusic.Count() == 0) { - targetMusic[0] = null; + targetMusic[mainTrackIndex] = null; } //switch the music if nothing playing atm or the currently playing clip is not suitable anymore - else if (targetMusic[0] == null || currentMusic[0] == null || !currentMusic[0].IsPlaying() || !suitableMusic.Any(m => m.File == currentMusic[0].Filename)) + else if (targetMusic[mainTrackIndex] == null || currentMusic[mainTrackIndex] == null || !currentMusic[mainTrackIndex].IsPlaying() || !suitableMusic.Any(m => m.File == currentMusic[mainTrackIndex].Filename)) { if (currentMusicType == "default") { if (previousDefaultMusic == null) { - targetMusic[0] = previousDefaultMusic = suitableMusic.GetRandom(); + targetMusic[mainTrackIndex] = previousDefaultMusic = suitableMusic.GetRandom(); } else { - targetMusic[0] = previousDefaultMusic; + targetMusic[mainTrackIndex] = previousDefaultMusic; } } else { - targetMusic[0] = suitableMusic.GetRandom(); + targetMusic[mainTrackIndex] = suitableMusic.GetRandom(); } } - + + + if (Level.Loaded?.Type == LevelData.LevelType.LocationConnection) + { + // Find background noise loop for the current biome + IEnumerable suitableNoiseLoops = Screen.Selected == GameMain.GameScreen ? + GetSuitableMusicClips(Level.Loaded.LevelData?.Biome?.Identifier, currentIntensity) : + Enumerable.Empty(); + + int noiseLoopIndex = 1; + if (suitableNoiseLoops.Count() == 0) + { + targetMusic[noiseLoopIndex] = null; + } + // Switch the noise loop if nothing playing atm or the currently playing clip is not suitable anymore + else if (targetMusic[noiseLoopIndex] == null || currentMusic[noiseLoopIndex] == null || !suitableNoiseLoops.Any(m => m.File == currentMusic[noiseLoopIndex].Filename)) + { + targetMusic[noiseLoopIndex] = suitableNoiseLoops.GetRandom(); + } + } + //get the appropriate intensity layers for current situation IEnumerable suitableIntensityMusic = Screen.Selected == GameMain.GameScreen ? GetSuitableMusicClips("intensity", currentIntensity) : Enumerable.Empty(); - for (int i = 1; i < MaxMusicChannels; i++) + int intensityTrackStartIndex = 2; + for (int i = intensityTrackStartIndex; i < MaxMusicChannels; i++) { //disable targetmusics that aren't suitable anymore if (targetMusic[i] != null && !suitableIntensityMusic.Any(m => m.File == targetMusic[i].File)) @@ -869,7 +891,7 @@ namespace Barotrauma //already playing, do nothing if (targetMusic.Any(m => m != null && m.File == intensityMusic.File)) { continue; } - for (int i = 1; i < MaxMusicChannels; i++) + for (int i = intensityTrackStartIndex; i < MaxMusicChannels; i++) { if (targetMusic[i] == null) { @@ -877,7 +899,7 @@ namespace Barotrauma break; } } - } + } updateMusicTimer = UpdateMusicInterval; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/VideoSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/VideoSound.cs index d1a282e15..42422aefa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/VideoSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/VideoSound.cs @@ -114,10 +114,7 @@ namespace Barotrauma.Sounds { lock (mutex) { - if (soundChannel != null) - { - soundChannel.Dispose(); - } + soundChannel?.Dispose(); base.Dispose(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs index 2a86615d1..143a70d25 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs @@ -121,7 +121,7 @@ namespace Barotrauma } } - public void ReloadTexture(bool updateAllSprites = false) => ReloadTexture(updateAllSprites ? LoadedSprites.Where(s => s.Texture == texture) : new Sprite[] { this }); + public void ReloadTexture(bool updateAllSprites = false) => ReloadTexture(updateAllSprites ? LoadedSprites.Where(s => s.texture == texture).ToList() : new List() { this }); public void ReloadTexture(IEnumerable spritesToUpdate) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs index 7cd361e71..a65c33d7b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs @@ -55,8 +55,6 @@ namespace Barotrauma partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable targets, Hull hull, Vector2 worldPosition, bool playSound) { - if (entity == null) { return; } - if (playSound) { PlaySound(entity, hull, worldPosition); @@ -66,7 +64,7 @@ namespace Barotrauma { float angle = 0.0f; float particleRotation = 0.0f; - if (emitter.Prefab.CopyEntityAngle) + if (emitter.Prefab.Properties.CopyEntityAngle) { Limb targetLimb = null; if (entity is Item item && item.body != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs index a930107d0..3ed3228ac 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs @@ -215,15 +215,10 @@ namespace Barotrauma } else { - DebugConsole.NewMessage($"Could not compress a texture because the dimensions aren't a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})", Color.Orange); + DebugConsole.AddWarning($"Could not compress a texture because the dimensions aren't a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})"); } } - if (((width & 0x03) != 0) || ((height & 0x03) != 0)) - { - DebugConsole.AddWarning($"Cannot compress a texture because the dimensions are not a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})"); - } - Texture2D tex = null; CrossThread.RequestExecutionOnMainThread(() => { diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 70605fd5b..5ef085ced 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1400.0.0 + 0.1400.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index a97958ec4..edc13608f 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1400.0.0 + 0.1400.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index a3d90f8c0..3829e5364 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1400.0.0 + 0.1400.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 257e9dd48..0fe198237 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1400.0.0 + 0.1400.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 0817a9bb7..53c5c401f 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1400.0.0 + 0.1400.1.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 290eafdbd..151e71450 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -356,8 +356,12 @@ namespace Barotrauma int orderIndex = Order.PrefabList.IndexOf(orderPrefab); msg.WriteRangedInteger(orderIndex, 0, Order.PrefabList.Count); if (!orderPrefab.HasOptions) { break; } - int optionIndex = orderPrefab.Options.IndexOf(currentOrderInfo.Value.OrderOption); - msg.WriteRangedInteger(optionIndex, 0, orderPrefab.Options.Length); + int optionIndex = orderPrefab.AllOptions.IndexOf(currentOrderInfo.Value.OrderOption); + if (optionIndex == -1) + { + DebugConsole.AddWarning($"Error while writing order data. Order option \"{(currentOrderInfo.Value.OrderOption ?? null)}\" not found in the order prefab \"{orderPrefab.Name}\"."); + } + msg.WriteRangedInteger(optionIndex, -1, orderPrefab.AllOptions.Length); } else if (type == 2) { @@ -381,6 +385,13 @@ namespace Barotrauma break; case NetEntityEvent.Type.AddToCrew: msg.WriteRangedInteger(9, min, max); + msg.Write((byte)(CharacterTeamType)extraData[1]); // team id + ushort[] inventoryItemIDs = (ushort[])extraData[2]; + msg.Write((ushort)inventoryItemIDs.Length); + for (int i = 0; i < inventoryItemIDs.Length; i++) + { + msg.Write(inventoryItemIDs[i]); + } break; default: DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs index 8b872694f..7569c73c0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs @@ -1,5 +1,7 @@ using Barotrauma.Networking; +using System; using System.Collections.Generic; +using System.Linq; namespace Barotrauma { @@ -15,9 +17,45 @@ namespace Barotrauma private static readonly Dictionary lastActiveAction = new Dictionary(); private readonly HashSet targetClients = new HashSet(); + private readonly Dictionary ignoredClients = new Dictionary(); + public IEnumerable TargetClients { - get { return targetClients; } + get + { + UpdateIgnoredClients(); + return targetClients.Where(c => !ignoredClients.ContainsKey(c)); + } + } + + private void UpdateIgnoredClients() + { + if (ignoredClients.Any()) + { + HashSet clientsToRemove = null; + foreach (var k in ignoredClients.Keys) + { + if (ignoredClients[k] < DateTime.Now) + { + clientsToRemove ??= new HashSet(); + clientsToRemove.Add(k); + } + } + if (!(clientsToRemove is null)) + { + foreach (var k in clientsToRemove) + { + ignoredClients.Remove(k); + } + } + } + } + + public void IgnoreClient(Client c, float seconds) + { + if (!ignoredClients.ContainsKey(c)) { ignoredClients.Add(c, DateTime.Now); } + ignoredClients[c] = DateTime.Now + TimeSpan.FromSeconds(seconds); + Reset(); } private bool IsBlockedByAnotherConversation(IEnumerable targets) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs index 776cb20ed..7446dfd5d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs @@ -28,7 +28,14 @@ namespace Barotrauma continue; } - convAction.SelectedOption = selectedOption; + if (selectedOption == byte.MaxValue) + { + convAction.IgnoreClient(sender, 3f); + } + else + { + convAction.SelectedOption = selectedOption; + } return; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs index 73620bce3..e649ec598 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs @@ -1,16 +1,20 @@ using Barotrauma.Networking; using System; +using System.Collections.Generic; using System.Linq; namespace Barotrauma { partial class AbandonedOutpostMission : Mission { + private readonly List spawnedItems = new List(); + public override void ServerWriteInitial(IWriteMessage msg, Client c) { - if (characters.Count == 0) + msg.Write((ushort)spawnedItems.Count); + foreach (Item item in spawnedItems) { - throw new InvalidOperationException("Server attempted to write AbandonedOutpostMission data when no characters had been spawned."); + item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0); } msg.Write((byte)characters.Count); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs index 123f2e422..c7912eb2a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs @@ -21,7 +21,7 @@ namespace Barotrauma } } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { if (!initialized) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs index aaeff11f4..8720caf11 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs @@ -18,6 +18,7 @@ namespace Barotrauma foreach (Character character in characters) { character.WriteSpawnData(msg, character.ID, restrictMessageSize: false); + msg.Write(terroristCharacters.Contains(character)); List characterItems = characterDictionary[character]; // items must be written in a specific sequence so that child items aren't written before their parents msg.Write((ushort)characterItems.Count()); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs index afbd78dbe..dbbc96902 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs @@ -6,10 +6,12 @@ namespace Barotrauma { partial void ShowMessageProjSpecific(int missionState) { - if (missionState >= Headers.Count && missionState >= Messages.Count) return; + int messageIndex = missionState - 1; + if (messageIndex >= Headers.Count && messageIndex >= Messages.Count) { return; } + if (messageIndex < 0) { return; } - string header = missionState < Headers.Count ? Headers[missionState] : ""; - string message = missionState < Messages.Count ? Messages[missionState] : ""; + string header = messageIndex < Headers.Count ? Headers[messageIndex] : ""; + string message = messageIndex < Messages.Count ? Messages[messageIndex] : ""; GameServer.Log(TextManager.Get("MissionInfo") + ": " + header + " - " + message, ServerLog.MessageType.ServerMessage); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/OutpostDestroyMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/OutpostDestroyMission.cs deleted file mode 100644 index 1c7c667af..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/OutpostDestroyMission.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Barotrauma.Networking; -using System.Collections.Generic; - -namespace Barotrauma -{ - partial class OutpostDestroyMission : AbandonedOutpostMission - { - private readonly List spawnedItems = new List(); - public override void ServerWriteInitial(IWriteMessage msg, Client c) - { - base.ServerWriteInitial(msg, c); - msg.Write((ushort)spawnedItems.Count); - foreach (Item item in spawnedItems) - { - item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0); - } - } - } -} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 6abe50855..95b3ee358 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -35,6 +35,8 @@ namespace Barotrauma private set; } + public static Thread MainThread { get; private set; } + //only screens the server implements public static GameScreen GameScreen; public static NetLobbyScreen NetLobbyScreen; @@ -91,6 +93,8 @@ namespace Barotrauma Console.WriteLine("Initializing GameScreen"); GameScreen = new GameScreen(); + + MainThread = Thread.CurrentThread; } public void Init() @@ -388,6 +392,8 @@ namespace Barotrauma if (GameSettings.SaveDebugConsoleLogs) { DebugConsole.SaveLogs(); } if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); } + + MainThread = null; } public static void ResetFrameTime() diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 838bf1fbf..3c6b7c154 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -214,6 +214,7 @@ namespace Barotrauma c.CharacterHealth.Save(c.Info.HealthData); c.Info.InventoryData = new XElement("inventory"); c.SaveInventory(); + c.Info.SaveOrderData(); } c.Inventory.DeleteAllItems(); @@ -332,7 +333,7 @@ namespace Barotrauma CargoManager.OnSoldItemsChanged += () => { LastUpdateID++; }; UpgradeManager.OnUpgradesChanged += () => { LastUpdateID++; }; Map.OnLocationSelected += (loc, connection) => { LastUpdateID++; }; - Map.OnMissionSelected += (loc, mission) => { LastUpdateID++; }; + Map.OnMissionsSelected += (loc, mission) => { LastUpdateID++; }; Reputation.OnAnyReputationValueChanged += () => { LastUpdateID++; }; } //increment save ID so clients know they're lacking the most up-to-date save file @@ -431,8 +432,15 @@ namespace Barotrauma msg.Write(lastSaveID); msg.Write(map.Seed); msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); - msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); - msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex); + msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); + + var selectedMissionIndices = map.GetSelectedMissionIndices(); + msg.Write((byte)selectedMissionIndices.Count()); + foreach (int selectedMissionIndex in selectedMissionIndices) + { + msg.Write((byte)selectedMissionIndex); + } + msg.Write(map.AllowDebugTeleport); msg.Write(reputation != null); if (reputation != null) { msg.Write(reputation.Value); } @@ -535,7 +543,14 @@ namespace Barotrauma { UInt16 currentLocIndex = msg.ReadUInt16(); UInt16 selectedLocIndex = msg.ReadUInt16(); - byte selectedMissionIndex = msg.ReadByte(); + + byte selectedMissionCount = msg.ReadByte(); + List selectedMissionIndices = new List(); + for (int i = 0; i < selectedMissionCount; i++) + { + selectedMissionIndices.Add(msg.ReadByte()); + } + bool purchasedHullRepairs = msg.ReadBoolean(); bool purchasedItemRepairs = msg.ReadBoolean(); bool purchasedLostShuttles = msg.ReadBoolean(); @@ -663,7 +678,7 @@ namespace Barotrauma Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); if (Map.SelectedLocation == null) { Map.SelectRandomLocation(preferUndiscovered: true); } - if (Map.SelectedConnection != null) { Map.SelectMission(selectedMissionIndex); } + if (Map.SelectedConnection != null) { Map.SelectMission(selectedMissionIndices); } List currentBuyCrateItems = new List(CargoManager.ItemsInBuyCrate); currentBuyCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInBuyCrate(i.ItemPrefab, -i.Quantity)); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/UpgradeManager.cs index 58ac829aa..949f18657 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/UpgradeManager.cs @@ -20,33 +20,5 @@ namespace Barotrauma } } } - - /// - /// Sends a message to all clients telling them that all upgrades on the submarine were reset. - /// - /// - /// is supposed to have a list of reloaded metadata but seeing as - /// this method is currently only used when switching submarines and that disables the repair NPC - /// until the next round so currently there's no need for it as we get the new values from the save - /// file anyways. - /// - /// - private void SendUpgradeResetMessage(Dictionary newUpgrades) - { - foreach (Client c in GameMain.Server.ConnectedClients) - { - IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte)ServerPacketHeader.RESET_UPGRADES); - outmsg.Write(true); - outmsg.Write(Campaign.Money); - // outmsg.Write((uint)newUpgrades.Count); - // foreach (var (key, value) in newUpgrades) - // { - // outmsg.Write(key); - // outmsg.Write((byte)value); - // } - GameMain.Server?.ServerPeer?.Send(outmsg, c.Connection, DeliveryMethod.Reliable); - } - } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs index 61f312772..b66f144ff 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs @@ -41,7 +41,7 @@ namespace Barotrauma.Items.Components } //don't allow rewiring locked panels - if (Locked || !GameMain.NetworkMember.ServerSettings.AllowRewiring) { return; } + if (Locked || TemporarilyLocked || !GameMain.NetworkMember.ServerSettings.AllowRewiring) { return; } item.CreateServerEvent(this); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 8ed61ee28..f98d6a2b3 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -378,7 +378,7 @@ namespace Barotrauma.Networking if (gameStarted) { - if (respawnManager != null) { respawnManager.Update(deltaTime); } + respawnManager?.Update(deltaTime); entityEventManager.Update(connectedClients); @@ -406,10 +406,7 @@ namespace Barotrauma.Networking } } - if (TraitorManager != null) - { - TraitorManager.Update(deltaTime); - } + TraitorManager?.Update(deltaTime); if (serverSettings.Voting.VoteRunning) { @@ -433,7 +430,7 @@ namespace Barotrauma.Networking connectedClients.All(c => c.Character == null || c.Character.IsDead || c.Character.IsIncapacitated); bool subAtLevelEnd = false; - if (Submarine.MainSub != null && Submarine.MainSubs[1] == null) + if (Submarine.MainSub != null && !(GameMain.GameSession.GameMode is PvPMode)) { if (Level.Loaded?.EndOutpost != null) { @@ -488,8 +485,10 @@ namespace Barotrauma.Networking } else if (isCrewDead && (GameMain.GameSession?.GameMode is CampaignMode)) { +#if !DEBUG endRoundDelay = 1.0f; endRoundTimer += deltaTime; +#endif } else { @@ -3281,7 +3280,6 @@ namespace Barotrauma.Networking if (voteType != VoteType.PurchaseSub) { SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(targetSubmarine, deliveryFee); - GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(newSub, true); } serverSettings.Voting.StopSubmarineVote(true); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 1f2107de1..994d9190c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -306,7 +306,8 @@ namespace Barotrauma.Networking { if (Enum.TryParse(missionTypeName, out MissionType missionType)) { - if (missionType == Barotrauma.MissionType.None) continue; + if (missionType == Barotrauma.MissionType.None) { continue; } + if (MissionPrefab.HiddenMissionClasses.Contains(missionType)) { continue; } AllowedRandomMissionTypes.Add(missionType); } } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index cd505875a..2e5f61e9e 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1400.0.0 + 0.1400.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 1d8424be9..d95ba89e2 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -257,5 +257,7 @@ + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs b/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs index 234ae434e..060108b4b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs @@ -66,7 +66,7 @@ namespace Barotrauma private IEnumerable Update(ISpatialEntity targetEntity, Camera cam) { - if (targetEntity == null) { yield return CoroutineStatus.Success; } + if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; } prevControlled = Character.Controlled; if (RemoveControlFromCharacter) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 8b679c678..680b2ff77 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1475,7 +1475,9 @@ namespace Barotrauma Character thief = character; bool someoneSpoke = false; - if (item.SpawnedInOutpost && !item.AllowStealing && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag("handlocker")) + bool stolenItemsInside = item.OwnInventory?.FindAllItems(it => it.SpawnedInOutpost && !it.AllowStealing, recursive: true).Any() ?? false; + + if ((item.SpawnedInOutpost && !item.AllowStealing || stolenItemsInside) && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag("handlocker")) { foreach (Character otherCharacter in Character.CharacterList) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs index e2fa4e2b6..66376cc1d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -56,8 +56,23 @@ namespace Barotrauma public float BasePriority { get; set; } public float PriorityModifier { get; private set; } = 1; - // For forcing the highest priority temporarily. Will reset after each priority calculation, so it will need to be kept alive by something. - public bool ForceHighestPriority { get; set; } + private float resetPriorityTimer; + private readonly float resetPriorityTime = 1; + private bool _forceHighestPriority; + // For forcing the highest priority temporarily. Will reset automatically after one second, unless kept alive by something. + public bool ForceHighestPriority + { + get { return _forceHighestPriority; } + set + { + if (_forceHighestPriority == value) { return; } + _forceHighestPriority = value; + if (_forceHighestPriority) + { + resetPriorityTimer = resetPriorityTime; + } + } + } // For temporarily forcing walking. Will reset after each priority calculation, so it will need to be kept alive by something. // The intention of this boolean to allow walking even when the priority is higher than AIObjectiveManager.RunPriority. @@ -171,7 +186,6 @@ namespace Barotrauma Act(deltaTime); } - // TODO: check turret aioperate public void AddSubObjective(AIObjective objective, bool addFirst = false) { var type = objective.GetType(); @@ -294,6 +308,14 @@ namespace Barotrauma public virtual void Update(float deltaTime) { + if (resetPriorityTimer > 0) + { + resetPriorityTimer -= deltaTime; + } + else + { + ForceHighestPriority = false; + } if (!objectiveManager.IsOrder(this) && objectiveManager.WaitTimer <= 0) { UpdateDevotion(deltaTime); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs index 6e372d70e..763c92de9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -91,7 +91,7 @@ namespace Barotrauma protected override void Act(float deltaTime) { - if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI())) + if (container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI()) { Abandon = true; return; @@ -146,7 +146,10 @@ namespace Barotrauma { DialogueIdentifier = "dialogcannotreachtarget", TargetName = container.Item.Name, - AbortCondition = obj => !ItemToContain.IsOwnedBy(character), + AbortCondition = obj => + container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI() || + ItemToContain == null || ItemToContain.Removed || + !ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character, SpeakIfFails = !objectiveManager.IsCurrentOrder() }, onAbandon: () => Abandon = true, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 4df1b7213..6c868993d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -614,7 +614,11 @@ namespace Barotrauma /// /// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently. /// - public IEnumerable GetActiveObjectives() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T); + public IEnumerable GetActiveObjectives() where T : AIObjective + { + if (CurrentObjective == null) { return Enumerable.Empty(); } + return CurrentObjective.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T); + } public bool HasActiveObjective() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 677d4666f..2ce8cd4c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -46,7 +46,7 @@ namespace Barotrauma Abandon = !isOrder; return Priority; } - if (component.Item.ConditionPercentage <= 0) + if (!isOrder && component.Item.ConditionPercentage <= 0) { Priority = 0; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs index 216df0a91..70699139c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -1,6 +1,7 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; using System.Linq; using Barotrauma.Extensions; @@ -43,7 +44,6 @@ namespace Barotrauma } return Priority; } - // TODO: priority list? // Ignore items that are being repaired by someone else. if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { @@ -66,7 +66,20 @@ namespace Barotrauma float devotion = (CumulatedDevotion + selectedBonus) / 100; float reduction = isPriority ? 1 : isSelected ? 2 : 3; float max = AIObjectiveManager.LowestOrderPriority - reduction; - Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1)); + float highestWeight = -1; + foreach (string tag in Item.Prefab.Tags) + { + if (JobPrefab.ItemRepairPriorities.TryGetValue(tag, out float weight) && weight > highestWeight) + { + highestWeight = weight; + } + } + if (highestWeight == -1) + { + // Predefined weight not found. + highestWeight = 1; + } + Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * highestWeight * PriorityModifier), 0, 1)); } return Priority; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index 4fcfbfd83..c5331d79d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -150,6 +150,8 @@ namespace Barotrauma //legacy support public readonly string[] AppropriateJobs; public readonly string[] Options; + public readonly string[] HiddenOptions; + public readonly string[] AllOptions; private readonly Dictionary OptionNames; public readonly Dictionary OptionSprites; @@ -310,6 +312,8 @@ namespace Barotrauma TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false); AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]); Options = orderElement.GetAttributeStringArray("options", new string[0]); + HiddenOptions = orderElement.GetAttributeStringArray("hiddenoptions", new string[0]); + AllOptions = Options.Concat(HiddenOptions).ToArray(); var category = orderElement.GetAttributeString("category", null); if (!string.IsNullOrWhiteSpace(category)) { this.Category = (OrderCategory)Enum.Parse(typeof(OrderCategory), category, true); } MustSetTarget = orderElement.GetAttributeBool("mustsettarget", false); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs index e61e4c082..07104f091 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs @@ -400,7 +400,7 @@ namespace Barotrauma spawnPos = spawnPoint?.WorldPosition ?? Submarine.MainSub.WorldPosition; } var pet = Character.Create(speciesName, spawnPos, seed); - var petBehavior = (pet.AIController as EnemyAIController)?.PetBehavior; + var petBehavior = (pet?.AIController as EnemyAIController)?.PetBehavior; if (petBehavior != null) { petBehavior.Owner = owner; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorker.cs index 43b76bdc2..8e4a6d4e4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorker.cs @@ -113,7 +113,7 @@ namespace Barotrauma if (CurrentOrder != null && OrderedCharacter.GetCurrentOrderWithTopPriority()?.Order != CurrentOrder) { #if DEBUG - DebugConsole.NewMessage($"Order {CurrentOrder.Name} did not match current order for character {OrderedCharacter} in {this}"); + ShipCommandManager.ShipCommandLog($"Order {CurrentOrder.Name} did not match current order for character {OrderedCharacter} in {this}"); #endif return false; } @@ -121,7 +121,7 @@ namespace Barotrauma if (!shipCommandManager.AbleToTakeOrder(OrderedCharacter)) { #if DEBUG - DebugConsole.NewMessage(OrderedCharacter + " was unable to perform assigned order in " + this); + ShipCommandManager.ShipCommandLog(OrderedCharacter + " was unable to perform assigned order in " + this); #endif return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommandManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommandManager.cs index 07c079bfe..c85cf7885 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommandManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommandManager.cs @@ -93,7 +93,7 @@ namespace Barotrauma } } - static void ShipCommandLog(string text) + public static void ShipCommandLog(string text) { if (GameSettings.VerboseLogging) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index eae42212a..e11d69838 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -74,7 +74,7 @@ namespace Barotrauma } } - public bool HasMultipleLimbsOfSameType => Limbs.Length > limbDictionary.Count; + public bool HasMultipleLimbsOfSameType => limbs == null ? false : Limbs.Length > limbDictionary.Count; private bool frozen; public bool Frozen @@ -416,10 +416,7 @@ namespace Barotrauma protected void CreateColliders() { - if (collider != null) - { - collider.ForEach(c => c.Remove()); - } + collider?.ForEach(c => c.Remove()); DebugConsole.Log($"Creating colliders from {RagdollParams.Name}."); collider = new List(); foreach (var cParams in RagdollParams.Colliders) @@ -479,10 +476,7 @@ namespace Barotrauma protected void CreateLimbs() { - if (limbs != null) - { - limbs.ForEach(l => l.Remove()); - } + limbs?.ForEach(l => l.Remove()); DebugConsole.Log($"Creating limbs from {RagdollParams.Name}."); limbDictionary = new Dictionary(); limbs = new Limb[RagdollParams.Limbs.Count]; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs index e5ee2ce1a..b50cda9b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs @@ -395,14 +395,12 @@ namespace Barotrauma Affliction affliction; string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant(); afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, System.StringComparison.OrdinalIgnoreCase)); - if (afflictionPrefab != null) + if (afflictionPrefab == null) { - affliction = afflictionPrefab.Instantiate(0.0f); - } - else - { - affliction = new Affliction(null, 0); + DebugConsole.ThrowError($"Couldn't find the affliction with the identifier {afflictionIdentifier} referenced in {element.Document.ParseContentPathFromUri()}"); + continue; } + affliction = afflictionPrefab.Instantiate(0.0f); affliction.Deserialize(subElement); //backwards compatibility if (subElement.Attribute("amount") != null && subElement.Attribute("strength") == null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 4ecae7339..9e2dff4e5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -1523,6 +1523,16 @@ namespace Barotrauma greatestNegativeHealthMultiplier = 1f; } + /// + /// Can be used to modify a character's health for runtime session. Change with AddHealthMultiplier + /// + public float StaticHealthMultiplier { get; private set; } = 1; + + public void AddStaticHealthMultiplier(float newMultiplier) + { + StaticHealthMultiplier *= newMultiplier; + } + /// /// Speed reduction from the current limb specific damage. Min 0, max 1. /// @@ -1916,19 +1926,19 @@ namespace Barotrauma return AnimController.GetLimb(LimbType.Head) ?? AnimController.GetLimb(LimbType.Torso) ?? AnimController.MainLimb; } - public bool CanSeeTarget(ISpatialEntity target, Limb seeingLimb = null) + public bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity = null) { - seeingLimb ??= GetSeeingLimb(); - if (seeingLimb == null) { return false; } - ISpatialEntity seeingEntity = AnimController.SimplePhysicsEnabled ? this : seeingLimb as ISpatialEntity; + seeingEntity ??= AnimController.SimplePhysicsEnabled ? this as ISpatialEntity : GetSeeingLimb() as ISpatialEntity; + if (seeingEntity == null) { return false; } + ISpatialEntity sourceEntity = seeingEntity ; // TODO: Could we just use the method below? If not, let's refactor it so that we can. - Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - seeingEntity.WorldPosition); + Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - sourceEntity.WorldPosition); Body closestBody; //both inside the same sub (or both outside) //OR the we're inside, the other character outside if (target.Submarine == Submarine || target.Submarine == null) { - closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff); + closestBody = Submarine.CheckVisibility(sourceEntity.SimPosition, sourceEntity.SimPosition + diff); } //we're outside, the other character inside else if (Submarine == null) @@ -1938,7 +1948,7 @@ namespace Barotrauma //both inside different subs else { - closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff); + closestBody = Submarine.CheckVisibility(sourceEntity.SimPosition, sourceEntity.SimPosition + diff); if (!IsBlocking(closestBody)) { closestBody = Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff); @@ -1966,29 +1976,6 @@ namespace Barotrauma } } - /// - /// TODO: ensure that works. CheckVisibility takes positions in sim space, but this method uses world positions - /// - public bool CanSeeCharacter(Character target, Vector2 sourceWorldPos) - { - Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - sourceWorldPos); - Body closestBody; - if (target.Submarine == null) - { - closestBody = Submarine.CheckVisibility(sourceWorldPos, sourceWorldPos + diff); - if (closestBody == null) { return true; } - } - else - { - closestBody = Submarine.CheckVisibility(target.WorldPosition, target.WorldPosition - diff); - if (closestBody == null) { return true; } - } - Structure wall = closestBody.UserData as Structure; - Item item = closestBody.UserData as Item; - Door door = item?.GetComponent(); - return (wall == null || !wall.CastShadow) && (door == null || door.CanBeTraversed); - } - /// /// A simple check if the character Dir is towards the target or not. Uses the world coordinates. /// @@ -2233,7 +2220,10 @@ namespace Barotrauma Rectangle itemDisplayRect = new Rectangle(item.InteractionRect.X, item.InteractionRect.Y - item.InteractionRect.Height, item.InteractionRect.Width, item.InteractionRect.Height); // Get the point along the line between lowerBodyPosition and upperBodyPosition which is closest to the center of itemDisplayRect - Vector2 playerDistanceCheckPosition = Vector2.Clamp(itemDisplayRect.Center.ToVector2(), lowerBodyPosition, upperBodyPosition); + Vector2 playerDistanceCheckPosition = + lowerBodyPosition.Y < upperBodyPosition.Y ? + Vector2.Clamp(itemDisplayRect.Center.ToVector2(), lowerBodyPosition, upperBodyPosition) : + Vector2.Clamp(itemDisplayRect.Center.ToVector2(), upperBodyPosition, lowerBodyPosition); // If playerDistanceCheckPosition is inside the itemDisplayRect then we consider the character to within 0 distance of the item if (itemDisplayRect.Contains(playerDistanceCheckPosition)) @@ -2271,7 +2261,7 @@ namespace Barotrauma itemPosition -= Submarine.SimPosition; } var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true); - if (body != null && body.UserData as Item != item) { return false; } + if (body != null && body.UserData as Item != item && Submarine.LastPickedFixture?.UserData as Item != item) { return false; } } return true; @@ -3657,7 +3647,11 @@ namespace Barotrauma // OnDamaged is called only for the limb that is hit. AnimController.Limbs.ForEach(l => l.ApplyStatusEffects(actionType, deltaTime)); } - CharacterHealth.ApplyAfflictionStatusEffects(actionType); + //OnActive effects are handled by the afflictions themselves + if (actionType != ActionType.OnActive) + { + CharacterHealth.ApplyAfflictionStatusEffects(actionType); + } } private void Implode(bool isNetworkMessage = false) @@ -3801,10 +3795,7 @@ namespace Barotrauma return; } - if (aiTarget != null) - { - aiTarget.Remove(); - } + aiTarget?.Remove(); aiTarget = new AITarget(this); CharacterHealth.RemoveAllAfflictions(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 2c2530c61..14e7f34c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -456,7 +456,7 @@ namespace Barotrauma public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1; // Used for creating the data - public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced) + public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced, string npcIdentifier = "") { if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) { @@ -485,8 +485,12 @@ namespace Barotrauma { Name = name; } - else + else if (!string.IsNullOrEmpty(npcIdentifier) && TextManager.Get("npctitle." + npcIdentifier, true) is string npcTitle) { + Name = npcTitle; + } + else + { name = ""; if (CharacterConfigElement.Element("name") != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 300272bb4..02a6775f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -150,12 +150,9 @@ namespace Barotrauma { max += Character.Info.Job.Prefab.VitalityModifier; } + max *= Character.StaticHealthMultiplier; return max * Character.HealthMultiplier; } - set - { - maxVitality = Math.Max(0, value); - } } public float MinVitality diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index 5d8d3c27d..81885bd92 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -121,11 +121,12 @@ namespace Barotrauma public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null) { - npc.CharacterHealth.MaxVitality *= HealthMultiplier; + npc.AddStaticHealthMultiplier(HealthMultiplier); if (GameMain.NetworkMember != null) { - npc.CharacterHealth.MaxVitality *= HealthMultiplierInMultiplayer; + npc.AddStaticHealthMultiplier(HealthMultiplierInMultiplayer); } + var humanAI = npc.AIController as HumanAIController; if (humanAI != null) { @@ -227,10 +228,7 @@ namespace Barotrauma } IdCard idCardComponent = item.GetComponent(); - if (idCardComponent != null) - { - idCardComponent.Initialize(character.Info); - } + idCardComponent?.Initialize(character.Info); var idCardTags = itemElement.GetAttributeStringArray("tags", new string[0]); foreach (string tag in idCardTags) @@ -243,10 +241,7 @@ namespace Barotrauma { wifiComponent.TeamID = character.TeamID; } - if (parentItem != null) - { - parentItem.Combine(item, user: null); - } + parentItem?.Combine(item, user: null); foreach (XElement childItemElement in itemElement.Elements()) { InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs index e5591632d..d8cd67162 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs @@ -204,10 +204,7 @@ namespace Barotrauma item.AddTag("job:" + Name); IdCard idCardComponent = item.GetComponent(); - if (idCardComponent != null) - { - idCardComponent.Initialize(character.Info); - } + idCardComponent?.Initialize(character.Info); } foreach (WifiComponent wifiComponent in item.GetComponents()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index 54235252f..86271a9c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs @@ -1,9 +1,9 @@ -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Xml.Linq; -using Barotrauma.Extensions; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; using System.Linq; +using System.Xml.Linq; namespace Barotrauma { @@ -43,6 +43,12 @@ namespace Barotrauma Prefabs.Remove(this); } + private static readonly Dictionary _itemRepairPriorities = new Dictionary(); + /// + /// Tag -> priority. + /// + public static IReadOnlyDictionary ItemRepairPriorities => _itemRepairPriorities; + public static XElement NoJobElement; public static JobPrefab Get(string identifier) { @@ -294,7 +300,7 @@ namespace Barotrauma } foreach (XElement element in mainElement.Elements()) { - if (element.Name.ToString().Equals("nojob", StringComparison.OrdinalIgnoreCase)) { continue; } + if (!element.Name.ToString().Equals("job", StringComparison.OrdinalIgnoreCase)) { continue; } if (element.IsOverride()) { var job = new JobPrefab(element.FirstElement(), file.Path) @@ -312,8 +318,31 @@ namespace Barotrauma Prefabs.Add(job, false); } } - NoJobElement = NoJobElement ?? mainElement.Element("NoJob"); - NoJobElement = NoJobElement ?? mainElement.Element("nojob"); + NoJobElement ??= mainElement.GetChildElement("nojob"); + var itemRepairPrioritiesElement = mainElement.GetChildElement("ItemRepairPriorities"); + if (itemRepairPrioritiesElement != null) + { + foreach (var subElement in itemRepairPrioritiesElement.Elements()) + { + string tag = subElement.GetAttributeString("tag", null); + if (tag != null) + { + float priority = subElement.GetAttributeFloat("priority", -1f); + if (priority >= 0) + { + _itemRepairPriorities.TryAdd(tag, priority); + } + else + { + DebugConsole.AddWarning($"The 'priority' attribute is missing from the the item repair priorities definition in {subElement} of {file.Path}."); + } + } + else + { + DebugConsole.AddWarning($"The 'tag' attribute is missing from the the item repair priorities definition in {subElement} of {file.Path}."); + } + } + } } public static void RemoveByFile(string filePath) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index cb841480c..92087ad79 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -837,10 +837,7 @@ namespace Barotrauma } } - if (attack != null) - { - attack.UpdateCoolDown(deltaTime); - } + attack?.UpdateCoolDown(deltaTime); } private float reEnableTimer = -1; diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs index 2fc071d3c..dbfb4cd19 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs @@ -194,13 +194,13 @@ namespace Barotrauma isCorePackage = value; if (isCorePackage && regularPackages.Contains(this)) { - corePackages.Add(this); - regularPackages.Remove(this); + corePackages.AddOnMainThread(this); + regularPackages.RemoveOnMainThread(this); } else if (!isCorePackage && corePackages.Contains(this)) { - regularPackages.Add(this); - corePackages.Remove(this); + regularPackages.AddOnMainThread(this); + corePackages.RemoveOnMainThread(this); } } } @@ -411,6 +411,7 @@ namespace Barotrauma case ContentType.Submarine: case ContentType.Wreck: case ContentType.BeaconStation: + case ContentType.EnemySubmarine: break; default: try @@ -528,7 +529,7 @@ namespace Barotrauma { refreshFiles = true; } - corePackages.Remove(p); + corePackages.RemoveOnMainThread(p); } else { @@ -536,16 +537,16 @@ namespace Barotrauma { refreshFiles = true; } - regularPackages.Remove(p); + regularPackages.RemoveOnMainThread(p); } } if (IsCorePackage) { - corePackages.Add(this); + corePackages.AddOnMainThread(this); } else { - regularPackages.Add(this); + regularPackages.AddOnMainThread(this); } if (refreshFiles) @@ -743,18 +744,18 @@ namespace Barotrauma } if (newPackage.IsCorePackage) { - corePackages.Add(newPackage); + corePackages.AddOnMainThread(newPackage); } else { - regularPackages.Add(newPackage); + regularPackages.AddOnMainThread(newPackage); } } public static void RemovePackage(ContentPackage package) { - if (package.IsCorePackage) { corePackages.Remove(package); } - else { regularPackages.Remove(package); } + if (package.IsCorePackage) { corePackages.RemoveOnMainThread(package); } + else { regularPackages.RemoveOnMainThread(package); } } public static void LoadAll() @@ -775,9 +776,9 @@ namespace Barotrauma IEnumerable files = Directory.GetFiles(folder, "*.xml"); - corePackages.Clear(); + corePackages.ClearOnMainThread(); var prevRegularPackages = regularPackages.Select(p => p.Name.ToLowerInvariant()).ToList(); - regularPackages.Clear(); + regularPackages.ClearOnMainThread(); foreach (string filePath in files) { @@ -815,7 +816,7 @@ namespace Barotrauma .OrderBy(p => order(p)) .ThenBy(p => regularPackages.IndexOf(p)) .ToList(); - regularPackages.Clear(); regularPackages.AddRange(ordered); + regularPackages.ClearOnMainThread(); regularPackages.AddRangeOnMainThread(ordered); (config ?? GameMain.Config)?.SortContentPackages(refreshAll); } @@ -825,12 +826,12 @@ namespace Barotrauma { if (IsCorePackage) { - corePackages.Remove(this); + corePackages.RemoveOnMainThread(this); if (GameMain.Config.CurrentCorePackage == this) { GameMain.Config.AutoSelectCorePackage(null); } } else { - regularPackages.Remove(this); + regularPackages.RemoveOnMainThread(this); if (GameMain.Config.EnabledRegularPackages.Contains(this)) { GameMain.Config.DisableRegularPackage(this); } } GameMain.Config.SaveNewPlayerConfig(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs index 8d00c9b7e..135312e97 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs @@ -303,9 +303,15 @@ namespace Barotrauma private bool IsValidTarget(Entity e) { - return - e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated && + bool isValid = e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated && (e == Character.Controlled || character.IsRemotePlayer); +#if SERVER + UpdateIgnoredClients(); + isValid &= !ignoredClients.Keys.Any(c => c.Character == e); +#elif CLIENT + isValid &= (e != Character.Controlled || !GUI.InputBlockingMenuOpen); +#endif + return isValid; } private void TryStartConversation(Character speaker, Character targetCharacter = null) @@ -348,7 +354,7 @@ namespace Barotrauma { ParentEvent.AddTarget(InvokerTag, targetCharacter); } - + ShowDialog(speaker, targetCharacter); dialogOpened = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs index 9fc395d0a..301ca0f74 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs @@ -14,6 +14,18 @@ namespace Barotrauma [Serialize("", true)] public string MissionTag { get; set; } + [Serialize("", true, description: "The type of the location the mission will be unlocked in (if empty, any location can be selected).")] + public string LocationType { get; set; } + + [Serialize(0, true, description: "Minimum distance to the location the mission is unlocked in (1 = one path between locations).")] + public int MinLocationDistance { get; set; } + + [Serialize(true, true, description: "If true, the mission has to be unlocked in a location further on the campaign map.")] + public bool UnlockFurtherOnMap { get; set; } + + [Serialize(false, true, description: "If true, a suitable location is forced on the map if one isn't found.")] + public bool CreateLocationIfNotFound { get; set; } + private bool isFinished; public MissionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) @@ -44,33 +56,82 @@ namespace Barotrauma if (GameMain.GameSession.GameMode is CampaignMode campaign) { MissionPrefab prefab = null; - if (!string.IsNullOrEmpty(MissionIdentifier)) + var unlockLocation = FindUnlockLocation(); + if (unlockLocation == null && CreateLocationIfNotFound) { - prefab = campaign.Map.CurrentLocation.UnlockMissionByIdentifier(MissionIdentifier); - } - else if (!string.IsNullOrEmpty(MissionTag)) - { - prefab = campaign.Map.CurrentLocation.UnlockMissionByTag(MissionTag); - } - if (campaign is MultiPlayerCampaign mpCampaign) - { - mpCampaign.LastUpdateID++; + //find an empty location at least 3 steps away, further on the map + var emptyLocation = FindUnlockLocationRecursive(campaign.Map.CurrentLocation, Math.Max(MinLocationDistance, 3), "none", true, new HashSet()); + if (emptyLocation != null) + { + emptyLocation.ChangeType(Barotrauma.LocationType.List.Find(lt => lt.Identifier.Equals(LocationType, StringComparison.OrdinalIgnoreCase))); + unlockLocation = emptyLocation; + } } - if (prefab != null) + if (unlockLocation != null) { -#if CLIENT - new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name), - new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128)) + if (!string.IsNullOrEmpty(MissionIdentifier)) { - IconColor = prefab.IconColor - }; -#else - NotifyMissionUnlock(prefab); -#endif + prefab = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier); + } + else if (!string.IsNullOrEmpty(MissionTag)) + { + prefab = unlockLocation.UnlockMissionByTag(MissionTag); + } + if (campaign is MultiPlayerCampaign mpCampaign) + { + mpCampaign.LastUpdateID++; + } + if (prefab != null) + { + DebugConsole.NewMessage($"Unlocked mission \"{prefab.Name}\" in the location \"{unlockLocation.Name}\"."); + #if CLIENT + new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name), + new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128)) + { + IconColor = prefab.IconColor + }; + #else + NotifyMissionUnlock(prefab); + #endif + } + } + else + { + DebugConsole.AddWarning($"Failed to find a suitable location to unlock a mission in (LocationType: {LocationType}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})"); } } - isFinished = true; + isFinished = true; + } + + private Location FindUnlockLocation() + { + var campaign = GameMain.GameSession.GameMode as CampaignMode; + if (string.IsNullOrEmpty(LocationType) && MinLocationDistance <= 1) + { + return campaign.Map.CurrentLocation; + } + + return FindUnlockLocationRecursive(campaign.Map.CurrentLocation, 0, LocationType, UnlockFurtherOnMap, new HashSet()); + } + + private Location FindUnlockLocationRecursive(Location currLocation, int currDistance, string locationType, bool unlockFurtherOnMap, HashSet checkedLocations) + { + var campaign = GameMain.GameSession.GameMode as CampaignMode; + if (currLocation.Type.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase) && currDistance >= MinLocationDistance && + (!unlockFurtherOnMap || currLocation.MapPosition.X > campaign.Map.CurrentLocation.MapPosition.X)) + { + return currLocation; + } + checkedLocations.Add(currLocation); + foreach (LocationConnection connection in currLocation.Connections) + { + var otherLocation = connection.OtherLocation(currLocation); + if (checkedLocations.Contains(otherLocation)) { continue; } + var unlockLocation = FindUnlockLocationRecursive(otherLocation, ++currDistance, locationType, unlockFurtherOnMap, checkedLocations); + if (unlockLocation != null) { return unlockLocation; } + } + return null; } public override string ToDebugString() @@ -84,8 +145,8 @@ namespace Barotrauma foreach (Client client in GameMain.Server.ConnectedClients) { IWriteMessage outmsg = new WriteOnlyMessage(); - outmsg.Write((byte) ServerPacketHeader.EVENTACTION); - outmsg.Write((byte) EventManager.NetworkEventType.MISSION); + outmsg.Write((byte)ServerPacketHeader.EVENTACTION); + outmsg.Write((byte)EventManager.NetworkEventType.MISSION); outmsg.Write(prefab.Identifier); GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs index b5ba1cfb0..f72877974 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs @@ -41,9 +41,14 @@ namespace Barotrauma foreach (Item item in npc.Inventory.AllItems) { item.AllowStealing = true; + var wifiComponent = item.GetComponent(); + if (wifiComponent != null) + { + wifiComponent.TeamID = newTeam; + } } #if SERVER - GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew }); + GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, newTeam, npc.Inventory.AllItems.Select(it => it.ID).ToArray() }); #endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index 14105db67..d78d9d2ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -15,7 +15,8 @@ namespace Barotrauma Outpost, MainPath, Ruin, - Wreck + Wreck, + BeaconStation } [Serialize("", true, description: "Species name of the character to spawn.")] @@ -225,6 +226,7 @@ namespace Barotrauma SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsOutpost), SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsWreck), SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.ParentRuin != null), + SpawnLocationType.BeaconStation => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsBeacon), _ => throw new NotImplementedException() }; @@ -250,6 +252,7 @@ namespace Barotrauma SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost), SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsWreck), SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.ParentRuin != null), + SpawnLocationType.BeaconStation => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsBeacon), _ => throw new NotImplementedException() }; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/StatusEffectAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/StatusEffectAction.cs index 91244267e..8a3630247 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/StatusEffectAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/StatusEffectAction.cs @@ -51,7 +51,14 @@ namespace Barotrauma { foreach (var target in targets) { - effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity); + if (target is Item targetItem) + { + effect.Apply(effect.type, deltaTime, target, targetItem.AllPropertyObjects); + } + else + { + effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity); + } } } #if SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs index f19069422..22d5bc143 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs @@ -124,7 +124,7 @@ namespace Barotrauma npc.CampaignInteractionType = CampaignMode.InteractionType.Examine; #if CLIENT npc.SetCustomInteract( - Trigger, + (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } }, TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use))); #else npc.SetCustomInteract( diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 5d753e12d..da945d940 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -107,7 +107,7 @@ namespace Barotrauma totalPathLength = 0.0f; if (level != null) { - var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(Level.Loaded.StartPosition), ConvertUnits.ToSimUnits(Level.Loaded.EndPosition)); + var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(level.StartPosition), ConvertUnits.ToSimUnits(level.EndPosition)); totalPathLength = steeringPath.TotalLength; } @@ -124,7 +124,7 @@ namespace Barotrauma } MTRandom rand = new MTRandom(seed); - var initialEventSet = SelectRandomEvents(EventSet.List); + var initialEventSet = SelectRandomEvents(EventSet.List, rand); if (initialEventSet != null) { pendingEventSets.Add(initialEventSet); @@ -386,21 +386,25 @@ namespace Barotrauma { if (level == null) { return; } if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; } - +#if DEBUG + DebugConsole.NewMessage($"Loading event set {eventSet.DebugIdentifier}", Color.LightBlue); +#else + DebugConsole.Log($"Loading event set {eventSet.DebugIdentifier}"); +#endif int applyCount = 1; List> spawnPosFilter = new List>(); if (eventSet.PerRuin) { - applyCount = Level.Loaded.Ruins.Count(); - foreach (var ruin in Level.Loaded.Ruins) + applyCount = level.Ruins.Count(); + foreach (var ruin in level.Ruins) { spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Ruin == ruin; }); } } else if (eventSet.PerCave) { - applyCount = Level.Loaded.Caves.Count(); - foreach (var cave in Level.Loaded.Caves) + applyCount = level.Caves.Count(); + foreach (var cave in level.Caves) { spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Cave == cave; }); } @@ -417,7 +421,8 @@ namespace Barotrauma var suitablePrefabs = eventSet.EventPrefabs.FindAll(e => string.IsNullOrEmpty(e.First.BiomeIdentifier) || - e.First.BiomeIdentifier.Equals(Level.Loaded.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase)); + e.First.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase)); + for (int i = 0; i < applyCount; i++) { if (eventSet.ChooseRandom) @@ -435,7 +440,11 @@ namespace Barotrauma if (newEvent == null) { continue; } newEvent.Init(true); if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; } - DebugConsole.Log("Initialized event " + newEvent.ToString()); +#if DEBUG + DebugConsole.NewMessage($"Initialized event {newEvent}"); +#else + DebugConsole.Log($"Initialized event {newEvent}"); +#endif if (!selectedEvents.ContainsKey(eventSet)) { selectedEvents.Add(eventSet, new List()); @@ -447,8 +456,11 @@ namespace Barotrauma } if (eventSet.ChildSets.Count > 0) { - var newEventSet = SelectRandomEvents(eventSet.ChildSets); - if (newEventSet != null) { CreateEvents(newEventSet, rand); } + var newEventSet = SelectRandomEvents(eventSet.ChildSets, rand); + if (newEventSet != null) + { + CreateEvents(newEventSet, rand); + } } } else @@ -458,7 +470,11 @@ namespace Barotrauma var newEvent = eventPrefab.First.CreateInstance(); if (newEvent == null) { continue; } newEvent.Init(true); - DebugConsole.Log("Initialized event " + newEvent.ToString()); +#if DEBUG + DebugConsole.NewMessage($"Initialized event {newEvent}"); +#else + DebugConsole.Log($"Initialized event {newEvent}"); +#endif if (!selectedEvents.ContainsKey(eventSet)) { selectedEvents.Add(eventSet, new List()); @@ -474,10 +490,10 @@ namespace Barotrauma } } - private EventSet SelectRandomEvents(List eventSets) + private EventSet SelectRandomEvents(List eventSets, Random random = null) { if (level == null) { return null; } - MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed)); + Random rand = random ?? new MTRandom(ToolBox.StringToInt(level.Seed)); var allowedEventSets = eventSets.Where(es => @@ -496,7 +512,8 @@ namespace Barotrauma } float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level)); - float randomNumber = (float)rand.NextDouble() * totalCommonness; + float randomNumber = (float)rand.NextDouble(); + randomNumber *= totalCommonness; foreach (EventSet eventSet in allowedEventSets) { float commonness = eventSet.GetCommonness(level); @@ -835,7 +852,7 @@ namespace Barotrauma { if (level == null) { return 0.0f; } var refEntity = GetRefEntity(); - Vector2 target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition); + Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition); var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target); if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength)) { @@ -953,15 +970,15 @@ namespace Barotrauma const int maxDist = 1000; - if (Level.Loaded != null) + if (level != null) { - foreach (var ruin in Level.Loaded.Ruins) + foreach (var ruin in level.Ruins) { Rectangle area = ruin.Area; area.Inflate(maxDist, maxDist); if (area.Contains(character.WorldPosition)) { return true; } } - foreach (var cave in Level.Loaded.Caves) + foreach (var cave in level.Caves) { Rectangle area = cave.Area; area.Inflate(maxDist, maxDist); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index 86ae02a2f..daa096af0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -1,4 +1,5 @@ using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -15,6 +16,10 @@ namespace Barotrauma protected readonly HashSet requireKill = new HashSet(); protected readonly HashSet requireRescue = new HashSet(); + private readonly string itemTag; + private readonly XElement itemConfig; + private readonly List items = new List(); + protected const int HostagesKilledState = 5; private readonly string hostagesKilledMessage; @@ -33,6 +38,43 @@ namespace Barotrauma } } + public override IEnumerable SonarPositions + { + get + { + if (State > 0) + { + return Enumerable.Empty(); + } + else + { + return Targets.Select(t => t.WorldPosition); + } + } + } + + private IEnumerable Targets + { + get + { + if (State > 0) + { + return Enumerable.Empty(); + } + else + { + if (items.Any()) + { + return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast().Concat(requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(requireRescue); + } + else + { + return requireKill.Concat(requireRescue); + } + } + } + } + protected bool wasDocked; public AbandonedOutpostMission(MissionPrefab prefab, Location[] locations, Submarine sub) : @@ -42,6 +84,9 @@ namespace Barotrauma string msgTag = prefab.ConfigElement.GetAttributeString("hostageskilledmessage", ""); hostagesKilledMessage = TextManager.Get(msgTag, returnNull: true) ?? msgTag; + + itemConfig = prefab.ConfigElement.Element("Items"); + itemTag = prefab.ConfigElement.GetAttributeString("targetitem", ""); } protected override void StartMissionSpecific(Level level) @@ -52,8 +97,13 @@ namespace Barotrauma characterItems.Clear(); requireKill.Clear(); requireRescue.Clear(); + items.Clear(); +#if SERVER + spawnedItems.Clear(); +#endif var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub; + InitItems(submarine); if (!IsClient) { InitCharacters(submarine); @@ -62,49 +112,101 @@ namespace Barotrauma wasDocked = Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost); } + private void InitItems(Submarine submarine) + { + if (!string.IsNullOrEmpty(itemTag)) + { + var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag)); + if (!itemsToDestroy.Any()) + { + DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\"."); + } + else + { + items.AddRange(itemsToDestroy); + } + } + + if (itemConfig != null && !IsClient) + { + foreach (XElement element in itemConfig.Elements()) + { + string itemIdentifier = element.GetAttributeString("identifier", ""); + if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab)) + { + DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found"); + continue; + } + + string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null); + string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null); + ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos( + SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy, + moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false)); + if (spawnPoint == null) + { + spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom(); + } + Vector2 spawnPos = spawnPoint.WorldPosition; + if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100) + { + spawnPos = new Vector2( + MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50), + wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f); + } + var item = new Item(itemPrefab, spawnPos, null); + items.Add(item); +#if SERVER + spawnedItems.Add(item); +#endif + } + } + } + private void InitCharacters(Submarine submarine) { characters.Clear(); characterItems.Clear(); - if (characterConfig == null) { return; } - - foreach (XElement element in characterConfig.Elements()) - { - if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; } - - int defaultCount = element.GetAttributeInt("count", -1); - if (defaultCount < 0) + if (characterConfig != null) + { + foreach (XElement element in characterConfig.Elements()) { - defaultCount = element.GetAttributeInt("amount", 1); - } - int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255); - int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255); - int count = Rand.Range(min, max + 1); + if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; } - if (element.Attribute("identifier") != null && element.Attribute("from") != null) - { - HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element); - for (int i = 0; i < count; i++) + int defaultCount = element.GetAttributeInt("count", -1); + if (defaultCount < 0) { - LoadHuman(humanPrefab, element, submarine); + defaultCount = element.GetAttributeInt("amount", 1); + } + int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255); + int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255); + int count = Rand.Range(min, max + 1); + + if (element.Attribute("identifier") != null && element.Attribute("from") != null) + { + HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element); + for (int i = 0; i < count; i++) + { + LoadHuman(humanPrefab, element, submarine); + } + } + else + { + string speciesName = element.GetAttributeString("character", element.GetAttributeString("identifier", "")); + var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName); + if (characterPrefab == null) + { + DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found"); + continue; + } + for (int i = 0; i < count; i++) + { + LoadMonster(characterPrefab, element, submarine); + } } } - else - { - string speciesName = element.GetAttributeString("character", element.GetAttributeString("identifier", "")); - var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName); - if (characterPrefab == null) - { - DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found"); - continue; - } - for (int i = 0; i < count; i++) - { - LoadMonster(characterPrefab, element, submarine); - } - } - } + } } private void LoadHuman(HumanPrefab humanPrefab, XElement element, Submarine submarine) @@ -175,7 +277,7 @@ namespace Barotrauma } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { if (State != HostagesKilledState) { @@ -203,7 +305,8 @@ namespace Barotrauma { case 0: - if (requireKill.All(c => c.Removed || c.IsDead) && + if (items.All(it => it.Removed || it.Condition <= 0.0f) && + requireKill.All(c => c.Removed || c.IsDead) && requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player)) { State = 1; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs index 4a1bb12fa..463da3632 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs @@ -53,7 +53,7 @@ namespace Barotrauma } } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { if (IsClient) { return; } if (!swarmSpawned && level.CheckBeaconActive()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index a278cfa16..fc2531528 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -15,7 +15,7 @@ namespace Barotrauma private readonly Dictionary parentInventoryIDs = new Dictionary(); private readonly Dictionary parentItemContainerIndices = new Dictionary(); - private int requiredDeliveryAmount; + private float requiredDeliveryAmount; private readonly List<(XElement element, ItemContainer container)> itemsToSpawn = new List<(XElement element, ItemContainer container)>(); private int? rewardPerCrate; @@ -29,7 +29,7 @@ namespace Barotrauma { this.sub = sub; itemConfig = prefab.ConfigElement.Element("Items"); - requiredDeliveryAmount = prefab.ConfigElement.GetAttributeInt("requireddeliveryamount", 0); + requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.9f), 1.0f); DetermineCargo(); } @@ -123,12 +123,7 @@ namespace Barotrauma LoadItemAsChild(element, container?.Item); } - if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } - if (requiredDeliveryAmount > items.Count) - { - DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver."); - requiredDeliveryAmount = items.Count; - } + if (requiredDeliveryAmount <= 0.0f) { requiredDeliveryAmount = 1.0f; } } private ItemPrefab FindItemPrefab(XElement element) @@ -220,7 +215,7 @@ namespace Barotrauma if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit) { int deliveredItemCount = items.Count(i => i.CurrentHull != null && !i.Removed && i.Condition > 0.0f); - if (deliveredItemCount >= requiredDeliveryAmount) + if (deliveredItemCount / (float)items.Count >= requiredDeliveryAmount) { GiveReward(); completed = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs index e216bc3cc..7e9dc916c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs @@ -39,13 +39,10 @@ namespace Barotrauma itemConfig = prefab.ConfigElement.Element("TerroristItems"); } - public override int Reward + public override int GetReward(Submarine sub) { - get - { - int multiplier = CalculateScalingEscortedCharacterCount(); - return Prefab.Reward * multiplier; - } + int multiplier = CalculateScalingEscortedCharacterCount(); + return Prefab.Reward * multiplier; } int CalculateScalingEscortedCharacterCount(bool inMission = false) @@ -58,7 +55,7 @@ namespace Barotrauma } return 1; } - return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * Submarine.MainSub.Info.RecommendedCrewSizeMin); + return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * (Submarine.MainSub.Info.RecommendedCrewSizeMin + Submarine.MainSub.Info.RecommendedCrewSizeMax) / 2); } private void InitEscort() @@ -88,6 +85,25 @@ namespace Barotrauma } } } + + if (terroristChance > 0f) + { + int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count); + terroristCount = Math.Clamp(terroristCount, 1, characters.Count); + + terroristCharacters.Clear(); + characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c)); + + terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f); + +#if DEBUG + DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared)); + foreach (Character character in terroristCharacters) + { + DebugConsole.AddWarning(character.Name + " is a terrorist."); + } +#endif + } } private void InitCharacters() @@ -120,26 +136,6 @@ namespace Barotrauma } i++; } - - if (!IsClient && terroristChance > 0f) - { - int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count); - terroristCount = Math.Clamp(terroristCount, 1, characters.Count); - - terroristCharacters.Clear(); - characters.Shuffle(); - characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c)); - - terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f); - -#if DEBUG - DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared)); - foreach (Character character in terroristCharacters) - { - DebugConsole.AddWarning(character.Name + " is a terrorist."); - } -#endif - } } protected override void StartMissionSpecific(Level level) @@ -207,10 +203,10 @@ namespace Barotrauma bool NonTerroristsStillAlive(IEnumerable characterList) { - return characterList.Any(c => !terroristCharacters.Contains(c) && IsAlive(c)); + return characterList.All(c => terroristCharacters.Contains(c) || IsAlive(c)); } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { if (!IsClient) { @@ -261,9 +257,10 @@ namespace Barotrauma if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit) { bool terroristsSurvived = terroristCharacters.Any(c => Survived(c) && !IsCaptured(c)); - bool friendliesSurvived = characters.Except(terroristCharacters).Any(c => Survived(c)); + bool friendliesSurvived = characters.Except(terroristCharacters).All(c => Survived(c)); bool vipDied = false; + // this logic is currently irrelevant, as the mission is failed regardless of who dies if (vipCharacter != null) { vipDied = !Survived(vipCharacter); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs new file mode 100644 index 000000000..3b077be2c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs @@ -0,0 +1,28 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class GoToMission : Mission + { + public GoToMission(MissionPrefab prefab, Location[] locations, Submarine sub) + : base(prefab, locations, sub) + { + } + + protected override void UpdateMissionSpecific(float deltaTime) + { + State = 1; + } + +#if CLIENT + public override void ClientReadInitial(IReadMessage msg) + { + } +#elif SERVER + + public override void ServerWriteInitial(IWriteMessage msg, Client c) + { + } +#endif + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs index 54c807e05..04278f4d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs @@ -115,7 +115,7 @@ namespace Barotrauma FindRelevantLevelResources(); } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { if (IsClient) { return; } switch (State) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 65e7b67a8..bd904048e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -23,6 +23,7 @@ namespace Barotrauma if (state != value) { state = value; + TryTriggerEvents(state); #if SERVER GameMain.Server?.UpdateMissionState(this, state); #endif @@ -128,6 +129,20 @@ namespace Barotrauma { get { return Prefab.Difficulty; } } + + private class DelayedTriggerEvent + { + public readonly MissionPrefab.TriggerEvent TriggerEvent; + public float Delay; + + public DelayedTriggerEvent(MissionPrefab.TriggerEvent triggerEvent, float delay) + { + TriggerEvent = triggerEvent; + Delay = delay; + } + } + + private List delayedTriggerEvents = new List(); public Mission(MissionPrefab prefab, Location[] locations, Submarine sub) { @@ -167,6 +182,9 @@ namespace Barotrauma Messages[m] = Messages[m].Replace("[reward]", rewardText); } } + + public virtual void SetDifficulty(float difficulty) { } + public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false) { return LoadRandom(locations, new MTRandom(ToolBox.StringToInt(seed)), requireCorrectLocationType, missionType, isSinglePlayer); @@ -216,9 +234,11 @@ namespace Barotrauma public void Start(Level level) { + state = 0; #if CLIENT shownMessages.Clear(); #endif + delayedTriggerEvents.Clear(); foreach (string categoryToShow in Prefab.UnhideEntitySubCategories) { foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab?.HasSubCategory(categoryToShow) ?? false)) @@ -227,12 +247,27 @@ namespace Barotrauma } } this.level = level; + TryTriggerEvents(0); StartMissionSpecific(level); } protected virtual void StartMissionSpecific(Level level) { } - public virtual void Update(float deltaTime) { } + public void Update(float deltaTime) + { + for (int i = delayedTriggerEvents.Count - 1; i>=0;i--) + { + delayedTriggerEvents[i].Delay -= deltaTime; + if (delayedTriggerEvents[i].Delay <= 0.0f) + { + TriggerEvent(delayedTriggerEvents[i].TriggerEvent); + delayedTriggerEvents.RemoveAt(i); + } + } + UpdateMissionSpecific(deltaTime); + } + + protected virtual void UpdateMissionSpecific(float deltaTime) { } protected void ShowMessage(int missionState) { @@ -241,6 +276,55 @@ namespace Barotrauma partial void ShowMessageProjSpecific(int missionState); + + private void TryTriggerEvents(int state) + { + foreach (var triggerEvent in Prefab.TriggerEvents) + { + if (triggerEvent.State == state) + { + TryTriggerEvent(triggerEvent); + } + } + } + + /// + /// Triggers the event or adds it to the delayedTriggerEvents it if it has a delay + /// + private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger) + { + if (trigger.Delay > 0) + { + if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger)) + { + delayedTriggerEvents.Add(new DelayedTriggerEvent(trigger, trigger.Delay)); + } + } + else + { + TriggerEvent(trigger); + } + } + + /// + /// Triggers the event immediately, ignoring any delays + /// + private void TriggerEvent(MissionPrefab.TriggerEvent trigger) + { + var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier.Equals(trigger.EventIdentifier, StringComparison.OrdinalIgnoreCase)); + if (eventPrefab == null) + { + DebugConsole.ThrowError($"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\")."); + return; + } + if (GameMain.GameSession?.EventManager != null) + { + var newEvent = eventPrefab.CreateInstance(); + GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent); + newEvent.Init(true); + } + } + /// /// End the mission and give a reward if it was completed successfully /// @@ -344,7 +428,7 @@ namespace Barotrauma { positionToStayIn = WayPoint.GetRandom(SpawnType.Human, null, submarine); } - var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync); + var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync); characterInfo.TeamID = teamType; Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false); humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index 2d0525467..0aed7f4a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -18,11 +18,11 @@ namespace Barotrauma Nest = 0x10, Mineral = 0x20, Combat = 0x40, - OutpostDestroy = 0x80, - OutpostRescue = 0x100, - Escort = 0x200, - Pirate = 0x400, - All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | OutpostDestroy | OutpostRescue | Escort | Pirate + AbandonedOutpost = 0x80, + Escort = 0x100, + Pirate = 0x200, + GoTo = 0x400, + All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo } partial class MissionPrefab @@ -37,15 +37,17 @@ namespace Barotrauma { MissionType.Beacon, typeof(BeaconMission) }, { MissionType.Nest, typeof(NestMission) }, { MissionType.Mineral, typeof(MineralMission) }, - { MissionType.OutpostDestroy, typeof(OutpostDestroyMission) }, - { MissionType.OutpostRescue, typeof(AbandonedOutpostMission) }, + { MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) }, { MissionType.Escort, typeof(EscortMission) }, - { MissionType.Pirate, typeof(PirateMission) } + { MissionType.Pirate, typeof(PirateMission) }, + { MissionType.GoTo, typeof(GoToMission) } }; public static readonly Dictionary PvPMissionClasses = new Dictionary() { { MissionType.Combat, typeof(CombatMission) } }; + + public static readonly HashSet HiddenMissionClasses = new HashSet() { MissionType.GoTo }; private readonly ConstructorInfo constructor; @@ -87,6 +89,8 @@ namespace Barotrauma public readonly bool IsSideObjective; + public readonly bool RequireWreck; + /// /// The mission can only be received when travelling from Pair.First to Pair.Second /// @@ -102,6 +106,25 @@ namespace Barotrauma /// public readonly List UnhideEntitySubCategories = new List(); + public class TriggerEvent + { + [Serialize("", true)] + public string EventIdentifier { get; private set; } + + [Serialize(0, true)] + public int State { get; private set; } + + [Serialize(0.0f, true)] + public float Delay { get; private set; } + + public TriggerEvent(XElement element) + { + SerializableProperty.DeserializeProperties(this, element); + } + } + + public readonly List TriggerEvents = new List(); + public LocationTypeChange LocationTypeChangeOnCompleted; public readonly XElement ConfigElement; @@ -160,6 +183,7 @@ namespace Barotrauma Reward = element.GetAttributeInt("reward", 1); AllowRetry = element.GetAttributeBool("allowretry", false); IsSideObjective = element.GetAttributeBool("sideobjective", false); + RequireWreck = element.GetAttributeBool("requirewreck", false); Commonness = element.GetAttributeInt("commonness", 1); if (element.GetAttribute("difficulty") != null) { @@ -272,10 +296,19 @@ namespace Barotrauma DataRewards.Add(Tuple.Create(identifier, value, operation)); } break; + case "triggerevent": + TriggerEvents.Add(new TriggerEvent(subElement)); + break; } } string missionTypeName = element.GetAttributeString("type", ""); + //backwards compatibility + if (missionTypeName.Equals("outpostdestroy", StringComparison.OrdinalIgnoreCase) || missionTypeName.Equals("outpostrescue", StringComparison.OrdinalIgnoreCase)) + { + missionTypeName = "AbandonedOutpost"; + } + if (!Enum.TryParse(missionTypeName, out Type)) { DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type."); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs index 4404deba5..31109da8e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs @@ -160,7 +160,7 @@ namespace Barotrauma } } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { switch (State) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs index c26836fd2..0dc3ce997 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs @@ -216,7 +216,7 @@ namespace Barotrauma level.LevelObjectManager.PlaceNestObjects(level, cave, nestPosition, nestObjectRadius, nestObjectAmount); } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { if (IsClient) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/OutpostDestroyMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/OutpostDestroyMission.cs deleted file mode 100644 index 6b9505c21..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/OutpostDestroyMission.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Barotrauma.Extensions; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma -{ - partial class OutpostDestroyMission : AbandonedOutpostMission - { - private readonly string itemTag; - private readonly XElement itemConfig; - private readonly List items = new List(); - - public override IEnumerable SonarPositions - { - get - { - if (State > 0) - { - return Enumerable.Empty(); - } - else - { - return Targets.Select(t => t.WorldPosition); - } - } - } - - private IEnumerable Targets - { - get - { - if (State > 0) - { - return Enumerable.Empty(); - } - else - { - if (items.Any()) - { - return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast().Concat(requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(requireRescue); - } - else - { - return requireKill.Concat(requireRescue); - } - } - } - } - - public OutpostDestroyMission(MissionPrefab prefab, Location[] locations, Submarine sub) : - base(prefab, locations, sub) - { - itemConfig = prefab.ConfigElement.Element("Items"); - itemTag = prefab.ConfigElement.GetAttributeString("targetitem", ""); - } - - protected override void StartMissionSpecific(Level level) - { - items.Clear(); -#if SERVER - spawnedItems.Clear(); -#endif - if (!string.IsNullOrEmpty(itemTag)) - { - var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag)); - if (!itemsToDestroy.Any()) - { - DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\"."); - } - else - { - items.AddRange(itemsToDestroy); - } - } - if (itemConfig != null && !IsClient) - { - foreach (XElement element in itemConfig.Elements()) - { - string itemIdentifier = element.GetAttributeString("identifier", ""); - if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab)) - { - DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found"); - continue; - } - - string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null); - string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null); - ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos( - SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy, - moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false)); - if (spawnPoint == null) - { - var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub; - spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom(); - } - Vector2 spawnPos = spawnPoint.WorldPosition; - if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100) - { - spawnPos = new Vector2( - MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50), - wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f); - } - var item = new Item(itemPrefab, spawnPos, null); - items.Add(item); -#if SERVER - spawnedItems.Add(item); -#endif - } - } - - base.StartMissionSpecific(level); - } - - public override void Update(float deltaTime) - { - if (requireRescue.Any(r => r.Removed || r.IsDead)) - { -#if SERVER - if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null) - { - GameMain.Server.EndGame(); - } -#endif - return; - } - - switch (state) - { - case 0: - if (items.Any()) - { - if (items.All(it => it.Removed || it.Condition <= 0.0f) && - requireKill.All(c => c.Removed || c.IsDead) && - requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player)) - { - State = 1; - } - } - else - { - if (requireKill.All(c => c.Removed || c.IsDead) && - requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player)) - { - State = 1; - } - } - break; -#if SERVER - case 1: - if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null) - { - if (!Submarine.MainSub.AtStartExit || (wasDocked && !Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost))) - { - GameMain.Server.EndGame(); - State = 2; - } - } - break; -#endif - } - } - } -} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index c1cff2ab7..5e6adf829 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -11,11 +11,15 @@ namespace Barotrauma { partial class PirateMission : Mission { + private readonly XElement submarineTypeConfig; private readonly XElement characterConfig; - private readonly XElement submarineConfig; + private readonly XElement characterTypeConfig; + private readonly float addedMissionDifficultyPerPlayer; + + private float missionDifficulty; + private int alternateReward; private Submarine enemySub; - private Item reactorItem; private readonly List characters = new List(); private readonly Dictionary> characterDictionary = new Dictionary>(); @@ -51,17 +55,55 @@ namespace Barotrauma } } + public override int GetReward(Submarine sub) + { + return alternateReward; + } + private SubmarineInfo submarineInfo; - public override SubmarineInfo EnemySubmarineInfo => submarineInfo; + public override SubmarineInfo EnemySubmarineInfo + { + get + { + return submarineInfo; + } + } + + // these values could also be defined within the mission XML + private const float RandomnessModifier = 25; + private const float ShipRandomnessModifier = 15; + + private const float MaxDifficulty = 100; public PirateMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) { - submarineConfig = prefab.ConfigElement.Element("Submarine"); + submarineTypeConfig = prefab.ConfigElement.Element("SubmarineTypes"); characterConfig = prefab.ConfigElement.Element("Characters"); + characterTypeConfig = prefab.ConfigElement.Element("CharacterTypes"); + addedMissionDifficultyPerPlayer = prefab.ConfigElement.GetAttributeFloat("addedmissiondifficultyperplayer", 0); + + // for campaign missions, set difficulty at construction + LevelData levelData = locations[0].Connections.Where(c => c.Locations.Contains(locations[1])).FirstOrDefault()?.LevelData ?? locations[0]?.LevelData; + + SetDifficulty(levelData?.Difficulty ?? Level.Loaded?.Difficulty ?? 0f); + } + + public override void SetDifficulty(float difficulty) + { + if (missionDifficulty > 0f) + { + // difficulty already set + return; + } + + missionDifficulty = difficulty; + + XElement submarineConfig = GetRandomDifficultyModifiedElement(submarineTypeConfig, missionDifficulty, ShipRandomnessModifier); + + alternateReward = submarineConfig.GetAttributeInt("alternatereward", Reward); string submarineIdentifier = submarineConfig.GetAttributeString("identifier", string.Empty); - if (submarineIdentifier == string.Empty) { DebugConsole.ThrowError("No identifier used for submarine for pirate mission!"); @@ -74,9 +116,36 @@ namespace Barotrauma DebugConsole.ThrowError("No submarine file found with the identifier!"); return; } + submarineInfo = new SubmarineInfo(contentFile.Path); } + private float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier) + { + return Math.Abs(levelDifficulty - preferredDifficulty + (Rand.Range(-randomnessModifier, randomnessModifier, Rand.RandSync.Server))); + } + private int GetDifficultyModifiedAmount(int minAmount, int maxAmount, float levelDifficulty) + { + return Math.Max((int)Math.Round(minAmount + (maxAmount - minAmount) * ((levelDifficulty + Rand.Range(-RandomnessModifier, RandomnessModifier, Rand.RandSync.Server)) / MaxDifficulty)), minAmount); + } + + private XElement GetRandomDifficultyModifiedElement(XElement parentElement, float levelDifficulty, float randomnessModifier) + { + // look for the element that is closest to our difficulty, with some randomness + XElement bestElement = null; + float bestValue = float.MaxValue; + foreach (XElement element in parentElement.Elements()) + { + float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f, "preferreddifficulty"), levelDifficulty, randomnessModifier); + if (applicabilityValue < bestValue) + { + bestElement = element; + bestValue = applicabilityValue; + } + } + return bestElement; + } + private void CreateMissionPositions(out Vector2 preferredSpawnPos) { Vector2 patrolPos = enemySub.WorldPosition; @@ -116,7 +185,6 @@ namespace Barotrauma if (enemySub.GetItems(alsoFromConnectedSubs: false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent() is Reactor reactor) { reactor.PowerUpImmediately(); - reactorItem = reactor.Item; } enemySub.EnableMaintainPosition(); enemySub.SetPosition(spawnPos); @@ -134,21 +202,45 @@ namespace Barotrauma return; } + int playerCount = 1; + +#if SERVER + playerCount = GameMain.Server.ConnectedClients.Where(c => !c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating).Count(); +#endif + + float enemyCreationDifficulty = missionDifficulty + playerCount * addedMissionDifficultyPerPlayer; + bool commanderAssigned = false; foreach (XElement element in characterConfig.Elements()) { - Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterDictionary, enemySub, CharacterTeamType.None, null); - if (!commanderAssigned) + // it is possible to get more than the "max" amount of characters if the modified difficulty is high enough; this is intentional + // if necessary, another "hard max" value could be used to clamp the value for performance/gameplay concerns + int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty); + for (int i = 0; i < amountCreated; i++) { - bool isCommander = element.GetAttributeBool("iscommander", false); - if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController) + XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString("typeidentifier", string.Empty) == element.GetAttributeString("typeidentifier", string.Empty)).FirstOrDefault(); + + if (characterType == null) { - humanAIController.InitShipCommandManager(); - foreach (var patrolPos in patrolPositions) + DebugConsole.ThrowError("No character types defined in CharacterTypes for a declared type identifier in mission file " + this); + return; + } + + XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier); + + Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(variantElement), characters, characterDictionary, enemySub, CharacterTeamType.None, null); + if (!commanderAssigned) + { + bool isCommander = variantElement.GetAttributeBool("iscommander", false); + if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController) { - humanAIController.ShipCommandManager.patrolPositions.Add(patrolPos); + humanAIController.InitShipCommandManager(); + foreach (var patrolPos in patrolPositions) + { + humanAIController.ShipCommandManager.patrolPositions.Add(patrolPos); + } + commanderAssigned = true; } - commanderAssigned = true; } } } @@ -217,7 +309,7 @@ namespace Barotrauma } } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { int newState = State; float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange); @@ -268,7 +360,7 @@ namespace Barotrauma State = newState; } - private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m)) || reactorItem.Condition <= 0f); + private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m))); private bool Survived(Character character) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 727e48c4b..36f611211 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -205,7 +205,7 @@ namespace Barotrauma } } - public override void Update(float deltaTime) + protected override void UpdateMissionSpecific(float deltaTime) { if (item == null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index 8eef6f234..98887847b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -442,7 +442,7 @@ namespace Barotrauma } else if (!spawnPosType.HasFlag(Level.PositionType.MainPath)) { - scatterAmount = 100; + scatterAmount = 0; } for (int i = 0; i < amount; i++) { @@ -455,7 +455,7 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer, "Clients should not create monster events."); Vector2 pos = spawnPos.Value + Rand.Vector(scatterAmount); - if (scatterAmount > 100) + if (scatterAmount > 0) { if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos))) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 59f841f39..21abf1111 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -49,7 +49,7 @@ namespace Barotrauma //duration of the camera transition at the end of a round protected const float EndTransitionDuration = 5.0f; //there can be no events before this time has passed during the 1st campaign round - const float FirstRoundEventDelay = 30.0f; + const float FirstRoundEventDelay = 0.0f; public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub } @@ -113,12 +113,15 @@ namespace Barotrauma { get { - if (Map.CurrentLocation?.SelectedMission != null) + if (Map.CurrentLocation != null) { - if (Map.CurrentLocation.SelectedMission.Locations[0] == Map.CurrentLocation.SelectedMission.Locations[1] || - Map.CurrentLocation.SelectedMission.Locations.Contains(Map.SelectedLocation)) + foreach (Mission mission in map.CurrentLocation.SelectedMissions) { - yield return Map.CurrentLocation.SelectedMission; + if (mission.Locations[0] == mission.Locations[1] || + mission.Locations.Contains(Map.SelectedLocation)) + { + yield return mission; + } } } foreach (Mission mission in extraMissions) @@ -240,23 +243,24 @@ namespace Barotrauma if (levelData.Type == LevelData.LevelType.Outpost) { //if there's an available mission that takes place in the outpost, select it - var availableMissionsInLocation = currentLocation.AvailableMissions.Where(m => m.Locations[0] == currentLocation && m.Locations[1] == currentLocation); - if (availableMissionsInLocation.Any()) + foreach (var availableMission in currentLocation.AvailableMissions) { - currentLocation.SelectedMission = availableMissionsInLocation.FirstOrDefault(); - } - else - { - currentLocation.SelectedMission = null; + if (availableMission.Locations[0] == currentLocation && availableMission.Locations[1] == currentLocation) + { + currentLocation.SelectMission(availableMission); + } } } else { - //if we had selected a mission that takes place in the outpost, deselect it when leaving the outpost - if (currentLocation.SelectedMission?.Locations[0] == currentLocation && - currentLocation.SelectedMission?.Locations[1] == currentLocation) + foreach (Mission mission in currentLocation.SelectedMissions.ToList()) { - currentLocation.SelectedMission = null; + //if we had selected a mission that takes place in the outpost, deselect it when leaving the outpost + if (mission.Locations[0] == currentLocation && + mission.Locations[1] == currentLocation) + { + currentLocation.DeselectMission(mission); + } } if (levelData.HasBeaconStation && !levelData.IsBeaconActive) @@ -851,11 +855,14 @@ namespace Barotrauma DebugConsole.NewMessage(" " + i + ". " + destination.Name, Color.White); } } - - if (map.CurrentLocation?.SelectedMission != null) + + if (map.CurrentLocation != null) { - DebugConsole.NewMessage(" Selected mission: " + map.CurrentLocation.SelectedMission.Name, Color.White); - DebugConsole.NewMessage("\n" + map.CurrentLocation.SelectedMission.Description, Color.White); + foreach (Mission mission in map.CurrentLocation.SelectedMissions) + { + DebugConsole.NewMessage(" Selected mission: " + mission.Name, Color.White); + DebugConsole.NewMessage("\n" + mission.Description, Color.White); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 8aa19c7b4..7876a4b1a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -162,6 +162,18 @@ namespace Barotrauma private GameMode InstantiateGameMode(GameModePreset gameModePreset, string seed, SubmarineInfo selectedSub, CampaignSettings settings, IEnumerable missionPrefabs = null, MissionType missionType = MissionType.None) { + if (gameModePreset.GameModeType == typeof(CoOpMode) || gameModePreset.GameModeType == typeof(PvPMode)) + { + //don't allow hidden mission types (e.g. GoTo) in single mission modes + var missionTypes = (MissionType[])Enum.GetValues(typeof(MissionType)); + for (int i = 0; i < missionTypes.Length; i++) + { + if (MissionPrefab.HiddenMissionClasses.Contains(missionTypes[i])) + { + missionType &= ~missionTypes[i]; + } + } + } if (gameModePreset.GameModeType == typeof(CoOpMode)) { return missionPrefabs != null ? @@ -354,6 +366,12 @@ namespace Barotrauma } } + foreach (Mission mission in GameMode.Missions) + { + // setting difficulty for missions that may involve difficulty-related submarine creation + mission.SetDifficulty(levelData?.Difficulty ?? 0f); + } + if (Submarine.MainSubs[1] == null) { var enemySubmarineInfo = GameMode is PvPMode ? SubmarineInfo : GameMode.Missions.FirstOrDefault(m => m.EnemySubmarineInfo != null)?.EnemySubmarineInfo; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs index 5d9f627d5..ecd468277 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -91,7 +91,6 @@ namespace Barotrauma private CampaignMetadata Metadata => Campaign.CampaignMetadata; private readonly CampaignMode Campaign; - private int spentMoney; public event Action? OnUpgradesChanged; @@ -125,7 +124,7 @@ namespace Barotrauma } } - public int DetermineItemSwapCost(Item item, ItemPrefab replacement) + public int DetermineItemSwapCost(Item item, ItemPrefab? replacement) { if (replacement == null) { @@ -226,7 +225,6 @@ namespace Barotrauma } Campaign.Money -= price; - spentMoney += price; PurchasedUpgrade? upgrade = FindMatchingUpgrade(prefab, category); @@ -270,17 +268,22 @@ namespace Barotrauma DebugConsole.ThrowError($"Cannot swap null item!"); return; } + if (itemToRemove.HiddenInGame) + { + DebugConsole.ThrowError($"Cannot swap item \"{itemToRemove.Name}\" because it's set to be hidden in-game."); + return; + } + if (!itemToRemove.AllowSwapping) + { + DebugConsole.ThrowError($"Cannot swap item \"{itemToRemove.Name}\" because it's configured to be non-swappable."); + return; + } if (!UpgradeCategory.Categories.Any(c => c.ItemTags.Any(t => itemToRemove.HasTag(t)) && c.ItemTags.Any(t => itemToInstall.Tags.Contains(t)))) { DebugConsole.ThrowError($"Failed to swap item \"{itemToRemove.Name}\" with \"{itemToInstall.Name}\" (not in the same upgrade category)."); return; } - /*if (itemToRemove.PendingItemSwap != null) - { - CancelItemSwap(itemToRemove); - } - else */ if (itemToRemove.prefab == itemToInstall) { DebugConsole.ThrowError($"Failed to swap item \"{itemToRemove.Name}\" (trying to swap with the same item!)."); @@ -318,7 +321,6 @@ namespace Barotrauma } Campaign.Money -= price; - spentMoney += price; itemToRemove.AvailableSwaps.Add(itemToRemove.Prefab); if (itemToInstall != null) { itemToRemove.AvailableSwaps.Add(itemToInstall); } @@ -436,36 +438,12 @@ namespace Barotrauma { int newLevel = BuyUpgrade(prefab, category, Submarine.MainSub, level); DebugConsole.Log($" - {category.Identifier}.{prefab.Identifier} lvl. {level}, new: ({newLevel})"); - if (newLevel > 0) - { - SetUpgradeLevel(prefab, category, Math.Clamp(newLevel, 0, prefab.MaxLevel)); - } + SetUpgradeLevel(prefab, category, Math.Clamp(level, 0, prefab.MaxLevel)); } PendingUpgrades.Clear(); loadedUpgrades?.Clear(); loadedUpgrades = null; - spentMoney = 0; - } - - /// - /// Cancels the pending upgrades and refunds the money spent - /// - private void RefundUpgrades() - { - DebugConsole.Log($"Refunded {spentMoney} marks in pending upgrades."); - if (spentMoney > 0) - { -#if CLIENT - GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("UpgradeRefundTitle"), TextManager.Get("UpgradeRefundBody"), new[] { TextManager.Get("Ok") }); - msgBox.Buttons[0].OnClicked += msgBox.Close; -#endif - } - - Campaign.Money += spentMoney; - spentMoney = 0; - PendingUpgrades.Clear(); - PurchasedUpgrades.Clear(); } public void CreateUpgradeErrorMessage(string text, bool isSinglePlayer, Character character) @@ -523,7 +501,7 @@ namespace Barotrauma if (upgrade == null || upgrade.Level != level || isOverMax) { - DebugConsole.AddWarning($"{wall.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing..."); + DebugLog($"{wall.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing..."); FixUpgradeOnItem(wall, prefab, level); } } @@ -552,7 +530,7 @@ namespace Barotrauma if (upgrade == null || upgrade.Level != level || isOverMax) { - DebugConsole.AddWarning($"{item.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}{(isOverMax ? " (Over max level!)" : string.Empty)}. Fixing..."); + DebugLog($"{item.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}{(isOverMax ? " (Over max level!)" : string.Empty)}. Fixing..."); FixUpgradeOnItem(item, prefab, level); } } @@ -695,112 +673,6 @@ namespace Barotrauma return Campaign.PendingSubmarineSwitch == null; } - public void RefundResetAndReload(SubmarineInfo newSubmarine, bool notifyClients = false) - { - RefundUpgrades(); - ResetUpgrades(); - Dictionary newUpgrades = ReloadUpgradeValues(newSubmarine); -#if SERVER - if (notifyClients) - { - SendUpgradeResetMessage(newUpgrades); - } -#endif - } - - /// - /// Parses a SubmarineInfo and sets the store values accordingly. - /// Used when reloading a previously saved submarine. - /// - /// - private Dictionary ReloadUpgradeValues(SubmarineInfo info) - { - Dictionary newValues = new Dictionary(); - IEnumerable linkedSubElements = info.SubmarineElement.Elements().Where(element => element.Name.ToString().Equals("LinkedSubmarine", StringComparison.OrdinalIgnoreCase)).SelectMany(element => element.Elements()); - IEnumerable mainSubElements = info.SubmarineElement.Elements().Where(Predicate); - List elements = mainSubElements.Concat(linkedSubElements.Where(Predicate)).ToList(); - foreach (UpgradeCategory category in UpgradeCategory.Categories) - { - foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) - { - if (!prefab.UpgradeCategories.Contains(category)) { continue; } - - List levels = GetUpgradeFromXML(elements, category, prefab); - if (levels.Any()) - { - int level = (int) levels.Average(i => i); - newValues.Add(FormatIdentifier(prefab, category), level); - } - } - } - - foreach (var (dataIdentifier, level) in newValues) - { - Campaign.CampaignMetadata.SetValue(dataIdentifier, level); - } - - return newValues; - - static List GetUpgradeFromXML(List elements, UpgradeCategory category, UpgradePrefab prefab) - { - List levels = new List(); - foreach (XElement subElement in elements) - { - if (!category.CanBeApplied(subElement, prefab)) { continue; } - - foreach (XElement component in subElement.Elements()) - { - if (string.Equals(component.Name.ToString(), "upgrade", StringComparison.OrdinalIgnoreCase)) - { - string identifier = component.GetAttributeString("identifier", string.Empty); - int level = component.GetAttributeInt("level", -1); - if (string.IsNullOrWhiteSpace(identifier) || level <= 0) { continue; } - - UpgradePrefab? matchingPrefab = UpgradePrefab.Find(identifier); - if (matchingPrefab == null || matchingPrefab != prefab) { continue; } - - if (matchingPrefab.UpgradeCategories.Contains(category)) { levels.Add(level); } - } - } - } - - return levels; - } - - static bool Predicate(XElement element) => element.HasElements && element.Elements().Any(e => e.Name.ToString().Equals("upgrade", StringComparison.OrdinalIgnoreCase)); - } - - - /// - /// Resets our upgrade progress and prices. - /// This does not actually remove the upgrades from the submarine but resets the store interface. - /// - /// - /// This method works by iterating thru all upgrade categories and prefabs and checking if they have a - /// valid key stored in the metadata, if they do set it to 0, upgrades without a key stored are always - /// assumed to be 0 so they don't need to be reset. - /// - /// Should initially be called server side as we can't trust clients with such a simple notification. - /// - private void ResetUpgrades() - { - foreach (UpgradeCategory category in UpgradeCategory.Categories) - { - foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) - { - if (!prefab.UpgradeCategories.Contains(category)) { continue; } - - string dataIdentifier = FormatIdentifier(prefab, category); - if (Metadata.HasKey(dataIdentifier)) - { - Metadata.SetValue(dataIdentifier, 0); - } - } - } - - OnUpgradesChanged?.Invoke(); - } - public void Save(XElement? parent) { if (parent == null) { return; } @@ -877,34 +749,10 @@ namespace Barotrauma DebugConsole.ThrowError(error.TrimEnd('\n'), e); } - public static Dictionary GetMetadataLevels(CampaignMetadata? metadata) - { - Dictionary values = new Dictionary(); - - if (metadata == null) { return values; } - - foreach (UpgradeCategory category in UpgradeCategory.Categories) - { - foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) - { - string identifier = FormatIdentifier(prefab, category); - if (metadata.HasKey(identifier) && !values.ContainsKey(identifier)) - { - values.Add(identifier, metadata.GetInt(identifier)); - } - } - } - - return values; - } - /// /// Used to sync the pending upgrades list in multiplayer. /// /// - /// - /// In singleplayer this is not used and should not be. - /// public void SetPendingUpgrades(List upgrades) { PendingUpgrades.Clear(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs index dabd8d959..73edb3c68 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs @@ -887,10 +887,7 @@ namespace Barotrauma.Items.Components } var wire = item.GetComponent(); - if (wire != null) - { - wire.Drop(null); - } + wire?.Drop(null); if (joint != null) { @@ -991,7 +988,7 @@ namespace Barotrauma.Items.Components { if (DockingTarget.Door != null && doorBody != null) { - doorBody.Enabled = DockingTarget.Door.Body.Enabled; + doorBody.Enabled = DockingTarget.Door.Body.Enabled && !(DockingTarget.Door.Body.FarseerBody.FixtureList.FirstOrDefault()?.IsSensor ?? false); } dockingState = MathHelper.Lerp(dockingState, 1.0f, deltaTime * 10.0f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index 528fd48a5..6c0e1e608 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -259,6 +259,10 @@ namespace Barotrauma.Items.Components Body.SetTransformIgnoreContacts( ConvertUnits.ToSimUnits(new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)), 0.0f); + if (isBroken) + { + DisableBody(); + } } public override void Move(Vector2 amount) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index 205814cb6..cbfc92b1a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -440,7 +440,7 @@ namespace Barotrauma.Items.Components if (item.CurrentHull == null) { - return attachTargetCell != null && Structure.GetAttachTarget(item.WorldPosition) != null; + return attachTargetCell != null || Structure.GetAttachTarget(item.WorldPosition) != null; } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index 5ad4e89bf..3052da20c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -548,6 +548,11 @@ namespace Barotrauma.Items.Components } return true; } + else if (targetBody.UserData is LevelObject levelObject && levelObject.Prefab.TakeLevelWallDamage) + { + levelObject.AddDamage(-LevelWallFixAmount, deltaTime, item); + return true; + } else if (targetBody.UserData is Character targetCharacter) { if (targetCharacter.Removed) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 7e19a9248..2080a95d0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -290,10 +290,12 @@ namespace Barotrauma.Items.Components { for (int i = 0; i < ingredient.Amount; i++) { - var availableItem = availableIngredients.FirstOrDefault(it => it != null && ingredient.ItemPrefabs.Contains(it.Prefab) && it.ConditionPercentage >= ingredient.MinCondition * 100.0f); + var availableItem = availableIngredients.FirstOrDefault(it => + it != null && ingredient.ItemPrefabs.Contains(it.Prefab) && + it.ConditionPercentage >= ingredient.MinCondition * 100.0f && + it.ConditionPercentage <= ingredient.MaxCondition * 100.0f); if (availableItem == null) { continue; } - - //Item4 = use condition bool + if (ingredient.UseCondition && availableItem.ConditionPercentage - ingredient.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0 { availableItem.Condition -= availableItem.Prefab.Health * ingredient.MinCondition; @@ -493,7 +495,8 @@ namespace Barotrauma.Items.Components return item != null && requiredItem.ItemPrefabs.Contains(item.prefab) && - item.Condition / item.Prefab.Health >= requiredItem.MinCondition; + item.Condition / item.Prefab.Health >= requiredItem.MinCondition && + item.Condition / item.Prefab.Health <= requiredItem.MaxCondition; } public override XElement Save(XElement parentElement) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index 3b4717d8e..c5fd6c2c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -242,7 +242,7 @@ namespace Barotrauma.Items.Components optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance); tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess); allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance); - + optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess); allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess); @@ -314,6 +314,33 @@ namespace Barotrauma.Items.Components } } + if (!loadQueue.Any() && PowerOn) + { + //loadQueue is empty, round must've just started + //reset the fission rate, turbine output and + //temperature to optimal levels to prevent fires + //at the start of the round + correctTurbineOutput = currentLoad / MaxPowerOutput * 100.0f; + tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess); + optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance); + tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess); + allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance); + + float desiredTurbineOutput = MathHelper.Clamp(correctTurbineOutput, 0.0f, 100.0f); + DebugConsole.Log($"Turbine output reset: {targetTurbineOutput}, {turbineOutput} -> {desiredTurbineOutput}"); + targetTurbineOutput = desiredTurbineOutput; + turbineOutput = desiredTurbineOutput; + + float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f; + DebugConsole.Log($"Fission rate reset: {targetFissionRate}, {fissionRate} -> {desiredFissionRate}"); + targetFissionRate = desiredFissionRate; + fissionRate = desiredFissionRate; + + float desiredTemperature = (optimalTemperature.X + optimalTemperature.Y) / 2.0f; + DebugConsole.Log($"Temperature reset: {temperature} -> {desiredTemperature}"); + temperature = desiredTemperature; + } + loadQueue.Enqueue(currentLoad); while (loadQueue.Count() > 60.0f) { @@ -619,6 +646,18 @@ namespace Barotrauma.Items.Components } else { + if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character)) + { + if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f) + { + objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true)); + return false; + } + else + { + character.Speak(TextManager.Get("DialogReactorIsBroken"), identifier: "reactorisbroken", minDurationBetweenSimilar: 30.0f); + } + } if (TooMuchFuel()) { DropFuel(minCondition: 0.1f, maxCondition: 100); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index 1c497b7c2..7e9a9fc27 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -705,6 +705,20 @@ namespace Barotrauma.Items.Components } } user = character; + + if (Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(Item, character)) + { + if (Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f) + { + objective.AddSubObjective(new AIObjectiveRepairItem(character, Item, objective.objectiveManager, isPriority: true)); + return false; + } + else + { + character.Speak(TextManager.Get("DialogNavTerminalIsBroken"), identifier: "navterminalisbroken", minDurationBetweenSimilar: 30.0f); + } + } + if (!AutoPilot) { unsentChanges = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index d01ffa299..086924fd5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -171,6 +171,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, false, description: "Override random spread with static spread; hitscan are launched with an equal amount of angle between them. Only applies when firing multiple hitscan.")] + public bool StaticSpread + { + get; + set; + } + public Body StickTarget { get; @@ -267,9 +274,24 @@ namespace Barotrauma.Items.Components { if (character != null && !characterUsable) { return false; } + for (int i = 0; i < HitScanCount; i++) { - float launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * Rand.Range(-0.5f, 0.5f)); + float launchAngle = 0f; + + if (StaticSpread) + { + float staticSpread = Spread / (HitScanCount - 1); + // because the position of the item changes as hitscan are fired, we will set an + // initial offset on the first hitscan and then increase the item's angle by a set amount as hitscan are fired + float offset = i == 0 ? -staticSpread * (HitScanCount -1) : 0f; + launchAngle = item.body.Rotation + MathHelper.ToRadians(staticSpread + offset); + } + else + { + launchAngle = item.body.Rotation + MathHelper.ToRadians(Spread * Rand.Range(-0.5f, 0.5f)); + } + Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle)); if (Hitscan) { @@ -430,8 +452,15 @@ namespace Barotrauma.Items.Components var aabb = new FarseerPhysics.Collision.AABB(rayStart - Vector2.One * 0.001f, rayStart + Vector2.One * 0.001f); GameMain.World.QueryAABB((fixture) => { - //ignore sensors and items - if (fixture?.Body == null || fixture.IsSensor) { return true; } + if (fixture?.Body.UserData is LevelObject levelObj) + { + if (!levelObj.Prefab.TakeLevelWallDamage) { return true; } + } + else if (fixture?.Body == null || fixture.IsSensor) + { + //ignore sensors and items + return true; + } if (fixture.Body.UserData is VineTile) { return true; } if (fixture.Body.UserData is Item item && (item.GetComponent() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; } if (fixture.Body.UserData as string == "ruinroom") { return true; } @@ -439,6 +468,7 @@ namespace Barotrauma.Items.Components //if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub if (submarine != null) { + if (fixture.Body.UserData is VoronoiCell) { return true; } if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return true; } } @@ -459,7 +489,15 @@ namespace Barotrauma.Items.Components GameMain.World.RayCast((fixture, point, normal, fraction) => { //ignore sensors and items - if (fixture?.Body == null || fixture.IsSensor) { return -1; } + if (fixture?.Body.UserData is LevelObject levelObj) + { + if (!levelObj.Prefab.TakeLevelWallDamage) { return -1; } + } + else if (fixture?.Body == null || fixture.IsSensor) + { + //ignore sensors and items + return -1; + } if (fixture.Body.UserData is VineTile) { return -1; } if (fixture.Body.UserData is Item item && (item.GetComponent() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; } @@ -473,21 +511,40 @@ namespace Barotrauma.Items.Components //if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub if (submarine != null) { + if (fixture.Body.UserData is VoronoiCell) { return -1; } if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return -1; } } //ignore level cells if the item and the point of impact are inside a sub - if (fixture.Body.UserData is VoronoiCell && this.item.Submarine != null) + if (fixture.Body.UserData is VoronoiCell) { - if (Hull.FindHull(ConvertUnits.ToDisplayUnits(point), this.item.CurrentHull) != null) + if (Hull.FindHull(ConvertUnits.ToDisplayUnits(point), this.item.CurrentHull) != null && this.item.Submarine != null) { return -1; } } + if (hits.Count > 50) + { + float furthestHit = 0.0f; + int furthestHitIndex = -1; + for (int i = 0; i < hits.Count; i++) + { + if (hits[i].Fraction > furthestHit) + { + furthestHitIndex = i; + furthestHit = hits[i].Fraction; + } + } + if (furthestHitIndex > -1) + { + hits.RemoveAt(furthestHitIndex); + } + } + hits.Add(new HitscanResult(fixture, point, normal, fraction)); - return hits.Count < 25 ? 1 : 0; + return 1; }, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel); return hits; @@ -590,11 +647,11 @@ namespace Barotrauma.Items.Components } else if (target.Body.UserData is Limb limb) { - //severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive) if (limb.IsSevered) { - target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass); - return true; + //push the severed limb around a bit, but let the projectile pass through it + limb.body?.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass * 0.1f, item.SimPosition); + return false; } } else if (target.Body.UserData is Item item) @@ -654,9 +711,7 @@ namespace Barotrauma.Items.Components projectileNewSpeed = 1f; projectileDeflectedNewSpeed = 0.8f; } - //severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive) - if (limb.IsSevered) { return true; } - if (limb.character == null || limb.character.Removed) { return false; } + if (limb.IsSevered || limb.character == null || limb.character.Removed) { return false; } limb.character.LastDamageSource = item; if (Attack != null) { attackResult = Attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f); } @@ -674,7 +729,7 @@ namespace Barotrauma.Items.Components { if (Attack != null) { attackResult = Attack.DoDamage(User, damageable, item.WorldPosition, 1.0f); } } - else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.StructureDamage) > 0.0f) + else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.LevelWallDamage) > 0.0f) { if (Level.Loaded?.ExtraWalls.Find(w => w.Body == target.Body) is DestructibleLevelWall destructibleWall) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs index 4834c6d92..1f923cc23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs @@ -197,19 +197,13 @@ namespace Barotrauma.Items.Components if (Math.Abs(SourcePullForce) > 0.001f) { var sourceBody = GetBodyToPull(source); - if (sourceBody != null) - { - sourceBody.ApplyForce(forceDir * SourcePullForce); - } + sourceBody?.ApplyForce(forceDir * SourcePullForce); } if (Math.Abs(TargetPullForce) > 0.001f) { var targetBody = GetBodyToPull(target); - if (targetBody != null) - { - targetBody.ApplyForce(-forceDir * TargetPullForce); - } + targetBody?.ApplyForce(-forceDir * TargetPullForce); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index 89543f080..38c56e427 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs @@ -34,6 +34,11 @@ namespace Barotrauma.Items.Components set; } + public bool TemporarilyLocked + { + get { return Level.IsLoadedOutpost && item.GetComponent() != null; } + } + //connection panels can't be deactivated externally (by signals or status effects) public override bool IsActive { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index cab1434f4..0c292771d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components range = MathHelper.Clamp(value, 0.0f, 4096.0f); #if CLIENT item.ResetCachedVisibleSize(); - if (light != null) { light.Range = range; } + if (Light != null) { Light.Range = range; } #endif } } @@ -53,7 +53,7 @@ namespace Barotrauma.Items.Components { castShadows = value; #if CLIENT - if (light != null) light.CastShadows = value; + if (Light != null) Light.CastShadows = value; #endif } } @@ -67,7 +67,7 @@ namespace Barotrauma.Items.Components { drawBehindSubs = value; #if CLIENT - if (light != null) light.IsBackground = drawBehindSubs; + if (Light != null) Light.IsBackground = drawBehindSubs; #endif } } @@ -93,7 +93,7 @@ namespace Barotrauma.Items.Components { flicker = MathHelper.Clamp(value, 0.0f, 1.0f); #if CLIENT - if (light != null) { light.LightSourceParams.Flicker = flicker; } + if (Light != null) { Light.LightSourceParams.Flicker = flicker; } #endif } } @@ -106,7 +106,7 @@ namespace Barotrauma.Items.Components { flickerSpeed = value; #if CLIENT - if (light != null) { light.LightSourceParams.FlickerSpeed = flickerSpeed; } + if (Light != null) { Light.LightSourceParams.FlickerSpeed = flickerSpeed; } #endif } } @@ -119,7 +119,7 @@ namespace Barotrauma.Items.Components { pulseFrequency = MathHelper.Clamp(value, 0.0f, 60.0f); #if CLIENT - if (light != null) { light.LightSourceParams.PulseFrequency = pulseFrequency; } + if (Light != null) { Light.LightSourceParams.PulseFrequency = pulseFrequency; } #endif } } @@ -132,7 +132,7 @@ namespace Barotrauma.Items.Components { pulseAmount = MathHelper.Clamp(value, 0.0f, 1.0f); #if CLIENT - if (light != null) { light.LightSourceParams.PulseAmount = pulseAmount; } + if (Light != null) { Light.LightSourceParams.PulseAmount = pulseAmount; } #endif } } @@ -145,7 +145,7 @@ namespace Barotrauma.Items.Components { blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f); #if CLIENT - if (light != null) { light.LightSourceParams.BlinkFrequency = blinkFrequency; } + if (Light != null) { Light.LightSourceParams.BlinkFrequency = blinkFrequency; } #endif } } @@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components { lightColor = value; #if CLIENT - if (light != null) light.Color = IsActive ? lightColor : Color.Transparent; + if (Light != null) Light.Color = IsActive ? lightColor : Color.Transparent; #endif } } @@ -173,7 +173,7 @@ namespace Barotrauma.Items.Components public override void Move(Vector2 amount) { #if CLIENT - light.Position += amount; + Light.Position += amount; #endif } @@ -197,7 +197,7 @@ namespace Barotrauma.Items.Components : base(item, element) { #if CLIENT - light = new LightSource(element) + Light = new LightSource(element) { ParentSub = item.CurrentHull?.Submarine, Position = item.Position, @@ -206,11 +206,11 @@ namespace Barotrauma.Items.Components SpriteScale = Vector2.One * item.Scale, Range = range }; - light.LightSourceParams.Flicker = flicker; - light.LightSourceParams.FlickerSpeed = FlickerSpeed; - light.LightSourceParams.PulseAmount = pulseAmount; - light.LightSourceParams.PulseFrequency = pulseFrequency; - light.LightSourceParams.BlinkFrequency = blinkFrequency; + Light.LightSourceParams.Flicker = flicker; + Light.LightSourceParams.FlickerSpeed = FlickerSpeed; + Light.LightSourceParams.PulseAmount = pulseAmount; + Light.LightSourceParams.PulseFrequency = pulseFrequency; + Light.LightSourceParams.BlinkFrequency = blinkFrequency; #endif IsActive = IsOn; @@ -233,7 +233,7 @@ namespace Barotrauma.Items.Components UpdateOnActiveEffects(deltaTime); #if CLIENT - light.ParentSub = item.Submarine; + Light.ParentSub = item.Submarine; #endif if (item.Container != null) { @@ -243,23 +243,23 @@ namespace Barotrauma.Items.Components #if CLIENT if (ParentBody != null) { - light.Position = ParentBody.Position; + Light.Position = ParentBody.Position; } else if (turret != null) { - light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y); + Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y); } else { - light.Position = item.Position; + Light.Position = item.Position; } #endif PhysicsBody body = ParentBody ?? item.body; if (body != null) { #if CLIENT - light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi; - light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically; + Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi; + Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically; #endif if (!body.Enabled) { @@ -270,8 +270,8 @@ namespace Barotrauma.Items.Components else { #if CLIENT - light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation); - light.LightSpriteEffect = item.SpriteEffects; + Light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation); + Light.LightSpriteEffect = item.SpriteEffects; #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index d0cce2fa9..01058e3e5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -71,7 +71,7 @@ namespace Barotrauma.Items.Components get { if (GameMain.NetworkMember?.ServerSettings != null && !GameMain.NetworkMember.ServerSettings.AllowRewiring) { return false; } - return locked || connections.Any(c => c != null && c.ConnectionPanel.Locked); + return locked || connections.Any(c => c != null && (c.ConnectionPanel.Locked || c.ConnectionPanel.TemporarilyLocked)); } set { locked = value; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 795d8203f..4d369329a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -920,14 +920,14 @@ namespace Barotrauma.Items.Components if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead) { - character.Speak(TextManager.Get("DialogTurretTargetDead"), null, 0.0f, "killedtarget" + previousTarget.ID, 10.0f); + character.Speak(TextManager.Get("DialogTurretTargetDead"), identifier: "killedtarget" + previousTarget.ID, minDurationBetweenSimilar: 10.0f); character.AIController.SelectTarget(null); } + bool canShoot = true; if (!HasPowerToShoot()) { var batteries = item.GetConnectedComponents(); - float lowestCharge = 0.0f; PowerContainer batteryToLoad = null; foreach (PowerContainer battery in batteries) @@ -938,15 +938,31 @@ namespace Barotrauma.Items.Components batteryToLoad = battery; lowestCharge = battery.Charge; } + if (battery.Item.ConditionPercentage <= 0 && AIObjectiveRepairItems.IsValidTarget(battery.Item, character)) + { + if (battery.Item.Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f) + { + objective.AddSubObjective(new AIObjectiveRepairItem(character, battery.Item, objective.objectiveManager, isPriority: true)); + return false; + } + else + { + character.Speak(TextManager.Get("DialogSupercapacitorIsBroken"), identifier: "supercapacitorisbroken", minDurationBetweenSimilar: 30.0f); + canShoot = false; + } + } } - - if (batteryToLoad == null) return true; - + if (batteryToLoad == null) { return true; } if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f) { objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: false)); return false; } + if (lowestCharge <= 0 && batteryToLoad.Item.ConditionPercentage > 0) + { + character.Speak(TextManager.Get("DialogTurretHasNoPower"), identifier: "turrethasnopower", minDurationBetweenSimilar: 30.0f); + canShoot = false; + } } int usableProjectileCount = 0; @@ -983,7 +999,7 @@ namespace Barotrauma.Items.Components { if (character.IsOnPlayerTeam) { - character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "cannotloadturret", 30.0f); + character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "cannotloadturret", minDurationBetweenSimilar: 30.0f); } return true; } @@ -993,7 +1009,7 @@ namespace Barotrauma.Items.Components loadItemsObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier }; if (character.IsOnPlayerTeam) { - character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "loadturret", 30.0f); + character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "loadturret", minDurationBetweenSimilar: 30.0f); } loadItemsObjective.Abandoned += CheckRemainingAmmo; loadItemsObjective.Completed += CheckRemainingAmmo; @@ -1007,11 +1023,11 @@ namespace Barotrauma.Items.Components int remainingAmmo = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(ammoType) && i.Condition > 1); if (remainingAmmo == 0) { - character.Speak(TextManager.Get($"DialogOutOf{ammoType}", fallBackTag: "DialogOutOfTurretAmmo"), null, 0.0f, "outofammo", 30.0f); + character.Speak(TextManager.Get($"DialogOutOf{ammoType}", fallBackTag: "DialogOutOfTurretAmmo"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f); } else if (remainingAmmo < 3) { - character.Speak(TextManager.Get($"DialogLowOn{ammoType}"), null, 0.0f, "outofammo", 30.0f); + character.Speak(TextManager.Get($"DialogLowOn{ammoType}"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f); } } } @@ -1253,12 +1269,15 @@ namespace Barotrauma.Items.Components return false; } } - if (character.IsOnPlayerTeam) + if (canShoot) { - character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f); + if (character.IsOnPlayerTeam) + { + character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f); + } + character.SetInput(InputType.Shoot, true, true); } aiTargetingGraceTimer = 5f; - character.SetInput(InputType.Shoot, true, true); return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index a1f872c6f..0bb2e3652 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -164,10 +164,7 @@ namespace Barotrauma if (IsInitialized) { return; } _gender = UnassignedSpritePath.Contains("[GENDER]") ? gender : Gender.None; ParsePath(false); - if (Sprite != null) - { - Sprite.Remove(); - } + Sprite?.Remove(); Sprite = new Sprite(SourceElement, file: SpritePath); Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true); HideLimb = SourceElement.GetAttributeBool("hidelimb", false); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 7881c45b8..4954907d3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -125,8 +125,7 @@ namespace Barotrauma { if (!component.AllowInGameEditing) { continue; } if (component.SerializableProperties.Values.Any(p => p.Attributes.OfType().Any()) - || component.SerializableProperties.Values.Any(p => p.Attributes.OfType().Any(a => a.IsEditable())) - ) + || component.SerializableProperties.Values.Any(p => p.Attributes.OfType().Any(a => a.IsEditable(this)))) { hasInGameEditableProperties = true; break; @@ -198,6 +197,13 @@ namespace Barotrauma set; } + [ConditionallyEditable(ConditionallyEditable.ConditionType.IsSwappableItem), Serialize(true, true, alwaysUseInstanceValues: true)] + public bool AllowSwapping + { + get; + set; + } + /// /// Checks both and /// @@ -1084,10 +1090,7 @@ namespace Barotrauma public void RemoveContained(Item contained) { - if (ownInventory != null) - { - ownInventory.RemoveItem(contained); - } + ownInventory?.RemoveItem(contained); contained.Container = null; } @@ -2043,6 +2046,8 @@ namespace Barotrauma public bool TryInteract(Character picker, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceActionKey = false) { + if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; } + bool picked = false, selected = false; #if CLIENT bool hasRequiredSkills = true; @@ -2467,7 +2472,7 @@ namespace Barotrauma private List> GetInGameEditableProperties() { return GetProperties() - .Where(ce => ce.Second.GetAttribute().IsEditable()) + .Where(ce => ce.Second.GetAttribute().IsEditable(this)) .Union(GetProperties()).ToList(); } @@ -2746,7 +2751,7 @@ namespace Barotrauma } } } - if (usePrefabValues) + if (usePrefabValues && appliedSwap == null) { //use prefab scale when overriding a non-overridden item or vice versa item.Scale = prefab.ConfigElement.GetAttributeFloat(item.scale, "scale", "Scale"); @@ -2764,22 +2769,34 @@ namespace Barotrauma } } + float prevRotation = item.Rotation; if (element.GetAttributeBool("flippedx", false)) { item.FlipX(false); } if (element.GetAttributeBool("flippedy", false)) { item.FlipY(false); } + item.Rotation = prevRotation; - if (appliedSwap != null && oldPrefab.SwappableItem != null && prefab.SwappableItem != null) + if (appliedSwap != null) { - Vector2 oldRelativeOrigin = (oldPrefab.SwappableItem.SwapOrigin - oldPrefab.Size / 2) * element.GetAttributeFloat(item.scale, "scale", "Scale"); - oldRelativeOrigin.Y = -oldRelativeOrigin.Y; - oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, item.rotationRad); - Vector2 oldOrigin = centerPos + oldRelativeOrigin; + item.SpriteDepth = element.GetAttributeFloat("spritedepth", item.SpriteDepth); + item.SpriteColor = element.GetAttributeColor("spritecolor", item.SpriteColor); + item.Rotation = element.GetAttributeFloat("rotation", item.Rotation); - Vector2 relativeOrigin = (prefab.SwappableItem.SwapOrigin - prefab.Size / 2) * item.Scale; - relativeOrigin.Y = -relativeOrigin.Y; - relativeOrigin = MathUtils.RotatePoint(relativeOrigin, item.rotationRad); - Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin; + float scaleRelativeToPrefab = element.GetAttributeFloat(item.scale, "scale", "Scale") / oldPrefab.Scale; + item.Scale *= scaleRelativeToPrefab; - item.rect.Location -= (origin - oldOrigin).ToPoint(); + if (oldPrefab.SwappableItem != null && prefab.SwappableItem != null) + { + Vector2 oldRelativeOrigin = (oldPrefab.SwappableItem.SwapOrigin - oldPrefab.Size / 2) * element.GetAttributeFloat(item.scale, "scale", "Scale"); + oldRelativeOrigin.Y = -oldRelativeOrigin.Y; + oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, item.rotationRad); + Vector2 oldOrigin = centerPos + oldRelativeOrigin; + + Vector2 relativeOrigin = (prefab.SwappableItem.SwapOrigin - prefab.Size / 2) * item.Scale; + relativeOrigin.Y = -relativeOrigin.Y; + relativeOrigin = MathUtils.RotatePoint(relativeOrigin, item.rotationRad); + Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin; + + item.rect.Location -= (origin - oldOrigin).ToPoint(); + } } float condition = element.GetAttributeFloat("condition", item.MaxCondition); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 18716d19c..f86000589 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -3,6 +3,7 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; @@ -39,21 +40,24 @@ namespace Barotrauma public readonly List ItemPrefabs; public int Amount; public readonly float MinCondition; + public readonly float MaxCondition; public readonly bool UseCondition; - public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition) + public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition) { ItemPrefabs = new List() { itemPrefab }; Amount = amount; MinCondition = minCondition; + MaxCondition = maxCondition; UseCondition = useCondition; } - public RequiredItem(IEnumerable itemPrefabs, int amount, float minCondition, bool useCondition) + public RequiredItem(IEnumerable itemPrefabs, int amount, float minCondition, float maxCondition, bool useCondition) { ItemPrefabs = new List(itemPrefabs); Amount = amount; MinCondition = minCondition; + MaxCondition = maxCondition; UseCondition = useCondition; } } @@ -71,7 +75,7 @@ namespace Barotrauma { TargetItem = itemPrefab; string displayName = element.GetAttributeString("displayname", ""); - DisplayName = string.IsNullOrEmpty(displayName) ? itemPrefab.Name : TextManager.Get($"DisplayName.{displayName}"); + DisplayName = string.IsNullOrEmpty(displayName) ? itemPrefab.Name : TextManager.GetWithVariable($"DisplayName.{displayName}", "[itemname]", itemPrefab.Name); SuitableFabricatorIdentifiers = element.GetAttributeStringArray("suitablefabricators", new string[0]); @@ -107,6 +111,7 @@ namespace Barotrauma } float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f); + float maxCondition = subElement.GetAttributeFloat("maxcondition", 1.0f); //Substract mincondition from required item's condition or delete it regardless? bool useCondition = subElement.GetAttributeBool("usecondition", true); int count = subElement.GetAttributeInt("count", 1); @@ -119,10 +124,12 @@ namespace Barotrauma continue; } - var existing = RequiredItems.Find(r => r.ItemPrefabs.Count == 1 && r.ItemPrefabs[0] == requiredItem && MathUtils.NearlyEqual(r.MinCondition, minCondition)); + var existing = RequiredItems.Find(r => + r.ItemPrefabs.Count == 1 && r.ItemPrefabs[0] == requiredItem && + MathUtils.NearlyEqual(r.MinCondition, minCondition) && MathUtils.NearlyEqual(r.MaxCondition, maxCondition)); if (existing == null) { - RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition)); + RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, maxCondition, useCondition)); } else { @@ -138,10 +145,13 @@ namespace Barotrauma continue; } - var existing = RequiredItems.Find(r => r.ItemPrefabs.SequenceEqual(matchingItems) && MathUtils.NearlyEqual(r.MinCondition, minCondition)); + var existing = RequiredItems.Find(r => + r.ItemPrefabs.SequenceEqual(matchingItems) && + MathUtils.NearlyEqual(r.MinCondition, minCondition) && + MathUtils.NearlyEqual(r.MaxCondition, maxCondition)); if (existing == null) { - RequiredItems.Add(new RequiredItem(matchingItems, count, minCondition, useCondition)); + RequiredItems.Add(new RequiredItem(matchingItems, count, minCondition, maxCondition, useCondition)); } else { @@ -204,6 +214,8 @@ namespace Barotrauma public List<(string requiredTag, string swapTo)> ConnectedItemsToSwap = new List<(string requiredTag, string swapTo)>(); + public readonly Sprite SchematicSprite; + public int GetPrice(Location location = null) { int price = BasePrice; @@ -221,6 +233,9 @@ namespace Barotrauma { switch (subElement.Name.ToString().ToLowerInvariant()) { + case "schematicsprite": + SchematicSprite = new Sprite(subElement); + break; case "swapconnecteditem": ConnectedItemsToSwap.Add( (subElement.GetAttributeString("tag", ""), @@ -1203,9 +1218,9 @@ namespace Barotrauma } } - public List> GetBuyPricesUnder(int maxCost = 0) + public ImmutableDictionary GetBuyPricesUnder(int maxCost = 0) { - List> priceLocations = new List>(); + Dictionary priceLocations = new Dictionary(); foreach (KeyValuePair locationPrice in locationPrices) { PriceInfo priceInfo = locationPrice.Value; @@ -1220,19 +1235,19 @@ namespace Barotrauma } if (priceInfo.Price < maxCost || maxCost == 0) { - priceLocations.Add(new Tuple(locationPrice.Key, priceInfo)); + priceLocations.Add(locationPrice.Key, priceInfo); } } - return priceLocations; + return priceLocations.ToImmutableDictionary(); } - public List> GetSellPricesOver(int minCost = 0, bool sellingImportant = true) + public ImmutableDictionary GetSellPricesOver(int minCost = 0, bool sellingImportant = true) { - List> priceLocations = new List>(); + Dictionary priceLocations = new Dictionary(); if (!CanBeSold && sellingImportant) { - return priceLocations; + return priceLocations.ToImmutableDictionary(); } foreach (KeyValuePair locationPrice in locationPrices) @@ -1245,10 +1260,10 @@ namespace Barotrauma } if (priceInfo.Price > minCost) { - priceLocations.Add(new Tuple(locationPrice.Key, priceInfo)); + priceLocations.Add(locationPrice.Key, priceInfo); } } - return priceLocations; + return priceLocations.ToImmutableDictionary(); } public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index 7806c2708..7536b319d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -25,12 +25,12 @@ namespace Barotrauma private readonly float screenColorRange, screenColorDuration; private bool sparks, shockwave, flames, smoke, flash, underwaterBubble; - private bool playTinnitus; - private bool applyFireEffects; - private string[] ignoreFireEffectsForTags; - private bool ignoreCover; - private bool onlyInside; - private bool onlyOutside; + private readonly Color flashColor; + private readonly bool playTinnitus; + private readonly bool applyFireEffects; + private readonly string[] ignoreFireEffectsForTags; + private readonly bool ignoreCover; + private readonly bool onlyInside,onlyOutside; private readonly float flashDuration; private readonly float? flashRange; private readonly string decal; @@ -81,6 +81,7 @@ namespace Barotrauma flash = element.GetAttributeBool("flash", true); flashDuration = element.GetAttributeFloat("flashduration", 0.05f); if (element.Attribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); } + flashColor = element.GetAttributeColor("flashcolor", Color.LightYellow); EmpStrength = element.GetAttributeFloat("empstrength", 0.0f); BallastFloraDamage = element.GetAttributeFloat("ballastfloradamage", 0.0f); @@ -395,7 +396,7 @@ namespace Barotrauma for (int i = 0; i < structure.SectionCount; i++) { float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange); - if (distFactor <= 0.0f) continue; + if (distFactor <= 0.0f) { continue; } structure.AddDamage(i, damage * distFactor, attacker); @@ -412,6 +413,19 @@ namespace Barotrauma if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f)) { + if (Level.Loaded?.LevelObjectManager != null) + { + foreach (var levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(worldPosition, worldRange)) + { + if (levelObject.Prefab.TakeLevelWallDamage) + { + float distFactor = 1.0f - (Vector2.Distance(levelObject.WorldPosition, worldPosition) / worldRange); + if (distFactor <= 0.0f) { continue; } + levelObject.AddDamage(levelWallDamage * distFactor, 1.0f, null); + } + } + } + for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--) { if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index eee503110..93b0bc920 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -745,19 +745,22 @@ namespace Barotrauma Oxygen -= OxygenDeteriorationSpeed * deltaTime; - if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f) + if (FakeFireSources.Count > 0) { - for (int i = FakeFireSources.Count - 1; i >= 0; i--) + if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f) { - if (FakeFireSources[i].CausedByPsychosis) + for (int i = FakeFireSources.Count - 1; i >= 0; i--) { - FakeFireSources[i].Remove(); + if (FakeFireSources[i].CausedByPsychosis) + { + FakeFireSources[i].Remove(); + } } } + FireSource.UpdateAll(FakeFireSources, deltaTime); } FireSource.UpdateAll(FireSources, deltaTime); - FireSource.UpdateAll(FakeFireSources, deltaTime); foreach (Decal decal in decals) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index e73d6468a..7d82128c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -35,7 +35,7 @@ namespace Barotrauma Cave = 0x4, Ruin = 0x8, Wreck = 0x10, - BeaconStation = 0x20, + BeaconStation = 0x20, // Not used anywhere Abyss = 0x40, AbyssCave = 0x80 } @@ -389,7 +389,7 @@ namespace Barotrauma private void Generate(bool mirror) { - if (Loaded != null) { Loaded.Remove(); } + Loaded?.Remove(); Loaded = this; Generating = true; @@ -1154,7 +1154,7 @@ namespace Barotrauma } CreateWrecks(); - CreateBeaconStation(cells); + CreateBeaconStation(); EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); @@ -3007,20 +3007,30 @@ namespace Barotrauma return originalTag + "_" + shortSeed; } + public bool IsCloseToStart(Vector2 position, float minDist) => IsCloseToStart(position.ToPoint(), minDist); + public bool IsCloseToEnd(Vector2 position, float minDist) => IsCloseToEnd(position.ToPoint(), minDist); + + public bool IsCloseToStart(Point position, float minDist) + { + return MathUtils.LineSegmentToPointDistanceSquared(StartPosition.ToPoint(), StartExitPosition.ToPoint(), position) < minDist * minDist; + } + + public bool IsCloseToEnd(Point position, float minDist) + { + return MathUtils.LineSegmentToPointDistanceSquared(EndPosition.ToPoint(), EndExitPosition.ToPoint(), position) < minDist * minDist; + } + private Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type) { var tempSW = new Stopwatch(); // Min distance between a sub and the start/end/other sub. float minDistance = Sonar.DefaultSonarRange; - float squaredMinDistance = minDistance * minDistance; - Vector2 start = startPosition.ToVector2(); - Vector2 end = endPosition.ToVector2(); var waypoints = WayPoint.WayPointList.Where(wp => wp.Submarine == null && wp.SpawnType == SpawnType.Path && - Vector2.DistanceSquared(wp.WorldPosition, start) > squaredMinDistance && - Vector2.DistanceSquared(wp.WorldPosition, end) > squaredMinDistance).ToList(); + !IsCloseToStart(wp.WorldPosition, minDistance) && + !IsCloseToEnd(wp.WorldPosition, minDistance)).ToList(); var subDoc = SubmarineInfo.OpenFile(contentFile.Path); Rectangle subBorders = Submarine.GetBorders(subDoc.Root); @@ -3163,7 +3173,7 @@ namespace Barotrauma else { var sp = spawnPoint; - if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < squaredMinDistance)) + if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < minDistance * minDistance)) { Debug.WriteLine($"Invalid position {spawnPoint}. Too close to other wreck(s)."); return false; @@ -3321,7 +3331,13 @@ namespace Barotrauma int minWreckCount = Math.Min(Loaded.GenerationParams.MinWreckCount, wreckFiles.Count); int maxWreckCount = Math.Min(Loaded.GenerationParams.MaxWreckCount, wreckFiles.Count); - int wreckCount = Rand.Range(minWreckCount, maxWreckCount, Rand.RandSync.Server); + int wreckCount = Rand.Range(minWreckCount, maxWreckCount + 1, Rand.RandSync.Server); + + if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireWreck) ?? false) + { + wreckCount = Math.Max(wreckCount, 1); + } + Wrecks = new List(wreckCount); for (int i = 0; i < wreckCount; i++) { @@ -3379,7 +3395,7 @@ namespace Barotrauma for (int i = 0; i < 2; i++) { - if (GameMain.GameSession.GameMode is PvPMode) { continue; } + if (GameMain.GameSession?.GameMode is PvPMode) { continue; } bool isStart = (i == 0) == !Mirrored; if (isStart) @@ -3561,7 +3577,7 @@ namespace Barotrauma } } - private void CreateBeaconStation(List mainPath) + private void CreateBeaconStation() { if (!LevelData.HasBeaconStation) { return; } var beaconStationFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.BeaconStation).ToList(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs index 652a65e26..673ce3a7b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs @@ -8,7 +8,7 @@ using System.Xml.Linq; namespace Barotrauma { - partial class LevelObject : ISpatialEntity + partial class LevelObject : ISpatialEntity, IDamageable, ISerializableEntity { public readonly LevelObjectPrefab Prefab; public Vector3 Position; @@ -21,6 +21,8 @@ namespace Barotrauma private int spriteIndex; + protected bool tookDamage; + public LevelObjectPrefab ActivePrefab; public PhysicsBody PhysicsBody @@ -37,11 +39,15 @@ namespace Barotrauma public bool NeedsNetworkSyncing { - get { return Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing); } + get + { + return tookDamage || (Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing)); + } set { if (Triggers == null) { return; } - Triggers.ForEach(t => t.NeedsNetworkSyncing = false); + Triggers.ForEach(t => t.NeedsNetworkSyncing = false); + tookDamage = false; } } @@ -50,6 +56,12 @@ namespace Barotrauma get; private set; } + public float Health + { + get; + private set; + } + public Sprite Sprite { get @@ -67,6 +79,10 @@ namespace Barotrauma public Submarine Submarine => null; + public string Name => Prefab?.Name ?? "LevelObject (null)"; + + public Dictionary SerializableProperties { get; } = new Dictionary(); + public Level.Cave ParentCave; public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f) @@ -75,6 +91,7 @@ namespace Barotrauma Position = position; Scale = scale; Rotation = rotation; + Health = prefab.Health; spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1; @@ -89,10 +106,13 @@ namespace Barotrauma if (PhysicsBody != null) { + PhysicsBody.UserData = this; PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition, -Rotation); PhysicsBody.BodyType = BodyType.Static; PhysicsBody.CollisionCategories = Physics.CollisionLevel; - PhysicsBody.CollidesWith = Physics.CollisionWall | Physics.CollisionCharacter; + PhysicsBody.CollidesWith = Prefab.TakeLevelWallDamage? + Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionProjectile : + Physics.CollisionWall | Physics.CollisionCharacter; } foreach (XElement triggerElement in prefab.LevelTriggerElements) @@ -111,6 +131,10 @@ namespace Barotrauma } var newTrigger = new LevelTrigger(triggerElement, new Vector2(position.X, position.Y) + triggerPosition, -rotation, scale, prefab.Name); + if (newTrigger.PhysicsBody != null) + { + newTrigger.PhysicsBody.UserData = this; + } int parentTriggerIndex = prefab.LevelTriggerElements.IndexOf(triggerElement.Parent); if (parentTriggerIndex > -1) { newTrigger.ParentTrigger = Triggers[parentTriggerIndex]; } Triggers.Add(newTrigger); @@ -135,7 +159,54 @@ namespace Barotrauma } partial void InitProjSpecific(); - + + public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true) + { + if (Health <= 0.0f) { return new AttackResult(0.0f); } + + float damage = 0.0f; + if (Prefab.TakeLevelWallDamage) + { + damage += attack.GetLevelWallDamage(deltaTime); + } + damage = Math.Max(Health, damage); + AddDamage(damage, deltaTime, attacker); + return new AttackResult(damage); + } + + public void AddDamage(float damage, float deltaTime, Entity attacker, bool isNetworkEvent = false) + { + if (Health <= 0.0f) { return; } + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkEvent) + { + return; + } + tookDamage |= !MathUtils.NearlyEqual(damage, 0.0f); + Health -= damage; + if (Health <= 0.0f) + { +#if CLIENT + if (GameMain.GameSession?.Level?.LevelObjectManager != null) + { + GameMain.GameSession.Level.LevelObjectManager.ForceRefreshVisibleObjects = true; + } +#endif + if (PhysicsBody != null) + { + PhysicsBody.Enabled = false; + } + foreach (LevelTrigger trigger in Triggers) + { + trigger.PhysicsBody.Enabled = false; + foreach (StatusEffect effect in trigger.StatusEffects) + { + if (effect.type != ActionType.OnBroken) { continue; } + effect.Apply(effect.type, deltaTime, attacker, this, worldPosition: WorldPosition); + } + } + } + } + public Vector2 LocalToWorld(Vector2 localPosition, float swingState = 0.0f) { Vector2 emitterPos = localPosition * Scale; @@ -169,6 +240,10 @@ namespace Barotrauma public void ServerWrite(IWriteMessage msg, Client c) { if (Triggers == null) { return; } + if (Prefab.TakeLevelWallDamage) + { + msg.WriteRangedSingle(MathHelper.Clamp(Health, 0.0f, Prefab.Health), 0.0f, Prefab.Health, 8); + } for (int j = 0; j < Triggers.Count; j++) { if (!Triggers[j].UseNetworkSyncing) { continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs index 3ab9b89e8..e4288f792 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -136,27 +136,18 @@ namespace Barotrauma if (prefab == null) { continue; } if (!suitableSpawnPositions.ContainsKey(prefab)) { + float minDistance = level.Size.X * 0.2f; + suitableSpawnPositions.Add(prefab, availableSpawnPositions.Where(sp => sp.SpawnPosTypes.Any(type => prefab.SpawnPos.HasFlag(type)) && sp.Length >= prefab.MinSurfaceWidth && - (prefab.AllowAtStart || !closeToStart(sp.GraphEdge.Center)) && - (prefab.AllowAtEnd || !closeToEnd(sp.GraphEdge.Center)) && + (prefab.AllowAtStart || !level.IsCloseToStart(sp.GraphEdge.Center, minDistance)) && + (prefab.AllowAtEnd || !level.IsCloseToEnd(sp.GraphEdge.Center, minDistance)) && (sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList()); spawnPositionWeights.Add(prefab, suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList()); - - bool closeToStart(Vector2 position) - { - float minDist = level.Size.X * 0.2f; - return MathUtils.LineSegmentToPointDistanceSquared(level.StartPosition.ToPoint(), level.StartExitPosition.ToPoint(), position.ToPoint()) < minDist * minDist; - } - bool closeToEnd(Vector2 position) - { - float minDist = level.Size.X * 0.2f; - return MathUtils.LineSegmentToPointDistanceSquared(level.EndPosition.ToPoint(), level.EndExitPosition.ToPoint(), position.ToPoint()) < minDist * minDist; - } } SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server); @@ -442,10 +433,10 @@ namespace Barotrauma public IEnumerable GetAllObjects(Vector2 worldPosition, float radius) { var minIndices = GetGridIndices(worldPosition - Vector2.One * radius); - if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) return Enumerable.Empty(); + if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) { return Enumerable.Empty(); } var maxIndices = GetGridIndices(worldPosition + Vector2.One * radius); - if (maxIndices.X < 0 || maxIndices.Y < 0) return Enumerable.Empty(); + if (maxIndices.X < 0 || maxIndices.Y < 0) { return Enumerable.Empty(); } minIndices.X = Math.Max(0, minIndices.X); minIndices.Y = Math.Max(0, minIndices.Y); @@ -460,6 +451,7 @@ namespace Barotrauma if (objectGrid[x, y] == null) { continue; } foreach (LevelObject obj in objectGrid[x, y]) { + if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; } objectsInRange.Add(obj); } } @@ -522,6 +514,7 @@ namespace Barotrauma obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval; } } + if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; } if (obj.Triggers != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs index d383f421c..b9b83f5d7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs @@ -264,6 +264,27 @@ namespace Barotrauma private set; } + [Serialize(false, true, description: "Can the object take damage from weapons/attacks that damage level walls."), Editable] + public bool TakeLevelWallDamage + { + get; + private set; + } + + [Serialize(false, true), Editable] + public bool HideWhenBroken + { + get; + private set; + } + + [Serialize(100.0f, true), Editable] + public float Health + { + get; + private set; + } + public string Identifier { get; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index 432d5b60a..6259d240d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -38,6 +38,10 @@ namespace Barotrauma /// Effects applied to entities that are inside the trigger /// private readonly List statusEffects = new List(); + public IEnumerable StatusEffects + { + get { return statusEffects; } + } /// /// Attacks applied to entities that are inside the trigger @@ -205,7 +209,7 @@ namespace Barotrauma PhysicsBody = new PhysicsBody(element, scale) { CollisionCategories = Physics.CollisionLevel, - CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall + CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall, }; PhysicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision; PhysicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation; @@ -523,6 +527,7 @@ namespace Barotrauma { foreach (StatusEffect effect in statusEffects) { + if (effect.type == ActionType.OnBroken) { continue; } Vector2? position = null; if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; } if (triggerer is Character character) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 9f76aa1e5..7ddec0380 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -181,27 +181,50 @@ namespace Barotrauma } } - public Mission SelectedMission + private readonly List selectedMissions = new List(); + public IEnumerable SelectedMissions { - get; - set; + get { return selectedMissions; } } - public int SelectedMissionIndex + public void SelectMission(Mission mission) { - get + if (!SelectedMissions.Contains(mission) && mission != null) { - if (SelectedMission == null) { return -1; } - return availableMissions.IndexOf(SelectedMission); + selectedMissions.Add(mission); } - set + } + + public void DeselectMission(Mission mission) + { + selectedMissions.Remove(mission); + } + + + public List GetSelectedMissionIndices() + { + List selectedMissionIndices = new List(); + foreach (Mission mission in SelectedMissions) { - if (value < 0 || value >= AvailableMissions.Count()) + if (availableMissions.Contains(mission)) { - SelectedMission = null; - return; + selectedMissionIndices.Add(availableMissions.IndexOf(mission)); } - SelectedMission = availableMissions[value]; + } + return selectedMissionIndices; + } + + public void SetSelectedMissionIndices(IEnumerable missionIndices) + { + selectedMissions.Clear(); + foreach (int missionIndex in missionIndices) + { + if (missionIndex < 0 || missionIndex >= availableMissions.Count) + { + DebugConsole.ThrowError($"Failed to select a mission in location \"{Name}\". Mission index out of bounds ({missionIndex}, available missions: {availableMissions.Count})"); + break; + } + selectedMissions.Add(availableMissions[missionIndex]); } } @@ -415,6 +438,12 @@ namespace Barotrauma { if (newType == Type) { return; } + if (newType == null) + { + DebugConsole.ThrowError($"Failed to change the type of the location \"{Name}\" to null.\n" + Environment.StackTrace.CleanupStackTrace()); + return; + } + DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType); Type = newType; @@ -573,6 +602,7 @@ namespace Barotrauma public void InstantiateLoadedMissions(Map map) { availableMissions.Clear(); + selectedMissions.Clear(); if (loadedMissions != null && loadedMissions.Any()) { foreach (LoadedMission loadedMission in loadedMissions) @@ -588,7 +618,7 @@ namespace Barotrauma } var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination }, Submarine.MainSub); availableMissions.Add(mission); - if (loadedMission.SelectedMission) { SelectedMission = mission; } + if (loadedMission.SelectedMission) { selectedMissions.Add(mission); } } loadedMissions = null; } @@ -612,7 +642,7 @@ namespace Barotrauma public void ClearMissions() { availableMissions.Clear(); - SelectedMissionIndex = -1; + selectedMissions.Clear(); } public bool HasOutpost() @@ -1180,7 +1210,7 @@ namespace Barotrauma missionsElement.Add(new XElement("mission", new XAttribute("prefabid", mission.Prefab.Identifier), new XAttribute("destinationindex", i), - new XAttribute("selected", mission == SelectedMission))); + new XAttribute("selected", selectedMissions.Contains(mission)))); } locationElement.Add(missionsElement); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 5f27063a4..77cd99589 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -24,7 +24,7 @@ namespace Barotrauma /// From -> To /// public Action OnLocationChanged; - public Action OnMissionSelected; + public Action> OnMissionsSelected; public Location EndLocation { get; private set; } @@ -44,9 +44,9 @@ namespace Barotrauma get { return Locations.IndexOf(SelectedLocation); } } - public int SelectedMissionIndex + public IEnumerable GetSelectedMissionIndices() { - get { return SelectedConnection == null ? -1 : CurrentLocation.SelectedMissionIndex; } + return SelectedConnection == null ? Enumerable.Empty() : CurrentLocation.GetSelectedMissionIndices(); } public LocationConnection SelectedConnection { get; private set; } @@ -388,8 +388,14 @@ namespace Barotrauma } } - LocationConnection[] connectionsBetweenZones = new LocationConnection[generationParams.DifficultyZones]; - foreach (var connection in Connections) + List[] connectionsBetweenZones = new List[generationParams.DifficultyZones]; + for (int i = 0; i < generationParams.DifficultyZones; i++) + { + connectionsBetweenZones[i] = new List(); + } + var shuffledConnections = Connections.ToList(); + shuffledConnections.Shuffle(Rand.RandSync.Server); + foreach (var connection in shuffledConnections) { int zone1 = GetZoneIndex(connection.Locations[0].MapPosition.X); int zone2 = GetZoneIndex(connection.Locations[1].MapPosition.X); @@ -401,17 +407,25 @@ namespace Barotrauma zone1 = temp; } - if (connectionsBetweenZones[zone1] == null) + if (generationParams.GateCount[zone1] == 0) { continue; } + + if (!connectionsBetweenZones[zone1].Any()) { - connectionsBetweenZones[zone1] = connection; + connectionsBetweenZones[zone1].Add(connection); } - else + else if (generationParams.GateCount[zone1] == 1) { - if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].CenterPos.Y - Height / 2)) + //if there's only one connection, place it at the center of the map + if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].First().CenterPos.Y - Height / 2)) { - connectionsBetweenZones[zone1] = connection; + connectionsBetweenZones[zone1].Clear(); + connectionsBetweenZones[zone1].Add(connection); } } + else if (connectionsBetweenZones[zone1].Count() < generationParams.GateCount[zone1]) + { + connectionsBetweenZones[zone1].Add(connection); + } } for (int i = Connections.Count - 1; i >= 0; i--) @@ -421,7 +435,9 @@ namespace Barotrauma if (zone1 == zone2) { continue; } if (zone1 == generationParams.DifficultyZones || zone2 == generationParams.DifficultyZones) { continue; } - if (!connectionsBetweenZones.Contains(Connections[i])) + if (generationParams.GateCount[Math.Min(zone1, zone2)] == 0) { continue; } + + if (!connectionsBetweenZones[Math.Min(zone1, zone2)].Contains(Connections[i])) { Connections.RemoveAt(i); } @@ -756,7 +772,7 @@ namespace Barotrauma OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection); } - public void SelectMission(int missionIndex) + public void SelectMission(IEnumerable missionIndices) { if (CurrentLocation == null) { @@ -765,23 +781,24 @@ namespace Barotrauma GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } - CurrentLocation.SelectedMissionIndex = missionIndex; - if (CurrentLocation.SelectedMission == null) { return; } + CurrentLocation.SetSelectedMissionIndices(missionIndices); - if (CurrentLocation.SelectedMission.Locations[0] != CurrentLocation || - CurrentLocation.SelectedMission.Locations[1] != CurrentLocation) + foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList()) { - if (SelectedConnection == null) { return; } - //the destination must be the same as the destination of the mission - if (CurrentLocation.SelectedMission != null && - CurrentLocation.SelectedMission.Locations[1] != SelectedLocation) + if (selectedMission.Locations[0] != CurrentLocation || + selectedMission.Locations[1] != CurrentLocation) { - CurrentLocation.SelectedMissionIndex = -1; + if (SelectedConnection == null) { return; } + //the destination must be the same as the destination of the mission + if (selectedMission.Locations[1] != SelectedLocation) + { + CurrentLocation.DeselectMission(selectedMission); + } } } - OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission); + OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions); } public void SelectRandomLocation(bool preferUndiscovered) @@ -977,6 +994,12 @@ namespace Barotrauma string prevName = location.Name; var newType = LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase)); + if (newType == null) + { + DebugConsole.ThrowError($"Failed to change the type of the location \"{location.Name}\". Location type \"{change.ChangeToType}\" not found."); + return; + } + if (newType.OutpostTeam != location.Type.OutpostTeam || newType.HasOutpost != location.Type.HasOutpost) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/MapGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/MapGenerationParams.cs index 6e9194dd6..3c47acbd5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/MapGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/MapGenerationParams.cs @@ -67,6 +67,8 @@ namespace Barotrauma [Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)] public float ConnectionIndicatorDisplacementMultiplier { get; set; } + public int[] GateCount { get; private set; } + #if CLIENT [Serialize(0.75f, true), Editable(DecimalCount = 2)] @@ -201,6 +203,16 @@ namespace Barotrauma { SerializableProperties = SerializableProperty.DeserializeProperties(this, element); + GateCount = element.GetAttributeIntArray("gatecount", null) ?? element.GetAttributeIntArray("GateCount", null); + if (GateCount == null) + { + GateCount = new int[DifficultyZones]; + for (int i = 0; i < DifficultyZones; i++) + { + GateCount[i] = 1; + } + } + foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 2734b83dd..e9517fff8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -420,7 +420,7 @@ namespace Barotrauma if (cloneItem == null) { continue; } var door = cloneItem.GetComponent(); - if (door != null) { door.RefreshLinkedGap(); } + door?.RefreshLinkedGap(); var cloneWire = cloneItem.GetComponent(); if (cloneWire == null) continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs index bef44a09f..9980cf459 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -1447,7 +1447,7 @@ namespace Barotrauma } else { - npc.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier; + npc.AddStaticHealthMultiplier(humanPrefab.HealthMultiplier); } humanPrefab.GiveItems(npc, outpost, Rand.RandSync.Server); foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 6c7fe54dc..6116a8eeb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -353,10 +353,7 @@ namespace Barotrauma } #if CLIENT - if (convexHulls!=null) - { - convexHulls.ForEach(x => x.Move(amount)); - } + convexHulls?.ForEach(x => x.Move(amount)); #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 6df700c27..f31ae6f5c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -77,6 +77,7 @@ namespace Barotrauma private static Vector2 lastPickedPosition; private static float lastPickedFraction; + private static Fixture lastPickedFixture; private static Vector2 lastPickedNormal; private Vector2 prevPosition; @@ -99,6 +100,11 @@ namespace Barotrauma get { return lastPickedFraction; } } + public static Fixture LastPickedFixture + { + get { return lastPickedFixture; } + } + public static Vector2 LastPickedNormal { get { return lastPickedNormal; } @@ -669,6 +675,7 @@ namespace Barotrauma float closestFraction = 1.0f; Vector2 closestNormal = Vector2.Zero; + Fixture closestFixture = null; Body closestBody = null; if (allowInsideFixture) { @@ -682,13 +689,15 @@ namespace Barotrauma closestFraction = 0.0f; closestNormal = Vector2.Normalize(rayEnd - rayStart); - if (fixture.Body != null) closestBody = fixture.Body; + closestFixture = fixture; + if (fixture.Body != null) { closestBody = fixture.Body; } return false; }, ref aabb); if (closestFraction <= 0.0f) { lastPickedPosition = rayStart; lastPickedFraction = closestFraction; + lastPickedFixture = closestFixture; lastPickedNormal = closestNormal; return closestBody; } @@ -702,6 +711,7 @@ namespace Barotrauma { closestFraction = fraction; closestNormal = normal; + closestFixture = fixture; if (fixture.Body != null) closestBody = fixture.Body; } return fraction; @@ -709,6 +719,7 @@ namespace Barotrauma lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; lastPickedFraction = closestFraction; + lastPickedFixture = closestFixture; lastPickedNormal = closestNormal; return closestBody; @@ -752,6 +763,7 @@ namespace Barotrauma lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction; lastPickedFraction = fraction; lastPickedNormal = normal; + lastPickedFixture = fixture; } //continue return -1; @@ -772,6 +784,7 @@ namespace Barotrauma lastPickedPosition = rayStart; lastPickedFraction = 0.0f; lastPickedNormal = Vector2.Normalize(rayEnd - rayStart); + lastPickedFixture = fixture; bodies.Add(fixture.Body); bodyDist[fixture.Body] = 0.0f; return false; @@ -828,6 +841,7 @@ namespace Barotrauma { Body closestBody = null; float closestFraction = 1.0f; + Fixture closestFixture = null; Vector2 closestNormal = Vector2.Zero; if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f) @@ -847,6 +861,8 @@ namespace Barotrauma if (ignoreSubs && fixture.Body.UserData is Submarine) { return -1; } if (ignoreBranches && fixture.Body.UserData is VineTile) { return -1; } if (fixture.Body.UserData as string == "ruinroom") { return -1; } + //the hulls have solid fixtures in the submarine's world space collider, ignore them + if (fixture.UserData is Hull) { return -1; } if (fixture.Body.UserData is Structure structure) { if (structure.IsPlatform || structure.StairDirection != Direction.None) { return -1; } @@ -861,6 +877,7 @@ namespace Barotrauma { closestBody = fixture.Body; closestFraction = fraction; + closestFixture = fixture; closestNormal = normal; } return closestFraction; @@ -870,6 +887,7 @@ namespace Barotrauma lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction; lastPickedFraction = closestFraction; + lastPickedFixture = closestFixture; lastPickedNormal = closestNormal; return closestBody; } @@ -944,19 +962,23 @@ namespace Barotrauma mapEntity.Move(HiddenSubPosition); } - foreach (Item item in Item.ItemList) + for (int i = 0; i < 2; i++) { - if (bodyItems.Contains(item)) + foreach (Item item in Item.ItemList) { - item.Submarine = this; - if (Position == Vector2.Zero) item.Move(-HiddenSubPosition); + //two passes: flip docking ports on the 2nd pass because the doors need to be correctly flipped for the port's orientation to be determined correctly + if ((item.GetComponent() != null) == (i == 0)) { continue; } + if (bodyItems.Contains(item)) + { + item.Submarine = this; + if (Position == Vector2.Zero) { item.Move(-HiddenSubPosition); } + } + else if (item.Submarine != this) + { + continue; + } + item.FlipX(true); } - else if (item.Submarine != this) - { - continue; - } - - item.FlipX(true); } Item.UpdateHulls(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs index d60b084ea..4894b2a32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs @@ -79,7 +79,6 @@ namespace Barotrauma.Networking TRAITOR_MESSAGE, MISSION, EVENTACTION, - RESET_UPGRADES, //inform the clients that the upgrades on the submarine have been reset CREW, //anything related to managing bots in multiplayer READY_CHECK //start, end and update a ready check } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs index a09c710fa..bee897452 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs @@ -186,11 +186,7 @@ namespace Barotrauma.Networking if (updateReturnTimer > 1.0f) { updateReturnTimer = 0.0f; - - if (shuttleSteering != null) - { - shuttleSteering.SetDestinationLevelStart(); - } + shuttleSteering?.SetDestinationLevelStart(); UpdateReturningProjSpecific(deltaTime); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index ab646b389..cd5933994 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -69,21 +69,24 @@ namespace Barotrauma this.conditionType = conditionType; } - private ConditionType conditionType; + private readonly ConditionType conditionType; public enum ConditionType { //These need to exist at compile time, so it is a little awkward //I would love to see a better way to do this - AllowLinkingWifiToChat + AllowLinkingWifiToChat, + IsSwappableItem } - public bool IsEditable() + public bool IsEditable(ISerializableEntity entity) { switch (conditionType) { case ConditionType.AllowLinkingWifiToChat: return GameMain.NetworkMember?.ServerSettings?.AllowLinkingWifiToChat ?? true; + case ConditionType.IsSwappableItem: + return entity is Item item && item.Prefab.SwappableItem != null; } return false; } @@ -766,6 +769,10 @@ namespace Barotrauma if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property)) { FixValue(property, entity, attribute); + if (property.Name == nameof(ItemComponent.Msg) && entity is ItemComponent component) + { + component.ParseMsg(); + } } else if (entity is Item item1) { @@ -774,6 +781,10 @@ namespace Barotrauma if (component.SerializableProperties.TryGetValue(attributeName, out SerializableProperty componentProperty)) { FixValue(componentProperty, component, attribute); + if (componentProperty.Name == nameof(ItemComponent.Msg)) + { + ((ItemComponent)component).ParseMsg(); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs index 5ff8bbccd..6c0eccc0e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs @@ -28,7 +28,7 @@ namespace Barotrauma return spr; } return null; - }).Where(s => s!=null).ToList(); + }).Where(s => s != null).ToList(); } return retVal; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs index 65891b222..1881729df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs @@ -46,6 +46,8 @@ namespace Barotrauma { var value = (float) OriginalValue; + if (level == 0) { return value; } + if (Multiplier[^1] != '%') { float multiplier = ParseValue(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/CrossThread.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/CrossThread.cs similarity index 69% rename from Barotrauma/BarotraumaClient/ClientSource/Utils/CrossThread.cs rename to Barotrauma/BarotraumaShared/SharedSource/Utils/CrossThread.cs index bf5eb95c1..c1225eb7b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/CrossThread.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/CrossThread.cs @@ -59,5 +59,25 @@ namespace Barotrauma newTask.PerformWait(); } } + + public static void AddOnMainThread(this List list, T element) + { + RequestExecutionOnMainThread(() => { list.Add(element); }); + } + + public static void AddRangeOnMainThread(this List list, IEnumerable elements) + { + RequestExecutionOnMainThread(() => { list.AddRange(elements); }); + } + + public static void RemoveOnMainThread(this List list, T element) + { + RequestExecutionOnMainThread(() => { list.Remove(element); }); + } + + public static void ClearOnMainThread(this List list) + { + RequestExecutionOnMainThread(() => { list.Clear(); }); + } } } diff --git a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub index 1762ca22c..9e6500f26 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub and b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub index b1ad35355..fe459925d 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub and b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub index d4796a526..09ae292f8 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/DugongChaingun.sub b/Barotrauma/BarotraumaShared/Submarines/DugongChaingun.sub deleted file mode 100644 index 12fb83660..000000000 Binary files a/Barotrauma/BarotraumaShared/Submarines/DugongChaingun.sub and /dev/null differ diff --git a/Barotrauma/BarotraumaShared/Submarines/DugongPulseLaser.sub b/Barotrauma/BarotraumaShared/Submarines/DugongPulseLaser.sub deleted file mode 100644 index 90d71e0ff..000000000 Binary files a/Barotrauma/BarotraumaShared/Submarines/DugongPulseLaser.sub and /dev/null differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub b/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub index 3c5994800..9f6a1c043 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub and b/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 7a5dc04a7..0589a5be5 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/HumpbackPirate2.0.sub b/Barotrauma/BarotraumaShared/Submarines/HumpbackPirate2.0.sub deleted file mode 100644 index ed1d89f97..000000000 Binary files a/Barotrauma/BarotraumaShared/Submarines/HumpbackPirate2.0.sub and /dev/null differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub index 695dfc9dd..563ea6cd4 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub and b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub index 28b1c9410..648425a0b 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index 9b5517749..f9ef5c781 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/R-29.sub b/Barotrauma/BarotraumaShared/Submarines/R-29.sub index 4bfa0e876..053269bfd 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/R-29.sub and b/Barotrauma/BarotraumaShared/Submarines/R-29.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index fd371a320..0d6e002ef 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub index 943887519..aea6bb39b 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index 5ccacb192..f9293f457 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub index bdd414450..14bad2a27 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 1e2dab5ae..33ee75e58 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,78 @@ +------------------------------------------------------------------------------------------------------ +v0.1400.1.0 (unstable) +--------------------------------------------------------------------------------------------------------- + +Changes: +- Improvements/balancing to the pulse laser and chaingun. +- Option to choose multiple missions per level. +- Added some hardpoints to the vanilla subs. +- Replaced the old railgun loaders with the new smaller versions. +- Improvements to the weapon customization menu. +- Docking ports can't be rewired in outpost levels because it can be abused to force to submarine to depart from the outpost. +- The ice shards and exploding mushrooms in caves can be destroyed with weapons and explosives. +- The pirate missions can no longer be completed by exploding the reactor: all the pirates need to be killed for the mission to be successful. +- Changed the way diving suits are picked up: now LMB equips the suit like it did before, and the suits can be picked up with E. +- Taking items that contain stolen items counts as stealing, so you can't for example put a toolbox inside an outpost cabinet, load it full of items and then take it. +- Added a button for resetting in-game hints to the settings. +- Mention diving suits' depth limits in their descriptions. +- Modified the locked path tooltips to mention the officer in the outpost. +- Reset reactor fission rate, turbine output and temperature to optimal levels at the start of a round. Prevents the reactor from catching fire at the start of a round if it was being operated at a high fission rate at the end of the previous round. +- Increased canister shell pellet count from 7 to 10, increased pellet damage from 22 to 25, reduced spread from 25 to 20. +- Option to disable swapping specific weapons in the sub editor. +- Items that are set to be hidden in-game can't be swapped. +- Removed ability to drag and drop seeds into planters. +- Added ability to load .sub, .xml, .png and .jpeg files in sub editor by dragging and dropping them onto the game window. +- Added new biome-specific background noise tracks. +- Made upgrades affect all submarines. Old purchased submarines will have their upgrades overridden by the currently loaded submarine's upgrades. +- Improved the command interface minimap view by adding connector lines between icons and item positions to better visualize which item each icon is linked to. +- Projectiles go through severed limbs. + +Fixes: +- Fixed rotation of mirrored items getting messed up when saving and reloading a sub. +- Fixed inability to detach items attached outside the sub. +- Removed colliders from decorative pirate submarine pieces. +- Fixed pirate bandana sprite. +- Fixed depth decoy sprite. +- Fixed items that are inside a hull being difficult to target from outside the sub (docking ports/hatches in particular). +- Fixed crashing when trying to load a campaign save that contains pets that can't be found (e.g. if you've saved while using a mod that adds custom pets and try to load the save in the vanilla game). +- Fixed crashing when trying to change a location's type to a type that can't be found (e.g. if a mod includes custom location types which are configured incorrectly). +- Fixed "are you sure you want to depart without a mission" prompt popping up even if there's no missions available. +- Fixed pirate missions not ending automatically when docking with the destination outpost in mission mode. +- Fixed client-side "index out of range" errors when a pirate gets assigned the "navigatetactical" order. +- Fixed health bars showing up through walls. +- Fixed main sub's docking ports not showing up on the respawn shuttle's sonar. +- Fixed abandoned outposts' docking ports not showing up on sonar. +- Fixed handheld sonar showing the enemy sub in PvP mode when used outside the main sub. +- Fixed how weapons are displayed on the outpost display shelves. +- Fixed location portrait overlapping with the texts in the tab menu's mission tab. +- Fixed horizontal docking ports sometimes docking from the wrong side in mirrored subs. +- Fixed mouse wheel zooming the nav terminal view when the server log (or any other UI element) is blocking it. +- Fixed chainguns not damaging items (e.g. eggs, piezo crystals). +- Fixed Watcher always spawning at the start when the difficulty is between 10 and 20. +- Fixed wrecks never spawning in certain biomes, even if there's a wreck mission selected. +- Fixed legacy railgun loaders exploding the shells. +- Fixed campaign's initial text popup getting stuck if the sub automatically undocks at the start of a round. +- Swapped weapons inherit the scale, rotation, sprite depth and sprite color from the previously installed item. +- Non-empty items (ammo boxes, fuel rods, etc) can't be recycled. +- Fixed afflictions applying their status effects multiple times, causing afflictions that apply other afflictions way faster than they should (e.g. opiate withdrawal caused by opiate addiction, burns caused by radiation sickness). +- Fixed all sonar markers of a given mission displaying the same distance reading. +- Fixed command interface minimap tooltips not being displayed. +- Fixed bot orders not being saved in multiplayer. +- Disabled the tab menu missions tab in the test game modes (e.g. sub editor) to fix a related crash. +- Made airlock door assembly behave a bit more reliably. The circuit in the assembly simply toggles the state of both doors, meaning that one of the doors needs to always be closed and the other open for the logic to work correctly. If using the assembly in a respawn shuttle, it'd break when the shuttle leaves and it's doors are forced to close. +- Fixed inability to go through docking ports when the door/hatch at the other side is broken. +- Fixed other entities' sprites disappearing when reloading a sprite in the sub editor. +- Fixed Remora drone's docking hatch being repairable with a welding tool instead of a wrench. +- Fixed some of Remora drone's walls being transparent. + +Modding: +- Made it possible to use StatusHUD components in non-equippable items like turrets. +- Fixed scripted event's StatusEffectAction not being able to target ItemComponents. +- Added support for making triggering scripted events. +- Option to configure the color of an explosion's flash. +- Option to configure the number of gates between biomes (see MapGenerationParameters.xml). +- Fixed "publishing [mod] in the steam workshop failed" error when trying to publish a mod that contains enemy subs. + ------------------------------------------------------------------------------------------------------ v0.1400.0.0 (unstable) --------------------------------------------------------------------------------------------------------- diff --git a/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Delaunay/DelaunayTriangle.cs b/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Delaunay/DelaunayTriangle.cs index 2eda5b482..4352e34c7 100644 --- a/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Delaunay/DelaunayTriangle.cs +++ b/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Delaunay/DelaunayTriangle.cs @@ -211,10 +211,7 @@ namespace FarseerPhysics.Common.Decomposition.CDT.Delaunay for (int i = 0; i < 3; i++) { t = Neighbors[i]; - if (t != null) - { - t.ClearNeighbor(this); - } + t?.ClearNeighbor(this); } ClearNeighbors(); Points[0] = Points[1] = Points[2] = null; diff --git a/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Delaunay/Sweep/DTSweep.cs b/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Delaunay/Sweep/DTSweep.cs index df5dacf35..ef50f2a88 100644 --- a/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Delaunay/Sweep/DTSweep.cs +++ b/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Delaunay/Sweep/DTSweep.cs @@ -491,10 +491,7 @@ namespace FarseerPhysics.Common.Decomposition.CDT.Delaunay.Sweep { triangle.MarkConstrainedEdge(index); triangle = triangle.Neighbors[index]; - if (triangle != null) - { - triangle.MarkConstrainedEdge(ep, eq); - } + triangle?.MarkConstrainedEdge(ep, eq); return true; } return false; diff --git a/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Polygon/Polygon.cs b/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Polygon/Polygon.cs index f39fe5be3..0bf8aa999 100644 --- a/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Polygon/Polygon.cs +++ b/Libraries/Farseer Physics Engine 3.5/Common/Decomposition/CDT/Polygon/Polygon.cs @@ -187,10 +187,7 @@ namespace FarseerPhysics.Common.Decomposition.CDT.Polygon public void ClearSteinerPoints() { - if (_steinerPoints != null) - { - _steinerPoints.Clear(); - } + _steinerPoints?.Clear(); } /// diff --git a/Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Store/GAStore.cs b/Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Store/GAStore.cs index b252ab126..2575cd43a 100644 --- a/Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Store/GAStore.cs +++ b/Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Store/GAStore.cs @@ -201,15 +201,9 @@ namespace GameAnalyticsSDK.Net.Store } finally { - if(command != null) - { - command.Dispose(); - } + command?.Dispose(); - if(transaction != null) - { - transaction.Dispose(); - } + transaction?.Dispose(); } // Return results diff --git a/Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Utilities/SimpleJSON.cs b/Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Utilities/SimpleJSON.cs index 160760eb7..9f4faaad3 100644 --- a/Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Utilities/SimpleJSON.cs +++ b/Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Utilities/SimpleJSON.cs @@ -595,10 +595,7 @@ namespace GameAnalyticsSDK.Net.Utilities break; } stack.Push(new JSONObject()); - if (ctx != null) - { - ctx.Add(TokenName, stack.Peek()); - } + ctx?.Add(TokenName, stack.Peek()); TokenName = ""; Token.Length = 0; ctx = stack.Peek(); @@ -612,10 +609,7 @@ namespace GameAnalyticsSDK.Net.Utilities } stack.Push(new JSONArray()); - if (ctx != null) - { - ctx.Add(TokenName, stack.Peek()); - } + ctx?.Add(TokenName, stack.Peek()); TokenName = ""; Token.Length = 0; ctx = stack.Peek();