Unstable 0.1400.3.0 (Bad sleep schedule edition)

This commit is contained in:
Markus Isberg
2021-06-03 03:34:18 +03:00
parent 0b3fb5e440
commit de04525d51
47 changed files with 703 additions and 213 deletions
@@ -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) public void UpdateHUD(float deltaTime)
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) return;
@@ -966,11 +972,9 @@ namespace Barotrauma
highlightedLimbIndex = -1; highlightedLimbIndex = -1;
} }
Rectangle hoverArea = Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea);
healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud(); healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud();
if (Character.AllowInput && UseHealthWindow && healthBar.Enabled && healthBar.CanBeFocused && 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; healthBar.State = GUIComponent.ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonClicked()) if (PlayerInput.PrimaryMouseButtonClicked())
@@ -1087,8 +1091,11 @@ namespace Barotrauma
DrawStatusHUD(spriteBatch); DrawStatusHUD(spriteBatch);
} }
private Pair<Affliction, string> highlightedAfflictionIcon = null;
public void DrawStatusHUD(SpriteBatch spriteBatch) public void DrawStatusHUD(SpriteBatch spriteBatch)
{ {
highlightedAfflictionIcon = null;
//Rectangle interactArea = healthBar.Rect; //Rectangle interactArea = healthBar.Rect;
if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null) if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
{ {
@@ -1103,7 +1110,6 @@ namespace Barotrauma
statusIcons.Add(new Pair<Affliction, string>(affliction, affliction.Prefab.Name)); statusIcons.Add(new Pair<Affliction, string>(affliction, affliction.Prefab.Name));
} }
Pair<Affliction, string> highlightedIcon = null;
Vector2 highlightedIconPos = Vector2.Zero; Vector2 highlightedIconPos = Vector2.Zero;
Rectangle afflictionArea = HUDLayoutSettings.AfflictionAreaLeft; Rectangle afflictionArea = HUDLayoutSettings.AfflictionAreaLeft;
@@ -1124,9 +1130,9 @@ namespace Barotrauma
AfflictionPrefab afflictionPrefab = affliction.Prefab; AfflictionPrefab afflictionPrefab = affliction.Prefab;
Rectangle afflictionIconRect = new Rectangle(pos, new Point(iconSize)); 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(); highlightedIconPos = afflictionIconRect.Location.ToVector2();
} }
@@ -1146,7 +1152,7 @@ namespace Barotrauma
highlightedIcon == statusIcon ? slot.HoverColor : slot.Color);*/ highlightedIcon == statusIcon ? slot.HoverColor : slot.Color);*/
float alphaMultiplier = highlightedIcon == statusIcon ? 1f : 0.8f; float alphaMultiplier = highlightedAfflictionIcon == statusIcon ? 1f : 0.8f;
afflictionPrefab.Icon?.Draw(spriteBatch, afflictionPrefab.Icon?.Draw(spriteBatch,
pos.ToVector2(), pos.ToVector2(),
@@ -1161,9 +1167,9 @@ namespace Barotrauma
pos.Y += iconSize + (int)(5 * GUI.Scale); 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); Vector2 offset = GUI.Font.MeasureString(nameTooltip);
GUI.DrawString(spriteBatch, GUI.DrawString(spriteBatch,
@@ -997,8 +997,7 @@ namespace Barotrauma
return editor.GetMouseCursorState(); return editor.GetMouseCursorState();
// Portrait area during gameplay // Portrait area during gameplay
case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true): case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true):
if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || CharacterHealth.IsMouseOnHealthBar())
Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea).Contains(PlayerInput.MousePosition))
{ {
return CursorState.Hand; return CursorState.Hand;
} }
@@ -17,29 +17,30 @@ namespace Barotrauma
private readonly Dictionary<StoreTab, GUIListBox> tabLists = new Dictionary<StoreTab, GUIListBox>(); private readonly Dictionary<StoreTab, GUIListBox> tabLists = new Dictionary<StoreTab, GUIListBox>();
private readonly Dictionary<StoreTab, SortingMethod> tabSortingMethods = new Dictionary<StoreTab, SortingMethod>(); private readonly Dictionary<StoreTab, SortingMethod> tabSortingMethods = new Dictionary<StoreTab, SortingMethod>();
private readonly List<PurchasedItem> itemsToSell = new List<PurchasedItem>(); private readonly List<PurchasedItem> itemsToSell = new List<PurchasedItem>();
private readonly List<PurchasedItem> itemsToSellFromSub = new List<PurchasedItem>();
private StoreTab activeTab = StoreTab.Buy; private StoreTab activeTab = StoreTab.Buy;
private MapEntityCategory? selectedItemCategory; private MapEntityCategory? selectedItemCategory;
private bool suppressBuySell; private bool suppressBuySell;
private int buyTotal, sellTotal; private int buyTotal, sellTotal, sellFromSubTotal;
private GUITextBlock merchantBalanceBlock; private GUITextBlock merchantBalanceBlock;
private GUITextBlock currentSellValueBlock, newSellValueBlock; private GUITextBlock currentSellValueBlock, newSellValueBlock;
private GUIImage sellValueChangeArrow; private GUIImage sellValueChangeArrow;
private GUIDropDown sortingDropDown; private GUIDropDown sortingDropDown;
private GUITextBox searchBox; private GUITextBox searchBox;
private GUIListBox storeBuyList, storeSellList; private GUIListBox storeBuyList, storeSellList, storeSellFromSubList;
/// <summary> /// <summary>
/// Can be null when there are no deals at the current location /// Can be null when there are no deals at the current location
/// </summary> /// </summary>
private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup; private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup, storeRequestedSubGoodGroup;
private Color storeSpecialColor; private Color storeSpecialColor;
private GUIListBox shoppingCrateBuyList, shoppingCrateSellList; private GUIListBox shoppingCrateBuyList, shoppingCrateSellList, shoppingCrateSellFromSubList;
private GUITextBlock shoppingCrateTotal; private GUITextBlock shoppingCrateTotal;
private GUIButton clearAllButton, confirmButton; private GUIButton clearAllButton, confirmButton;
private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh; private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh, needsSellingFromSubRefresh, needsItemsToSellFromSubRefresh;
private Point resolutionWhenCreated; private Point resolutionWhenCreated;
private bool hadPermissions; private bool hadPermissions;
@@ -50,14 +51,29 @@ namespace Barotrauma
private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation; private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation;
private int PlayerMoney => campaignUI.Campaign.Money; private int PlayerMoney => campaignUI.Campaign.Money;
private bool HasPermissions => campaignUI.Campaign.AllowedToManageCampaign(); private bool HasPermissions => campaignUI.Campaign.AllowedToManageCampaign();
private bool IsBuying => activeTab != StoreTab.Sell; private bool IsBuying => activeTab switch
private bool IsSelling => activeTab == StoreTab.Sell; {
private GUIListBox ActiveShoppingCrateList => IsBuying ? shoppingCrateBuyList : shoppingCrateSellList; 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, Buy,
Sell Sell,
SellFromSub
} }
private enum SortingMethod private enum SortingMethod
@@ -73,11 +89,8 @@ namespace Barotrauma
{ {
this.campaignUI = campaignUI; this.campaignUI = campaignUI;
this.parentComponent = parentComponent; this.parentComponent = parentComponent;
hadPermissions = HasPermissions; hadPermissions = HasPermissions;
CreateUI(); CreateUI();
campaignUI.Campaign.Map.OnLocationChanged += UpdateLocation; campaignUI.Campaign.Map.OnLocationChanged += UpdateLocation;
if (CurrentLocation?.Reputation != null) if (CurrentLocation?.Reputation != null)
{ {
@@ -89,8 +102,10 @@ namespace Barotrauma
campaignUI.Campaign.CargoManager.OnSoldItemsChanged += () => campaignUI.Campaign.CargoManager.OnSoldItemsChanged += () =>
{ {
needsItemsToSellRefresh = true; needsItemsToSellRefresh = true;
needsItemsToSellFromSubRefresh = true;
needsRefresh = true; needsRefresh = true;
}; };
campaignUI.Campaign.CargoManager.OnItemsInSellFromSubCrateChanged += () => { needsSellingFromSubRefresh = true; };
} }
public void Refresh(bool updateOwned = true) public void Refresh(bool updateOwned = true)
@@ -99,6 +114,7 @@ namespace Barotrauma
if (updateOwned) { UpdateOwnedItems(); } if (updateOwned) { UpdateOwnedItems(); }
RefreshBuying(updateOwned: false); RefreshBuying(updateOwned: false);
RefreshSelling(updateOwned: false); RefreshSelling(updateOwned: false);
RefreshSellingFromSub(updateOwned: false);
needsRefresh = false; needsRefresh = false;
} }
@@ -124,6 +140,20 @@ namespace Barotrauma
needsSellingRefresh = false; 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() private void CreateUI()
{ {
if (parentComponent.FindChild(c => c.UserData as string == "glow") is GUIComponent glowChild) if (parentComponent.FindChild(c => c.UserData as string == "glow") is GUIComponent glowChild)
@@ -236,9 +266,13 @@ namespace Barotrauma
{ {
if (CurrentLocation != null) if (CurrentLocation != null)
{ {
int balanceAfterTransaction = IsBuying ? int balanceAfterTransaction = activeTab switch
CurrentLocation.StoreCurrentBalance + buyTotal : {
CurrentLocation.StoreCurrentBalance - sellTotal; StoreTab.Buy => CurrentLocation.StoreCurrentBalance + buyTotal,
StoreTab.Sell => CurrentLocation.StoreCurrentBalance - sellTotal,
StoreTab.SellFromSub => CurrentLocation.StoreCurrentBalance - sellFromSubTotal,
_ => throw new NotImplementedException(),
};
if (balanceAfterTransaction != CurrentLocation.StoreCurrentBalance) if (balanceAfterTransaction != CurrentLocation.StoreCurrentBalance)
{ {
var newStatus = Location.GetStoreBalanceStatus(balanceAfterTransaction); var newStatus = Location.GetStoreBalanceStatus(balanceAfterTransaction);
@@ -300,8 +334,14 @@ namespace Barotrauma
tabSortingMethods.Clear(); tabSortingMethods.Clear();
foreach (StoreTab tab in tabs) 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), 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, UserData = tab,
OnClicked = (button, userData) => OnClicked = (button, userData) =>
@@ -416,6 +456,17 @@ namespace Barotrauma
storeRequestedGoodGroup = CreateDealsGroup(storeSellList); storeRequestedGoodGroup = CreateDealsGroup(storeSellList);
tabLists.Add(StoreTab.Sell, 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 ------------------------------------------------------------------------------------------------------------------------------------------ // Shopping Crate ------------------------------------------------------------------------------------------------------------------------------------------
var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.TopRight) 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); 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 }; shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
shoppingCrateSellList = 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) 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) => OnClicked = (button, userData) =>
{ {
if (!HasPermissions) { return false; } if (!HasPermissions) { return false; }
var itemsToRemove = new List<PurchasedItem>(IsBuying ? CargoManager.ItemsInBuyCrate : CargoManager.ItemsInSellCrate); var itemsToRemove = activeTab switch
{
StoreTab.Buy => new List<PurchasedItem>(CargoManager.ItemsInBuyCrate),
StoreTab.Sell => new List<PurchasedItem>(CargoManager.ItemsInSellCrate),
StoreTab.SellFromSub => new List<PurchasedItem>(CargoManager.ItemsInSellFromSubCrate),
_ => throw new NotImplementedException(),
};
itemsToRemove.ForEach(i => ClearFromShoppingCrate(i)); itemsToRemove.ForEach(i => ClearFromShoppingCrate(i));
return true; return true;
} }
@@ -560,6 +621,7 @@ namespace Barotrauma
private void ChangeStoreTab(StoreTab tab) private void ChangeStoreTab(StoreTab tab)
{ {
if (IsTabUnavailable(tab)) { return; }
activeTab = tab; activeTab = tab;
foreach (GUIButton tabButton in storeTabButtons) foreach (GUIButton tabButton in storeTabButtons)
{ {
@@ -571,24 +633,56 @@ namespace Barotrauma
SetConfirmButtonBehavior(); SetConfirmButtonBehavior();
SetConfirmButtonStatus(); SetConfirmButtonStatus();
FilterStoreItems(); FilterStoreItems();
if (tab == StoreTab.Buy) switch (tab)
{ {
storeSellList.Visible = false; case StoreTab.Buy:
storeBuyList.Visible = true; storeSellList.Visible = false;
shoppingCrateSellList.Visible = false; if (storeSellFromSubList != null)
shoppingCrateBuyList.Visible = true; {
} storeSellFromSubList.Visible = false;
else if (tab == StoreTab.Sell) }
{ storeBuyList.Visible = true;
storeBuyList.Visible = false; shoppingCrateSellList.Visible = false;
storeSellList.Visible = true; if (shoppingCrateSellFromSubList != null)
shoppingCrateBuyList.Visible = false; {
shoppingCrateSellList.Visible = true; 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) private void FilterStoreItems(MapEntityCategory? category, string filter)
{ {
if (IsTabUnavailable(activeTab)) { return; }
selectedItemCategory = category; selectedItemCategory = category;
var list = tabLists[activeTab]; var list = tabLists[activeTab];
filter = filter?.ToLower(); filter = filter?.ToLower();
@@ -668,7 +762,7 @@ namespace Barotrauma
if (itemFrame == null) if (itemFrame == null)
{ {
var parentComponent = isDailySpecial ? storeDailySpecialsGroup : storeBuyList as GUIComponent; 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 else
{ {
@@ -688,7 +782,7 @@ namespace Barotrauma
removedItemFrames.AddRange(storeDailySpecialsGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList()); removedItemFrames.AddRange(storeDailySpecialsGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
} }
removedItemFrames.ForEach(f => f.RectTransform.Parent = null); removedItemFrames.ForEach(f => f.RectTransform.Parent = null);
if (IsBuying) { FilterStoreItems(); } if (activeTab == StoreTab.Buy) { FilterStoreItems(); }
SortItems(StoreTab.Buy); SortItems(StoreTab.Buy);
storeBuyList.BarScroll = prevBuyListScroll; storeBuyList.BarScroll = prevBuyListScroll;
@@ -743,7 +837,7 @@ namespace Barotrauma
if (itemFrame == null) if (itemFrame == null)
{ {
var parentComponent = isRequestedGood ? storeRequestedGoodGroup : storeSellList as GUIComponent; 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 else
{ {
@@ -766,13 +860,91 @@ namespace Barotrauma
removedItemFrames.AddRange(storeRequestedGoodGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList()); removedItemFrames.AddRange(storeRequestedGoodGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
} }
removedItemFrames.ForEach(f => f.RectTransform.Parent = null); removedItemFrames.ForEach(f => f.RectTransform.Parent = null);
if (IsSelling) { FilterStoreItems(); } if (activeTab == StoreTab.Sell) { FilterStoreItems(); }
SortItems(StoreTab.Sell); SortItems(StoreTab.Sell);
storeSellList.BarScroll = prevSellListScroll; storeSellList.BarScroll = prevSellListScroll;
shoppingCrateSellList.BarScroll = prevShoppingCrateScroll; shoppingCrateSellList.BarScroll = prevShoppingCrateScroll;
} }
private void RefreshStoreSellFromSubList()
{
float prevSellListScroll = storeSellFromSubList.BarScroll;
float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll;
bool hasPermissions = HasPermissions;
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
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) private void SetPriceGetters(GUIComponent itemFrame, bool buying)
{ {
if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; } if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; }
@@ -834,11 +1006,41 @@ namespace Barotrauma
needsItemsToSellRefresh = false; needsItemsToSellRefresh = false;
} }
private void RefreshShoppingCrateList(List<PurchasedItem> 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<PurchasedItem>(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<PurchasedItem> items, GUIListBox listBox, StoreTab tab)
{ {
bool hasPermissions = HasPermissions; bool hasPermissions = HasPermissions;
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>(); HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
bool refreshingBuyList = listBox == shoppingCrateBuyList;
int totalPrice = 0; int totalPrice = 0;
foreach (PurchasedItem item in items) foreach (PurchasedItem item in items)
{ {
@@ -849,7 +1051,7 @@ namespace Barotrauma
GUINumberInput numInput = null; GUINumberInput numInput = null;
if (itemFrame == 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; numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
} }
else else
@@ -860,7 +1062,7 @@ namespace Barotrauma
{ {
numInput.UserData = item; numInput.UserData = item;
numInput.Enabled = hasPermissions; numInput.Enabled = hasPermissions;
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, refreshingBuyList ? StoreTab.Buy : StoreTab.Sell); numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab);
} }
SetOwnedLabelText(itemFrame); SetOwnedLabelText(itemFrame);
SetItemFrameStatus(itemFrame, hasPermissions); SetItemFrameStatus(itemFrame, hasPermissions);
@@ -875,7 +1077,7 @@ namespace Barotrauma
} }
suppressBuySell = false; suppressBuySell = false;
var price = refreshingBuyList ? var price = tab == StoreTab.Buy ?
CurrentLocation.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo) : CurrentLocation.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo) :
CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo); CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo);
totalPrice += item.Quantity * price; totalPrice += item.Quantity * price;
@@ -886,23 +1088,31 @@ namespace Barotrauma
SortItems(listBox, SortingMethod.CategoryAsc); SortItems(listBox, SortingMethod.CategoryAsc);
listBox.UpdateScrollBarSize(); listBox.UpdateScrollBarSize();
if (refreshingBuyList) switch (tab)
{ {
buyTotal = totalPrice; case StoreTab.Buy:
if (IsBuying) { SetShoppingCrateTotalText(); } buyTotal = totalPrice;
break;
case StoreTab.Sell:
sellTotal = totalPrice;
break;
case StoreTab.SellFromSub:
sellFromSubTotal = totalPrice;
break;
} }
else if (activeTab == tab)
{ {
sellTotal = totalPrice; SetShoppingCrateTotalText();
if(IsSelling) { SetShoppingCrateTotalText(); }
} }
SetClearAllButtonStatus(); SetClearAllButtonStatus();
SetConfirmButtonStatus(); 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) private void SortItems(GUIListBox list, SortingMethod sortingMethod)
{ {
@@ -934,7 +1144,7 @@ namespace Barotrauma
else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc) else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc)
{ {
SortItems(list, SortingMethod.AlphabeticalAsc); SortItems(list, SortingMethod.AlphabeticalAsc);
if (list == storeSellList || list == shoppingCrateSellList) if (list != storeBuyList && list != shoppingCrateBuyList)
{ {
list.Content.RectTransform.SortChildren(CompareBySellPrice); list.Content.RectTransform.SortChildren(CompareBySellPrice);
if (GetSpecialsGroup() is GUILayoutGroup specialsGroup) if (GetSpecialsGroup() is GUILayoutGroup specialsGroup)
@@ -1016,6 +1226,10 @@ namespace Barotrauma
{ {
return storeRequestedGoodGroup; return storeRequestedGoodGroup;
} }
else if (list == storeSellFromSubList)
{
return storeRequestedSubGoodGroup;
}
else else
{ {
return null; return null;
@@ -1047,15 +1261,20 @@ namespace Barotrauma
private void SortItems(StoreTab tab, SortingMethod sortingMethod) private void SortItems(StoreTab tab, SortingMethod sortingMethod)
{ {
if (IsTabUnavailable(tab)) { return; }
tabSortingMethods[tab] = sortingMethod; tabSortingMethods[tab] = sortingMethod;
SortItems(tabLists[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 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; var tooltip = pi.ItemPrefab.Name;
if (!string.IsNullOrWhiteSpace(pi.ItemPrefab.Description)) if (!string.IsNullOrWhiteSpace(pi.ItemPrefab.Description))
@@ -1116,8 +1335,8 @@ namespace Barotrauma
CanBeFocused = false, CanBeFocused = false,
Stretch = true Stretch = true
}; };
var isSellingRelatedList = parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup || parentComponent == shoppingCrateSellList; bool isSellingRelatedList = containingTab != StoreTab.Buy;
var locationHasDealOnItem = isSellingRelatedList ? bool locationHasDealOnItem = isSellingRelatedList ?
CurrentLocation.RequestedGoods.Contains(pi.ItemPrefab) : CurrentLocation.DailySpecials.Contains(pi.ItemPrefab); CurrentLocation.RequestedGoods.Contains(pi.ItemPrefab) : CurrentLocation.DailySpecials.Contains(pi.ItemPrefab);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), nameAndQuantityGroup.RectTransform), GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), nameAndQuantityGroup.RectTransform),
pi.ItemPrefab.Name, font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft) pi.ItemPrefab.Name, font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft)
@@ -1142,14 +1361,15 @@ namespace Barotrauma
}; };
dealIcon.SetAsFirstChild(); dealIcon.SetAsFirstChild();
} }
var isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup || bool isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup; parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup ||
parentComponent == storeSellFromSubList || parentComponent == storeRequestedSubGoodGroup;
GUILayoutGroup shoppingCrateAmountGroup = null; GUILayoutGroup shoppingCrateAmountGroup = null;
GUINumberInput amountInput = null; GUINumberInput amountInput = null;
if (isParentOnLeftSideOfInterface) if (isParentOnLeftSideOfInterface)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), nameAndQuantityGroup.RectTransform), 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, CanBeFocused = false,
Shadow = locationHasDealOnItem, Shadow = locationHasDealOnItem,
@@ -1158,7 +1378,7 @@ namespace Barotrauma
UserData = "quantitylabel" UserData = "quantitylabel"
}; };
} }
else if (!isParentOnLeftSideOfInterface) else
{ {
var relativePadding = nameBlock.Padding.X / nameBlock.Rect.Width; 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) }, 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) amountInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), shoppingCrateAmountGroup.RectTransform), GUINumberInput.NumberType.Int)
{ {
MinValueInt = 0, MinValueInt = 0,
MaxValueInt = GetMaxAvailable(pi.ItemPrefab, isSellingRelatedList ? StoreTab.Sell : StoreTab.Buy), MaxValueInt = GetMaxAvailable(pi.ItemPrefab, containingTab),
UserData = pi, UserData = pi,
IntValue = pi.Quantity 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.quantity", "[amount]", quantity.ToString()) :
TextManager.GetWithVariable("campaignstore.instock", "[amount]", quantity.ToString()); TextManager.GetWithVariable("campaignstore.instock", "[amount]", quantity.ToString());
@@ -1419,10 +1639,16 @@ namespace Barotrauma
private int GetMaxAvailable(ItemPrefab itemPrefab, StoreTab mode) 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 (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); var purchasedItem = CargoManager.PurchasedItems.Find(i => i.ItemPrefab == item.ItemPrefab);
if (purchasedItem != null) { return Math.Max(item.Quantity - purchasedItem.Quantity, 0); } if (purchasedItem != null) { return Math.Max(item.Quantity - purchasedItem.Quantity, 0); }
@@ -1471,11 +1697,37 @@ namespace Barotrauma
return false; return false;
} }
private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => IsBuying ? private bool ModifySellFromSubQuantity(PurchasedItem item, int quantity)
ModifyBuyQuantity(item, quantity) : ModifySellQuantity(item, 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 ? private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => activeTab switch
ModifyBuyQuantity(item, -item.Quantity) : ModifySellQuantity(item, -item.Quantity); {
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() private bool BuyItems()
{ {
@@ -1512,18 +1764,17 @@ namespace Barotrauma
private bool SellItems() private bool SellItems()
{ {
if (!HasPermissions) { return false; } if (!HasPermissions) { return false; }
var itemsToSell = activeTab switch
var itemsToSell = new List<PurchasedItem>(CargoManager.ItemsInSellCrate); {
StoreTab.Sell => new List<PurchasedItem>(CargoManager.ItemsInSellCrate),
StoreTab.SellFromSub => new List<PurchasedItem>(CargoManager.ItemsInSellFromSubCrate),
_ => throw new NotImplementedException()
};
var itemsToRemove = new List<PurchasedItem>(); var itemsToRemove = new List<PurchasedItem>();
var totalValue = 0; var totalValue = 0;
foreach (PurchasedItem item in itemsToSell) foreach (PurchasedItem item in itemsToSell)
{ {
if (item?.ItemPrefab == null) if (item?.ItemPrefab?.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo)
{
itemsToRemove.Add(item);
continue;
}
if (item.ItemPrefab.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo)
{ {
totalValue += item.Quantity * CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo); totalValue += item.Quantity * CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo);
} }
@@ -1533,12 +1784,13 @@ namespace Barotrauma
} }
} }
itemsToRemove.ForEach(i => itemsToSell.Remove(i)); itemsToRemove.ForEach(i => itemsToSell.Remove(i));
if (itemsToSell.None() || totalValue > CurrentLocation.StoreCurrentBalance) { return false; } if (itemsToSell.None() || totalValue > CurrentLocation.StoreCurrentBalance) { return false; }
CargoManager.SellItems(itemsToSell, activeTab);
CargoManager.SellItems(itemsToSell); if (activeTab == StoreTab.Sell)
GameMain.Client?.SendCampaignState(); {
// TODO: Implement selling sub items in multiplayer
GameMain.Client?.SendCampaignState();
}
return false; return false;
} }
@@ -1551,8 +1803,14 @@ namespace Barotrauma
} }
else else
{ {
shoppingCrateTotal.Text = GetCurrencyFormatted(sellTotal); int total = activeTab switch
shoppingCrateTotal.TextColor = CurrentLocation != null && sellTotal > CurrentLocation.StoreCurrentBalance ? Color.Red : Color.White; {
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 = private void SetConfirmButtonStatus() => confirmButton.Enabled =
HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any() && 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 = private void SetClearAllButtonStatus() => clearAllButton.Enabled =
HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any(); HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any();
private float ownedItemsUpdateTimer = 0.0f; private float ownedItemsUpdateTimer = 0.0f, sellableItemsFromSubUpdateTimer = 0.0f;
private readonly float ownedItemsUpdateInterval = 1.5f; private readonly float timerUpdateInterval = 1.5f;
public void Update(float deltaTime) 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 // Update the owned items at short intervals and check if the interface should be refreshed
ownedItemsUpdateTimer += deltaTime; ownedItemsUpdateTimer += deltaTime;
if (ownedItemsUpdateTimer >= ownedItemsUpdateInterval) if (ownedItemsUpdateTimer >= timerUpdateInterval)
{ {
var prevOwnedItems = new Dictionary<ItemPrefab, int>(OwnedItems); var prevOwnedItems = new Dictionary<ItemPrefab, int>(OwnedItems);
UpdateOwnedItems(); UpdateOwnedItems();
@@ -1614,12 +1878,21 @@ namespace Barotrauma
needsRefresh = true; 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 (needsItemsToSellRefresh) { RefreshItemsToSell(); }
if (needsItemsToSellFromSubRefresh) { RefreshItemsToSellFromSub(); }
if (needsRefresh || hadPermissions != HasPermissions) { Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f); } if (needsRefresh || hadPermissions != HasPermissions) { Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f); }
if (needsBuyingRefresh) { RefreshBuying(); } if (needsBuyingRefresh) { RefreshBuying(); }
if (needsSellingRefresh) { RefreshSelling(); } if (needsSellingRefresh) { RefreshSelling(); }
if (needsSellingFromSubRefresh) { RefreshSellingFromSub(updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f); }
} }
} }
} }
@@ -1,4 +1,5 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -42,10 +43,7 @@ namespace Barotrauma
public IEnumerable<Item> GetSellableItems(Character character) public IEnumerable<Item> GetSellableItems(Character character)
{ {
if (character == null) { return new List<Item>(); } if (character == null) { return new List<Item>(); }
// Only consider items which have been: var confirmedSoldEntities = GetConfirmedSoldEntities();
// 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);
// The bag slot is intentionally left out since we want to be able to sell items from there // The bag slot is intentionally left out since we want to be able to sell items from there
var equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card }; var equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
return character.Inventory.FindAllItems(item => return character.Inventory.FindAllItems(item =>
@@ -72,6 +70,43 @@ namespace Barotrauma
} }
} }
public IEnumerable<Item> GetSellableItemsFromSub()
{
if (Submarine.MainSub == null) { return new List<Item>(); }
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<SoldEntity> 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<PurchasedItem> items) public void SetItemsInBuyCrate(List<PurchasedItem> items)
{ {
ItemsInBuyCrate.Clear(); ItemsInBuyCrate.Clear();
@@ -119,10 +154,34 @@ namespace Barotrauma
OnItemsInSellCrateChanged?.Invoke(); OnItemsInSellCrateChanged?.Invoke();
} }
public void SellItems(List<PurchasedItem> itemsToSell) public void ModifyItemQuantityInSellFromSubCrate(ItemPrefab itemPrefab, int changeInQuantity)
{ {
var itemsInInventory = GetSellableItems(Character.Controlled); var itemToSell = ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == itemPrefab);
var canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null; 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<PurchasedItem> 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; var sellerId = GameMain.Client?.ID ?? 0;
// Check all the prices before starting the transaction // Check all the prices before starting the transaction
@@ -137,7 +196,7 @@ namespace Barotrauma
if (Location.StoreCurrentBalance < itemValue) { continue; } 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) // 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) if (matchingItems.Count() <= item.Quantity)
{ {
foreach (Item i in matchingItems) foreach (Item i in matchingItems)
@@ -163,12 +222,21 @@ namespace Barotrauma
campaign.Money += itemValue; campaign.Money += itemValue;
// Remove from the sell crate // 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; inventoryItem.Quantity -= item.Quantity;
if (itemToSell.Quantity < 1) 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);
} }
} }
} }
@@ -123,17 +123,17 @@ namespace Barotrauma.Tutorials
SetDoorAccess(tutorial_lockedDoor_2, null, false); SetDoorAccess(tutorial_lockedDoor_2, null, false);
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("mechanic")); 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.TeamID = CharacterTeamType.Team1;
captain_mechanic.GiveJobItems(); captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer")); 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.TeamID = CharacterTeamType.Team1;
captain_security.GiveJobItems(); captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer")); 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.TeamID = CharacterTeamType.Team1;
captain_engineer.GiveJobItems(); captain_engineer.GiveJobItems();
@@ -94,19 +94,19 @@ namespace Barotrauma.Tutorials
patient2.AIController.Enabled = false; patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer")); 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.TeamID = CharacterTeamType.Team1;
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1); subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer")); 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.TeamID = CharacterTeamType.Team1;
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2); subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer")); 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.TeamID = CharacterTeamType.Team1;
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient3); subPatients.Add(subPatient3);
@@ -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) protected bool HasOrder(Character character, string identifier, string option = null)
@@ -1077,8 +1077,8 @@ namespace Barotrauma
new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform), new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft); 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 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.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice)) 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, SelectedColor = Color.Gold * 0.3f,
UserData = InputType.Voice UserData = InputType.Voice
@@ -1089,14 +1089,16 @@ namespace Barotrauma
new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform), new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft); 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 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.4f, 1.0f), localVoiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.LocalVoice)) 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, SelectedColor = Color.Gold * 0.3f,
UserData = InputType.LocalVoice UserData = InputType.LocalVoice
}; };
localVoiceKeyBox.OnSelected += KeyBoxSelected; 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) var cutoffPreventionText = new GUITextBlock(new RectTransform(textBlockScale, voiceChatContent.RectTransform), TextManager.Get("CutoffPrevention"), font: GUI.SubHeadingFont)
{ {
ToolTip = TextManager.Get("CutoffPreventionTooltip") ToolTip = TextManager.Get("CutoffPreventionTooltip")
@@ -7,13 +7,15 @@ namespace Barotrauma.Items.Components
{ {
private void GetDamageModifierText(ref string description, float damageMultiplier, string afflictionIdentifier) 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); 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) 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"; description += "\n";
} }
@@ -22,7 +24,7 @@ namespace Barotrauma.Items.Components
{ {
foreach (DamageModifier damageModifier in damageModifiers) foreach (DamageModifier damageModifier in damageModifiers)
{ {
if (damageModifier.DamageMultiplier == 1f) if (MathUtils.NearlyEqual(damageModifier.DamageMultiplier, 1f))
{ {
continue; continue;
} }
@@ -42,7 +44,9 @@ namespace Barotrauma.Items.Components
foreach (var skillModifier in SkillModifiers) foreach (var skillModifier in SkillModifiers)
{ {
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green); 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}";
} }
} }
} }
@@ -480,7 +480,10 @@ namespace Barotrauma
if (IsSelected || IsHighlighted) 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))); Color.White, false, 0, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
foreach (Rectangle t in Prefab.Triggers) foreach (Rectangle t in Prefab.Triggers)
@@ -527,7 +527,7 @@ namespace Barotrauma.Networking
string pwMsg = TextManager.Get("PasswordRequired"); string pwMsg = TextManager.Get("PasswordRequired");
var msgBox = new GUIMessageBox(pwMsg, "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, 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 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) }) 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) 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(); passwordHolder.Recalculate();
} }
@@ -469,18 +469,17 @@ namespace Barotrauma
this.game = game; this.game = game;
menuTabs[(int)Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null); 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"); {
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); 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"); 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 #endregion
@@ -1514,6 +1514,9 @@ namespace Barotrauma
case SubmarineType.Wreck: case SubmarineType.Wreck:
contentType = ContentType.Wreck; contentType = ContentType.Wreck;
break; break;
case SubmarineType.EnemySubmarine:
contentType = ContentType.EnemySubmarine;
break;
} }
if (contentType != ContentType.Submarine) if (contentType != ContentType.Submarine)
{ {
@@ -1829,7 +1832,12 @@ namespace Barotrauma
subTypeContainer.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y)); subTypeContainer.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
foreach (SubmarineType subType in Enum.GetValues(typeof(SubmarineType))) 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) 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) }, 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 CanBeFocused = false
}; };
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1400.2.0</Version> <Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1400.2.0</Version> <Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1400.2.0</Version> <Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1400.2.0</Version> <Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1400.2.0</Version> <Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1400.2.0</Version> <Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -66,7 +66,7 @@ namespace Barotrauma
private float Sight => AIParams.Sight; private float Sight => AIParams.Sight;
private float Hearing => AIParams.Hearing; private float Hearing => AIParams.Hearing;
private float FleeHealthThreshold => AIParams.FleeHealthThreshold; private float FleeHealthThreshold => AIParams.FleeHealthThreshold;
private bool AggressiveBoarding => AIParams.AggressiveBoarding; private bool IsAggressiveBoarder => AIParams.AggressiveBoarding;
private FishAnimController FishAnimController => Character.AnimController as FishAnimController; private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
@@ -1271,7 +1271,7 @@ namespace Barotrauma
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition; Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition;
Vector2 toTarget = attackWorldPos - attackLimbPos; 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 // 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) if (wallTarget.Structure.Submarine != null)
{ {
@@ -1440,10 +1440,11 @@ namespace Barotrauma
else else
{ {
pathSteering.SteeringSeek(steerPos, 2, startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), checkVisiblity: true); 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 (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
if ((!canAttackWalls || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{ {
State = AIState.Idle; State = AIState.Idle;
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
return; return;
} }
} }
@@ -1674,16 +1675,6 @@ namespace Barotrauma
} }
if (canAttack) if (canAttack)
{ {
if (SelectedAiTarget.Entity is Item targetItem)
{
var door = targetItem.GetComponent<Door>();
if (door != null && door.CanBeTraversed)
{
ResetAITarget();
State = PreviousState;
return;
}
}
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb)) if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
{ {
IgnoreTarget(SelectedAiTarget); IgnoreTarget(SelectedAiTarget);
@@ -2292,25 +2283,24 @@ namespace Barotrauma
var section = s.Sections[i]; var section = s.Sections[i];
if (section.gap == null) { continue; } if (section.gap == null) { continue; }
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null; bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
isInnerWall = isInnerWall || !leadsInside;
if (Character.AnimController.CanEnterSubmarine) if (Character.AnimController.CanEnterSubmarine)
{ {
if (!isCharacterInside) if (!isCharacterInside)
{ {
if (CanPassThroughHole(s, i)) 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 // Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open; valueModifier *= 1 + section.gap.Open * 0.25f;
} }
} }
else else
{ {
// Inside // Inside
if (AggressiveBoarding) if (IsAggressiveBoarder)
{ {
if (!isInnerWall) if (!isInnerWall)
{ {
@@ -2327,6 +2317,10 @@ namespace Barotrauma
valueModifier = 0; valueModifier = 0;
break; break;
} }
else
{
valueModifier = 0.1f;
}
} }
else else
{ {
@@ -2350,7 +2344,7 @@ namespace Barotrauma
valueModifier = 0; valueModifier = 0;
break; break;
} }
else if (AggressiveBoarding) else if (IsAggressiveBoarder)
{ {
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside // Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
// (Bonethreshers) // (Bonethreshers)
@@ -2384,17 +2378,24 @@ namespace Barotrauma
// Ignore broken and open doors, if cannot enter submarine // Ignore broken and open doors, if cannot enter submarine
continue; 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) 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 else
{ {
// Inside -> ignore open doors and outer doors // 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) private void UpdateWallTarget(int requiredHoleCount)
{ {
wallTarget = null; wallTarget = null;
if (AIParams.CanOpenDoors && HasValidPath(requireNonDirty: true)) { return; }
if (SelectedAiTarget == null) { return; } if (SelectedAiTarget == null) { return; }
if (SelectedAiTarget.Entity == null) { return; } if (SelectedAiTarget.Entity == null) { return; }
if (HasValidPath(requireNonDirty: true)) { return; }
wallHits.Clear(); wallHits.Clear();
Structure wall = null; Structure wall = null;
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target)) if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
@@ -2729,9 +2730,12 @@ namespace Barotrauma
{ {
if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine) if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine)
{ {
bool isTargetingDoor = SelectedAiTarget.Entity is Item i && i.GetComponent<Door>() != 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. // 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); if (!isTargetingDoor)
ResetAITarget(); {
ResetAITarget();
}
} }
else else
{ {
@@ -2741,7 +2745,6 @@ namespace Barotrauma
else else
{ {
// Blocked by a disabled wall. // Blocked by a disabled wall.
IgnoreTarget(SelectedAiTarget);
ResetAITarget(); ResetAITarget();
} }
} }
@@ -1926,26 +1926,33 @@ namespace Barotrauma
public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter) public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter)
{ {
operatingCharacter = null; operatingCharacter = null;
float highestPriority = -1.0f;
float highestPriorityModifier = -1.0f;
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
if (c.Removed) { continue; } if (c.Removed) { continue; }
if (c.TeamID != team) { continue; } if (c.TeamID != team) { continue; }
if (c.IsIncapacitated) { continue; } if (c.IsIncapacitated) { continue; }
bool isOperated = c.SelectedConstruction == target.Item; if (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)
{ {
operatingCharacter = c;
return true; 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. // 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.
@@ -637,7 +637,7 @@ namespace Barotrauma
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity)); impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
} }
} }
return true; return !f2.IsSensor;
} }
Vector2 colliderBottom = GetColliderBottom(); Vector2 colliderBottom = GetColliderBottom();
@@ -1541,6 +1541,7 @@ namespace Barotrauma
case Physics.CollisionLevel: case Physics.CollisionLevel:
if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; } if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; }
if (fixture.Body.UserData is Submarine && character.Submarine != null) { return -1; } if (fixture.Body.UserData is Submarine && character.Submarine != null) { return -1; }
if (fixture.IsSensor) { return -1; }
if (fraction < standOnFloorFraction) if (fraction < standOnFloorFraction)
{ {
standOnFloorFraction = fraction; standOnFloorFraction = fraction;
@@ -2377,6 +2377,10 @@ namespace Barotrauma
var item = FindItemAtPosition(mouseSimPos, aimAssist); var item = FindItemAtPosition(mouseSimPos, aimAssist);
focusedItem = CanInteract ? item : null; focusedItem = CanInteract ? item : null;
if (focusedItem != null && focusedItem.CampaignInteractionType != CampaignMode.InteractionType.None)
{
FocusedCharacter = null;
}
findFocusedTimer = 0.05f; findFocusedTimer = 0.05f;
} }
} }
@@ -23,7 +23,17 @@ namespace Barotrauma
protected PropertyConditional.OperatorType Operator { get; set; } 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() protected override bool? DetermineSuccess()
{ {
@@ -213,7 +213,14 @@ namespace Barotrauma
if (dialogOpened) if (dialogOpened)
{ {
#if CLIENT #if CLIENT
Character.DisableControls = true; if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "ConversationAction"))
{
Character.DisableControls = true;
}
else
{
Reset();
}
#endif #endif
if (ShouldInterrupt()) if (ShouldInterrupt())
{ {
@@ -20,11 +20,7 @@ namespace Barotrauma
{ {
if (string.IsNullOrWhiteSpace(ItemIdentifier)) if (string.IsNullOrWhiteSpace(ItemIdentifier))
{ {
ItemIdentifier = element.GetAttributeString("itemidentifiers", ""); ItemIdentifier = element.GetAttributeString("itemidentifiers", null) ?? element.GetAttributeString("identifier", "");
}
if (string.IsNullOrWhiteSpace(ItemIdentifier))
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - RemoveItemAction without an item identifier.");
} }
} }
@@ -66,7 +62,7 @@ namespace Barotrauma
var item = inventory.FindItem(it => var item = inventory.FindItem(it =>
it != null && it != null &&
!removedItems.Contains(it) && !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; } if (item == null) { break; }
Entity.Spawner.AddToRemoveQueue(item); Entity.Spawner.AddToRemoveQueue(item);
removedItems.Add(item); removedItems.Add(item);
@@ -74,7 +70,7 @@ namespace Barotrauma
} }
else if (target is Item item) 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); Entity.Spawner.AddToRemoveQueue(item);
removedItems.Add(item); removedItems.Add(item);
@@ -129,7 +129,7 @@ namespace Barotrauma
TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use))); TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use)));
#else #else
npc.SetCustomInteract( npc.SetCustomInteract(
Trigger, (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.Get("CampaignInteraction.Talk")); TextManager.Get("CampaignInteraction.Talk"));
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction }); GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
#endif #endif
@@ -185,7 +185,7 @@ namespace Barotrauma
if (element.Attribute("identifier") != null && element.Attribute("from") != null) if (element.Attribute("identifier") != null && element.Attribute("from") != null)
{ {
HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element); HumanPrefab humanPrefab = GetHumanPrefabFromElement(element);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
LoadHuman(humanPrefab, element, submarine); LoadHuman(humanPrefab, element, submarine);
@@ -42,7 +42,7 @@ namespace Barotrauma
{ {
this.sub = sub; this.sub = sub;
itemConfig = prefab.ConfigElement.Element("Items"); 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(); DetermineCargo();
} }
@@ -84,8 +84,7 @@ namespace Barotrauma
{ {
characters.Clear(); characters.Clear();
characterItems.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); WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
Rand.RandSync randSync = Rand.RandSync.Server; Rand.RandSync randSync = Rand.RandSync.Server;
@@ -95,12 +94,30 @@ namespace Barotrauma
randSync = Rand.RandSync.Unsynced; 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()) foreach (XElement element in characterConfig.Elements())
{ {
int count = CalculateScalingEscortedCharacterCount(inMission: true); int count = CalculateScalingEscortedCharacterCount(inMission: true);
for (int i = 0; i < count; i++) 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) if (spawnedCharacter.AIController is HumanAIController humanAI)
{ {
humanAI.InitMentalStateManager(); humanAI.InitMentalStateManager();
@@ -293,6 +293,7 @@ namespace Barotrauma
/// </summary> /// </summary>
private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger) private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger)
{ {
if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; }
if (trigger.Delay > 0) if (trigger.Delay > 0)
{ {
if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger)) if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger))
@@ -311,6 +312,7 @@ namespace Barotrauma
/// </summary> /// </summary>
private void TriggerEvent(MissionPrefab.TriggerEvent trigger) 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)); var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier.Equals(trigger.EventIdentifier, StringComparison.OrdinalIgnoreCase));
if (eventPrefab == null) if (eventPrefab == null)
{ {
@@ -398,7 +400,7 @@ namespace Barotrauma
public virtual void AdjustLevelData(LevelData levelData) { } 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) // 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) if (element.Attribute("name") != null)
{ {
@@ -117,6 +117,9 @@ namespace Barotrauma
[Serialize(0.0f, true)] [Serialize(0.0f, true)]
public float Delay { get; private set; } public float Delay { get; private set; }
[Serialize(false, true)]
public bool CampaignOnly { get; private set; }
public TriggerEvent(XElement element) public TriggerEvent(XElement element)
{ {
SerializableProperty.DeserializeProperties(this, element); SerializableProperty.DeserializeProperties(this, element);
@@ -191,6 +191,8 @@ namespace Barotrauma
} }
enemySub.EnableMaintainPosition(); enemySub.EnableMaintainPosition();
enemySub.TeamID = CharacterTeamType.None; 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() private void InitPirates()
@@ -230,7 +232,7 @@ namespace Barotrauma
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier); 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) if (!commanderAssigned)
{ {
bool isCommander = variantElement.GetAttributeBool("iscommander", false); bool isCommander = variantElement.GetAttributeBool("iscommander", false);
@@ -431,7 +431,15 @@ namespace Barotrauma
float scatterAmount = scatter; float scatterAmount = scatter;
if (spawnPosType.HasFlag(Level.PositionType.SidePath)) 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)) else if (!spawnPosType.HasFlag(Level.PositionType.MainPath))
{ {
@@ -46,6 +46,7 @@ namespace Barotrauma
public List<PurchasedItem> ItemsInBuyCrate { get; } = new List<PurchasedItem>(); public List<PurchasedItem> ItemsInBuyCrate { get; } = new List<PurchasedItem>();
public List<PurchasedItem> ItemsInSellCrate { get; } = new List<PurchasedItem>(); public List<PurchasedItem> ItemsInSellCrate { get; } = new List<PurchasedItem>();
public List<PurchasedItem> ItemsInSellFromSubCrate { get; } = new List<PurchasedItem>();
public List<PurchasedItem> PurchasedItems { get; } = new List<PurchasedItem>(); public List<PurchasedItem> PurchasedItems { get; } = new List<PurchasedItem>();
public List<SoldItem> SoldItems { get; } = new List<SoldItem>(); public List<SoldItem> SoldItems { get; } = new List<SoldItem>();
@@ -55,6 +56,7 @@ namespace Barotrauma
public Action OnItemsInBuyCrateChanged; public Action OnItemsInBuyCrateChanged;
public Action OnItemsInSellCrateChanged; public Action OnItemsInSellCrateChanged;
public Action OnItemsInSellFromSubCrateChanged;
public Action OnPurchasedItemsChanged; public Action OnPurchasedItemsChanged;
public Action OnSoldItemsChanged; public Action OnSoldItemsChanged;
@@ -75,6 +77,12 @@ namespace Barotrauma
OnItemsInSellCrateChanged?.Invoke(); OnItemsInSellCrateChanged?.Invoke();
} }
public void ClearItemsInSellFromSubCrate()
{
ItemsInSellFromSubCrate.Clear();
OnItemsInSellFromSubCrateChanged?.Invoke();
}
public void SetPurchasedItems(List<PurchasedItem> items) public void SetPurchasedItems(List<PurchasedItem> items)
{ {
PurchasedItems.Clear(); PurchasedItems.Clear();
@@ -417,7 +417,7 @@ namespace Barotrauma
if (isControlledCharacterNull) { return null; } if (isControlledCharacterNull) { return null; }
#endif #endif
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemTargetedBySomeone(order.TargetItemComponent, controlledCharacter != null ? controlledCharacter.TeamID : CharacterTeamType.Team1, out Character operatingCharacter) && 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; return operatingCharacter;
} }
@@ -558,12 +558,14 @@ namespace Barotrauma
{ {
CargoManager.ClearItemsInBuyCrate(); CargoManager.ClearItemsInBuyCrate();
CargoManager.ClearItemsInSellCrate(); CargoManager.ClearItemsInSellCrate();
CargoManager.ClearItemsInSellFromSubCrate();
} }
else else
{ {
if (GameMain.NetworkMember.IsServer) if (GameMain.NetworkMember.IsServer)
{ {
CargoManager?.ClearItemsInBuyCrate(); CargoManager?.ClearItemsInBuyCrate();
// TODO: CargoManager?.ClearItemsInSellFromSubCrate();
} }
else if (GameMain.NetworkMember.IsClient) else if (GameMain.NetworkMember.IsClient)
{ {
@@ -8,7 +8,6 @@ namespace Barotrauma.Items.Components
{ {
partial class MotionSensor : ItemComponent partial class MotionSensor : ItemComponent
{ {
private const float UpdateInterval = 0.1f;
private float rangeX, rangeY; private float rangeX, rangeY;
private Vector2 detectOffset; 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; 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.")] [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 public int MaxOutputLength
@@ -135,6 +141,9 @@ namespace Barotrauma.Items.Components
{ {
rangeX = rangeY = element.GetAttributeFloat("range", 0.0f); 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) public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
@@ -17,7 +17,7 @@ namespace Barotrauma
public readonly string Identifier; public readonly string Identifier;
public readonly int OriginalContainerIndex; 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; OriginalID = originalID;
OriginalContainerIndex = originalContainerIndex; OriginalContainerIndex = originalContainerIndex;
@@ -345,7 +345,7 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\""); DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\"");
continue; 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]}\""); DebugConsole.ThrowError($"Error in saved location: could not parse taken container index \"{takenItemSplit[2]}\"");
continue; continue;
@@ -577,6 +577,10 @@ namespace Barotrauma
{ {
weight /= missionCount * 2; weight /= missionCount * 2;
} }
if (destination.IsRadiated())
{
weight *= 0.001f;
}
} }
return weight; return weight;
} }
@@ -53,7 +53,16 @@ namespace Barotrauma
//dimensions of the wall sections' physics bodies (only used for debug rendering) //dimensions of the wall sections' physics bodies (only used for debug rendering)
private readonly List<Vector2> bodyDebugDimensions = new List<Vector2>(); private readonly List<Vector2> bodyDebugDimensions = new List<Vector2>();
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 //sections of the wall that are supposed to be rendered
public WallSection[] Sections public WallSection[] Sections
@@ -798,7 +807,7 @@ namespace Barotrauma
public void AddDamage(int sectionIndex, float damage, Character attacker = null) 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; } if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) { return; }
@@ -23,7 +23,7 @@ namespace Barotrauma
HideInMenus = 2 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 } public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
partial class SubmarineInfo : IDisposable partial class SubmarineInfo : IDisposable
@@ -766,14 +766,14 @@ namespace Barotrauma
if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); } 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 => return WayPointList.GetRandom(wp =>
wp.Submarine == sub && wp.Submarine == sub &&
wp.ParentRuin == ruin && wp.ParentRuin == ruin &&
wp.spawnType == spawnType && wp.spawnType == spawnType &&
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob.Prefab)) (assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
, useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced); useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
} }
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine) public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)
Binary file not shown.
+30
View File
@@ -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) v0.1400.2.0 (unstable)
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------