diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index 9a8d2b202..d96f35a87 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -116,7 +116,7 @@ namespace Barotrauma Stretch = true }; - var skills = Job.Skills; + var skills = Job.GetSkills().ToList(); skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level)); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("skills"), string.Empty), font: font) { Padding = Vector4.Zero }; @@ -560,17 +560,7 @@ namespace Barotrauma ch.SetPersonalityTrait(); if (ch.Job != null) { - foreach (KeyValuePair skill in skillLevels) - { - Skill matchingSkill = ch.Job.Skills.Find(s => s.Identifier == skill.Key); - if (matchingSkill == null) - { - ch.Job.Skills.Add(new Skill(skill.Key, skill.Value)); - continue; - } - matchingSkill.Level = skill.Value; - } - ch.Job.Skills.RemoveAll(s => !skillLevels.ContainsKey(s.Identifier)); + ch.Job.OverrideSkills(skillLevels); } ch.ExperiencePoints = inc.ReadUInt16(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index b7544631b..c11cd30b7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -694,6 +694,7 @@ namespace Barotrauma AssignRelayToServer("simulatedlatency", false); AssignRelayToServer("simulatedloss", false); AssignRelayToServer("simulatedduplicateschance", false); + AssignRelayToServer("simulatedlongloadingtime", false); AssignRelayToServer("storeinfo", false); #endif diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs index 37935913a..43b772d1c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement; namespace Barotrauma { @@ -21,6 +22,8 @@ namespace Barotrauma private GUIButton validateHiresButton; private GUIButton clearAllButton; + private PlayerBalanceElement? playerBalanceElement; + private List PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires; private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(ClientPermissions.ManageHires); @@ -157,23 +160,7 @@ namespace Barotrauma RelativeSpacing = 0.02f }; - var playerBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), pendingAndCrewMainGroup.RectTransform), childAnchor: Anchor.TopRight) - { - RelativeSpacing = 0.005f - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), - TextManager.Get("campaignstore.balance"), font: GUIStyle.Font, textAlignment: Alignment.BottomRight) - { - AutoScaleVertical = true, - ForceUpperCase = ForceUpperCase.Yes - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), - "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopRight) - { - AutoScaleVertical = true, - TextScale = 1.1f, - TextGetter = () => TextManager.FormatCurrency(campaign.Wallet.Balance) - }; + playerBalanceElement = CampaignUI.AddBalanceElement(pendingAndCrewMainGroup, new Vector2(1.0f, 0.75f / 14.0f)); var pendingAndCrewGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center, parent: new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), pendingAndCrewMainGroup.RectTransform) @@ -344,7 +331,7 @@ namespace Barotrauma Color? jobColor = null; if (characterInfo.Job != null) { - skill = characterInfo.Job?.PrimarySkill ?? characterInfo.Job.Skills.OrderByDescending(s => s.Level).FirstOrDefault(); + skill = characterInfo.Job?.PrimarySkill ?? characterInfo.Job.GetSkills().OrderByDescending(s => s.Level).FirstOrDefault(); jobColor = characterInfo.Job.Prefab.UIColor; } @@ -547,8 +534,8 @@ namespace Barotrauma GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.475f), mainGroup.RectTransform), isHorizontal: true); GUILayoutGroup skillNameGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), skillGroup.RectTransform)); GUILayoutGroup skillLevelGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1.0f), skillGroup.RectTransform)); - List characterSkills = characterInfo.Job.Skills; - blockHeight = 1.0f / characterSkills.Count; + var characterSkills = characterInfo.Job.GetSkills(); + blockHeight = 1.0f / characterSkills.Count(); foreach (Skill skill in characterSkills) { new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillNameGroup.RectTransform), TextManager.Get("SkillName." + skill.Identifier)); @@ -630,7 +617,7 @@ namespace Barotrauma total += ((InfoSkill)c.UserData).CharacterInfo.Salary; }); totalBlock.Text = TextManager.FormatCurrency(total); - bool enoughMoney = campaign == null || campaign.Wallet.CanAfford(total); + bool enoughMoney = campaign == null || campaign.CanAfford(total); totalBlock.TextColor = enoughMoney ? Color.White : Color.Red; validateHiresButton.Enabled = enoughMoney && HasPermission && pendingList.Content.RectTransform.Children.Any(); } @@ -652,7 +639,7 @@ namespace Barotrauma int total = nonDuplicateHires.Aggregate(0, (total, info) => total + info.Salary); - if (!campaign.Wallet.CanAfford(total)) { return false; } + if (!campaign.CanAfford(total)) { return false; } bool atLeastOneHired = false; foreach (CharacterInfo ci in nonDuplicateHires) @@ -792,6 +779,10 @@ namespace Barotrauma CreateUI(); UpdateLocationView(campaign.Map.CurrentLocation, false); } + else + { + playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement); + } (GUIComponent highlightedFrame, CharacterInfo highlightedInfo) = FindHighlightedCharacter(GUI.MouseOn); if (highlightedFrame != null && highlightedInfo != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index af00e06c9..4b2e186a8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -567,9 +567,9 @@ namespace Barotrauma GameMain.GameSession?.EventManager?.DrawPinnedEvent(spriteBatch); - if (HUDLayoutSettings.DebugDraw) HUDLayoutSettings.Draw(spriteBatch); + if (HUDLayoutSettings.DebugDraw) { HUDLayoutSettings.Draw(spriteBatch); } - if (GameMain.Client != null) GameMain.Client.Draw(spriteBatch); + GameMain.Client?.Draw(spriteBatch); if (Character.Controlled?.Inventory != null) { @@ -616,28 +616,46 @@ namespace Barotrauma } DrawSavingIndicator(spriteBatch); - - if (GameMain.WindowActive && !HideCursor) - { - spriteBatch.End(); - spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable); - - if (GameMain.GameSession?.CrewManager is { DraggedOrderPrefab: { SymbolSprite: { } orderSprite, Color: var color }, DragOrder: true }) - { - float spriteSize = Math.Max(orderSprite.size.X, orderSprite.size.Y); - orderSprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, color, orderSprite.size / 2f, scale: 32f / spriteSize * Scale); - } - - var sprite = MouseCursorSprites[MouseCursor] ?? MouseCursorSprites[CursorState.Default]; - sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f); - - spriteBatch.End(); - spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerState, rasterizerState: GameMain.ScissorTestEnable); - } + DrawCursor(spriteBatch); HideCursor = false; } } + public static void DrawMessageBoxesOnly(SpriteBatch spriteBatch) + { + bool anyDrawn = false; + foreach (var component in updateList) + { + component.DrawAuto(spriteBatch); + anyDrawn = true; + } + if (anyDrawn) + { + DrawCursor(spriteBatch); + } + } + + private static void DrawCursor(SpriteBatch spriteBatch) + { + if (GameMain.WindowActive && !HideCursor && MouseCursorSprites.Prefabs.Any()) + { + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable); + + if (GameMain.GameSession?.CrewManager is { DraggedOrderPrefab: { SymbolSprite: { } orderSprite, Color: var color }, DragOrder: true }) + { + float spriteSize = Math.Max(orderSprite.size.X, orderSprite.size.Y); + orderSprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, color, orderSprite.size / 2f, scale: 32f / spriteSize * Scale); + } + + var sprite = MouseCursorSprites[MouseCursor] ?? MouseCursorSprites[CursorState.Default]; + sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f); + + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerState, rasterizerState: GameMain.ScissorTestEnable); + } + } + public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, float aberrationStrength = 1.0f) { double aberrationT = (Timing.TotalTime * 0.5f); @@ -1204,6 +1222,17 @@ namespace Barotrauma } } + public static void UpdateGUIMessageBoxesOnly(float deltaTime) + { + GUIMessageBox.AddActiveToGUIUpdateList(); + RefreshUpdateList(); + UpdateMouseOn(); + foreach (var c in updateList) + { + c.UpdateAuto(deltaTime); + } + } + private static void UpdateMessages(float deltaTime) { lock (mutex) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs index 72084edbe..fb9756c54 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs @@ -36,6 +36,8 @@ namespace Barotrauma public string Tag { get; private set; } public bool Closed { get; private set; } + public bool DisplayInLoadingScreens; + public GUIImage Icon { get; @@ -451,6 +453,10 @@ namespace Barotrauma continue; } if (messageBox.type != type) { continue; } + if (!messageBox.DisplayInLoadingScreens && GameMain.Instance.LoadingScreenOpen) + { + continue; + } // These are handled separately in GUI.HandlePersistingElements() if (MessageBoxes[i].UserData as string == "verificationprompt") { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs index f295fdc0a..e4c047bff 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs @@ -395,14 +395,13 @@ namespace Barotrauma origin = TextSize * 0.5f; origin.X = 0; - if (textAlignment.HasFlag(Alignment.Left) && !overflowClipActive) + if (textAlignment.HasFlag(Alignment.Left)) { textPos.X = padding.X; } - if (textAlignment.HasFlag(Alignment.Right) || overflowClipActive) + if (textAlignment.HasFlag(Alignment.Right)) { textPos.X = rect.Width - padding.Z; - //origin.X = TextSize.X; } if (textAlignment.HasFlag(Alignment.Top)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs index aa18a98e6..34641e906 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs @@ -453,24 +453,7 @@ namespace Barotrauma if (CaretEnabled) { - if (textBlock.OverflowClipActive) - { - float left = textBlock.Rect.X + textBlock.Padding.X; - if (CaretScreenPos.X < left) - { - float diff = left - CaretScreenPos.X; - textBlock.TextPos = new Vector2(textBlock.TextPos.X + diff, textBlock.TextPos.Y); - CalculateCaretPos(); - } - - float right = textBlock.Rect.Right - textBlock.Padding.Z; - if (CaretScreenPos.X > right) - { - float diff = CaretScreenPos.X - right; - textBlock.TextPos = new Vector2(textBlock.TextPos.X - diff, textBlock.TextPos.Y); - CalculateCaretPos(); - } - } + HandleCaretBoundsOverflow(); caretTimer += deltaTime; caretVisible = ((caretTimer * 1000.0f) % 1000) < 500; if (caretVisible && caretPosDirty) @@ -496,6 +479,29 @@ namespace Barotrauma textBlock.State = State; } + private void HandleCaretBoundsOverflow() + { + if (textBlock.OverflowClipActive) + { + CalculateCaretPos(); + float left = textBlock.Rect.X + textBlock.Padding.X; + if (CaretScreenPos.X < left) + { + float diff = left - CaretScreenPos.X; + textBlock.TextPos = new Vector2(textBlock.TextPos.X + diff, textBlock.TextPos.Y); + CalculateCaretPos(); + } + + float right = textBlock.Rect.Right - textBlock.Padding.Z; + if (CaretScreenPos.X > right) + { + float diff = CaretScreenPos.X - right; + textBlock.TextPos = new Vector2(textBlock.TextPos.X - diff, textBlock.TextPos.Y); + CalculateCaretPos(); + } + } + } + private void DrawCaretAndSelection(SpriteBatch spriteBatch, GUICustomComponent customComponent) { if (!Visible) { return; } @@ -564,15 +570,33 @@ namespace Barotrauma { RemoveSelectedText(); } - Vector2 textPos = textBlock.TextPos; - bool wasOverflowClipActive = textBlock.OverflowClipActive; + using var _ = new TextPosPreservation(this); if (SetText(Text.Insert(CaretIndex, input))) { CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length); OnTextChanged?.Invoke(this, Text); + } + } + + private readonly ref struct TextPosPreservation + { + private readonly GUITextBox textBox; + private GUITextBlock textBlock => textBox.TextBlock; + private readonly bool wasOverflowClipActive; + private readonly Vector2 textPos; + + public TextPosPreservation(GUITextBox tb) + { + textBox = tb; + wasOverflowClipActive = tb.TextBlock.OverflowClipActive; + textPos = tb.TextBlock.TextPos; + } + + public void Dispose() + { if (textBlock.OverflowClipActive && wasOverflowClipActive && !MathUtils.NearlyEqual(textBlock.TextPos, textPos)) { - textBlock.TextPos = textPos + Vector2.UnitX * Font.MeasureString(input).X * TextBlock.TextScale; + textBlock.TextPos = textPos; } } } @@ -587,6 +611,8 @@ namespace Barotrauma switch (command) { case '\b' when !Readonly: //backspace + { + using var _ = new TextPosPreservation(this); if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl)) { SetText(string.Empty, false); @@ -605,6 +631,7 @@ namespace Barotrauma } OnTextChanged?.Invoke(this, Text); break; + } case (char)0x3: // ctrl-c CopySelectedText(); break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs index 35bcab726..b8026a67c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs @@ -1,13 +1,11 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; +using Barotrauma.Media; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; -using System.Xml.Linq; -using Barotrauma.Media; using System.Linq; -using Barotrauma.Extensions; -using System.Collections.Immutable; namespace Barotrauma { @@ -16,7 +14,7 @@ namespace Barotrauma private readonly Texture2D defaultBackgroundTexture, overlay; private readonly SpriteSheet decorativeGraph, decorativeMap; private Texture2D currentBackgroundTexture; - private Sprite noiseSprite; + private readonly Sprite noiseSprite; private string randText = ""; @@ -250,8 +248,8 @@ namespace Barotrauma } } } - } + GUI.DrawMessageBoxesOnly(spriteBatch); spriteBatch.End(); spriteBatch.Begin(blendState: BlendState.Additive); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs index 93f8803cb..211f69381 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Barotrauma.Extensions; using Microsoft.Xna.Framework; +using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement; namespace Barotrauma { @@ -180,6 +181,8 @@ namespace Barotrauma private const float refreshTimerMax = 3f; private float refreshTimer = 0; + private PlayerBalanceElement? playerBalanceElement; + public MedicalClinicUI(MedicalClinic clinic, GUIComponent parent) { medicalClinic = clinic; @@ -271,7 +274,7 @@ namespace Barotrauma healList.PriceBlock.Text = TextManager.FormatCurrency(totalCost); healList.PriceBlock.TextColor = GUIStyle.Red; healList.HealButton.Enabled = false; - if (medicalClinic.GetWallet().CanAfford(totalCost)) + if (medicalClinic.GetBalance() >= totalCost) { healList.PriceBlock.TextColor = GUIStyle.TextColorNormal; if (medicalClinic.PendingHeals.Any()) @@ -428,6 +431,7 @@ namespace Barotrauma { container.ClearChildren(); pendingHealList = null; + playerBalanceElement = null; int panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560)); GUIFrame paddedParent = new GUIFrame(new RectTransform(new Vector2(0.95f), container.RectTransform, Anchor.Center), style: null); @@ -458,19 +462,7 @@ namespace Barotrauma RelativeSpacing = 0.01f }; - GUILayoutGroup balanceLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), crewContent.RectTransform)); - GUITextBlock balanceLabel = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), balanceLayout.RectTransform), TextManager.Get("campaignstore.balance"), textAlignment: Alignment.BottomRight, font: GUIStyle.Font) - { - AutoScaleVertical = true, - ForceUpperCase = ForceUpperCase.Yes - }; - - GUITextBlock moneyLabel = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), balanceLayout.RectTransform), string.Empty, textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont) - { - TextGetter = () => TextManager.FormatCurrency(medicalClinic.GetWallet().Balance), - AutoScaleVertical = true, - TextScale = 1.1f - }; + playerBalanceElement = CampaignUI.AddBalanceElement(crewContent, new Vector2(1f, 0.1f)); GUIFrame crewBackground = new GUIFrame(new RectTransform(Vector2.One, crewContent.RectTransform)); @@ -577,7 +569,7 @@ namespace Barotrauma GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), footerLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterRight); GUIButton healButton = new GUIButton(new RectTransform(new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get("medicalclinic.heal")) { - Enabled = medicalClinic.PendingHeals.Any() && medicalClinic.GetWallet().CanAfford(medicalClinic.GetTotalCost()), + Enabled = medicalClinic.PendingHeals.Any() && medicalClinic.GetBalance() >= medicalClinic.GetTotalCost(), OnClicked = (button, _) => { button.Enabled = false; @@ -1050,6 +1042,10 @@ namespace Barotrauma { CreateUI(); } + else + { + playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement); + } refreshTimer += deltaTime; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index bdad817ee..e51c586c9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; +using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement; namespace Barotrauma { @@ -66,12 +67,15 @@ namespace Barotrauma private Point resolutionWhenCreated; + private PlayerBalanceElement? playerBalanceElement; + private Dictionary OwnedItems { get; } = new Dictionary(); private Location.StoreInfo ActiveStore { get; set; } private CargoManager CargoManager => campaignUI.Campaign.CargoManager; private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation; - private Wallet PlayerWallet => campaignUI.Campaign.Wallet; + private int Balance => campaignUI.Campaign.GetBalance(); + private bool IsBuying => activeTab switch { StoreTab.Buy => true, @@ -207,7 +211,7 @@ namespace Barotrauma { if (CurrentLocation?.Stores != null) { - if (CurrentLocation.GetStore(identifier) is { } store) + if (!identifier.IsEmpty && CurrentLocation.GetStore(identifier) is { } store) { ActiveStore = store; if (storeNameBlock != null) @@ -223,14 +227,45 @@ namespace Barotrauma else { ActiveStore = null; - string msg = $"Error selecting store with identifier \"{identifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location."; + string errorId, msg; + if (identifier.IsEmpty) + { + errorId = "Store.SelectStore:IdentifierEmpty"; + msg = $"Error selecting store at {CurrentLocation}: identifier is empty."; + } + else + { + errorId = "Store.SelectStore:StoreDoesntExist"; + msg = $"Error selecting store with identifier \"{identifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location."; + } DebugConsole.ShowError(msg); - GameAnalyticsManager.AddErrorEventOnce("Store.SelectStore:StoreDoesntExist", GameAnalyticsManager.ErrorSeverity.Error, msg); + GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg); } } else { ActiveStore = null; + string errorId = "", msg = ""; + if (campaignUI.Campaign.Map == null) + { + errorId = "Store.SelectStore:MapNull"; + msg = $"Error selecting store with identifier \"{identifier}\": Map is null."; + } + else if (CurrentLocation == null) + { + errorId = "Store.SelectStore:CurrentLocationNull"; + msg = $"Error selecting store with identifier \"{identifier}\": CurrentLocation is null."; + } + else if (CurrentLocation.Stores == null) + { + errorId = "Store.SelectStore:StoresNull"; + msg = $"Error selecting store with identifier \"{identifier}\": CurrentLocation.Stores is null."; + } + if (!msg.IsNullOrEmpty()) + { + DebugConsole.ShowError(msg); + GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg); + } } RefreshItemsToSell(); Refresh(); @@ -615,23 +650,7 @@ namespace Barotrauma }; // Player balance ------------------------------------------------ - var playerBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), shoppingCrateContent.RectTransform), childAnchor: Anchor.TopRight) - { - RelativeSpacing = 0.005f - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), - TextManager.Get("campaignstore.balance"), font: GUIStyle.Font, textAlignment: Alignment.BottomRight) - { - AutoScaleVertical = true, - ForceUpperCase = ForceUpperCase.Yes - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), - "", textColor: Color.White, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopRight) - { - AutoScaleVertical = true, - TextScale = 1.1f, - TextGetter = GetPlayerBalanceText - }; + playerBalanceElement = CampaignUI.AddBalanceElement(shoppingCrateContent, new Vector2(1.0f, 0.75f / 14.0f)); // Divider ------------------------------------------------ var dividerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f / 14.0f), shoppingCrateContent.RectTransform), style: null); @@ -663,7 +682,7 @@ namespace Barotrauma { CanBeFocused = false, TextScale = 1.1f, - TextGetter = () => IsBuying ? GetPlayerBalanceText() : GetMerchantBalanceText() + TextGetter = () => IsBuying ? CampaignUI.GetTotalBalance() : GetMerchantBalanceText() }; var totalContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true) @@ -712,8 +731,6 @@ namespace Barotrauma private LocalizedString GetMerchantBalanceText() => TextManager.FormatCurrency(ActiveStore?.Balance ?? 0); - private LocalizedString GetPlayerBalanceText() => TextManager.FormatCurrency(PlayerWallet.Balance); - private GUILayoutGroup CreateDealsGroup(GUIListBox parentList, int elementCount) { // Add 1 for the header @@ -2037,7 +2054,7 @@ namespace Barotrauma totalPrice += item.Quantity * ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo); } itemsToRemove.ForEach(i => itemsToPurchase.Remove(i)); - if (itemsToPurchase.None() || !PlayerWallet.CanAfford(totalPrice)) { return false; } + if (itemsToPurchase.None() || Balance < totalPrice) { return false; } CargoManager.PurchaseItems(ActiveStore.Identifier, itemsToPurchase, true); GameMain.Client?.SendCampaignState(); var dialog = new GUIMessageBox( @@ -2091,7 +2108,7 @@ namespace Barotrauma if (IsBuying) { shoppingCrateTotal.Text = TextManager.FormatCurrency(buyTotal); - shoppingCrateTotal.TextColor = !PlayerWallet.CanAfford(buyTotal) ? Color.Red : Color.White; + shoppingCrateTotal.TextColor = Balance < buyTotal ? Color.Red : Color.White; } else { @@ -2137,7 +2154,7 @@ namespace Barotrauma ActiveShoppingCrateList.Content.RectTransform.Children.Any() && activeTab switch { - StoreTab.Buy => PlayerWallet.CanAfford(buyTotal), + StoreTab.Buy => Balance >= buyTotal, StoreTab.Sell => CurrentLocation != null && sellTotal <= ActiveStore.Balance, StoreTab.SellSub => CurrentLocation != null && sellFromSubTotal <= ActiveStore.Balance, _ => false @@ -2151,6 +2168,7 @@ namespace Barotrauma ActiveShoppingCrateList.Content.RectTransform.Children.Any(); } + private int prevBalance; private float ownedItemsUpdateTimer = 0.0f, sellableItemsFromSubUpdateTimer = 0.0f; private const float timerUpdateInterval = 1.5f; private readonly Stopwatch updateStopwatch = new Stopwatch(); @@ -2166,6 +2184,8 @@ namespace Barotrauma } else { + playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement); + // Update the owned items at short intervals and check if the interface should be refreshed ownedItemsUpdateTimer += deltaTime; if (ownedItemsUpdateTimer >= timerUpdateInterval) @@ -2202,6 +2222,16 @@ namespace Barotrauma } } } + // Refresh the interface if balance changes and the buy tab is open + if (activeTab == StoreTab.Buy) + { + int currBalance = Balance; + if (prevBalance != currBalance) + { + needsBuyingRefresh = true; + prevBalance = currBalance; + } + } if (needsItemsToSellRefresh) { RefreshItemsToSell(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs index d4461b472..35ec743ca 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs @@ -4,6 +4,8 @@ using Microsoft.Xna.Framework; using System.Linq; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; +using System.Globalization; +using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement; namespace Barotrauma { @@ -31,7 +33,7 @@ namespace Barotrauma private readonly List subsToShow; private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage]; private SubmarineInfo selectedSubmarine = null; - private LocalizedString purchaseAndSwitchText, purchaseOnlyText, deliveryText, currentSubText, deliveryFeeText, priceText, switchText, missingPreviewText, currencyName; + private LocalizedString purchaseAndSwitchText, purchaseOnlyText, deliveryText, currentSubText, switchText, missingPreviewText, currencyName; private readonly RectTransform parent; private readonly Action closeAction; private Sprite pageIndicator; @@ -44,6 +46,8 @@ namespace Barotrauma private static readonly Color indicatorColor = new Color(112, 149, 129); private Point createdForResolution; + private PlayerBalanceElement? playerBalanceElement; + private struct SubmarineDisplayContent { public GUIFrame background; @@ -85,12 +89,10 @@ namespace Barotrauma { initialized = true; currentSubText = TextManager.Get("currentsub"); - deliveryFeeText = TextManager.Get("deliveryfee"); deliveryText = TextManager.Get("requestdeliverybutton"); switchText = TextManager.Get("switchtosubmarinebutton"); purchaseAndSwitchText = TextManager.Get("purchaseandswitch"); purchaseOnlyText = TextManager.Get("purchase"); - priceText = TextManager.Get("price"); if (transferService) { deliveryFee = CalculateDeliveryFee(); @@ -126,10 +128,7 @@ namespace Barotrauma content = new GUILayoutGroup(new RectTransform(new Point(background.Rect.Width - HUDLayoutSettings.Padding * 4, background.Rect.Height - HUDLayoutSettings.Padding * 4), background.RectTransform, Anchor.Center)) { AbsoluteSpacing = (int)(HUDLayoutSettings.Padding * 1.5f) }; GUITextBlock header = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), content.RectTransform), transferService ? TextManager.Get("switchsubmarineheader") : TextManager.GetWithVariable("outpostshipyard", "[location]", GameMain.GameSession.Map.CurrentLocation.Name), font: GUIStyle.LargeFont); header.CalculateHeightFromText(0, true); - GUITextBlock credits = new GUITextBlock(new RectTransform(Vector2.One, header.RectTransform), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) - { - TextGetter = CampaignUI.GetMoney - }; + playerBalanceElement = CampaignUI.AddBalanceElement(header, new Vector2(1.0f, 1.5f)); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), content.RectTransform), style: "HorizontalLine"); @@ -257,6 +256,10 @@ namespace Barotrauma { RefreshSubmarineDisplay(true); } + else + { + playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement); + } // Input if (PlayerInput.KeyHit(Keys.Left)) @@ -271,9 +274,22 @@ namespace Barotrauma public void RefreshSubmarineDisplay(bool updateSubs) { - if (!initialized) Initialize(); - if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) CreateGUI(); - if (updateSubs) UpdateSubmarines(); + if (!initialized) + { + Initialize(); + } + if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) + { + CreateGUI(); + } + else + { + playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement); + } + if (updateSubs) + { + UpdateSubmarines(); + } if (pageIndicators != null) { @@ -327,12 +343,12 @@ namespace Barotrauma }; submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName; - submarineDisplays[i].submarineClass.Text = $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}"))}"; + submarineDisplays[i].submarineClass.Text = TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}")); if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay)) { LocalizedString amountString = TextManager.FormatCurrency(subToDisplay.Price); - submarineDisplays[i].submarineFee.Text = priceText.Replace("[amount]", amountString).Replace("[currencyname]", string.Empty).TrimEnd(); + submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("price", "[amount]", amountString); } else { @@ -341,7 +357,7 @@ namespace Barotrauma if (deliveryFee > 0) { LocalizedString amountString = TextManager.FormatCurrency(deliveryFee); - submarineDisplays[i].submarineFee.Text = deliveryFeeText.Replace("[amount]", amountString).Replace("[currencyname]", string.Empty).TrimEnd(); + submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("deliveryfee", "[amount]", amountString); } else { @@ -577,7 +593,7 @@ namespace Barotrauma private void ShowTransferPrompt() { - if (!GameMain.GameSession.Campaign.Wallet.CanAfford(deliveryFee) && deliveryFee > 0) + if (!GameMain.GameSession.Campaign.CanAfford(deliveryFee) && deliveryFee > 0) { new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("notenoughmoneyfordeliverytext", ("[currencyname]", currencyName), @@ -625,7 +641,7 @@ namespace Barotrauma private void ShowBuyPrompt(bool purchaseOnly) { - if (!GameMain.GameSession.Campaign.Wallet.CanAfford(selectedSubmarine.Price)) + if (!GameMain.GameSession.Campaign.CanAfford(selectedSubmarine.Price)) { new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("notenoughmoneyforpurchasetext", ("[currencyname]", currencyName), diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 74c244c3a..8530291a5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -698,6 +698,12 @@ namespace Barotrauma Color = Color.Transparent }; + frame.OnSecondaryClicked += (component, data) => + { + NetLobbyScreen.CreateModerationContextMenu(client); + return true; + }; + var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true) { AbsoluteSpacing = 2 @@ -1057,7 +1063,7 @@ namespace Barotrauma return; } - bool hasMoneyPermissions = campaign.AllowedToManageCampaign(ClientPermissions.ManageMoney); + bool hasMoneyPermissions = CampaignMode.AllowedToManageWallets(); salarySlider.Enabled = hasMoneyPermissions; Wallet otherWallet; @@ -1402,6 +1408,12 @@ namespace Barotrauma private void CreateMissionInfo(GUIFrame infoFrame) { + if (Level.Loaded?.LevelData == null) + { + DebugConsole.ThrowError("Failed to display mission info in the tab menu (no level loaded).\n" + Environment.StackTrace); + return; + } + infoFrame.ClearChildren(); GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); int padding = (int)(0.0245f * missionFrame.Rect.Height); @@ -2016,7 +2028,7 @@ namespace Barotrauma { parent.Content.ClearChildren(); List skillNames = new List(); - foreach (Skill skill in character.Info.Job.Skills) + foreach (Skill skill in character.Info.Job.GetSkills()) { GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index cbdf71c9f..f694cb4d5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -11,6 +11,7 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; +using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement; // ReSharper disable UnusedVariable @@ -41,7 +42,7 @@ namespace Barotrauma private readonly CampaignUI campaignUI; private CampaignMode? Campaign => campaignUI.Campaign; - private Wallet PlayerWallet => Campaign?.Wallet ?? Wallet.Invalid; + private int PlayerBalance => Campaign?.GetBalance() ?? 0; private UpgradeTab selectedUpgradeTab = UpgradeTab.Upgrade; private GUIMessageBox? currectConfirmation; @@ -75,6 +76,8 @@ namespace Barotrauma private bool needsRefresh = true; + private PlayerBalanceElement? playerBalanceElement; + /// /// While set to true any call to will cause the buy button to be disabled and to not update the prices. /// This is to prevent us from buying another upgrade before the server has given us the new prices and causing potential syncing issues. @@ -293,9 +296,14 @@ namespace Barotrauma * |---------------------------------------------------------------------------------------------------| */ GUILayoutGroup rightLayout = new GUILayoutGroup(rectT(0.5f, 1, topHeaderLayout), childAnchor: Anchor.TopRight); - GUILayoutGroup priceLayout = new GUILayoutGroup(rectT(1, 0.8f, rightLayout), childAnchor: Anchor.Center) { RelativeSpacing = 0.08f }; - new GUITextBlock(rectT(1f, 0f, priceLayout), TextManager.Get("CampaignStore.Balance"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right); - new GUITextBlock(rectT(1f, 0f, priceLayout), TextManager.FormatCurrency(PlayerWallet.Balance), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right) { TextGetter = () => TextManager.FormatCurrency(PlayerWallet.Balance) }; + playerBalanceElement = CampaignUI.AddBalanceElement(rightLayout, new Vector2(1.0f, 0.8f)); + if (playerBalanceElement is { } balanceElement) + { + balanceElement.TotalBalanceContainer.OnAddedToGUIUpdateList += (_) => + { + playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement); + }; + } new GUIFrame(rectT(0.5f, 0.1f, rightLayout, Anchor.BottomRight), style: "HorizontalLine") { IgnoreLayoutGroups = true }; repairButton.OnClicked = upgradeButton.OnClicked = (button, o) => @@ -435,14 +443,14 @@ namespace Barotrauma return false; } - if (PlayerWallet.CanAfford(hullRepairCost)) + if (PlayerBalance >= hullRepairCost) { LocalizedString body = TextManager.GetWithVariable("WallRepairs.PurchasePromptBody", "[amount]", hullRepairCost.ToString()); currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () => { - if (PlayerWallet.Balance >= hullRepairCost) + if (PlayerBalance >= hullRepairCost) { - PlayerWallet.TryDeduct(hullRepairCost); + Campaign.TryPurchase(null, hullRepairCost); GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs"); Campaign.PurchasedHullRepairs = true; button.Enabled = false; @@ -470,14 +478,14 @@ namespace Barotrauma CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallitems"), "RepairItemsButton", itemRepairCost, (button, o) => { - if (PlayerWallet.Balance >= itemRepairCost && !Campaign.PurchasedItemRepairs) + if (PlayerBalance >= itemRepairCost && !Campaign.PurchasedItemRepairs) { LocalizedString body = TextManager.GetWithVariable("ItemRepairs.PurchasePromptBody", "[amount]", itemRepairCost.ToString()); currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () => { - if (PlayerWallet.Balance >= itemRepairCost && !Campaign.PurchasedItemRepairs) + if (PlayerBalance >= itemRepairCost && !Campaign.PurchasedItemRepairs) { - PlayerWallet.TryDeduct(itemRepairCost); + Campaign.TryPurchase(null, itemRepairCost); GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs"); Campaign.PurchasedItemRepairs = true; button.Enabled = false; @@ -516,14 +524,14 @@ namespace Barotrauma return false; } - if (PlayerWallet.CanAfford(shuttleRetrieveCost) && !Campaign.PurchasedLostShuttles) + if (PlayerBalance >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles) { LocalizedString body = TextManager.GetWithVariable("ReplaceLostShuttles.PurchasePromptBody", "[amount]", shuttleRetrieveCost.ToString()); currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () => { - if (PlayerWallet.Balance >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles) + if (PlayerBalance >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles) { - PlayerWallet.TryDeduct(shuttleRetrieveCost); + Campaign.TryPurchase(null, shuttleRetrieveCost); GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle"); Campaign.PurchasedLostShuttles = true; button.Enabled = false; @@ -581,13 +589,13 @@ namespace Barotrauma new GUITextBlock(rectT(1, 0, textLayout), title, font: GUIStyle.SubHeadingFont) { CanBeFocused = false, AutoScaleHorizontal = true }; new GUITextBlock(rectT(1, 0, textLayout), TextManager.FormatCurrency(price)); GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, contentLayout), childAnchor: Anchor.Center) { UserData = "buybutton" }; - new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { ClickSound = GUISoundType.HireRepairClick, Enabled = PlayerWallet.Balance >= price && !isDisabled, OnClicked = onPressed }; + new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { ClickSound = GUISoundType.HireRepairClick, Enabled = PlayerBalance >= price && !isDisabled, OnClicked = onPressed }; contentLayout.Recalculate(); buyButtonLayout.Recalculate(); if (disableElement) { - frameChild.Enabled = PlayerWallet.Balance >= price && !isDisabled; + frameChild.Enabled = PlayerBalance >= price && !isDisabled; } if (!HasPermission) @@ -972,7 +980,7 @@ namespace Barotrauma buttonStyle: isPurchased ? "WeaponInstallButton" : "StoreAddToCrateButton")); if (!(frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton buyButton)) { continue; } - if (PlayerWallet.CanAfford(price)) + if (PlayerBalance >= price) { buyButton.Enabled = true; buyButton.OnClicked += (button, o) => @@ -1602,7 +1610,7 @@ namespace Barotrauma if (button != null) { button.Enabled = currentLevel < prefab.MaxLevel; - if (WaitForServerUpdate || !campaign.Wallet.CanAfford(price)) + if (WaitForServerUpdate || campaign.GetBalance() < price) { button.Enabled = false; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index d03924def..252bbb5ad 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -671,6 +671,8 @@ namespace Barotrauma if (!TitleScreen.PlayingSplashScreen) { SoundPlayer.Update((float)Timing.Step); + GUI.ClearUpdateList(); + GUI.UpdateGUIMessageBoxesOnly((float)Timing.Step); } if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && @@ -1117,10 +1119,10 @@ namespace Barotrauma var msgBox = new GUIMessageBox(TextManager.Get("EditorDisclaimerTitle"), TextManager.Get("EditorDisclaimerText")); var linkHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), msgBox.Content.RectTransform)) { Stretch = true, RelativeSpacing = 0.025f }; linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height); - List<(LocalizedString Caption, LocalizedString Url)> links = new List<(LocalizedString, LocalizedString)>() + List<(LocalizedString Caption, string Url)> links = new List<(LocalizedString, string)>() { - (TextManager.Get("EditorDisclaimerWikiLink"), TextManager.Get("EditorDisclaimerWikiUrl")), - (TextManager.Get("EditorDisclaimerDiscordLink"), TextManager.Get("EditorDisclaimerDiscordUrl")), + (TextManager.Get("EditorDisclaimerWikiLink"), TextManager.Get("EditorDisclaimerWikiUrl").Fallback("https://barotraumagame.com/wiki").Value), + (TextManager.Get("EditorDisclaimerDiscordLink"), TextManager.Get("EditorDisclaimerDiscordUrl").Fallback("https://discordapp.com/invite/undertow").Value), }; foreach (var link in links) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs index 6b0927e40..4f9411739 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace Barotrauma @@ -28,6 +29,8 @@ namespace Barotrauma protected GUIFrame campaignUIContainer; public CampaignUI CampaignUI; + public static CancellationTokenSource StartRoundCancellationToken { get; private set; } + public bool ForceMapUI { get; @@ -99,6 +102,16 @@ namespace Barotrauma GameMain.Client.ConnectedClients.None(c => c.InGame && (c.IsOwner || c.HasPermission(permissions))); } + public static bool AllowedToManageWallets() + { + if (GameMain.Client == null) { return true; } + + return + GameMain.Client.HasPermission(ClientPermissions.ManageMoney) || + GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || + GameMain.Client.IsServerOwner; + } + public override void Draw(SpriteBatch spriteBatch) { if (overlayColor.A > 0) @@ -245,10 +258,11 @@ namespace Barotrauma GUI.ClearCursorWait(); + StartRoundCancellationToken = new CancellationTokenSource(); var loadTask = Task.Run(async () => { await Task.Yield(); - Rand.ThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId; + Rand.ThreadId = Thread.CurrentThread.ManagedThreadId; try { GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror); @@ -258,7 +272,7 @@ namespace Barotrauma roundSummaryScreen.LoadException = e; } Rand.ThreadId = 0; - }); + }, StartRoundCancellationToken.Token); TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) => { overlayColor = Color.Transparent; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index 2e35a7dac..0bde737f0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -37,6 +37,16 @@ namespace Barotrauma public Wallet PersonalWallet => Character.Controlled?.Wallet ?? Wallet.Invalid; public override Wallet Wallet => GetWallet(); + public override int GetBalance(Client client = null) + { + if (!AllowedToManageWallets()) + { + return PersonalWallet.Balance; + } + + return PersonalWallet.Balance + Bank.Balance; + } + public override Wallet GetWallet(Client client = null) { return PersonalWallet; @@ -913,6 +923,31 @@ namespace Barotrauma } } + public override bool TryPurchase(Client client, int price) + { + if (!AllowedToManageCampaign(ClientPermissions.ManageCampaign)) + { + return PersonalWallet.TryDeduct(price); + } + + int balance = PersonalWallet.Balance; + + if (balance >= price) + { + return PersonalWallet.TryDeduct(price); + } + + if (balance + Bank.Balance >= price) + { + int remainder = price - balance; + if (balance > 0) { PersonalWallet.Deduct(balance); } + Bank.Deduct(remainder); + return true ; + } + + return false; + } + public override void Save(XElement element) { //do nothing, the clients get the save files from the server diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs index cc05194e2..23083323d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs @@ -56,10 +56,9 @@ namespace Barotrauma.Items.Components ContentXElement spriteElement = limbElement.GetChildElement("sprite"); if (spriteElement == null) { continue; } - string spritePath = spriteElement.GetAttribute("texture").Value; - - spritePath = characterInfo.ReplaceVars(spritePath); + ContentPath contentPath = spriteElement.GetAttributeContentPath("texture"); + string spritePath = characterInfo.ReplaceVars(contentPath.Value); string fileName = Path.GetFileNameWithoutExtension(spritePath); //go through the files in the directory to find a matching sprite diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index e2df43900..42fc88545 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -584,19 +584,16 @@ namespace Barotrauma.Items.Components { availableCharge = 0.0f; availableCapacity = 0.0f; - if (item.Connections == null) { return; } - foreach (Connection c in item.Connections) + if (item.Connections == null || powerIn == null) { return; } + var recipients = powerIn.Recipients; + foreach (Connection recipient in recipients) { - var recipients = c.Recipients; - foreach (Connection recipient in recipients) - { - if (!recipient.IsPower || !recipient.IsOutput) { continue; } - var battery = recipient.Item?.GetComponent(); - if (battery == null) { continue; } - availableCharge += battery.Charge; - availableCapacity += battery.Capacity; - } - } + if (!recipient.IsPower || !recipient.IsOutput) { continue; } + var battery = recipient.Item?.GetComponent(); + if (battery == null || battery.Item.Condition <= 0.0f) { continue; } + availableCharge += battery.Charge; + availableCapacity += battery.Capacity; + } } /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 6224c628c..96c563ea2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -273,10 +273,16 @@ namespace Barotrauma foreach (string tag in readTags) { string[] s = tag.Split(':'); - if (s[0] == "name") - idName = s[1]; - if (s[0] == "job") - idJob = s[1]; + switch (s[0]) + { + case "name": + idName = s[1]; + break; + case "job": + case "jobid": + idJob = s[1]; + break; + } } if (idName != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index b993de620..7a16fca75 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -1550,9 +1550,10 @@ namespace Barotrauma DebugConsole.Log($"Received entity spawn message for item \"{itemName}\" (identifier: {itemIdentifier}, id: {itemId})"); - var itemPrefab = string.IsNullOrEmpty(itemIdentifier) ? - MapEntityPrefab.Find(itemName, null, showErrorMessages: false) as ItemPrefab : - MapEntityPrefab.Find(itemName, itemIdentifier, showErrorMessages: false) as ItemPrefab; + ItemPrefab itemPrefab = + string.IsNullOrEmpty(itemIdentifier) ? + ItemPrefab.Find(itemName, Identifier.Empty) : + ItemPrefab.Find(itemName, itemIdentifier.ToIdentifier()); Vector2 pos = Vector2.Zero; Submarine sub = null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/ItemAssemblyPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/ItemAssemblyPrefab.cs index 01fb485ac..ed2f608d8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/ItemAssemblyPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/ItemAssemblyPrefab.cs @@ -18,8 +18,8 @@ namespace Barotrauma foreach ((Identifier identifier, Rectangle rect) in DisplayEntities) { - var entityPrefab = MapEntityPrefab.FindByIdentifier(identifier); - if (entityPrefab is CoreEntityPrefab) { continue; } + var entityPrefab = FindByIdentifier(identifier); + if (entityPrefab is CoreEntityPrefab || entityPrefab == null) { continue; } var drawRect = new Rectangle( (int)(rect.X * scale) + drawArea.Center.X, (int)((rect.Y) * scale) - drawArea.Center.Y, (int)(rect.Width * scale), (int)(rect.Height * scale)); @@ -32,11 +32,10 @@ namespace Barotrauma base.DrawPlacing(spriteBatch, cam); foreach ((Identifier identifier, Rectangle rect) in DisplayEntities) { - var entityPrefab = MapEntityPrefab.Find(p => p.Identifier == identifier); + var entityPrefab = FindByIdentifier(identifier); + if (entityPrefab == null) { continue; } Rectangle drawRect = rect; - - drawRect.Location += placePosition != Vector2.Zero ? placePosition.ToPoint() : Submarine.MouseToWorldGrid(cam, Submarine.MainSub).ToPoint(); - + drawRect.Location += placePosition != Vector2.Zero ? placePosition.ToPoint() : Submarine.MouseToWorldGrid(cam, Submarine.MainSub).ToPoint(); entityPrefab.DrawPlacing(spriteBatch, drawRect, entityPrefab.Scale); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs index 897e86a23..573c648c4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs @@ -1,10 +1,12 @@ -using Barotrauma.IO; +using System; +using Barotrauma.IO; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System.Linq; using System.Xml.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -107,6 +109,25 @@ namespace Barotrauma } var pathContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true); + + string filePath = this.filePath; + if (filePath.StartsWith("Submarines")) + { + //this is the old submarines path, try to find a local mod that has a submarine with this name + string subName = Path.GetFileNameWithoutExtension(filePath); + string foundPath = ContentPackageManager.LocalPackages.Concat(ContentPackageManager.VanillaCorePackage.ToEnumerable()) + .SelectMany(p => p.GetFiles()) + .FirstOrDefault(f => Path.GetFileNameWithoutExtension(f.Path.Value).Equals(subName, StringComparison.OrdinalIgnoreCase)) + ?.Path.Value; + if (foundPath.IsNullOrEmpty()) + { + //no such sub found among the local mods, just guess the correct path + foundPath = Path.Combine(ContentPackage.LocalModsDir, subName, $"{subName}.sub"); + } + + filePath = foundPath; + } + var pathBox = new GUITextBox(new RectTransform(new Vector2(0.75f, 1.0f), pathContainer.RectTransform), filePath, font: GUIStyle.SmallFont); var reloadButton = new GUIButton(new RectTransform(new Vector2(0.25f / pathBox.RectTransform.RelativeSize.X, 1.0f), pathBox.RectTransform, Anchor.CenterRight, Pivot.CenterLeft), TextManager.Get("ReloadLinkedSub"), style: "GUIButtonSmall") diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index d6d47fdf7..eff30e678 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -40,7 +40,7 @@ namespace Barotrauma visibleSubs.Clear(); foreach (Submarine sub in Loaded) { - if (sub.WorldPosition.Y < Level.MaxEntityDepth) { continue; } + if (Level.Loaded != null && sub.WorldPosition.Y < Level.MaxEntityDepth) { continue; } int margin = 500; Rectangle worldBorders = new Rectangle( diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs index 4ebdb2c37..f9348813e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs @@ -78,7 +78,7 @@ namespace Barotrauma.Networking VoipSound = null; } - public void SetPermissions(ClientPermissions permissions, List permittedConsoleCommands) + public void SetPermissions(ClientPermissions permissions, IEnumerable permittedConsoleCommands) { List permittedCommands = new List(); foreach (string commandName in permittedConsoleCommands) @@ -92,14 +92,18 @@ namespace Barotrauma.Networking SetPermissions(permissions, permittedCommands); } - public void SetPermissions(ClientPermissions permissions, List permittedConsoleCommands) + public void SetPermissions(ClientPermissions permissions, IEnumerable permittedConsoleCommands) { if (GameMain.Client == null) { return; } Permissions = permissions; - PermittedConsoleCommands.Clear(); PermittedConsoleCommands.AddRange(permittedConsoleCommands); + PermittedConsoleCommands.Clear(); + foreach (var command in permittedConsoleCommands) + { + PermittedConsoleCommands.Add(command); + } } public void GivePermission(ClientPermissions permission) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/EntitySpawner.cs index 972c3f083..8ce5ecdea 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/EntitySpawner.cs @@ -1,10 +1,13 @@ using Barotrauma.Items.Components; using Barotrauma.Networking; +using System.Collections.Generic; namespace Barotrauma { partial class EntitySpawner : Entity, IServerSerializable { + public readonly List<(Entity entity, bool isRemoval)> receivedEvents = new List<(Entity entity, bool isRemoval)>(); + public void ClientEventRead(IReadMessage message, float sendingTime) { bool remove = message.ReadBoolean(); @@ -12,7 +15,6 @@ namespace Barotrauma if (remove) { ushort entityId = message.ReadUInt16(); - var entity = FindEntityByID(entityId); if (entity != null) { @@ -27,6 +29,7 @@ namespace Barotrauma { DebugConsole.Log("Received entity removal message for ID " + entityId + ". Entity with a matching ID not found."); } + receivedEvents.Add((entity, true)); } else { @@ -34,13 +37,29 @@ namespace Barotrauma { case (byte)SpawnableType.Item: var newItem = Item.ReadSpawnData(message, true); - if (newItem is Item item && item.Container?.GetComponent() != null) + if (newItem == null) { - GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + item.Prefab.Identifier); + DebugConsole.ThrowError("Received an item spawn message, but spawning the item failed."); + } + else + { + if (newItem.Container?.GetComponent() != null) + { + GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + newItem.Prefab.Identifier); + } + receivedEvents.Add((newItem, false)); } break; case (byte)SpawnableType.Character: - Character.ReadSpawnData(message); + var character = Character.ReadSpawnData(message); + if (character == null) + { + DebugConsole.ThrowError("Received character spawn message, but spawning the character failed."); + } + else + { + receivedEvents.Add((character, false)); + } break; default: DebugConsole.ThrowError("Received invalid entity spawn message (unknown spawnable type)"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 4bce2dec7..9a2846817 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -79,13 +79,14 @@ namespace Barotrauma.Networking Starting, WaitingForStartGameFinalize, Started, - TimedOut, Error, Interrupted } private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted; + public bool RoundStarting => roundInitStatus == RoundInitStatus.Starting || roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize; + private byte myID; private readonly List otherClients; @@ -692,11 +693,8 @@ namespace Barotrauma.Networking GameMain.LuaCs.Networking.NetMessageReceived(inc, header); - if (roundInitStatus != RoundInitStatus.Started && - roundInitStatus != RoundInitStatus.NotStarted && - roundInitStatus != RoundInitStatus.Error && - roundInitStatus != RoundInitStatus.Interrupted && - header != ServerPacketHeader.STARTGAMEFINALIZE && + if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize && + roundInitStatus == RoundInitStatus.Started && header != ServerPacketHeader.ENDGAME && header != ServerPacketHeader.PING_REQUEST && header != ServerPacketHeader.FILE_TRANSFER) @@ -1688,12 +1686,15 @@ namespace Barotrauma.Networking roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize; DateTime? timeOut = null; + TimeSpan timeOutDuration = new TimeSpan(0, 0, seconds: 30); DateTime requestFinalizeTime = DateTime.Now; TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2); IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE); clientPeer.Send(msg, DeliveryMethod.Unreliable); + GUIMessageBox interruptPrompt = null; + while (true) { try @@ -1707,11 +1708,30 @@ namespace Barotrauma.Networking clientPeer.Send(msg, DeliveryMethod.Unreliable); requestFinalizeTime = DateTime.Now + requestFinalizeInterval; } - if (DateTime.Now > timeOut) + if (DateTime.Now > timeOut && interruptPrompt == null) { - DebugConsole.ThrowError("Error while starting the round (did not receive STARTGAMEFINALIZE message from the server). Stopping the round..."); - roundInitStatus = RoundInitStatus.TimedOut; - break; + interruptPrompt = new GUIMessageBox(string.Empty, TextManager.Get("WaitingForStartGameFinalizeTakingTooLong"), + new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }) + { + DisplayInLoadingScreens = true + }; + interruptPrompt.Buttons[0].OnClicked += (btn, userData) => + { + roundInitStatus = RoundInitStatus.Interrupted; + DebugConsole.ThrowError("Error while starting the round (did not receive STARTGAMEFINALIZE message from the server). Returning to the lobby..."); + gameStarted = true; + GameMain.NetLobbyScreen.Select(); + interruptPrompt.Close(); + interruptPrompt = null; + return true; + }; + interruptPrompt.Buttons[1].OnClicked += (btn, userData) => + { + timeOut = DateTime.Now + timeOutDuration; + interruptPrompt.Close(); + interruptPrompt = null; + return true; + }; } } else @@ -1723,7 +1743,7 @@ namespace Barotrauma.Networking } //wait for up to 30 seconds for the server to send the STARTGAMEFINALIZE message - timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 30); + timeOut = DateTime.Now + timeOutDuration; } if (!connected) @@ -1745,6 +1765,9 @@ namespace Barotrauma.Networking yield return CoroutineStatus.Running; } + interruptPrompt?.Close(); + interruptPrompt = null; + if (roundInitStatus != RoundInitStatus.Started) { if (roundInitStatus != RoundInitStatus.Interrupted) @@ -2713,6 +2736,8 @@ namespace Barotrauma.Networking SteamManager.LeaveLobby(); } + CampaignMode.StartRoundCancellationToken?.Cancel(); + clientPeer?.Close(); clientPeer = null; @@ -3098,7 +3123,31 @@ namespace Barotrauma.Networking protected GUIFrame inGameHUD; protected ChatBox chatBox; + public GUIButton ShowLogButton; //TODO: move to NetLobbyScreen + private bool hasPermissionToUseLogButton; + + public void UpdateLogButtonPermissions() + { + hasPermissionToUseLogButton = GameMain.Client.HasPermission(ClientPermissions.ServerLog); + UpdateLogButtonVisibility(); + } + + private void UpdateLogButtonVisibility() + { + if (ShowLogButton != null) + { + if (Screen.Selected != GameMain.GameScreen) + { + ShowLogButton.Visible = hasPermissionToUseLogButton; + } + else + { + var campaign = GameMain.GameSession?.Campaign; + ShowLogButton.Visible = hasPermissionToUseLogButton && (campaign == null || !campaign.ShowCampaignUI); + } + } + } public GUIFrame InGameHUD { @@ -3178,6 +3227,8 @@ namespace Barotrauma.Networking msgBox = GameMain.NetLobbyScreen.ChatInput; } + UpdateLogButtonVisibility(); + if (gameStarted && Screen.Selected == GameMain.GameScreen) { var controller = Character.Controlled?.SelectedConstruction?.GetComponent(); @@ -3653,6 +3704,19 @@ namespace Barotrauma.Networking errorLines.Add(e.ErrorLine); } + if (Entity.Spawner != null) + { + errorLines.Add(""); + errorLines.Add("EntitySpawner events:"); + foreach ((Entity entity, bool isRemoval) in Entity.Spawner.receivedEvents) + { + errorLines.Add( + (isRemoval ? "Remove " : "Create ") + + entity.ToString() + + " (" + entity.ID + ")"); + } + } + errorLines.Add(""); errorLines.Add("Last debug messages:"); for (int i = DebugConsole.Messages.Count - 1; i > 0 && i > DebugConsole.Messages.Count - 15; i--) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs index d6a96d556..2b72f5dfa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs @@ -153,7 +153,10 @@ namespace Barotrauma.Networking { if (!isActive) { return; } - timeout -= deltaTime; + if (GameMain.Client == null || !GameMain.Client.RoundStarting) + { + timeout -= deltaTime; + } heartbeatTimer -= deltaTime; if (initializationStep != ConnectionInitialization.Password && diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index c1864e3cc..81973be7f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -149,7 +149,7 @@ namespace Barotrauma } else { - if (Campaign.Wallet.TryDeduct(CampaignMode.HullRepairCost)) + if (Campaign.TryPurchase(null, CampaignMode.HullRepairCost)) { GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.HullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs"); Campaign.PurchasedHullRepairs = true; @@ -194,7 +194,7 @@ namespace Barotrauma } else { - if (Campaign.Wallet.TryDeduct(CampaignMode.ItemRepairCost)) + if (Campaign.TryPurchase(null, CampaignMode.ItemRepairCost)) { GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.ItemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs"); Campaign.PurchasedItemRepairs = true; @@ -246,7 +246,7 @@ namespace Barotrauma } else { - if (Campaign.Wallet.TryDeduct(CampaignMode.ShuttleReplaceCost)) + if (Campaign.TryPurchase(null, CampaignMode.ShuttleReplaceCost)) { GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.ShuttleReplaceCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle"); Campaign.PurchasedLostShuttles = true; @@ -736,12 +736,115 @@ namespace Barotrauma public static LocalizedString GetMoney() { - return TextManager.GetWithVariable("PlayerCredits", "[credits]", (GameMain.GameSession?.Campaign == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", GameMain.GameSession.Campaign.Wallet.Balance)); + return TextManager.GetWithVariable("PlayerCredits", "[credits]", (GameMain.GameSession?.Campaign == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", GameMain.GameSession.Campaign.GetBalance())); + } + + public static LocalizedString GetTotalBalance() + { + return TextManager.FormatCurrency(GameMain.GameSession?.Campaign is { } campaign ? campaign.GetBalance() : 0); + } + + public static LocalizedString GetBankBalance() + { + return TextManager.FormatCurrency(GameMain.GameSession?.Campaign is { } campaign ? campaign.Bank.Balance : 0); + } + + public static LocalizedString GetWalletBalance() + { + return TextManager.FormatCurrency(GameMain.GameSession?.Campaign is { } campaign ? campaign.Wallet.Balance : 0); } private void UpdateMaxMissions(Location location) { hasMaxMissions = Campaign.NumberOfMissionsAtLocation(location) >= Campaign.Settings.TotalMaxMissionCount; } + + public readonly struct PlayerBalanceElement + { + public readonly bool DisplaySeparateBalances; + public readonly GUILayoutGroup ParentComponent; + public readonly GUILayoutGroup TotalBalanceContainer; + public readonly GUILayoutGroup BankBalanceContainer; + + public PlayerBalanceElement(bool displaySeparateBalances, GUILayoutGroup parentComponent, GUILayoutGroup totalBalanceContainer, GUILayoutGroup bankBalanceContainer) + { + DisplaySeparateBalances = displaySeparateBalances; + ParentComponent = parentComponent; + TotalBalanceContainer = totalBalanceContainer; + BankBalanceContainer = bankBalanceContainer; + } + + public PlayerBalanceElement(PlayerBalanceElement element, bool displaySeparateBalances) + { + DisplaySeparateBalances = displaySeparateBalances; + ParentComponent = element.ParentComponent; + TotalBalanceContainer = element.TotalBalanceContainer; + BankBalanceContainer = element.BankBalanceContainer; + } + } + + public static PlayerBalanceElement? AddBalanceElement(GUIComponent elementParent, Vector2 relativeSize) + { + var parent = new GUILayoutGroup(new RectTransform(relativeSize, elementParent.RectTransform), isHorizontal: true, childAnchor: Anchor.TopRight); + if (GameMain.IsSingleplayer) + { + AddBalance(parent, true, TextManager.Get("campaignstore.balance"), GetTotalBalance); + return null; + } + else + { + bool displaySeparateBalances = CampaignMode.AllowedToManageWallets(); + var totalBalanceContainer = AddBalance(parent, displaySeparateBalances, TextManager.Get("campaignstore.total"), GetTotalBalance); + var bankBalanceContainer = AddBalance(parent, displaySeparateBalances, TextManager.Get("crewwallet.bank"), GetBankBalance); + AddBalance(parent, true, TextManager.Get("crewwallet.wallet"), GetWalletBalance); + var playerBalanceElement = new PlayerBalanceElement(displaySeparateBalances, parent, totalBalanceContainer, bankBalanceContainer); + parent.Recalculate(); + return playerBalanceElement; + } + + static GUILayoutGroup AddBalance(GUIComponent parent, bool visible, LocalizedString text, GUITextBlock.TextGetterHandler textGetter) + { + float balanceContainerWidth = GameMain.IsSingleplayer ? 1 : 1 / 3f; + var rt = new RectTransform(new Vector2(balanceContainerWidth, 1.0f), parent.RectTransform) + { + MaxSize = new Point(GUI.IntScale(GUI.AdjustForTextScale(120)), int.MaxValue) + }; + var balanceContainer = new GUILayoutGroup(rt, childAnchor: Anchor.TopRight) + { + RelativeSpacing = 0.005f, + Visible = visible + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), balanceContainer.RectTransform), text, + font: GUIStyle.Font, textAlignment: Alignment.BottomRight) + { + AutoScaleVertical = true, + ForceUpperCase = ForceUpperCase.Yes + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), balanceContainer.RectTransform), "", + textColor: Color.White, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopRight) + { + AutoScaleVertical = true, + TextScale = 1.1f, + TextGetter = textGetter + }; + return balanceContainer; + } + } + + public static PlayerBalanceElement? UpdateBalanceElement(PlayerBalanceElement? playerBalanceElement) + { + if (playerBalanceElement is { } balanceElement) + { + bool displaySeparateBalances = CampaignMode.AllowedToManageWallets(); + if (displaySeparateBalances != balanceElement.DisplaySeparateBalances) + { + balanceElement.TotalBalanceContainer.Visible = displaySeparateBalances; + balanceElement.BankBalanceContainer.Visible = displaySeparateBalances; + playerBalanceElement = new PlayerBalanceElement(balanceElement, displaySeparateBalances); + balanceElement.ParentComponent.Recalculate(); + } + } + return playerBalanceElement; + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index a0cb2b9f4..2e9797dba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -966,7 +966,15 @@ namespace Barotrauma { OnClicked = (_, __) => { - GameMain.Client?.RequestSelectMode(ModeList.Content.GetChildIndex(ModeList.Content.GetChildByUserData(GameModePreset.Sandbox))); + if (GameMain.Client == null) { return false; } + if (GameMain.Client.GameStarted) + { + GameMain.Client.RequestRoundEnd(save: false); + } + else + { + GameMain.Client.RequestSelectMode(ModeList.Content.GetChildIndex(ModeList.Content.GetChildByUserData(GameModePreset.Sandbox))); + } return true; } }; @@ -1344,9 +1352,9 @@ namespace Barotrauma shuttleTickBox.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings) && !GameMain.Client.GameStarted; SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub)); ShuttleList.Enabled = ShuttleList.ButtonEnabled = GameMain.Client.HasPermission(ClientPermissions.SelectSub) && !GameMain.Client.GameStarted; - ModeList.Enabled = GameMain.Client.ServerSettings.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode); + ModeList.Enabled = !GameMain.Client.GameStarted && (GameMain.Client.ServerSettings.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode)); LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); - GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); + GameMain.Client.UpdateLogButtonPermissions(); roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible); roundControlsHolder.Children.ForEach(c => c.RectTransform.RelativeSize = Vector2.One); roundControlsHolder.Recalculate(); @@ -1559,7 +1567,7 @@ namespace Barotrauma }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContainer.RectTransform), TextManager.Get("Skills"), font: GUIStyle.SubHeadingFont); - foreach (Skill skill in characterInfo.Job.Skills) + foreach (Skill skill in characterInfo.Job.GetSkills()) { Color textColor = Color.White * (0.5f + skill.Level / 200.0f); var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContainer.RectTransform), @@ -2190,17 +2198,17 @@ namespace Barotrauma canKick = canBan = canPromo = false; } - List options = new List(); - - options.Add(new ContextMenuOption("ViewSteamProfile", isEnabled: hasSteam, onSelected: delegate - { - Steamworks.SteamFriends.OpenWebOverlay($"https://steamcommunity.com/profiles/{client.SteamID}"); - })); - - options.Add(new ContextMenuOption("ModerationMenu.ManagePlayer", isEnabled: true, onSelected: delegate + List options = new List { - GameMain.NetLobbyScreen?.SelectPlayer(client); - })); + new ContextMenuOption("ViewSteamProfile", isEnabled: hasSteam, onSelected: delegate + { + Steamworks.SteamFriends.OpenWebOverlay($"https://steamcommunity.com/profiles/{client.SteamID}"); + }), + new ContextMenuOption("ModerationMenu.ManagePlayer", isEnabled: true, onSelected: delegate + { + GameMain.NetLobbyScreen?.SelectPlayer(client); + }) + }; // Creates sub context menu options for all the ranks List rankOptions = new List(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index ef0d4bd08..ffc08ae67 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1170,6 +1170,9 @@ namespace Barotrauma #endif CreateEntityElement(ep, entitiesPerRow, allEntityList.Content); } + allEntityList.Content.RectTransform.SortChildren((i1, i2) => + string.Compare(((MapEntityPrefab)i1.GUIComponent.UserData)?.Name.Value, (i2.GUIComponent.UserData as MapEntityPrefab)?.Name.Value, StringComparison.Ordinal)); + } private void CreateEntityElement(MapEntityPrefab ep, int entitiesPerRow, GUIComponent parent) @@ -1911,6 +1914,7 @@ namespace Barotrauma } SubmarineInfo.RefreshSavedSub(savePath); if (prevSavePath != null && prevSavePath != savePath) { SubmarineInfo.RefreshSavedSub(prevSavePath); } + MainSub.Info.PreviewImage = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.FilePath == savePath)?.PreviewImage; string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder); linkedSubBox.ClearChildren(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs index 51db74fde..49632af2b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs @@ -73,11 +73,11 @@ namespace Barotrauma.Steam { if (!SteamManager.IsInitialized) { return; } - uint numSubscribedMods = Steamworks.SteamUGC.NumSubscribedItems; + uint numSubscribedMods = SteamManager.GetNumSubscribedItems(); if (numSubscribedMods == memSubscribedModCount) { return; } memSubscribedModCount = numSubscribedMods; - var subscribedIds = Steamworks.SteamUGC.GetSubscribedItems().ToHashSet(); + var subscribedIds = SteamManager.GetSubscribedItems().ToHashSet(); var installedIds = ContentPackageManager.WorkshopPackages.Select(p => p.SteamWorkshopId).ToHashSet(); foreach (var id in subscribedIds.Where(id2 => !installedIds.Contains(id2))) { @@ -93,10 +93,17 @@ namespace Barotrauma.Steam if (!t.TryGetResult(out ISet publishedItems)) { return; } var allRequiredInstalled = subscribedIds.Union(publishedItems.Select(it => it.Id)).ToHashSet(); + bool needsRefresh = false; foreach (var id in installedIds.Where(id2 => !allRequiredInstalled.Contains(id2))) { Steamworks.Ugc.Item item = new Steamworks.Ugc.Item(id); SteamManager.Workshop.Uninstall(item); + needsRefresh = true; + } + + if (needsRefresh) + { + PopulateInstalledModLists(); } }); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/SpriteRecorder.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/SpriteRecorder.cs index 7990dee16..6fdfc7098 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/SpriteRecorder.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/SpriteRecorder.cs @@ -214,9 +214,9 @@ namespace Barotrauma //try to place commands of the same texture //contiguously for optimal buffer generation //while maintaining the same visual result - for (int i=1;i= 0; j--) { @@ -244,6 +244,7 @@ namespace Barotrauma //requires a vertex buffer to be rendered CrossThread.RequestExecutionOnMainThread(() => { + if (isDisposed) { return; } if (commandList.Count == 0) { return; } int startIndex = 0; for (int i = 1; i < commandList.Count; i++) diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index e43e398ab..9ac4187e3 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.12.0 + 0.17.15.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index b8d2c0bb6..8cc20bde7 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.12.0 + 0.17.15.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index ed8890703..e338b2524 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.12.0 + 0.17.15.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 01ff705b2..a664f29b2 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.12.0 + 0.17.15.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 1c6a0c85a..9d9680c52 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -1,4 +1,3 @@ - Exe @@ -6,7 +5,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.12.0 + 0.17.15.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 096e49741..a63d4e033 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -63,8 +63,9 @@ namespace Barotrauma { msg.Write(Job.Prefab.Identifier); msg.Write((byte)Job.Variant); - msg.Write((byte)Job.Skills.Count); - foreach (Skill skill in Job.Skills) + var skills = Job.GetSkills(); + msg.Write((byte)skills.Count()); + foreach (Skill skill in skills) { msg.Write(skill.Identifier); msg.Write(skill.Level); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 16a2100d7..77b1a5abd 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -434,8 +434,9 @@ namespace Barotrauma } else { - msg.Write((byte)Info.Job.Skills.Count); - foreach (Skill skill in Info.Job.Skills) + var skills = Info.Job.GetSkills(); + msg.Write((byte)skills.Count()); + foreach (Skill skill in skills) { msg.Write(skill.Identifier); msg.Write(skill.Level); diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 06d8f0e8c..e45d285f7 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -2470,7 +2470,7 @@ namespace Barotrauma if (isMax) { level = 100; } if (skillIdentifier == "all") { - foreach (Skill skill in character.Info.Job.Skills) + foreach (Skill skill in character.Info.Job.GetSkills()) { character.Info.SetSkillLevel(skill.Identifier, level); } @@ -2531,30 +2531,10 @@ namespace Barotrauma #if DEBUG commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) => { - /*foreach (Item item in Item.ItemList) + foreach (Item item in Item.ItemList) { - foreach (ItemComponent component in item.Components) - { - if (component is IServerSerializable) - { - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(component) }); - } - var itemContainer = item.GetComponent(); - if (itemContainer != null) - { - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.InventoryState, 0 }); - } - - GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); - } - } - foreach (Character c in Character.CharacterList) - { - GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); - }*/ - foreach (Hull hull in Hull.HullList) - { - GameMain.Server.CreateEntityEvent(hull); + item.TryCreateServerEventSpam(); + item.CreateStatusEvent(); } foreach (Structure wall in Structure.WallList) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs index 5b041b615..454f1627d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs @@ -35,7 +35,7 @@ namespace Barotrauma int itemValue = sellValues[item.ItemPrefab]; if (store.Balance < itemValue || item.Removed) { continue; } store.Balance += itemValue; - campaign.GetWallet(client).TryDeduct(itemValue); + campaign.TryPurchase(client, itemValue); storeSpecificItems.Remove(item); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index d91e1d0d7..965530ee9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -178,6 +178,14 @@ namespace Barotrauma GameMain.Server.ConnectedClients.None(c => c.InGame && (IsOwner(c) || c.HasPermission(permissions))); } + public bool AllowedToManageWallets(Client client) + { + return + client.HasPermission(ClientPermissions.ManageCampaign) || + client.HasPermission(ClientPermissions.ManageMoney) || + IsOwner(client); + } + public void SaveExperiencePoints(Client client) { ClearSavedExperiencePoints(client); @@ -430,7 +438,7 @@ namespace Barotrauma } public bool CanPurchaseSub(SubmarineInfo info, Client client) - => GetWallet(client).CanAfford(info.Price) && GetCampaignSubs().Contains(info); + => CanAfford(info.Price, client) && GetCampaignSubs().Contains(info); private readonly List discardedCharacters = new List(); public void DiscardClientCharacterData(Client client) @@ -755,8 +763,8 @@ namespace Barotrauma { switch (purchasedHullRepairs) { - case true when personalWallet.CanAfford(hullRepairCost): - personalWallet.Deduct(hullRepairCost); + case true when GetBalance(sender) >= hullRepairCost: + TryPurchase(sender, hullRepairCost); PurchasedHullRepairs = true; GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs"); break; @@ -771,8 +779,8 @@ namespace Barotrauma { switch (purchasedItemRepairs) { - case true when personalWallet.CanAfford(itemRepairCost): - personalWallet.Deduct(itemRepairCost); + case true when GetBalance(sender) >= itemRepairCost: + TryPurchase(sender, itemRepairCost); PurchasedItemRepairs = true; GameAnalyticsManager.AddMoneySpentEvent(itemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs"); break; @@ -789,7 +797,7 @@ namespace Barotrauma { GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox); } - else if (purchasedLostShuttles && personalWallet.TryDeduct(shuttleRetrieveCost)) + else if (purchasedLostShuttles && TryPurchase(sender, shuttleRetrieveCost)) { PurchasedLostShuttles = true; GameAnalyticsManager.AddMoneySpentEvent(shuttleRetrieveCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle"); @@ -972,7 +980,7 @@ namespace Barotrauma switch (transfer.Sender) { case Some { Value: var id }: - if (id != sender.CharacterID && !AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) { return; } + if (id != sender.CharacterID && !AllowedToManageWallets(sender)) { return; } Wallet wallet = GetWalletByID(id); if (wallet is InvalidWallet) { return; } @@ -980,7 +988,7 @@ namespace Barotrauma TransferMoney(wallet); break; case None _: - if (!AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) + if (!AllowedToManageWallets(sender)) { if (transfer.Receiver is Some { Value: var receiverId } && receiverId == sender.CharacterID) { @@ -1025,7 +1033,7 @@ namespace Barotrauma { NetWalletSetSalaryUpdate update = INetSerializableStruct.Read(msg); - if (!AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) { return; } + if (!AllowedToManageWallets(sender)) { return; } Character targetCharacter = Character.CharacterList.FirstOrDefault(c => c.ID == update.Target); targetCharacter?.Wallet.SetRewardDistribution(update.NewRewardDistribution); @@ -1231,6 +1239,45 @@ namespace Barotrauma } } + public override bool TryPurchase(Client client, int price) + { + Wallet wallet = GetWallet(client); + if (!AllowedToManageWallets(client)) + { + return wallet.TryDeduct(price); + } + + int balance = wallet.Balance; + + if (balance >= price) + { + return wallet.TryDeduct(price); + } + + if (balance + Bank.Balance >= price) + { + int remainder = price - balance; + if (balance > 0) { wallet.Deduct(balance); } + Bank.Deduct(remainder); + return true ; + } + + return false; + } + + public override int GetBalance(Client client = null) + { + if (client is null) { return 0; } + + Wallet wallet = GetWallet(client); + if (!AllowedToManageWallets(client)) + { + return wallet.Balance; + } + + return wallet.Balance + Bank.Balance; + } + public override void Save(XElement element) { element.Add(new XAttribute("campaignid", CampaignID)); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OutpostTerminal.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OutpostTerminal.cs deleted file mode 100644 index 266398e7e..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OutpostTerminal.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Barotrauma.Networking; - -namespace Barotrauma.Items.Components -{ - partial class OutpostTerminal : ItemComponent, IClientSerializable, IServerSerializable - { - public void ServerEventRead(IReadMessage msg, Client c) - { - - } - - public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) - { - - } - } -} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs index a92cc70d9..2e27864a2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs @@ -188,11 +188,10 @@ namespace Barotrauma.Items.Components //already connected, no need to do anything if (Connections[i].Wires.Contains(newWire)) { continue; } - Connections[i].TryAddLink(newWire); newWire.Connect(Connections[i], true, true); + Connections[i].TryAddLink(newWire); var otherConnection = newWire.OtherConnection(Connections[i]); - if (otherConnection == null) { GameServer.Log(GameServer.CharacterLogName(c.Character) + " connected a wire to " + diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index f7db7648e..32f261856 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -79,6 +79,7 @@ namespace Barotrauma Vector2? worldPosition = applyStatusEffectEventData.WorldPosition; Character targetCharacter = applyStatusEffectEventData.TargetCharacter; + if (targetCharacter != null && targetCharacter.Removed) { targetCharacter = null; } byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255; msg.WriteRangedInteger((int)actionType, 0, Enum.GetValues(typeof(ActionType)).Length - 1); @@ -372,5 +373,20 @@ namespace Barotrauma if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false"); } GameMain.Server.CreateEntityEvent(this, eventData); } + +#if DEBUG + public void TryCreateServerEventSpam() + { + if (GameMain.Server == null) { return; } + + foreach (ItemComponent ic in components) + { + if (!(ic is IServerSerializable)) { continue; } + var eventData = new ComponentStateEventData(ic, ic.ServerGetEventData()); + if (!ic.ValidateEventData(eventData)) { continue; } + GameMain.Server.CreateEntityEvent(this, eventData); + } + } +#endif } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs index b9daadc66..5a31dc4cf 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs @@ -160,10 +160,14 @@ namespace Barotrauma.Networking return Connection.EndpointMatches(endPoint); } - public void SetPermissions(ClientPermissions permissions, List permittedConsoleCommands) + public void SetPermissions(ClientPermissions permissions, IEnumerable permittedConsoleCommands) { this.Permissions = permissions; - this.PermittedConsoleCommands = new List(permittedConsoleCommands); + this.PermittedConsoleCommands.Clear(); + foreach (var command in permittedConsoleCommands) + { + this.PermittedConsoleCommands.Add(command); + } } public void GivePermission(ClientPermissions permission) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs index 266600615..146ae81c0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs @@ -15,11 +15,14 @@ namespace Barotrauma if (GameMain.Server == null || spawnOrRemove?.Entity == null) { return; } GameMain.Server.CreateEntityEvent(this, spawnOrRemove); - if (spawnOrRemove.Entity is Character { Info: { } } character) + if (spawnOrRemove is SpawnEntity) { - foreach (var statKey in character.Info.SavedStatValues.Keys) + if (spawnOrRemove.Entity is Character { Info: { } } character && !character.Removed) { - GameMain.NetworkMember.CreateEntityEvent(character, new Character.UpdatePermanentStatsEventData(statKey)); + foreach (var statKey in character.Info.SavedStatValues.Keys) + { + GameMain.NetworkMember.CreateEntityEvent(character, new Character.UpdatePermanentStatsEventData(statKey)); + } } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 1791eea6f..a3ee147ce 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -286,7 +286,10 @@ namespace Barotrauma.Networking if (newClient.Connection == OwnerConnection && OwnerConnection != null) { newClient.GivePermission(ClientPermissions.All); - newClient.PermittedConsoleCommands.AddRange(DebugConsole.Commands); + foreach (var command in DebugConsole.Commands) + { + newClient.PermittedConsoleCommands.Add(command); + } SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient); } @@ -1239,16 +1242,6 @@ namespace Barotrauma.Networking } } - #warning TODO: remove this later - /*private IEnumerable RoundRestartLoop() - { - yield return new WaitForSeconds(8.0f); - EndGame(); - yield return new WaitForSeconds(8.0f); - StartGame(); - yield return CoroutineStatus.Success; - }*/ - private void ReadCrewMessage(IReadMessage inc, Client sender) { if (GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) @@ -1407,10 +1400,16 @@ namespace Barotrauma.Networking bool continueCampaign = inc.ReadBoolean(); if (mpCampaign != null && mpCampaign.GameOver || continueCampaign) { - if (mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageMap)) + if (gameStarted) + { + SendDirectChatMessage("Cannot continue the campaign from the previous save (round already running).", sender, ChatMessageType.Error); + break; + } + else if (mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageMap)) { MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath); } + } else if (!gameStarted && !initiatedStartGame) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs index 07c409d06..634c1db54 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs @@ -364,6 +364,13 @@ namespace Barotrauma.Networking break; } +#if DEBUG + netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Server.SimulatedDuplicatesChance; + netPeerConfiguration.SimulatedMinimumLatency = GameMain.Server.SimulatedMinimumLatency; + netPeerConfiguration.SimulatedRandomLatency = GameMain.Server.SimulatedRandomLatency; + netPeerConfiguration.SimulatedLoss = GameMain.Server.SimulatedLoss; +#endif + NetOutgoingMessage lidgrenMsg = netServer.CreateMessage(); byte[] msgData = new byte[msg.LengthBytes]; msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index 0bca511b4..c328231af 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -541,7 +541,7 @@ namespace Barotrauma.Networking public static void ReduceCharacterSkills(CharacterInfo characterInfo) { if (characterInfo?.Job == null) { return; } - foreach (Skill skill in characterInfo.Job.Skills) + foreach (Skill skill in characterInfo.Job.GetSkills()) { var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier == s.Identifier); if (skillPrefab == null) { continue; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 93e20603d..44b1da5cc 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -466,7 +466,7 @@ namespace Barotrauma.Networking } ClientPermissions permissions = Networking.ClientPermissions.None; - List permittedCommands = new List(); + HashSet permittedCommands = new HashSet(); if (clientElement.Attribute("preset") == null) { @@ -513,7 +513,7 @@ namespace Barotrauma.Networking else { permissions = preset.Permissions; - permittedCommands = preset.PermittedCommands.ToList(); + permittedCommands = preset.PermittedCommands.ToHashSet(); } } @@ -577,15 +577,15 @@ namespace Barotrauma.Networking foreach (string line in lines) { string[] separatedLine = line.Split('|'); - if (separatedLine.Length < 3) continue; + if (separatedLine.Length < 3) { continue; } string name = string.Join("|", separatedLine.Take(separatedLine.Length - 2)); string ip = separatedLine[separatedLine.Length - 2]; - ClientPermissions permissions = Networking.ClientPermissions.None; + ClientPermissions permissions; if (Enum.TryParse(separatedLine.Last(), out permissions)) { - ClientPermissions.Add(new SavedClientPermission(name, ip, permissions, new List())); + ClientPermissions.Add(new SavedClientPermission(name, ip, permissions, new HashSet())); } } } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 15ea2b067..35693828b 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.12.0 + 0.17.15.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index ae1573e52..907b533ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -838,7 +838,7 @@ namespace Barotrauma if (container == null) { return 0; } if (!container.Inventory.CanBePut(containableItem)) { return 0; } var rootContainer = container.Item.GetRootContainer(); - if (rootContainer?.GetComponent() != null || rootContainer?.GetComponent() != null) { return 0; } + if (rootContainer?.GetComponent() != null || rootContainer?.GetComponent() != null) { return 0; } if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined)) { if (isRestrictionsDefined) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs index 53cec3b04..594ba9045 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs @@ -57,8 +57,6 @@ namespace Barotrauma private readonly int speakerIndex; private readonly ImmutableHashSet allowedSpeakerTags; private readonly bool requireNextLine; - // used primarily for team1 characters interacting with escorted personnel (TODO: not used anywhere) - private readonly bool requireSight; public NPCConversation(XElement element) { @@ -75,7 +73,6 @@ namespace Barotrauma Responses = element.Elements().Select(s => new NPCConversation(s)).ToImmutableArray(); requireNextLine = element.GetAttributeBool("requirenextline", false); - requireSight = element.GetAttributeBool("requiresight", false); } private static List GetCurrentFlags(Character speaker) @@ -162,23 +159,38 @@ namespace Barotrauma return currentFlags; } - private static List previousConversations = new List(); + private static readonly List previousConversations = new List(); - public static List> CreateRandom(List availableSpeakers) + public static List<(Character speaker, string line)> CreateRandom(List availableSpeakers) { Dictionary assignedSpeakers = new Dictionary(); - List> lines = new List>(); + List<(Character speaker, string line)> lines = new List<(Character speaker, string line)>(); + + var language = GameSettings.CurrentConfig.Language; + if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language)) + { + DebugConsole.AddWarning($"Could not find NPC conversations for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead.."); + language = TextManager.DefaultLanguage; + } CreateConversation(availableSpeakers, assignedSpeakers, null, lines, - availableConversations: NPCConversationCollection.Collections[GameSettings.CurrentConfig.Language].SelectMany(cc => cc.Conversations).ToList()); + availableConversations: NPCConversationCollection.Collections[language].SelectMany(cc => cc.Conversations).ToList()); return lines; } - public static List> CreateRandom(List availableSpeakers, IEnumerable requiredFlags) + public static List<(Character speaker, string line)> CreateRandom(List availableSpeakers, IEnumerable requiredFlags) { Dictionary assignedSpeakers = new Dictionary(); - List> lines = new List>(); - var availableConversations = NPCConversationCollection.Collections[GameSettings.CurrentConfig.Language] + List<(Character speaker, string line)> lines = new List<(Character speaker, string line)>(); + + var language = GameSettings.CurrentConfig.Language; + if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language)) + { + DebugConsole.AddWarning($"Could not find NPC conversations for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead.."); + language = TextManager.DefaultLanguage; + } + + var availableConversations = NPCConversationCollection.Collections[language] .SelectMany(cc => cc.Conversations.Where(c => requiredFlags.All(f => c.Flags.Contains(f)))).ToList(); if (availableConversations.Count > 0) { @@ -191,7 +203,7 @@ namespace Barotrauma List availableSpeakers, Dictionary assignedSpeakers, NPCConversation baseConversation, - IList> lineList, + IList<(Character speaker, string line)> lineList, IList availableConversations, bool ignoreFlags = false) { @@ -271,7 +283,7 @@ namespace Barotrauma previousConversations.Insert(0, selectedConversation); if (previousConversations.Count > MaxPreviousConversations) previousConversations.RemoveAt(MaxPreviousConversations); } - lineList.Add(new Pair(speaker, selectedConversation.Line)); + lineList.Add((speaker, selectedConversation.Line)); CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 7af59a771..47c6469c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -2749,12 +2749,15 @@ namespace Barotrauma if (!Enabled) { return; } - if (Level.Loaded != null && WorldPosition.Y < Level.MaxEntityDepth || - (Submarine != null && Submarine.WorldPosition.Y < Level.MaxEntityDepth)) + if (Level.Loaded != null) { - Enabled = false; - Kill(CauseOfDeathType.Pressure, null); - return; + if (WorldPosition.Y < Level.MaxEntityDepth || + (Submarine != null && Submarine.WorldPosition.Y < Level.MaxEntityDepth)) + { + Enabled = false; + Kill(CauseOfDeathType.Pressure, null); + return; + } } ApplyStatusEffects(ActionType.Always, deltaTime); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 1d384e608..61790a3b2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -130,15 +130,15 @@ namespace Barotrauma head = value; HeadSprite = null; AttachmentSprites = null; - IsMale = value.Preset?.TagSet?.Contains("Male".ToIdentifier()) ?? false; - IsFemale = value.Preset?.TagSet?.Contains("Female".ToIdentifier()) ?? false; } } } - public bool IsMale { get; private set; } + private readonly Identifier maleIdentifier = "Male".ToIdentifier(); + private readonly Identifier femaleIdentifier = "Female".ToIdentifier(); - public bool IsFemale { get; private set; } + public bool IsMale { get { return head?.Preset?.TagSet?.Contains(maleIdentifier) ?? false; } } + public bool IsFemale { get { return head?.Preset?.TagSet?.Contains(femaleIdentifier) ?? false; } } public CharacterInfoPrefab Prefab => CharacterPrefab.Prefabs[SpeciesName].CharacterInfoPrefab; public class HeadPreset : ISerializableEntity @@ -1033,7 +1033,7 @@ namespace Barotrauma if (Name == null || Job == null) { return 0; } int salary = 0; - foreach (Skill skill in Job.Skills) + foreach (Skill skill in Job.GetSkills()) { salary += (int)(skill.Level * skill.PriceMultiplier); } @@ -1076,10 +1076,10 @@ namespace Barotrauma { if (Job == null) { return; } - var skill = Job.Skills.Find(s => s.Identifier == skillIdentifier); + var skill = Job.GetSkill(skillIdentifier); if (skill == null) { - Job.Skills.Add(new Skill(skillIdentifier, level)); + Job.IncreaseSkillLevel(skillIdentifier, level, increasePastMax: false); OnSkillChanged(skillIdentifier, 0.0f, level); } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 065a7cc10..4bc5d873b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -384,7 +384,7 @@ namespace Barotrauma foreach (var itemPrefab in ItemPrefab.Prefabs) { float suitability = Math.Max(itemPrefab.GetTreatmentSuitability(Identifier), itemPrefab.GetTreatmentSuitability(AfflictionType)); - if (suitability > 0.0f) + if (!MathUtils.NearlyEqual(suitability, 0.0f)) { yield return new KeyValuePair(itemPrefab.Identifier, suitability); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index f48d7aa79..46a7da94f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -154,10 +154,14 @@ namespace Barotrauma public void GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true) { + if (ItemSets == null || !ItemSets.Any()) { return; } var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), randSync); - foreach (XElement itemElement in spawnItems.GetChildElements("item")) + if (spawnItems != null) { - InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents); + foreach (XElement itemElement in spawnItems.GetChildElements("item")) + { + InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs index 077e8a3da..f9f45d936 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs @@ -17,8 +17,6 @@ namespace Barotrauma public JobPrefab Prefab => prefab; - public List Skills => skills.Values.ToList(); - public int Variant; public Skill PrimarySkill { get; } @@ -80,7 +78,12 @@ namespace Barotrauma var prefab = JobPrefab.Random(randSync); var variant = Rand.Range(0, prefab.Variants, randSync); return new Job(prefab, randSync, variant); - } + } + + public IEnumerable GetSkills() + { + return skills.Values; + } public float GetSkillLevel(Identifier skillIdentifier) { @@ -89,6 +92,22 @@ namespace Barotrauma return skill?.Level ?? 0.0f; } + public Skill GetSkill(Identifier skillIdentifier) + { + if (skillIdentifier.IsEmpty) { return null; } + skills.TryGetValue(skillIdentifier, out Skill skill); + return skill; + } + + public void OverrideSkills(Dictionary newSkills) + { + skills.Clear(); + foreach (var newSkill in newSkills) + { + skills.Add(newSkill.Key, new Skill(newSkill.Key, newSkill.Value)); + } + } + public void IncreaseSkillLevel(Identifier skillIdentifier, float increase, bool increasePastMax) { if (skills.TryGetValue(skillIdentifier, out Skill skill)) @@ -171,7 +190,7 @@ namespace Barotrauma character.Inventory.TryPutItem(item, null, item.AllowedSlots); } - Wearable wearable = ((List)item.Components)?.Find(c => c is Wearable) as Wearable; + Wearable wearable = item.GetComponent(); if (wearable != null) { if (Variant > 0 && Variant <= wearable.Variants) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/NPCPersonalityTrait.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/NPCPersonalityTrait.cs index e12965df7..292b58fc6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/NPCPersonalityTrait.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/NPCPersonalityTrait.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Xml.Linq; namespace Barotrauma @@ -12,7 +11,7 @@ namespace Barotrauma public readonly List AllowedDialogTags; - private float commonness; + private readonly float commonness; public float Commonness { get { return commonness; } @@ -20,12 +19,22 @@ namespace Barotrauma public static IEnumerable GetAll(LanguageIdentifier language) { + if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language)) + { + DebugConsole.AddWarning($"Could not find NPC personality traits for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead.."); + language = TextManager.DefaultLanguage; + } return NPCConversationCollection.Collections[language] .SelectMany(cc => cc.PersonalityTraits.Values); } public static NPCPersonalityTrait Get(LanguageIdentifier language, Identifier traitName) { + if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language)) + { + DebugConsole.AddWarning($"Could not find NPC personality traits for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead.."); + language = TextManager.DefaultLanguage; + } return NPCConversationCollection.Collections[language] .FirstOrDefault(cc => cc.PersonalityTraits.ContainsKey(traitName)) .PersonalityTraits[traitName]; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index f6a9d1d4c..f2859aaa2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -293,6 +293,10 @@ namespace Barotrauma { return Create(fullPath, speciesName, animationType); } + if (type == typeof(HumanCrouchParams)) + { + return Create(fullPath, speciesName, animationType); + } if (type == typeof(FishWalkParams)) { return Create(fullPath, speciesName, animationType); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs index 966bac5f4..cee2198ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -47,7 +47,7 @@ namespace Barotrauma.Abilities { if (skillIdentifier == "random") { - var skill = character.Info?.Job?.Skills?.GetRandomUnsynced(); + var skill = character.Info?.Job?.GetSkills()?.GetRandomUnsynced(); if (skill == null) { return; } character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease, gainedFromAbility: true); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs index 3311a5ff6..26cec9b48 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs @@ -30,11 +30,12 @@ namespace Barotrauma.Abilities if (useAll && Character.Info?.Job != null) { - foreach (Skill skill in Character.Info.Job.Skills) + var skills = Character.Info.Job.GetSkills(); + foreach (Skill skill in skills) { skillTotal += Character.GetSkillLevel(skill.Identifier); } - skillTotal /= Character.Info.Job.Skills.Count; + skillTotal /= skills.Count(); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 744ff3a1b..06086ed2e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -825,7 +825,7 @@ namespace Barotrauma if (isMax) { level = 100; } if (skillIdentifier == "all") { - foreach (Skill skill in character.Info.Job.Skills) + foreach (Skill skill in character.Info.Job.GetSkills()) { character.Info.SetSkillLevel(skill.Identifier, level); } @@ -845,7 +845,7 @@ namespace Barotrauma { return new[] { - Character.Controlled?.Info?.Job?.Skills?.Select(skill => skill.Identifier.Value).ToArray() ?? Array.Empty(), + Character.Controlled?.Info?.Job?.GetSkills()?.Select(skill => skill.Identifier.Value).ToArray() ?? Array.Empty(), new[]{ "max" }, Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n).ToArray(), }; @@ -1747,20 +1747,12 @@ namespace Barotrauma ThrowError(args[1] + " is not a valid latency value."); return; } -#if CLIENT - if (GameMain.Client != null) + if (GameMain.NetworkMember != null) { - GameMain.Client.SimulatedMinimumLatency = minimumLatency; - GameMain.Client.SimulatedRandomLatency = randomLatency; + GameMain.NetworkMember.SimulatedMinimumLatency = minimumLatency; + GameMain.NetworkMember.SimulatedRandomLatency = randomLatency; } -#elif SERVER - if (GameMain.Server != null) - { - GameMain.Server.SimulatedMinimumLatency = minimumLatency; - GameMain.Server.SimulatedRandomLatency = randomLatency; - } -#endif - NewMessage("Set simulated minimum latency to " + minimumLatency + " and random latency to " + randomLatency + ".", Color.White); + NewMessage("Set simulated minimum latency to " + minimumLatency.ToString(CultureInfo.InvariantCulture) + " and random latency to " + randomLatency.ToString(CultureInfo.InvariantCulture) + ".", Color.White); })); commands.Add(new Command("simulatedloss", "simulatedloss [lossratio]: applies simulated packet loss to network messages. For example, a value of 0.1 would mean 10% of the packets are dropped. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) => @@ -1771,17 +1763,10 @@ namespace Barotrauma ThrowError(args[0] + " is not a valid loss ratio."); return; } -#if CLIENT - if (GameMain.Client != null) + if (GameMain.NetworkMember != null) { - GameMain.Client.SimulatedLoss = loss; + GameMain.NetworkMember.SimulatedLoss = loss; } -#elif SERVER - if (GameMain.Server != null) - { - GameMain.Server.SimulatedLoss = loss; - } -#endif NewMessage("Set simulated packet loss to " + (int)(loss * 100) + "%.", Color.White); })); commands.Add(new Command("simulatedduplicateschance", "simulatedduplicateschance [duplicateratio]: simulates packet duplication in network messages. For example, a value of 0.1 would mean there's a 10% chance a packet gets sent twice. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) => @@ -1792,21 +1777,27 @@ namespace Barotrauma ThrowError(args[0] + " is not a valid duplicate ratio."); return; } -#if CLIENT - if (GameMain.Client != null) + if (GameMain.NetworkMember != null) { - GameMain.Client.SimulatedDuplicatesChance = duplicates; + GameMain.NetworkMember.SimulatedDuplicatesChance = duplicates; } -#elif SERVER - if (GameMain.Server != null) - { - GameMain.Server.SimulatedDuplicatesChance = duplicates; - } -#endif NewMessage("Set packet duplication to " + (int)(duplicates * 100) + "%.", Color.White); })); #if DEBUG + + commands.Add(new Command("simulatedlongloadingtime", "simulatedlongloadingtime [minimum loading time]: forces loading a round to take at least the specified amount of seconds.", (string[] args) => + { + if (args.Count() < 1 || (GameMain.NetworkMember == null)) return; + if (!float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float time)) + { + ThrowError(args[0] + " is not a valid duration ratio."); + return; + } + GameSession.MinimumLoadingTime = time; + NewMessage("Set minimum loading time to " + time + " seconds.", Color.White); + })); + commands.Add(new Command("storeinfo", "", (string[] args) => { if (GameMain.GameSession?.Map?.CurrentLocation is Location location) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index 2df3d5961..2492beea5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -316,7 +316,7 @@ namespace Barotrauma } // Exchange money int itemValue = item.Quantity * buyValues[item.ItemPrefab]; - campaign.GetWallet(client).TryDeduct(itemValue); + campaign.TryPurchase(client, itemValue); GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); store.Balance += itemValue; if (removeFromCrate) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 6b4cc9fd5..6bf837b8b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -13,7 +13,7 @@ namespace Barotrauma const float ConversationIntervalMax = 180.0f; const float ConversationIntervalMultiplierMultiplayer = 5.0f; private float conversationTimer, conversationLineTimer; - private readonly List> pendingConversationLines = new List>(); + private readonly List<(Character speaker, string line)> pendingConversationLines = new List<(Character speaker, string line)>(); public const int MaxCrewSize = 16; @@ -339,7 +339,7 @@ namespace Barotrauma #region Dialog - public void AddConversation(List> conversationLines) + public void AddConversation(List<(Character speaker, string line)> conversationLines) { if (conversationLines == null || conversationLines.Count == 0) { return; } pendingConversationLines.AddRange(conversationLines); @@ -428,16 +428,16 @@ namespace Barotrauma if (conversationLineTimer <= 0.0f) { //speaker of the next line can't speak, interrupt the conversation - if (pendingConversationLines[0].First.SpeechImpediment >= 100.0f) + if (pendingConversationLines[0].speaker.SpeechImpediment >= 100.0f) { pendingConversationLines.Clear(); return; } - pendingConversationLines[0].First.Speak(pendingConversationLines[0].Second, null); + pendingConversationLines[0].speaker.Speak(pendingConversationLines[0].line, null); if (pendingConversationLines.Count > 1) { - conversationLineTimer = MathHelper.Clamp(pendingConversationLines[0].Second.Length * 0.1f, 1.0f, 5.0f); + conversationLineTimer = MathHelper.Clamp(pendingConversationLines[0].line.Length * 0.1f, 1.0f, 5.0f); } pendingConversationLines.RemoveAt(0); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index fa1aeaf55..019a75046 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -227,6 +227,21 @@ namespace Barotrauma return Bank; } + public virtual bool TryPurchase(Client client, int price) + { + return GetWallet(client).TryDeduct(price); + } + + public virtual int GetBalance(Client client = null) + { + return GetWallet(client).Balance; + } + + public bool CanAfford(int cost, Client client = null) + { + return GetBalance(client) >= cost; + } + /// /// The location that's displayed as the "current one" in the map screen. Normally the current outpost or the location at the start of the level, /// but when selecting the next destination at the end of the level at an uninhabited location we use the location at the end @@ -766,7 +781,7 @@ namespace Barotrauma public bool TryHireCharacter(Location location, CharacterInfo characterInfo, Client client = null) { if (characterInfo == null) { return false; } - if (!GetWallet(client).TryDeduct(characterInfo.Salary)) { return false; } + if (!TryPurchase(client, characterInfo.Salary)) { return false; } characterInfo.IsNewHire = true; location.RemoveHireableCharacter(characterInfo); CrewManager.AddCharacterInfo(characterInfo); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 2ac46f975..249b9477c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -14,6 +14,10 @@ namespace Barotrauma { partial class GameSession { +#if DEBUG + public static float MinimumLoadingTime; +#endif + public enum InfoFrameTab { Crew, Mission, MyCharacter, Traitor }; public readonly EventManager EventManager; @@ -292,7 +296,7 @@ namespace Barotrauma if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && cost > 0) { - Campaign!.GetWallet(client).TryDeduct(cost); + Campaign!.TryPurchase(client, cost); } GameAnalyticsManager.AddMoneySpentEvent(cost, GameAnalyticsManager.MoneySink.SubmarineSwitch, newSubmarine.Name); Campaign!.PendingSubmarineSwitch = newSubmarine; @@ -303,7 +307,7 @@ namespace Barotrauma public void PurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null) { if (Campaign is null) { return; } - if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && !Campaign.GetWallet(client).TryDeduct(newSubmarine.Price)) { return; } + if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && !Campaign.TryPurchase(client, newSubmarine.Price)) { return; } if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name)) { GameAnalyticsManager.AddMoneySpentEvent(newSubmarine.Price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.Name); @@ -355,6 +359,9 @@ namespace Barotrauma public void StartRound(LevelData? levelData, bool mirrorLevel = false, SubmarineInfo? startOutpost = null, SubmarineInfo? endOutpost = null) { +#if DEBUG + DateTime startTime = DateTime.Now; +#endif AfflictionPrefab.LoadAllEffects(); MirrorLevel = mirrorLevel; @@ -485,6 +492,15 @@ namespace Barotrauma } } +#if DEBUG + double startDuration = (DateTime.Now - startTime).TotalSeconds; + if (startDuration < MinimumLoadingTime) + { + int sleepTime = (int)((MinimumLoadingTime - startDuration) * 1000); + DebugConsole.NewMessage($"Stalling round start by {sleepTime / 1000.0f} s (minimum loading time set to {MinimumLoadingTime})...", Color.Magenta); + System.Threading.Thread.Sleep(sleepTime); + } +#endif #if CLIENT if (GameMode is CampaignMode && levelData != null) { SteamAchievementManager.OnBiomeDiscovered(levelData.Biome); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs index 3204ee2a5..91b63855f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs @@ -213,7 +213,7 @@ namespace Barotrauma if (!force) { if (IsOutpostInCombat()) { return HealRequestResult.Refused; } - if (!GetWallet(client).TryDeduct(totalCost)) { return HealRequestResult.InsufficientFunds; } + if (!(campaign?.TryPurchase(client, totalCost) ?? false)) { return HealRequestResult.InsufficientFunds; } } ImmutableArray crew = GetCrewCharacters(); @@ -314,10 +314,7 @@ namespace Barotrauma private int GetAdjustedPrice(int price) => campaign?.Map?.CurrentLocation is { Type: { HasOutpost: true } } currentLocation ? currentLocation.GetAdjustedHealCost(price) : int.MaxValue; - public Wallet GetWallet(Client? c = null) - { - return campaign?.GetWallet(c) ?? Wallet.Invalid; - } + public int GetBalance() => campaign?.GetBalance() ?? 0; public static ImmutableArray GetCrewCharacters() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs index f85b6bafe..ac3b984b5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -216,7 +216,7 @@ namespace Barotrauma price = 0; } - if (Campaign.GetWallet(client).TryDeduct(price)) + if (Campaign.TryPurchase(client, price)) { if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { @@ -313,7 +313,7 @@ namespace Barotrauma price = 0; } - if (Campaign.GetWallet(client).TryDeduct(price)) + if (Campaign.TryPurchase(client, price)) { PurchasedItemSwaps.RemoveAll(p => linkedItems.Contains(p.ItemToRemove)); if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index bcee8a722..76dd68595 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs @@ -3,7 +3,6 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Items.Components { @@ -18,6 +17,8 @@ namespace Barotrauma.Items.Components const int MaxNodes = 100; const float MaxNodeDistance = 150.0f; + private bool waitForVoltageRecalculation; + public struct Node { public Vector2 WorldPosition; @@ -120,6 +121,7 @@ namespace Barotrauma.Items.Components CurrPowerConsumption = powerConsumption; Voltage = 0.0f; + waitForVoltageRecalculation = true; charging = true; timer = Duration; IsActive = true; @@ -134,6 +136,12 @@ namespace Barotrauma.Items.Components #if CLIENT frameOffset = Rand.Int(electricitySprite.FrameCount); #endif + if (waitForVoltageRecalculation) + { + waitForVoltageRecalculation = false; + return; + } + if (timer <= 0.0f) { IsActive = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs index e0c965cf4..eb57d4ba2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs @@ -33,9 +33,6 @@ namespace Barotrauma.Items.Components set; } - private JobPrefab cachedJobPrefab; - private string cachedName; - public ImmutableHashSet OwnerTagSet { get; set; } [Serialize("", IsPropertySaveable.Yes, alwaysUseInstanceValues: true)] @@ -98,6 +95,7 @@ namespace Barotrauma.Items.Components OwnerName = info.Name; OwnerJobId = info.Job?.Prefab.Identifier ?? Identifier.Empty; + item.AddTag($"jobid:{OwnerJobId}"); OwnerTagSet = info.Head.Preset.TagSet; OwnerHairIndex = head.HairIndex; OwnerBeardIndex = head.BeardIndex; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 1b93ba4b9..a35141def 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -315,6 +315,15 @@ namespace Barotrauma.Items.Components } } + private Client GetUsingClient() + { +#if SERVER + return GameMain.Server.ConnectedClients.Find(c => c.Character == user); +#elif CLIENT + return null; +#endif + } + private void Fabricate() { RefreshAvailableIngredients(); @@ -327,9 +336,20 @@ namespace Barotrauma.Items.Components if (fabricatedItem.RequiredMoney > 0) { if (user == null) { return; } - if (GameMain.GameSession?.GameMode is MultiPlayerCampaign) + if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign) { - user.Wallet.Deduct(fabricatedItem.RequiredMoney); +#if CLIENT + mpCampaign.TryPurchase(null, fabricatedItem.RequiredMoney); +#elif SERVER + if (GetUsingClient() is { } client) + { + mpCampaign.TryPurchase(client, fabricatedItem.RequiredMoney); + } + else + { + user.Wallet.Deduct(fabricatedItem.RequiredMoney); + } +#endif } else if (GameMain.GameSession?.GameMode is CampaignMode campaign) { @@ -530,6 +550,10 @@ namespace Barotrauma.Items.Components { floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag); } + if (!fabricatedItem.TargetItem.Tags.Contains(fabricatedItem.TargetItem.Identifier)) + { + floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, fabricatedItem.TargetItem.Identifier); + } quality = (int)floatQuality; const int MaxCraftingSkill = 100; @@ -548,17 +572,22 @@ namespace Barotrauma.Items.Components if (fabricableItem.RequiredMoney > 0) { - if (GameMain.GameSession?.GameMode is MultiPlayerCampaign) + switch (GameMain.GameSession?.GameMode) { - if (character?.Wallet == null || character.Wallet.Balance < fabricableItem.RequiredMoney) { return false; } - } - else if (GameMain.GameSession?.GameMode is CampaignMode campaign) - { - if (campaign.Bank.Balance < fabricableItem.RequiredMoney) { return false; } - } - else - { - return false; + case MultiPlayerCampaign mpCampaign: + { + if (!mpCampaign.CanAfford(fabricableItem.RequiredMoney, GetUsingClient())) { return false; } + + break; + } + case CampaignMode campaign: + { + if (campaign.Bank.Balance < fabricableItem.RequiredMoney) { return false; } + + break; + } + default: + return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OutpostTerminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OutpostTerminal.cs index a9439661a..d72756115 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OutpostTerminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OutpostTerminal.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; +using System.Xml.Linq; namespace Barotrauma.Items.Components { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs index b6d481651..d22c343b5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs @@ -565,16 +565,20 @@ namespace Barotrauma.Items.Components //Iterate through all connections in the group to get their minmax power and sum them foreach (Connection c in scrGroup.Connections) { - Powered device = c.Item.GetComponent(); - scrGroup.MinMaxPower += device.MinMaxPowerOut(c, grid.Load); + foreach (var device in c.Item.GetComponents()) + { + scrGroup.MinMaxPower += device.MinMaxPowerOut(c, grid.Load); + } } //Iterate through all connections to get their final power out provided the min max information float addedPower = 0; foreach (Connection c in scrGroup.Connections) { - Powered device = c.Item.GetComponent(); - addedPower += device.GetConnectionPowerOut(c, grid.Power, scrGroup.MinMaxPower, grid.Load); + foreach (var device in c.Item.GetComponents()) + { + addedPower += device.GetConnectionPowerOut(c, grid.Power, scrGroup.MinMaxPower, grid.Load); + } } //Add the power to the grid @@ -591,10 +595,12 @@ namespace Barotrauma.Items.Components grid.Voltage = newVoltage; //Iterate through all connections on that grid and run their gridResolved function - foreach (Connection con in grid.Connections) + foreach (Connection c in grid.Connections) { - Powered device = con.Item.GetComponent(); - device?.GridResolved(con); + foreach (var device in c.Item.GetComponents()) + { + device?.GridResolved(c); + } } } @@ -671,21 +677,18 @@ namespace Barotrauma.Items.Components /// protected float GetAvailableInstantaneousBatteryPower() { - if (item.Connections == null) { return 0.0f; } + if (item.Connections == null || powerIn == null) { return 0.0f; } float availablePower = 0.0f; - foreach (Connection c in item.Connections) + var recipients = powerIn.Recipients; + foreach (Connection recipient in recipients) { - var recipients = c.Recipients; - foreach (Connection recipient in recipients) - { - if (!recipient.IsPower || !recipient.IsOutput) { continue; } - var battery = recipient.Item?.GetComponent(); - if (battery == null) { continue; } - float maxOutputPerFrame = battery.MaxOutPut / 60.0f; - float framesPerMinute = 3600.0f; - availablePower += Math.Min(battery.Charge * framesPerMinute, maxOutputPerFrame); - } - } + if (!recipient.IsPower || !recipient.IsOutput) { continue; } + var battery = recipient.Item?.GetComponent(); + if (battery == null || battery.Item.Condition <= 0.0f) { continue; } + float maxOutputPerFrame = battery.MaxOutPut / 60.0f; + float framesPerMinute = 3600.0f; + availablePower += Math.Min(battery.Charge * framesPerMinute, maxOutputPerFrame); + } return availablePower; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index cbd83caea..45d5d3ae4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -597,7 +597,7 @@ namespace Barotrauma.Items.Components else if (ic is PowerTransfer pt) { //power transfer items (junction boxes, relays) don't deteriorate if they're no carrying any power - if (Math.Abs(pt.CurrPowerConsumption) > 0.1f) { return true; } + if (pt.Voltage > 0.1f) { return true; } } else if (ic is PowerContainer pc) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index 1d49ba47c..913529dbd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -768,8 +768,11 @@ namespace Barotrauma { for (int j = 0; j < capacity; j++) { - if (slots[j].Contains(item)) { visualSlots[j].ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.9f); } + if (slots[j].Contains(item)) { visualSlots[j].ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.9f); } } + } + if (otherInventory.visualSlots != null) + { for (int j = 0; j < otherInventory.capacity; j++) { if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) { otherInventory.visualSlots[j].ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.9f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 52fe5d778..4ab48ada7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1792,7 +1792,7 @@ namespace Barotrauma if (Math.Abs(body.LinearVelocity.X) > 0.01f || Math.Abs(body.LinearVelocity.Y) > 0.01f || transformDirty) { UpdateTransform(); - if (CurrentHull == null && body.SimPosition.Y < ConvertUnits.ToSimUnits(Level.MaxEntityDepth)) + if (CurrentHull == null && Level.Loaded != null && body.SimPosition.Y < ConvertUnits.ToSimUnits(Level.MaxEntityDepth)) { Spawner?.AddItemToRemoveQueue(this); return; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs index 2be6ddb93..d6a3357f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs @@ -51,11 +51,26 @@ namespace Barotrauma.MapCreatures.Behavior private bool inflate; private float pulseDelay = Rand.Range(0f, 3f); - public readonly BallastFloraBranch? ParentBranch; + private BallastFloraBranch? parentBranch; + public BallastFloraBranch? ParentBranch + { + get { return parentBranch; } + set + { + if (value != parentBranch) + { + parentBranch = value; + if (parentBranch != null) + { + BranchDepth = parentBranch.BranchDepth + 1; + } + } + } + } /// /// How far from the root this branch is /// - public readonly int BranchDepth; + public int BranchDepth { get; private set; } public float AccumulatedDamage; public float DamageVisualizationTimer; @@ -71,10 +86,6 @@ namespace Barotrauma.MapCreatures.Behavior { ParentBranch = parentBranch; ParentBallastFlora = parent; - if (parentBranch != null) - { - BranchDepth = parentBranch.BranchDepth + 1; - } } public void UpdateHealth() @@ -319,6 +330,7 @@ namespace Barotrauma.MapCreatures.Behavior foreach (BallastFloraBranch branch in Branches) { + SetHull(branch); if (branch.ClaimedItemId > -1) { if (Entity.FindEntityByID((ushort)branch.ClaimedItemId) is Item item) @@ -422,6 +434,7 @@ namespace Barotrauma.MapCreatures.Behavior public void LoadSave(XElement element, IdRemap idRemap) { + List<(BallastFloraBranch branch, int parentBranchId)> branches = new List<(BallastFloraBranch branch, int parentBranchId)>(); SerializableProperties = SerializableProperty.DeserializeProperties(this, element); Offset = element.GetAttributeVector2("offset", Vector2.Zero); foreach (var subElement in element.Elements()) @@ -442,6 +455,14 @@ namespace Barotrauma.MapCreatures.Behavior } } + foreach ((BallastFloraBranch branch, int parentBranchId) in branches) + { + if (parentBranchId > -1 && parentBranchId < Branches.Count) + { + branch.ParentBranch = Branches[parentBranchId]; + } + } + void LoadBranch(XElement branchElement, IdRemap idRemap) { Vector2 pos = branchElement.GetAttributeVector2("pos", Vector2.Zero); @@ -456,13 +477,7 @@ namespace Barotrauma.MapCreatures.Behavior int claimedId = branchElement.GetAttributeInt("claimed", -1); int parentBranchId = branchElement.GetAttributeInt("parentbranch", -1); - BallastFloraBranch? parentBranch = null; - if (parentBranchId > -1) - { - parentBranch = Branches[parentBranchId]; - } - - BallastFloraBranch newBranch = new BallastFloraBranch(this, parentBranch, pos, VineTileType.CrossJunction, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafconfig)) + BallastFloraBranch newBranch = new BallastFloraBranch(this, null, pos, VineTileType.CrossJunction, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafconfig)) { ID = id, Health = health, @@ -471,6 +486,8 @@ namespace Barotrauma.MapCreatures.Behavior BlockedSides = (TileSide) blockedSides, IsRoot = isRoot }; + branches.Add((newBranch, parentBranchId)); + if (newBranch.IsRoot) { root = newBranch; } if (claimedId > -1) @@ -731,7 +748,7 @@ namespace Barotrauma.MapCreatures.Behavior } // could probably be moved to the branch constructor - private void SetHull(BallastFloraBranch branch) + public void SetHull(BallastFloraBranch branch) { branch.CurrentHull = Hull.FindHull(GetWorldPosition() + branch.Position, Parent, true); } @@ -1204,7 +1221,7 @@ namespace Barotrauma.MapCreatures.Behavior _entityList.Remove(this); #if SERVER - CreateNetworkMessage(new KillEventData()); + CreateNetworkMessage(new RemoveEventData()); #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraEventData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraEventData.cs index 40c174e1a..ee6c91737 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraEventData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraEventData.cs @@ -19,6 +19,11 @@ namespace Barotrauma.MapCreatures.Behavior public NetworkHeader NetworkHeader => NetworkHeader.Kill; } + private readonly struct RemoveEventData : IEventData + { + public NetworkHeader NetworkHeader => NetworkHeader.Remove; + } + private readonly struct BranchCreateEventData : IEventData { public NetworkHeader NetworkHeader => NetworkHeader.BranchCreate; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index efc2b8ea2..763fb0324 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -8,10 +8,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; -using System.Xml.Linq; -using Barotrauma.IO; using Voronoi2; namespace Barotrauma @@ -25,7 +22,7 @@ namespace Barotrauma } //all entities are disabled after they reach this depth - public const int MaxEntityDepth = -300000; + public const int MaxEntityDepth = -1000000; public const float ShaftHeight = 1000.0f; /// /// The level generator won't try to adjust the width of the main path above this limit. diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs index 75ead9148..f6f1802ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs @@ -164,18 +164,14 @@ namespace Barotrauma.Networking } public bool HasSpawned; //has the client spawned as a character during the current round - private List kickVoters; + private readonly List kickVoters; public HashSet GivenAchievements = new HashSet(); public ClientPermissions Permissions = ClientPermissions.None; - public List PermittedConsoleCommands - { - get; - private set; - } + public readonly HashSet PermittedConsoleCommands = new HashSet(); - private object[] votes; + private readonly object[] votes; public int KickVoteCount { @@ -195,7 +191,6 @@ namespace Barotrauma.Networking this.Name = name; this.ID = ID; - PermittedConsoleCommands = new List(); kickVoters = new List(); votes = new object[Enum.GetNames(typeof(VoteType)).Length]; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs index d18e54eef..051c2b1f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ClientPermissions.cs @@ -37,7 +37,7 @@ namespace Barotrauma.Networking public readonly LocalizedString Name; public readonly LocalizedString Description; public readonly ClientPermissions Permissions; - public readonly List PermittedCommands; + public readonly HashSet PermittedCommands; public PermissionPreset(XElement element) { @@ -51,7 +51,7 @@ namespace Barotrauma.Networking DebugConsole.ThrowError("Error in permission preset \"" + Name + "\" - " + permissionsStr + " is not a valid permission!"); } - PermittedCommands = new List(); + PermittedCommands = new HashSet(); if (Permissions.HasFlag(ClientPermissions.ConsoleCommands)) { foreach (var subElement in element.Elements()) @@ -87,7 +87,7 @@ namespace Barotrauma.Networking } } - public bool MatchesPermissions(ClientPermissions permissions, List permittedConsoleCommands) + public bool MatchesPermissions(ClientPermissions permissions, HashSet permittedConsoleCommands) { return permissions == this.Permissions && PermittedCommands.SequenceEqual(permittedConsoleCommands); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs index 8865bf8dc..12be53f66 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs @@ -443,6 +443,9 @@ namespace Barotrauma { removeQueue.Clear(); spawnQueue.Clear(); +#if CLIENT + receivedEvents.Clear(); +#endif } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index 9ff742ad3..c6c61b146 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -72,18 +72,18 @@ namespace Barotrauma.Networking public readonly string EndPoint; public readonly ulong SteamID; public readonly string Name; - public List PermittedCommands; + public HashSet PermittedCommands; public ClientPermissions Permissions; - public SavedClientPermission(string name, string endpoint, ClientPermissions permissions, List permittedCommands) + public SavedClientPermission(string name, string endpoint, ClientPermissions permissions, HashSet permittedCommands) { this.Name = name; this.EndPoint = endpoint; this.Permissions = permissions; this.PermittedCommands = permittedCommands; } - public SavedClientPermission(string name, ulong steamID, ClientPermissions permissions, List permittedCommands) + public SavedClientPermission(string name, ulong steamID, ClientPermissions permissions, HashSet permittedCommands) { this.Name = name; this.SteamID = steamID; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs index 7292d5bf0..d1aac1552 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs @@ -43,7 +43,7 @@ namespace Barotrauma { Config config = new Config { - Language = LanguageIdentifier.None, + Language = TextManager.DefaultLanguage, SubEditorUndoBuffer = 32, MaxAutoSaves = 8, AutoSaveIntervalSeconds = 300, @@ -99,6 +99,10 @@ namespace Barotrauma Config retVal = fallback ?? GetDefault(); retVal.DeserializeElement(element); + if (retVal.Language == LanguageIdentifier.None) + { + retVal.Language = TextManager.DefaultLanguage; + } retVal.Graphics = GraphicsSettings.FromElements(element.GetChildElements("graphicsmode", "graphicssettings"), retVal.Graphics); retVal.Audio = AudioSettings.FromElements(element.GetChildElements("audio"), retVal.Audio); diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 0fdf89fa9..d540a059f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1485,7 +1485,7 @@ namespace Barotrauma Identifier GetRandomSkill() { - return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandomUnsynced() ?? Identifier.Empty; + return targetCharacter.Info?.Job?.GetSkills().GetRandomUnsynced()?.Identifier ?? Identifier.Empty; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Steam/SteamManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Steam/SteamManager.cs index a3f7cb201..ca1a02271 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Steam/SteamManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Steam/SteamManager.cs @@ -1,3 +1,4 @@ +using Steamworks.Data; using System; using System.Collections.Generic; using System.Linq; @@ -74,6 +75,24 @@ namespace Barotrauma.Steam return Steamworks.SteamClient.Name; } + public static uint GetNumSubscribedItems() + { + if (!IsInitialized || !Steamworks.SteamClient.IsValid) + { + return 0; + } + return Steamworks.SteamUGC.NumSubscribedItems; + } + + public static PublishedFileId[] GetSubscribedItems() + { + if (!IsInitialized || !Steamworks.SteamClient.IsValid) + { + return new PublishedFileId[0]; + } + return Steamworks.SteamUGC.GetSubscribedItems(); + } + public static bool UnlockAchievement(string achievementIdentifier) => UnlockAchievement(achievementIdentifier.ToIdentifier()); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs b/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs index f313f89fc..09e9c36ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs @@ -250,12 +250,15 @@ namespace Barotrauma.Steam public static void DeleteFailedCopies() { - foreach (var dir in Directory.EnumerateDirectories(ContentPackage.WorkshopModsDir, "**")) + if (Directory.Exists(ContentPackage.WorkshopModsDir)) { - string copyingIndicatorPath = Path.Combine(dir, ContentPackageManager.CopyIndicatorFileName); - if (File.Exists(copyingIndicatorPath)) + foreach (var dir in Directory.EnumerateDirectories(ContentPackage.WorkshopModsDir, "**")) { - Directory.Delete(dir, recursive: true); + string copyingIndicatorPath = Path.Combine(dir, ContentPackageManager.CopyIndicatorFileName); + if (File.Exists(copyingIndicatorPath)) + { + Directory.Delete(dir, recursive: true); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Text/RichString.cs b/Barotrauma/BarotraumaShared/SharedSource/Text/RichString.cs index 67cb2fbf0..7a710827c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Text/RichString.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Text/RichString.cs @@ -80,7 +80,7 @@ namespace Barotrauma #if DEBUG if (!lStr.IsNullOrEmpty() && lStr.Contains("‖")) { - if (Debugger.IsAttached) { Debugger.Break(); } + //if (Debugger.IsAttached) { Debugger.Break(); } } #endif return Plain(lStr ?? string.Empty); diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index d21b87768..37b6f8943 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,70 @@ +--------------------------------------------------------------------------------------------------------- +v0.17.15.0 +--------------------------------------------------------------------------------------------------------- + +Fixes: +- Fixed crashing if a custom language doesn't configure NPC personality traits or conversations. +- Fixed crashing when you try to disguise as someone else when using a mod that overrides the vanilla human config. +- Fixed characters getting instakilled if you dive too deep in the sub editor test mode. + +--------------------------------------------------------------------------------------------------------- +v0.17.14.0 +--------------------------------------------------------------------------------------------------------- + +Changes: +- Display both wallet and bank balance on campaign interfaces when the player has access to the bank funds. + +Fixes: +- Hopefully fixed the frequent "SteamP2P connection timed out" errors during loading screens. +- Fixed "missing entity" error when a character who's stats have been modified by a talent gets removed (e.g. eaten by a monster, despawning). +- If starting a multiplayer round takes a long time, instead of throwing the "did not receive STARTGAMEFINALIZE message" error, you're asked whether you want to keep waiting or return to the lobby. +- Fixed "failed to parse the string 'COLOR.GUI.GREEN' to Color" errors when using the submarine upgrade interface in Spanish. +- Fixed junction boxes not deteriorating over time. +- Fixed turrets being able to fire without consuming power when the power is wired to some other connection than power_in. +- Fixed broken supercapacitors providing unlimited power to turrets. +- Fixed IsMale/IsFemale properties resetting when saving and reloading (not used by the vanilla game). +- Fixed haloperidol not healing psychosis. +- Fixed ballast flora sometimes becoming unkillable client-side when entering a new level. +- Fixed the Server Log button overlapping campaign interfaces by hiding it whenever a campaign interface is open. +- Fixed an inconsistency in the assault rifle mag recipe. +- Fixed job not showing up in ID card description. +- Fixed store interface not being updated when the player balance changes. + +--------------------------------------------------------------------------------------------------------- +v0.17.13.0 +--------------------------------------------------------------------------------------------------------- + +Changes: +- Players who are allowed to manage money in the multiplayer campaign can use money directly from the bank without having to transfer it to their wallet first. +- Managing money always requires permissions: unlike other campaign-related permissions, not having anyone with permissions on the server doesn't give everyone permissions. + +Fixes: +- Fixed deconstructors, fabricators and research stations not being powered in some colony modules. +- Fixed power connections not getting recalculated server-side when disconnecting and reconnecting a wire. +- Fixed client context menu not working in the tab menu if the client's not controlling a character. +- Fixed preview image disappearing when saving a sub. +- Fixed custom jobid tags not working on ID cards. +- Fixed crashing with the error "Coroutine Barotrauma.SinglePlayerCampaign+d__16 threw an exception" when trying to give items to a human prefab instance that has no item sets configured. +- Fixed installed mods list not refreshing when uninstalling an unsubscribed mod. +- Fixed outpost reactors using mechanical skill for repairs instead of electrical. +- Fixed outdated Dugong preview image. +- Fixed Deadeye Carbine firing an inconsistent number of rounds per burst in multiplayer. +- Fixed crashing when launching the server with a fresh config file due to the language being set to None. +- Fixed crashing on startup if the workshop mod directory doesn't exist. +- Fixed "Canned Heat" not having an effect on oxygenite tanks. +- Fixed entity list's search results being in a random order in the sub editor. +- Fixed crashing when trying to view an item assembly that contains entities that can't be found in the sub editor. +- Fixed bots not taking medical items' negative effects into account when determining which meds to use, often leading to overdoses/suffocation when using opiates. +- Fixed bots trying to clean up items into deconstructors. +- Fixed clients failing to spawn items with console commands when there's a structure prefab with the same identifier (e.g. ladders). +- Fixed characters being unable to gain skills added by a mod if the job doesn't initially have those skills defined. +- Automatically correct linked submarine paths in the submarine editor. +- Fixed electrical discharge coils sometimes working with insufficient power. +- Fixed crashing when creating a humanoid character in character editor. +- Fixed cargo missions putting cargo in non-interactable and hidden-in-game containers. +- Fixed only the first Powered component being considered when determining how much power an item is supplying to the grid. Prevented alien generators from working. +- Fixed textbox's text position breaking when there's overflow and you're editing in the middle of the string. + --------------------------------------------------------------------------------------------------------- v0.17.12.0 ---------------------------------------------------------------------------------------------------------