using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Steam;
namespace Barotrauma
{
partial class CrewManager
{
private Point screenResolution;
#region UI
public GUIComponent ReportButtonFrame { get; set; }
private GUIFrame guiFrame;
private GUIComponent crewAreaWithButtons;
private GUIFrame crewArea;
private GUIListBox crewList;
private GUIButton commandButton, toggleCrewButton;
private float crewListOpenState;
private bool _isCrewMenuOpen = true;
private Point crewListEntrySize;
private GUIFrame contextMenu;
private GUIListBox subContextMenu;
///
/// Present only in single player games. In multiplayer. The chatbox is found from GameSession.Client.
///
public ChatBox ChatBox { get; private set; }
private float prevUIScale;
public bool AllowCharacterSwitch = true;
///
/// This property stores the preference in settings. Don't use for automatic logic.
/// Use AutoShowCrewList(), AutoHideCrewList(), and ResetCrewList().
///
public bool IsCrewMenuOpen
{
get { return _isCrewMenuOpen; }
set
{
if (_isCrewMenuOpen == value) { return; }
_isCrewMenuOpen = GameMain.Config.CrewMenuOpen = value;
}
}
public bool AutoShowCrewList() => _isCrewMenuOpen = true;
public void AutoHideCrewList() => _isCrewMenuOpen = false;
public void ResetCrewList() => _isCrewMenuOpen = GameMain.Config.CrewMenuOpen;
const float CommandNodeAnimDuration = 0.2f;
public List OrderOptionButtons = new List();
private Sprite jobIndicatorBackground, previousOrderArrow, cancelIcon;
private const int MaxOrderIcons = 3;
#endregion
#region Constructors
public CrewManager(XElement element, bool isSinglePlayer)
: this(isSinglePlayer)
{
AddCharacterElements(element);
}
partial void InitProjectSpecific()
{
guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent)
{
CanBeFocused = false
};
#region Crew Area
crewAreaWithButtons = new GUIFrame(
HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform),
style: null,
color: Color.Transparent)
{
CanBeFocused = false
};
var commandButtonHeight = (int)(GUI.Scale * 40);
var buttonSize = new Point((int)(182f / 99f * commandButtonHeight), commandButtonHeight);
var crewListToggleButtonHeight = (int)(64f * buttonSize.X / 175f);
crewArea = new GUIFrame(
new RectTransform(
new Point(crewAreaWithButtons.Rect.Width, crewAreaWithButtons.Rect.Height - commandButtonHeight - crewListToggleButtonHeight - 2 * HUDLayoutSettings.Padding),
crewAreaWithButtons.RectTransform,
Anchor.BottomLeft),
style: null,
color: Color.Transparent)
{
CanBeFocused = false
};
commandButton = new GUIButton(
new RectTransform(buttonSize, parent: crewAreaWithButtons.RectTransform),
style: "CommandButton")
{
// TODO: Update keybind if it's changed
ToolTip = TextManager.Get("inputtype.command") + " (" + GameMain.Config.KeyBindText(InputType.Command) + ")",
OnClicked = (button, userData) =>
{
ToggleCommandUI();
return true;
}
};
// AbsoluteOffset is set in UpdateProjectSpecific based on crewListOpenState
crewList = new GUIListBox(
new RectTransform(
Vector2.One,
crewArea.RectTransform),
style: null,
isScrollBarOnDefaultSide: false)
{
AutoHideScrollBar = false,
CanBeFocused = false,
OnSelected = (component, userData) => false,
SelectMultiple = false,
Spacing = (int)(GUI.Scale * 10)
};
buttonSize.Y = crewListToggleButtonHeight;
toggleCrewButton = new GUIButton(
new RectTransform(buttonSize, parent: crewAreaWithButtons.RectTransform)
{
AbsoluteOffset = new Point(0, commandButtonHeight + HUDLayoutSettings.Padding)
},
style: "CrewListToggleButton")
{
OnClicked = (GUIButton btn, object userdata) =>
{
IsCrewMenuOpen = !IsCrewMenuOpen;
return true;
}
};
jobIndicatorBackground = new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(0, 512, 128, 128));
previousOrderArrow = new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(128, 512, 128, 128));
cancelIcon = new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(512, 384, 128, 128));
// Calculate and store crew list entry size so it doesn't have to be calculated for every entry
crewListEntrySize = new Point(crewList.Content.Rect.Width - HUDLayoutSettings.Padding, 0);
int crewListEntryMinHeight = 32;
crewListEntrySize.Y = Math.Max(crewListEntryMinHeight, (int)(crewListEntrySize.X / 8f));
float charactersPerView = crewList.Content.Rect.Height / (float)(crewListEntrySize.Y + crewList.Spacing);
int adjustedHeight = (int)Math.Ceiling(crewList.Content.Rect.Height / Math.Round(charactersPerView)) - crewList.Spacing;
if (adjustedHeight < crewListEntryMinHeight) { adjustedHeight = (int)Math.Ceiling(crewList.Content.Rect.Height / Math.Floor(charactersPerView)) - crewList.Spacing; }
crewListEntrySize.Y = adjustedHeight;
#endregion
#region Chatbox
if (IsSinglePlayer)
{
ChatBox = new ChatBox(guiFrame, isSinglePlayer: true)
{
OnEnterMessage = (textbox, text) =>
{
if (Character.Controlled?.Info == null)
{
textbox.Deselect();
textbox.Text = "";
return true;
}
textbox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
if (!string.IsNullOrWhiteSpace(text))
{
string msgCommand = ChatMessage.GetChatMessageCommand(text, out string msg);
// add to local history
ChatBox.ChatManager.Store(text);
AddSinglePlayerChatMessage(
Character.Controlled.Info.Name,
msg,
((msgCommand == "r" || msgCommand == "radio") && ChatMessage.CanUseRadio(Character.Controlled)) ? ChatMessageType.Radio : ChatMessageType.Default,
Character.Controlled);
var headset = GetHeadset(Character.Controlled, true);
if (headset != null && headset.CanTransmit())
{
headset.TransmitSignal(stepsTaken: 0, signal: msg, source: headset.Item, sender: Character.Controlled, sentFromChat: true);
}
}
textbox.Deselect();
textbox.Text = "";
if (ChatBox.CloseAfterMessageSent)
{
ChatBox.ToggleOpen = false;
ChatBox.CloseAfterMessageSent = false;
}
return true;
}
};
ChatBox.InputBox.OnTextChanged += ChatBox.TypingChatMessage;
}
#endregion
#region Reports
var chatBox = ChatBox ?? GameMain.Client?.ChatBox;
if (chatBox != null)
{
chatBox.ToggleButton = new GUIButton(new RectTransform(new Point((int)(182f * GUI.Scale * 0.4f), (int)(99f * GUI.Scale * 0.4f)), chatBox.GUIFrame.Parent.RectTransform), style: "ChatToggleButton");
chatBox.ToggleButton.RectTransform.AbsoluteOffset = new Point(0, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height);
chatBox.ToggleButton.OnClicked += (GUIButton btn, object userdata) =>
{
chatBox.ToggleOpen = !chatBox.ToggleOpen;
chatBox.CloseAfterMessageSent = false;
return true;
};
}
var reports = Order.PrefabList.FindAll(o => o.IsReport && o.SymbolSprite != null && !o.Hidden);
if (reports.None())
{
DebugConsole.ThrowError("No valid orders for report buttons found! Cannot create report buttons. The orders for the report buttons must have 'targetallcharacters' attribute enabled and a valid 'symbolsprite' defined.");
return;
}
ReportButtonFrame = new GUILayoutGroup(new RectTransform(
new Point((HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height - (int)((reports.Count - 1) * 5 * GUI.Scale)) / reports.Count, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height), guiFrame.RectTransform))
{
AbsoluteSpacing = (int)(5 * GUI.Scale),
UserData = "reportbuttons",
CanBeFocused = false
};
ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height);
//report buttons
foreach (Order order in reports)
{
if (!order.IsReport || order.SymbolSprite == null || order.Hidden) { continue; }
var btn = new GUIButton(new RectTransform(new Point(ReportButtonFrame.Rect.Width), ReportButtonFrame.RectTransform), style: null)
{
OnClicked = (GUIButton button, object userData) =>
{
if (!CanIssueOrders) { return false; }
var sub = Character.Controlled.Submarine;
if (sub == null || sub.TeamID != Character.Controlled.TeamID || sub.Info.IsWreck) { return false; }
SetCharacterOrder(null, order, null, Character.Controlled);
if (IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); }
return true;
},
UserData = order,
ToolTip = order.Name
};
new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlowCircular")
{
Color = GUI.Style.Red * 0.8f,
HoverColor = GUI.Style.Red * 1.0f,
PressedColor = GUI.Style.Red * 0.6f,
UserData = "highlighted",
CanBeFocused = false,
Visible = false
};
var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite, scaleToFit: true)
{
Color = order.Color,
HoverColor = Color.Lerp(order.Color, Color.White, 0.5f),
ToolTip = order.Name,
SpriteEffects = SpriteEffects.FlipHorizontally
};
}
#endregion
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
prevUIScale = GUI.Scale;
_isCrewMenuOpen = GameMain.Config.CrewMenuOpen;
dismissedOrderPrefab ??= Order.GetPrefab("dismissed");
}
#endregion
#region Character list management
public Rectangle GetActiveCrewArea()
{
return crewArea.Rect;
}
public IEnumerable GetCharacters()
{
return characters;
}
public IEnumerable GetCharacterInfos()
{
return characterInfos;
}
///
/// Remove the character from the crew (and crew menus).
///
/// The character to remove
/// If the character info is also removed, the character will not be visible in the round summary.
public void RemoveCharacter(Character character, bool removeInfo = false)
{
if (character == null)
{
DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace.CleanupStackTrace());
return;
}
characters.Remove(character);
if (removeInfo) { characterInfos.Remove(character.Info); }
}
private void AddCharacterToCrewList(Character character)
{
if (character == null) { return; }
var background = new GUIFrame(
new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight),
style: "CrewListBackground")
{
UserData = character,
OnSecondaryClicked = (comp, data) =>
{
if (data == null) { return false; }
var client = GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data);
if (client != null)
{
CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client);
return true;
}
return false;
}
};
var iconRelativeWidth = (float)crewListEntrySize.Y / background.Rect.Width;
var layoutGroup = new GUILayoutGroup(
new RectTransform(Vector2.One, parent: background.RectTransform),
isHorizontal: true,
childAnchor: Anchor.CenterLeft)
{
CanBeFocused = false,
RelativeSpacing = 0.1f * iconRelativeWidth,
UserData = character
};
var commandButtonAbsoluteHeight = Math.Min(40.0f, 0.67f * background.Rect.Height);
var paddingRelativeWidth = 0.35f * commandButtonAbsoluteHeight / background.Rect.Width;
// "Padding" to prevent member-specific command button from overlapping job indicator
new GUIFrame(new RectTransform(new Vector2(paddingRelativeWidth, 1.0f), layoutGroup.RectTransform), style: null);
var jobIconBackground = new GUIImage(
new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform),
jobIndicatorBackground,
scaleToFit: true)
{
CanBeFocused = false,
UserData = "job"
};
if (character?.Info?.Job.Prefab?.Icon != null)
{
new GUIImage(
new RectTransform(Vector2.One, jobIconBackground.RectTransform),
character.Info.Job.Prefab.Icon,
scaleToFit: true)
{
CanBeFocused = false,
Color = character.Info.Job.Prefab.UIColor,
HoverColor = character.Info.Job.Prefab.UIColor,
PressedColor = character.Info.Job.Prefab.UIColor,
SelectedColor = character.Info.Job.Prefab.UIColor
};
}
var nameRelativeWidth = 1.0f
// Start padding
- paddingRelativeWidth
// 5 icons (job, 3 orders, sound)
- (5 * 0.8f * iconRelativeWidth)
// Vertical line
- (0.1f * iconRelativeWidth)
// Spacing
- (7 * layoutGroup.RelativeSpacing);
var font = layoutGroup.Rect.Width < 150 ? GUI.SmallFont : GUI.Font;
var nameBlock = new GUITextBlock(
new RectTransform(
new Vector2(nameRelativeWidth, 1.0f),
layoutGroup.RectTransform)
{
MaxSize = new Point(150, background.Rect.Height)
},
ToolBox.LimitString(character.Name, font, (int)(nameRelativeWidth * layoutGroup.Rect.Width)),
font: font,
textColor: character.Info?.Job?.Prefab?.UIColor)
{
CanBeFocused = false
};
var nameActualRealtiveWidth = Math.Min(nameRelativeWidth * background.Rect.Width, 150) / background.Rect.Width;
var characterButton = new GUIButton(
new RectTransform(
new Vector2(paddingRelativeWidth + 0.8f * iconRelativeWidth + nameActualRealtiveWidth + 2 * layoutGroup.RelativeSpacing, 1.0f),
background.RectTransform),
style: null)
{
UserData = character
};
// Only create a tooltip if the name doesn't fit the name block
if (nameBlock.Text.EndsWith("..."))
{
var characterTooltip = character.Name;
if (character.Info?.Job?.Name != null) { characterTooltip += " (" + character.Info.Job.Name + ")"; };
characterButton.ToolTip = characterTooltip;
if (character.Info?.Job?.Prefab != null)
{
characterButton.TooltipRichTextData = new List() { new RichTextData()
{
Color = character.Info.Job.Prefab.UIColor,
EndIndex = characterTooltip.Length - 1
}};
}
}
if (IsSinglePlayer)
{
characterButton.OnClicked = CharacterClicked;
}
else
{
characterButton.CanBeFocused = false;
characterButton.CanBeSelected = false;
}
new GUIImage(
new RectTransform(new Vector2(0.1f * iconRelativeWidth, 0.5f), layoutGroup.RectTransform),
style: "VerticalLine")
{
CanBeFocused = false
};
var soundIcons = new GUIFrame(new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "soundicons"
};
new GUIImage(
new RectTransform(Vector2.One, soundIcons.RectTransform),
GUI.Style.GetComponentStyle("GUISoundIcon").GetDefaultSprite(),
scaleToFit: true)
{
CanBeFocused = false,
UserData = new Pair("soundicon", 0.0f),
Visible = true
};
new GUIImage(
new RectTransform(Vector2.One, soundIcons.RectTransform),
"GUISoundIconDisabled",
scaleToFit: true)
{
CanBeFocused = true,
UserData = "soundicondisabled",
Visible = false
};
new GUIButton(new RectTransform(new Point((int)commandButtonAbsoluteHeight), background.RectTransform), style: "CrewListCommandButton")
{
ToolTip = TextManager.Get("inputtype.command"),
OnClicked = (component, userData) =>
{
if (!CanIssueOrders) { return false; }
CreateCommandUI(character);
return true;
}
};
}
///
/// Sets which character is selected in the crew UI (highlight effect etc)
///
public bool CharacterClicked(GUIComponent component, object selection)
{
if (!AllowCharacterSwitch) { return false; }
Character character = selection as Character;
if (character == null || character.IsDead || character.IsUnconscious) { return false; }
SelectCharacter(character);
if (GUI.KeyboardDispatcher.Subscriber == crewList) { GUI.KeyboardDispatcher.Subscriber = null; }
return true;
}
public void ReviveCharacter(Character revivedCharacter)
{
if (crewList.Content.GetChildByUserData(revivedCharacter) is GUIComponent characterComponent)
{
crewList.Content.RemoveChild(characterComponent);
}
if (characterInfos.Contains(revivedCharacter.Info)) { AddCharacter(revivedCharacter); }
}
public void KillCharacter(Character killedCharacter)
{
if (crewList.Content.GetChildByUserData(killedCharacter) is GUIComponent characterComponent)
{
CoroutineManager.StartCoroutine(KillCharacterAnim(characterComponent));
}
RemoveCharacter(killedCharacter);
}
private IEnumerable