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

View File

@@ -698,6 +698,12 @@ namespace Barotrauma
}
}
public static bool IsMouseOnHealthBar()
{
if (Character.Controlled?.CharacterHealth == null) { return false; }
return Character.Controlled.CharacterHealth.healthBar.State == GUIComponent.ComponentState.Hover;
}
public void UpdateHUD(float deltaTime)
{
if (GUI.DisableHUD) return;
@@ -966,11 +972,9 @@ namespace Barotrauma
highlightedLimbIndex = -1;
}
Rectangle hoverArea = Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea);
healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud();
if (Character.AllowInput && UseHealthWindow && healthBar.Enabled && healthBar.CanBeFocused &&
hoverArea.Contains(PlayerInput.MousePosition) && Inventory.SelectedSlot == null)
(GUI.IsMouseOn(healthBar) || highlightedAfflictionIcon != null) && Inventory.SelectedSlot == null)
{
healthBar.State = GUIComponent.ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonClicked())
@@ -1087,8 +1091,11 @@ namespace Barotrauma
DrawStatusHUD(spriteBatch);
}
private Pair<Affliction, string> highlightedAfflictionIcon = null;
public void DrawStatusHUD(SpriteBatch spriteBatch)
{
highlightedAfflictionIcon = null;
//Rectangle interactArea = healthBar.Rect;
if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
{
@@ -1103,7 +1110,6 @@ namespace Barotrauma
statusIcons.Add(new Pair<Affliction, string>(affliction, affliction.Prefab.Name));
}
Pair<Affliction, string> highlightedIcon = null;
Vector2 highlightedIconPos = Vector2.Zero;
Rectangle afflictionArea = HUDLayoutSettings.AfflictionAreaLeft;
@@ -1124,9 +1130,9 @@ namespace Barotrauma
AfflictionPrefab afflictionPrefab = affliction.Prefab;
Rectangle afflictionIconRect = new Rectangle(pos, new Point(iconSize));
if (afflictionIconRect.Contains(PlayerInput.MousePosition) && !Character.ShouldLockHud())
if (afflictionIconRect.Contains(PlayerInput.MousePosition) && !Character.ShouldLockHud() && GUI.MouseOn == null)
{
highlightedIcon = statusIcon;
highlightedAfflictionIcon = statusIcon;
highlightedIconPos = afflictionIconRect.Location.ToVector2();
}
@@ -1146,7 +1152,7 @@ namespace Barotrauma
highlightedIcon == statusIcon ? slot.HoverColor : slot.Color);*/
float alphaMultiplier = highlightedIcon == statusIcon ? 1f : 0.8f;
float alphaMultiplier = highlightedAfflictionIcon == statusIcon ? 1f : 0.8f;
afflictionPrefab.Icon?.Draw(spriteBatch,
pos.ToVector2(),
@@ -1161,9 +1167,9 @@ namespace Barotrauma
pos.Y += iconSize + (int)(5 * GUI.Scale);
}
if (highlightedIcon != null)
if (highlightedAfflictionIcon != null)
{
string nameTooltip = highlightedIcon.Second;
string nameTooltip = highlightedAfflictionIcon.Second;
Vector2 offset = GUI.Font.MeasureString(nameTooltip);
GUI.DrawString(spriteBatch,

View File

@@ -997,8 +997,7 @@ namespace Barotrauma
return editor.GetMouseCursorState();
// Portrait area during gameplay
case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true):
if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) ||
Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea).Contains(PlayerInput.MousePosition))
if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || CharacterHealth.IsMouseOnHealthBar())
{
return CursorState.Hand;
}

View File

@@ -17,47 +17,63 @@ namespace Barotrauma
private readonly Dictionary<StoreTab, GUIListBox> tabLists = new Dictionary<StoreTab, GUIListBox>();
private readonly Dictionary<StoreTab, SortingMethod> tabSortingMethods = new Dictionary<StoreTab, SortingMethod>();
private readonly List<PurchasedItem> itemsToSell = new List<PurchasedItem>();
private readonly List<PurchasedItem> itemsToSellFromSub = new List<PurchasedItem>();
private StoreTab activeTab = StoreTab.Buy;
private MapEntityCategory? selectedItemCategory;
private bool suppressBuySell;
private int buyTotal, sellTotal;
private int buyTotal, sellTotal, sellFromSubTotal;
private GUITextBlock merchantBalanceBlock;
private GUITextBlock currentSellValueBlock, newSellValueBlock;
private GUIImage sellValueChangeArrow;
private GUIDropDown sortingDropDown;
private GUITextBox searchBox;
private GUIListBox storeBuyList, storeSellList;
private GUIListBox storeBuyList, storeSellList, storeSellFromSubList;
/// <summary>
/// Can be null when there are no deals at the current location
/// </summary>
private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup;
private GUILayoutGroup storeDailySpecialsGroup, storeRequestedGoodGroup, storeRequestedSubGoodGroup;
private Color storeSpecialColor;
private GUIListBox shoppingCrateBuyList, shoppingCrateSellList;
private GUIListBox shoppingCrateBuyList, shoppingCrateSellList, shoppingCrateSellFromSubList;
private GUITextBlock shoppingCrateTotal;
private GUIButton clearAllButton, confirmButton;
private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh;
private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh, needsSellingFromSubRefresh, needsItemsToSellFromSubRefresh;
private Point resolutionWhenCreated;
private bool hadPermissions;
private Dictionary<ItemPrefab, int> OwnedItems { get; } = new Dictionary<ItemPrefab, int>();
private Dictionary<ItemPrefab, int> OwnedItems { get; } = new Dictionary<ItemPrefab, int>();
private CargoManager CargoManager => campaignUI.Campaign.CargoManager;
private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation;
private int PlayerMoney => campaignUI.Campaign.Money;
private bool HasPermissions => campaignUI.Campaign.AllowedToManageCampaign();
private bool IsBuying => activeTab != StoreTab.Sell;
private bool IsSelling => activeTab == StoreTab.Sell;
private GUIListBox ActiveShoppingCrateList => IsBuying ? shoppingCrateBuyList : shoppingCrateSellList;
private bool IsBuying => activeTab switch
{
StoreTab.Buy => true,
StoreTab.Sell => false,
StoreTab.SellFromSub => false,
_ => throw new NotImplementedException()
};
private bool IsSelling => !IsBuying;
private GUIListBox ActiveShoppingCrateList => activeTab switch
{
StoreTab.Buy => shoppingCrateBuyList,
StoreTab.Sell => shoppingCrateSellList,
StoreTab.SellFromSub => shoppingCrateSellFromSubList,
_ => throw new NotImplementedException()
};
private enum StoreTab
private bool IsTabUnavailable(StoreTab tab) => !tabLists.ContainsKey(tab);
public enum StoreTab
{
Buy,
Sell
Sell,
SellFromSub
}
private enum SortingMethod
@@ -73,11 +89,8 @@ namespace Barotrauma
{
this.campaignUI = campaignUI;
this.parentComponent = parentComponent;
hadPermissions = HasPermissions;
CreateUI();
campaignUI.Campaign.Map.OnLocationChanged += UpdateLocation;
if (CurrentLocation?.Reputation != null)
{
@@ -89,8 +102,10 @@ namespace Barotrauma
campaignUI.Campaign.CargoManager.OnSoldItemsChanged += () =>
{
needsItemsToSellRefresh = true;
needsItemsToSellFromSubRefresh = true;
needsRefresh = true;
};
campaignUI.Campaign.CargoManager.OnItemsInSellFromSubCrateChanged += () => { needsSellingFromSubRefresh = true; };
}
public void Refresh(bool updateOwned = true)
@@ -99,6 +114,7 @@ namespace Barotrauma
if (updateOwned) { UpdateOwnedItems(); }
RefreshBuying(updateOwned: false);
RefreshSelling(updateOwned: false);
RefreshSellingFromSub(updateOwned: false);
needsRefresh = false;
}
@@ -124,6 +140,20 @@ namespace Barotrauma
needsSellingRefresh = false;
}
private void RefreshSellingFromSub(bool updateOwned = true, bool updateItemsToSellFromSub = true)
{
if (IsTabUnavailable(StoreTab.SellFromSub)) { return; }
if (updateOwned) { UpdateOwnedItems(); }
if (updateItemsToSellFromSub) RefreshItemsToSellFromSub();
RefreshShoppingCrateSellFromSubList();
RefreshStoreSellFromSubList();
// TODO: Separate permissions from regular campaign permissions
var hasPermissions = HasPermissions;
storeSellFromSubList.Enabled = hasPermissions;
shoppingCrateSellFromSubList.Enabled = hasPermissions;
needsSellingFromSubRefresh = false;
}
private void CreateUI()
{
if (parentComponent.FindChild(c => c.UserData as string == "glow") is GUIComponent glowChild)
@@ -236,9 +266,13 @@ namespace Barotrauma
{
if (CurrentLocation != null)
{
int balanceAfterTransaction = IsBuying ?
CurrentLocation.StoreCurrentBalance + buyTotal :
CurrentLocation.StoreCurrentBalance - sellTotal;
int balanceAfterTransaction = activeTab switch
{
StoreTab.Buy => CurrentLocation.StoreCurrentBalance + buyTotal,
StoreTab.Sell => CurrentLocation.StoreCurrentBalance - sellTotal,
StoreTab.SellFromSub => CurrentLocation.StoreCurrentBalance - sellFromSubTotal,
_ => throw new NotImplementedException(),
};
if (balanceAfterTransaction != CurrentLocation.StoreCurrentBalance)
{
var newStatus = Location.GetStoreBalanceStatus(balanceAfterTransaction);
@@ -300,8 +334,14 @@ namespace Barotrauma
tabSortingMethods.Clear();
foreach (StoreTab tab in tabs)
{
if (tab == StoreTab.SellFromSub && GameMain.IsMultiplayer) { continue; }
string text = tab switch
{
StoreTab.SellFromSub => TextManager.Get("submarine"),
_ => TextManager.Get("campaignstoretab." + tab)
};
var tabButton = new GUIButton(new RectTransform(new Vector2(1.0f / (tabs.Length + 1), 1.0f), modeButtonContainer.RectTransform),
text: TextManager.Get("campaignstoretab." + tab), style: "GUITabButton")
text: text, style: "GUITabButton")
{
UserData = tab,
OnClicked = (button, userData) =>
@@ -416,6 +456,17 @@ namespace Barotrauma
storeRequestedGoodGroup = CreateDealsGroup(storeSellList);
tabLists.Add(StoreTab.Sell, storeSellList);
if (GameMain.IsSingleplayer)
{
storeSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, storeItemListContainer.RectTransform))
{
AutoHideScrollBar = false,
Visible = false
};
storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList);
tabLists.Add(StoreTab.SellFromSub, storeSellFromSubList);
}
// Shopping Crate ------------------------------------------------------------------------------------------------------------------------------------------
var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.TopRight)
@@ -475,6 +526,10 @@ namespace Barotrauma
var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), shoppingCrateInventoryContainer.RectTransform), style: null);
shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
if (GameMain.IsSingleplayer)
{
shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
}
var totalContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true)
{
@@ -504,7 +559,13 @@ namespace Barotrauma
OnClicked = (button, userData) =>
{
if (!HasPermissions) { return false; }
var itemsToRemove = new List<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));
return true;
}
@@ -560,6 +621,7 @@ namespace Barotrauma
private void ChangeStoreTab(StoreTab tab)
{
if (IsTabUnavailable(tab)) { return; }
activeTab = tab;
foreach (GUIButton tabButton in storeTabButtons)
{
@@ -571,24 +633,56 @@ namespace Barotrauma
SetConfirmButtonBehavior();
SetConfirmButtonStatus();
FilterStoreItems();
if (tab == StoreTab.Buy)
switch (tab)
{
storeSellList.Visible = false;
storeBuyList.Visible = true;
shoppingCrateSellList.Visible = false;
shoppingCrateBuyList.Visible = true;
}
else if (tab == StoreTab.Sell)
{
storeBuyList.Visible = false;
storeSellList.Visible = true;
shoppingCrateBuyList.Visible = false;
shoppingCrateSellList.Visible = true;
case StoreTab.Buy:
storeSellList.Visible = false;
if (storeSellFromSubList != null)
{
storeSellFromSubList.Visible = false;
}
storeBuyList.Visible = true;
shoppingCrateSellList.Visible = false;
if (shoppingCrateSellFromSubList != null)
{
shoppingCrateSellFromSubList.Visible = false;
}
shoppingCrateBuyList.Visible = true;
break;
case StoreTab.Sell:
storeBuyList.Visible = false;
if (storeSellFromSubList != null)
{
storeSellFromSubList.Visible = false;
}
storeSellList.Visible = true;
shoppingCrateBuyList.Visible = false;
if (shoppingCrateSellFromSubList != null)
{
shoppingCrateSellFromSubList.Visible = false;
}
shoppingCrateSellList.Visible = true;
break;
case StoreTab.SellFromSub:
storeBuyList.Visible = false;
storeSellList.Visible = false;
if (storeSellFromSubList != null)
{
storeSellFromSubList.Visible = true;
}
shoppingCrateBuyList.Visible = false;
shoppingCrateSellList.Visible = false;
if (shoppingCrateSellFromSubList != null)
{
shoppingCrateSellFromSubList.Visible = true;
}
break;
}
}
private void FilterStoreItems(MapEntityCategory? category, string filter)
{
if (IsTabUnavailable(activeTab)) { return; }
selectedItemCategory = category;
var list = tabLists[activeTab];
filter = filter?.ToLower();
@@ -668,7 +762,7 @@ namespace Barotrauma
if (itemFrame == null)
{
var parentComponent = isDailySpecial ? storeDailySpecialsGroup : storeBuyList as GUIComponent;
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, quantity), priceInfo, parentComponent, forceDisable: !hasPermissions);
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, quantity), parentComponent, StoreTab.Buy, forceDisable: !hasPermissions);
}
else
{
@@ -688,7 +782,7 @@ namespace Barotrauma
removedItemFrames.AddRange(storeDailySpecialsGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
}
removedItemFrames.ForEach(f => f.RectTransform.Parent = null);
if (IsBuying) { FilterStoreItems(); }
if (activeTab == StoreTab.Buy) { FilterStoreItems(); }
SortItems(StoreTab.Buy);
storeBuyList.BarScroll = prevBuyListScroll;
@@ -743,7 +837,7 @@ namespace Barotrauma
if (itemFrame == null)
{
var parentComponent = isRequestedGood ? storeRequestedGoodGroup : storeSellList as GUIComponent;
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, itemQuantity), priceInfo, parentComponent, forceDisable: !hasPermissions);
itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, itemQuantity), parentComponent, StoreTab.Sell, forceDisable: !hasPermissions);
}
else
{
@@ -766,13 +860,91 @@ namespace Barotrauma
removedItemFrames.AddRange(storeRequestedGoodGroup.Children.Where(c => c.UserData is PurchasedItem).Except(existingItemFrames).ToList());
}
removedItemFrames.ForEach(f => f.RectTransform.Parent = null);
if (IsSelling) { FilterStoreItems(); }
if (activeTab == StoreTab.Sell) { FilterStoreItems(); }
SortItems(StoreTab.Sell);
storeSellList.BarScroll = prevSellListScroll;
shoppingCrateSellList.BarScroll = prevShoppingCrateScroll;
}
private void RefreshStoreSellFromSubList()
{
float prevSellListScroll = storeSellFromSubList.BarScroll;
float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll;
bool hasPermissions = HasPermissions;
HashSet<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)
{
if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; }
@@ -834,11 +1006,41 @@ namespace Barotrauma
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;
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
bool refreshingBuyList = listBox == shoppingCrateBuyList;
int totalPrice = 0;
foreach (PurchasedItem item in items)
{
@@ -849,7 +1051,7 @@ namespace Barotrauma
GUINumberInput numInput = null;
if (itemFrame == null)
{
itemFrame = CreateItemFrame(item, priceInfo, listBox, forceDisable: !hasPermissions);
itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions);
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
}
else
@@ -860,7 +1062,7 @@ namespace Barotrauma
{
numInput.UserData = item;
numInput.Enabled = hasPermissions;
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, refreshingBuyList ? StoreTab.Buy : StoreTab.Sell);
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab);
}
SetOwnedLabelText(itemFrame);
SetItemFrameStatus(itemFrame, hasPermissions);
@@ -875,7 +1077,7 @@ namespace Barotrauma
}
suppressBuySell = false;
var price = refreshingBuyList ?
var price = tab == StoreTab.Buy ?
CurrentLocation.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo) :
CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo);
totalPrice += item.Quantity * price;
@@ -885,24 +1087,32 @@ namespace Barotrauma
removedItemFrames.ForEach(f => listBox.Content.RemoveChild(f));
SortItems(listBox, SortingMethod.CategoryAsc);
listBox.UpdateScrollBarSize();
if (refreshingBuyList)
listBox.UpdateScrollBarSize();
switch (tab)
{
buyTotal = totalPrice;
if (IsBuying) { SetShoppingCrateTotalText(); }
case StoreTab.Buy:
buyTotal = totalPrice;
break;
case StoreTab.Sell:
sellTotal = totalPrice;
break;
case StoreTab.SellFromSub:
sellFromSubTotal = totalPrice;
break;
}
else
if (activeTab == tab)
{
sellTotal = totalPrice;
if(IsSelling) { SetShoppingCrateTotalText(); }
SetShoppingCrateTotalText();
}
SetClearAllButtonStatus();
SetConfirmButtonStatus();
}
private void RefreshShoppingCrateBuyList() => RefreshShoppingCrateList(CargoManager.ItemsInBuyCrate, shoppingCrateBuyList);
private void RefreshShoppingCrateBuyList() => RefreshShoppingCrateList(CargoManager.ItemsInBuyCrate, shoppingCrateBuyList, StoreTab.Buy);
private void RefreshShoppingCrateSellList() => RefreshShoppingCrateList(CargoManager.ItemsInSellCrate, shoppingCrateSellList);
private void RefreshShoppingCrateSellList() => RefreshShoppingCrateList(CargoManager.ItemsInSellCrate, shoppingCrateSellList, StoreTab.Sell);
private void RefreshShoppingCrateSellFromSubList() => RefreshShoppingCrateList(CargoManager.ItemsInSellFromSubCrate, shoppingCrateSellFromSubList, StoreTab.SellFromSub);
private void SortItems(GUIListBox list, SortingMethod sortingMethod)
{
@@ -934,7 +1144,7 @@ namespace Barotrauma
else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc)
{
SortItems(list, SortingMethod.AlphabeticalAsc);
if (list == storeSellList || list == shoppingCrateSellList)
if (list != storeBuyList && list != shoppingCrateBuyList)
{
list.Content.RectTransform.SortChildren(CompareBySellPrice);
if (GetSpecialsGroup() is GUILayoutGroup specialsGroup)
@@ -1016,6 +1226,10 @@ namespace Barotrauma
{
return storeRequestedGoodGroup;
}
else if (list == storeSellFromSubList)
{
return storeRequestedSubGoodGroup;
}
else
{
return null;
@@ -1047,15 +1261,20 @@ namespace Barotrauma
private void SortItems(StoreTab tab, SortingMethod sortingMethod)
{
if (IsTabUnavailable(tab)) { return; }
tabSortingMethods[tab] = sortingMethod;
SortItems(tabLists[tab], sortingMethod);
}
private void SortItems(StoreTab tab) => SortItems(tab, tabSortingMethods[tab]);
private void SortItems(StoreTab tab)
{
if (IsTabUnavailable(tab)) { return; }
SortItems(tab, tabSortingMethods[tab]);
}
private void SortActiveTabItems(SortingMethod sortingMethod) => SortItems(activeTab, sortingMethod);
private GUIComponent CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIComponent parentComponent, bool forceDisable = false)
private GUIComponent CreateItemFrame(PurchasedItem pi, GUIComponent parentComponent, StoreTab containingTab, bool forceDisable = false)
{
var tooltip = pi.ItemPrefab.Name;
if (!string.IsNullOrWhiteSpace(pi.ItemPrefab.Description))
@@ -1116,8 +1335,8 @@ namespace Barotrauma
CanBeFocused = false,
Stretch = true
};
var isSellingRelatedList = parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup || parentComponent == shoppingCrateSellList;
var locationHasDealOnItem = isSellingRelatedList ?
bool isSellingRelatedList = containingTab != StoreTab.Buy;
bool locationHasDealOnItem = isSellingRelatedList ?
CurrentLocation.RequestedGoods.Contains(pi.ItemPrefab) : CurrentLocation.DailySpecials.Contains(pi.ItemPrefab);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), nameAndQuantityGroup.RectTransform),
pi.ItemPrefab.Name, font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft)
@@ -1142,14 +1361,15 @@ namespace Barotrauma
};
dealIcon.SetAsFirstChild();
}
var isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup;
bool isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
parentComponent == storeSellList || parentComponent == storeRequestedGoodGroup ||
parentComponent == storeSellFromSubList || parentComponent == storeRequestedSubGoodGroup;
GUILayoutGroup shoppingCrateAmountGroup = null;
GUINumberInput amountInput = null;
if (isParentOnLeftSideOfInterface)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), nameAndQuantityGroup.RectTransform),
CreateQuantityLabelText(isSellingRelatedList ? StoreTab.Sell : StoreTab.Buy, pi.Quantity), font: GUI.Font, textAlignment: Alignment.BottomLeft)
CreateQuantityLabelText(containingTab, pi.Quantity), font: GUI.Font, textAlignment: Alignment.BottomLeft)
{
CanBeFocused = false,
Shadow = locationHasDealOnItem,
@@ -1158,7 +1378,7 @@ namespace Barotrauma
UserData = "quantitylabel"
};
}
else if (!isParentOnLeftSideOfInterface)
else
{
var relativePadding = nameBlock.Padding.X / nameBlock.Rect.Width;
shoppingCrateAmountGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativePadding, 0.6f), nameAndQuantityGroup.RectTransform) { RelativeOffset = new Vector2(relativePadding, 0) },
@@ -1169,7 +1389,7 @@ namespace Barotrauma
amountInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), shoppingCrateAmountGroup.RectTransform), GUINumberInput.NumberType.Int)
{
MinValueInt = 0,
MaxValueInt = GetMaxAvailable(pi.ItemPrefab, isSellingRelatedList ? StoreTab.Sell : StoreTab.Buy),
MaxValueInt = GetMaxAvailable(pi.ItemPrefab, containingTab),
UserData = pi,
IntValue = pi.Quantity
};
@@ -1396,7 +1616,7 @@ namespace Barotrauma
}
}
private string CreateQuantityLabelText(StoreTab mode, int quantity) => mode == StoreTab.Sell ?
private string CreateQuantityLabelText(StoreTab mode, int quantity) => mode != StoreTab.Buy ?
TextManager.GetWithVariable("campaignstore.quantity", "[amount]", quantity.ToString()) :
TextManager.GetWithVariable("campaignstore.instock", "[amount]", quantity.ToString());
@@ -1419,10 +1639,16 @@ namespace Barotrauma
private int GetMaxAvailable(ItemPrefab itemPrefab, StoreTab mode)
{
var list = mode == StoreTab.Sell ? itemsToSell : CurrentLocation.StoreStock;
var list = mode switch
{
StoreTab.Buy => CurrentLocation.StoreStock,
StoreTab.Sell => itemsToSell,
StoreTab.SellFromSub => itemsToSellFromSub,
_ => throw new NotImplementedException()
};
if (list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item)
{
if (mode != StoreTab.Sell)
if (mode == StoreTab.Buy)
{
var purchasedItem = CargoManager.PurchasedItems.Find(i => i.ItemPrefab == item.ItemPrefab);
if (purchasedItem != null) { return Math.Max(item.Quantity - purchasedItem.Quantity, 0); }
@@ -1471,11 +1697,37 @@ namespace Barotrauma
return false;
}
private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => IsBuying ?
ModifyBuyQuantity(item, quantity) : ModifySellQuantity(item, quantity);
private bool ModifySellFromSubQuantity(PurchasedItem item, int quantity)
{
if (item == null || item.ItemPrefab == null) { return false; }
if (!HasPermissions) { return false; }
if (quantity > 0)
{
// Make sure there's enough available to sell
var itemToSell = CargoManager.ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == item.ItemPrefab);
var totalQuantityToSell = itemToSell != null ? itemToSell.Quantity + quantity : quantity;
if (totalQuantityToSell > GetMaxAvailable(item.ItemPrefab, StoreTab.SellFromSub)) { return false; }
}
CargoManager.ModifyItemQuantityInSellFromSubCrate(item.ItemPrefab, quantity);
// TODO: GameMain.Client?.SendCampaignState();
return false;
}
private bool ClearFromShoppingCrate(PurchasedItem item) => IsBuying ?
ModifyBuyQuantity(item, -item.Quantity) : ModifySellQuantity(item, -item.Quantity);
private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => activeTab switch
{
StoreTab.Buy => ModifyBuyQuantity(item, quantity),
StoreTab.Sell => ModifySellQuantity(item, quantity),
StoreTab.SellFromSub => ModifySellFromSubQuantity(item, quantity),
_ => throw new NotImplementedException(),
};
private bool ClearFromShoppingCrate(PurchasedItem item) => activeTab switch
{
StoreTab.Buy => ModifyBuyQuantity(item, -item.Quantity),
StoreTab.Sell => ModifySellQuantity(item, -item.Quantity),
StoreTab.SellFromSub => ModifySellFromSubQuantity(item, -item.Quantity),
_ => throw new NotImplementedException(),
};
private bool BuyItems()
{
@@ -1512,18 +1764,17 @@ namespace Barotrauma
private bool SellItems()
{
if (!HasPermissions) { return false; }
var itemsToSell = new List<PurchasedItem>(CargoManager.ItemsInSellCrate);
var itemsToSell = activeTab switch
{
StoreTab.Sell => new List<PurchasedItem>(CargoManager.ItemsInSellCrate),
StoreTab.SellFromSub => new List<PurchasedItem>(CargoManager.ItemsInSellFromSubCrate),
_ => throw new NotImplementedException()
};
var itemsToRemove = new List<PurchasedItem>();
var totalValue = 0;
foreach (PurchasedItem item in itemsToSell)
{
if (item?.ItemPrefab == null)
{
itemsToRemove.Add(item);
continue;
}
if (item.ItemPrefab.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo)
if (item?.ItemPrefab?.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo)
{
totalValue += item.Quantity * CurrentLocation.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo);
}
@@ -1533,12 +1784,13 @@ namespace Barotrauma
}
}
itemsToRemove.ForEach(i => itemsToSell.Remove(i));
if (itemsToSell.None() || totalValue > CurrentLocation.StoreCurrentBalance) { return false; }
CargoManager.SellItems(itemsToSell);
GameMain.Client?.SendCampaignState();
CargoManager.SellItems(itemsToSell, activeTab);
if (activeTab == StoreTab.Sell)
{
// TODO: Implement selling sub items in multiplayer
GameMain.Client?.SendCampaignState();
}
return false;
}
@@ -1551,8 +1803,14 @@ namespace Barotrauma
}
else
{
shoppingCrateTotal.Text = GetCurrencyFormatted(sellTotal);
shoppingCrateTotal.TextColor = CurrentLocation != null && sellTotal > CurrentLocation.StoreCurrentBalance ? Color.Red : Color.White;
int total = activeTab switch
{
StoreTab.Sell => sellTotal,
StoreTab.SellFromSub => sellFromSubTotal,
_ => throw new NotImplementedException(),
};
shoppingCrateTotal.Text = GetCurrencyFormatted(total);
shoppingCrateTotal.TextColor = CurrentLocation != null && total > CurrentLocation.StoreCurrentBalance ? Color.Red : Color.White;
}
}
@@ -1582,13 +1840,19 @@ namespace Barotrauma
private void SetConfirmButtonStatus() => confirmButton.Enabled =
HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any() &&
((IsBuying && buyTotal <= PlayerMoney) || (IsSelling && CurrentLocation != null && sellTotal <= CurrentLocation.StoreCurrentBalance));
activeTab switch
{
StoreTab.Buy => buyTotal <= PlayerMoney,
StoreTab.Sell => CurrentLocation != null && sellTotal <= CurrentLocation.StoreCurrentBalance,
StoreTab.SellFromSub => CurrentLocation != null && sellFromSubTotal <= CurrentLocation.StoreCurrentBalance,
_ => throw new NotImplementedException(),
};
private void SetClearAllButtonStatus() => clearAllButton.Enabled =
HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any();
private float ownedItemsUpdateTimer = 0.0f;
private readonly float ownedItemsUpdateInterval = 1.5f;
private float ownedItemsUpdateTimer = 0.0f, sellableItemsFromSubUpdateTimer = 0.0f;
private readonly float timerUpdateInterval = 1.5f;
public void Update(float deltaTime)
{
@@ -1600,7 +1864,7 @@ namespace Barotrauma
{
// Update the owned items at short intervals and check if the interface should be refreshed
ownedItemsUpdateTimer += deltaTime;
if (ownedItemsUpdateTimer >= ownedItemsUpdateInterval)
if (ownedItemsUpdateTimer >= timerUpdateInterval)
{
var prevOwnedItems = new Dictionary<ItemPrefab, int>(OwnedItems);
UpdateOwnedItems();
@@ -1614,12 +1878,21 @@ namespace Barotrauma
needsRefresh = true;
}
}
// Update the sellable sub items at short intervals and check if the interface should be refreshed
sellableItemsFromSubUpdateTimer += deltaTime;
if (sellableItemsFromSubUpdateTimer >= timerUpdateInterval)
{
needsItemsToSellFromSubRefresh = true;
needsRefresh = true;
}
}
if (needsItemsToSellRefresh) { RefreshItemsToSell(); }
if (needsItemsToSellFromSubRefresh) { RefreshItemsToSellFromSub(); }
if (needsRefresh || hadPermissions != HasPermissions) { Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f); }
if (needsBuyingRefresh) { RefreshBuying(); }
if (needsSellingRefresh) { RefreshSelling(); }
if (needsSellingFromSubRefresh) { RefreshSellingFromSub(updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f); }
}
}
}

View File

@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
@@ -42,10 +43,7 @@ namespace Barotrauma
public IEnumerable<Item> GetSellableItems(Character character)
{
if (character == null) { return new List<Item>(); }
// Only consider items which have been:
// a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or
// b) sold locally in multiplayer (SellStatus.Local), but the client has not received a campaing state update yet after selling them
var confirmedSoldEntities = SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed);
var confirmedSoldEntities = GetConfirmedSoldEntities();
// The bag slot is intentionally left out since we want to be able to sell items from there
var equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
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)
{
ItemsInBuyCrate.Clear();
@@ -119,10 +154,34 @@ namespace Barotrauma
OnItemsInSellCrateChanged?.Invoke();
}
public void SellItems(List<PurchasedItem> itemsToSell)
public void ModifyItemQuantityInSellFromSubCrate(ItemPrefab itemPrefab, int changeInQuantity)
{
var itemsInInventory = GetSellableItems(Character.Controlled);
var canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null;
var itemToSell = ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == itemPrefab);
if (itemToSell != null)
{
itemToSell.Quantity += changeInQuantity;
if (itemToSell.Quantity < 1)
{
ItemsInSellFromSubCrate.Remove(itemToSell);
}
}
else if (changeInQuantity > 0)
{
itemToSell = new PurchasedItem(itemPrefab, changeInQuantity);
ItemsInSellFromSubCrate.Add(itemToSell);
}
OnItemsInSellFromSubCrateChanged?.Invoke();
}
public void SellItems(List<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;
// Check all the prices before starting the transaction
@@ -137,7 +196,7 @@ namespace Barotrauma
if (Location.StoreCurrentBalance < itemValue) { continue; }
// TODO: Write logic for prioritizing certain items over others (e.g. lone Battery Cell should be preferred over one inside a Stun Baton)
var matchingItems = itemsInInventory.Where(i => i.Prefab == item.ItemPrefab);
var matchingItems = sellableItems.Where(i => i.Prefab == item.ItemPrefab);
if (matchingItems.Count() <= item.Quantity)
{
foreach (Item i in matchingItems)
@@ -163,12 +222,21 @@ namespace Barotrauma
campaign.Money += itemValue;
// Remove from the sell crate
if (ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } itemToSell)
// TODO: Simplify duplicate logic?
if (sellingMode == Store.StoreTab.Sell && ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } inventoryItem)
{
itemToSell.Quantity -= item.Quantity;
if (itemToSell.Quantity < 1)
inventoryItem.Quantity -= item.Quantity;
if (inventoryItem.Quantity < 1)
{
ItemsInSellCrate.Remove(itemToSell);
ItemsInSellCrate.Remove(inventoryItem);
}
}
else if(sellingMode == Store.StoreTab.SellFromSub && ItemsInSellFromSubCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } subItem)
{
subItem.Quantity -= item.Quantity;
if (subItem.Quantity < 1)
{
ItemsInSellFromSubCrate.Remove(subItem);
}
}
}

View File

@@ -123,17 +123,17 @@ namespace Barotrauma.Tutorials
SetDoorAccess(tutorial_lockedDoor_2, null, false);
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("mechanic"));
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic.TeamID = CharacterTeamType.Team1;
captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer"));
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security.TeamID = CharacterTeamType.Team1;
captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer.TeamID = CharacterTeamType.Team1;
captain_engineer.GiveJobItems();

View File

@@ -94,19 +94,19 @@ namespace Barotrauma.Tutorials
patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3");
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient1.TeamID = CharacterTeamType.Team1;
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer"));
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3");
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient2.TeamID = CharacterTeamType.Team1;
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3");
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient3.TeamID = CharacterTeamType.Team1;
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient3);

View File

@@ -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)

View File

@@ -1077,8 +1077,8 @@ namespace Barotrauma
new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"), font: GUI.SubHeadingFont);
var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice))
var voiceKeybindLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"), font: GUI.SubHeadingFont);
var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.3f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice))
{
SelectedColor = Color.Gold * 0.3f,
UserData = InputType.Voice
@@ -1089,14 +1089,16 @@ namespace Barotrauma
new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), localVoiceInputContainer.RectTransform), TextManager.Get("InputType.LocalVoice"), font: GUI.SubHeadingFont);
var localVoiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), localVoiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.LocalVoice))
var localVoiceKeybindLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), localVoiceInputContainer.RectTransform), TextManager.Get("InputType.LocalVoice"), font: GUI.SubHeadingFont);
var localVoiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.3f, 1.0f), localVoiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.LocalVoice))
{
SelectedColor = Color.Gold * 0.3f,
UserData = InputType.LocalVoice
};
localVoiceKeyBox.OnSelected += KeyBoxSelected;
voiceKeybindLabel.RectTransform.SizeChanged += () => { GUITextBlock.AutoScaleAndNormalize(voiceKeybindLabel, localVoiceKeybindLabel); };
var cutoffPreventionText = new GUITextBlock(new RectTransform(textBlockScale, voiceChatContent.RectTransform), TextManager.Get("CutoffPrevention"), font: GUI.SubHeadingFont)
{
ToolTip = TextManager.Get("CutoffPreventionTooltip")

View File

@@ -7,13 +7,15 @@ namespace Barotrauma.Items.Components
{
private void GetDamageModifierText(ref string description, float damageMultiplier, string afflictionIdentifier)
{
int roundedValue = (int)Math.Round((1 - damageMultiplier) * 100);
if (roundedValue == 0) { return; }
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green);
description += $"\n ‖color:{colorStr}‖-{Math.Round((1 - damageMultiplier) * 100)}%‖color:end‖ {TextManager.Get("AfflictionName." + afflictionIdentifier, true) ?? afflictionIdentifier}";
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("+0;-#")}%‖color:end‖ {TextManager.Get("AfflictionName." + afflictionIdentifier, true) ?? afflictionIdentifier}";
}
public override void AddTooltipInfo(ref string description)
{
if (damageModifiers.Any(d => d.DamageMultiplier != 1f) || SkillModifiers.Any())
if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f)) || SkillModifiers.Any())
{
description += "\n";
}
@@ -22,7 +24,7 @@ namespace Barotrauma.Items.Components
{
foreach (DamageModifier damageModifier in damageModifiers)
{
if (damageModifier.DamageMultiplier == 1f)
if (MathUtils.NearlyEqual(damageModifier.DamageMultiplier, 1f))
{
continue;
}
@@ -42,7 +44,9 @@ namespace Barotrauma.Items.Components
foreach (var skillModifier in SkillModifiers)
{
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green);
description += $"\n ‖color:{colorStr}‖+{skillModifier.Value}‖color:end‖ {TextManager.Get("SkillName." + skillModifier.Key, true) ?? skillModifier.Key}";
int roundedValue = (int)Math.Round(skillModifier.Value);
if (roundedValue == 0) { continue; }
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("+0;-#")}‖color:end‖ {TextManager.Get("SkillName." + skillModifier.Key, true) ?? skillModifier.Key}";
}
}
}

View File

@@ -480,7 +480,10 @@ namespace Barotrauma
if (IsSelected || IsHighlighted)
{
GUI.DrawRectangle(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)), new Vector2(rect.Width, rect.Height),
Vector2 drawPos = new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2));
Vector2 drawSize = new Vector2(MathF.Ceiling(rect.Width + Math.Abs(drawPos.X - (int)drawPos.X)), MathF.Ceiling(rect.Height + Math.Abs(drawPos.Y - (int)drawPos.Y)));
drawPos = new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y));
GUI.DrawRectangle(spriteBatch, drawPos, drawSize,
Color.White, false, 0, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
foreach (Rectangle t in Prefab.Triggers)

View File

@@ -527,7 +527,7 @@ namespace Barotrauma.Networking
string pwMsg = TextManager.Get("PasswordRequired");
var msgBox = new GUIMessageBox(pwMsg, "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") },
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, (int)(170 * Math.Max(1.0f, GUI.Scale))));
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, GUI.IntScale(170)));
var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), msgBox.Content.RectTransform), childAnchor: Anchor.TopCenter);
var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1f), passwordHolder.RectTransform) { MinSize = new Point(0, 20) })
{
@@ -537,7 +537,8 @@ namespace Barotrauma.Networking
if (wrongPassword)
{
new GUITextBlock(new RectTransform(new Vector2(1f, 0.33f), passwordHolder.RectTransform), TextManager.Language == "English" ? TextManager.Get("incorrectpassword") : "Incorrect password", GUI.Style.Red, GUI.Font, textAlignment: Alignment.Center);
var incorrectPasswordText = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), passwordHolder.RectTransform), TextManager.Get("incorrectpassword"), GUI.Style.Red, GUI.Font, textAlignment: Alignment.Center);
incorrectPasswordText.RectTransform.MinSize = new Point(0, (int)incorrectPasswordText.TextSize.Y);
passwordHolder.Recalculate();
}

View File

@@ -469,18 +469,17 @@ namespace Barotrauma
this.game = game;
menuTabs[(int)Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[(int)Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
menuTabs[(int)Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null)
{
CanBeFocused = false
};
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[(int)Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker")
{
CanBeFocused = false
};
var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
new GUIButton(new RectTransform(new Vector2(0.1f, 0.05f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.BottomLeft) { RelativeOffset = new Vector2(0.25f, 0.02f) },
TextManager.Get("Back"), style: "GUIButtonLarge")
{
OnClicked = SelectTab
};
}
#endregion

View File

@@ -1514,6 +1514,9 @@ namespace Barotrauma
case SubmarineType.Wreck:
contentType = ContentType.Wreck;
break;
case SubmarineType.EnemySubmarine:
contentType = ContentType.EnemySubmarine;
break;
}
if (contentType != ContentType.Submarine)
{
@@ -1829,7 +1832,12 @@ namespace Barotrauma
subTypeContainer.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
foreach (SubmarineType subType in Enum.GetValues(typeof(SubmarineType)))
{
subTypeDropdown.AddItem(TextManager.Get("submarinetype."+subType.ToString().ToLowerInvariant()), subType);
string textTag = "SubmarineType." + subType;
if (subType == SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag))
{
textTag = "MissionType.Pirate";
}
subTypeDropdown.AddItem(TextManager.Get(textTag), subType);
}
//---------------------------------------
@@ -2591,8 +2599,13 @@ namespace Barotrauma
{
if (prevSub == null || prevSub.Type != sub.Type)
{
string textTag = "SubmarineType." + sub.Type;
if (sub.Type == SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag))
{
textTag = "MissionType.Pirate";
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), subList.Content.RectTransform) { MinSize = new Point(0, 35) },
TextManager.Get("SubmarineType." + sub.Type), font: GUI.LargeFont, textAlignment: Alignment.Center, style: "ListBoxElement")
TextManager.Get(textTag), font: GUI.LargeFont, textAlignment: Alignment.Center, style: "ListBoxElement")
{
CanBeFocused = false
};

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1400.2.0</Version>
<Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1400.2.0</Version>
<Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1400.2.0</Version>
<Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1400.2.0</Version>
<Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1400.2.0</Version>
<Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1400.2.0</Version>
<Version>0.1400.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -66,7 +66,7 @@ namespace Barotrauma
private float Sight => AIParams.Sight;
private float Hearing => AIParams.Hearing;
private float FleeHealthThreshold => AIParams.FleeHealthThreshold;
private bool AggressiveBoarding => AIParams.AggressiveBoarding;
private bool IsAggressiveBoarder => AIParams.AggressiveBoarding;
private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
@@ -1271,7 +1271,7 @@ namespace Barotrauma
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition;
Vector2 toTarget = attackWorldPos - attackLimbPos;
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it if the attack takes some time to execute
if (wallTarget != null)
if (wallTarget != null && Character.Submarine == null)
{
if (wallTarget.Structure.Submarine != null)
{
@@ -1440,10 +1440,11 @@ namespace Barotrauma
else
{
pathSteering.SteeringSeek(steerPos, 2, startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), checkVisiblity: true);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackWalls || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
return;
}
}
@@ -1674,16 +1675,6 @@ namespace Barotrauma
}
if (canAttack)
{
if (SelectedAiTarget.Entity is Item targetItem)
{
var door = targetItem.GetComponent<Door>();
if (door != null && door.CanBeTraversed)
{
ResetAITarget();
State = PreviousState;
return;
}
}
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
{
IgnoreTarget(SelectedAiTarget);
@@ -2292,25 +2283,24 @@ namespace Barotrauma
var section = s.Sections[i];
if (section.gap == null) { continue; }
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
isInnerWall = isInnerWall || !leadsInside;
if (Character.AnimController.CanEnterSubmarine)
{
if (!isCharacterInside)
{
if (CanPassThroughHole(s, i))
{
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0;
valueModifier *= leadsInside ? (IsAggressiveBoarder ? 3 : 1) : 0;
}
else if (AggressiveBoarding && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
else if (IsAggressiveBoarder && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
{
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open;
// Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open * 0.25f;
}
}
else
{
// Inside
if (AggressiveBoarding)
if (IsAggressiveBoarder)
{
if (!isInnerWall)
{
@@ -2327,6 +2317,10 @@ namespace Barotrauma
valueModifier = 0;
break;
}
else
{
valueModifier = 0.1f;
}
}
else
{
@@ -2350,7 +2344,7 @@ namespace Barotrauma
valueModifier = 0;
break;
}
else if (AggressiveBoarding)
else if (IsAggressiveBoarder)
{
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
// (Bonethreshers)
@@ -2384,17 +2378,24 @@ namespace Barotrauma
// Ignore broken and open doors, if cannot enter submarine
continue;
}
if (AggressiveBoarding)
if (IsAggressiveBoarder)
{
// Increase the priority if the character is outside and the door is from outside to inside
if (character.CurrentHull == null)
{
valueModifier *= isOpen ? 5 : 1;
// Increase the priority if the character is outside and the door is from outside to inside
if (door.CanBeTraversed)
{
valueModifier = 3;
}
else if (door.LinkedGap != null)
{
valueModifier = 1 + door.LinkedGap.Open;
}
}
else
{
// Inside -> ignore open doors and outer doors
valueModifier *= isOpen || isOutdoor ? 0 : 1;
valueModifier = isOpen || isOutdoor ? 0 : 1;
}
}
}
@@ -2643,9 +2644,9 @@ namespace Barotrauma
private void UpdateWallTarget(int requiredHoleCount)
{
wallTarget = null;
if (AIParams.CanOpenDoors && HasValidPath(requireNonDirty: true)) { return; }
if (SelectedAiTarget == null) { return; }
if (SelectedAiTarget.Entity == null) { return; }
if (HasValidPath(requireNonDirty: true)) { return; }
wallHits.Clear();
Structure wall = null;
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
@@ -2729,9 +2730,12 @@ namespace Barotrauma
{
if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine)
{
bool isTargetingDoor = SelectedAiTarget.Entity is Item i && i.GetComponent<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.
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
if (!isTargetingDoor)
{
ResetAITarget();
}
}
else
{
@@ -2741,7 +2745,6 @@ namespace Barotrauma
else
{
// Blocked by a disabled wall.
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
}
}

View File

@@ -1926,26 +1926,33 @@ namespace Barotrauma
public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter)
{
operatingCharacter = null;
float highestPriority = -1.0f;
float highestPriorityModifier = -1.0f;
foreach (Character c in Character.CharacterList)
{
if (c.Removed) { continue; }
if (c.TeamID != team) { continue; }
if (c.IsIncapacitated) { continue; }
bool isOperated = c.SelectedConstruction == target.Item;
if (!isOperated)
{
if (c.AIController is HumanAIController humanAI)
{
isOperated = humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item);
}
}
operatingCharacter = c;
if (isOperated)
if (c.SelectedConstruction == target.Item)
{
operatingCharacter = c;
return true;
}
if (c.AIController is HumanAIController humanAI)
{
foreach (var objective in humanAI.ObjectiveManager.Objectives)
{
if (!(objective is AIObjectiveOperateItem operateObjective)) { continue; }
if (operateObjective.Component.Item != target.Item) { continue; }
if (operateObjective.Priority < highestPriority) { continue; }
if (operateObjective.PriorityModifier < highestPriorityModifier) { continue; }
operatingCharacter = c;
highestPriority = operateObjective.Priority;
highestPriorityModifier = operateObjective.PriorityModifier;
}
}
}
return false;
return operatingCharacter != null;
}
// There's some duplicate logic in the two methods below, but making them use the same code would require some changes in the target classes so that we could use exactly the same checks.

View File

@@ -637,7 +637,7 @@ namespace Barotrauma
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
}
}
return true;
return !f2.IsSensor;
}
Vector2 colliderBottom = GetColliderBottom();
@@ -1541,6 +1541,7 @@ namespace Barotrauma
case Physics.CollisionLevel:
if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; }
if (fixture.Body.UserData is Submarine && character.Submarine != null) { return -1; }
if (fixture.IsSensor) { return -1; }
if (fraction < standOnFloorFraction)
{
standOnFloorFraction = fraction;

View File

@@ -2377,6 +2377,10 @@ namespace Barotrauma
var item = FindItemAtPosition(mouseSimPos, aimAssist);
focusedItem = CanInteract ? item : null;
if (focusedItem != null && focusedItem.CampaignInteractionType != CampaignMode.InteractionType.None)
{
FocusedCharacter = null;
}
findFocusedTimer = 0.05f;
}
}

View File

@@ -23,7 +23,17 @@ namespace Barotrauma
protected PropertyConditional.OperatorType Operator { get; set; }
public CheckDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
public CheckDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
{
if (string.IsNullOrEmpty(Condition))
{
Condition = element.GetAttributeString("value", string.Empty);
if (string.IsNullOrEmpty(Condition))
{
DebugConsole.ThrowError($"Error in scripted event \"{parentEvent.Prefab.Identifier}\". CheckDataAction with no condition set ({element}).");
}
}
}
protected override bool? DetermineSuccess()
{

View File

@@ -213,7 +213,14 @@ namespace Barotrauma
if (dialogOpened)
{
#if CLIENT
Character.DisableControls = true;
if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "ConversationAction"))
{
Character.DisableControls = true;
}
else
{
Reset();
}
#endif
if (ShouldInterrupt())
{

View File

@@ -20,11 +20,7 @@ namespace Barotrauma
{
if (string.IsNullOrWhiteSpace(ItemIdentifier))
{
ItemIdentifier = element.GetAttributeString("itemidentifiers", "");
}
if (string.IsNullOrWhiteSpace(ItemIdentifier))
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - RemoveItemAction without an item identifier.");
ItemIdentifier = element.GetAttributeString("itemidentifiers", null) ?? element.GetAttributeString("identifier", "");
}
}
@@ -66,7 +62,7 @@ namespace Barotrauma
var item = inventory.FindItem(it =>
it != null &&
!removedItems.Contains(it) &&
it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase), recursive: true);
(string.IsNullOrEmpty(ItemIdentifier) || it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase)), recursive: true);
if (item == null) { break; }
Entity.Spawner.AddToRemoveQueue(item);
removedItems.Add(item);
@@ -74,7 +70,7 @@ namespace Barotrauma
}
else if (target is Item item)
{
if (item.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase))
if (string.IsNullOrEmpty(ItemIdentifier) || item.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase))
{
Entity.Spawner.AddToRemoveQueue(item);
removedItems.Add(item);

View File

@@ -128,8 +128,8 @@ namespace Barotrauma
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use)));
#else
npc.SetCustomInteract(
Trigger,
npc.SetCustomInteract(
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.Get("CampaignInteraction.Talk"));
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
#endif

View File

@@ -185,7 +185,7 @@ namespace Barotrauma
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
{
HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element);
HumanPrefab humanPrefab = GetHumanPrefabFromElement(element);
for (int i = 0; i < count; i++)
{
LoadHuman(humanPrefab, element, submarine);

View File

@@ -42,7 +42,7 @@ namespace Barotrauma
{
this.sub = sub;
itemConfig = prefab.ConfigElement.Element("Items");
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.9f), 1.0f);
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f);
DetermineCargo();
}

View File

@@ -84,8 +84,7 @@ namespace Barotrauma
{
characters.Clear();
characterItems.Clear();
// VIP transport mission characters stay in the same location; other characters roam at will
// could be replaced with a designated waypoint for VIPs, such as cargo or crew
WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
Rand.RandSync randSync = Rand.RandSync.Server;
@@ -95,12 +94,30 @@ namespace Barotrauma
randSync = Rand.RandSync.Unsynced;
}
//if any of the escortees have a job defined, try to use a spawnpoint designated for that job
foreach (XElement element in characterConfig.Elements())
{
var humanPrefab = GetHumanPrefabFromElement(element);
if (humanPrefab == null || string.IsNullOrEmpty(humanPrefab.Job) || humanPrefab.Job.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; }
var jobPrefab = humanPrefab.GetJobPrefab();
if (jobPrefab != null)
{
var jobSpecificSpawnPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, Submarine.MainSub);
if (jobSpecificSpawnPos != null)
{
explicitStayInHullPos = jobSpecificSpawnPos;
break;
}
}
}
foreach (XElement element in characterConfig.Elements())
{
int count = CalculateScalingEscortedCharacterCount(inMission: true);
for (int i = 0; i < count; i++)
{
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
if (spawnedCharacter.AIController is HumanAIController humanAI)
{
humanAI.InitMentalStateManager();

View File

@@ -293,6 +293,7 @@ namespace Barotrauma
/// </summary>
private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger)
{
if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; }
if (trigger.Delay > 0)
{
if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger))
@@ -311,6 +312,7 @@ namespace Barotrauma
/// </summary>
private void TriggerEvent(MissionPrefab.TriggerEvent trigger)
{
if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; }
var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier.Equals(trigger.EventIdentifier, StringComparison.OrdinalIgnoreCase));
if (eventPrefab == null)
{
@@ -398,7 +400,7 @@ namespace Barotrauma
public virtual void AdjustLevelData(LevelData levelData) { }
// putting these here since both escort and pirate missions need them. could be tucked away into another class that they can inherit from (or use composition)
protected HumanPrefab CreateHumanPrefabFromElement(XElement element)
protected HumanPrefab GetHumanPrefabFromElement(XElement element)
{
if (element.Attribute("name") != null)
{

View File

@@ -117,6 +117,9 @@ namespace Barotrauma
[Serialize(0.0f, true)]
public float Delay { get; private set; }
[Serialize(false, true)]
public bool CampaignOnly { get; private set; }
public TriggerEvent(XElement element)
{
SerializableProperty.DeserializeProperties(this, element);

View File

@@ -191,6 +191,8 @@ namespace Barotrauma
}
enemySub.EnableMaintainPosition();
enemySub.TeamID = CharacterTeamType.None;
//make the enemy sub withstand atleast the same depth as the player sub
enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth);
}
private void InitPirates()
@@ -230,7 +232,7 @@ namespace Barotrauma
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(variantElement), characters, characterItems, enemySub, CharacterTeamType.None, null);
Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(variantElement), characters, characterItems, enemySub, CharacterTeamType.None, null);
if (!commanderAssigned)
{
bool isCommander = variantElement.GetAttributeBool("iscommander", false);

View File

@@ -431,7 +431,15 @@ namespace Barotrauma
float scatterAmount = scatter;
if (spawnPosType.HasFlag(Level.PositionType.SidePath))
{
scatterAmount = Math.Min(scatter, Level.Loaded.Tunnels.Where(t => t.Type == Level.TunnelType.SidePath).Min(t => t.MinWidth) / 2);
var sidePaths = Level.Loaded.Tunnels.Where(t => t.Type == Level.TunnelType.SidePath);
if (sidePaths.Any())
{
scatterAmount = Math.Min(scatter, sidePaths.Min(t => t.MinWidth) / 2);
}
else
{
scatterAmount = scatter;
}
}
else if (!spawnPosType.HasFlag(Level.PositionType.MainPath))
{

View File

@@ -46,6 +46,7 @@ namespace Barotrauma
public List<PurchasedItem> ItemsInBuyCrate { 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<SoldItem> SoldItems { get; } = new List<SoldItem>();
@@ -55,6 +56,7 @@ namespace Barotrauma
public Action OnItemsInBuyCrateChanged;
public Action OnItemsInSellCrateChanged;
public Action OnItemsInSellFromSubCrateChanged;
public Action OnPurchasedItemsChanged;
public Action OnSoldItemsChanged;
@@ -75,6 +77,12 @@ namespace Barotrauma
OnItemsInSellCrateChanged?.Invoke();
}
public void ClearItemsInSellFromSubCrate()
{
ItemsInSellFromSubCrate.Clear();
OnItemsInSellFromSubCrateChanged?.Invoke();
}
public void SetPurchasedItems(List<PurchasedItem> items)
{
PurchasedItems.Clear();

View File

@@ -417,7 +417,7 @@ namespace Barotrauma
if (isControlledCharacterNull) { return null; }
#endif
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemTargetedBySomeone(order.TargetItemComponent, controlledCharacter != null ? controlledCharacter.TeamID : CharacterTeamType.Team1, out Character operatingCharacter) &&
(isControlledCharacterNull || operatingCharacter.CanHearCharacter(controlledCharacter)))
(isControlledCharacterNull || operatingCharacter.CanHearCharacter(controlledCharacter)))
{
return operatingCharacter;
}

View File

@@ -558,12 +558,14 @@ namespace Barotrauma
{
CargoManager.ClearItemsInBuyCrate();
CargoManager.ClearItemsInSellCrate();
CargoManager.ClearItemsInSellFromSubCrate();
}
else
{
if (GameMain.NetworkMember.IsServer)
{
CargoManager?.ClearItemsInBuyCrate();
// TODO: CargoManager?.ClearItemsInSellFromSubCrate();
}
else if (GameMain.NetworkMember.IsClient)
{

View File

@@ -8,7 +8,6 @@ namespace Barotrauma.Items.Components
{
partial class MotionSensor : ItemComponent
{
private const float UpdateInterval = 0.1f;
private float rangeX, rangeY;
private Vector2 detectOffset;
@@ -75,6 +74,13 @@ namespace Barotrauma.Items.Components
}
}
[Editable(MinValueFloat = 0.1f, MaxValueFloat = 100.0f, DecimalCount = 2), Serialize(0.1f, true, description: "How often the sensor checks if there's something moving near it. Higher values are better for performance.", alwaysUseInstanceValues: true)]
public float UpdateInterval
{
get;
set;
}
private int maxOutputLength;
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
public int MaxOutputLength
@@ -135,6 +141,9 @@ namespace Barotrauma.Items.Components
{
rangeX = rangeY = element.GetAttributeFloat("range", 0.0f);
}
//randomize update timer so all sensors aren't updated during the same frame
updateTimer = Rand.Range(0.0f, UpdateInterval);
}
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)

View File

@@ -17,7 +17,7 @@ namespace Barotrauma
public readonly string Identifier;
public readonly int OriginalContainerIndex;
public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerIndex, ushort moduleIndex)
public TakenItem(string identifier, UInt16 originalID, int originalContainerIndex, ushort moduleIndex)
{
OriginalID = originalID;
OriginalContainerIndex = originalContainerIndex;
@@ -345,7 +345,7 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\"");
continue;
}
if (!ushort.TryParse(takenItemSplit[2], out ushort containerIndex))
if (!int.TryParse(takenItemSplit[2], out int containerIndex))
{
DebugConsole.ThrowError($"Error in saved location: could not parse taken container index \"{takenItemSplit[2]}\"");
continue;
@@ -577,6 +577,10 @@ namespace Barotrauma
{
weight /= missionCount * 2;
}
if (destination.IsRadiated())
{
weight *= 0.001f;
}
}
return weight;
}

View File

@@ -53,7 +53,16 @@ namespace Barotrauma
//dimensions of the wall sections' physics bodies (only used for debug rendering)
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
public WallSection[] Sections
@@ -798,7 +807,7 @@ namespace Barotrauma
public void AddDamage(int sectionIndex, float damage, Character attacker = null)
{
if (!Prefab.Body || Prefab.Platform || Indestructible ) { return; }
if (!Prefab.Body || Prefab.Platform || Indestructible) { return; }
if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) { return; }

View File

@@ -23,7 +23,7 @@ namespace Barotrauma
HideInMenus = 2
}
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation }
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine }
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
partial class SubmarineInfo : IDisposable

View File

@@ -766,14 +766,14 @@ namespace Barotrauma
if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); }
}
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
{
return WayPointList.GetRandom(wp =>
wp.Submarine == sub &&
wp.ParentRuin == ruin &&
wp.spawnType == spawnType &&
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob.Prefab))
, useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
}
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)

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)
---------------------------------------------------------------------------------------------------------