5dc31c213f
commit 7245c721339885d062567befc052a592391b3b4a Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sun Mar 10 15:22:31 2019 +0200 Fixed StatusEffects only applying afflictions to one limb even if the target is "Character" instead of "Limb", added a subtle screen distortion effect to heavy radiation sickness. Closes #1256 commit e0db27e62ec9546fd4b182a0cc97f7e5830645ae Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Mar 9 21:53:51 2019 +0200 Fixed WrapText adding unnecessary spaces after every line break. Closes #1215 commit 988bc58d51c195ad9265b84a1e97e0101cd3f808 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Mar 9 21:12:50 2019 +0200 Fixed crashing when attempting to create a body for a wall section that's less than 1 unit long (e.g. if a wall that's just slightly longer than the wall section size receives damage). commit 8c31157425a9e2ec02312618d1bfa359ab3ee87d Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Mar 9 20:30:44 2019 +0200 Fixed clients being unable to toggle the respawn shuttle on/off commit a4ccb039219830efe9cd305c26942dda1bd04e9c Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Mar 9 19:33:22 2019 +0200 Fixed inability to select the respawn shuttle as a client host commit b89b2d2c282d8c74d7ccd37b3f29dcab51eff680 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Mar 9 19:32:41 2019 +0200 Made it possible to edit the style of the ListBox under GUIDropDowns, increased the opacity of the listbox to make the contents more readable when there's text behind it commit 8f6d9aef3d637fe37a18c78f4b15ef8fd266374e Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Mar 9 18:11:23 2019 +0200 Fixed NetLobbyScreen not showing the names of the submarines the client doesn't have
1470 lines
64 KiB
C#
1470 lines
64 KiB
C#
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;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
partial class CrewManager
|
|
{
|
|
const float ChatMessageFadeTime = 10.0f;
|
|
|
|
/// <summary>
|
|
/// How long the previously selected character waits doing nothing when switching to another character
|
|
/// </summary>
|
|
const float CharacterWaitOnSwitch = 20.0f;
|
|
|
|
|
|
private List<CharacterInfo> characterInfos = new List<CharacterInfo>();
|
|
private List<Character> characters = new List<Character>();
|
|
|
|
private Point screenResolution;
|
|
|
|
#region UI
|
|
|
|
private GUIFrame guiFrame;
|
|
private GUIFrame crewArea;
|
|
private GUIListBox characterListBox;
|
|
|
|
private GUIComponent reportButtonFrame;
|
|
|
|
private GUIButton scrollButtonUp, scrollButtonDown;
|
|
|
|
private GUIButton toggleCrewButton;
|
|
private float crewAreaOpenState;
|
|
private bool toggleCrewAreaOpen = true;
|
|
private int characterInfoWidth;
|
|
|
|
private ChatBox chatBox;
|
|
|
|
private float prevUIScale;
|
|
|
|
private GUIComponent orderTargetFrame, orderTargetFrameShadow;
|
|
|
|
public bool ToggleCrewAreaOpen
|
|
{
|
|
get { return toggleCrewAreaOpen; }
|
|
set { toggleCrewAreaOpen = value; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
public CrewManager(XElement element, bool isSinglePlayer)
|
|
: this(isSinglePlayer)
|
|
{
|
|
guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
|
|
Point scrollButtonSize = new Point((int)(200 * GUI.Scale), (int)(30 * GUI.Scale));
|
|
|
|
crewArea = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), "", Color.Transparent)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
toggleCrewButton = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), HUDLayoutSettings.CrewArea.Height), guiFrame.RectTransform)
|
|
{ AbsoluteOffset = HUDLayoutSettings.CrewArea.Location },
|
|
"", style: "UIToggleButton");
|
|
toggleCrewButton.OnClicked += (GUIButton btn, object userdata) =>
|
|
{
|
|
toggleCrewAreaOpen = !toggleCrewAreaOpen;
|
|
foreach (GUIComponent child in btn.Children)
|
|
{
|
|
child.SpriteEffects = toggleCrewAreaOpen ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
var characterInfo = new CharacterInfo(subElement);
|
|
characterInfos.Add(characterInfo);
|
|
foreach (XElement invElement in subElement.Elements())
|
|
{
|
|
if (invElement.Name.ToString().ToLowerInvariant() != "inventory") continue;
|
|
characterInfo.InventoryData = invElement;
|
|
break;
|
|
}
|
|
}
|
|
|
|
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
|
|
|
prevUIScale = GUI.Scale;
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Character list management
|
|
|
|
public Rectangle GetCharacterListArea()
|
|
{
|
|
return characterListBox.Rect;
|
|
}
|
|
|
|
partial void InitProjectSpecific()
|
|
{
|
|
guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
|
|
Point scrollButtonSize = new Point((int)(200 * GUI.Scale), (int)(30 * GUI.Scale));
|
|
|
|
crewArea = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), "", Color.Transparent)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
toggleCrewButton = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), HUDLayoutSettings.CrewArea.Height), guiFrame.RectTransform)
|
|
{ AbsoluteOffset = HUDLayoutSettings.CrewArea.Location },
|
|
"", style: "UIToggleButton");
|
|
toggleCrewButton.OnClicked += (GUIButton btn, object userdata) =>
|
|
{
|
|
toggleCrewAreaOpen = !toggleCrewAreaOpen;
|
|
foreach (GUIComponent child in btn.Children)
|
|
{
|
|
child.SpriteEffects = toggleCrewAreaOpen ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
characterListBox = new GUIListBox(new RectTransform(new Point(100, (int)(crewArea.Rect.Height - scrollButtonSize.Y * 1.6f)), crewArea.RectTransform, Anchor.CenterLeft), false, Color.Transparent, null)
|
|
{
|
|
//Spacing = (int)(3 * GUI.Scale),
|
|
ScrollBarEnabled = false,
|
|
ScrollBarVisible = false,
|
|
CanBeFocused = false
|
|
};
|
|
|
|
scrollButtonUp = new GUIButton(new RectTransform(scrollButtonSize, crewArea.RectTransform, Anchor.TopLeft, Pivot.TopLeft), "", Alignment.Center, "GUIButtonVerticalArrow")
|
|
{
|
|
Visible = false,
|
|
UserData = -1,
|
|
OnClicked = ScrollCharacterList
|
|
};
|
|
scrollButtonDown = new GUIButton(new RectTransform(scrollButtonSize, crewArea.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft), "", Alignment.Center, "GUIButtonVerticalArrow")
|
|
{
|
|
Visible = false,
|
|
UserData = 1,
|
|
OnClicked = ScrollCharacterList
|
|
};
|
|
scrollButtonDown.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipVertically);
|
|
|
|
if (isSinglePlayer)
|
|
{
|
|
chatBox = new ChatBox(guiFrame, isSinglePlayer: true)
|
|
{
|
|
OnEnterMessage = (textbox, text) =>
|
|
{
|
|
if (Character.Controlled == null) { return true; }
|
|
|
|
textbox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
|
|
|
|
if (!string.IsNullOrWhiteSpace(text))
|
|
{
|
|
string msgCommand = ChatMessage.GetChatMessageCommand(text, out string msg);
|
|
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, sendToChat: false);
|
|
}
|
|
}
|
|
textbox.Deselect();
|
|
textbox.Text = "";
|
|
return true;
|
|
}
|
|
};
|
|
|
|
chatBox.InputBox.OnTextChanged += chatBox.TypingChatMessage;
|
|
}
|
|
|
|
var reports = Order.PrefabList.FindAll(o => o.TargetAllCharacters && o.SymbolSprite != null);
|
|
reportButtonFrame = new GUILayoutGroup(new RectTransform(
|
|
new Point((HUDLayoutSettings.CrewArea.Height - (int)((reports.Count - 1) * 5 * GUI.Scale)) / reports.Count, HUDLayoutSettings.CrewArea.Height), guiFrame.RectTransform))
|
|
{
|
|
AbsoluteSpacing = (int)(5 * GUI.Scale),
|
|
UserData = "reportbuttons",
|
|
CanBeFocused = false
|
|
};
|
|
|
|
//report buttons
|
|
foreach (Order order in reports)
|
|
{
|
|
if (!order.TargetAllCharacters || order.SymbolSprite == null) continue;
|
|
var btn = new GUIButton(new RectTransform(new Point(reportButtonFrame.Rect.Width), reportButtonFrame.RectTransform), style: null)
|
|
{
|
|
OnClicked = (GUIButton button, object userData) =>
|
|
{
|
|
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
|
|
SetCharacterOrder(null, order, null, Character.Controlled);
|
|
return true;
|
|
},
|
|
UserData = order,
|
|
ToolTip = order.Name
|
|
};
|
|
|
|
new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow")
|
|
{
|
|
Color = Color.Red * 0.8f,
|
|
HoverColor = Color.Red * 1.0f,
|
|
PressedColor = Color.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
|
|
};
|
|
}
|
|
|
|
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
|
|
|
prevUIScale = GUI.Scale;
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Character list management
|
|
|
|
public Rectangle GetCharacterListArea()
|
|
{
|
|
return characterListBox.Rect;
|
|
}
|
|
|
|
public IEnumerable<Character> GetCharacters()
|
|
{
|
|
return characters;
|
|
}
|
|
|
|
public IEnumerable<CharacterInfo> GetCharacterInfos()
|
|
{
|
|
return characterInfos;
|
|
}
|
|
|
|
public void AddCharacter(Character character)
|
|
{
|
|
if (character.Removed)
|
|
{
|
|
DebugConsole.ThrowError("Tried to add a removed character to CrewManager!\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
if (character.IsDead)
|
|
{
|
|
DebugConsole.ThrowError("Tried to add a dead character to CrewManager!\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
|
|
if (!characters.Contains(character)) characters.Add(character);
|
|
if (!characterInfos.Contains(character.Info))
|
|
{
|
|
characterInfos.Add(character.Info);
|
|
}
|
|
|
|
CreateCharacterFrame(character, characterListBox.Content);
|
|
characterListBox.Content.RectTransform.SortChildren((c1, c2) => { return c2.NonScaledSize.X - c1.NonScaledSize.X; });
|
|
|
|
if (character is AICharacter)
|
|
{
|
|
var ai = character.AIController as HumanAIController;
|
|
if (ai == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in crewmanager - attempted to give orders to a character with no HumanAIController");
|
|
return;
|
|
}
|
|
character.SetOrder(ai.CurrentOrder, "", null, false);
|
|
}
|
|
}
|
|
|
|
public void AddCharacterInfo(CharacterInfo characterInfo)
|
|
{
|
|
if (characterInfos.Contains(characterInfo))
|
|
{
|
|
DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
if (characterInfos.Contains(revivedCharacter.Info)) AddCharacter(revivedCharacter);
|
|
}
|
|
|
|
characterInfos.Add(characterInfo);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove the character from the crew (and crew menus).
|
|
/// </summary>
|
|
/// <param name="character">The character to remove</param>
|
|
/// <param name="removeInfo">If the character info is also removed, the character will not be visible in the round summary.</param>
|
|
public void RemoveCharacter(Character character, bool removeInfo = false)
|
|
{
|
|
if (character == null)
|
|
{
|
|
DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
characters.Remove(character);
|
|
if (removeInfo) characterInfos.Remove(character.Info);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove info of a selected character. The character will not be visible in any menus or the round summary.
|
|
/// </summary>
|
|
/// <param name="characterInfo"></param>
|
|
public void RemoveCharacterInfo(CharacterInfo characterInfo)
|
|
{
|
|
characterInfos.Remove(characterInfo);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the UI component that holds the character's portrait and order/report buttons for the character
|
|
/// </summary>
|
|
private GUIComponent CreateCharacterFrame(Character character, GUIComponent parent)
|
|
{
|
|
int correctOrderCount = 0, neutralOrderCount = 0, wrongOrderCount = 0;
|
|
//sort the orders
|
|
// 1. generic orders (follow, wait, etc)
|
|
// 2. orders appropriate for the character's job (captain -> steer, etc)
|
|
// 3. orders inappropriate for the job (captain -> operate reactor, etc)
|
|
List<Order> orders = new List<Order>();
|
|
foreach (Order order in Order.PrefabList)
|
|
{
|
|
if (order.TargetAllCharacters || order.SymbolSprite == null) continue;
|
|
if (order.AppropriateJobs == null || order.AppropriateJobs.Length == 0)
|
|
{
|
|
orders.Insert(0, order);
|
|
correctOrderCount++;
|
|
}
|
|
else if (order.HasAppropriateJob(character))
|
|
{
|
|
orders.Add(order);
|
|
neutralOrderCount++;
|
|
}
|
|
}
|
|
foreach (Order order in Order.PrefabList)
|
|
{
|
|
if (order.SymbolSprite == null) continue;
|
|
if (!order.TargetAllCharacters && !orders.Contains(order))
|
|
{
|
|
orders.Add(order);
|
|
wrongOrderCount++;
|
|
}
|
|
}
|
|
|
|
int spacing = (int)(10 * GUI.Scale);
|
|
int height = (int)(45 * GUI.Scale);
|
|
characterInfoWidth = (int)(200 * GUI.Scale);
|
|
|
|
float charactersPerView = characterListBox.Rect.Height / (float)(height + characterListBox.Spacing);
|
|
|
|
//if we can fit less than 25% of the last character or more than 75%,
|
|
//change the size of the character frame slightly to fit them more nicely in the list box
|
|
if (charactersPerView % 1.0f < 0.25f || charactersPerView % 1.0f > 0.75f)
|
|
{
|
|
height = (int)(characterListBox.Rect.Height / (float)Math.Round(charactersPerView)) - characterListBox.Spacing;
|
|
}
|
|
|
|
int iconSize = (int)(height * 0.8f);
|
|
|
|
|
|
var frame = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, height), parent.RectTransform), style: "InnerFrame")
|
|
{
|
|
UserData = character,
|
|
CanBeFocused = false
|
|
};
|
|
frame.Color = character.Info.Job.Prefab.UIColor;
|
|
frame.SelectedColor = Color.Lerp(frame.Color, Color.White, 0.5f);
|
|
frame.HoverColor = Color.Lerp(frame.Color, Color.White, 0.9f);
|
|
|
|
new GUIFrame(new RectTransform(new Point(characterInfoWidth, (int)(frame.Rect.Height * 1.3f)), frame.RectTransform, Anchor.CenterLeft), style: "OuterGlow")
|
|
{
|
|
UserData = "highlight",
|
|
Color = frame.SelectedColor,
|
|
HoverColor = frame.SelectedColor,
|
|
PressedColor = frame.SelectedColor,
|
|
SelectedColor = frame.SelectedColor,
|
|
CanBeFocused = false
|
|
};
|
|
//---------------- character area ----------------
|
|
|
|
string characterToolTip = character.Info.Name;
|
|
if (character.Info.Job != null)
|
|
{
|
|
characterToolTip += " (" + character.Info.Job.Name + ")";
|
|
}
|
|
var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox")
|
|
{
|
|
UserData = character,
|
|
Color = frame.Color,
|
|
SelectedColor = frame.SelectedColor,
|
|
HoverColor = frame.HoverColor,
|
|
ToolTip = characterToolTip
|
|
};
|
|
|
|
var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
|
|
"GUISoundIcon")
|
|
{
|
|
UserData = "soundicon",
|
|
CanBeFocused = false,
|
|
Visible = true
|
|
};
|
|
soundIcon.Color = new Color(soundIcon.Color, 0.0f);
|
|
new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
|
|
"GUISoundIconDisabled")
|
|
{
|
|
UserData = "soundicondisabled",
|
|
CanBeFocused = true,
|
|
Visible = false
|
|
};
|
|
|
|
if (isSinglePlayer)
|
|
{
|
|
characterArea.OnClicked = CharacterClicked;
|
|
}
|
|
else
|
|
{
|
|
characterArea.CanBeFocused = false;
|
|
characterArea.CanBeSelected = false;
|
|
}
|
|
|
|
var characterImage = new GUICustomComponent(new RectTransform(new Point(characterArea.Rect.Height), characterArea.RectTransform, Anchor.CenterLeft),
|
|
onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
|
|
{
|
|
CanBeFocused = false,
|
|
HoverColor = Color.White,
|
|
SelectedColor = Color.White,
|
|
ToolTip = characterToolTip
|
|
};
|
|
|
|
var characterName = new GUITextBlock(new RectTransform(new Point(characterArea.Rect.Width - characterImage.Rect.Width - soundIcon.Rect.Width - 10, characterArea.Rect.Height),
|
|
characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(soundIcon.Rect.Width + 10, 0) },
|
|
character.Name, textColor: frame.Color, font: GUI.SmallFont, wrap: true)
|
|
{
|
|
Color = frame.Color,
|
|
HoverColor = Color.Transparent,
|
|
SelectedColor = Color.Transparent,
|
|
CanBeFocused = false,
|
|
ToolTip = characterToolTip,
|
|
AutoScale = true
|
|
};
|
|
|
|
//---------------- order buttons ----------------
|
|
|
|
var orderButtonFrame = new GUILayoutGroup(new RectTransform(new Point(100, frame.Rect.Height), frame.RectTransform)
|
|
{ AbsoluteOffset = new Point(characterInfoWidth + spacing, 0) },
|
|
isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
|
{
|
|
AbsoluteSpacing = (int)(10 * GUI.Scale),
|
|
UserData = "orderbuttons",
|
|
CanBeFocused = false
|
|
};
|
|
|
|
//listbox for holding the orders inappropriate for this character
|
|
//(so we can easily toggle their visibility)
|
|
var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null)
|
|
{
|
|
ScrollBarEnabled = false,
|
|
ScrollBarVisible = false,
|
|
Enabled = false,
|
|
Spacing = spacing,
|
|
ClampMouseRectToParent = false
|
|
};
|
|
wrongOrderList.Content.ClampMouseRectToParent = false;
|
|
|
|
for (int i = 0; i < orders.Count; i++)
|
|
{
|
|
var order = orders[i];
|
|
if (order.TargetAllCharacters) continue;
|
|
|
|
RectTransform btnParent = (i >= correctOrderCount + neutralOrderCount) ?
|
|
wrongOrderList.Content.RectTransform :
|
|
orderButtonFrame.RectTransform;
|
|
|
|
var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft),
|
|
style: null);
|
|
|
|
new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow")
|
|
{
|
|
Color = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.8f,
|
|
HoverColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 1.0f,
|
|
PressedColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.6f,
|
|
UserData = "selected",
|
|
CanBeFocused = false,
|
|
Visible = false
|
|
};
|
|
|
|
var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite);
|
|
img.Scale = iconSize / (float)img.SourceRect.Width;
|
|
img.Color = Color.Lerp(order.Color, frame.Color, 0.5f);
|
|
img.ToolTip = order.Name;
|
|
img.HoverColor = Color.Lerp(img.Color, Color.White, 0.5f);
|
|
|
|
btn.OnClicked += (GUIButton button, object userData) =>
|
|
{
|
|
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
|
|
|
|
if (btn.GetChildByUserData("selected").Visible)
|
|
{
|
|
SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled);
|
|
}
|
|
else
|
|
{
|
|
if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1)
|
|
{
|
|
CreateOrderTargetFrame(button, character, order);
|
|
}
|
|
else
|
|
{
|
|
SetCharacterOrder(character, order, null, Character.Controlled);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
btn.UserData = order;
|
|
btn.ToolTip = order.Name;
|
|
|
|
//divider between different groups of orders
|
|
if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1)
|
|
{
|
|
//TODO: divider sprite
|
|
new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton");
|
|
}
|
|
}
|
|
|
|
var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
|
|
"", style: "UIToggleButton")
|
|
{
|
|
UserData = "togglewrongorder",
|
|
CanBeFocused = false
|
|
};
|
|
|
|
wrongOrderList.RectTransform.NonScaledSize = new Point(
|
|
wrongOrderList.Content.Children.Sum(c => c.Rect.Width + wrongOrderList.Spacing),
|
|
wrongOrderList.RectTransform.NonScaledSize.Y);
|
|
wrongOrderList.RectTransform.SetAsLastChild();
|
|
|
|
new GUIFrame(new RectTransform(new Point(
|
|
wrongOrderList.Rect.Width - toggleWrongOrderBtn.Rect.Width - wrongOrderList.Spacing * 2,
|
|
wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
|
|
style: null)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
|
|
//scale to fit the content
|
|
orderButtonFrame.RectTransform.NonScaledSize = new Point(
|
|
orderButtonFrame.Children.Sum(c => c.Rect.Width + orderButtonFrame.AbsoluteSpacing),
|
|
orderButtonFrame.RectTransform.NonScaledSize.Y);
|
|
|
|
frame.RectTransform.NonScaledSize = new Point(
|
|
characterInfoWidth + spacing + (orderButtonFrame.Rect.Width - wrongOrderList.Rect.Width),
|
|
frame.RectTransform.NonScaledSize.Y);
|
|
|
|
characterListBox.RectTransform.NonScaledSize = new Point(
|
|
characterListBox.Content.Children.Max(c => c.Rect.Width) + wrongOrderList.Rect.Width,
|
|
characterListBox.RectTransform.NonScaledSize.Y);
|
|
characterListBox.Content.RectTransform.NonScaledSize = characterListBox.RectTransform.NonScaledSize;
|
|
characterListBox.UpdateScrollBarSize();
|
|
return frame;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets which character is selected in the crew UI (highlight effect etc)
|
|
/// </summary>
|
|
public bool CharacterClicked(GUIComponent component, object selection)
|
|
{
|
|
Character character = selection as Character;
|
|
if (character == null || character.IsDead || character.IsUnconscious) return false;
|
|
SelectCharacter(character);
|
|
return true;
|
|
}
|
|
|
|
public void ReviveCharacter(Character revivedCharacter)
|
|
{
|
|
if (characterListBox.Content.GetChildByUserData(revivedCharacter) is GUIComponent characterBlock)
|
|
{
|
|
characterBlock.Parent.RemoveChild(characterBlock);
|
|
}
|
|
if (characterInfos.Contains(revivedCharacter.Info)) AddCharacter(revivedCharacter);
|
|
}
|
|
|
|
public void KillCharacter(Character killedCharacter)
|
|
{
|
|
if (characterListBox.Content.GetChildByUserData(killedCharacter) is GUIComponent characterBlock)
|
|
{
|
|
CoroutineManager.StartCoroutine(KillCharacterAnim(characterBlock));
|
|
}
|
|
RemoveCharacter(killedCharacter);
|
|
}
|
|
|
|
private bool ScrollCharacterList(GUIButton button, object obj)
|
|
{
|
|
if (characterListBox.Content.CountChildren == 0) return false;
|
|
int dir = (int)obj;
|
|
|
|
float step =
|
|
(characterListBox.Content.Children.First().Rect.Height + characterListBox.Spacing) /
|
|
(characterListBox.TotalSize - characterListBox.Rect.Height);
|
|
characterListBox.BarScroll -= characterListBox.BarScroll % step;
|
|
characterListBox.BarScroll += dir * step;
|
|
|
|
return radioItem.GetComponent<WifiComponent>();
|
|
}
|
|
|
|
private IEnumerable<object> KillCharacterAnim(GUIComponent component)
|
|
{
|
|
List<GUIComponent> components = component.GetAllChildren().ToList();
|
|
components.Add(component);
|
|
components.RemoveAll(c => c.UserData as string == "soundicon" || c.UserData as string == "soundicondisabled");
|
|
|
|
foreach (GUIComponent comp in components)
|
|
{
|
|
comp.Color = Color.DarkRed;
|
|
}
|
|
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
|
c.AIController is HumanAIController &&
|
|
!c.IsDead &&
|
|
c.SpeechImpediment <= 100.0f);
|
|
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
|
}
|
|
|
|
yield return new WaitForSeconds(1.0f);
|
|
|
|
float timer = 0.0f;
|
|
float hideDuration = 1.0f;
|
|
while (timer < hideDuration)
|
|
{
|
|
foreach (GUIComponent comp in components)
|
|
{
|
|
comp.Color = Color.Lerp(Color.DarkRed, Color.Transparent, timer / hideDuration);
|
|
comp.RectTransform.LocalScale = new Vector2(comp.RectTransform.LocalScale.X, 1.0f - (timer / hideDuration));
|
|
}
|
|
timer += CoroutineManager.DeltaTime;
|
|
yield return CoroutineStatus.Running;
|
|
}
|
|
component.Parent?.RemoveChild(component);
|
|
characterListBox.UpdateScrollBarSize();
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Dialog
|
|
/// <summary>
|
|
/// Adds the message to the single player chatbox.
|
|
/// </summary>
|
|
public void AddSinglePlayerChatMessage(string senderName, string text, ChatMessageType messageType, Character sender)
|
|
{
|
|
if (!isSinglePlayer)
|
|
{
|
|
DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
if (string.IsNullOrEmpty(text)) { return; }
|
|
|
|
chatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
|
|
}
|
|
|
|
private WifiComponent GetHeadset(Character character, bool requireEquipped)
|
|
{
|
|
if (character?.Inventory == null) return null;
|
|
|
|
var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent<WifiComponent>() != null);
|
|
if (radioItem == null) return null;
|
|
if (requireEquipped && !character.HasEquippedItem(radioItem)) return null;
|
|
|
|
return radioItem.GetComponent<WifiComponent>();
|
|
}
|
|
|
|
partial void CreateRandomConversation()
|
|
{
|
|
if (GameMain.Client != null)
|
|
{
|
|
//let the server create random conversations in MP
|
|
return;
|
|
}
|
|
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
|
c.AIController is HumanAIController &&
|
|
!c.IsDead &&
|
|
c.SpeechImpediment <= 100.0f);
|
|
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Voice chat
|
|
|
|
public void SetPlayerVoiceIconState(Client client, bool muted, bool mutedLocally)
|
|
{
|
|
if (client?.Character == null) { return; }
|
|
|
|
var playerFrame = characterListBox.Content.FindChild(client.Character)?.FindChild(client.Character);
|
|
if (playerFrame == null) { return; }
|
|
var soundIcon = playerFrame.FindChild("soundicon");
|
|
var soundIconDisabled = playerFrame.FindChild("soundicondisabled");
|
|
|
|
if (!soundIcon.Visible)
|
|
{
|
|
soundIcon.Color = new Color(soundIcon.Color, 0.0f);
|
|
}
|
|
soundIcon.Visible = !muted && !mutedLocally;
|
|
soundIconDisabled.Visible = muted || mutedLocally;
|
|
soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally");
|
|
}
|
|
|
|
public void SetPlayerSpeaking(Client client)
|
|
{
|
|
if (client?.Character == null) { return; }
|
|
|
|
var playerFrame = characterListBox.Content.FindChild(client.Character)?.FindChild(client.Character);
|
|
if (playerFrame == null) { return; }
|
|
var soundIcon = playerFrame.FindChild("soundicon");
|
|
soundIcon.Color = new Color(soundIcon.Color, 1.0f);
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Sets the character's current order (if it's close enough to receive messages from orderGiver) and
|
|
/// displays the order in the crew UI
|
|
/// </summary>
|
|
public void SetCharacterOrder(Character character, Order order, string option, Character orderGiver)
|
|
{
|
|
if (order != null && order.TargetAllCharacters)
|
|
{
|
|
if (orderGiver == null || orderGiver.CurrentHull == null) return;
|
|
AddOrder(new Order(order.Prefab, orderGiver.CurrentHull, null), order.Prefab.FadeOutTime);
|
|
|
|
if (IsSinglePlayer)
|
|
{
|
|
orderGiver.Speak(
|
|
order.GetChatMessage("", orderGiver.CurrentHull?.RoomName, givingOrderToSelf: character == orderGiver), ChatMessageType.Order);
|
|
}
|
|
else
|
|
{
|
|
OrderChatMessage msg = new OrderChatMessage(order, "", orderGiver.CurrentHull, null, orderGiver);
|
|
if (GameMain.Client != null)
|
|
{
|
|
GameMain.Client.SendChatMessage(msg);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
character.SetOrder(order, option, orderGiver, speak: orderGiver != character);
|
|
if (IsSinglePlayer)
|
|
{
|
|
orderGiver?.Speak(
|
|
order.GetChatMessage(character.Name, orderGiver.CurrentHull?.RoomName, givingOrderToSelf: character == orderGiver, orderOption: option), null);
|
|
}
|
|
else if (orderGiver != null)
|
|
{
|
|
OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver);
|
|
if (GameMain.Client != null)
|
|
{
|
|
GameMain.Client.SendChatMessage(msg);
|
|
}
|
|
}
|
|
DisplayCharacterOrder(character, order);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Displays the specified order in the crew UI next to the character.
|
|
/// </summary>
|
|
public void DisplayCharacterOrder(Character character, Order order)
|
|
{
|
|
foreach (GUIComponent characterListElement in characterListBox.Content.Children)
|
|
{
|
|
var characterFrame = characterListElement.FindChild(character);
|
|
if (characterFrame == null) continue;
|
|
|
|
var orderButtonFrame = characterListElement.GetChildByUserData("orderbuttons");
|
|
|
|
//get all order buttons from the frame
|
|
List<GUIButton> orderButtons = new List<GUIButton>();
|
|
foreach (GUIComponent child in orderButtonFrame.Children)
|
|
{
|
|
if (child is GUIButton orderBtn)
|
|
{
|
|
orderButtons.Add(orderBtn);
|
|
}
|
|
//the non-character-appropriate orders are in a hideable listbox, we need to go deeper!
|
|
else if (child is GUIListBox listBox)
|
|
{
|
|
foreach (GUIComponent listBoxElement in listBox.Content.Children)
|
|
{
|
|
if (listBoxElement is GUIButton orderBtn2 && listBoxElement.UserData is Order) orderButtons.Add(orderBtn2);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (GUIButton button in orderButtons)
|
|
{
|
|
var selectedIndicator = button.GetChildByUserData("selected");
|
|
if (selectedIndicator != null)
|
|
{
|
|
selectedIndicator.Visible = (order != null && ((Order)button.UserData).Prefab == order.Prefab);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the UI panel that's used to select the target and options for a given order
|
|
/// (which railgun to use, whether to power up the reactor or shut it down...)
|
|
/// </summary>
|
|
private void CreateOrderTargetFrame(GUIComponent orderButton, Character character, Order order)
|
|
{
|
|
Submarine submarine = Character.Controlled != null && Character.Controlled.TeamID == Character.TeamType.Team2 && Submarine.MainSubs.Length > 1 ?
|
|
Submarine.MainSubs[1] :
|
|
Submarine.MainSub;
|
|
|
|
List<Item> matchingItems = new List<Item>();
|
|
if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0)
|
|
{
|
|
matchingItems = order.ItemIdentifiers.Length > 0 ?
|
|
Item.ItemList.FindAll(it => order.ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(order.ItemIdentifiers)) :
|
|
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == order.ItemComponentType));
|
|
|
|
matchingItems.RemoveAll(it => it.Submarine != submarine && !submarine.DockedTo.Contains(it.Submarine));
|
|
matchingItems.RemoveAll(it => it.Submarine != null && it.Submarine.IsOutpost);
|
|
}
|
|
|
|
//more than one target item -> create a minimap-like selection with a pic of the sub
|
|
if (matchingItems.Count > 1)
|
|
{
|
|
Rectangle subBorders = submarine.GetDockedBorders();
|
|
|
|
Point frameSize;
|
|
if (subBorders.Width > subBorders.Height)
|
|
{
|
|
//make sure the right side doesn't go over the right side of the screen
|
|
frameSize.X = Math.Min(GameMain.GraphicsWidth / 2, GameMain.GraphicsWidth - orderButton.Rect.Center.X - 50);
|
|
//height depends on the dimensions of the sub
|
|
frameSize.Y = (int)(frameSize.X * (subBorders.Height / (float)subBorders.Width));
|
|
}
|
|
else
|
|
{
|
|
//make sure the bottom side doesn't go over the bottom of the screen
|
|
frameSize.Y = Math.Min((int)(GameMain.GraphicsHeight * 0.6f), GameMain.GraphicsHeight - orderButton.Rect.Center.Y - 50);
|
|
//width depends on the dimensions of the sub
|
|
frameSize.X = (int)(frameSize.Y * (subBorders.Width / (float)subBorders.Height));
|
|
}
|
|
orderTargetFrame = new GUIFrame(new RectTransform(frameSize, GUI.Canvas)
|
|
{ AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) },
|
|
style: "InnerFrame")
|
|
{
|
|
UserData = character
|
|
};
|
|
submarine.CreateMiniMap(orderTargetFrame, matchingItems);
|
|
|
|
new GUICustomComponent(new RectTransform(Vector2.One, orderTargetFrame.RectTransform), DrawMiniMapOverlay)
|
|
{
|
|
CanBeFocused = false,
|
|
UserData = submarine
|
|
};
|
|
|
|
List<GUIComponent> optionFrames = new List<GUIComponent>();
|
|
foreach (Item item in matchingItems)
|
|
{
|
|
var itemTargetFrame = orderTargetFrame.Children.First().FindChild(item);
|
|
if (itemTargetFrame == null) continue;
|
|
|
|
Anchor anchor = Anchor.TopLeft;
|
|
if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f)
|
|
anchor = Anchor.BottomRight;
|
|
else if (itemTargetFrame.RectTransform.RelativeOffset.X > 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f)
|
|
anchor = Anchor.BottomLeft;
|
|
else if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f && itemTargetFrame.RectTransform.RelativeOffset.Y > 0.5f)
|
|
anchor = Anchor.TopRight;
|
|
|
|
var optionFrame = new GUIFrame(new RectTransform(new Point((int)(250 * GUI.Scale), (int)((40 + order.Options.Length * 40) * GUI.Scale)), itemTargetFrame.RectTransform, anchor),
|
|
style: "InnerFrame");
|
|
optionFrames.Add(optionFrame);
|
|
|
|
new GUIFrame(new RectTransform(Vector2.One, optionFrame.RectTransform, Anchor.Center),
|
|
style: "OuterGlow")
|
|
{
|
|
Color = Color.Black * 0.7f
|
|
};
|
|
|
|
var optionContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f), optionFrame.RectTransform, Anchor.Center))
|
|
{
|
|
Stretch = true,
|
|
RelativeSpacing = 0.05f
|
|
};
|
|
|
|
new GUITextBlock(new RectTransform(new Vector2(1.0f,0.3f), optionContainer.RectTransform), item != null ? item.Name : order.Name);
|
|
for (int i = 0; i < order.Options.Length; i++)
|
|
{
|
|
string option = order.Options[i];
|
|
var optionButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.2f), optionContainer.RectTransform),
|
|
order.OptionNames[i], style: "GUITextBox")
|
|
{
|
|
UserData = item == null ? order : new Order(order, item, item.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType)),
|
|
Font = GUI.SmallFont,
|
|
OnClicked = (btn, userData) =>
|
|
{
|
|
if (Character.Controlled == null) return false;
|
|
SetCharacterOrder(character, userData as Order, option, Character.Controlled);
|
|
orderTargetFrame = null;
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
GUI.PreventElementOverlap(optionFrames, null, new Rectangle(10, 10, GameMain.GraphicsWidth - 20, GameMain.GraphicsHeight - 20));
|
|
}
|
|
//only one target (or an order with no particular targets), just show options
|
|
else
|
|
{
|
|
orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas)
|
|
{ AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) },
|
|
isHorizontal: true, childAnchor: Anchor.BottomLeft)
|
|
{
|
|
UserData = character,
|
|
Stretch = true
|
|
};
|
|
//line connecting the order button to the option buttons
|
|
//TODO: sprite
|
|
new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), orderTargetFrame.RectTransform), style: null);
|
|
|
|
for (int i = 0; i < order.Options.Length; i++)
|
|
{
|
|
Item item = matchingItems.Count > 0 ? matchingItems[0] : null;
|
|
string option = order.Options[i];
|
|
var optionButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.5f), orderTargetFrame.RectTransform),
|
|
order.OptionNames[i], style: "GUITextBox")
|
|
{
|
|
UserData = item == null ? order : new Order(order, item, item.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType)),
|
|
OnClicked = (btn, userData) =>
|
|
{
|
|
if (Character.Controlled == null) return false;
|
|
SetCharacterOrder(character, userData as Order, option, Character.Controlled);
|
|
orderTargetFrame = null;
|
|
return true;
|
|
}
|
|
};
|
|
//lines between the order buttons
|
|
if (i < order.Options.Length - 1)
|
|
{
|
|
//TODO: sprite
|
|
new GUIFrame(new RectTransform(new Vector2(0.1f, 1.0f), orderTargetFrame.RectTransform), style: null);
|
|
}
|
|
}
|
|
}
|
|
int shadowSize = (int)(200 * GUI.Scale);
|
|
orderTargetFrameShadow = new GUIFrame(new RectTransform(orderTargetFrame.Rect.Size + new Point(shadowSize * 2), GUI.Canvas)
|
|
{ AbsoluteOffset = orderTargetFrame.Rect.Location - new Point(shadowSize) },
|
|
style: "OuterGlow",
|
|
color: matchingItems.Count > 1 ? Color.Black * 0.9f : Color.Black * 0.7f);
|
|
}
|
|
|
|
#region Updating and drawing the UI
|
|
|
|
private void DrawMiniMapOverlay(SpriteBatch spriteBatch, GUICustomComponent container)
|
|
{
|
|
Submarine sub = container.UserData as Submarine;
|
|
|
|
if (sub?.HullVertices == null) { return; }
|
|
|
|
var dockedBorders = sub.GetDockedBorders();
|
|
dockedBorders.Location += sub.WorldPosition.ToPoint();
|
|
|
|
float scale = Math.Min(
|
|
container.Rect.Width / (float)dockedBorders.Width,
|
|
container.Rect.Height / (float)dockedBorders.Height) * 0.9f;
|
|
|
|
float displayScale = ConvertUnits.ToDisplayUnits(scale);
|
|
Vector2 offset = (sub.WorldPosition - new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)) * scale;
|
|
Vector2 center = container.Rect.Center.ToVector2();
|
|
|
|
for (int i = 0; i < sub.HullVertices.Count; i++)
|
|
{
|
|
Vector2 start = (sub.HullVertices[i] * displayScale + offset);
|
|
start.Y = -start.Y;
|
|
Vector2 end = (sub.HullVertices[(i + 1) % sub.HullVertices.Count] * displayScale + offset);
|
|
end.Y = -end.Y;
|
|
GUI.DrawLine(spriteBatch, center + start, center + end, Color.DarkCyan * Rand.Range(0.3f, 0.35f), width: 10);
|
|
}
|
|
}
|
|
|
|
public void AddToGUIUpdateList()
|
|
{
|
|
if (GUI.DisableHUD || GUI.DisableUpperHUD) return;
|
|
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y ||
|
|
prevUIScale != GUI.Scale)
|
|
{
|
|
var prevCharacterListBox = characterListBox;
|
|
InitProjectSpecific();
|
|
|
|
foreach (GUIComponent c in prevCharacterListBox.Content.Children)
|
|
{
|
|
Character character = c.UserData as Character;
|
|
if (character == null) continue;
|
|
AddCharacter(character);
|
|
DisplayCharacterOrder(character, character.CurrentOrder);
|
|
}
|
|
}
|
|
|
|
guiFrame.AddToGUIUpdateList();
|
|
if (orderTargetFrame != null)
|
|
{
|
|
orderTargetFrameShadow?.AddToGUIUpdateList();
|
|
orderTargetFrame?.AddToGUIUpdateList();
|
|
}
|
|
}
|
|
|
|
public void SelectNextCharacter()
|
|
{
|
|
if (GameMain.IsMultiplayer) { return; }
|
|
if (characters.None()) { return; }
|
|
SelectCharacter(characters[TryAdjustIndex(1)]);
|
|
}
|
|
|
|
public void SelectPreviousCharacter()
|
|
{
|
|
if (GameMain.IsMultiplayer) { return; }
|
|
if (characters.None()) { return; }
|
|
SelectCharacter(characters[TryAdjustIndex(-1)]);
|
|
}
|
|
|
|
private void SelectCharacter(Character character)
|
|
{
|
|
//make the previously selected character wait in place for some time
|
|
//(so they don't immediately start idling and walking away from their station)
|
|
if (Character.Controlled?.AIController?.ObjectiveManager != null)
|
|
{
|
|
Character.Controlled.AIController.ObjectiveManager.WaitTimer = CharacterWaitOnSwitch;
|
|
}
|
|
Character.Controlled = character;
|
|
}
|
|
|
|
private int TryAdjustIndex(int amount)
|
|
{
|
|
int index = Character.Controlled == null ? 0 : characters.IndexOf(Character.Controlled) + amount;
|
|
int lastIndex = characters.Count - 1;
|
|
if (index > lastIndex)
|
|
{
|
|
index = 0;
|
|
}
|
|
if (index < 0)
|
|
{
|
|
index = lastIndex;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
partial void UpdateProjectSpecific(float deltaTime)
|
|
{
|
|
// Quick selection
|
|
if (!GameMain.IsMultiplayer && GUI.KeyboardDispatcher.Subscriber == null)
|
|
{
|
|
if (PlayerInput.KeyHit(InputType.SelectNextCharacter))
|
|
{
|
|
SelectNextCharacter();
|
|
}
|
|
if (PlayerInput.KeyHit(InputType.SelectPreviousCharacter))
|
|
{
|
|
SelectPreviousCharacter();
|
|
}
|
|
}
|
|
|
|
if (GUI.DisableHUD || GUI.DisableUpperHUD) return;
|
|
if (chatBox != null)
|
|
{
|
|
chatBox.Update(deltaTime);
|
|
chatBox.InputBox.Visible = Character.Controlled != null;
|
|
|
|
if (!DebugConsole.IsOpen && chatBox.InputBox.Visible)
|
|
{
|
|
if (PlayerInput.KeyHit(InputType.Chat) && !chatBox.InputBox.Selected)
|
|
{
|
|
chatBox.GUIFrame.Flash(Color.DarkGreen, 0.5f);
|
|
chatBox.InputBox.Select();
|
|
}
|
|
|
|
if (PlayerInput.KeyHit(InputType.RadioChat) && !chatBox.InputBox.Selected)
|
|
{
|
|
chatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
|
|
chatBox.InputBox.Select();
|
|
chatBox.InputBox.Text = "r; ";
|
|
}
|
|
}
|
|
}
|
|
|
|
crewArea.Visible = characters.Count > 0 && CharacterHealth.OpenHealthWindow == null;
|
|
if (orderTargetFrame != null) orderTargetFrame.Visible = characterListBox.Visible;
|
|
|
|
scrollButtonUp.Visible = characterListBox.BarScroll > 0.01f && characterListBox.BarSize < 1.0f;
|
|
scrollButtonDown.Visible = characterListBox.BarScroll < 0.99 && characterListBox.BarSize < 1.0f;
|
|
|
|
foreach (GUIComponent child in characterListBox.Content.Children)
|
|
{
|
|
Character character = (Character)child.UserData;
|
|
child.Visible =
|
|
Character.Controlled == null ||
|
|
(Character.Controlled.TeamID == character.TeamID);
|
|
|
|
if (child.Visible)
|
|
{
|
|
child.GetChildByUserData("highlight").Visible = character == Character.Controlled;
|
|
|
|
var soundIcon = child.FindChild(character)?.FindChild("soundicon");
|
|
if (soundIcon != null)
|
|
{
|
|
soundIcon.Color = new Color(soundIcon.Color, (soundIcon.Color.A / 255.0f) - deltaTime);
|
|
}
|
|
|
|
|
|
GUIListBox wrongOrderList = child.GetChildByUserData("orderbuttons")?.GetChild<GUIListBox>();
|
|
if (wrongOrderList != null)
|
|
{
|
|
Rectangle hoverRect = wrongOrderList.Rect;
|
|
if (wrongOrderList.BarScroll < 0.5f)
|
|
{
|
|
//higher tolerance when the orderlist is open (mouse needs to be moved further before it closes)
|
|
hoverRect.Inflate((int)(50 * GUI.Scale), (int)(50 * GUI.Scale));
|
|
}
|
|
else
|
|
{
|
|
hoverRect.Inflate((int)(30 * GUI.Scale), (int)(0 * GUI.Scale));
|
|
}
|
|
|
|
bool toggleOpen =
|
|
characterListBox.Content.Rect.Contains(PlayerInput.MousePosition) &&
|
|
hoverRect.Contains(PlayerInput.MousePosition);
|
|
wrongOrderList.CanBeFocused = toggleOpen;
|
|
wrongOrderList.Content.CanBeFocused = toggleOpen;
|
|
var wrongOrderBtn = wrongOrderList.GetChildByUserData("togglewrongorderbtn");
|
|
if (wrongOrderBtn != null)
|
|
{
|
|
wrongOrderBtn.CanBeFocused = toggleOpen;
|
|
}
|
|
|
|
//order target frame open on this character, check if we're giving any of the orders in wrongOrderList
|
|
if (!toggleOpen && orderTargetFrame != null && orderTargetFrame.UserData == child.UserData)
|
|
{
|
|
toggleOpen = wrongOrderList.Content.Children.Any(c =>
|
|
c.UserData is Order order &&
|
|
orderTargetFrame.Children.Any(c2 => c2.UserData == c.UserData));
|
|
}
|
|
|
|
float scroll = MathHelper.Clamp(wrongOrderList.BarScroll + (toggleOpen ? -deltaTime * 5.0f : deltaTime * 5.0f), 0.0f, 1.0f);
|
|
if (Math.Abs(wrongOrderList.BarScroll - scroll) > 0.01f) { wrongOrderList.BarScroll = scroll; }
|
|
}
|
|
}
|
|
}
|
|
|
|
crewArea.RectTransform.AbsoluteOffset =
|
|
Vector2.SmoothStep(new Vector2(-crewArea.Rect.Width, 0), new Vector2(toggleCrewButton.Rect.Width, 0), crewAreaOpenState).ToPoint();
|
|
crewAreaOpenState = toggleCrewAreaOpen ?
|
|
Math.Min(crewAreaOpenState + deltaTime * 2.0f, 1.0f) :
|
|
Math.Max(crewAreaOpenState - deltaTime * 2.0f, 0.0f);
|
|
|
|
if (GUI.KeyboardDispatcher.Subscriber == null &&
|
|
PlayerInput.KeyHit(InputType.CrewOrders) &&
|
|
characters.Contains(Character.Controlled))
|
|
{
|
|
//deselect construction unless it's the ladders the character is climbing
|
|
if (Character.Controlled != null &&
|
|
Character.Controlled.SelectedConstruction != null &&
|
|
Character.Controlled.SelectedConstruction.GetComponent<Items.Components.Ladder>() == null)
|
|
{
|
|
Character.Controlled.SelectedConstruction = null;
|
|
}
|
|
toggleCrewAreaOpen = !toggleCrewAreaOpen;
|
|
}
|
|
|
|
UpdateReports(deltaTime);
|
|
|
|
if (orderTargetFrame != null)
|
|
{
|
|
Rectangle hoverArea = orderTargetFrame.Rect;
|
|
foreach (GUIComponent child in orderTargetFrame.Children.First().Children)
|
|
{
|
|
if (!(child.UserData is Item)) continue;
|
|
foreach (GUIComponent grandChild in child.Children)
|
|
{
|
|
hoverArea = Rectangle.Union(hoverArea, grandChild.Rect);
|
|
}
|
|
}
|
|
hoverArea.Inflate(100, 100);
|
|
|
|
if (!hoverArea.Contains(PlayerInput.MousePosition)) orderTargetFrame = null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Creates a listbox that includes all the characters in the crew, can be used externally (round info menus etc)
|
|
/// </summary>
|
|
public void CreateCrewListFrame(IEnumerable<Character> crew, GUIFrame crewFrame)
|
|
{
|
|
List<Character.TeamType> teamIDs = crew.Select(c => c.TeamID).Distinct().ToList();
|
|
|
|
if (!teamIDs.Any()) teamIDs.Add(Character.TeamType.None);
|
|
|
|
int listBoxHeight = 300 / teamIDs.Count;
|
|
|
|
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), crewFrame.RectTransform))
|
|
{
|
|
Stretch = true
|
|
};
|
|
|
|
for (int i = 0; i < teamIDs.Count; i++)
|
|
{
|
|
if (teamIDs.Count > 1)
|
|
{
|
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), content.RectTransform), CombatMission.GetTeamName(teamIDs[i]));
|
|
}
|
|
|
|
GUIListBox crewList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) })
|
|
{
|
|
UserData = crewFrame
|
|
};
|
|
crewList.OnSelected = (component, obj) =>
|
|
{
|
|
SelectCrewCharacter(component.UserData as Character, crewList);
|
|
return true;
|
|
};
|
|
|
|
foreach (Character character in crew.Where(c => c.TeamID == teamIDs[i]))
|
|
{
|
|
GUIFrame frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), crewList.Content.RectTransform), style: "ListBoxElement")
|
|
{
|
|
UserData = character,
|
|
Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? Color.Gold * 0.2f : Color.Transparent
|
|
};
|
|
|
|
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
|
|
{
|
|
RelativeSpacing = 0.05f,
|
|
Stretch = true
|
|
};
|
|
|
|
new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), paddedFrame.RectTransform, Anchor.CenterLeft),
|
|
onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
|
|
{
|
|
CanBeFocused = false,
|
|
HoverColor = Color.White,
|
|
SelectedColor = Color.White
|
|
};
|
|
|
|
GUITextBlock textBlock = new GUITextBlock(new RectTransform(Vector2.One, paddedFrame.RectTransform),
|
|
ToolBox.LimitString(character.Info.Name + " (" + character.Info.Job.Name + ")", GUI.Font, paddedFrame.Rect.Width - paddedFrame.Rect.Height),
|
|
textColor: character.Info.Job.Prefab.UIColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Select a character from CrewListFrame
|
|
/// </summary>
|
|
protected bool SelectCrewCharacter(Character character, GUIComponent crewList)
|
|
{
|
|
if (character == null) return false;
|
|
|
|
GUIComponent crewFrame = (GUIComponent)crewList.UserData;
|
|
|
|
GUIComponent existingPreview = crewFrame.FindChild("SelectedCharacter");
|
|
if (existingPreview != null) crewFrame.RemoveChild(existingPreview);
|
|
|
|
var previewPlayer = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.8f), crewFrame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.05f, 0.0f) }, style: "InnerFrame")
|
|
{
|
|
UserData = "SelectedCharacter"
|
|
};
|
|
|
|
character.Info.CreateInfoFrame(previewPlayer);
|
|
|
|
if (GameMain.NetworkMember != null) GameMain.Client.SelectCrewCharacter(character, previewPlayer);
|
|
|
|
private bool ReportButtonClicked(GUIButton button, object userData)
|
|
{
|
|
//order targeted to all characters
|
|
Order order = userData as Order;
|
|
if (order.TargetAllCharacters)
|
|
{
|
|
if (Character.Controlled == null || Character.Controlled.CurrentHull == null) return false;
|
|
AddOrder(new Order(order.Prefab, Character.Controlled.CurrentHull, null), order.Prefab.FadeOutTime);
|
|
SetCharacterOrder(null, order, "", Character.Controlled);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#region Reports
|
|
|
|
/// <summary>
|
|
/// Enables/disables report buttons when needed
|
|
/// </summary>
|
|
public void UpdateReports(float deltaTime)
|
|
{
|
|
bool canIssueOrders = false;
|
|
if (Character.Controlled?.CurrentHull != null && Character.Controlled.SpeechImpediment < 100.0f)
|
|
{
|
|
WifiComponent radio = GetHeadset(Character.Controlled, true);
|
|
canIssueOrders = radio != null && radio.CanTransmit();
|
|
}
|
|
|
|
if (canIssueOrders)
|
|
{
|
|
reportButtonFrame.Visible = true;
|
|
|
|
var reportButtonParent = chatBox ?? GameMain.Client.ChatBox;
|
|
reportButtonFrame.RectTransform.AbsoluteOffset = new Point(
|
|
Math.Min(reportButtonParent.GUIFrame.Rect.X, reportButtonParent.ToggleButton.Rect.X) - reportButtonFrame.Rect.Width - (int)(10 * GUI.Scale),
|
|
reportButtonParent.GUIFrame.Rect.Y);
|
|
|
|
bool hasFires = Character.Controlled.CurrentHull.FireSources.Count > 0;
|
|
ToggleReportButton("reportfire", hasFires);
|
|
|
|
bool hasLeaks = Character.Controlled.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f);
|
|
ToggleReportButton("reportbreach", hasLeaks);
|
|
|
|
bool hasIntruders = Character.CharacterList.Any(c =>
|
|
c.CurrentHull == Character.Controlled.CurrentHull && !c.IsDead &&
|
|
(c.AIController is EnemyAIController || (c.TeamID != Character.Controlled.TeamID && c.TeamID != Character.TeamType.FriendlyNPC)));
|
|
|
|
ToggleReportButton("reportintruders", hasIntruders);
|
|
|
|
foreach (GUIComponent reportButton in reportButtonFrame.Children)
|
|
{
|
|
var highlight = reportButton.GetChildByUserData("highlighted");
|
|
if (highlight.Visible)
|
|
{
|
|
highlight.RectTransform.LocalScale = new Vector2(1.25f + (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.25f);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
reportButtonFrame.Visible = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should report buttons be visible on the screen atm?
|
|
/// </summary>
|
|
private bool ReportButtonsVisible()
|
|
{
|
|
return CharacterHealth.OpenHealthWindow == null;
|
|
}
|
|
|
|
private bool ReportButtonClicked(GUIButton button, object userData)
|
|
{
|
|
//order targeted to all characters
|
|
Order order = userData as Order;
|
|
if (order.TargetAllCharacters)
|
|
{
|
|
if (Character.Controlled == null || Character.Controlled.CurrentHull == null) return false;
|
|
AddOrder(new Order(order.Prefab, Character.Controlled.CurrentHull, null), order.Prefab.FadeOutTime);
|
|
SetCharacterOrder(null, order, "", Character.Controlled);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void ToggleReportButton(string orderAiTag, bool enabled)
|
|
{
|
|
Order order = Order.PrefabList.Find(o => o.AITag == orderAiTag);
|
|
|
|
//already reported, disable the button
|
|
/*if (GameMain.GameSession.CrewManager.ActiveOrders.Any(o =>
|
|
o.First.TargetEntity == Character.Controlled.CurrentHull &&
|
|
o.First.AITag == orderAiTag))
|
|
{
|
|
enabled = false;
|
|
}*/
|
|
|
|
var reportButton = reportButtonFrame.GetChildByUserData(order);
|
|
if (reportButton != null)
|
|
{
|
|
reportButton.GetChildByUserData("highlighted").Visible = enabled;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void InitSinglePlayerRound()
|
|
{
|
|
characterListBox.ClearChildren();
|
|
characters.Clear();
|
|
|
|
WayPoint[] waypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
|
|
|
|
for (int i = 0; i < waypoints.Length; i++)
|
|
{
|
|
Character character;
|
|
character = Character.Create(characterInfos[i], waypoints[i].WorldPosition, characterInfos[i].Name);
|
|
if (character.Info != null && !character.Info.StartItemsGiven)
|
|
{
|
|
character.GiveJobItems(waypoints[i]);
|
|
character.Info.StartItemsGiven = true;
|
|
}
|
|
|
|
if (character.Info?.InventoryData != null)
|
|
{
|
|
character.Info.SpawnInventoryItems(character.Inventory, character.Info.InventoryData);
|
|
}
|
|
|
|
AddCharacter(character);
|
|
if (i == 0)
|
|
{
|
|
Character.Controlled = character;
|
|
}
|
|
}
|
|
|
|
if (characters.Any()) characterListBox.Select(0);
|
|
|
|
conversationTimer = Rand.Range(5.0f, 10.0f);
|
|
}
|
|
|
|
public void EndRound()
|
|
{
|
|
//remove characterinfos whose characters have been removed or killed
|
|
characterInfos.RemoveAll(c => c.Character == null || c.Character.Removed || c.CauseOfDeath != null);
|
|
|
|
characters.Clear();
|
|
characterListBox.ClearChildren();
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
characters.Clear();
|
|
characterInfos.Clear();
|
|
characterListBox.ClearChildren();
|
|
}
|
|
|
|
public void Save(XElement parentElement)
|
|
{
|
|
XElement element = new XElement("crew");
|
|
|
|
foreach (CharacterInfo ci in characterInfos)
|
|
{
|
|
var infoElement = ci.Save(element);
|
|
if (ci.InventoryData != null)
|
|
{
|
|
infoElement.Add(ci.InventoryData);
|
|
}
|
|
}
|
|
parentElement.Add(element);
|
|
}
|
|
}
|
|
}
|