Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop

This commit is contained in:
EvilFactory
2025-04-10 10:37:09 -03:00
296 changed files with 8420 additions and 2945 deletions
@@ -18,17 +18,40 @@ namespace Barotrauma
public readonly ChatManager ChatManager = new ChatManager();
public bool IsSinglePlayer { get; private set; }
private bool _toggleOpen = true;
public bool ToggleOpen
{
get { return _toggleOpen; }
set
{
_toggleOpen = PreferChatBoxOpen = value;
if (value) { hideableElements.Visible = true; }
}
get => _toggleOpen;
set => SetToggleOpenState(value, setPreference: true);
}
public static ChatBox GetChatBox()
{
if (GameMain.GameSession?.GameMode is not GameMode gameMode) { return null; }
return gameMode.IsSinglePlayer ? GameMain.GameSession.CrewManager?.ChatBox : GameMain.Client?.ChatBox;
}
public static void AutoHideChatBox() => SetChatBoxOpen(false);
private void SetToggleOpenState(bool value, bool setPreference = true)
{
_toggleOpen = value;
if (setPreference)
{
PreferChatBoxOpen = value;
}
if (value) { hideableElements.Visible = true; }
}
public static void ResetChatBoxOpenState() => GetChatBox()?.ResetOpenState();
public void ResetOpenState() => SetOpen(PreferChatBoxOpen);
private static void SetChatBoxOpen(bool isOpen) => GetChatBox()?.SetOpen(isOpen);
private void SetOpen(bool value) => SetToggleOpenState(value, setPreference: false);
private float openState;
public static bool PreferChatBoxOpen = true;
@@ -199,7 +222,7 @@ namespace Barotrauma
if (channelMemPending)
{
int.TryParse(channelText.Text, out int newChannel);
radio.SetChannelMemory(index, newChannel);
SetChannelMemory(index, newChannel);
btn.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
("[index]", index.ToString()),
("[channel]", radio.GetChannelMemory(index).ToString()));
@@ -330,7 +353,7 @@ namespace Barotrauma
};
showNewMessagesButton.Visible = false;
ToggleOpen = PreferChatBoxOpen = GameSettings.CurrentConfig.ChatOpen;
SetToggleOpenState(GameSettings.CurrentConfig.ChatOpen, setPreference: true);
}
public void Toggle()
@@ -802,6 +825,15 @@ namespace Barotrauma
}
}
}
private void SetChannelMemory(int index, int channel)
{
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
radio.SetChannelMemory(index, channel);
radio.Item.CreateClientEvent(radio);
}
}
public void ApplySelectionInputs() => ApplySelectionInputs(InputBox, true, ChatKeyStates.GetChatKeyStates());
@@ -11,13 +11,14 @@ internal class DeathPrompt
{
private static CoroutineHandle? createPromptCoroutine;
private GUIFrame? deathPromptFrame;
private GUIComponent? skillPanel;
private GUIComponent? newCharacterPanel;
private GUIComponent? takeOverBotPanel;
private GUIComponent? content;
public static GUIComponent? takeOverBotPanelFrame;
private static GUIComponent? takeOverBotPanelFrame;
/// <summary>
/// Private constructor, because these should only be created using the Show method
@@ -73,11 +74,11 @@ internal class DeathPrompt
foreground.FadeIn(wait: 0, duration: 5.0f);
foreground.Pulsate(startScale: Vector2.One, Vector2.One * 0.8f, duration: 25.0f);
var frame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.3f), background.RectTransform, Anchor.Center))
deathPromptFrame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.3f), background.RectTransform, Anchor.Center))
{
UserData = this
};
frame.FadeIn(wait: 0, duration: FadeInDuration);
deathPromptFrame.FadeIn(wait: 0, duration: FadeInDuration);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.1f), background.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.2f) }, string.Empty, font: GUIStyle.LargeFont, textAlignment: Alignment.TopCenter)
{
@@ -90,7 +91,7 @@ internal class DeathPrompt
}
};
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), frame.RectTransform, Anchor.Center))
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), deathPromptFrame.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.05f
@@ -188,7 +189,7 @@ internal class DeathPrompt
{
if (takeOverBotPanel == null)
{
CreateTakeOverBotPanel(frame, this);
CreateTakeOverBotPanel(deathPromptFrame, this);
}
else
{
@@ -202,7 +203,7 @@ internal class DeathPrompt
}
else
{
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerRight.RectTransform), TextManager.Get("deathprompt.respawnnow"))
var respawnNowButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerRight.RectTransform), TextManager.Get("deathprompt.respawnnow"))
{
OnClicked = (btn, userdata) =>
{
@@ -211,7 +212,13 @@ internal class DeathPrompt
return true;
},
Enabled = GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.MidRound }
}.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
};
if (GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.BetweenRounds })
{
respawnNowButton.ToolTip = TextManager.Get("respawnnotavailable.respawnmode.betweenrounds");
}
respawnNowButton.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
}
//"info buttons" at the bottom
@@ -249,7 +256,7 @@ internal class DeathPrompt
{
if (skillPanel == null)
{
CreateSkillPanel(frame, GameMain.Client?.Character?.Info ?? GameMain.Client?.CharacterInfo);
CreateSkillPanel(deathPromptFrame, GameMain.Client?.Character?.Info ?? GameMain.Client?.CharacterInfo);
}
else
{
@@ -266,7 +273,7 @@ internal class DeathPrompt
{
if (newCharacterPanel == null)
{
CreateNewCharacterPanel(frame);
CreateNewCharacterPanel(deathPromptFrame);
}
else
{
@@ -279,15 +286,6 @@ internal class DeathPrompt
}
}
//TODO
/*new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), infoButtonContainer.RectTransform), "Respawn settings", style: "GUIButtonSmall")
{
OnClicked = (btn, userdata) =>
{
return true;
}
}.FadeIn(wait: FadeInInterval * 5, duration: FadeInDuration, alsoChildren: true);*/
this.content = background;
}
@@ -382,8 +380,10 @@ internal class DeathPrompt
{
OnClicked = (btn, userdata) =>
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(onYes: () =>
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(onYes: () =>
{
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
GameMain.NetLobbyScreen.CampaignCharacterDiscarded = false;
frame.Parent?.RemoveChild(frame);
newCharacterPanel = null;
});
@@ -465,6 +465,11 @@ internal class DeathPrompt
{
if (botList.SelectedData is CharacterInfo selectedCharacter && GameMain.Client is GameClient client)
{
if (!GetAvailableBots().Contains(selectedCharacter)) // Someone may have taken over the bot while the list was open, etc
{
CreateTakeOverBotPanel(frame, deathPrompt); // Update
return true;
}
client.SendTakeOverBotRequest(selectedCharacter);
GUIMessageBox.MessageBoxes.Remove(frame.Parent);
deathPrompt?.Close();
@@ -484,15 +489,26 @@ internal class DeathPrompt
return frame;
}
public void UpdateBotList()
{
if (deathPromptFrame != null && takeOverBotPanelFrame != null)
{
CloseBotPanel();
CreateTakeOverBotPanel(deathPromptFrame, deathPrompt: this);
}
}
private static IEnumerable<CharacterInfo> GetAvailableBots()
{
if (GameMain.GameSession?.CrewManager is { } crewManager)
{
return crewManager.GetCharacterInfos().Where(c =>
/*either an alive bot */
c is { Character.IsBot: true, Character.IsDead: false } ||
/* or a newly hired bot that hasn't spawned yet */
(c.IsNewHire && c.Character == null));
return crewManager.GetCharacterInfos(includeReserveBench: true).Where(c =>
// a bot on reserve bench
c.IsOnReserveBench ||
// an alive bot
(c.Character != null && c.Character is { IsBot: true, IsDead: false }) ||
// a newly hired bot that hasn't spawned yet
(c.Character == null && c.IsNewHire));
}
else
{
@@ -106,12 +106,35 @@ namespace Barotrauma
public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth;
public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y);
public static float RelativeVerticalAspectRatio => VerticalAspectRatio / (ReferenceResolution.Y / ReferenceResolution.X);
/// <summary>
/// Returns the difference of the current aspect ratio to the reference aspect ratio (16:9).
/// E.g. if the aspect ratio is 16:9, returns 0; if it's 4:3, returns 0.444; if the aspect ratio is 12:5, returns -0.623.
/// </summary>
public static float AspectRatioDifference
{
get
{
// ~ 1.777
float referenceAspectRatio = ReferenceResolution.X / ReferenceResolution.Y;
float aspectRatioDifference = referenceAspectRatio - HorizontalAspectRatio;
if (MathUtils.NearlyEqual(aspectRatioDifference, 0))
{
// Handle possible rounding errors, so that we can trust that this returns 0 when the aspect ratio matches the reference aspect ratio.
return 0;
}
return aspectRatioDifference;
}
}
/// <summary>
/// A horizontal scaling factor for low aspect ratios (small width relative to height)
/// </summary>
public static float AspectRatioAdjustment => HorizontalAspectRatio < 1.4f ? (1.0f - (1.4f - HorizontalAspectRatio)) : 1.0f;
public static bool IsUltrawide => HorizontalAspectRatio > 2.3f;
public static bool IsHUDScaled => GameSettings.CurrentConfig.Graphics.HUDScale > 1 || GameSettings.CurrentConfig.Graphics.InventoryScale > 1;
public static int UIWidth
{
@@ -625,9 +648,13 @@ namespace Barotrauma
DrawMessages(spriteBatch, cam);
if (MouseOn != null && !MouseOn.ToolTip.IsNullOrWhiteSpace())
{
MouseOn.DrawToolTip(spriteBatch);
if (MouseOn != null)
{
if (!MouseOn.ToolTip.IsNullOrWhiteSpace())
{
MouseOn.DrawToolTip(spriteBatch);
}
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
}
if (SubEditorScreen.IsSubEditor())
@@ -884,6 +911,11 @@ namespace Barotrauma
PauseMenu.AddToGUIUpdateList();
}
foreach (var openAccordion in GUIComponent.OpenAccordionPopups)
{
openAccordion.AddToGUIUpdateList(order: 1);
}
SocialOverlay.Instance?.AddToGuiUpdateList();
GUIContextMenu.AddActiveToGUIUpdateList();
@@ -2495,7 +2527,12 @@ namespace Barotrauma
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("bugreportbutton") + $" (v{GameMain.Version})",
OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; }
OnClicked = (btn, userdata) =>
{
if (PauseMenuOpen) { TogglePauseMenu(); }
GameMain.Instance.ShowBugReporter();
return true;
}
};
CreateButton("PauseMenuResume", buttonContainer, null);
@@ -2531,23 +2568,37 @@ namespace Barotrauma
GameMain.GameSession?.EndRound("");
});
}
else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound))
else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null)
{
bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsFriendlyOutpostLevel();
if (canSave)
//server owner (host) can't return to the lobby without ending the round for everyone
if (!GameMain.Client.IsServerOwner)
{
CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
{
GameMain.Client?.RequestRoundEnd(save: true);
});
CreateButton("ReturnToServerlobby", buttonContainer,
verificationTextTag: "PauseMenuReturnToServerLobbyVerificationSelf",
action: () =>
{
GameMain.Client?.EndRoundForSelf();
});
}
CreateButton(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby" : "EndRound", buttonContainer,
verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
action: () =>
if (GameMain.Client.HasPermission(ClientPermissions.ManageRound))
{
bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsFriendlyOutpostLevel();
if (canSave)
{
GameMain.Client?.RequestRoundEnd(save: false);
});
CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
{
GameMain.Client?.RequestEndRound(save: true);
}, color: GUIStyle.Red);
}
CreateButton("EndRound", buttonContainer,
verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
action: () =>
{
GameMain.Client?.RequestEndRound(save: false);
}, color: GUIStyle.Red);
}
}
}
@@ -2575,9 +2626,9 @@ namespace Barotrauma
}
void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null)
void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null, Color? color = null)
{
new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), TextManager.Get(textTag))
var button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), TextManager.Get(textTag))
{
OnClicked = (btn, userData) =>
{
@@ -2593,25 +2644,29 @@ namespace Barotrauma
return true;
}
};
if (color.HasValue)
{
button.Color = color.Value;
}
}
void CreateVerificationPrompt(string textTag, Action confirmAction)
}
public static void CreateVerificationPrompt(string textTag, Action confirmAction)
{
var msgBox = new GUIMessageBox("", TextManager.Get(textTag),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
var msgBox = new GUIMessageBox("", TextManager.Get(textTag),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "verificationprompt",
DrawOnTop = true
};
msgBox.Buttons[0].OnClicked = (_, __) =>
{
PauseMenuOpen = false;
confirmAction?.Invoke();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
UserData = "verificationprompt",
DrawOnTop = true
};
msgBox.Buttons[0].OnClicked = (_, __) =>
{
PauseMenuOpen = false;
confirmAction?.Invoke();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
private static bool TogglePauseMenu(GUIButton button, object obj)
@@ -2697,12 +2752,6 @@ namespace Barotrauma
}
}
public static bool IsFourByThree()
{
float aspectRatio = HorizontalAspectRatio;
return aspectRatio > 1.3f && aspectRatio < 1.4f;
}
public static void SetSavingIndicatorState(bool enabled)
{
if (enabled)
@@ -9,6 +9,7 @@ using Barotrauma.IO;
using RestSharp;
using System.Net;
using Barotrauma.Steam;
using Steamworks;
namespace Barotrauma
{
@@ -158,6 +159,12 @@ namespace Barotrauma
public Action<GUIComponent> OnAddedToGUIUpdateList;
/// <summary>
/// Triggers when a tooltip should be draw on the component.
/// Note that the callback triggers even if the item has no tooltip (which can be useful for e.g. only contructing the tooltip when needed).
/// </summary>
public Action<GUIComponent> OnDrawToolTip;
public enum ComponentState { None, Hover, Pressed, Selected, HoverSelected };
protected Alignment alignment;
@@ -1120,7 +1127,7 @@ namespace Barotrauma
component = LoadGUIImage(element, parent);
break;
case "accordion":
return LoadAccordion(element, parent);
return LoadAccordion(element, parent, openOnTop: element.GetAttributeBool("openontop", false));
case "gridtext":
LoadGridText(element, parent);
return null;
@@ -1138,6 +1145,21 @@ namespace Barotrauma
FromXML(subElement, component is GUIListBox listBox ? listBox.Content.RectTransform : component.RectTransform);
}
component.toolTip = element.GetAttributeString("tooltip", string.Empty);
GUITextBlock textBlock = component as GUITextBlock ?? (component as GUIButton)?.TextBlock;
if (textBlock != null)
{
if (element.GetAttributeBool("autoscalevertical", false))
{
textBlock.AutoScaleVertical = true;
}
if (element.GetAttributeBool("autoscalehorizontal", false))
{
textBlock.AutoScaleHorizontal = true;
}
}
if (element.GetAttributeBool("resizetofitchildren", false))
{
Vector2 relativeResizeScale = element.GetAttributeVector2("relativeresizescale", Vector2.One);
@@ -1180,7 +1202,8 @@ namespace Barotrauma
{
foreach (XAttribute attribute in element.Attributes())
{
switch (attribute.Name.ToString().ToLowerInvariant())
string conditionName = attribute.Name.ToString().ToLowerInvariant();
switch (conditionName)
{
case "language":
var languages = element.GetAttributeIdentifierArray(attribute.Name.ToString(), Array.Empty<Identifier>())
@@ -1199,6 +1222,10 @@ namespace Barotrauma
var maxVersion = new Version(attribute.Value);
if (GameMain.Version > maxVersion) { return false; }
break;
case "identifierdismissed":
Identifier identifier = element.GetAttributeIdentifier(attribute.Name.ToString(), Identifier.Empty);
if (MainMenuScreen.DismissedNotifications.Contains(identifier)) { return false; }
break;
case "buildconfiguration":
switch (attribute.Value.ToString().ToLowerInvariant())
{
@@ -1222,6 +1249,20 @@ namespace Barotrauma
#endif
}
return false;
case "mingamelaunches":
if (int.TryParse(attribute.Value, out int minLaunches))
{
return SteamManager.GetStatInt(AchievementStat.GameLaunchCount) > minLaunches;
}
return false;
case "appsubscribed":
case "appnotsubscribed":
if (SteamManager.IsInitialized &&
int.TryParse(attribute.Value, out int appId))
{
return SteamApps.IsSubscribedToApp(appId) == (conditionName == "appsubscribed");
}
return false;
}
}
@@ -1262,12 +1303,18 @@ namespace Barotrauma
private static GUIButton LoadLink(XElement element, RectTransform parent)
{
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
var button = LoadGUIButton(element, parent);
string url = element.GetAttributeString("url", "");
button.OnClicked = (btn, userdata) =>
{
try
{
if (!identifier.IsEmpty)
{
MainMenuScreen.AddDismissedNotification(identifier);
}
if (SteamManager.IsInitialized)
{
SteamManager.OverlayCustomUrl(url);
@@ -1324,6 +1371,8 @@ namespace Barotrauma
private static GUIButton LoadGUIButton(XElement element, RectTransform parent)
{
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
string style = element.GetAttributeString("style", "");
if (style == "null") { style = null; }
@@ -1335,10 +1384,19 @@ namespace Barotrauma
element.GetAttributeString("text", "");
text = text.Replace(@"\n", "\n");
return new GUIButton(RectTransform.Load(element, parent),
var button = new GUIButton(RectTransform.Load(element, parent),
text: text,
textAlignment: textAlignment,
style: style);
button.OnClicked = (btn, userdata) =>
{
if (!identifier.IsEmpty)
{
MainMenuScreen.AddDismissedNotification(identifier);
}
return true;
};
return button;
}
private static GUIListBox LoadGUIListBox(XElement element, RectTransform parent)
@@ -1391,11 +1449,14 @@ namespace Barotrauma
{
sprite = new Sprite(element);
}
return new GUIImage(RectTransform.Load(element, parent), sprite, scaleToFit: true);
var scaleToFit = element.GetAttributeEnum("scaletofit", GUIImage.ScalingMode.ScaleToFitSmallestExtent);
return new GUIImage(RectTransform.Load(element, parent), sprite, scaleToFit: scaleToFit);
}
private static GUIButton LoadAccordion(ContentXElement element, RectTransform parent)
public static readonly List<GUIComponent> OpenAccordionPopups = new List<GUIComponent>();
/// <param name="openOnTop">Should the contents of the accordion be forced to open on top of other UI elements?</param>
private static GUIButton LoadAccordion(ContentXElement element, RectTransform parent, bool openOnTop)
{
var button = LoadGUIButton(element, parent);
List<GUIComponent> content = new List<GUIComponent>();
@@ -1407,6 +1468,7 @@ namespace Barotrauma
contentElement.Visible = false;
contentElement.IgnoreLayoutGroups = true;
content.Add(contentElement);
contentElement.UserData = (contentElement.RectTransform.Anchor, contentElement.RectTransform.Pivot);
}
}
button.OnClicked = (btn, userdata) =>
@@ -1414,8 +1476,32 @@ namespace Barotrauma
bool visible = content.FirstOrDefault()?.Visible ?? true;
foreach (GUIComponent contentElement in content)
{
contentElement.Visible = !visible;
contentElement.IgnoreLayoutGroups = !contentElement.Visible;
if (openOnTop)
{
contentElement.rectTransform.Parent = null;
//the element is drawn in screen space over anything (no longer a child of the original parent),
//we need to calculate the screen space position manually
contentElement.rectTransform.SetPosition(Anchor.TopLeft);
(Anchor anchor, Pivot pivot) = ((Anchor anchor, Pivot pivot))contentElement.UserData;
contentElement.rectTransform.ScreenSpaceOffset =
RectTransform.CalculateAnchorPoint(anchor, button.Rect) +
RectTransform.CalculatePivotOffset(pivot, contentElement.Rect.Size);
contentElement.Visible = true;
if (OpenAccordionPopups.Contains(contentElement))
{
OpenAccordionPopups.Remove(contentElement);
}
else
{
OpenAccordionPopups.Clear();
OpenAccordionPopups.Add(contentElement);
}
}
else
{
contentElement.Visible = !visible;
contentElement.IgnoreLayoutGroups = !contentElement.Visible;
}
}
if (button.Parent is GUILayoutGroup layoutGroup)
{
@@ -2,7 +2,6 @@
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Barotrauma
@@ -14,6 +13,24 @@ namespace Barotrauma
private static bool loadingTextures;
public enum ScalingMode
{
/// <summary>
/// No automatic scaling, the image is drawn using <see cref="Scale"/>
/// </summary>
None,
/// <summary>
/// Automatically scales the image so it fits the smallest extent of the component
/// (leaving empty space around the image if its aspect ratio is different than that of the GUIImage)
/// </summary>
ScaleToFitSmallestExtent,
/// <summary>
/// Automatically scales the image so it fits the largest extent of the component,
/// cutting out the parts that go outside the bounds of the component.
/// </summary>
ScaleToFitLargestExtent,
}
public static bool LoadingTextures
{
get
@@ -30,7 +47,7 @@ namespace Barotrauma
private bool crop;
private readonly bool scaleToFit;
private readonly ScalingMode scaleToFit;
private bool lazyLoaded, loading;
@@ -89,7 +106,7 @@ namespace Barotrauma
sprite = value;
sourceRect = value == null ? Rectangle.Empty : value.SourceRect;
origin = value == null ? Vector2.Zero : value.size / 2;
if (scaleToFit) { RecalculateScale(); }
if (scaleToFit != ScalingMode.None) { RecalculateScale(); }
}
}
@@ -97,17 +114,27 @@ namespace Barotrauma
public ComponentState? OverrideState = null;
public GUIImage(RectTransform rectT, string style, bool scaleToFit = false)
public GUIImage(RectTransform rectT, string style, bool scaleToFit)
: this(rectT, null, null, scaleToFit ? ScalingMode.ScaleToFitSmallestExtent : ScalingMode.None, style)
{
}
public GUIImage(RectTransform rectT, string style, ScalingMode scaleToFit = ScalingMode.None)
: this(rectT, null, null, scaleToFit, style)
{
}
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, bool scaleToFit = false)
public GUIImage(RectTransform rectT, Sprite sprite, bool scaleToFit, Rectangle? sourceRect = null)
: this(rectT, sprite, sourceRect, scaleToFit ? ScalingMode.ScaleToFitSmallestExtent : ScalingMode.None, null)
{
}
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, ScalingMode scaleToFit = ScalingMode.None)
: this(rectT, sprite, sourceRect, scaleToFit, null)
{
}
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT)
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, ScalingMode scaleToFit, string style) : base(style, rectT)
{
this.scaleToFit = scaleToFit;
Sprite = sprite;
@@ -123,7 +150,7 @@ namespace Barotrauma
{
color = hoverColor = selectedColor = pressedColor = disabledColor = Color.White;
}
if (!scaleToFit)
if (scaleToFit == ScalingMode.None)
{
Scale = 1.0f;
}
@@ -176,9 +203,11 @@ namespace Barotrauma
Color currentColor = GetColor(State);
if (BlendState != null)
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (BlendState != null || scaleToFit == ScalingMode.ScaleToFitLargestExtent)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Rect);
spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
@@ -205,9 +234,10 @@ namespace Barotrauma
Scale, SpriteEffects, 0.0f);
}
if (BlendState != null)
if (BlendState != null || scaleToFit == ScalingMode.ScaleToFitLargestExtent)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
}
@@ -219,9 +249,18 @@ namespace Barotrauma
sourceRect = sprite.SourceRect;
}
Scale = sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0 ?
1.0f :
Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
if (sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0)
{
Scale = 1.0f;
}
else if (scaleToFit == ScalingMode.ScaleToFitLargestExtent)
{
Scale = Math.Max(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
}
else
{
Scale = Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
}
}
private async Task<bool> LoadTextureAsync()
@@ -1030,7 +1030,7 @@ namespace Barotrauma
while (index < Content.CountChildren)
{
GUIComponent child = Content.GetChild(index);
if (child.Visible)
if (child.Visible && child.CanBeFocused)
{
Select(index, force, GetAutoScroll(!SmoothScroll && autoScroll == AutoScroll.Enabled), takeKeyBoardFocus, playSelectSound);
if (SmoothScroll)
@@ -1049,7 +1049,7 @@ namespace Barotrauma
while (index >= 0)
{
GUIComponent child = Content.GetChild(index);
if (child.Visible)
if (child.Visible && child.CanBeFocused)
{
Select(index, force, GetAutoScroll(!SmoothScroll && autoScroll == AutoScroll.Enabled), takeKeyBoardFocus, playSelectSound);
if (SmoothScroll)
@@ -289,7 +289,7 @@ namespace Barotrauma
GUIStyle.Apply(Text, "", this);
Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height);
new Point(Text.Rect.Width, Math.Min(Text.Rect.Height, GameMain.GraphicsHeight));
Text.RectTransform.IsFixedSize = true;
if (headerText.IsNullOrWhiteSpace())
{
@@ -38,6 +38,8 @@ namespace Barotrauma
private static bool ReplacingPermanentlyDeadCharacter =>
GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath, IronmanMode: false } &&
GameMain.Client?.CharacterInfo is { PermanentlyDead: true };
private static bool ReserveBenchEnabled => GameMain.GameSession?.Campaign is MultiPlayerCampaign;
private bool hadPermissionToHire;
private static bool HasPermissionToHire => ReplacingPermanentlyDeadCharacter ?
@@ -277,13 +279,42 @@ namespace Barotrauma
}
else
{
PendingHires?.ForEach(ci => AddPendingHire(ci));
PendingHires?.ForEach(ci => AddPendingHire(ci, createNetworkMessage: false));
}
SetTotalHireCost();
}
UpdateCrew();
}
/// <summary>
/// This will simply update each of the HR view lists (hireables, pending hires, and crew) from the most up to date information.
/// It is a sane version of UpdateLocationView that won't break things even if used outside of whatever arbitrary conditions that one was made for.
/// </summary>
public void RefreshHRView()
{
if (campaign?.CurrentLocation is not Location currentLocation)
{
return;
}
if (characterPreviewFrame != null)
{
characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame);
characterPreviewFrame = null;
}
UpdateHireables(currentLocation);
if (pendingList != null)
{
pendingList.Content.ClearChildren();
PendingHires?.ForEach(ci => AddPendingHire(ci, checkCrewSizeLimit: false, createNetworkMessage: false)); // don't check limits here, just display the data as it is
SetTotalHireCost();
}
UpdateCrew();
}
public void UpdateHireables()
{
UpdateHireables(campaign?.CurrentLocation);
@@ -329,10 +360,11 @@ namespace Barotrauma
public void UpdateCrew()
{
crewList.Content.Children.ToList().ForEach(c => crewList.Content.RemoveChild(c));
foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos())
foreach (CharacterInfo ci in GameMain.GameSession.CrewManager.GetCharacterInfos(includeReserveBench: true))
{
if (c == null || !((c.Character?.IsBot ?? true) || campaign is SinglePlayerCampaign)) { continue; }
CreateCharacterFrame(c, crewList);
// CrewManager is used to store info on all characters including players, but we only want bots in HR
if (ci.Character != null && (ci.Character.IsRemotePlayer || !ci.Character.IsBot)) { continue; }
CreateCharacterFrame(ci, crewList);
}
SortCharacters(crewList, SortingMethod.JobAsc);
crewList.UpdateScrollBarSize();
@@ -369,6 +401,10 @@ namespace Barotrauma
((InfoSkill)x.GUIComponent.UserData).SkillLevel.CompareTo(((InfoSkill)y.GUIComponent.UserData).SkillLevel));
if (sortingMethod == SortingMethod.SkillDesc) { list.Content.RectTransform.ReverseChildren(); }
}
// Always apply this in the end to group by reserve bench status (does nothing if there are no reserve benched bots)
list.Content.RectTransform.SortChildren((x, y) =>
((InfoSkill)x.GUIComponent.UserData).CharacterInfo.BotStatus.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.BotStatus));
int? CompareReputationRequirement(GUIComponent c1, GUIComponent c2)
{
@@ -401,6 +437,8 @@ namespace Barotrauma
public GUIComponent CreateCharacterFrame(CharacterInfo characterInfo, GUIListBox listBox, bool hideSalary = false)
{
string characterName = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
Skill skill = null;
Color? jobColor = null;
if (characterInfo.Job != null)
@@ -415,6 +453,7 @@ namespace Barotrauma
};
GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1,
Stretch = true
};
@@ -428,13 +467,15 @@ namespace Barotrauma
GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform)) { CanBeFocused = false };
GUILayoutGroup nameGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { CanBeFocused = false };
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameGroup.RectTransform),
listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name,
characterName,
textColor: jobColor, textAlignment: Alignment.BottomLeft)
{
CanBeFocused = false
};
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
const float smallColumnWidth = 0.6f / 3;
const float skillColumnWidth = smallColumnWidth * 0.7f;
const float buttonWidth = 0.12f;
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform),
characterInfo.Title ?? characterInfo.Job.Name, textColor: Color.White, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft)
{
@@ -449,33 +490,28 @@ namespace Barotrauma
}
}
var fullJobText = jobBlock.Text;
jobBlock.Text = ToolBox.LimitString(fullJobText, jobBlock.Font, jobBlock.Rect.Width);
if (jobBlock.Text != fullJobText)
{
jobBlock.ToolTip = fullJobText;
jobBlock.CanBeFocused = true;
}
float width = 0.6f / 3;
if (characterInfo.Job != null && skill != null)
{
GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(width, 0.6f), mainGroup.RectTransform), isHorizontal: true);
GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(skillColumnWidth, 0.6f), mainGroup.RectTransform), isHorizontal: true);
float iconWidth = (float)skillGroup.Rect.Height / skillGroup.Rect.Width;
new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 1.0f), skillGroup.RectTransform), ((int)skill.Level).ToString(),
textAlignment: Alignment.CenterRight)
{
Padding = Vector4.Zero,
CanBeFocused = false
};
GUIImage skillIcon = new GUIImage(new RectTransform(Vector2.One, skillGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), skill.Icon, scaleToFit: true)
{
CanBeFocused = false
};
if (jobColor.HasValue) { skillIcon.Color = jobColor.Value; }
new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 1.0f), skillGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false
};
}
if (!hideSalary)
{
if (listBox != crewList)
{
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
new GUITextBlock(new RectTransform(new Vector2(smallColumnWidth, 1.0f), mainGroup.RectTransform),
TextManager.FormatCurrency(ReplacingPermanentlyDeadCharacter ? campaign.NewCharacterCost(characterInfo) : HireManager.GetSalaryFor(characterInfo)),
textAlignment: Alignment.Center)
{
@@ -485,19 +521,24 @@ namespace Barotrauma
else
{
// Just a bit of padding to make list layouts similar
new GUIFrame(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
new GUIFrame(new RectTransform(new Vector2(smallColumnWidth, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
}
}
if (listBox == hireableList)
{
var hireButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
var hireButton = new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
{
ToolTip = TextManager.Get("hirebutton"),
ToolTip = TextManager.Get(ReserveBenchEnabled ? "hirebutton.crew" : "hirebutton"),
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = CanHire(characterInfo) && !ReplacingPermanentlyDeadCharacter,
OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
OnClicked = (b, o) =>
{
var currentCharacterInfo = (CharacterInfo)o;
currentCharacterInfo.BotStatus = BotStatus.PendingHireToActiveService;
return AddPendingHire(currentCharacterInfo);
}
};
hireButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
{
@@ -505,7 +546,7 @@ namespace Barotrauma
{
return;
}
if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
if (PendingHires.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
{
if (btn.Enabled)
{
@@ -523,7 +564,7 @@ namespace Barotrauma
if (ReplacingPermanentlyDeadCharacter)
{
bool canHire = CanHire(characterInfo) && campaign.CanAffordNewCharacter(characterInfo);
var takeoverButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementTakeControlButton")
var takeoverButton = new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementTakeControlButton")
{
ToolTip = canHire ? TextManager.Get("hireandtakecontrol") : TextManager.Get("hireandtakecontroldisabled"),
ClickSound = GUISoundType.ConfirmTransaction,
@@ -554,25 +595,90 @@ namespace Barotrauma
btn.Enabled = canHireCurrently;
};
}
if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
{
var hireToReserveBenchButton = new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddAsReserveButton")
{
ToolTip = TextManager.Get("hirebutton.reservebench"),
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = CanHire(characterInfo),
OnClicked = (b, o) =>
{
var currentCharacterInfo = (CharacterInfo)o;
currentCharacterInfo.BotStatus = BotStatus.PendingHireToReserveBench;
return AddPendingHire(currentCharacterInfo, checkCrewSizeLimit: false);
}
};
hireToReserveBenchButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
{
btn.Visible = ReserveBenchEnabled;
btn.Enabled = CanHire(characterInfo) && !ReplacingPermanentlyDeadCharacter;
};
}
}
else if (listBox == pendingList)
{
new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton")
if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
{
new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform),
style: characterInfo.BotStatus == BotStatus.PendingHireToActiveService ? "CrewManagementReserveBenchButtonActive" : "CrewManagementReserveBenchButtonReserve")
{
UserData = characterInfo,
ToolTip = TextManager.Get(characterInfo.BotStatus == BotStatus.PendingHireToActiveService ? "ReserveBenchTogglePendingHire.Active" : "ReserveBenchTogglePendingHire.Reserve"),
Enabled = CanHire(characterInfo) && (characterInfo.BotStatus == BotStatus.PendingHireToActiveService || !ActiveServiceFull()), // note that this is a toggle
OnClicked = (btn, obj) =>
{
SelectCharacter(null, null, null);
var currentCharacterInfo = (CharacterInfo)obj;
GameMain.Client?.ToggleReserveBench(currentCharacterInfo, pendingHire: true);
return true;
}
};
}
new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton")
{
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = CanHire(characterInfo),
Enabled = CanHire(characterInfo), // =just check user's rights
OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo)
};
}
else if (listBox == crewList && campaign != null)
{
var currentCrew = GameMain.GameSession.CrewManager.GetCharacterInfos();
new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementFireButton")
if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
{
new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform),
style: characterInfo.BotStatus == BotStatus.ActiveService ? "CrewManagementReserveBenchButtonActive" : "CrewManagementReserveBenchButtonReserve")
{
UserData = characterInfo,
ToolTip = TextManager.Get(characterInfo.BotStatus == BotStatus.ActiveService ? "ReserveBenchToggle.Active" : "ReserveBenchToggle.Reserve"),
Enabled = CanHire(characterInfo) && (characterInfo.BotStatus == BotStatus.ActiveService || !ActiveServiceFull()), // note that this is a toggle
OnClicked = (btn, obj) =>
{
SelectCharacter(null, null, null);
var currentCharacterInfo = (CharacterInfo)obj;
if (currentCharacterInfo.BotStatus == BotStatus.ActiveService && // switching to reserve bench
characterInfo.Character != null) // may not have a Character to remove if not spawned this round
{
GameMain.GameSession.CrewManager.RemoveCharacter(characterInfo.Character, removeInfo: true, resetCrewListIndex: true);
}
GameMain.Client?.ToggleReserveBench(currentCharacterInfo); // update changes to server
return true;
}
};
}
var cm = GameMain.GameSession.CrewManager;
// Can't fire if there's only one character in active service
var fireButtonEnabled = HasPermissionToHire && (characterInfo.IsOnReserveBench ||
(cm.GetCharacterInfos().Contains(characterInfo) && cm.GetCharacterInfos().Count() > 1));
new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementFireButton")
{
UserData = characterInfo,
//can't fire if there's only one character in the crew
Enabled = currentCrew.Contains(characterInfo) && currentCrew.Count() > 1 && HasPermissionToHire,
Enabled = fireButtonEnabled,
OnClicked = (btn, obj) =>
{
var confirmDialog = new GUIMessageBox(
@@ -587,11 +693,25 @@ namespace Barotrauma
}
};
}
else
{
if (ReserveBenchEnabled && characterInfo.IsOnReserveBench) // Applies to unspecified listings like the death prompt and the bot list after permadeath
{
new GUIImage(new RectTransform(new Vector2(smallColumnWidth / 2, 0.6f), mainGroup.RectTransform), style: "CrewManagementReserveBenchIconReserve")
{
ToolTip = TextManager.Get("ReserveBenchStatus.Reserve.WillSpawn")
};
}
else
{
new GUILayoutGroup(new RectTransform(new Vector2(smallColumnWidth / 2, 0.6f), mainGroup.RectTransform)) { CanBeFocused = false };
}
}
if (listBox == pendingList || listBox == crewList)
{
nameBlock.RectTransform.Resize(new Point(nameBlock.Rect.Width - nameBlock.Rect.Height, nameBlock.Rect.Height));
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.Text = ToolBox.LimitString(characterName, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.RectTransform.Resize(new Point((int)(nameBlock.Padding.X + nameBlock.TextSize.X + nameBlock.Padding.Z), nameBlock.Rect.Height));
Point size = new Point((int)(0.7f * nameBlock.Rect.Height));
new GUIImage(new RectTransform(size, nameGroup.RectTransform), "EditIcon") { CanBeFocused = false };
@@ -605,6 +725,16 @@ namespace Barotrauma
};
}
//recalculate everything and truncate texts if needed
mainGroup.Recalculate();
nameBlock.Text = ToolBox.LimitString(characterName, nameBlock.Font, nameBlock.Rect.Width);
jobBlock.Text = ToolBox.LimitString(fullJobText, jobBlock.Font, jobBlock.Rect.Width);
if (jobBlock.Text != fullJobText)
{
jobBlock.ToolTip = fullJobText;
jobBlock.CanBeFocused = true;
}
bool CanHire(CharacterInfo thisCharacterInfo)
{
if (!HasPermissionToHire) { return false; }
@@ -613,6 +743,15 @@ namespace Barotrauma
return frame;
}
/// <summary>
/// Is there (going to be) no space left in active service?
/// </summary>
private bool ActiveServiceFull()
{
int pendingHireCount = PendingHires?.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) ?? 0;
return pendingHireCount + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize;
}
private bool EnoughReputationToHire(CharacterInfo characterInfo)
{
@@ -656,7 +795,7 @@ namespace Barotrauma
GUILayoutGroup infoLabelGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
float blockHeight = 1.0f / 4;
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"), textColor: GUIStyle.TextColorBright);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), "");
string name = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
nameBlock.Text = ToolBox.LimitString(name, nameBlock.Font, nameBlock.Rect.Width);
@@ -664,17 +803,17 @@ namespace Barotrauma
if (characterInfo.HasSpecifierTags)
{
var menuCategoryVar = characterInfo.Prefab.MenuCategoryVar;
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(menuCategoryVar));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(menuCategoryVar), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get(characterInfo.ReplaceVars($"[{menuCategoryVar}]")));
}
if (characterInfo.Job is Job job)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("tabmenu.job"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("tabmenu.job"), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), job.Name);
}
if (characterInfo.PersonalityTrait is NPCPersonalityTrait trait)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait"), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), trait.DisplayName);
}
infoLabelGroup.Recalculate();
@@ -727,9 +866,9 @@ namespace Barotrauma
return true;
}
private bool AddPendingHire(CharacterInfo characterInfo, bool createNetworkMessage = true)
private bool AddPendingHire(CharacterInfo characterInfo, bool checkCrewSizeLimit = true, bool createNetworkMessage = true)
{
if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
if (checkCrewSizeLimit && characterInfo.BotStatus == BotStatus.PendingHireToActiveService && ActiveServiceFull())
{
return false;
}
@@ -792,7 +931,7 @@ namespace Barotrauma
List<CharacterInfo> nonDuplicateHires = new List<CharacterInfo>();
hires.ForEach(hireInfo =>
{
if (campaign.CrewManager.GetCharacterInfos().None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
if (campaign.CrewManager.GetCharacterInfos(includeReserveBench: true).None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
{
nonDuplicateHires.Add(hireInfo);
}
@@ -806,12 +945,21 @@ namespace Barotrauma
if (!campaign.CanAfford(total)) { return false; }
}
bool atLeastOneHired = false;
bool atLeastOneHiredToActiveDuty = false;
bool atLeastOneHiredToReserveBench = false;
foreach (CharacterInfo ci in nonDuplicateHires)
{
bool toReserveBench = ci.BotStatus == BotStatus.PendingHireToReserveBench;
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci, takeMoney: takeMoney))
{
atLeastOneHired = true;
if (toReserveBench)
{
atLeastOneHiredToReserveBench = true;
}
else
{
atLeastOneHiredToActiveDuty = true;
}
}
else
{
@@ -819,15 +967,27 @@ namespace Barotrauma
}
}
if (atLeastOneHired)
if (atLeastOneHiredToActiveDuty || atLeastOneHiredToReserveBench)
{
UpdateLocationView(campaign.Map.CurrentLocation, true);
SelectCharacter(null, null, null);
if (createNotification)
{
LocalizedString msg = string.Empty;
if (atLeastOneHiredToActiveDuty)
{
msg += TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName);
}
if (atLeastOneHiredToReserveBench)
{
if (!msg.IsNullOrEmpty()) { msg += "\n\n"; }
msg += GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath, IronmanMode: false } ?
TextManager.Get("crewhiredmessage.reservebench.permadeath") :
TextManager.Get( "crewhiredmessage.reservebench");
}
var dialog = new GUIMessageBox(
TextManager.Get("newcrewmembers"),
TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName),
TextManager.Get("newcrewmembers"), msg,
new LocalizedString[] { TextManager.Get("Ok") });
dialog.Buttons[0].OnClicked += dialog.Close;
}
@@ -1034,7 +1194,7 @@ namespace Barotrauma
}
}
public void SetPendingHires(List<UInt16> characterInfos, Location location)
public void SetPendingHires(List<UInt16> characterInfos, bool[] characterInfoReserveBenchStatuses, Location location, bool checkCrewSizeLimit)
{
List<CharacterInfo> oldHires = PendingHires.ToList();
foreach (CharacterInfo pendingHire in oldHires)
@@ -1042,18 +1202,25 @@ namespace Barotrauma
RemovePendingHire(pendingHire, createNetworkMessage: false);
}
PendingHires.Clear();
int i = 0;
foreach (UInt16 identifier in characterInfos)
{
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.ID == identifier);
if (match != null)
{
AddPendingHire(match, createNetworkMessage: false);
match.BotStatus = characterInfoReserveBenchStatuses[i] ? BotStatus.PendingHireToReserveBench : BotStatus.PendingHireToActiveService;
AddPendingHire(match, checkCrewSizeLimit: checkCrewSizeLimit, createNetworkMessage: false);
if (!PendingHires.Contains(match))
{
DebugConsole.ThrowError("Failed to add a pending hire");
}
System.Diagnostics.Debug.Assert(PendingHires.Contains(match));
}
else
{
DebugConsole.ThrowError("Received a hire that doesn't exist.");
}
i++;
}
}
@@ -1064,7 +1231,7 @@ namespace Barotrauma
/// <param name="renameCharacter">When not null tell the server to rename this character. Item1 is the character to rename, Item2 is the new name, Item3 indicates whether the renamed character is already a part of the crew.</param>
/// <param name="firedCharacter">When not null tell the server to fire this character</param>
/// <param name="validateHires">When set to true will tell the server to validate pending hires</param>
public void SendCrewState(bool updatePending, (CharacterInfo info, string newName) renameCharacter = default, CharacterInfo firedCharacter = null, bool validateHires = false)
public void SendCrewState(bool updatePending = false, (CharacterInfo info, string newName) renameCharacter = default, CharacterInfo firedCharacter = null, bool validateHires = false)
{
if (campaign is MultiPlayerCampaign)
{
@@ -1078,6 +1245,7 @@ namespace Barotrauma
foreach (CharacterInfo pendingHire in PendingHires)
{
msg.WriteUInt16(pendingHire.ID);
msg.WriteBoolean(pendingHire.BotStatus == BotStatus.PendingHireToReserveBench);
}
}
@@ -1089,7 +1257,9 @@ namespace Barotrauma
{
msg.WriteUInt16(renameCharacter.info.ID);
msg.WriteString(renameCharacter.newName);
bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.ID == renameCharacter.info.ID) ?? false;
bool existingCrewMember =
campaign.CrewManager is CrewManager crewManager &&
crewManager.GetCharacterInfos(includeReserveBench: true).Any(ci => ci.ID == renameCharacter.info.ID);
msg.WriteBoolean(existingCrewMember);
}
@@ -25,8 +25,6 @@ namespace Barotrauma
private Video currSplashScreen;
private DateTime videoStartTime;
private bool mirrorBackground;
public struct PendingSplashScreen
{
public string Filename;
@@ -108,8 +106,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea,
spriteEffects: mirrorBackground ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
double noiseT = Timing.TotalTime * 0.02f;
@@ -386,7 +383,6 @@ namespace Barotrauma
{
currentBackgroundTexture = missions.Where(m => m.Prefab.HasPortraits).First().Prefab.GetPortrait(Rand.Int(int.MaxValue));
}
mirrorBackground = Rand.Range(0.0f, 1.0f) < 0.5f;
while (!drawn)
{
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.Xna.Framework.Input;
using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
namespace Barotrauma
@@ -1573,15 +1574,24 @@ namespace Barotrauma
if (locationHasDealOnItem)
{
var relativeWidth = (0.9f * nameAndQuantityFrame.Rect.Height) / nameAndQuantityFrame.Rect.Width;
Vector2 dealIconSize = new Vector2(relativeWidth, 0.9f) * 0.5f;
var dealIcon = new GUIImage(
new RectTransform(new Vector2(relativeWidth, 0.9f), nameAndQuantityFrame.RectTransform, anchor: Anchor.CenterLeft)
new RectTransform(dealIconSize, nameAndQuantityFrame.RectTransform, anchor: Anchor.CenterRight)
{
AbsoluteOffset = new Point((int)nameBlock.Padding.X, 0)
},
"StoreDealIcon", scaleToFit: true)
{
CanBeFocused = false
CanBeFocused = false,
UserData = "StoreDealIcon"
};
var dealIconColor = dealIcon.Color;
if (forceDisable)
{
dealIconColor.A = 0;
}
dealIcon.Color = dealIconColor;
dealIcon.SetAsFirstChild();
}
bool isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
@@ -1713,7 +1723,7 @@ namespace Barotrauma
mainGroup.Recalculate();
mainGroup.RectTransform.RecalculateChildren(true, true);
amountInput?.LayoutGroup.Recalculate();
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.Text = ToolBox.LimitString(nameBlock.Text.SanitizedString, nameBlock.Font, nameBlock.Rect.Width);
mainGroup.RectTransform.Children.ForEach(c => c.IsFixedSize = true);
return frame;
@@ -1795,6 +1805,9 @@ namespace Barotrauma
private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled)
{
float full = 1f;
float dim = 0.7f;
float alpha = (enabled ? full : dim);
if (itemFrame?.UserData is not PurchasedItem pi) { return; }
bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
if (!refreshFrameStatus) { return; }
@@ -1802,14 +1815,14 @@ namespace Barotrauma
{
if (pi.ItemPrefab?.InventoryIcon != null)
{
icon.Color = pi.ItemPrefab.InventoryIconColor * (enabled ? 1.0f : 0.5f);
icon.Color = pi.ItemPrefab.InventoryIconColor * alpha;
}
else if (pi.ItemPrefab?.Sprite != null)
{
icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f);
icon.Color = pi.ItemPrefab.SpriteColor * alpha;
}
};
var color = Color.White * (enabled ? 1.0f : 0.5f);
var color = Color.White * alpha;
if (itemFrame.FindChild("name", recursive: true) is GUITextBlock name)
{
name.TextColor = color;
@@ -1835,7 +1848,7 @@ namespace Barotrauma
}
if (itemFrame.FindChild("price", recursive: true) is GUITextBlock priceBlock)
{
priceBlock.TextColor = isDiscounted ? storeSpecialColor * (enabled ? 1.0f : 0.5f) : color;
priceBlock.TextColor = isDiscounted ? storeSpecialColor * alpha : color;
}
if (itemFrame.FindChild("addbutton", recursive: true) is GUIButton addButton)
{
@@ -1845,6 +1858,10 @@ namespace Barotrauma
{
removeButton.Enabled = enabled;
}
if (itemFrame.FindChild("StoreDealIcon", recursive: true) is GUIImage dealIcon)
{
dealIcon.Color = dealIcon.Color * alpha;
}
pi.IsStoreComponentEnabled = enabled;
itemFrame.UserData = pi;
}
@@ -2271,6 +2288,15 @@ namespace Barotrauma
{
updateStopwatch.Restart();
if (GameMain.DevMode)
{
if (PlayerInput.KeyDown(Keys.D0))
{
CreateUI();
needsRefresh = true;
}
}
if (GameMain.GraphicsWidth != resolutionWhenCreated.X || GameMain.GraphicsHeight != resolutionWhenCreated.Y)
{
CreateUI();
@@ -255,7 +255,7 @@ namespace Barotrauma
pageIndicators = new GUIImage[pageCount];
for (int i = 0; i < pageCount; i++)
{
pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, null, true);
pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, scaleToFit: true);
xPos += indicatorSize.X + HUDLayoutSettings.Padding;
}
@@ -120,9 +120,20 @@ namespace Barotrauma
{
if (Client == null) { return; }
if (currentPing == Client.Ping) { return; }
currentPing = Client.Ping;
textBlock.Text = currentPing.ToString();
textBlock.TextColor = GetPingColor();
if (GameMain.NetworkMember != null && GameMain.NetworkMember.ConnectedClients.Contains(Client))
{
currentPing = Client.Ping;
textBlock.Text = currentPing.ToString();
textBlock.TextColor = GetPingColor();
textBlock.ToolTip = string.Empty;
}
else
{
currentPing = 0;
textBlock.Text = "-";
textBlock.TextColor = GUIStyle.Red;
textBlock.ToolTip = TextManager.Get("causeofdeathdescription.disconnected");
}
}
public void TryPermissionIconRefresh(Sprite icon)
@@ -461,7 +472,7 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
case InfoFrameTab.Talents:
talentMenu.CreateGUI(infoFrameHolder, Character.Controlled ?? GameMain.Client?.Character);
talentMenu.CreateGUI(infoFrameHolder, Character.Controlled?.Info ?? GameMain.Client?.CharacterInfo);
break;
}
}
@@ -709,38 +720,49 @@ namespace Barotrauma
var connectedClients = GameMain.Client.ConnectedClients;
for (int i = 0; i < teamIDs.Count; i++)
for (int teamID = 0; teamID < teamIDs.Count; teamID++)
{
foreach (Character character in crew.Where(c => c.TeamID == teamIDs[i]))
foreach (Character character in crew.Where(c => c.TeamID == teamIDs[teamID]))
{
if (character is not AICharacter && connectedClients.Any(c => c.Character == null && c.Name == character.Name)) { continue; }
CreateMultiPlayerCharacterElement(character, GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.Character == character), i);
CreateMultiPlayerCharacterElement(character, GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.Character == character), teamID);
}
foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager?.GetReserveBenchInfos() ?? Enumerable.Empty<CharacterInfo>())
{
CreateMultiPlayerCharacterElement(character: null, client: null, teamID, justCharacterInfo: characterInfo);
}
}
for (int j = 0; j < connectedClients.Count; j++)
{
Client client = connectedClients[j];
if (!client.InGame || client.Character == null || client.Character.IsDead)
if (client.Character == null || client.Character.IsDead)
{
CreateMultiPlayerClientElement(client);
}
}
}
private void CreateMultiPlayerCharacterElement(Character character, Client client, int i)
/// <param name="justCharacterInfo">The character element can be generated based on just a CharacterInfo, and Character and Client can be left null. Otherwise, those are required and the CharacterInfo of the Character is used.</param>
private void CreateMultiPlayerCharacterElement(Character character, Client client, int teamID, CharacterInfo justCharacterInfo = null)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement")
CharacterInfo characterInfo = justCharacterInfo ?? character.Info;
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[teamID].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[teamID].Content.RectTransform), style: "ListBoxElement")
{
UserData = character,
UserData = character != null ? character : characterInfo,
Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? OwnCharacterBGColor : Color.Transparent
};
frame.OnSecondaryClicked += (component, data) =>
if (client != null)
{
NetLobbyScreen.CreateModerationContextMenu(client);
return true;
};
frame.OnSecondaryClicked += (component, data) =>
{
NetLobbyScreen.CreateModerationContextMenu(client);
return true;
};
}
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
@@ -748,7 +770,18 @@ namespace Barotrauma
Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => character.Info.DrawJobIcon(sb, component.Rect))
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center),
onDraw: (sb, component) =>
{
if (client == null)
{
characterInfo?.DrawJobIcon(sb, component.Rect);
}
else
{
DrawClientJobIcon(sb, component.Rect, client);
}
})
{
CanBeFocused = false,
HoverColor = Color.White,
@@ -778,39 +811,60 @@ namespace Barotrauma
else
{
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
ToolBox.LimitString(characterInfo.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: characterInfo.Job.Prefab.UIColor);
if (GameMain.GameSession?.GameMode is PvPMode)
{
new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotKillCount(character.Info) ?? 0).ToString()
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotKillCount(characterInfo) ?? 0).ToString()
};
new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotDeathCount(character.Info) ?? 0).ToString()
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotDeathCount(characterInfo) ?? 0).ToString()
};
}
if (character is AICharacter)
{
linkedGUIList.Add(new LinkedGUI(character, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes }));
// "BOT" instead of ping (which isn't relevant for bots)
linkedGUIList.Add(new LinkedGUI(character, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes }));
}
else
else if (characterInfo.IsOnReserveBench)
{
linkedGUIList.Add(new LinkedGUI(client: null, frame, textBlock: null, permissionIcon: null));
new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect))
// Reserve bench icon
new GUIImage(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height - 4), paddedFrame.RectTransform), style: "CrewManagementReserveBenchIconReserve", scaleToFit: true)
{
CanBeFocused = false,
HoverColor = Color.White,
SelectedColor = Color.White
ToolTip = TextManager.Get("ReserveBenchStatus.Reserve")
};
}
if (characterInfo.IsOnReserveBench)
{
//black bar to dim out the elements (1px shorter and to the right so it won't dim the left border too)
new GUIFrame(
new RectTransform(new Point(paddedFrame.Rect.Width - 1, frame.Rect.Height), paddedFrame.RectTransform, Anchor.Center)
{
AbsoluteOffset = new Point(1, 0)
},
style: null, color: Color.Black * 0.7f)
{
IgnoreLayoutGroups = true,
CanBeFocused = false
};
}
}
CreateWalletCrewFrame(character, paddedFrame);
if (character != null)
{
CreateWalletCrewFrame(character, paddedFrame);
}
else if (characterInfo.IsOnReserveBench)
{
// Empty column for reserve benched bots
new GUILayoutGroup(new RectTransform(new Point(walletColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.Center) { CanBeFocused = false };
}
paddedFrame.Recalculate();
}
@@ -839,7 +893,7 @@ namespace Barotrauma
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center),
onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client))
onDraw: (sb, component) => DrawClientJobIcon(sb, component.Rect, client))
{
CanBeFocused = false,
HoverColor = Color.White,
@@ -864,10 +918,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon));
if (client.Character is { } character)
{
CreateWalletCrewFrame(character, paddedFrame);
}
CreateWalletCrewFrame(client.Character, paddedFrame);
paddedFrame.Recalculate();
}
@@ -925,7 +976,7 @@ namespace Barotrauma
ToolTip = TextManager.Get("walletdescription")
};
if (character.IsBot) { return; }
if (character == null || character.IsBot) { return; }
Sprite walletSprite = GUIStyle.CrewWalletIconSmall.Value.Sprite;
@@ -1033,13 +1084,13 @@ namespace Barotrauma
}
}
private void DrawNotInGameIcon(SpriteBatch spriteBatch, Rectangle area, Client client)
private void DrawClientJobIcon(SpriteBatch spriteBatch, Rectangle area, Client client)
{
if (client.Spectating)
{
spectateIcon.Draw(spriteBatch, area, Color.White);
}
else if (client.Character != null && client.Character.IsDead)
else if (client.Character != null && client.InGame)
{
client.Character.Info?.DrawJobIcon(spriteBatch, area);
}
@@ -1065,7 +1116,13 @@ namespace Barotrauma
GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter");
if (existingPreview != null) { infoFrameHolder.RemoveChild(existingPreview); }
if (userData is CharacterInfo { IsOnReserveBench: true })
{
return true;
}
// Modal info panel that pops up on the right
GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.69f), infoFrameHolder.RectTransform, Anchor.TopRight, Pivot.TopLeft) { RelativeOffset = new Vector2(-0.061f, 0) })
{
UserData = "SelectedCharacter"
@@ -1089,7 +1146,7 @@ namespace Barotrauma
{
talentButton.OnClicked = (button, o) =>
{
talentMenu.CreateGUI(infoFrameHolder, character);
talentMenu.CreateGUI(infoFrameHolder, character.Info);
return true;
};
}
@@ -1474,7 +1531,7 @@ namespace Barotrauma
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.322f), paddedFrame.RectTransform), isHorizontal: true);
new GUICustomComponent(new RectTransform(new Vector2(0.425f, 1.0f), headerArea.RectTransform),
onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client));
onDraw: (sb, component) => DrawClientJobIcon(sb, component.Rect, client));
GUIFont font = paddedFrame.Rect.Width < 280 ? GUIStyle.SmallFont : GUIStyle.Font;
@@ -1564,6 +1621,11 @@ namespace Barotrauma
}
linkedGUIList.Clear();
foreach (GUIListBox crewList in crewListArray)
{
crewList.Content.ClearChildren();
}
}
private void AddLineToLog(string line, PlayerConnectionChangeType type)
@@ -1648,7 +1710,7 @@ namespace Barotrauma
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.DisplayName, font: GUIStyle.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.GetLocationTypeToDisplay().Name, font: GUIStyle.SubHeadingFont);
if (location.Faction?.Prefab != null)
{
@@ -88,17 +88,17 @@ namespace Barotrauma
private StartAnimation? startAnimation;
private GUIComponent? talentMainArea;
public void CreateGUI(GUIFrame parent, Character? targetCharacter)
public void CreateGUI(GUIFrame parent, CharacterInfo? characterInfo)
{
this.characterInfo = characterInfo;
character = characterInfo?.Character;
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);
@@ -999,8 +999,8 @@ namespace Barotrauma
if (characterInfo is null || talentResetButton is null || talentApplyButton is null) { return; }
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentApplyButton.Enabled = talentCount > 0;
talentResetButton.Enabled = talentCount > 0 || characterInfo.TalentRefundPoints > 0;
talentApplyButton.Enabled = character != null && talentCount > 0;
talentResetButton.Enabled = character != null && (talentCount > 0 || characterInfo.TalentRefundPoints > 0);
if (talentCount == 0 && characterInfo.TalentRefundPoints > 0)
{
@@ -127,9 +127,10 @@ namespace Barotrauma
Campaign.OnMoneyChanged.RegisterOverwriteExisting(eventId, _ => RequestRefresh());
}
public void RequestRefresh()
public void RequestRefresh(bool refreshUpgrades = false)
{
needsRefresh = true;
if (refreshUpgrades) { SelectTab(UpgradeTab.Upgrade); }
}
private void RefreshAll()
@@ -673,6 +674,10 @@ namespace Barotrauma
}
}
}
if (!upgrades.ContainsKey(category) && HasSwappableItems(category))
{
upgrades.Add(category, new List<UpgradePrefab>());
}
}
foreach (var (category, prefabs) in upgrades)
@@ -771,20 +776,28 @@ namespace Barotrauma
{
if (Submarine.MainSub == null) { return false; }
subItems ??= GetSubItems();
return subItems.Any(i =>
i.Prefab.SwappableItem != null &&
!i.IsHidden && i.AllowSwapping &&
(i.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == i.Prefab.Identifier)) &&
Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && category.ItemTags.Any(t => i.HasTag(t)));
return subItems.Any(item => HasSwappableItems(category, item));
}
private static bool HasSwappableItems(UpgradeCategory category, Item item)
{
if (Submarine.MainSub == null) { return false; }
return
item.Prefab.SwappableItem != null &&
!item.IsHidden && item.AllowSwapping &&
(item.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == item.Prefab.Identifier)) &&
Submarine.MainSub.IsEntityFoundOnThisSub(item, true) && category.ItemTags.Any(t => item.HasTag(t));
}
private static List<Item> GetSubItems() => Submarine.MainSub?.GetItems(true) ?? new List<Item>();
private void SelectUpgradeCategory(List<UpgradePrefab> prefabs, UpgradeCategory category, Submarine submarine)
{
if (selectedUpgradeCategoryLayout == null) { return; }
customizeTabOpen = false;
bool hasSwappableItems = HasSwappableItems(category);
bool hasUpgradeModules = prefabs.Count > 0;
customizeTabOpen = !hasUpgradeModules && hasSwappableItems;
GUIComponent[] categoryFrames = GetFrames(category);
foreach (GUIComponent itemFrame in itemPreviews.Values)
@@ -799,9 +812,7 @@ namespace Barotrauma
GUIFrame frame = new GUIFrame(rectT(1.0f, 0.4f, selectedUpgradeCategoryLayout));
GUIFrame paddedFrame = new GUIFrame(rectT(0.93f, 0.9f, frame, Anchor.Center), style: null);
bool hasSwappableItems = HasSwappableItems(category);
float listHeight = hasSwappableItems ? 0.9f : 1.0f;
float listHeight = hasSwappableItems && hasUpgradeModules ? 0.9f : 1.0f;
GUIListBox prefabList = new GUIListBox(rectT(1.0f, listHeight, paddedFrame, Anchor.BottomLeft))
{
@@ -810,7 +821,8 @@ namespace Barotrauma
ScrollBarVisible = true
};
if (hasSwappableItems)
//both swappable items and upgrade modules -> create 2 tabs
if (hasSwappableItems && hasUpgradeModules)
{
GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1.0f, 0.1f, paddedFrame, anchor: Anchor.TopLeft), isHorizontal: true);
@@ -852,8 +864,15 @@ namespace Barotrauma
return true;
};
}
CreateUpgradePrefabList(prefabList, category, prefabs, submarine);
//only either upgrade modules or swappable items -> just create the list
else if (hasUpgradeModules)
{
CreateUpgradePrefabList(prefabList, category, prefabs, submarine);
}
else if (hasSwappableItems)
{
CreateSwappableItemList(prefabList, category, submarine);
}
}
private void CreateUpgradePrefabList(GUIListBox parent, UpgradeCategory category, List<UpgradePrefab> prefabs, Submarine submarine)
@@ -1370,9 +1389,10 @@ namespace Barotrauma
Item[] entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToArray();
foreach (UpgradeCategory category in UpgradeCategory.Categories)
{
//hide categories with no upgrades in them
if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category))) { continue; }
if (entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
//hide categories with no upgrades or swappables in them
bool hasSwappableItems = HasSwappableItems(category);
if (!hasSwappableItems && UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category))) { continue; }
if (hasSwappableItems || entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
{
yield return category;
}
@@ -1534,7 +1554,7 @@ namespace Barotrauma
description.Padding = new Vector4(description.Padding.X, 24 * GUI.Scale, description.Padding.Z, description.Padding.W);
List<Entity> pointsOfInterest = (from category in UpgradeCategory.Categories from item in submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs)
where category.CanBeApplied(item, null) && item.IsPlayerTeamInteractable select item).Cast<Entity>().Distinct().ToList();
where (category.CanBeApplied(item, null) || HasSwappableItems(category, item)) && item.IsPlayerTeamInteractable select item).Cast<Entity>().Distinct().ToList();
List<ushort> ids = GameMain.GameSession.SubmarineInfo?.LeftBehindDockingPortIDs ?? new List<ushort>();
pointsOfInterest.AddRange(submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs).Where(item => ids.Contains(item.ID)));
@@ -1799,7 +1819,7 @@ namespace Barotrauma
// Disables the parent and only re-enables if the submarine contains valid items
if (!category.IsWallUpgrade && drawnSubmarine?.Info != null)
{
if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0))
if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0) && !HasSwappableItems(category))
{
parent.ToolTip = TextManager.Get("upgradecategorynotapplicable");
parent.Enabled = false;