Build 0.20.4.0

This commit is contained in:
Markus Isberg
2022-11-11 17:57:23 +02:00
parent edaf4b09fe
commit 54712b5dc9
201 changed files with 7618 additions and 2020 deletions
@@ -255,7 +255,17 @@ namespace Barotrauma
ScreenChanged = false;
}
updateList.ForEach(c => c.DrawAuto(spriteBatch));
foreach (GUIComponent c in updateList)
{
c.DrawAuto(spriteBatch);
}
// always draw IME preview on top of everything else
foreach (GUIComponent c in updateList)
{
if (c is not GUITextBox box) { continue; }
box.DrawIMEPreview(spriteBatch);
}
if (ScreenOverlayColor.A > 0.0f)
{
@@ -1251,6 +1261,10 @@ namespace Barotrauma
UpdateMessages(deltaTime);
UpdateSavingIndicator(deltaTime);
}
#if WINDOWS
GUITextBox.UpdateIME();
#endif
}
public static void UpdateGUIMessageBoxesOnly(float deltaTime)
@@ -1357,7 +1371,7 @@ namespace Barotrauma
}
}
#region Element drawing
#region Element drawing
private static readonly List<float> usedIndicatorAngles = new List<float>();
@@ -1843,9 +1857,9 @@ namespace Barotrauma
Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2();
sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale));
}
#endregion
#endregion
#region Element creation
#region Element creation
public static Texture2D CreateCircle(int radius, bool filled = false)
{
@@ -2217,9 +2231,9 @@ namespace Barotrauma
return msgBox;
}
#endregion
#endregion
#region Element positioning
#region Element positioning
private static List<T> CreateElements<T>(int count, RectTransform parent, Func<RectTransform, T> constructor,
Vector2? relativeSize = null, Point? absoluteSize = null,
Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null,
@@ -2418,9 +2432,9 @@ namespace Barotrauma
}
}
#endregion
#endregion
#region Misc
#region Misc
public static void TogglePauseMenu()
{
if (Screen.Selected == GameMain.MainMenuScreen) { return; }
@@ -2658,6 +2672,6 @@ namespace Barotrauma
if (!isSavingIndicatorEnabled) { return; }
timeUntilSavingIndicatorDisabled = delay;
}
#endregion
#endregion
}
}
@@ -9,6 +9,7 @@ using Barotrauma.IO;
using RestSharp;
using System.Net;
using System.Collections.Immutable;
using Barotrauma.Tutorials;
namespace Barotrauma
{
@@ -736,7 +737,7 @@ namespace Barotrauma
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos)
{
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning) { return; }
if (ObjectiveManager.ContentRunning) { return; }
int width = (int)(400 * GUI.Scale);
int height = (int)(18 * GUI.Scale);
@@ -759,7 +760,7 @@ namespace Barotrauma
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle targetElement)
{
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning) { return; }
if (ObjectiveManager.ContentRunning) { return; }
int width = (int)(400 * GUI.Scale);
int height = (int)(18 * GUI.Scale);
@@ -112,6 +112,10 @@ namespace Barotrauma
public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { }
#if !WINDOWS
public void ReceiveEditingInput(string text, int start) { }
#endif
public void ReceiveSpecialInput(Keys key)
{
switch (key)
@@ -1349,6 +1349,10 @@ namespace Barotrauma
public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { }
#if !WINDOWS
public void ReceiveEditingInput(string text, int start) { }
#endif
public void ReceiveSpecialInput(Keys key)
{
switch (key)
@@ -24,7 +24,8 @@ namespace Barotrauma
InGame,
Vote,
Hint,
Tutorial
Tutorial,
Warning // Keep this last so that it's always drawn in front
}
private bool IsAnimated => type == Type.InGame || type == Type.Hint || type == Type.Tutorial;
@@ -84,8 +85,8 @@ namespace Barotrauma
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
public GUIMessageBox(LocalizedString headerText, LocalizedString text, Vector2? relativeSize = null, Point? minSize = null)
: this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize)
public GUIMessageBox(LocalizedString headerText, LocalizedString text, Vector2? relativeSize = null, Point? minSize = null, Type type = Type.Default)
: this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize, type: type)
{
this.Buttons[0].OnClicked = Close;
}
@@ -147,7 +148,7 @@ namespace Barotrauma
Tag = tag.ToIdentifier();
#warning TODO: These should be broken into separate methods at least
if (type == Type.Default || type == Type.Vote)
if (type == Type.Default || type == Type.Vote || type == Type.Warning)
{
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Barotrauma
@@ -285,8 +286,8 @@ namespace Barotrauma
/// This is the new constructor.
/// If the rectT height is set 0, the height is calculated from the text.
/// </summary>
public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
: base(style, rectT)
{
if (color.HasValue)
@@ -551,6 +552,8 @@ namespace Barotrauma
if (TextGetter != null) { Text = TextGetter(); }
string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (overflowClipActive)
{
@@ -561,7 +564,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
if (!text.IsNullOrEmpty())
if (!string.IsNullOrEmpty(textToShow))
{
Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset;
if (RoundToNearestPixel)
@@ -570,7 +573,8 @@ namespace Barotrauma
pos.Y = (int)pos.Y;
}
Color currentTextColor = State == ComponentState.Hover || State == ComponentState.HoverSelected ? HoverTextColor : TextColor;
Color currentTextColor = State is ComponentState.Hover or ComponentState.HoverSelected ? HoverTextColor : TextColor;
if (!enabled)
{
currentTextColor = disabledTextColor;
@@ -582,7 +586,6 @@ namespace Barotrauma
if (!HasColorHighlight)
{
string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
if (TextManager.DebugDraw)
{
@@ -604,10 +607,10 @@ namespace Barotrauma
{
if (OverrideRichTextDataAlpha)
{
RichTextData.Value.ForEach(rt => rt.Alpha = currentTextColor.A / 255.0f);
RichTextData?.ForEach(rt => rt.Alpha = currentTextColor.A / 255.0f);
}
Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text.SanitizedString).Value, pos,
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData.Value, alignment: textAlignment, forceUpperCase: ForceUpperCase);
Font.DrawStringWithColors(spriteBatch, textToShow, pos,
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData, alignment: textAlignment, forceUpperCase: ForceUpperCase);
}
Strikethrough?.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X,
@@ -11,7 +11,7 @@ namespace Barotrauma
public delegate void TextBoxEvent(GUITextBox sender, Keys key);
public class GUITextBox : GUIComponent, IKeyboardSubscriber
public partial class GUITextBox : GUIComponent, IKeyboardSubscriber
{
public event TextBoxEvent OnSelected;
public event TextBoxEvent OnDeselected;
@@ -67,12 +67,12 @@ namespace Barotrauma
private int selectionEndIndex;
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
private GUICustomComponent caretAndSelectionRenderer;
private readonly GUICustomComponent caretAndSelectionRenderer;
private bool mouseHeldInside;
private readonly Memento<string> memento = new Memento<string>();
// Skip one update cycle, fixes Enter key instantly deselecting the chatbox
private bool skipUpdate;
@@ -189,6 +189,7 @@ namespace Barotrauma
base.Font = value;
if (textBlock == null) { return; }
textBlock.Font = value;
imePreviewTextHandler.Font = Font;
}
}
@@ -253,6 +254,8 @@ namespace Barotrauma
public override bool PlaySoundOnSelect { get; set; } = true;
private readonly IMEPreviewTextHandler imePreviewTextHandler;
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true)
: base(style, rectT)
@@ -264,6 +267,7 @@ namespace Barotrauma
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap);
imePreviewTextHandler = new IMEPreviewTextHandler(textBlock.Font);
GUIStyle.Apply(textBlock, "", this);
if (font != null) { textBlock.Font = font; }
CaretEnabled = true;
@@ -295,18 +299,17 @@ namespace Barotrauma
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
}
Font = textBlock.Font;
Enabled = true;
rectT.SizeChanged += () =>
rectT.SizeChanged += () =>
{
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
caretPosDirty = true;
caretPosDirty = true;
};
rectT.ScaleChanged += () =>
{
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
caretPosDirty = true;
caretPosDirty = true;
};
}
@@ -381,14 +384,16 @@ namespace Barotrauma
{
GUI.KeyboardDispatcher.Subscriber = null;
}
OnDeselected?.Invoke(this, Keys.None);
imePreviewTextHandler.Reset();
}
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null)
{
frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
}
protected override void Update(float deltaTime)
{
if (!Visible) return;
@@ -673,6 +678,18 @@ namespace Barotrauma
break;
}
}
#if !WINDOWS
public void ReceiveEditingInput(string text, int start)
{
if (string.IsNullOrEmpty(text))
{
if (start is 0) { imePreviewTextHandler.Reset(); }
return;
}
imePreviewTextHandler.UpdateText(text, start);
}
#endif
public void ReceiveSpecialInput(Keys key)
{
@@ -864,6 +881,24 @@ namespace Barotrauma
}
}
public void DrawIMEPreview(SpriteBatch spriteBatch)
{
if (!imePreviewTextHandler.HasText) { return; }
Vector2 imePosition = CaretScreenPos;
int inflate = GUI.IntScale(3);
RectangleF rect = new RectangleF(imePosition, imePreviewTextHandler.TextSize);
rect.Inflate(inflate, inflate);
RectangleF borderRect = rect;
borderRect.Inflate(1, 1);
GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f);
GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f);
Font.DrawString(spriteBatch, imePreviewTextHandler.PreviewText, imePosition, GUIStyle.Orange, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, 0, alignment: textBlock.TextAlignment, forceUpperCase: textBlock.ForceUpperCase);
}
private void CalculateSelection()
{
string textDrawn = Censor ? textBlock.CensoredText : WrappedText;
@@ -0,0 +1,86 @@
using ImeSharp;
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma;
/// <summary>
/// A class for handling Input Method Editor (used for inputting e.g. Chinese and Japanese text)
/// </summary>
public partial class GUITextBox : GUIComponent
{
private static bool initialized;
public static GUIFrame IMEWindow { get; set; }
public static GUITextBlock IMETextBlock { get; set; }
public static void UpdateIME()
{
if (!initialized) { InitializeIME(); }
if (GUI.KeyboardDispatcher.Subscriber is GUITextBox { Selected: true })
{
IMEWindow?.AddToGUIUpdateList(order: 10);
}
}
private static void InitializeIME()
{
InputMethod.Initialize(GameMain.Instance.Window.Hwnd, false);
InputMethod.TextCompositionCallback = OnTextComposition;
InputMethod.CommitTextCompositionCallback = OnCommitTextComposition;
InputMethod.Enabled = true;
IMEWindow = new GUIFrame(new RectTransform(new Point(GUI.IntScale(300), GUI.IntScale(300)), GUI.Canvas), "InnerFrame") { CanBeFocused = false, Visible = false };
IMETextBlock = new GUITextBlock(new RectTransform(Vector2.One, IMEWindow.RectTransform), "") { CanBeFocused = false };
initialized = true;
}
private static void OnTextComposition(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection)
{
if (GUI.KeyboardDispatcher.Subscriber is not GUITextBox { Selected: true } textBox) { return; }
IMEWindow.Visible = true;
string text = compositionText.ToString().Insert(cursorPosition, "|");
if (candidateList != null)
{
text += "\n";
for (int i = 0; i < candidatePageSize; i++)
{
string candidateStr = $"\t{candidatePageStart + i + 1} {candidateList[i]}";
if (candidateSelection == i)
{
candidateStr = $" ‖color:{XMLExtensions.ToStringHex(Color.White)}‖{candidateStr}‖end‖";
}
candidateStr += "\n";
text += candidateStr;
}
}
IMETextBlock.Text = RichString.Rich(text);
IMEWindow.RectTransform.NonScaledSize = new Point(
Math.Max(IMEWindow.Rect.Width, (int)IMETextBlock.TextSize.X + GUI.IntScale(32)),
(int)IMETextBlock.TextSize.Y);
Point windowPos = new Point(textBox.Rect.X, textBox.Rect.Bottom);
if (windowPos.Y + IMEWindow.Rect.Height > GameMain.GraphicsHeight)
{
windowPos.Y = textBox.Rect.Y - IMEWindow.Rect.Height;
}
IMEWindow.RectTransform.ScreenSpaceOffset = windowPos;
}
private static void OnCommitTextComposition(string text)
{
if (IMEWindow.Visible)
{
foreach (char c in text)
{
if (!char.IsControl(c))
{
GUI.KeyboardDispatcher.Subscriber?.ReceiveTextInput(c);
}
}
}
IMEWindow.Visible = false;
}
}
@@ -65,7 +65,7 @@ namespace Barotrauma
get; private set;
}
public static Rectangle AfflictionAreaLeft
public static Rectangle HealthBarAfflictionArea
{
get; private set;
}
@@ -143,7 +143,7 @@ namespace Barotrauma
}
int healthBarHeight = (int)(50f * GUI.Scale);
HealthBarArea = new Rectangle(BottomRightInfoArea.Right - healthBarWidth + (int)Math.Floor(1 / GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + GUI.IntScale(10), healthBarWidth, healthBarHeight);
AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
HealthBarAfflictionArea = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
int messageAreaWidth = GameMain.GraphicsWidth / 3;
@@ -173,7 +173,7 @@ namespace Barotrauma
int objectiveListAreaX = HealthWindowAreaLeft.Right + Padding;
int objectiveListAreaY = ButtonAreaTop.Bottom + Padding;
TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (AfflictionAreaLeft.Top - Padding) - objectiveListAreaY);
TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (HealthBarAfflictionArea.Top - Padding) - objectiveListAreaY);
int votingAreaWidth = (int)(400 * GUI.Scale);
int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth;
@@ -193,7 +193,7 @@ namespace Barotrauma
DrawRectangle(CrewArea, Color.Blue * 0.5f);
DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f);
DrawRectangle(HealthBarArea, Color.Red * 0.5f);
DrawRectangle(AfflictionAreaLeft, Color.Red * 0.5f);
DrawRectangle(HealthBarAfflictionArea, Color.Red * 0.5f);
DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f);
DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f);
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f);
@@ -0,0 +1,54 @@
#nullable enable
using Microsoft.Xna.Framework;
namespace Barotrauma
{
public sealed class IMEPreviewTextHandler
{
public string PreviewText { get; private set; } = string.Empty;
public Vector2 TextSize { get; private set; }
public bool HasText => !string.IsNullOrEmpty(PreviewText);
// This has to be settable because for some reason we update the font of GUITextBox in some places
public GUIFont Font { get; set; }
public IMEPreviewTextHandler(GUIFont font)
{
Font = font;
}
public void Reset()
{
TextSize = Vector2.Zero;
PreviewText = string.Empty;
}
public void UpdateText(string text, int start)
{
if (string.IsNullOrEmpty(text) && start is 0)
{
Reset();
return;
}
int totalLength = start + text.Length;
string newText = PreviewText;
if (newText.Length > totalLength)
{
newText = newText[..totalLength];
}
if (totalLength > newText.Length)
{
// this is required for some reason on Windows
// my guess is that the order which TextEditing events come thru is not guaranteed
newText = newText.PadRight(totalLength);
}
newText = newText.Remove(start, text.Length).Insert(start, text);
PreviewText = newText;
TextSize = Font.MeasureString(PreviewText);
}
}
}
@@ -12,7 +12,7 @@ using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
namespace Barotrauma
{
[SuppressMessage("ReSharper", "UnusedVariable")]
internal class MedicalClinicUI
internal sealed class MedicalClinicUI
{
private enum ElementState
{
@@ -127,12 +127,14 @@ namespace Barotrauma
{
public readonly GUIComponent Panel;
public readonly GUIListBox HealList;
public readonly GUIComponent TreatAllButton;
public readonly List<CrewElement> HealElements;
public CrewHealList(GUIListBox healList, GUIComponent panel)
public CrewHealList(GUIListBox healList, GUIComponent panel, GUIComponent treatAllButton)
{
Panel = panel;
HealList = healList;
TreatAllButton = treatAllButton;
HealElements = new List<CrewElement>();
}
}
@@ -179,7 +181,7 @@ namespace Barotrauma
private PopupAfflictionList? selectedCrewAfflictionList;
private bool isWaitingForServer;
private const float refreshTimerMax = 3f;
private float refreshTimer = 0;
private float refreshTimer;
private PlayerBalanceElement? playerBalanceElement;
@@ -196,7 +198,7 @@ namespace Barotrauma
{
new GUIButton(new RectTransform(new Vector2(0.2f, 0.1f), parent.RectTransform, Anchor.TopCenter), "Recreate UI - NOT PRESENT IN RELEASE!")
{
OnClicked = (_, __) =>
OnClicked = (_, _) =>
{
parent.ClearChildren();
CreateUI();
@@ -254,7 +256,7 @@ namespace Barotrauma
continue;
}
CreatePendingHealElement(healList.HealList.Content, crewMember, healList, Array.Empty<MedicalClinic.NetAffliction>());
CreatePendingHealElement(healList.HealList.Content, crewMember, healList, ImmutableArray<MedicalClinic.NetAffliction>.Empty);
}
// check if there are elements that the crew doesn't have
@@ -309,7 +311,7 @@ namespace Barotrauma
private void UpdateCrewPanel()
{
if (!(crewHealList is { } healList)) { return; }
if (crewHealList is not { } healList) { return; }
ImmutableArray<CharacterInfo> crew = MedicalClinic.GetCrewCharacters();
@@ -334,12 +336,21 @@ namespace Barotrauma
healList.HealList.Content.RemoveChild(element.UIElement);
}
IEnumerable<CrewElement> orderedList = healList.HealElements.OrderBy(element => element.Target.Character?.HealthPercentage ?? 100);
IEnumerable<CrewElement> orderedList = healList.HealElements.OrderBy(static element => element.Target.Character?.HealthPercentage ?? 100);
foreach (CrewElement element in orderedList)
{
element.UIElement.SetAsLastChild();
}
healList.TreatAllButton.Enabled = false;
foreach (CrewElement element in healList.HealElements)
{
if (element.Afflictions.Count is 0) { continue; }
healList.TreatAllButton.Enabled = true;
break;
}
}
private static void UpdateAfflictionList(CrewElement healElement)
@@ -350,7 +361,7 @@ namespace Barotrauma
// sum up all the afflictions and their strengths
Dictionary<AfflictionPrefab, float> afflictionAndStrength = new Dictionary<AfflictionPrefab, float>();
foreach (Affliction affliction in health.GetAllAfflictions().Where(a => MedicalClinic.IsHealable(a)))
foreach (Affliction affliction in health.GetAllAfflictions().Where(MedicalClinic.IsHealable))
{
if (afflictionAndStrength.TryGetValue(affliction.Prefab, out float strength))
{
@@ -446,8 +457,8 @@ namespace Barotrauma
};
GUILayoutGroup clinicLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), clinicContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUIImage clinicIcon = new GUIImage(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "CrewManagementHeaderIcon", scaleToFit: true);
GUITextBlock clinicLabel = new GUITextBlock(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform), TextManager.Get("medicalclinic.medicalclinic"), font: GUIStyle.LargeFont);
new GUIImage(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "CrewManagementHeaderIcon", scaleToFit: true);
new GUITextBlock(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform), TextManager.Get("medicalclinic.medicalclinic"), font: GUIStyle.LargeFont);
GUIFrame clinicBackground = new GUIFrame(new RectTransform(Vector2.One, clinicContent.RectTransform));
@@ -480,22 +491,24 @@ namespace Barotrauma
Stretch = true
};
// GUILayoutGroup sortLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.05f), clinicContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
// new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), sortLayout.RectTransform), TextManager.Get("campaignstore.sortby"), font: GUI.SubHeadingFont);
// GUIDropDown sortDropdown = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1f), sortLayout.RectTransform));
//
// foreach (SortMode mode in Enum.GetValues(typeof(SortMode)).Cast<SortMode>())
// {
// sortDropdown.AddItem(TextManager.Get($"medicalclinic.sortmode.{mode}"), mode);
// }
//
// sortDropdown.SelectItem(SortMode.Severity);
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, clinicContainer.RectTransform));
crewHealList = new CrewHealList(crewList, parent);
GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), clinicContainer.RectTransform), TextManager.Get("medicalclinic.treateveryone"))
{
OnClicked = (_, _) =>
{
isWaitingForServer = true;
medicalClinic.TreatAllButtonAction(OnReceived);
return true;
}
};
crewHealList = new CrewHealList(crewList, parent, treatAllButton);
void OnReceived(MedicalClinic.CallbackOnlyRequest obj)
{
isWaitingForServer = false;
}
}
private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
@@ -525,9 +538,9 @@ namespace Barotrauma
TextColor = GUIStyle.Red
};
MedicalClinic.NetCrewMember member = new MedicalClinic.NetCrewMember { CharacterInfo = info, Afflictions = Array.Empty<MedicalClinic.NetAffliction>() };
MedicalClinic.NetCrewMember member = new MedicalClinic.NetCrewMember(info);
crewBackground.OnClicked = (_, __) =>
crewBackground.OnClicked = (_, _) =>
{
SelectCharacter(member, new Vector2(panel.Rect.Right, crewBackground.Rect.Top));
return true;
@@ -618,7 +631,7 @@ namespace Barotrauma
pendingHealList = list;
}
private void CreatePendingHealElement(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, PendingHealList healList, MedicalClinic.NetAffliction[] afflictions)
private void CreatePendingHealElement(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, PendingHealList healList, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
{
CharacterInfo? healInfo = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters());
if (healInfo is null) { return; }
@@ -803,7 +816,7 @@ namespace Barotrauma
}
allComponents.Add(treatAllButton);
treatAllButton.OnClicked = (_, __) =>
treatAllButton.OnClicked = (_, _) =>
{
ImmutableArray<MedicalClinic.NetAffliction> afflictions = request.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
if (!afflictions.Any()) { return true; }
@@ -873,9 +886,13 @@ namespace Barotrauma
{
RelativeSpacing = 0.05f
};
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform), prefab.Description, font: GUIStyle.SmallFont, wrap: true)
LocalizedString description = affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.OtherCharacter);
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform),
description,
font: GUIStyle.SmallFont,
wrap: true)
{
ToolTip = prefab.Description
ToolTip = description
};
bool truncated = false;
while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n'))
@@ -919,10 +936,9 @@ namespace Barotrauma
}
else
{
MedicalClinic.NetCrewMember newMember = new MedicalClinic.NetCrewMember
MedicalClinic.NetCrewMember newMember = crewMember with
{
CharacterInfoID = crewMember.CharacterInfoID,
Afflictions = Array.Empty<MedicalClinic.NetAffliction>()
Afflictions = ImmutableArray<MedicalClinic.NetAffliction>.Empty
};
existingMember = newMember;
@@ -936,7 +952,7 @@ namespace Barotrauma
}
}
existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToArray();
existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToImmutableArray();
ToggleElements(ElementState.Disabled, elementsToDisable);
medicalClinic.AddPendingButtonAction(existingMember, request =>
{
@@ -745,7 +745,7 @@ namespace Barotrauma
} ?? Enumerable.Empty<PurchasedItem>();
foreach (var button in itemCategoryButtons)
{
if (!(button.UserData is MapEntityCategory category))
if (button.UserData is not MapEntityCategory category)
{
continue;
}
@@ -1110,7 +1110,7 @@ namespace Barotrauma
private void SetPriceGetters(GUIComponent itemFrame, bool buying)
{
if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; }
if (itemFrame == null || itemFrame.UserData is not PurchasedItem pi) { return; }
if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock)
{
@@ -1667,8 +1667,8 @@ namespace Barotrauma
{
foreach (var subItem in subItems)
{
if (!subItem.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { continue; }
if (!subItem.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { continue; }
if (!subItem.Components.All(c => c is not Holdable h || !h.Attachable || !h.Attached)) { continue; }
if (!subItem.Components.All(c => c is not Wire w || w.Connections.All(c => c == null))) { continue; }
if (!ItemAndAllContainersInteractable(subItem)) { continue; }
AddOwnedItem(subItem);
}
@@ -1701,7 +1701,7 @@ namespace Barotrauma
void AddOwnedItem(Item item)
{
if (!(item?.Prefab.GetPriceInfo(ActiveStore) is PriceInfo priceInfo)) { return; }
if (item?.Prefab.GetPriceInfo(ActiveStore) is not PriceInfo priceInfo) { return; }
bool isNonEmpty = !priceInfo.DisplayNonEmpty || item.ConditionPercentage > 5.0f;
if (OwnedItems.TryGetValue(item.Prefab, out ItemQuantity itemQuantity))
{
@@ -1729,7 +1729,7 @@ namespace Barotrauma
private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled)
{
if (!(itemFrame?.UserData is PurchasedItem pi)) { return; }
if (itemFrame?.UserData is not PurchasedItem pi) { return; }
bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
if (!refreshFrameStatus) { return; }
if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon)
@@ -1841,11 +1841,7 @@ namespace Barotrauma
LocalizedString toolTip = string.Empty;
if (purchasedItem.ItemPrefab != null)
{
toolTip = purchasedItem.ItemPrefab.Name;
if (!purchasedItem.ItemPrefab.Description.IsNullOrEmpty())
{
toolTip += $"\n{purchasedItem.ItemPrefab.Description}";
}
toolTip = purchasedItem.ItemPrefab.GetTooltip();
if (itemQuantity != null)
{
if (itemQuantity.AllNonEmpty)
@@ -1859,7 +1855,7 @@ namespace Barotrauma
}
}
}
itemComponent.ToolTip = toolTip;
itemComponent.ToolTip = RichString.Rich(toolTip);
}
if (ownedLabel != null)
{
@@ -2181,7 +2177,7 @@ namespace Barotrauma
{
needsRefresh = itemsToSellFromSub.Count != prevSubItems.Count ||
itemsToSellFromSub.Sum(i => i.Quantity) != prevSubItems.Sum(i => i.Quantity) ||
itemsToSellFromSub.Any(i => !(prevSubItems.FirstOrDefault(prev => prev.ItemPrefab == i.ItemPrefab) is PurchasedItem prev) || i.Quantity != prev.Quantity) ||
itemsToSellFromSub.Any(i => prevSubItems.FirstOrDefault(prev => prev.ItemPrefab == i.ItemPrefab) is not PurchasedItem prev || i.Quantity != prev.Quantity) ||
prevSubItems.Any(prev => itemsToSellFromSub.None(i => i.ItemPrefab == prev.ItemPrefab));
}
}
@@ -472,7 +472,7 @@ namespace Barotrauma
if (transferService)
{
subsToShow.AddRange(GameMain.GameSession.OwnedSubmarines);
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
subsToShow.Sort(ComparePrice);
string currentSubName = CurrentOrPendingSubmarine().Name;
int currentIndex = subsToShow.FindIndex(s => s.Name == currentSubName);
if (currentIndex != -1)
@@ -484,7 +484,11 @@ namespace Barotrauma
{
subsToShow.AddRange((GameMain.Client is null ? SubmarineInfo.SavedSubmarines : MultiPlayerCampaign.GetCampaignSubs())
.Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
if (GameMain.GameSession.Campaign?.Map?.CurrentLocation is Location currentLocation)
{
subsToShow.RemoveAll(sub => !currentLocation.IsSubmarineAvailable(sub));
}
subsToShow.Sort(ComparePrice);
}
if (transferService)
@@ -492,10 +496,14 @@ namespace Barotrauma
SetConfirmButtonState(selectedSubmarine != null && selectedSubmarine.Name != CurrentOrPendingSubmarine().Name);
}
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
pageCount = Math.Max(1, (int)Math.Ceiling(subsToShow.Count / (float)submarinesPerPage));
UpdatePaging();
ContentRefreshRequired = false;
static int ComparePrice(SubmarineInfo x, SubmarineInfo y)
{
return x.Price.CompareTo(y.Price) * 100 + x.Name.CompareTo(y.Name);
}
}
private SubmarineInfo GetSubToDisplay(int index)
@@ -208,10 +208,7 @@ namespace Barotrauma
}
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
if (SelectedTab is InfoFrameTab.Talents)
{
talentMenu?.Update();
}
talentMenu?.Update();
if (SelectedTab != InfoFrameTab.Crew) { return; }
if (linkedGUIList == null) { return; }
@@ -248,10 +245,6 @@ namespace Barotrauma
{
infoFrame?.AddToGUIUpdateList();
NetLobbyScreen.JobInfoFrame?.AddToGUIUpdateList();
if (SelectedTab is InfoFrameTab.Talents)
{
talentMenu?.AddToGUIUpdateList();
}
}
public static void OnRoundEnded()
@@ -404,7 +397,7 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
case InfoFrameTab.Talents:
talentMenu.CreateGUI(infoFrameHolder);
talentMenu.CreateGUI(infoFrameHolder, Character.Controlled ?? GameMain.Client?.Character);
break;
}
}
@@ -958,16 +951,26 @@ namespace Barotrauma
if (character != null)
{
if (GameMain.Client == null)
if (GameMain.Client is null)
{
GUIComponent preview = character.Info.CreateInfoFrame(background, false, null);
}
else
{
GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character)));
GameMain.Client.SelectCrewCharacter(character, preview);
if (!character.IsBot && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) { CreateWalletFrame(background, character, mpCampaign); }
}
if (background.FindChild(TalentMenu.ManageBotTalentsButtonUserData, recursive: true) is GUIButton { Enabled: true } talentButton)
{
talentButton.OnClicked = (button, o) =>
{
talentMenu.CreateGUI(infoFrameHolder, character);
return true;
};
}
}
else if (client != null)
{
@@ -1792,10 +1795,10 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.TopRight);
float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level;
float modifiedSkillLevel = MathF.Floor(character?.GetSkillLevel(skill.Identifier) ?? skill.Level);
if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level)))
{
int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level);
int skillChange = (int)MathF.Floor(modifiedSkillLevel - MathF.Floor(skill.Level));
string stringColor = skillChange switch
{
> 0 => XMLExtensions.ToStringHex(GUIStyle.Green),
@@ -1806,7 +1809,7 @@ namespace Barotrauma
RichString changeText = RichString.Rich($"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)");
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText) { Padding = Vector4.Zero };
}
//skillContainer.Recalculate();
skillContainer.Recalculate();
}
parent.RecalculateChildren();
@@ -5,15 +5,18 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using static Barotrauma.TalentTree;
using static Barotrauma.TalentTree.TalentTreeStageState;
using static Barotrauma.TalentTree.TalentStages;
namespace Barotrauma
{
internal readonly record struct TalentButton(GUIButton Button,
GUIComponent IconComponent,
internal readonly record struct TalentShowCaseButton(ImmutableHashSet<TalentButton> Buttons,
GUIComponent IconComponent);
internal readonly record struct TalentButton(GUIComponent IconComponent,
TalentPrefab Prefab)
{
public Identifier Identifier => Prefab.Identifier;
@@ -39,6 +42,11 @@ namespace Barotrauma
internal sealed class TalentMenu
{
public const string ManageBotTalentsButtonUserData = "managebottalentsbutton";
private Character? character;
private CharacterInfo? characterInfo;
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),
@@ -46,8 +54,8 @@ namespace Barotrauma
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>
private static readonly ImmutableDictionary<TalentStages, TalentTreeStyle> talentStageStyles =
new Dictionary<TalentStages, TalentTreeStyle>
{
[Invalid] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
[Locked] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
@@ -57,10 +65,13 @@ namespace Barotrauma
}.ToImmutableDictionary();
private readonly HashSet<TalentButton> talentButtons = new HashSet<TalentButton>();
private readonly HashSet<TalentShowCaseButton> talentShowCaseButtons = new HashSet<TalentShowCaseButton>();
private readonly HashSet<GUIComponent> showCaseTalentFrames = new HashSet<GUIComponent>();
private readonly HashSet<TalentCornerIcon> talentCornerIcons = new HashSet<TalentCornerIcon>();
private HashSet<Identifier> selectedTalents = new HashSet<Identifier>();
private readonly Queue<Identifier> showCaseClosureQueue = new();
private GUIListBox? skillListBox;
private GUITextBlock? talentPointText;
private GUIProgressBar? experienceBar;
@@ -70,13 +81,17 @@ namespace Barotrauma
private GUIButton? talentApplyButton,
talentResetButton;
public void CreateGUI(GUIFrame parent)
public void CreateGUI(GUIFrame parent, Character? targetCharacter)
{
parent.ClearChildren();
talentButtons.Clear();
talentShowCaseButtons.Clear();
talentCornerIcons.Clear();
showCaseTalentFrames.Clear();
character = targetCharacter;
characterInfo = targetCharacter?.Info;
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);
@@ -89,23 +104,21 @@ namespace Barotrauma
Stretch = true
};
Character? controlledCharacter = Character.Controlled;
CharacterInfo? info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
if (info is null) { return; }
if (characterInfo is null) { return; }
CreateStatPanel(contentLayout, info);
CreateStatPanel(contentLayout, characterInfo);
new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
if (JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree? talentTree))
if (JobTalentTrees.TryGet(characterInfo.Job.Prefab.Identifier, out TalentTree? talentTree))
{
CreateTalentMenu(contentLayout, info, talentTree!);
CreateTalentMenu(contentLayout, characterInfo, talentTree!);
}
CreateFooter(contentLayout, info);
CreateFooter(contentLayout, characterInfo);
UpdateTalentInfo();
if (GameMain.NetworkMember != null)
if (GameMain.NetworkMember != null && IsOwnCharacter(characterInfo))
{
CreateMultiplayerCharacterSettings(frame, content);
}
@@ -164,7 +177,7 @@ namespace Barotrauma
OnClicked = (button, o) =>
{
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
characterSettingsFrame!.Visible = false;
characterSettingsFrame.Visible = false;
content.Visible = true;
return true;
}
@@ -179,8 +192,7 @@ namespace Barotrauma
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);
info.DrawPortrait(batch, component.Rect.Location.ToVector2(), Vector2.Zero, component.Rect.Width, false, false);
});
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
@@ -209,20 +221,22 @@ namespace Barotrauma
if (talentsOutsideTree.Any())
{
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null);
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), nameLayout.RectTransform), style: null);
GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.55f), 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 = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont)
{
AutoScaleVertical = true
};
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)
var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.7f), 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();
@@ -231,7 +245,7 @@ namespace Barotrauma
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),
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(extraTalent.Description.Value)),
Color = GUIStyle.Green
};
}
@@ -298,10 +312,11 @@ namespace Barotrauma
TalentOption option = subTree.TalentOptionStages[i];
CreateTalentOption(subTreeLayoutGroup, subTree, i, option, info);
}
subTreeLayoutGroup.RectTransform.Resize(new Point(subTreeLayoutGroup.Rect.Width,
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)));
@@ -310,15 +325,15 @@ namespace Barotrauma
}
var specializationList = GetSpecializationList();
GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(c => c.Rect.Width), specializationList.Rect.Height));
GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height));
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUIListBox GetSpecializationList()
{
if (mainList.Content.Children.LastOrDefault() is GUIListBox specializationList)
if (mainList.Content.Children.LastOrDefault() is GUIListBox specList)
{
return specializationList;
return specList;
}
GUIListBox newSpecializationList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), mainList.Content.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
@@ -354,21 +369,24 @@ namespace Barotrauma
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;
HashSet<TalentButton> buttonsToAdd = new();
if (hasShowcase)
Dictionary<GUILayoutGroup, ImmutableHashSet<Identifier>> showCaseTalentParents = new();
Dictionary<Identifier, GUIComponent> showCaseTalentButtonsToAdd = new();
foreach (var (showCaseTalentIdentifier, talents) in talentOption.ShowCaseTalents)
{
talentOptionIdentifiers.Add(showcaseTalentIdentifier);
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")
GUIFrame showCaseFrame = new GUIFrame(new RectTransform(new Point((int)(parentSize.X / 3f * (talents.Count - 1)), parentSize.Y)), style: "GUITooltip")
{
UserData = showcaseTalentIdentifier,
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 };
GUILayoutGroup showcaseLayout = new GUILayoutGroup(new RectTransform(Vector2.One, showcaseCenterGroup.RectTransform), isHorizontal: true) { Stretch = true };
showCaseTalentParents.Add(showcaseLayout, talents);
showCaseTalentFrames.Add(showCaseFrame);
}
@@ -376,16 +394,16 @@ namespace Barotrauma
{
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab? talent)) { continue; }
bool isShowCaseTalent = hasShowcase && talentId == showcaseTalentIdentifier;
GUIComponent talentParent;
bool isShowCaseTalent = talentOption.ShowCaseTalents.ContainsKey(talentId);
GUIComponent talentParent = talentOptionLayoutGroup;
if (hasShowcase && talentId != showcaseTalentIdentifier)
foreach (var (key, value) in showCaseTalentParents)
{
talentParent = showcaseLayout;
}
else
{
talentParent = talentOptionLayoutGroup;
if (value.Contains(talentId))
{
talentParent = key;
break;
}
}
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentParent.RectTransform), style: null)
@@ -396,7 +414,7 @@ namespace Barotrauma
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),
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(talent.Description.Value)),
UserData = talent.Identifier,
PressedColor = pressedColor,
Enabled = info.Character != null,
@@ -420,20 +438,18 @@ namespace Barotrauma
return true;
}
Character? controlledCharacter = info.Character;
if (controlledCharacter is null) { return false; }
if (character 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>())
foreach (Identifier identifier in selectedTalents)
{
if (guiButton.UserData is Identifier otherTalentIdentifier && guiButton != button)
if (character.HasTalent(identifier) || identifier == talentId) { continue; }
if (talentOptionIdentifiers.Contains(identifier))
{
if (!controlledCharacter.HasTalent(otherTalentIdentifier))
{
selectedTalents.Remove(otherTalentIdentifier);
}
selectedTalents.Remove(identifier);
}
}
}
@@ -451,7 +467,7 @@ namespace Barotrauma
selectedTalents.Remove(talentIdentifier);
}
}
else if (!controlledCharacter.HasTalent(talentIdentifier))
else if (!character.HasTalent(talentIdentifier))
{
selectedTalents.Remove(talentIdentifier);
}
@@ -487,9 +503,31 @@ namespace Barotrauma
}
iconImage.Enabled = talentButton.Enabled;
if (isShowCaseTalent) { continue; }
if (isShowCaseTalent)
{
showCaseTalentButtonsToAdd.Add(talentId, iconImage);
continue;
}
talentButtons.Add(new TalentButton(talentButton, iconImage, talent));
buttonsToAdd.Add(new TalentButton(iconImage, talent));
}
foreach (TalentButton button in buttonsToAdd)
{
talentButtons.Add(button);
}
foreach (var (key, value) in showCaseTalentButtonsToAdd)
{
HashSet<TalentButton> buttons = new();
foreach (Identifier identifier in talentOption.ShowCaseTalents[key])
{
if (talentButtons.FirstOrNull(talentButton => talentButton.Identifier == identifier) is not { } button) { continue; }
buttons.Add(button);
}
talentShowCaseButtons.Add(new TalentShowCaseButton(buttons.ToImmutableHashSet(), value));
}
talentCornerIcons.Add(new TalentCornerIcon(subTree.Identifier, index, cornerIcon, talentBackground, talentBackgroundHighlight));
@@ -534,6 +572,8 @@ namespace Barotrauma
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
if (characterInfo is null) { return false; }
selectedTalents = characterInfo.GetUnlockedTalentsInTree().ToHashSet();
UpdateTalentInfo();
return true;
}
@@ -554,16 +594,15 @@ namespace Barotrauma
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
if (controlledCharacter is null) { return false; }
if (character is null) { return false; }
ApplyTalents(controlledCharacter);
ApplyTalents(character);
return true;
}
public void UpdateTalentInfo()
{
if (!(Character.Controlled is { Info: var info } character)) { return; }
if (character is null || characterInfo is null) { return; }
bool unlockedAllTalents = character.HasUnlockedAllTalents();
@@ -576,15 +615,15 @@ namespace Barotrauma
}
else
{
experienceText.Text = $"{info.ExperiencePoints - info.GetExperienceRequiredForCurrentLevel()} / {info.GetExperienceRequiredToLevelUp() - info.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = info.GetProgressTowardsNextLevel();
experienceText.Text = $"{characterInfo.ExperiencePoints - characterInfo.GetExperienceRequiredForCurrentLevel()} / {characterInfo.GetExperienceRequiredToLevelUp() - characterInfo.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = characterInfo.GetProgressTowardsNextLevel();
}
selectedTalents = CheckTalentSelection(character, selectedTalents).ToHashSet();
string pointsLeft = info.GetAvailableTalentPoints().ToString();
string pointsLeft = characterInfo.GetAvailableTalentPoints().ToString();
int talentCount = selectedTalents.Count - info.GetUnlockedTalentsInTree().Count();
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
if (unlockedAllTalents)
{
@@ -603,7 +642,7 @@ namespace Barotrauma
foreach (TalentCornerIcon cornerIcon in talentCornerIcons)
{
TalentTreeStageState state = GetTalentOptionStageState(character, cornerIcon.TalentTree, cornerIcon.Index, selectedTalents);
TalentStages state = GetTalentOptionStageState(character, cornerIcon.TalentTree, cornerIcon.Index, selectedTalents);
TalentTreeStyle style = talentStageStyles[state];
GUIComponentStyle newStyle = style.ComponentStyle;
cornerIcon.IconComponent.ApplyStyle(newStyle);
@@ -614,60 +653,94 @@ namespace Barotrauma
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;
TalentStages stage = GetTalentState(character, talentButton.Identifier, selectedTalents);
ApplyTalentIconColor(stage, talentButton.IconComponent, talentButton.Prefab.ColorOverride);
}
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;
foreach (TalentShowCaseButton showCaseTalentButton in talentShowCaseButtons)
{
TalentStages collectiveTalentStage = GetCollectiveTalentState(character, showCaseTalentButton.Buttons, selectedTalents);
ApplyTalentIconColor(collectiveTalentStage, showCaseTalentButton.IconComponent, Option<Color>.None());
}
if (skillListBox is null) { return; }
TabMenu.CreateSkillList(character, info, skillListBox);
}
TabMenu.CreateSkillList(character, characterInfo, 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)
static TalentStages GetTalentState(Character character, Identifier talentIdentifier, IReadOnlyCollection<Identifier> selectedTalents)
{
component.AddToGUIUpdateList(order: 1);
if (!component.Visible) { continue; }
if (keyboardInteracted || (mouseInteracted && !component.Rect.Contains(PlayerInput.MousePosition)))
bool unselectable = !IsViableTalentForCharacter(character, talentIdentifier, selectedTalents) || character.HasTalent(talentIdentifier);
TalentStages stage = unselectable ? Locked : Available;
if (unselectable)
{
component.Visible = false;
stage = Locked;
}
if (character.HasTalent(talentIdentifier))
{
stage = Unlocked;
}
else if (selectedTalents.Contains(talentIdentifier))
{
stage = Highlighted;
}
return stage;
}
static void ApplyTalentIconColor(TalentStages stage, GUIComponent component, Option<Color> colorOverride)
{
Color color = stage switch
{
Invalid => unselectableColor,
Locked => unselectableColor,
Unlocked => GetColorOrOverride(GUIStyle.Green, colorOverride),
Highlighted => GetColorOrOverride(GUIStyle.Orange, colorOverride),
Available => GetColorOrOverride(unselectedColor, colorOverride),
_ => throw new ArgumentOutOfRangeException(nameof(stage), stage, null)
};
component.Color = color;
component.HoverColor = Color.Lerp(color, Color.White, 0.7f);
static Color GetColorOrOverride(Color color, Option<Color> colorOverride) => colorOverride.TryUnwrap(out Color overrideColor) ? overrideColor : color;
}
// this could also be reused for setting colors for talentCornerIcons but that's for another time
static TalentStages GetCollectiveTalentState(Character character, IReadOnlyCollection<TalentButton> buttons, IReadOnlyCollection<Identifier> selectedTalents)
{
HashSet<TalentStages> talentStages = new HashSet<TalentStages>();
foreach (TalentButton button in buttons)
{
talentStages.Add(GetTalentState(character, button.Identifier, selectedTalents));
}
TalentStages collectiveStage = talentStages.Any(static stage => stage is Locked)
? Locked
: Available;
foreach (TalentStages stage in talentStages)
{
if (stage is Highlighted)
{
collectiveStage = Highlighted;
break;
}
if (stage is Unlocked)
{
collectiveStage = Unlocked;
break;
}
}
return collectiveStage;
}
}
public void Update()
{
if (Character.Controlled?.Info is not { } characterInfo || talentResetButton is null || talentApplyButton is null) { return; }
if (characterInfo is null || talentResetButton is null || talentApplyButton is null) { return; }
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
@@ -675,6 +748,58 @@ namespace Barotrauma
{
talentApplyButton.Flash(GUIStyle.Orange);
}
while (showCaseClosureQueue.TryDequeue(out Identifier identifier))
{
foreach (GUIComponent component in showCaseTalentFrames)
{
if (component.UserData is Identifier showcaseIdentifier && showcaseIdentifier == identifier)
{
component.Visible = false;
}
}
}
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)
{
if (component.UserData is not Identifier identifier) { continue; }
component.AddToGUIUpdateList(order: 1);
if (!component.Visible) { continue; }
if (keyboardInteracted || (mouseInteracted && !component.Rect.Contains(PlayerInput.MousePosition)))
{
showCaseClosureQueue.Enqueue(identifier);
}
}
}
private static bool IsOwnCharacter(CharacterInfo? info)
{
if (info is null) { return false; }
CharacterInfo? ownCharacterInfo = Character.Controlled?.Info ?? GameMain.Client?.CharacterInfo;
if (ownCharacterInfo is null) { return false; }
return info == ownCharacterInfo;
}
public static bool CanManageTalents(CharacterInfo targetInfo)
{
// in singleplayer we can do whatever we want
if (GameMain.IsSingleplayer) { return true; }
// always allow managing talents for own character
if (IsOwnCharacter(targetInfo)) { return true; }
// don't allow controlling non-bot characters
if (targetInfo.Character is not { IsBot: true }) { return false; }
// lastly check if we have the permission to do this
return GameMain.Client is { } client && client.HasPermission(ClientPermissions.ManageBotTalents);
}
}
}
@@ -278,10 +278,13 @@ namespace Barotrauma
* | upgrades | maintenance | <- 1/3rd empty space |
* |---------------------------------------------------------------------------------------------------|
*/
GUILayoutGroup leftLayout = new GUILayoutGroup(rectT(0.5f, 1, topHeaderLayout)) { RelativeSpacing = 0.05f };
GUILayoutGroup leftLayout = new GUILayoutGroup(rectT(0.4f, 1, topHeaderLayout)) { RelativeSpacing = 0.05f };
GUILayoutGroup locationLayout = new GUILayoutGroup(rectT(1, 0.5f, leftLayout), isHorizontal: true);
GUIImage submarineIcon = new GUIImage(rectT(new Point(locationLayout.Rect.Height, locationLayout.Rect.Height), locationLayout), style: "SubmarineIcon", scaleToFit: true);
new GUITextBlock(rectT(1.0f - submarineIcon.RectTransform.RelativeSize.X, 1, locationLayout), TextManager.Get("UpgradeUI.Title"), font: GUIStyle.LargeFont);
var header = new GUITextBlock(rectT(1.0f - submarineIcon.RectTransform.RelativeSize.X, 1, locationLayout), TextManager.Get("UpgradeUI.Title"), font: GUIStyle.LargeFont);
header.RectTransform.MaxSize = new Point((int)(header.TextSize.X + header.Padding.X + header.Padding.Z), int.MaxValue);
new GUITextBlock(rectT(1.0f, 1, locationLayout), TextManager.Get("UpgradeUI.AllSubmarinesInfo"), font: GUIStyle.SmallFont, wrap: true);
categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true };
GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradeTab == UpgradeTab.Upgrade };
GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs };