diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 21a86f985..bbf59f241 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -698,6 +698,12 @@ namespace Barotrauma } } + public static bool IsMouseOnHealthBar() + { + if (Character.Controlled?.CharacterHealth == null) { return false; } + return Character.Controlled.CharacterHealth.healthBar.State == GUIComponent.ComponentState.Hover; + } + public void UpdateHUD(float deltaTime) { if (GUI.DisableHUD) return; @@ -966,11 +972,9 @@ namespace Barotrauma highlightedLimbIndex = -1; } - Rectangle hoverArea = Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea); - healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud(); if (Character.AllowInput && UseHealthWindow && healthBar.Enabled && healthBar.CanBeFocused && - hoverArea.Contains(PlayerInput.MousePosition) && Inventory.SelectedSlot == null) + (GUI.IsMouseOn(healthBar) || highlightedAfflictionIcon != null) && Inventory.SelectedSlot == null) { healthBar.State = GUIComponent.ComponentState.Hover; if (PlayerInput.PrimaryMouseButtonClicked()) @@ -1087,8 +1091,11 @@ namespace Barotrauma DrawStatusHUD(spriteBatch); } + + private Pair highlightedAfflictionIcon = null; public void DrawStatusHUD(SpriteBatch spriteBatch) { + highlightedAfflictionIcon = null; //Rectangle interactArea = healthBar.Rect; if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null) { @@ -1103,7 +1110,6 @@ namespace Barotrauma statusIcons.Add(new Pair(affliction, affliction.Prefab.Name)); } - Pair highlightedIcon = null; Vector2 highlightedIconPos = Vector2.Zero; Rectangle afflictionArea = HUDLayoutSettings.AfflictionAreaLeft; @@ -1124,9 +1130,9 @@ namespace Barotrauma AfflictionPrefab afflictionPrefab = affliction.Prefab; Rectangle afflictionIconRect = new Rectangle(pos, new Point(iconSize)); - if (afflictionIconRect.Contains(PlayerInput.MousePosition) && !Character.ShouldLockHud()) + if (afflictionIconRect.Contains(PlayerInput.MousePosition) && !Character.ShouldLockHud() && GUI.MouseOn == null) { - highlightedIcon = statusIcon; + highlightedAfflictionIcon = statusIcon; highlightedIconPos = afflictionIconRect.Location.ToVector2(); } @@ -1146,7 +1152,7 @@ namespace Barotrauma highlightedIcon == statusIcon ? slot.HoverColor : slot.Color);*/ - float alphaMultiplier = highlightedIcon == statusIcon ? 1f : 0.8f; + float alphaMultiplier = highlightedAfflictionIcon == statusIcon ? 1f : 0.8f; afflictionPrefab.Icon?.Draw(spriteBatch, pos.ToVector2(), @@ -1161,9 +1167,9 @@ namespace Barotrauma pos.Y += iconSize + (int)(5 * GUI.Scale); } - if (highlightedIcon != null) + if (highlightedAfflictionIcon != null) { - string nameTooltip = highlightedIcon.Second; + string nameTooltip = highlightedAfflictionIcon.Second; Vector2 offset = GUI.Font.MeasureString(nameTooltip); GUI.DrawString(spriteBatch, diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index ecaca21ef..40ee18f21 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -997,8 +997,7 @@ namespace Barotrauma return editor.GetMouseCursorState(); // Portrait area during gameplay case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true): - if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || - Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea).Contains(PlayerInput.MousePosition)) + if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || CharacterHealth.IsMouseOnHealthBar()) { return CursorState.Hand; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index 0d6f2b1e3..3771c3a7d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -17,47 +17,63 @@ namespace Barotrauma private readonly Dictionary tabLists = new Dictionary(); private readonly Dictionary tabSortingMethods = new Dictionary(); private readonly List itemsToSell = new List(); + private readonly List itemsToSellFromSub = new List(); private StoreTab activeTab = StoreTab.Buy; private MapEntityCategory? selectedItemCategory; private bool suppressBuySell; - private int buyTotal, sellTotal; + private int buyTotal, sellTotal, sellFromSubTotal; private GUITextBlock merchantBalanceBlock; private GUITextBlock currentSellValueBlock, newSellValueBlock; private GUIImage sellValueChangeArrow; private GUIDropDown sortingDropDown; private GUITextBox searchBox; - private GUIListBox storeBuyList, storeSellList; + private GUIListBox storeBuyList, storeSellList, storeSellFromSubList; /// /// Can be null when there are no deals at the current location /// - private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup; + private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup, storeRequestedSubGoodGroup; private Color storeSpecialColor; - private GUIListBox shoppingCrateBuyList, shoppingCrateSellList; + private GUIListBox shoppingCrateBuyList, shoppingCrateSellList, shoppingCrateSellFromSubList; private GUITextBlock shoppingCrateTotal; private GUIButton clearAllButton, confirmButton; - private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh; + private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh, needsSellingFromSubRefresh, needsItemsToSellFromSubRefresh; private Point resolutionWhenCreated; private bool hadPermissions; - private Dictionary OwnedItems { get; } = new Dictionary(); + private Dictionary OwnedItems { get; } = new Dictionary(); private CargoManager CargoManager => campaignUI.Campaign.CargoManager; private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation; private int PlayerMoney => campaignUI.Campaign.Money; private bool HasPermissions => campaignUI.Campaign.AllowedToManageCampaign(); - private bool IsBuying => activeTab != StoreTab.Sell; - private bool IsSelling => activeTab == StoreTab.Sell; - private GUIListBox ActiveShoppingCrateList => IsBuying ? shoppingCrateBuyList : shoppingCrateSellList; + private bool IsBuying => activeTab switch + { + StoreTab.Buy => true, + StoreTab.Sell => false, + StoreTab.SellFromSub => false, + _ => throw new NotImplementedException() + }; + private bool IsSelling => !IsBuying; + private GUIListBox ActiveShoppingCrateList => activeTab switch + { + StoreTab.Buy => shoppingCrateBuyList, + StoreTab.Sell => shoppingCrateSellList, + StoreTab.SellFromSub => shoppingCrateSellFromSubList, + _ => throw new NotImplementedException() + }; - private enum StoreTab + private bool IsTabUnavailable(StoreTab tab) => !tabLists.ContainsKey(tab); + + public enum StoreTab { Buy, - Sell + Sell, + SellFromSub } private enum SortingMethod @@ -73,11 +89,8 @@ namespace Barotrauma { this.campaignUI = campaignUI; this.parentComponent = parentComponent; - hadPermissions = HasPermissions; - CreateUI(); - campaignUI.Campaign.Map.OnLocationChanged += UpdateLocation; if (CurrentLocation?.Reputation != null) { @@ -89,8 +102,10 @@ namespace Barotrauma campaignUI.Campaign.CargoManager.OnSoldItemsChanged += () => { needsItemsToSellRefresh = true; + needsItemsToSellFromSubRefresh = true; needsRefresh = true; }; + campaignUI.Campaign.CargoManager.OnItemsInSellFromSubCrateChanged += () => { needsSellingFromSubRefresh = true; }; } public void Refresh(bool updateOwned = true) @@ -99,6 +114,7 @@ namespace Barotrauma if (updateOwned) { UpdateOwnedItems(); } RefreshBuying(updateOwned: false); RefreshSelling(updateOwned: false); + RefreshSellingFromSub(updateOwned: false); needsRefresh = false; } @@ -124,6 +140,20 @@ namespace Barotrauma needsSellingRefresh = false; } + private void RefreshSellingFromSub(bool updateOwned = true, bool updateItemsToSellFromSub = true) + { + if (IsTabUnavailable(StoreTab.SellFromSub)) { return; } + if (updateOwned) { UpdateOwnedItems(); } + if (updateItemsToSellFromSub) RefreshItemsToSellFromSub(); + RefreshShoppingCrateSellFromSubList(); + RefreshStoreSellFromSubList(); + // TODO: Separate permissions from regular campaign permissions + var hasPermissions = HasPermissions; + storeSellFromSubList.Enabled = hasPermissions; + shoppingCrateSellFromSubList.Enabled = hasPermissions; + needsSellingFromSubRefresh = false; + } + private void CreateUI() { if (parentComponent.FindChild(c => c.UserData as string == "glow") is GUIComponent glowChild) @@ -236,9 +266,13 @@ namespace Barotrauma { if (CurrentLocation != null) { - int balanceAfterTransaction = IsBuying ? - CurrentLocation.StoreCurrentBalance + buyTotal : - CurrentLocation.StoreCurrentBalance - sellTotal; + int balanceAfterTransaction = activeTab switch + { + StoreTab.Buy => CurrentLocation.StoreCurrentBalance + buyTotal, + StoreTab.Sell => CurrentLocation.StoreCurrentBalance - sellTotal, + StoreTab.SellFromSub => CurrentLocation.StoreCurrentBalance - sellFromSubTotal, + _ => throw new NotImplementedException(), + }; if (balanceAfterTransaction != CurrentLocation.StoreCurrentBalance) { var newStatus = Location.GetStoreBalanceStatus(balanceAfterTransaction); @@ -300,8 +334,14 @@ namespace Barotrauma tabSortingMethods.Clear(); foreach (StoreTab tab in tabs) { + if (tab == StoreTab.SellFromSub && GameMain.IsMultiplayer) { continue; } + string text = tab switch + { + StoreTab.SellFromSub => TextManager.Get("submarine"), + _ => TextManager.Get("campaignstoretab." + tab) + }; var tabButton = new GUIButton(new RectTransform(new Vector2(1.0f / (tabs.Length + 1), 1.0f), modeButtonContainer.RectTransform), - text: TextManager.Get("campaignstoretab." + tab), style: "GUITabButton") + text: text, style: "GUITabButton") { UserData = tab, OnClicked = (button, userData) => @@ -416,6 +456,17 @@ namespace Barotrauma storeRequestedGoodGroup = CreateDealsGroup(storeSellList); tabLists.Add(StoreTab.Sell, storeSellList); + if (GameMain.IsSingleplayer) + { + storeSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, storeItemListContainer.RectTransform)) + { + AutoHideScrollBar = false, + Visible = false + }; + storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList); + tabLists.Add(StoreTab.SellFromSub, storeSellFromSubList); + } + // Shopping Crate ------------------------------------------------------------------------------------------------------------------------------------------ var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.TopRight) @@ -475,6 +526,10 @@ namespace Barotrauma var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), shoppingCrateInventoryContainer.RectTransform), style: null); shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; + if (GameMain.IsSingleplayer) + { + shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; + } var totalContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true) { @@ -504,7 +559,13 @@ namespace Barotrauma OnClicked = (button, userData) => { if (!HasPermissions) { return false; } - var itemsToRemove = new List(IsBuying ? CargoManager.ItemsInBuyCrate : CargoManager.ItemsInSellCrate); + var itemsToRemove = activeTab switch + { + StoreTab.Buy => new List(CargoManager.ItemsInBuyCrate), + StoreTab.Sell => new List(CargoManager.ItemsInSellCrate), + StoreTab.SellFromSub => new List(CargoManager.ItemsInSellFromSubCrate), + _ => throw new NotImplementedException(), + }; itemsToRemove.ForEach(i => ClearFromShoppingCrate(i)); return true; } @@ -560,6 +621,7 @@ namespace Barotrauma private void ChangeStoreTab(StoreTab tab) { + if (IsTabUnavailable(tab)) { return; } activeTab = tab; foreach (GUIButton tabButton in storeTabButtons) { @@ -571,24 +633,56 @@ namespace Barotrauma SetConfirmButtonBehavior(); SetConfirmButtonStatus(); FilterStoreItems(); - if (tab == StoreTab.Buy) + switch (tab) { - storeSellList.Visible = false; - storeBuyList.Visible = true; - shoppingCrateSellList.Visible = false; - shoppingCrateBuyList.Visible = true; - } - else if (tab == StoreTab.Sell) - { - storeBuyList.Visible = false; - storeSellList.Visible = true; - shoppingCrateBuyList.Visible = false; - shoppingCrateSellList.Visible = true; + case StoreTab.Buy: + storeSellList.Visible = false; + if (storeSellFromSubList != null) + { + storeSellFromSubList.Visible = false; + } + storeBuyList.Visible = true; + shoppingCrateSellList.Visible = false; + if (shoppingCrateSellFromSubList != null) + { + shoppingCrateSellFromSubList.Visible = false; + } + shoppingCrateBuyList.Visible = true; + break; + case StoreTab.Sell: + storeBuyList.Visible = false; + if (storeSellFromSubList != null) + { + storeSellFromSubList.Visible = false; + } + storeSellList.Visible = true; + shoppingCrateBuyList.Visible = false; + if (shoppingCrateSellFromSubList != null) + { + shoppingCrateSellFromSubList.Visible = false; + } + shoppingCrateSellList.Visible = true; + break; + case StoreTab.SellFromSub: + storeBuyList.Visible = false; + storeSellList.Visible = false; + if (storeSellFromSubList != null) + { + storeSellFromSubList.Visible = true; + } + shoppingCrateBuyList.Visible = false; + shoppingCrateSellList.Visible = false; + if (shoppingCrateSellFromSubList != null) + { + shoppingCrateSellFromSubList.Visible = true; + } + break; } } private void FilterStoreItems(MapEntityCategory? category, string filter) { + if (IsTabUnavailable(activeTab)) { return; } selectedItemCategory = category; var list = tabLists[activeTab]; filter = filter?.ToLower(); @@ -668,7 +762,7 @@ namespace Barotrauma if (itemFrame == null) { var parentComponent = isDailySpecial ? storeDailySpecialsGroup : storeBuyList as GUIComponent; - itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, quantity), priceInfo, parentComponent, forceDisable: !hasPermissions); + itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, quantity), parentComponent, StoreTab.Buy, forceDisable: !hasPermissions); } else { @@ -688,7 +782,7 @@ namespace Barotrauma removedItemFrames.AddRange(storeDailySpecialsGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList()); } removedItemFrames.ForEach(f => f.RectTransform.Parent = null); - if (IsBuying) { FilterStoreItems(); } + if (activeTab == StoreTab.Buy) { FilterStoreItems(); } SortItems(StoreTab.Buy); storeBuyList.BarScroll = prevBuyListScroll; @@ -743,7 +837,7 @@ namespace Barotrauma if (itemFrame == null) { var parentComponent = isRequestedGood ? storeRequestedGoodGroup : storeSellList as GUIComponent; - itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, itemQuantity), priceInfo, parentComponent, forceDisable: !hasPermissions); + itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, itemQuantity), parentComponent, StoreTab.Sell, forceDisable: !hasPermissions); } else { @@ -766,13 +860,91 @@ namespace Barotrauma removedItemFrames.AddRange(storeRequestedGoodGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList()); } removedItemFrames.ForEach(f => f.RectTransform.Parent = null); - if (IsSelling) { FilterStoreItems(); } + if (activeTab == StoreTab.Sell) { FilterStoreItems(); } SortItems(StoreTab.Sell); storeSellList.BarScroll = prevSellListScroll; shoppingCrateSellList.BarScroll = prevShoppingCrateScroll; } + private void RefreshStoreSellFromSubList() + { + float prevSellListScroll = storeSellFromSubList.BarScroll; + float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll; + bool hasPermissions = HasPermissions; + HashSet existingItemFrames = new HashSet(); + + if ((storeRequestedSubGoodGroup != null) != CurrentLocation.RequestedGoods.Any()) + { + if (storeRequestedSubGoodGroup == null) + { + storeRequestedSubGoodGroup = CreateDealsGroup(storeSellList); + storeRequestedSubGoodGroup.Parent.SetAsFirstChild(); + } + else + { + storeSellFromSubList.RemoveChild(storeRequestedSubGoodGroup.Parent); + storeRequestedSubGoodGroup = null; + } + storeSellFromSubList.RecalculateChildren(); + } + + foreach (PurchasedItem item in itemsToSellFromSub) + { + CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity); + } + + foreach (var requestedGood in CurrentLocation.RequestedGoods) + { + if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) { continue; } + CreateOrUpdateItemFrame(requestedGood, 0); + } + + void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity) + { + PriceInfo priceInfo = itemPrefab.GetPriceInfo(CurrentLocation); + if (priceInfo == null) { return; } + var isRequestedGood = CurrentLocation.RequestedGoods.Contains(itemPrefab); + var itemFrame = isRequestedGood ? + storeRequestedSubGoodGroup.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) : + storeSellFromSubList.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab); + if (CargoManager.ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem itemInSellFromSubCrate) + { + itemQuantity = Math.Max(itemQuantity - itemInSellFromSubCrate.Quantity, 0); + } + if (itemFrame == null) + { + var parentComponent = isRequestedGood ? storeRequestedSubGoodGroup : storeSellFromSubList as GUIComponent; + itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, itemQuantity), parentComponent, StoreTab.SellFromSub, forceDisable: !hasPermissions); + } + else + { + (itemFrame.UserData as PurchasedItem).Quantity = itemQuantity; + SetQuantityLabelText(StoreTab.SellFromSub, itemFrame); + SetOwnedLabelText(itemFrame); + SetPriceGetters(itemFrame, false); + } + SetItemFrameStatus(itemFrame, hasPermissions && itemQuantity > 0); + if (itemQuantity < 1 && !isRequestedGood) + { + itemFrame.Visible = false; + } + existingItemFrames.Add(itemFrame); + } + + var removedItemFrames = storeSellFromSubList.Content.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList(); + if (storeRequestedSubGoodGroup != null) + { + removedItemFrames.AddRange(storeRequestedSubGoodGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList()); + } + removedItemFrames.ForEach(f => f.RectTransform.Parent = null); + if (activeTab == StoreTab.SellFromSub) { FilterStoreItems(); } + SortItems(StoreTab.SellFromSub); + + storeSellFromSubList.BarScroll = prevSellListScroll; + shoppingCrateSellFromSubList.BarScroll = prevShoppingCrateScroll; + } + private void SetPriceGetters(GUIComponent itemFrame, bool buying) { if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; } @@ -834,11 +1006,41 @@ namespace Barotrauma needsItemsToSellRefresh = false; } - private void RefreshShoppingCrateList(List items, GUIListBox listBox) + public void RefreshItemsToSellFromSub() + { + itemsToSellFromSub.Clear(); + var subItems = CargoManager.GetSellableItemsFromSub(); + foreach (Item subItem in subItems) + { + if (itemsToSellFromSub.FirstOrDefault(i => i.ItemPrefab == subItem.Prefab) is PurchasedItem item) + { + item.Quantity += 1; + } + else if (subItem.Prefab.GetPriceInfo(CurrentLocation) != null) + { + itemsToSellFromSub.Add(new PurchasedItem(subItem.Prefab, 1)); + } + } + + // Remove items from sell crate if they aren't on the sub anymore + var itemsInCrate = new List(CargoManager.ItemsInSellFromSubCrate); + foreach (PurchasedItem crateItem in itemsInCrate) + { + var subItem = itemsToSellFromSub.Find(i => i.ItemPrefab == crateItem.ItemPrefab); + var subItemQuantity = subItem != null ? subItem.Quantity : 0; + if (crateItem.Quantity > subItemQuantity) + { + CargoManager.ModifyItemQuantityInSellFromSubCrate(crateItem.ItemPrefab, subItemQuantity - crateItem.Quantity); + } + } + sellableItemsFromSubUpdateTimer = 0.0f; + needsItemsToSellFromSubRefresh = false; + } + + private void RefreshShoppingCrateList(List items, GUIListBox listBox, StoreTab tab) { bool hasPermissions = HasPermissions; HashSet existingItemFrames = new HashSet(); - bool refreshingBuyList = listBox == shoppingCrateBuyList; int totalPrice = 0; foreach (PurchasedItem item in items) { @@ -849,7 +1051,7 @@ namespace Barotrauma GUINumberInput numInput = null; if (itemFrame == null) { - itemFrame = CreateItemFrame(item, priceInfo, listBox, forceDisable: !hasPermissions); + itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions); numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput; } else @@ -860,7 +1062,7 @@ namespace Barotrauma { numInput.UserData = item; numInput.Enabled = hasPermissions; - numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, refreshingBuyList ? StoreTab.Buy : StoreTab.Sell); + numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab); } SetOwnedLabelText(itemFrame); SetItemFrameStatus(itemFrame, hasPermissions); @@ -875,7 +1077,7 @@ namespace Barotrauma } suppressBuySell = false; - var price = refreshingBuyList ? + var price = tab == StoreTab.Buy ? CurrentLocation.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo) : CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo); totalPrice += item.Quantity * price; @@ -885,24 +1087,32 @@ namespace Barotrauma removedItemFrames.ForEach(f => listBox.Content.RemoveChild(f)); SortItems(listBox, SortingMethod.CategoryAsc); - listBox.UpdateScrollBarSize(); - if (refreshingBuyList) + listBox.UpdateScrollBarSize(); + switch (tab) { - buyTotal = totalPrice; - if (IsBuying) { SetShoppingCrateTotalText(); } + case StoreTab.Buy: + buyTotal = totalPrice; + break; + case StoreTab.Sell: + sellTotal = totalPrice; + break; + case StoreTab.SellFromSub: + sellFromSubTotal = totalPrice; + break; } - else + if (activeTab == tab) { - sellTotal = totalPrice; - if(IsSelling) { SetShoppingCrateTotalText(); } + SetShoppingCrateTotalText(); } SetClearAllButtonStatus(); SetConfirmButtonStatus(); } - private void RefreshShoppingCrateBuyList() => RefreshShoppingCrateList(CargoManager.ItemsInBuyCrate, shoppingCrateBuyList); + private void RefreshShoppingCrateBuyList() => RefreshShoppingCrateList(CargoManager.ItemsInBuyCrate, shoppingCrateBuyList, StoreTab.Buy); - private void RefreshShoppingCrateSellList() => RefreshShoppingCrateList(CargoManager.ItemsInSellCrate, shoppingCrateSellList); + private void RefreshShoppingCrateSellList() => RefreshShoppingCrateList(CargoManager.ItemsInSellCrate, shoppingCrateSellList, StoreTab.Sell); + + private void RefreshShoppingCrateSellFromSubList() => RefreshShoppingCrateList(CargoManager.ItemsInSellFromSubCrate, shoppingCrateSellFromSubList, StoreTab.SellFromSub); private void SortItems(GUIListBox list, SortingMethod sortingMethod) { @@ -934,7 +1144,7 @@ namespace Barotrauma else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc) { SortItems(list, SortingMethod.AlphabeticalAsc); - if (list == storeSellList || list == shoppingCrateSellList) + if (list != storeBuyList && list != shoppingCrateBuyList) { list.Content.RectTransform.SortChildren(CompareBySellPrice); if (GetSpecialsGroup() is GUILayoutGroup specialsGroup) @@ -1016,6 +1226,10 @@ namespace Barotrauma { return storeRequestedGoodGroup; } + else if (list == storeSellFromSubList) + { + return storeRequestedSubGoodGroup; + } else { return null; @@ -1047,15 +1261,20 @@ namespace Barotrauma private void SortItems(StoreTab tab, SortingMethod sortingMethod) { + if (IsTabUnavailable(tab)) { return; } tabSortingMethods[tab] = sortingMethod; SortItems(tabLists[tab], sortingMethod); } - private void SortItems(StoreTab tab) => SortItems(tab, tabSortingMethods[tab]); + private void SortItems(StoreTab tab) + { + if (IsTabUnavailable(tab)) { return; } + SortItems(tab, tabSortingMethods[tab]); + } private void SortActiveTabItems(SortingMethod sortingMethod) => SortItems(activeTab, sortingMethod); - private GUIComponent CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIComponent parentComponent, bool forceDisable = false) + private GUIComponent CreateItemFrame(PurchasedItem pi, GUIComponent parentComponent, StoreTab containingTab, bool forceDisable = false) { var tooltip = pi.ItemPrefab.Name; if (!string.IsNullOrWhiteSpace(pi.ItemPrefab.Description)) @@ -1116,8 +1335,8 @@ namespace Barotrauma CanBeFocused = false, Stretch = true }; - var isSellingRelatedList = parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup || parentComponent == shoppingCrateSellList; - var locationHasDealOnItem = isSellingRelatedList ? + bool isSellingRelatedList = containingTab != StoreTab.Buy; + bool locationHasDealOnItem = isSellingRelatedList ? CurrentLocation.RequestedGoods.Contains(pi.ItemPrefab) : CurrentLocation.DailySpecials.Contains(pi.ItemPrefab); GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), nameAndQuantityGroup.RectTransform), pi.ItemPrefab.Name, font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft) @@ -1142,14 +1361,15 @@ namespace Barotrauma }; dealIcon.SetAsFirstChild(); } - var isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup || - parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup; + bool isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup || + parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup || + parentComponent == storeSellFromSubList || parentComponent == storeRequestedSubGoodGroup; GUILayoutGroup shoppingCrateAmountGroup = null; GUINumberInput amountInput = null; if (isParentOnLeftSideOfInterface) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), nameAndQuantityGroup.RectTransform), - CreateQuantityLabelText(isSellingRelatedList ? StoreTab.Sell : StoreTab.Buy, pi.Quantity), font: GUI.Font, textAlignment: Alignment.BottomLeft) + CreateQuantityLabelText(containingTab, pi.Quantity), font: GUI.Font, textAlignment: Alignment.BottomLeft) { CanBeFocused = false, Shadow = locationHasDealOnItem, @@ -1158,7 +1378,7 @@ namespace Barotrauma UserData = "quantitylabel" }; } - else if (!isParentOnLeftSideOfInterface) + else { var relativePadding = nameBlock.Padding.X / nameBlock.Rect.Width; shoppingCrateAmountGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativePadding, 0.6f), nameAndQuantityGroup.RectTransform) { RelativeOffset = new Vector2(relativePadding, 0) }, @@ -1169,7 +1389,7 @@ namespace Barotrauma amountInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), shoppingCrateAmountGroup.RectTransform), GUINumberInput.NumberType.Int) { MinValueInt = 0, - MaxValueInt = GetMaxAvailable(pi.ItemPrefab, isSellingRelatedList ? StoreTab.Sell : StoreTab.Buy), + MaxValueInt = GetMaxAvailable(pi.ItemPrefab, containingTab), UserData = pi, IntValue = pi.Quantity }; @@ -1396,7 +1616,7 @@ namespace Barotrauma } } - private string CreateQuantityLabelText(StoreTab mode, int quantity) => mode == StoreTab.Sell ? + private string CreateQuantityLabelText(StoreTab mode, int quantity) => mode != StoreTab.Buy ? TextManager.GetWithVariable("campaignstore.quantity", "[amount]", quantity.ToString()) : TextManager.GetWithVariable("campaignstore.instock", "[amount]", quantity.ToString()); @@ -1419,10 +1639,16 @@ namespace Barotrauma private int GetMaxAvailable(ItemPrefab itemPrefab, StoreTab mode) { - var list = mode == StoreTab.Sell ? itemsToSell : CurrentLocation.StoreStock; + var list = mode switch + { + StoreTab.Buy => CurrentLocation.StoreStock, + StoreTab.Sell => itemsToSell, + StoreTab.SellFromSub => itemsToSellFromSub, + _ => throw new NotImplementedException() + }; if (list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item) { - if (mode != StoreTab.Sell) + if (mode == StoreTab.Buy) { var purchasedItem = CargoManager.PurchasedItems.Find(i => i.ItemPrefab == item.ItemPrefab); if (purchasedItem != null) { return Math.Max(item.Quantity - purchasedItem.Quantity, 0); } @@ -1471,11 +1697,37 @@ namespace Barotrauma return false; } - private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => IsBuying ? - ModifyBuyQuantity(item, quantity) : ModifySellQuantity(item, quantity); + private bool ModifySellFromSubQuantity(PurchasedItem item, int quantity) + { + if (item == null || item.ItemPrefab == null) { return false; } + if (!HasPermissions) { return false; } + if (quantity > 0) + { + // Make sure there's enough available to sell + var itemToSell = CargoManager.ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == item.ItemPrefab); + var totalQuantityToSell = itemToSell != null ? itemToSell.Quantity + quantity : quantity; + if (totalQuantityToSell > GetMaxAvailable(item.ItemPrefab, StoreTab.SellFromSub)) { return false; } + } + CargoManager.ModifyItemQuantityInSellFromSubCrate(item.ItemPrefab, quantity); + // TODO: GameMain.Client?.SendCampaignState(); + return false; + } - private bool ClearFromShoppingCrate(PurchasedItem item) => IsBuying ? - ModifyBuyQuantity(item, -item.Quantity) : ModifySellQuantity(item, -item.Quantity); + private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => activeTab switch + { + StoreTab.Buy => ModifyBuyQuantity(item, quantity), + StoreTab.Sell => ModifySellQuantity(item, quantity), + StoreTab.SellFromSub => ModifySellFromSubQuantity(item, quantity), + _ => throw new NotImplementedException(), + }; + + private bool ClearFromShoppingCrate(PurchasedItem item) => activeTab switch + { + StoreTab.Buy => ModifyBuyQuantity(item, -item.Quantity), + StoreTab.Sell => ModifySellQuantity(item, -item.Quantity), + StoreTab.SellFromSub => ModifySellFromSubQuantity(item, -item.Quantity), + _ => throw new NotImplementedException(), + }; private bool BuyItems() { @@ -1512,18 +1764,17 @@ namespace Barotrauma private bool SellItems() { if (!HasPermissions) { return false; } - - var itemsToSell = new List(CargoManager.ItemsInSellCrate); + var itemsToSell = activeTab switch + { + StoreTab.Sell => new List(CargoManager.ItemsInSellCrate), + StoreTab.SellFromSub => new List(CargoManager.ItemsInSellFromSubCrate), + _ => throw new NotImplementedException() + }; var itemsToRemove = new List(); var totalValue = 0; foreach (PurchasedItem item in itemsToSell) { - if (item?.ItemPrefab == null) - { - itemsToRemove.Add(item); - continue; - } - if (item.ItemPrefab.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo) + if (item?.ItemPrefab?.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo) { totalValue += item.Quantity * CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo); } @@ -1533,12 +1784,13 @@ namespace Barotrauma } } itemsToRemove.ForEach(i => itemsToSell.Remove(i)); - if (itemsToSell.None() || totalValue > CurrentLocation.StoreCurrentBalance) { return false; } - - CargoManager.SellItems(itemsToSell); - GameMain.Client?.SendCampaignState(); - + CargoManager.SellItems(itemsToSell, activeTab); + if (activeTab == StoreTab.Sell) + { + // TODO: Implement selling sub items in multiplayer + GameMain.Client?.SendCampaignState(); + } return false; } @@ -1551,8 +1803,14 @@ namespace Barotrauma } else { - shoppingCrateTotal.Text = GetCurrencyFormatted(sellTotal); - shoppingCrateTotal.TextColor = CurrentLocation != null && sellTotal > CurrentLocation.StoreCurrentBalance ? Color.Red : Color.White; + int total = activeTab switch + { + StoreTab.Sell => sellTotal, + StoreTab.SellFromSub => sellFromSubTotal, + _ => throw new NotImplementedException(), + }; + shoppingCrateTotal.Text = GetCurrencyFormatted(total); + shoppingCrateTotal.TextColor = CurrentLocation != null && total > CurrentLocation.StoreCurrentBalance ? Color.Red : Color.White; } } @@ -1582,13 +1840,19 @@ namespace Barotrauma private void SetConfirmButtonStatus() => confirmButton.Enabled = HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any() && - ((IsBuying && buyTotal <= PlayerMoney) || (IsSelling && CurrentLocation != null && sellTotal <= CurrentLocation.StoreCurrentBalance)); + activeTab switch + { + StoreTab.Buy => buyTotal <= PlayerMoney, + StoreTab.Sell => CurrentLocation != null && sellTotal <= CurrentLocation.StoreCurrentBalance, + StoreTab.SellFromSub => CurrentLocation != null && sellFromSubTotal <= CurrentLocation.StoreCurrentBalance, + _ => throw new NotImplementedException(), + }; private void SetClearAllButtonStatus() => clearAllButton.Enabled = HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any(); - private float ownedItemsUpdateTimer = 0.0f; - private readonly float ownedItemsUpdateInterval = 1.5f; + private float ownedItemsUpdateTimer = 0.0f, sellableItemsFromSubUpdateTimer = 0.0f; + private readonly float timerUpdateInterval = 1.5f; public void Update(float deltaTime) { @@ -1600,7 +1864,7 @@ namespace Barotrauma { // Update the owned items at short intervals and check if the interface should be refreshed ownedItemsUpdateTimer += deltaTime; - if (ownedItemsUpdateTimer >= ownedItemsUpdateInterval) + if (ownedItemsUpdateTimer >= timerUpdateInterval) { var prevOwnedItems = new Dictionary(OwnedItems); UpdateOwnedItems(); @@ -1614,12 +1878,21 @@ namespace Barotrauma needsRefresh = true; } } + // Update the sellable sub items at short intervals and check if the interface should be refreshed + sellableItemsFromSubUpdateTimer += deltaTime; + if (sellableItemsFromSubUpdateTimer >= timerUpdateInterval) + { + needsItemsToSellFromSubRefresh = true; + needsRefresh = true; + } } if (needsItemsToSellRefresh) { RefreshItemsToSell(); } + if (needsItemsToSellFromSubRefresh) { RefreshItemsToSellFromSub(); } if (needsRefresh || hadPermissions != HasPermissions) { Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f); } if (needsBuyingRefresh) { RefreshBuying(); } if (needsSellingRefresh) { RefreshSelling(); } + if (needsSellingFromSubRefresh) { RefreshSellingFromSub(updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f); } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs index fba84ab41..cffadc3ee 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs @@ -1,4 +1,5 @@ using Barotrauma.Extensions; +using Barotrauma.Items.Components; using System.Collections.Generic; using System.Linq; @@ -42,10 +43,7 @@ namespace Barotrauma public IEnumerable GetSellableItems(Character character) { if (character == null) { return new List(); } - // Only consider items which have been: - // a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or - // b) sold locally in multiplayer (SellStatus.Local), but the client has not received a campaing state update yet after selling them - var confirmedSoldEntities = SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed); + var confirmedSoldEntities = GetConfirmedSoldEntities(); // The bag slot is intentionally left out since we want to be able to sell items from there var equipmentSlots = new List() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card }; return character.Inventory.FindAllItems(item => @@ -72,6 +70,43 @@ namespace Barotrauma } } + public IEnumerable GetSellableItemsFromSub() + { + if (Submarine.MainSub == null) { return new List(); } + var confirmedSoldEntities = GetConfirmedSoldEntities(); + return Submarine.MainSub.GetItems(true).FindAll(item => + { + if (!item.Prefab.CanBeSold) { return false; } + if (item.SpawnedInOutpost) { return false; } + if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; } + if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; } + if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; } + if (!ItemAndAllContainersInteractable(item)) { return false; } + if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; } + // There must be no contained items or the contained items must be confirmed as sold + if (!item.ContainedItems.All(it => confirmedSoldEntities.Any(se => se.Item == it))) { return false; } + return true; + }).Distinct(); + + static bool ItemAndAllContainersInteractable(Item item) + { + do + { + if (!item.IsPlayerTeamInteractable) { return false; } + item = item.Container; + } while (item != null); + return true; + } + } + + private IEnumerable GetConfirmedSoldEntities() + { + // Only consider items which have been: + // a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or + // b) sold locally in multiplayer (SellStatus.Local), but the client has not received a campaing state update yet after selling them + return SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed); + } + public void SetItemsInBuyCrate(List items) { ItemsInBuyCrate.Clear(); @@ -119,10 +154,34 @@ namespace Barotrauma OnItemsInSellCrateChanged?.Invoke(); } - public void SellItems(List itemsToSell) + public void ModifyItemQuantityInSellFromSubCrate(ItemPrefab itemPrefab, int changeInQuantity) { - var itemsInInventory = GetSellableItems(Character.Controlled); - var canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null; + var itemToSell = ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == itemPrefab); + if (itemToSell != null) + { + itemToSell.Quantity += changeInQuantity; + if (itemToSell.Quantity < 1) + { + ItemsInSellFromSubCrate.Remove(itemToSell); + } + } + else if (changeInQuantity > 0) + { + itemToSell = new PurchasedItem(itemPrefab, changeInQuantity); + ItemsInSellFromSubCrate.Add(itemToSell); + } + OnItemsInSellFromSubCrateChanged?.Invoke(); + } + + public void SellItems(List itemsToSell, Store.StoreTab sellingMode) + { + var sellableItems = sellingMode switch + { + Store.StoreTab.Sell => GetSellableItems(Character.Controlled), + Store.StoreTab.SellFromSub => GetSellableItemsFromSub(), + _ => throw new System.NotImplementedException(), + }; + bool canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null; var sellerId = GameMain.Client?.ID ?? 0; // Check all the prices before starting the transaction @@ -137,7 +196,7 @@ namespace Barotrauma if (Location.StoreCurrentBalance < itemValue) { continue; } // TODO: Write logic for prioritizing certain items over others (e.g. lone Battery Cell should be preferred over one inside a Stun Baton) - var matchingItems = itemsInInventory.Where(i => i.Prefab == item.ItemPrefab); + var matchingItems = sellableItems.Where(i => i.Prefab == item.ItemPrefab); if (matchingItems.Count() <= item.Quantity) { foreach (Item i in matchingItems) @@ -163,12 +222,21 @@ namespace Barotrauma campaign.Money += itemValue; // Remove from the sell crate - if (ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } itemToSell) + // TODO: Simplify duplicate logic? + if (sellingMode == Store.StoreTab.Sell && ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } inventoryItem) { - itemToSell.Quantity -= item.Quantity; - if (itemToSell.Quantity < 1) + inventoryItem.Quantity -= item.Quantity; + if (inventoryItem.Quantity < 1) { - ItemsInSellCrate.Remove(itemToSell); + ItemsInSellCrate.Remove(inventoryItem); + } + } + else if(sellingMode == Store.StoreTab.SellFromSub && ItemsInSellFromSubCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } subItem) + { + subItem.Quantity -= item.Quantity; + if (subItem.Quantity < 1) + { + ItemsInSellFromSubCrate.Remove(subItem); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs index 023d89045..60e4b869a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs @@ -123,17 +123,17 @@ namespace Barotrauma.Tutorials SetDoorAccess(tutorial_lockedDoor_2, null, false); var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("mechanic")); - captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic"); + captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "mechanic"); captain_mechanic.TeamID = CharacterTeamType.Team1; captain_mechanic.GiveJobItems(); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer")); - captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer"); + captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "securityofficer"); captain_security.TeamID = CharacterTeamType.Team1; captain_security.GiveJobItems(); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer")); - captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer"); + captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "engineer"); captain_engineer.TeamID = CharacterTeamType.Team1; captain_engineer.GiveJobItems(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs index 2dab531d3..1b10f65fd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs @@ -94,19 +94,19 @@ namespace Barotrauma.Tutorials patient2.AIController.Enabled = false; var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer")); - var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3"); + var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3"); subPatient1.TeamID = CharacterTeamType.Team1; subPatient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); subPatients.Add(subPatient1); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer")); - var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3"); + var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3"); subPatient2.TeamID = CharacterTeamType.Team1; subPatient2.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); subPatients.Add(subPatient2); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer")); - var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3"); + var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3"); subPatient3.TeamID = CharacterTeamType.Team1; subPatient3.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); subPatients.Add(subPatient3); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs index 6f90823d7..40ec19062 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs @@ -177,7 +177,7 @@ namespace Barotrauma.Tutorials } } - return WayPoint.GetRandom(spawnPointType, charInfo.Job, spawnSub); + return WayPoint.GetRandom(spawnPointType, charInfo.Job?.Prefab, spawnSub); } protected bool HasOrder(Character character, string identifier, string option = null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index 650369933..200d39720 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -1077,8 +1077,8 @@ namespace Barotrauma new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); - new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"), font: GUI.SubHeadingFont); - var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice)) + var voiceKeybindLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"), font: GUI.SubHeadingFont); + var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.3f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice)) { SelectedColor = Color.Gold * 0.3f, UserData = InputType.Voice @@ -1089,14 +1089,16 @@ namespace Barotrauma new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); - new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), localVoiceInputContainer.RectTransform), TextManager.Get("InputType.LocalVoice"), font: GUI.SubHeadingFont); - var localVoiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), localVoiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.LocalVoice)) + var localVoiceKeybindLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), localVoiceInputContainer.RectTransform), TextManager.Get("InputType.LocalVoice"), font: GUI.SubHeadingFont); + var localVoiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.3f, 1.0f), localVoiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.LocalVoice)) { SelectedColor = Color.Gold * 0.3f, UserData = InputType.LocalVoice }; localVoiceKeyBox.OnSelected += KeyBoxSelected; + voiceKeybindLabel.RectTransform.SizeChanged += () => { GUITextBlock.AutoScaleAndNormalize(voiceKeybindLabel, localVoiceKeybindLabel); }; + var cutoffPreventionText = new GUITextBlock(new RectTransform(textBlockScale, voiceChatContent.RectTransform), TextManager.Get("CutoffPrevention"), font: GUI.SubHeadingFont) { ToolTip = TextManager.Get("CutoffPreventionTooltip") diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs index 5aa13a52b..ed67bbe2c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs @@ -7,13 +7,15 @@ namespace Barotrauma.Items.Components { private void GetDamageModifierText(ref string description, float damageMultiplier, string afflictionIdentifier) { + int roundedValue = (int)Math.Round((1 - damageMultiplier) * 100); + if (roundedValue == 0) { return; } string colorStr = XMLExtensions.ColorToString(GUI.Style.Green); - description += $"\n ‖color:{colorStr}‖-{Math.Round((1 - damageMultiplier) * 100)}%‖color:end‖ {TextManager.Get("AfflictionName." + afflictionIdentifier, true) ?? afflictionIdentifier}"; + description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("+0;-#")}%‖color:end‖ {TextManager.Get("AfflictionName." + afflictionIdentifier, true) ?? afflictionIdentifier}"; } public override void AddTooltipInfo(ref string description) { - if (damageModifiers.Any(d => d.DamageMultiplier != 1f) || SkillModifiers.Any()) + if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f)) || SkillModifiers.Any()) { description += "\n"; } @@ -22,7 +24,7 @@ namespace Barotrauma.Items.Components { foreach (DamageModifier damageModifier in damageModifiers) { - if (damageModifier.DamageMultiplier == 1f) + if (MathUtils.NearlyEqual(damageModifier.DamageMultiplier, 1f)) { continue; } @@ -42,7 +44,9 @@ namespace Barotrauma.Items.Components foreach (var skillModifier in SkillModifiers) { string colorStr = XMLExtensions.ColorToString(GUI.Style.Green); - description += $"\n ‖color:{colorStr}‖+{skillModifier.Value}‖color:end‖ {TextManager.Get("SkillName." + skillModifier.Key, true) ?? skillModifier.Key}"; + int roundedValue = (int)Math.Round(skillModifier.Value); + if (roundedValue == 0) { continue; } + description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("+0;-#")}‖color:end‖ {TextManager.Get("SkillName." + skillModifier.Key, true) ?? skillModifier.Key}"; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 8c90d177f..72dd7def7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -480,7 +480,10 @@ namespace Barotrauma if (IsSelected || IsHighlighted) { - GUI.DrawRectangle(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)), new Vector2(rect.Width, rect.Height), + Vector2 drawPos = new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)); + Vector2 drawSize = new Vector2(MathF.Ceiling(rect.Width + Math.Abs(drawPos.X - (int)drawPos.X)), MathF.Ceiling(rect.Height + Math.Abs(drawPos.Y - (int)drawPos.Y))); + drawPos = new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y)); + GUI.DrawRectangle(spriteBatch, drawPos, drawSize, Color.White, false, 0, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom))); foreach (Rectangle t in Prefab.Triggers) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 354129a86..1dc23c7d1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -527,7 +527,7 @@ namespace Barotrauma.Networking string pwMsg = TextManager.Get("PasswordRequired"); var msgBox = new GUIMessageBox(pwMsg, "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, - relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, (int)(170 * Math.Max(1.0f, GUI.Scale)))); + relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, GUI.IntScale(170))); var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), msgBox.Content.RectTransform), childAnchor: Anchor.TopCenter); var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1f), passwordHolder.RectTransform) { MinSize = new Point(0, 20) }) { @@ -537,7 +537,8 @@ namespace Barotrauma.Networking if (wrongPassword) { - new GUITextBlock(new RectTransform(new Vector2(1f, 0.33f), passwordHolder.RectTransform), TextManager.Language == "English" ? TextManager.Get("incorrectpassword") : "Incorrect password", GUI.Style.Red, GUI.Font, textAlignment: Alignment.Center); + var incorrectPasswordText = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), passwordHolder.RectTransform), TextManager.Get("incorrectpassword"), GUI.Style.Red, GUI.Font, textAlignment: Alignment.Center); + incorrectPasswordText.RectTransform.MinSize = new Point(0, (int)incorrectPasswordText.TextSize.Y); passwordHolder.Recalculate(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index ddb594b69..89ebc3243 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -469,18 +469,17 @@ namespace Barotrauma this.game = game; - menuTabs[(int)Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null); - new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[(int)Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker"); + menuTabs[(int)Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null) + { + CanBeFocused = false + }; + new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[(int)Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker") + { + CanBeFocused = false + }; var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f); creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml"); - - new GUIButton(new RectTransform(new Vector2(0.1f, 0.05f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.BottomLeft) { RelativeOffset = new Vector2(0.25f, 0.02f) }, - TextManager.Get("Back"), style: "GUIButtonLarge") - { - OnClicked = SelectTab - }; - } #endregion diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 3d91c3542..9665e80fd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1514,6 +1514,9 @@ namespace Barotrauma case SubmarineType.Wreck: contentType = ContentType.Wreck; break; + case SubmarineType.EnemySubmarine: + contentType = ContentType.EnemySubmarine; + break; } if (contentType != ContentType.Submarine) { @@ -1829,7 +1832,12 @@ namespace Barotrauma subTypeContainer.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y)); foreach (SubmarineType subType in Enum.GetValues(typeof(SubmarineType))) { - subTypeDropdown.AddItem(TextManager.Get("submarinetype."+subType.ToString().ToLowerInvariant()), subType); + string textTag = "SubmarineType." + subType; + if (subType == SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag)) + { + textTag = "MissionType.Pirate"; + } + subTypeDropdown.AddItem(TextManager.Get(textTag), subType); } //--------------------------------------- @@ -2591,8 +2599,13 @@ namespace Barotrauma { if (prevSub == null || prevSub.Type != sub.Type) { + string textTag = "SubmarineType." + sub.Type; + if (sub.Type == SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag)) + { + textTag = "MissionType.Pirate"; + } new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), subList.Content.RectTransform) { MinSize = new Point(0, 35) }, - TextManager.Get("SubmarineType." + sub.Type), font: GUI.LargeFont, textAlignment: Alignment.Center, style: "ListBoxElement") + TextManager.Get(textTag), font: GUI.LargeFont, textAlignment: Alignment.Center, style: "ListBoxElement") { CanBeFocused = false }; diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 694e942d6..a87aad1a8 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1400.2.0 + 0.1400.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 6d0f6cba3..e6e7e370a 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1400.2.0 + 0.1400.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 7d94e6408..cc82d78d0 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1400.2.0 + 0.1400.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 7b62977ed..98f689559 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1400.2.0 + 0.1400.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index d6b119050..60929758d 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1400.2.0 + 0.1400.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index e75eb4474..00cb9e609 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1400.2.0 + 0.1400.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 7aba7c7e4..e63b0b3bf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -66,7 +66,7 @@ namespace Barotrauma private float Sight => AIParams.Sight; private float Hearing => AIParams.Hearing; private float FleeHealthThreshold => AIParams.FleeHealthThreshold; - private bool AggressiveBoarding => AIParams.AggressiveBoarding; + private bool IsAggressiveBoarder => AIParams.AggressiveBoarding; private FishAnimController FishAnimController => Character.AnimController as FishAnimController; @@ -1271,7 +1271,7 @@ namespace Barotrauma Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition; Vector2 toTarget = attackWorldPos - attackLimbPos; // Add a margin when the target is moving away, because otherwise it might be difficult to reach it if the attack takes some time to execute - if (wallTarget != null) + if (wallTarget != null && Character.Submarine == null) { if (wallTarget.Structure.Submarine != null) { @@ -1440,10 +1440,11 @@ namespace Barotrauma else { pathSteering.SteeringSeek(steerPos, 2, startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), checkVisiblity: true); - // Switch to Idle when cannot reach the target and if cannot damage the walls - if ((!canAttackWalls || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) + if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) { State = AIState.Idle; + IgnoreTarget(SelectedAiTarget); + ResetAITarget(); return; } } @@ -1674,16 +1675,6 @@ namespace Barotrauma } if (canAttack) { - if (SelectedAiTarget.Entity is Item targetItem) - { - var door = targetItem.GetComponent(); - if (door != null && door.CanBeTraversed) - { - ResetAITarget(); - State = PreviousState; - return; - } - } if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb)) { IgnoreTarget(SelectedAiTarget); @@ -2292,25 +2283,24 @@ namespace Barotrauma var section = s.Sections[i]; if (section.gap == null) { continue; } bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null; - isInnerWall = isInnerWall || !leadsInside; if (Character.AnimController.CanEnterSubmarine) { if (!isCharacterInside) { if (CanPassThroughHole(s, i)) { - valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0; + valueModifier *= leadsInside ? (IsAggressiveBoarder ? 3 : 1) : 0; } - else if (AggressiveBoarding && leadsInside && canAttackWalls && AIParams.TargetOuterWalls) + else if (IsAggressiveBoarder && leadsInside && canAttackWalls && AIParams.TargetOuterWalls) { - // Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside - valueModifier *= 1 + section.gap.Open; + // Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside + valueModifier *= 1 + section.gap.Open * 0.25f; } } else { // Inside - if (AggressiveBoarding) + if (IsAggressiveBoarder) { if (!isInnerWall) { @@ -2327,6 +2317,10 @@ namespace Barotrauma valueModifier = 0; break; } + else + { + valueModifier = 0.1f; + } } else { @@ -2350,7 +2344,7 @@ namespace Barotrauma valueModifier = 0; break; } - else if (AggressiveBoarding) + else if (IsAggressiveBoarder) { // Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside // (Bonethreshers) @@ -2384,17 +2378,24 @@ namespace Barotrauma // Ignore broken and open doors, if cannot enter submarine continue; } - if (AggressiveBoarding) + if (IsAggressiveBoarder) { - // Increase the priority if the character is outside and the door is from outside to inside if (character.CurrentHull == null) { - valueModifier *= isOpen ? 5 : 1; + // Increase the priority if the character is outside and the door is from outside to inside + if (door.CanBeTraversed) + { + valueModifier = 3; + } + else if (door.LinkedGap != null) + { + valueModifier = 1 + door.LinkedGap.Open; + } } else { // Inside -> ignore open doors and outer doors - valueModifier *= isOpen || isOutdoor ? 0 : 1; + valueModifier = isOpen || isOutdoor ? 0 : 1; } } } @@ -2643,9 +2644,9 @@ namespace Barotrauma private void UpdateWallTarget(int requiredHoleCount) { wallTarget = null; - if (AIParams.CanOpenDoors && HasValidPath(requireNonDirty: true)) { return; } if (SelectedAiTarget == null) { return; } if (SelectedAiTarget.Entity == null) { return; } + if (HasValidPath(requireNonDirty: true)) { return; } wallHits.Clear(); Structure wall = null; if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target)) @@ -2729,9 +2730,12 @@ namespace Barotrauma { if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine) { + bool isTargetingDoor = SelectedAiTarget.Entity is Item i && i.GetComponent() != null; // Blocked by a wall that shouldn't be targeted. The main intention here is to prevent monsters from entering the the tail and the nose pieces. - IgnoreTarget(SelectedAiTarget); - ResetAITarget(); + if (!isTargetingDoor) + { + ResetAITarget(); + } } else { @@ -2741,7 +2745,6 @@ namespace Barotrauma else { // Blocked by a disabled wall. - IgnoreTarget(SelectedAiTarget); ResetAITarget(); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 5a1a04001..bb204ccc1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1926,26 +1926,33 @@ namespace Barotrauma public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter) { operatingCharacter = null; + float highestPriority = -1.0f; + float highestPriorityModifier = -1.0f; foreach (Character c in Character.CharacterList) { if (c.Removed) { continue; } if (c.TeamID != team) { continue; } if (c.IsIncapacitated) { continue; } - bool isOperated = c.SelectedConstruction == target.Item; - if (!isOperated) - { - if (c.AIController is HumanAIController humanAI) - { - isOperated = humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item); - } - } - operatingCharacter = c; - if (isOperated) + if (c.SelectedConstruction == target.Item) { + operatingCharacter = c; return true; } + if (c.AIController is HumanAIController humanAI) + { + foreach (var objective in humanAI.ObjectiveManager.Objectives) + { + if (!(objective is AIObjectiveOperateItem operateObjective)) { continue; } + if (operateObjective.Component.Item != target.Item) { continue; } + if (operateObjective.Priority < highestPriority) { continue; } + if (operateObjective.PriorityModifier < highestPriorityModifier) { continue; } + operatingCharacter = c; + highestPriority = operateObjective.Priority; + highestPriorityModifier = operateObjective.PriorityModifier; + } + } } - return false; + return operatingCharacter != null; } // There's some duplicate logic in the two methods below, but making them use the same code would require some changes in the target classes so that we could use exactly the same checks. diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index e11d69838..f2343388a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -637,7 +637,7 @@ namespace Barotrauma impactQueue.Enqueue(new Impact(f1, f2, contact, velocity)); } } - return true; + return !f2.IsSensor; } Vector2 colliderBottom = GetColliderBottom(); @@ -1541,6 +1541,7 @@ namespace Barotrauma case Physics.CollisionLevel: if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; } if (fixture.Body.UserData is Submarine && character.Submarine != null) { return -1; } + if (fixture.IsSensor) { return -1; } if (fraction < standOnFloorFraction) { standOnFloorFraction = fraction; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 7a21cfbc0..c1bac70ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -2377,6 +2377,10 @@ namespace Barotrauma var item = FindItemAtPosition(mouseSimPos, aimAssist); focusedItem = CanInteract ? item : null; + if (focusedItem != null && focusedItem.CampaignInteractionType != CampaignMode.InteractionType.None) + { + FocusedCharacter = null; + } findFocusedTimer = 0.05f; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs index 1b8fd0967..fd60cddb2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs @@ -23,7 +23,17 @@ namespace Barotrauma protected PropertyConditional.OperatorType Operator { get; set; } - public CheckDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + public CheckDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + if (string.IsNullOrEmpty(Condition)) + { + Condition = element.GetAttributeString("value", string.Empty); + if (string.IsNullOrEmpty(Condition)) + { + DebugConsole.ThrowError($"Error in scripted event \"{parentEvent.Prefab.Identifier}\". CheckDataAction with no condition set ({element})."); + } + } + } protected override bool? DetermineSuccess() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs index 4bd734ec5..9f53430a9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs @@ -213,7 +213,14 @@ namespace Barotrauma if (dialogOpened) { #if CLIENT - Character.DisableControls = true; + if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "ConversationAction")) + { + Character.DisableControls = true; + } + else + { + Reset(); + } #endif if (ShouldInterrupt()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RemoveItemAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RemoveItemAction.cs index 93a8e0275..2d1e1e4d7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RemoveItemAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RemoveItemAction.cs @@ -20,11 +20,7 @@ namespace Barotrauma { if (string.IsNullOrWhiteSpace(ItemIdentifier)) { - ItemIdentifier = element.GetAttributeString("itemidentifiers", ""); - } - if (string.IsNullOrWhiteSpace(ItemIdentifier)) - { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - RemoveItemAction without an item identifier."); + ItemIdentifier = element.GetAttributeString("itemidentifiers", null) ?? element.GetAttributeString("identifier", ""); } } @@ -66,7 +62,7 @@ namespace Barotrauma var item = inventory.FindItem(it => it != null && !removedItems.Contains(it) && - it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase), recursive: true); + (string.IsNullOrEmpty(ItemIdentifier) || it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase)), recursive: true); if (item == null) { break; } Entity.Spawner.AddToRemoveQueue(item); removedItems.Add(item); @@ -74,7 +70,7 @@ namespace Barotrauma } else if (target is Item item) { - if (item.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase)) + if (string.IsNullOrEmpty(ItemIdentifier) || item.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase)) { Entity.Spawner.AddToRemoveQueue(item); removedItems.Add(item); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs index f8749155b..e4855e9a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs @@ -128,8 +128,8 @@ namespace Barotrauma (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } }, TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use))); #else - npc.SetCustomInteract( - Trigger, + npc.SetCustomInteract( + (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } }, TextManager.Get("CampaignInteraction.Talk")); GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction }); #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index daa096af0..55e7366cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -185,7 +185,7 @@ namespace Barotrauma if (element.Attribute("identifier") != null && element.Attribute("from") != null) { - HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element); + HumanPrefab humanPrefab = GetHumanPrefabFromElement(element); for (int i = 0; i < count; i++) { LoadHuman(humanPrefab, element, submarine); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index 6adc55c57..8364eedca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -42,7 +42,7 @@ namespace Barotrauma { this.sub = sub; itemConfig = prefab.ConfigElement.Element("Items"); - requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.9f), 1.0f); + requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f); DetermineCargo(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs index a369534b1..bffec355a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs @@ -84,8 +84,7 @@ namespace Barotrauma { characters.Clear(); characterItems.Clear(); - // VIP transport mission characters stay in the same location; other characters roam at will - // could be replaced with a designated waypoint for VIPs, such as cargo or crew + WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); Rand.RandSync randSync = Rand.RandSync.Server; @@ -95,12 +94,30 @@ namespace Barotrauma randSync = Rand.RandSync.Unsynced; } + //if any of the escortees have a job defined, try to use a spawnpoint designated for that job + foreach (XElement element in characterConfig.Elements()) + { + var humanPrefab = GetHumanPrefabFromElement(element); + if (humanPrefab == null || string.IsNullOrEmpty(humanPrefab.Job) || humanPrefab.Job.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; } + + var jobPrefab = humanPrefab.GetJobPrefab(); + if (jobPrefab != null) + { + var jobSpecificSpawnPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, Submarine.MainSub); + if (jobSpecificSpawnPos != null) + { + explicitStayInHullPos = jobSpecificSpawnPos; + break; + } + } + } + foreach (XElement element in characterConfig.Elements()) { int count = CalculateScalingEscortedCharacterCount(inMission: true); for (int i = 0; i < count; i++) { - Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync); + Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync); if (spawnedCharacter.AIController is HumanAIController humanAI) { humanAI.InitMentalStateManager(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index ce652ed59..516949ec1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -293,6 +293,7 @@ namespace Barotrauma /// private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger) { + if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; } if (trigger.Delay > 0) { if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger)) @@ -311,6 +312,7 @@ namespace Barotrauma /// private void TriggerEvent(MissionPrefab.TriggerEvent trigger) { + if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; } var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier.Equals(trigger.EventIdentifier, StringComparison.OrdinalIgnoreCase)); if (eventPrefab == null) { @@ -398,7 +400,7 @@ namespace Barotrauma public virtual void AdjustLevelData(LevelData levelData) { } // putting these here since both escort and pirate missions need them. could be tucked away into another class that they can inherit from (or use composition) - protected HumanPrefab CreateHumanPrefabFromElement(XElement element) + protected HumanPrefab GetHumanPrefabFromElement(XElement element) { if (element.Attribute("name") != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index 0aed7f4a6..80be751bf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -117,6 +117,9 @@ namespace Barotrauma [Serialize(0.0f, true)] public float Delay { get; private set; } + [Serialize(false, true)] + public bool CampaignOnly { get; private set; } + public TriggerEvent(XElement element) { SerializableProperty.DeserializeProperties(this, element); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 31f96efbe..57b63b0b7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -191,6 +191,8 @@ namespace Barotrauma } enemySub.EnableMaintainPosition(); enemySub.TeamID = CharacterTeamType.None; + //make the enemy sub withstand atleast the same depth as the player sub + enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth); } private void InitPirates() @@ -230,7 +232,7 @@ namespace Barotrauma XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier); - Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(variantElement), characters, characterItems, enemySub, CharacterTeamType.None, null); + Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(variantElement), characters, characterItems, enemySub, CharacterTeamType.None, null); if (!commanderAssigned) { bool isCommander = variantElement.GetAttributeBool("iscommander", false); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index d1f63d6a1..3edb99bdd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -431,7 +431,15 @@ namespace Barotrauma float scatterAmount = scatter; if (spawnPosType.HasFlag(Level.PositionType.SidePath)) { - scatterAmount = Math.Min(scatter, Level.Loaded.Tunnels.Where(t => t.Type == Level.TunnelType.SidePath).Min(t => t.MinWidth) / 2); + var sidePaths = Level.Loaded.Tunnels.Where(t => t.Type == Level.TunnelType.SidePath); + if (sidePaths.Any()) + { + scatterAmount = Math.Min(scatter, sidePaths.Min(t => t.MinWidth) / 2); + } + else + { + scatterAmount = scatter; + } } else if (!spawnPosType.HasFlag(Level.PositionType.MainPath)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index c63d385d8..d55314017 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -46,6 +46,7 @@ namespace Barotrauma public List ItemsInBuyCrate { get; } = new List(); public List ItemsInSellCrate { get; } = new List(); + public List ItemsInSellFromSubCrate { get; } = new List(); public List PurchasedItems { get; } = new List(); public List SoldItems { get; } = new List(); @@ -55,6 +56,7 @@ namespace Barotrauma public Action OnItemsInBuyCrateChanged; public Action OnItemsInSellCrateChanged; + public Action OnItemsInSellFromSubCrateChanged; public Action OnPurchasedItemsChanged; public Action OnSoldItemsChanged; @@ -75,6 +77,12 @@ namespace Barotrauma OnItemsInSellCrateChanged?.Invoke(); } + public void ClearItemsInSellFromSubCrate() + { + ItemsInSellFromSubCrate.Clear(); + OnItemsInSellFromSubCrateChanged?.Invoke(); + } + public void SetPurchasedItems(List items) { PurchasedItems.Clear(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 757b03269..8eed7fcfa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -417,7 +417,7 @@ namespace Barotrauma if (isControlledCharacterNull) { return null; } #endif if (order.Category == OrderCategory.Operate && HumanAIController.IsItemTargetedBySomeone(order.TargetItemComponent, controlledCharacter != null ? controlledCharacter.TeamID : CharacterTeamType.Team1, out Character operatingCharacter) && - (isControlledCharacterNull || operatingCharacter.CanHearCharacter(controlledCharacter))) + (isControlledCharacterNull || operatingCharacter.CanHearCharacter(controlledCharacter))) { return operatingCharacter; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index e5a5dab98..1ecab703e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -558,12 +558,14 @@ namespace Barotrauma { CargoManager.ClearItemsInBuyCrate(); CargoManager.ClearItemsInSellCrate(); + CargoManager.ClearItemsInSellFromSubCrate(); } else { if (GameMain.NetworkMember.IsServer) { CargoManager?.ClearItemsInBuyCrate(); + // TODO: CargoManager?.ClearItemsInSellFromSubCrate(); } else if (GameMain.NetworkMember.IsClient) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs index f8da64793..0ead9540d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs @@ -8,7 +8,6 @@ namespace Barotrauma.Items.Components { partial class MotionSensor : ItemComponent { - private const float UpdateInterval = 0.1f; private float rangeX, rangeY; private Vector2 detectOffset; @@ -75,6 +74,13 @@ namespace Barotrauma.Items.Components } } + [Editable(MinValueFloat = 0.1f, MaxValueFloat = 100.0f, DecimalCount = 2), Serialize(0.1f, true, description: "How often the sensor checks if there's something moving near it. Higher values are better for performance.", alwaysUseInstanceValues: true)] + public float UpdateInterval + { + get; + set; + } + private int maxOutputLength; [Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")] public int MaxOutputLength @@ -135,6 +141,9 @@ namespace Barotrauma.Items.Components { rangeX = rangeY = element.GetAttributeFloat("range", 0.0f); } + + //randomize update timer so all sensors aren't updated during the same frame + updateTimer = Rand.Range(0.0f, UpdateInterval); } public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 35120ad55..d8aa3610e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -17,7 +17,7 @@ namespace Barotrauma public readonly string Identifier; public readonly int OriginalContainerIndex; - public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerIndex, ushort moduleIndex) + public TakenItem(string identifier, UInt16 originalID, int originalContainerIndex, ushort moduleIndex) { OriginalID = originalID; OriginalContainerIndex = originalContainerIndex; @@ -345,7 +345,7 @@ namespace Barotrauma DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\""); continue; } - if (!ushort.TryParse(takenItemSplit[2], out ushort containerIndex)) + if (!int.TryParse(takenItemSplit[2], out int containerIndex)) { DebugConsole.ThrowError($"Error in saved location: could not parse taken container index \"{takenItemSplit[2]}\""); continue; @@ -577,6 +577,10 @@ namespace Barotrauma { weight /= missionCount * 2; } + if (destination.IsRadiated()) + { + weight *= 0.001f; + } } return weight; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index e83ae7dbf..fd433482a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -53,7 +53,16 @@ namespace Barotrauma //dimensions of the wall sections' physics bodies (only used for debug rendering) private readonly List bodyDebugDimensions = new List(); - public bool Indestructible; +#if DEBUG + [Serialize(false, true), Editable] +#else + [Serialize(false, true)] +#endif + public bool Indestructible + { + get; + set; + } //sections of the wall that are supposed to be rendered public WallSection[] Sections @@ -798,7 +807,7 @@ namespace Barotrauma public void AddDamage(int sectionIndex, float damage, Character attacker = null) { - if (!Prefab.Body || Prefab.Platform || Indestructible ) { return; } + if (!Prefab.Body || Prefab.Platform || Indestructible) { return; } if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index 8e6dc5f07..731c4e0f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -23,7 +23,7 @@ namespace Barotrauma HideInMenus = 2 } - public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation } + public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine } public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver } partial class SubmarineInfo : IDisposable diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index 5c0c1999a..65d32afa7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -766,14 +766,14 @@ namespace Barotrauma if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); } } - public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false) + public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false) { return WayPointList.GetRandom(wp => wp.Submarine == sub && wp.ParentRuin == ruin && wp.spawnType == spawnType && - (assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob.Prefab)) - , useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced); + (assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)), + useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced); } public static WayPoint[] SelectCrewSpawnPoints(List crew, Submarine submarine) diff --git a/Barotrauma/BarotraumaShared/Submarines/DugongPirate.sub b/Barotrauma/BarotraumaShared/Submarines/DugongPirate.sub new file mode 100644 index 000000000..788d06689 Binary files /dev/null and b/Barotrauma/BarotraumaShared/Submarines/DugongPirate.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 86a3d98ba..75588c8c2 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/HumpbackPirate.sub b/Barotrauma/BarotraumaShared/Submarines/HumpbackPirate.sub new file mode 100644 index 000000000..8b7dbc7f0 Binary files /dev/null and b/Barotrauma/BarotraumaShared/Submarines/HumpbackPirate.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon2Pirate.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon2Pirate.sub new file mode 100644 index 000000000..01c1d86da Binary files /dev/null and b/Barotrauma/BarotraumaShared/Submarines/Typhon2Pirate.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 1443f04e3..b6e56b539 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,33 @@ +--------------------------------------------------------------------------------------------------------- +v0.1400.3.0 (unstable) +--------------------------------------------------------------------------------------------------------- + +Changes: +- Increased the health of the hammerhead mission variants to match the previous buff of the default characters. +- Made it possible to sell items on the sub through a new store tab in singleplayer. +- Miscellaneous improvements and fixes to the new scripted events. +- Allow clicking on the main menu buttons when the credits are open. +- Added a dummy lightcomponent to turret hardpoints to make it possible to adjust the properties of the lights that will be installed on the hardpoint. +- Option to adjust motion sensor's update interval in the sub editor. +- Display pirate subs in a separate category in the sub editor. +- Escortees spawn at a spawnpoint suitable for their job if one is found. Shouldn't have an effect in the vanilla subs because they don't have specific spawnpoints for the escortees' jobs. + +Fixes: +- Fixed pirate subs getting crushed by pressure in levels later in the campaign. +- Hide the event variant of the psychosis artifact in the sub editor. +- Fixed less suitable characters getting quick-assigned for an order (e.g. captain getting assigned the "operate reactor" order). +- Fixed missions sometimes unlocking in paths leading to an irradiated location even if there's a more suitable path available. +- Fixed some campaign-specific scripted events triggering in abandoned outposts in the mission mode. +- Fixed mudraptors no longer targeting doors. +- Fixed white rectangle around selected items in the sub editor being slightly off if the item's position/size is not a whole number. +- Fixed "incorrect password" text overlapping with the buttons in the password prompt. +- Fixed "could not parse taken container index" error when stealing items that didn't spawn in a container in an outpost and returning to that outpost. +- Fixed z-fighting in Medium Weapons Display Case. +- Fixed inability to drop through broken hatches. +- Fixed sprite bleed in IC-4 block's inventory icon. +- Fixed damage modifiers in item tooltips always starting with a minus sign even if the modifier is positive, and skill modifiers always starting with "+". +- Fixed healthbar and affliction area being clickable even if there's an UI element in front of them, and even if there's no afflictions in the affliction area. Most noticeable when editing an electrical components properties. + --------------------------------------------------------------------------------------------------------- v0.1400.2.0 (unstable) ---------------------------------------------------------------------------------------------------------