Build 0.20.0.0

This commit is contained in:
Markus Isberg
2022-10-27 17:54:57 +03:00
parent 05c7b1f869
commit edaf4b09fe
197 changed files with 4344 additions and 1773 deletions
@@ -769,7 +769,7 @@ namespace Barotrauma
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
radio.Channel = channel;
GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()]));
GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()], radio));
if (setText)
{
@@ -25,7 +25,7 @@ namespace Barotrauma
private PlayerBalanceElement? playerBalanceElement;
private List<CharacterInfo> PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires;
private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(ClientPermissions.ManageHires);
private bool HasPermission => CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageHires);
private Point resolutionWhenCreated;
@@ -357,6 +357,17 @@ namespace Barotrauma
string txt = directory;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
if (!txt.EndsWith("/")) { txt += "/"; }
//get directory info
DirectoryInfo dirInfo = new DirectoryInfo(directory);
try
{
//this will throw an exception if the directory can't be opened
Directory.GetDirectories(directory);
}
catch (UnauthorizedAccessException)
{
continue;
}
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{
UserData = ItemIsDirectory.Yes
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.CharacterEditor;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
@@ -50,6 +49,14 @@ namespace Barotrauma
static class GUI
{
// Controls where a line is drawn for given coords.
public enum OutlinePosition
{
Default = 0, // Thickness is inside of top left and outside of bottom right coord
Inside = 1, // Thickness is subtracted from the inside
Centered = 2, // Thickness is centered on given coords
Outside = 3, // Tickness is added to the outside
}
public static GUICanvas Canvas => GUICanvas.Instance;
public static CursorState MouseCursor = CursorState.Default;
@@ -1605,6 +1612,54 @@ namespace Barotrauma
}
}
public static void DrawRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 origin, float rotation, Color clr, float depth = 0.0f, float thickness = 1, OutlinePosition outlinePos = OutlinePosition.Centered)
{
Vector2 topLeft = new Vector2(-origin.X, -origin.Y);
Vector2 topRight = new Vector2(-origin.X + size.X, -origin.Y);
Vector2 bottomLeft = new Vector2(-origin.X, -origin.Y + size.Y);
Vector2 actualSize = size;
switch(outlinePos)
{
case OutlinePosition.Default:
actualSize += new Vector2(thickness);
break;
case OutlinePosition.Centered:
topLeft -= new Vector2(thickness * 0.5f);
topRight -= new Vector2(thickness * 0.5f);
bottomLeft -= new Vector2(thickness * 0.5f);
actualSize += new Vector2(thickness);
break;
case OutlinePosition.Inside:
topRight -= new Vector2(thickness, 0.0f);
bottomLeft -= new Vector2(0.0f, thickness);
break;
case OutlinePosition.Outside:
topLeft -= new Vector2(thickness);
topRight -= new Vector2(0.0f, thickness);
bottomLeft -= new Vector2(thickness, 0.0f);
actualSize += new Vector2(thickness * 2.0f);
break;
}
Matrix rotate = Matrix.CreateRotationZ(rotation);
topLeft = Vector2.Transform(topLeft, rotate) + position;
topRight = Vector2.Transform(topRight, rotate) + position;
bottomLeft = Vector2.Transform(bottomLeft, rotate) + position;
Rectangle srcRect = new Rectangle(0, 0, 1, 1);
sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
sb.Draw(solidWhiteTexture, topRight, srcRect, clr, rotation, Vector2.Zero, new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
sb.Draw(solidWhiteTexture, bottomLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
}
public static void DrawFilledRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 pivot, float rotation, Color clr, float depth = 0.0f)
{
Rectangle srcRect = new Rectangle(0, 0, 1, 1);
sb.Draw(solidWhiteTexture, position, srcRect, clr, rotation, (pivot/size), size, SpriteEffects.None, depth);
}
public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr, float depth = 0.0f)
{
DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth);
@@ -584,6 +584,13 @@ namespace Barotrauma
{
string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
if (TextManager.DebugDraw)
{
if (!text.NestedStr.Loaded || text.NestedStr.Language == LanguageIdentifier.None)
{
colorToShow = Color.Magenta;
}
}
if (Shadow)
{
@@ -139,10 +139,10 @@ namespace Barotrauma
return tab switch
{
StoreTab.Buy => true,
StoreTab.Sell => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems),
StoreTab.SellSub => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems),
StoreTab.Sell => CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems),
StoreTab.SellSub => CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems),
_ => false,
};
};
}
private void UpdatePermissions()
@@ -892,7 +892,7 @@ namespace Barotrauma
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity)
{
if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo))
if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy())
{
bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab);
var itemFrame = isDailySpecial ?
@@ -1995,11 +1995,23 @@ namespace Barotrauma
int totalPrice = 0;
foreach (var item in itemsToPurchase)
{
if (item?.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo))
if (item is null) { continue; }
if (item.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo))
{
itemsToRemove.Add(item);
continue;
}
if (item.ItemPrefab.DefaultPrice.RequiresUnlock)
{
if (!CargoManager.HasUnlockedStoreItem(item.ItemPrefab))
{
itemsToRemove.Add(item);
continue;
}
}
totalPrice += item.Quantity * ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo);
}
itemsToRemove.ForEach(i => itemsToPurchase.Remove(i));
@@ -673,7 +673,7 @@ namespace Barotrauma
{
if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null)
{
return Submarine.MainSub.Info;
return Submarine.MainSub?.Info;
}
else
{
@@ -34,7 +34,7 @@ namespace Barotrauma
private List<CharacterTeamType> teamIDs;
private const string inLobbyString = "\u2022 \u2022 \u2022";
private GUIFrame pendingChangesFrame = null;
public static GUIFrame PendingChangesFrame = null;
public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
private bool isTransferMenuOpen;
@@ -44,6 +44,7 @@ namespace Barotrauma
private float transferMenuOpenState;
private bool transferMenuStateCompleted;
private readonly HashSet<Identifier> registeredEvents = new HashSet<Identifier>();
private readonly TalentMenu talentMenu = new TalentMenu();
private class LinkedGUI
{
@@ -206,14 +207,10 @@ namespace Barotrauma
transferMenuButton.RectTransform.AbsoluteOffset = new Point(0, -pos - transferMenu.Rect.Height);
}
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
if (Character.Controlled?.Info is { } characterInfo && talentResetButton != null && talentApplyButton != null)
if (SelectedTab is InfoFrameTab.Talents)
{
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
talentApplyButton.Flash(GUIStyle.Orange);
}
talentMenu?.Update();
}
if (SelectedTab != InfoFrameTab.Crew) { return; }
@@ -251,6 +248,10 @@ namespace Barotrauma
{
infoFrame?.AddToGUIUpdateList();
NetLobbyScreen.JobInfoFrame?.AddToGUIUpdateList();
if (SelectedTab is InfoFrameTab.Talents)
{
talentMenu?.AddToGUIUpdateList();
}
}
public static void OnRoundEnded()
@@ -325,11 +326,11 @@ namespace Barotrauma
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
}, style: null);
pendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
PendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false)
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
NetLobbyScreen.CreateChangesPendingFrame(PendingChangesFrame);
}
SetBalanceText(balanceText, campaignMode.Bank.Balance);
@@ -403,7 +404,7 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
case InfoFrameTab.Talents:
CreateCharacterInfo(infoFrameHolder);
talentMenu.CreateGUI(infoFrameHolder);
break;
}
}
@@ -1774,370 +1775,10 @@ namespace Barotrauma
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true);
}
}
private Color unselectedColor = new Color(240, 255, 255, 225);
private Color unselectableColor = new Color(100, 100, 100, 225);
private Color pressedColor = new Color(60, 60, 60, 225);
private readonly List<(GUIButton button, GUIComponent icon)> talentButtons = new List<(GUIButton button, GUIComponent icon)>();
private readonly List<(Identifier talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(Identifier talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>();
private List<Identifier> selectedTalents = new List<Identifier>();
private GUITextBlock experienceText;
private GUIProgressBar experienceBar;
private GUITextBlock talentPointText;
private GUIListBox skillListBox;
private GUIButton talentApplyButton,
talentResetButton;
private GUIImage talentPointNotification;
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> talentStageStyles = new Dictionary<TalentTree.TalentTreeStageState, GUIComponentStyle>
{
{ TalentTree.TalentTreeStageState.Invalid, GUIStyle.GetComponentStyle("TalentTreeLocked") },
{ TalentTree.TalentTreeStageState.Locked, GUIStyle.GetComponentStyle("TalentTreeLocked") },
{ TalentTree.TalentTreeStageState.Unlocked, GUIStyle.GetComponentStyle("TalentTreePurchased") },
{ TalentTree.TalentTreeStageState.Available, GUIStyle.GetComponentStyle("TalentTreeUnlocked") },
{ TalentTree.TalentTreeStageState.Highlighted, GUIStyle.GetComponentStyle("TalentTreeAvailable") },
}.ToImmutableDictionary();
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, Color> talentStageBackgroundColors = new Dictionary<TalentTree.TalentTreeStageState, Color>
{
{ TalentTree.TalentTreeStageState.Invalid, new Color(48,48,48,255) },
{ TalentTree.TalentTreeStageState.Locked, new Color(48,48,48,255) },
{ TalentTree.TalentTreeStageState.Unlocked, new Color(24,37,31,255) },
{ TalentTree.TalentTreeStageState.Available, new Color(50,47,33,255) },
{ TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) },
}.ToImmutableDictionary();
private void CreateCharacterInfo(GUIFrame infoFrame)
{
infoFrame.ClearChildren();
talentButtons.Clear();
talentCornerIcons.Clear();
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15);
GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null);
GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
GUIFrame characterSettingsFrame = null;
GUILayoutGroup characterLayout = null;
if (!(GameMain.NetworkMember is null))
{
characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: null) { Visible = false };
characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
}
Character controlledCharacter = Character.Controlled;
CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
if (info == null) { return; }
Job job = info.Job;
GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = GUI.IntScale(10),
Stretch = true
};
GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), contentLayout.RectTransform, Anchor.Center), isHorizontal: true);
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
{
float posY = component.Rect.Center.Y - component.Rect.Width / 2;
info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false);
});
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
{
AbsoluteSpacing = GUI.IntScale(5),
CanBeFocused = true
};
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
if (!info.OmitJobInMenus)
{
nameBlock.TextColor = job.Prefab.UIColor;
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
}
LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
IEnumerable<TalentPrefab> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e));
if (talentsOutsideTree.Count() > 0)
{
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null);
GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont);
talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y);
var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.8f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true)
{
AutoHideScrollBar = false,
ResizeContentToMakeSpaceForScrollBar = false
};
extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65));
extraTalentLayout.Recalculate();
extraTalentList.ForceLayoutRecalculation();
foreach (var extraTalent in talentsOutsideTree)
{
var img = new GUIImage(new RectTransform(new Point(extraTalentList.Content.Rect.Height), extraTalentList.Content.RectTransform), sprite: extraTalent.Icon, scaleToFit: true)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description),
Color = GUIStyle.Green
};
img.RectTransform.SizeChanged += () =>
{
img.RectTransform.MaxSize = new Point(img.Rect.Height);
};
}
}
GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
{
AbsoluteSpacing = GUI.IntScale(5),
Stretch = true
};
GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
CreateSkillList(controlledCharacter, info, skillListBox);
new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
if (controlledCharacter == null)
{
talentTreeListBox.Enabled = false;
}
else
{
if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
selectedTalents = info.GetUnlockedTalentsInTree().ToList();
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in talentTree.TalentSubTrees)
{
GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
for (int i = 0; i < 4; i++)
{
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground")
{
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
};
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{
CanBeFocused = false,
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
if (subTree.TalentOptionStages.Length <= i) { continue; }
TalentOption talentOption = subTree.TalentOptionStages[i];
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
foreach (Identifier talentId in talentOption.TalentIdentifiers.OrderBy(t => t))
{
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab talent)) { continue; }
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
{
CanBeFocused = false
};
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description),
UserData = talent.Identifier,
PressedColor = pressedColor,
Enabled = controlledCharacter != null,
OnClicked = (button, userData) =>
{
// deselect other buttons in tier by removing their selected talents from pool
foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>())
{
if (guiButton.UserData is Identifier otherTalentIdentifier && guiButton != button)
{
if (!controlledCharacter.HasTalent(otherTalentIdentifier))
{
selectedTalents.Remove(otherTalentIdentifier);
}
}
}
Identifier talentIdentifier = (Identifier)userData;
if (TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents))
{
if (!selectedTalents.Contains(talentIdentifier))
{
selectedTalents.Add(talentIdentifier);
}
}
else if (!controlledCharacter.HasTalent(talentIdentifier))
{
selectedTalents.Remove(talentIdentifier);
}
UpdateTalentInfo();
return true;
},
};
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
GUIComponent iconImage;
if (talent.Icon is null)
{
iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: null)
{
OutlineColor = GUIStyle.Red,
TextColor = GUIStyle.Red,
PressedColor = unselectableColor,
DisabledColor = unselectableColor,
CanBeFocused = false,
};
}
else
{
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
{
PressedColor = unselectableColor,
DisabledColor = unselectableColor * 0.5f,
CanBeFocused = false,
};
}
iconImage.Enabled = talentButton.Enabled;
talentButtons.Add((talentButton, iconImage));
}
talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
}
}
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true)
{
RelativeSpacing = 0.01f,
Stretch = true
};
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform));
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
{
IsHorizontal = true,
};
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
{
Shadow = true,
ToolTip = TextManager.Get("experiencetooltip")
};
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{
OnClicked = ResetTalentSelection
};
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{
OnClicked = ApplyTalentSelection,
};
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
}
if (!(GameMain.NetworkMember is null))
{
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
{
IgnoreLayoutGroups = false
};
newCharacterBox.TextBlock.AutoScaleHorizontal = true;
newCharacterBox.OnClicked = (button, o) =>
{
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
{
newCharacterBox.Text = TextManager.Get("settings");
if (pendingChangesFrame != null)
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
}
OpenMenu();
});
return true;
}
OpenMenu();
return true;
void OpenMenu()
{
characterSettingsFrame!.Visible = true;
content.Visible = false;
}
};
if (!(characterLayout is null))
{
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
{
OnClicked = (button, o) =>
{
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
characterSettingsFrame!.Visible = false;
content.Visible = true;
return true;
}
};
}
}
UpdateTalentInfo();
}
private void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
public static void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
{
parent.Content.ClearChildren();
List<GUITextBlock> skillNames = new List<GUITextBlock>();
@@ -2172,116 +1813,10 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(skillNames);
}
private void UpdateTalentInfo()
{
Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return; }
if (SelectedTab != InfoFrameTab.Talents) { return; }
bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents();
if (unlockedAllTalents)
{
experienceText.Text = string.Empty;
experienceBar.BarSize = 1f;
}
else
{
experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel();
}
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString();
int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count();
if (unlockedAllTalents)
{
talentPointText.SetRichText($"‖color:{XMLExtensions.ToStringHex(Color.Gray)}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
}
else if (talentCount > 0)
{
string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUIStyle.Red)}‖{-talentCount}‖color:end‖";
LocalizedString localizedString = TextManager.GetWithVariables("talentmenu.points.spending", ("[amount]", pointsLeft), ("[used]", pointsUsed));
talentPointText.SetRichText(localizedString);
}
else
{
talentPointText.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
}
foreach (var (talentTree, index, icon, frame, glow) in talentCornerIcons)
{
TalentTree.TalentTreeStageState state = TalentTree.GetTalentOptionStageState(controlledCharacter, talentTree, index, selectedTalents);
GUIComponentStyle newStyle = talentStageStyles[state];
icon.ApplyStyle(newStyle);
icon.Color = newStyle.Color;
frame.Color = talentStageBackgroundColors[state];
glow.Visible = state == TalentTree.TalentTreeStageState.Highlighted;
}
foreach (var talentButton in talentButtons)
{
Identifier talentIdentifier = (Identifier)talentButton.button.UserData;
bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier);
Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
Color hoverColor = Color.White;
if (controlledCharacter.HasTalent(talentIdentifier))
{
newTalentColor = GUIStyle.Green;
}
else if (selectedTalents.Contains(talentIdentifier))
{
newTalentColor = GUIStyle.Orange;
hoverColor = Color.Lerp(GUIStyle.Orange, Color.White, 0.7f);
}
talentButton.icon.Color = newTalentColor;
talentButton.icon.HoverColor = hoverColor;
}
CreateSkillList(controlledCharacter, controlledCharacter.Info, skillListBox);
}
private void ApplyTalents(Character controlledCharacter)
{
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
foreach (Identifier talent in selectedTalents)
{
controlledCharacter.GiveTalent(talent);
if (GameMain.Client != null)
{
GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
}
}
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentInfo();
}
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
ApplyTalents(controlledCharacter);
return true;
}
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return false; }
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentInfo();
return true;
}
public void OnExperienceChanged(Character character)
{
if (character != Character.Controlled) { return; }
UpdateTalentInfo();
talentMenu.UpdateTalentInfo();
}
public void OnClose()
@@ -0,0 +1,680 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using static Barotrauma.TalentTree;
using static Barotrauma.TalentTree.TalentTreeStageState;
namespace Barotrauma
{
internal readonly record struct TalentButton(GUIButton Button,
GUIComponent IconComponent,
TalentPrefab Prefab)
{
public Identifier Identifier => Prefab.Identifier;
}
internal readonly record struct TalentCornerIcon(Identifier TalentTree,
int Index,
GUIImage IconComponent,
GUIFrame BackgroundComponent,
GUIFrame GlowComponent);
internal readonly struct TalentTreeStyle
{
public readonly GUIComponentStyle ComponentStyle;
public readonly Color Color;
public TalentTreeStyle(string componentStyle, Color color)
{
ComponentStyle = GUIStyle.GetComponentStyle(componentStyle);
Color = color;
}
}
internal sealed class TalentMenu
{
private static readonly Color unselectedColor = new Color(240, 255, 255, 225),
unselectableColor = new Color(100, 100, 100, 225),
pressedColor = new Color(60, 60, 60, 225),
lockedColor = new Color(48, 48, 48, 255),
unlockedColor = new Color(24, 37, 31, 255),
availableColor = new Color(50, 47, 33, 255);
private static readonly ImmutableDictionary<TalentTreeStageState, TalentTreeStyle> talentStageStyles =
new Dictionary<TalentTreeStageState, TalentTreeStyle>
{
[Invalid] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
[Locked] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
[Unlocked] = new TalentTreeStyle("TalentTreePurchased", unlockedColor),
[Available] = new TalentTreeStyle("TalentTreeUnlocked", availableColor),
[Highlighted] = new TalentTreeStyle("TalentTreeAvailable", availableColor)
}.ToImmutableDictionary();
private readonly HashSet<TalentButton> talentButtons = new HashSet<TalentButton>();
private readonly HashSet<GUIComponent> showCaseTalentFrames = new HashSet<GUIComponent>();
private readonly HashSet<TalentCornerIcon> talentCornerIcons = new HashSet<TalentCornerIcon>();
private HashSet<Identifier> selectedTalents = new HashSet<Identifier>();
private GUIListBox? skillListBox;
private GUITextBlock? talentPointText;
private GUIProgressBar? experienceBar;
private GUITextBlock? experienceText;
private GUILayoutGroup? skillLayout;
private GUIButton? talentApplyButton,
talentResetButton;
public void CreateGUI(GUIFrame parent)
{
parent.ClearChildren();
talentButtons.Clear();
talentCornerIcons.Clear();
showCaseTalentFrames.Clear();
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15);
GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), parent.RectTransform, Anchor.Center), style: null);
GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = GUI.IntScale(10),
Stretch = true
};
Character? controlledCharacter = Character.Controlled;
CharacterInfo? info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
if (info is null) { return; }
CreateStatPanel(contentLayout, info);
new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
if (JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree? talentTree))
{
CreateTalentMenu(contentLayout, info, talentTree!);
}
CreateFooter(contentLayout, info);
UpdateTalentInfo();
if (GameMain.NetworkMember != null)
{
CreateMultiplayerCharacterSettings(frame, content);
}
}
private void CreateMultiplayerCharacterSettings(GUIComponent parent, GUIComponent content)
{
if (skillLayout is null) { return; }
GUIFrame characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null) { Visible = false };
GUILayoutGroup characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
{
IgnoreLayoutGroups = false,
TextBlock =
{
AutoScaleHorizontal = true
}
};
newCharacterBox.OnClicked = (button, o) =>
{
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
{
newCharacterBox.Text = TextManager.Get("settings");
if (TabMenu.PendingChangesFrame != null)
{
NetLobbyScreen.CreateChangesPendingFrame(TabMenu.PendingChangesFrame);
}
OpenMenu();
});
return true;
}
OpenMenu();
return true;
void OpenMenu()
{
characterSettingsFrame!.Visible = true;
content.Visible = false;
}
};
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
{
OnClicked = (button, o) =>
{
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
characterSettingsFrame!.Visible = false;
content.Visible = true;
return true;
}
};
}
private void CreateStatPanel(GUIComponent parent, CharacterInfo info)
{
Job job = info.Job;
GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform, Anchor.Center), isHorizontal: true);
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
{
float posY = component.Rect.Center.Y - component.Rect.Width / 2;
info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false);
});
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
{
AbsoluteSpacing = GUI.IntScale(5),
CanBeFocused = true
};
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
if (!info.OmitJobInMenus)
{
nameBlock.TextColor = job.Prefab.UIColor;
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
}
if (info.PersonalityTrait != null)
{
LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
}
ImmutableHashSet<TalentPrefab?> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(static e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)).ToImmutableHashSet();
if (talentsOutsideTree.Any())
{
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null);
GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont);
talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y);
var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.8f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true)
{
AutoHideScrollBar = false,
ResizeContentToMakeSpaceForScrollBar = false
};
extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65));
extraTalentLayout.Recalculate();
extraTalentList.ForceLayoutRecalculation();
foreach (var extraTalent in talentsOutsideTree)
{
if (extraTalent is null) { continue; }
GUIImage talentImg = new GUIImage(new RectTransform(Vector2.One, extraTalentList.Content.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite: extraTalent.Icon, scaleToFit: true)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description),
Color = GUIStyle.Green
};
}
}
skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
{
AbsoluteSpacing = GUI.IntScale(5),
Stretch = true
};
GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
TabMenu.CreateSkillList(info.Character, info, skillListBox);
}
private void CreateTalentMenu(GUIComponent parent, CharacterInfo info, TalentTree tree)
{
GUIListBox mainList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), parent.RectTransform, anchor: Anchor.TopCenter));
selectedTalents = info.GetUnlockedTalentsInTree().ToHashSet();
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in tree.TalentSubTrees)
{
GUIListBox talentList;
GUIComponent talentParent;
Vector2 treeSize;
switch (subTree.Type)
{
case TalentTreeType.Primary:
talentList = mainList;
treeSize = new Vector2(1f, 0.5f);
break;
case TalentTreeType.Specialization:
talentList = GetSpecializationList();
treeSize = new Vector2(0.333f, 1f);
break;
default:
throw new ArgumentOutOfRangeException($"Invalid TalentTreeType \"{subTree.Type}\"");
}
talentParent = talentList.Content;
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(treeSize, talentParent.RectTransform), isHorizontal: false, childAnchor: Anchor.TopCenter)
{
Stretch = true
};
if (subTree.Type != TalentTreeType.Primary)
{
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.05f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter)
{ MinSize = new Point(0, GUI.IntScale(30)) }, style: null);
subtreeTitleFrame.RectTransform.IsFixedSize = true;
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
}
int optionAmount = subTree.TalentOptionStages.Length;
for (int i = 0; i < optionAmount; i++)
{
TalentOption option = subTree.TalentOptionStages[i];
CreateTalentOption(subTreeLayoutGroup, subTree, i, option, info);
}
subTreeLayoutGroup.RectTransform.Resize(new Point(subTreeLayoutGroup.Rect.Width,
subTreeLayoutGroup.Children.Sum(c => c.Rect.Height + subTreeLayoutGroup.AbsoluteSpacing)));
subTreeLayoutGroup.RectTransform.MinSize = new Point(subTreeLayoutGroup.Rect.Width, subTreeLayoutGroup.Rect.Height);
subTreeLayoutGroup.Recalculate();
if (subTree.Type == TalentTreeType.Specialization)
{
talentList.RectTransform.Resize(new Point(talentList.Rect.Width, Math.Max(subTreeLayoutGroup.Rect.Height, talentList.Rect.Height)));
talentList.RectTransform.MinSize = new Point(0, talentList.Rect.Height);
}
}
var specializationList = GetSpecializationList();
GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(c => c.Rect.Width), specializationList.Rect.Height));
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUIListBox GetSpecializationList()
{
if (mainList.Content.Children.LastOrDefault() is GUIListBox specializationList)
{
return specializationList;
}
GUIListBox newSpecializationList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), mainList.Content.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
return newSpecializationList;
}
}
private void CreateTalentOption(GUIComponent parent, TalentSubTree subTree, int index, TalentOption talentOption, CharacterInfo info)
{
int elementPadding = GUI.IntScale(8);
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.01f), parent.RectTransform, anchor: Anchor.TopCenter)
{ MinSize = new Point(0, GUI.IntScale(65)) }, style: null);
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center),
style: "TalentBackground")
{
Color = talentStageStyles[Locked].Color
};
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{
CanBeFocused = false,
Color = talentStageStyles[Locked].Color
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 0.9f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
HashSet<Identifier> talentOptionIdentifiers = talentOption.TalentIdentifiers.OrderBy(static t => t).ToHashSet();
bool hasShowcase = talentOption.ShowcaseTalent.TryUnwrap(out Identifier showcaseTalentIdentifier);
GUILayoutGroup showcaseLayout = talentOptionLayoutGroup;
if (hasShowcase)
{
talentOptionIdentifiers.Add(showcaseTalentIdentifier);
Point parentSize = talentBackground.RectTransform.NonScaledSize;
GUIFrame showCaseFrame = new GUIFrame(new RectTransform(new Point((int)(parentSize.X / 3f * (talentOptionIdentifiers.Count - 1)), parentSize.Y)), style: "GUITooltip")
{
UserData = showcaseTalentIdentifier,
IgnoreLayoutGroups = true,
Visible = false
};
GUILayoutGroup showcaseCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.7f), showCaseFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
showcaseLayout = new GUILayoutGroup(new RectTransform(Vector2.One, showcaseCenterGroup.RectTransform), isHorizontal: true) { Stretch = true };
showCaseTalentFrames.Add(showCaseFrame);
}
foreach (Identifier talentId in talentOptionIdentifiers)
{
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab? talent)) { continue; }
bool isShowCaseTalent = hasShowcase && talentId == showcaseTalentIdentifier;
GUIComponent talentParent;
if (hasShowcase && talentId != showcaseTalentIdentifier)
{
talentParent = showcaseLayout;
}
else
{
talentParent = talentOptionLayoutGroup;
}
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentParent.RectTransform), style: null)
{
CanBeFocused = false
};
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description),
UserData = talent.Identifier,
PressedColor = pressedColor,
Enabled = info.Character != null,
OnClicked = (button, userData) =>
{
if (isShowCaseTalent)
{
foreach (GUIComponent component in showCaseTalentFrames)
{
if (component.UserData is Identifier showcaseIdentifier && showcaseIdentifier == talentId)
{
component.RectTransform.ScreenSpaceOffset = new Point((int)(button.Rect.Location.X - component.Rect.Width / 2f + button.Rect.Width / 2f), button.Rect.Location.Y - component.Rect.Height);
component.Visible = true;
}
else
{
component.Visible = false;
}
}
return true;
}
Character? controlledCharacter = info.Character;
if (controlledCharacter is null) { return false; }
if (talentOption.MaxChosenTalents is 1)
{
// deselect other buttons in tier by removing their selected talents from pool
foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>())
{
if (guiButton.UserData is Identifier otherTalentIdentifier && guiButton != button)
{
if (!controlledCharacter.HasTalent(otherTalentIdentifier))
{
selectedTalents.Remove(otherTalentIdentifier);
}
}
}
}
Identifier talentIdentifier = (Identifier)userData;
if (IsViableTalentForCharacter(info.Character, talentIdentifier, selectedTalents))
{
if (!selectedTalents.Contains(talentIdentifier))
{
selectedTalents.Add(talentIdentifier);
}
else
{
selectedTalents.Remove(talentIdentifier);
}
}
else if (!controlledCharacter.HasTalent(talentIdentifier))
{
selectedTalents.Remove(talentIdentifier);
}
UpdateTalentInfo();
return true;
},
};
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
GUIComponent iconImage;
if (talent.Icon is null)
{
iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: null)
{
OutlineColor = GUIStyle.Red,
TextColor = GUIStyle.Red,
PressedColor = unselectableColor,
DisabledColor = unselectableColor,
CanBeFocused = false,
};
}
else
{
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
{
Color = talent.ColorOverride.TryUnwrap(out Color color) ? color : Color.White,
PressedColor = unselectableColor,
DisabledColor = unselectableColor * 0.5f,
CanBeFocused = false,
};
}
iconImage.Enabled = talentButton.Enabled;
if (isShowCaseTalent) { continue; }
talentButtons.Add(new TalentButton(talentButton, iconImage, talent));
}
talentCornerIcons.Add(new TalentCornerIcon(subTree.Identifier, index, cornerIcon, talentBackground, talentBackgroundHighlight));
}
private void CreateFooter(GUIComponent parent, CharacterInfo info)
{
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), parent.RectTransform, Anchor.TopCenter), isHorizontal: true)
{
RelativeSpacing = 0.01f,
Stretch = true
};
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform));
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
{
IsHorizontal = true,
};
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
{
Shadow = true,
ToolTip = TextManager.Get("experiencetooltip")
};
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight)
{ AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{
OnClicked = ResetTalentSelection
};
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{
OnClicked = ApplyTalentSelection,
};
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
}
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
UpdateTalentInfo();
return true;
}
private void ApplyTalents(Character controlledCharacter)
{
foreach (Identifier talent in CheckTalentSelection(controlledCharacter, selectedTalents))
{
controlledCharacter.GiveTalent(talent);
if (GameMain.Client != null)
{
GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
}
}
UpdateTalentInfo();
}
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
if (controlledCharacter is null) { return false; }
ApplyTalents(controlledCharacter);
return true;
}
public void UpdateTalentInfo()
{
if (!(Character.Controlled is { Info: var info } character)) { return; }
bool unlockedAllTalents = character.HasUnlockedAllTalents();
if (experienceBar is null || experienceText is null) { return; }
if (unlockedAllTalents)
{
experienceText.Text = string.Empty;
experienceBar.BarSize = 1f;
}
else
{
experienceText.Text = $"{info.ExperiencePoints - info.GetExperienceRequiredForCurrentLevel()} / {info.GetExperienceRequiredToLevelUp() - info.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = info.GetProgressTowardsNextLevel();
}
selectedTalents = CheckTalentSelection(character, selectedTalents).ToHashSet();
string pointsLeft = info.GetAvailableTalentPoints().ToString();
int talentCount = selectedTalents.Count - info.GetUnlockedTalentsInTree().Count();
if (unlockedAllTalents)
{
talentPointText?.SetRichText($"‖color:{Color.Gray.ToStringHex()}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
}
else if (talentCount > 0)
{
string pointsUsed = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{-talentCount}‖color:end‖";
LocalizedString localizedString = TextManager.GetWithVariables("talentmenu.points.spending", ("[amount]", pointsLeft), ("[used]", pointsUsed));
talentPointText?.SetRichText(localizedString);
}
else
{
talentPointText?.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
}
foreach (TalentCornerIcon cornerIcon in talentCornerIcons)
{
TalentTreeStageState state = GetTalentOptionStageState(character, cornerIcon.TalentTree, cornerIcon.Index, selectedTalents);
TalentTreeStyle style = talentStageStyles[state];
GUIComponentStyle newStyle = style.ComponentStyle;
cornerIcon.IconComponent.ApplyStyle(newStyle);
cornerIcon.IconComponent.Color = newStyle.Color;
cornerIcon.BackgroundComponent.Color = style.Color;
cornerIcon.GlowComponent.Visible = state == Highlighted;
}
foreach (TalentButton talentButton in talentButtons)
{
Identifier talentIdentifier = talentButton.Identifier;
bool unselectable = !IsViableTalentForCharacter(character, talentIdentifier, selectedTalents) || character.HasTalent(talentIdentifier);
Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
Color hoverColor = Color.White;
bool selected = false;
if (character.HasTalent(talentIdentifier))
{
selected = true;
newTalentColor = GUIStyle.Green;
}
else if (selectedTalents.Contains(talentIdentifier))
{
selected = true;
newTalentColor = GUIStyle.Orange;
hoverColor = Color.Lerp(GUIStyle.Orange, Color.White, 0.7f);
}
bool shouldOverride = !unselectable || selected;
if (shouldOverride && talentButton.Prefab.ColorOverride.TryUnwrap(out Color overrideColor))
{
newTalentColor = overrideColor;
}
talentButton.IconComponent.Color = newTalentColor;
talentButton.IconComponent.HoverColor = hoverColor;
}
if (skillListBox is null) { return; }
TabMenu.CreateSkillList(character, info, skillListBox);
}
public void AddToGUIUpdateList()
{
bool mouseInteracted = PlayerInput.PrimaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.ScrollWheelSpeed != 0;
bool keyboardInteracted = PlayerInput.KeyHit(Keys.Escape) || GameSettings.CurrentConfig.KeyMap.Bindings[InputType.InfoTab].IsHit();
foreach (GUIComponent component in showCaseTalentFrames)
{
component.AddToGUIUpdateList(order: 1);
if (!component.Visible) { continue; }
if (keyboardInteracted || (mouseInteracted && !component.Rect.Contains(PlayerInput.MousePosition)))
{
component.Visible = false;
}
}
}
public void Update()
{
if (Character.Controlled?.Info is not { } characterInfo || talentResetButton is null || talentApplyButton is null) { return; }
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
talentApplyButton.Flash(GUIStyle.Orange);
}
}
}
}
@@ -433,8 +433,8 @@ namespace Barotrauma
Location location = Campaign.Map.CurrentLocation;
int hullRepairCost = Campaign.GetHullRepairCost();
int itemRepairCost = Campaign.GetItemRepairCost();
int hullRepairCost = CampaignMode.GetHullRepairCost();
int itemRepairCost = CampaignMode.GetItemRepairCost();
int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost;
if (location != null)
{
@@ -847,7 +847,7 @@ namespace Barotrauma
foreach (UpgradePrefab prefab in prefabs)
{
if (prefab.MaxLevel is 0) { continue; }
if (prefab.GetMaxLevelForCurrentSub() == 0) { continue; }
CreateUpgradeEntry(prefab, category, parent.Content, submarine, entitiesOnSub);
}
}
@@ -1080,7 +1080,7 @@ namespace Barotrauma
public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category));
}
@@ -1177,11 +1177,12 @@ namespace Barotrauma
private static void UpdateUpgradePercentageText(GUITextBlock text, UpgradePrefab upgradePrefab, int currentLevel)
{
float nextIncrease = upgradePrefab.IncreaseOnTooltip * (Math.Min(currentLevel + 1, upgradePrefab.MaxLevel));
int maxLevel = upgradePrefab.GetMaxLevelForCurrentSub();
float nextIncrease = upgradePrefab.IncreaseOnTooltip * Math.Min(currentLevel + 1, maxLevel);
if (nextIncrease != 0f)
{
text.Text = $"{Math.Round(nextIncrease, 1)} %";
if (currentLevel == upgradePrefab.MaxLevel)
if (currentLevel == maxLevel)
{
text.TextColor = Color.Gray;
}
@@ -1221,7 +1222,7 @@ namespace Barotrauma
{
LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody",
("[upgradename]", prefab.Name),
("[amount]", prefab.Price.GetBuyprice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation).ToString()));
("[amount]", prefab.Price.GetBuyPrice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation).ToString()));
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
{
if (GameMain.NetworkMember != null)
@@ -1617,14 +1618,15 @@ namespace Barotrauma
{
int currentLevel = campaign.UpgradeManager.GetUpgradeLevel(prefab, category);
LocalizedString progressText = TextManager.GetWithVariables("upgrades.progressformat", ("[level]", currentLevel.ToString()), ("[maxlevel]", prefab.MaxLevel.ToString()));
int maxLevel = prefab.GetMaxLevelForCurrentSub();
LocalizedString progressText = TextManager.GetWithVariables("upgrades.progressformat", ("[level]", currentLevel.ToString()), ("[maxlevel]", maxLevel.ToString()));
if (prefabFrame.FindChild("progressbar", true) is { } progressParent)
{
GUIProgressBar bar = progressParent.GetChild<GUIProgressBar>();
if (bar != null)
{
bar.BarSize = currentLevel / (float) prefab.MaxLevel;
bar.Color = currentLevel >= prefab.MaxLevel ? GUIStyle.Green : GUIStyle.Orange;
bar.BarSize = currentLevel / (float)maxLevel;
bar.Color = currentLevel >= maxLevel ? GUIStyle.Green : GUIStyle.Orange;
}
GUITextBlock block = progressParent.GetChild<GUITextBlock>();
@@ -1637,12 +1639,12 @@ namespace Barotrauma
GUITextBlock priceLabel = textBlocks[0];
priceLabel.Visible = true;
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
if (priceLabel != null && !WaitForServerUpdate)
{
priceLabel.Text = TextManager.FormatCurrency(price);
if (currentLevel >= prefab.MaxLevel)
if (currentLevel >= maxLevel)
{
priceLabel.Text = TextManager.Get("Upgrade.MaxedUpgrade");
}
@@ -1651,7 +1653,7 @@ namespace Barotrauma
GUIButton button = buttonParent.GetChild<GUIButton>();
if (button != null)
{
button.Enabled = currentLevel < prefab.MaxLevel;
button.Enabled = currentLevel < maxLevel;
if (WaitForServerUpdate || campaign.GetBalance() < price)
{
button.Enabled = false;
@@ -1697,13 +1699,14 @@ namespace Barotrauma
foreach (GUIComponent component in indicators.Children)
{
if (!(component is GUIImage image)) { continue; }
if (component is not GUIImage image) { continue; }
foreach (UpgradePrefab prefab in prefabs)
{
if (component.UserData != prefab) { continue; }
if (prefab.MaxLevel is 0)
int maxLevel = prefab.GetMaxLevelForCurrentSub();
if (maxLevel == 0)
{
component.Visible = false;
continue;
@@ -1715,7 +1718,6 @@ namespace Barotrauma
GUIComponentStyle onStyle = styles["upgradeindicatoron".ToIdentifier()];
GUIComponentStyle dimStyle = styles["upgradeindicatordim".ToIdentifier()];
GUIComponentStyle offStyle = styles["upgradeindicatoroff".ToIdentifier()];
int maxLevel = prefab.MaxLevel;
if (maxLevel == 0)
{