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;
public Order DraggedOrder;
public bool DragOrder;
private bool dropOrder;
private int framesToSkip = 2;
private float dragOrderTreshold;
private Vector2 dragPoint = Vector2.Zero;
#region UI
public GUIComponent ReportButtonFrame { get; set; }
private GUIFrame guiFrame;
private GUIFrame crewArea;
private GUIListBox crewList;
private float crewListOpenState;
private bool _isCrewMenuOpen = true;
private Point crewListEntrySize;
///
/// 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;
#endregion
#region Constructors
public CrewManager(XElement element, bool isSinglePlayer)
: this(isSinglePlayer)
{
AddCharacterElements(element);
ActiveOrdersElement = element.GetChildElement("activeorders");
}
partial void InitProjectSpecific()
{
guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent)
{
CanBeFocused = false
};
#region Crew Area
crewArea = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), style: null, color: Color.Transparent)
{
CanBeFocused = false
};
// 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,
CanDragElements = true,
CanInteractWhenUnfocusable = true,
OnSelected = (component, userData) => false,
SelectMultiple = false,
Spacing = (int)(GUI.Scale * 10),
OnRearranged = OnCrewListRearranged
};
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);
if (ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent headset))
{
Signal s = new Signal(msg, sender: Character.Controlled, source: headset.Item);
headset.TransmitSignal(s, 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")
{
ToolTip = TextManager.Get("chat"),
ClampMouseRectToParent = false
};
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;
};
}
List 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,
Visible = false
};
ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height);
CreateReportButtons(this, ReportButtonFrame, reports, false);
#endregion
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
prevUIScale = GUI.Scale;
_isCrewMenuOpen = GameMain.Config.CrewMenuOpen;
dismissedOrderPrefab ??= Order.GetPrefab("dismissed");
}
public static void CreateReportButtons(CrewManager crewManager, GUIComponent parent, List reports, bool isHorizontal)
{
//report buttons
foreach (Order order in reports)
{
if (!order.IsReport || order.SymbolSprite == null || order.Hidden) { continue; }
var btn = new GUIButton(new RectTransform(new Point(isHorizontal ? parent.Rect.Height : parent.Rect.Width), parent.RectTransform), style: null)
{
OnClicked = (button, userData) =>
{
if (!CanIssueOrders || crewManager?.DraggedOrder != null) { return false; }
var sub = Character.Controlled.Submarine;
if (sub == null || sub.TeamID != Character.Controlled.TeamID || sub.Info.IsWreck) { return false; }
if (crewManager != null)
{
crewManager.SetCharacterOrder(null, order, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled);
if (crewManager.IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); }
}
return true;
},
UserData = order,
ClampMouseRectToParent = false
};
btn.ToolTip = $"‖color:{XMLExtensions.ColorToString(order.Prefab.Color)}‖{order.Name}‖color:end‖\n{TextManager.Get("draganddropreports")}";
if (crewManager != null)
{
btn.OnButtonDown = () =>
{
crewManager.dragOrderTreshold = Math.Max(btn.Rect.Width, btn.Rect.Height) / 2f;
crewManager.DraggedOrder = order;
crewManager.dropOrder = false;
crewManager.framesToSkip = 2;
crewManager.dragPoint = btn.Rect.Center.ToVector2();
return true;
};
}
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 = btn.RawToolTip,
SpriteEffects = SpriteEffects.FlipHorizontally,
UserData = order
};
}
}
#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); }
}
///
/// Add character to the list without actually adding it to the crew
///
public 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; }
if (GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data) is Client client)
{
CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client);
return true;
}
return false;
}
};
SetCharacterComponentTooltip(background);
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)
{
CanBeFocused = false
};
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)
}, "",
font: font,
textColor: character.Info?.Job?.Prefab?.UIColor)
{
CanBeFocused = false,
UserData = "name"
};
nameBlock.Text = ToolBox.LimitString(character.Name, font, (int)nameBlock.Rect.Width);
new GUIImage(
new RectTransform(new Vector2(0.1f * iconRelativeWidth, 0.5f), layoutGroup.RectTransform),
style: "VerticalLine")
{
CanBeFocused = false
};
var orderGroup = new GUILayoutGroup(new RectTransform(new Vector2(3 * 0.8f * iconRelativeWidth, 0.8f), parent: layoutGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
CanBeFocused = false,
Stretch = true
};
// Current orders
var currentOrderList = new GUIListBox(new RectTransform(new Vector2(0.0f, 1.0f), parent: orderGroup.RectTransform), isHorizontal: true, style: null)
{
AllowMouseWheelScroll = false,
CanDragElements = true,
HideChildrenOutsideFrame = false,
KeepSpaceForScrollBar = false,
OnRearranged = OnOrdersRearranged,
ScrollBarVisible = false,
Spacing = 2,
UserData = character
};
currentOrderList.RectTransform.IsFixedSize = true;
currentOrderList.OnAddedToGUIUpdateList += (component) =>
{
if (component is GUIListBox list)
{
list.CanBeFocused = CanIssueOrders;
list.CanDragElements = CanIssueOrders && list.Content.CountChildren > 1;
}
};
// Previous orders
new GUILayoutGroup(new RectTransform(Vector2.One, parent: orderGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
CanBeFocused = false,
Stretch = false
};
var extraIconFrame = new GUIFrame(new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "extraicons"
};
var soundIconParent = new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "soundicons",
Visible = character.IsPlayer
};
new GUIImage(
new RectTransform(Vector2.One, soundIconParent.RectTransform),
GUI.Style.GetComponentStyle("GUISoundIcon").GetDefaultSprite(),
scaleToFit: true)
{
CanBeFocused = false,
UserData = new Pair("soundicon", 0.0f),
Visible = true
};
new GUIImage(
new RectTransform(Vector2.One, soundIconParent.RectTransform),
"GUISoundIconDisabled",
scaleToFit: true)
{
CanBeFocused = true,
UserData = "soundicondisabled",
Visible = false
};
if (character.IsBot)
{
new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "objectiveicon",
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;
}
};
}
private void SetCharacterComponentTooltip(GUIComponent characterComponent)
{
if (!(characterComponent?.UserData is Character character)) { return; }
if (character.Info?.Job?.Prefab == null) { return; }
string color = XMLExtensions.ColorToString(character.Info.Job.Prefab.UIColor);
string tooltip = $"‖color:{color}‖{character.Name} ({character.Info.Job.Name})‖color:end‖";
var richTextData = RichTextData.GetRichTextData(tooltip, out string sanitizedTooltip);
characterComponent.ToolTip = sanitizedTooltip;
characterComponent.TooltipRichTextData = richTextData;
}
///
/// Sets which character is selected in the crew UI (highlight effect etc)
///
public bool CharacterClicked(GUIComponent component, object selection)
{
if (!AllowCharacterSwitch) { return false; }
if (!(selection is Character character) || character.IsDead || character.IsUnconscious) { return false; }
if (!character.IsOnPlayerTeam) { 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