Unstable v0.19.3.0
This commit is contained in:
@@ -179,9 +179,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
|
||||
|
||||
msg.Write((byte)ClientNetObject.SPECTATING_POS);
|
||||
msg.Write(position.X);
|
||||
msg.Write(position.Y);
|
||||
msg.WriteByte((byte)ClientNetObject.SPECTATING_POS);
|
||||
msg.WriteSingle(position.X);
|
||||
msg.WriteSingle(position.Y);
|
||||
}
|
||||
|
||||
private void CreateMatrices()
|
||||
|
||||
@@ -114,27 +114,27 @@ namespace Barotrauma
|
||||
|
||||
public void ClientWriteInput(IWriteMessage msg)
|
||||
{
|
||||
msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
|
||||
msg.WriteByte((byte)ClientNetObject.CHARACTER_INPUT);
|
||||
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
memInput.RemoveRange(60, memInput.Count - 60);
|
||||
}
|
||||
|
||||
msg.Write(LastNetworkUpdateID);
|
||||
msg.WriteUInt16(LastNetworkUpdateID);
|
||||
byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
|
||||
msg.Write(inputCount);
|
||||
msg.WriteByte(inputCount);
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
msg.WriteRangedInteger((int)memInput[i].states, 0, (int)InputNetFlags.MaxVal);
|
||||
msg.Write(memInput[i].intAim);
|
||||
msg.WriteUInt16(memInput[i].intAim);
|
||||
if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
|
||||
memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
|
||||
memInput[i].states.HasFlag(InputNetFlags.Use) ||
|
||||
memInput[i].states.HasFlag(InputNetFlags.Health) ||
|
||||
memInput[i].states.HasFlag(InputNetFlags.Grab))
|
||||
{
|
||||
msg.Write(memInput[i].interact);
|
||||
msg.WriteUInt16(memInput[i].interact);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,16 +150,16 @@ namespace Barotrauma
|
||||
Inventory.ClientEventWrite(msg, inventoryStateEventData);
|
||||
break;
|
||||
case TreatmentEventData _:
|
||||
msg.Write(AnimController.Anim == AnimController.Animation.CPR);
|
||||
msg.WriteBoolean(AnimController.Anim == AnimController.Animation.CPR);
|
||||
break;
|
||||
case CharacterStatusEventData _:
|
||||
//do nothing
|
||||
break;
|
||||
case UpdateTalentsEventData _:
|
||||
msg.Write((ushort)characterTalents.Count);
|
||||
msg.WriteUInt16((ushort)characterTalents.Count);
|
||||
foreach (var unlockedTalent in characterTalents)
|
||||
{
|
||||
msg.Write(unlockedTalent.Prefab.UintIdentifier);
|
||||
msg.WriteUInt32(unlockedTalent.Prefab.UintIdentifier);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
get => name;
|
||||
set
|
||||
{
|
||||
var charsToRemove = Path.GetInvalidFileNameChars();
|
||||
var charsToRemove = Path.GetInvalidFileNameCharsCrossPlatform();
|
||||
name = string.Concat(value.Where(c => !charsToRemove.Contains(c)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,7 +723,7 @@ namespace Barotrauma
|
||||
|
||||
AssignOnExecute("explosion", (string[] args) =>
|
||||
{
|
||||
Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
Vector2 explosionPos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
float range = 500, force = 10, damage = 50, structureDamage = 20, itemDamage = 100, empStrength = 0.0f, ballastFloraStrength = 50f;
|
||||
if (args.Length > 0) float.TryParse(args[0], out range);
|
||||
if (args.Length > 1) float.TryParse(args[1], out force);
|
||||
@@ -3056,6 +3056,11 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("reloadwearables", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args =>
|
||||
{
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
ThrowError("Using the command is not allowed during an active game session: the command is intended to be used in the character editor or in the main menu.");
|
||||
return;
|
||||
}
|
||||
var character = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, true);
|
||||
if (character == null)
|
||||
{
|
||||
@@ -3067,6 +3072,11 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("loadwearable", "Force select certain variant for the selected character.", args =>
|
||||
{
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
ThrowError("Using the command is not allowed during an active game session: the command is intended to be used in the character editor or in the main menu.");
|
||||
return;
|
||||
}
|
||||
var character = Character.Controlled;
|
||||
if (character == null)
|
||||
{
|
||||
|
||||
@@ -381,18 +381,18 @@ namespace Barotrauma
|
||||
private static void SendResponse(UInt16 actionId, int selectedOption)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE);
|
||||
outmsg.Write(actionId);
|
||||
outmsg.Write((byte)selectedOption);
|
||||
outmsg.WriteByte((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE);
|
||||
outmsg.WriteUInt16(actionId);
|
||||
outmsg.WriteByte((byte)selectedOption);
|
||||
GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
private static void SendIgnore(UInt16 actionId)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE);
|
||||
outmsg.Write(actionId);
|
||||
outmsg.Write(byte.MaxValue);
|
||||
outmsg.WriteByte((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE);
|
||||
outmsg.WriteUInt16(actionId);
|
||||
outmsg.WriteByte(byte.MaxValue);
|
||||
GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CombatMission : Mission
|
||||
{
|
||||
@@ -8,7 +6,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (descriptions == null) return "";
|
||||
if (descriptions == null) { return ""; }
|
||||
|
||||
if (GameMain.Client?.Character == null)
|
||||
{
|
||||
|
||||
@@ -80,6 +80,10 @@ namespace Barotrauma
|
||||
|
||||
LocalizedString header = messageIndex < Headers.Length ? Headers[messageIndex] : "";
|
||||
LocalizedString message = messageIndex < Messages.Length ? Messages[messageIndex] : "";
|
||||
if (!message.IsNullOrEmpty())
|
||||
{
|
||||
message = ModifyMessage(message);
|
||||
}
|
||||
|
||||
CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message));
|
||||
}
|
||||
|
||||
@@ -325,6 +325,12 @@ namespace Barotrauma
|
||||
ToggleOpen = PreferChatBoxOpen = GameSettings.CurrentConfig.ChatOpen;
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
ToggleOpen = !ToggleOpen;
|
||||
CloseAfterMessageSent = false;
|
||||
}
|
||||
|
||||
public bool TypingChatMessage(GUITextBox textBox, string text)
|
||||
{
|
||||
string command = ChatMessage.GetChatMessageCommand(text, out _);
|
||||
@@ -605,22 +611,29 @@ namespace Barotrauma
|
||||
showNewMessagesButton.Visible = false;
|
||||
}
|
||||
|
||||
if (PlayerInput.KeyHit(InputType.ToggleChatMode) && GUI.KeyboardDispatcher.Subscriber == null && Screen.Selected == GameMain.GameScreen)
|
||||
if (Screen.Selected == GameMain.GameScreen && GUI.KeyboardDispatcher.Subscriber == null)
|
||||
{
|
||||
try
|
||||
if (PlayerInput.KeyHit(InputType.ToggleChatMode))
|
||||
{
|
||||
var mode = GameMain.ActiveChatMode switch
|
||||
try
|
||||
{
|
||||
ChatMode.Local => ChatMode.Radio,
|
||||
ChatMode.Radio => ChatMode.Local,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
ChatModeDropDown.SelectItem(mode);
|
||||
// TODO: Play a sound?
|
||||
var mode = GameMain.ActiveChatMode switch
|
||||
{
|
||||
ChatMode.Local => ChatMode.Radio,
|
||||
ChatMode.Radio => ChatMode.Local,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
ChatModeDropDown.SelectItem(mode);
|
||||
// TODO: Play a sound?
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\"");
|
||||
}
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
else if (PlayerInput.KeyHit(InputType.ChatBox))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\"");
|
||||
Toggle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -889,35 +889,35 @@ namespace Barotrauma
|
||||
if (campaign is MultiPlayerCampaign)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.CREW);
|
||||
msg.WriteByte((byte)ClientPacketHeader.CREW);
|
||||
|
||||
msg.Write(updatePending);
|
||||
msg.WriteBoolean(updatePending);
|
||||
if (updatePending)
|
||||
{
|
||||
msg.Write((ushort)PendingHires.Count);
|
||||
msg.WriteUInt16((ushort)PendingHires.Count);
|
||||
foreach (CharacterInfo pendingHire in PendingHires)
|
||||
{
|
||||
msg.Write(pendingHire.GetIdentifierUsingOriginalName());
|
||||
msg.WriteInt32(pendingHire.GetIdentifierUsingOriginalName());
|
||||
}
|
||||
}
|
||||
|
||||
msg.Write(validateHires);
|
||||
msg.WriteBoolean(validateHires);
|
||||
|
||||
bool validRenaming = renameCharacter.info != null && !string.IsNullOrEmpty(renameCharacter.newName);
|
||||
msg.Write(validRenaming);
|
||||
msg.WriteBoolean(validRenaming);
|
||||
if (validRenaming)
|
||||
{
|
||||
int identifier = renameCharacter.info.GetIdentifierUsingOriginalName();
|
||||
msg.Write(identifier);
|
||||
msg.Write(renameCharacter.newName);
|
||||
msg.WriteInt32(identifier);
|
||||
msg.WriteString(renameCharacter.newName);
|
||||
bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.GetIdentifierUsingOriginalName() == identifier) ?? false;
|
||||
msg.Write(existingCrewMember);
|
||||
msg.WriteBoolean(existingCrewMember);
|
||||
}
|
||||
|
||||
msg.Write(firedCharacter != null);
|
||||
msg.WriteBoolean(firedCharacter != null);
|
||||
if (firedCharacter != null)
|
||||
{
|
||||
msg.Write(firedCharacter.GetIdentifier());
|
||||
msg.WriteInt32(firedCharacter.GetIdentifier());
|
||||
}
|
||||
|
||||
GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||
|
||||
@@ -1018,6 +1018,11 @@ namespace Barotrauma
|
||||
|
||||
if (c.Enabled)
|
||||
{
|
||||
var dragHandle = c as GUIDragHandle ?? parent as GUIDragHandle;
|
||||
if (dragHandle != null)
|
||||
{
|
||||
return dragHandle.Dragging ? CursorState.Dragging : CursorState.Hand;
|
||||
}
|
||||
// Some parent elements take priority
|
||||
// but not when the child is a GUIButton or GUITickBox
|
||||
if (!(parent is GUIButton) && !(parent is GUIListBox) ||
|
||||
@@ -1027,6 +1032,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Children in list boxes can be interacted with despite not having
|
||||
// a GUIButton inside of them so instead of hard coding we check if
|
||||
// the children can be interacted with by checking their hover state
|
||||
@@ -1097,6 +1103,8 @@ namespace Barotrauma
|
||||
return list;
|
||||
case GUIScrollBar bar:
|
||||
return bar;
|
||||
case GUIDragHandle dragHandle:
|
||||
return dragHandle;
|
||||
}
|
||||
}
|
||||
component = parent;
|
||||
|
||||
@@ -813,9 +813,9 @@ namespace Barotrauma
|
||||
flashColor = (color == null) ? GUIStyle.Red : (Color)color;
|
||||
}
|
||||
|
||||
public void FadeOut(float duration, bool removeAfter, float wait = 0.0f)
|
||||
public void FadeOut(float duration, bool removeAfter, float wait = 0.0f, Action onRemove = null)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait));
|
||||
CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait, onRemove));
|
||||
}
|
||||
|
||||
public void FadeIn(float wait, float duration)
|
||||
@@ -877,7 +877,7 @@ namespace Barotrauma
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f)
|
||||
private IEnumerable<CoroutineStatus> LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f, Action onRemove = null)
|
||||
{
|
||||
State = ComponentState.None;
|
||||
float t = 0.0f;
|
||||
@@ -902,6 +902,7 @@ namespace Barotrauma
|
||||
if (removeAfter && Parent != null)
|
||||
{
|
||||
Parent.RemoveChild(this);
|
||||
onRemove?.Invoke();
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Barotrauma
|
||||
|
||||
public Func<RectTransform, bool> ValidatePosition;
|
||||
|
||||
public bool Dragging => dragStarted;
|
||||
|
||||
public GUIDragHandle(RectTransform rectT, RectTransform elementToMove, string style = "GUIDragIndicator")
|
||||
: base(style, rectT)
|
||||
{
|
||||
|
||||
@@ -57,7 +57,8 @@ namespace Barotrauma
|
||||
{
|
||||
SelectSingle,
|
||||
SelectMultiple,
|
||||
RequireShiftToSelectMultiple
|
||||
RequireShiftToSelectMultiple,
|
||||
None
|
||||
}
|
||||
|
||||
public SelectMode CurrentSelectMode = SelectMode.SelectSingle;
|
||||
@@ -1058,7 +1059,7 @@ namespace Barotrauma
|
||||
|
||||
public void Select(int childIndex, Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus = TakeKeyBoardFocus.No, PlaySelectSound playSelectSound = PlaySelectSound.No)
|
||||
{
|
||||
if (childIndex >= Content.CountChildren || childIndex < 0) { return; }
|
||||
if (childIndex >= Content.CountChildren || childIndex < 0 || CurrentSelectMode == SelectMode.None) { return; }
|
||||
|
||||
GUIComponent child = Content.GetChild(childIndex);
|
||||
if (child is null) { return; }
|
||||
@@ -1157,6 +1158,7 @@ namespace Barotrauma
|
||||
|
||||
public void Select(IEnumerable<GUIComponent> children)
|
||||
{
|
||||
if (CurrentSelectMode == SelectMode.None) { return; }
|
||||
Selected = true;
|
||||
selected.Clear();
|
||||
selected.AddRange(children.Where(c => Content.Children.Contains(c)));
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -25,9 +23,12 @@ namespace Barotrauma
|
||||
Default,
|
||||
InGame,
|
||||
Vote,
|
||||
Hint
|
||||
Hint,
|
||||
Tutorial
|
||||
}
|
||||
|
||||
private bool IsAnimated => type == Type.InGame || type == Type.Hint || type == Type.Tutorial;
|
||||
|
||||
public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>();
|
||||
public GUILayoutGroup Content { get; private set; }
|
||||
public GUIFrame InnerFrame { get; private set; }
|
||||
@@ -60,6 +61,9 @@ namespace Barotrauma
|
||||
public GUIImage BackgroundIcon { get; private set; }
|
||||
private GUIImage newBackgroundIcon;
|
||||
|
||||
/// <summary>
|
||||
/// Close the message box automatically after enough time has passed (<see cref="inGameCloseTime"/>)
|
||||
/// </summary>
|
||||
public bool AutoClose;
|
||||
|
||||
private float openState;
|
||||
@@ -69,6 +73,11 @@ namespace Barotrauma
|
||||
|
||||
private readonly Type type;
|
||||
|
||||
/// <summary>
|
||||
/// Close the message box automatically if the condition is met
|
||||
/// </summary>
|
||||
private readonly Func<bool> autoCloseCondition;
|
||||
|
||||
public Type MessageBoxType => type;
|
||||
|
||||
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
|
||||
@@ -79,7 +88,9 @@ namespace Barotrauma
|
||||
this.Buttons[0].OnClicked = Close;
|
||||
}
|
||||
|
||||
public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null)
|
||||
public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons,
|
||||
Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "",
|
||||
Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null, Func<bool> autoCloseCondition = null, bool hideCloseButton = false)
|
||||
: base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUIStyle.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox")
|
||||
{
|
||||
int width = (int)(DefaultWidth * type switch
|
||||
@@ -116,6 +127,7 @@ namespace Barotrauma
|
||||
Anchor anchor = type switch
|
||||
{
|
||||
Type.InGame => Anchor.TopCenter,
|
||||
Type.Tutorial => Anchor.TopCenter,
|
||||
Type.Hint => Anchor.TopRight,
|
||||
Type.Vote => Anchor.TopRight,
|
||||
_ => Anchor.Center
|
||||
@@ -188,7 +200,7 @@ namespace Barotrauma
|
||||
Buttons.Add(button);
|
||||
}
|
||||
}
|
||||
else if (type == Type.InGame)
|
||||
else if (type == Type.InGame || type == Type.Tutorial)
|
||||
{
|
||||
InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight);
|
||||
CanBeFocused = false;
|
||||
@@ -212,37 +224,43 @@ namespace Barotrauma
|
||||
|
||||
Content = new GUILayoutGroup(new RectTransform(new Vector2(Icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform));
|
||||
|
||||
var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null);
|
||||
Buttons = new List<GUIButton>(1)
|
||||
if (!hideCloseButton)
|
||||
{
|
||||
new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
|
||||
style: "UIToggleButton")
|
||||
var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null);
|
||||
Buttons = new List<GUIButton>(1)
|
||||
{
|
||||
OnClicked = Close
|
||||
}
|
||||
};
|
||||
|
||||
InputType? closeInput = null;
|
||||
if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Use].MouseButton == MouseButton.None)
|
||||
{
|
||||
closeInput = InputType.Use;
|
||||
}
|
||||
else if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select].MouseButton == MouseButton.None)
|
||||
{
|
||||
closeInput = InputType.Select;
|
||||
}
|
||||
if (closeInput.HasValue)
|
||||
{
|
||||
Buttons[0].ToolTip = TextManager.ParseInputTypes($"{TextManager.Get("Close")} ([InputType.{closeInput.Value}])");
|
||||
Buttons[0].OnAddedToGUIUpdateList += (GUIComponent component) =>
|
||||
{
|
||||
if (!closing && openState >= 1.0f && PlayerInput.KeyHit(closeInput.Value))
|
||||
new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
|
||||
style: "UIToggleButton")
|
||||
{
|
||||
GUIButton btn = component as GUIButton;
|
||||
btn?.OnClicked(btn, btn.UserData);
|
||||
btn?.Flash(GUIStyle.Green);
|
||||
OnClicked = Close
|
||||
}
|
||||
};
|
||||
InputType? closeInput = null;
|
||||
if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Use].MouseButton == MouseButton.None)
|
||||
{
|
||||
closeInput = InputType.Use;
|
||||
}
|
||||
else if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select].MouseButton == MouseButton.None)
|
||||
{
|
||||
closeInput = InputType.Select;
|
||||
}
|
||||
if (closeInput.HasValue)
|
||||
{
|
||||
Buttons[0].ToolTip = TextManager.ParseInputTypes($"{TextManager.Get("Close")} ([InputType.{closeInput.Value}])");
|
||||
Buttons[0].OnAddedToGUIUpdateList += (GUIComponent component) =>
|
||||
{
|
||||
if (!closing && openState >= 1.0f && PlayerInput.KeyHit(closeInput.Value))
|
||||
{
|
||||
GUIButton btn = component as GUIButton;
|
||||
btn?.OnClicked(btn, btn.UserData);
|
||||
btn?.Flash(GUIStyle.Green);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Buttons.Clear();
|
||||
}
|
||||
|
||||
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true);
|
||||
@@ -274,7 +292,10 @@ namespace Barotrauma
|
||||
Content.RectTransform.NonScaledSize =
|
||||
new Point(Content.Rect.Width, height);
|
||||
}
|
||||
Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y);
|
||||
if (!hideCloseButton)
|
||||
{
|
||||
Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y);
|
||||
}
|
||||
}
|
||||
else if (type == Type.Hint)
|
||||
{
|
||||
@@ -408,6 +429,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
this.autoCloseCondition = autoCloseCondition;
|
||||
|
||||
MessageBoxes.Add(this);
|
||||
}
|
||||
|
||||
@@ -511,13 +534,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (type == Type.InGame || type == Type.Hint)
|
||||
if (IsAnimated)
|
||||
{
|
||||
Vector2 initialPos, defaultPos, endPos;
|
||||
if (type == Type.InGame)
|
||||
if (type == Type.InGame || type == Type.Tutorial)
|
||||
{
|
||||
initialPos = new Vector2(0.0f, GameMain.GraphicsHeight);
|
||||
defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale);
|
||||
defaultPos = type == Type.InGame ?
|
||||
new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale) :
|
||||
new Vector2(0.0f, GameMain.GraphicsHeight / 2);
|
||||
endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y);
|
||||
}
|
||||
else
|
||||
@@ -549,7 +574,7 @@ namespace Barotrauma
|
||||
inGameCloseTimer += deltaTime;
|
||||
}
|
||||
|
||||
if (inGameCloseTimer >= inGameCloseTime)
|
||||
if (inGameCloseTimer >= inGameCloseTime || (autoCloseCondition != null && autoCloseCondition()))
|
||||
{
|
||||
Close();
|
||||
}
|
||||
@@ -612,7 +637,7 @@ namespace Barotrauma
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (type == Type.InGame || type == Type.Hint)
|
||||
if (IsAnimated)
|
||||
{
|
||||
closing = true;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,23 @@ namespace Barotrauma
|
||||
public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium");
|
||||
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh");
|
||||
|
||||
public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
|
||||
public static Point ItemFrameMargin
|
||||
{
|
||||
get
|
||||
{
|
||||
Point size = new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
|
||||
|
||||
var style = GetComponentStyle("ItemUI");
|
||||
var sprite = style?.Sprites[GUIComponent.ComponentState.None].First();
|
||||
if (sprite != null)
|
||||
{
|
||||
size.X = Math.Min(sprite.Slices[0].Width + sprite.Slices[2].Width, size.X);
|
||||
size.Y = Math.Min(sprite.Slices[0].Height + sprite.Slices[6].Height, size.Y);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
|
||||
|
||||
public static GUIComponentStyle GetComponentStyle(string name)
|
||||
|
||||
@@ -587,7 +587,7 @@ namespace Barotrauma
|
||||
|
||||
if (Shadow)
|
||||
{
|
||||
Vector2 shadowOffset = new Vector2(GUI.IntScale(2));
|
||||
Vector2 shadowOffset = new Vector2(Math.Max(GUI.IntScale(2), 1));
|
||||
Font.DrawString(spriteBatch, textToShow, pos + shadowOffset, Color.Black, 0.0f, origin, TextScale, SpriteEffects.None, textDepth, alignment: textAlignment, forceUpperCase: ForceUpperCase);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ namespace Barotrauma
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
public static Rectangle TutorialObjectiveListArea
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle MessageAreaTop
|
||||
{
|
||||
@@ -167,6 +171,10 @@ namespace Barotrauma
|
||||
|
||||
HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight);
|
||||
|
||||
int objectiveListAreaX = HealthWindowAreaLeft.Right + Padding;
|
||||
int objectiveListAreaY = ButtonAreaTop.Bottom + Padding;
|
||||
TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (AfflictionAreaLeft.Top - Padding) - objectiveListAreaY);
|
||||
|
||||
int votingAreaWidth = (int)(400 * GUI.Scale);
|
||||
int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth;
|
||||
int votingAreaY = Padding + ButtonAreaTop.Height;
|
||||
@@ -179,16 +187,19 @@ namespace Barotrauma
|
||||
|
||||
public static void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, ButtonAreaTop, Color.White * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, MessageAreaTop, GUIStyle.Orange * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, CrewArea, Color.Blue * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, ChatBoxArea, Color.Cyan * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, HealthBarArea, Color.Red * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, AfflictionAreaLeft, Color.Red * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, InventoryAreaLower, Color.Yellow * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, HealthWindowAreaLeft, Color.Red * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, BottomRightInfoArea, Color.Green * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, ItemHUDArea, Color.Magenta * 0.3f);
|
||||
DrawRectangle(ButtonAreaTop, Color.White * 0.5f);
|
||||
DrawRectangle(TutorialObjectiveListArea, GUIStyle.Blue * 0.5f);
|
||||
DrawRectangle(MessageAreaTop, GUIStyle.Orange * 0.5f);
|
||||
DrawRectangle(CrewArea, Color.Blue * 0.5f);
|
||||
DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f);
|
||||
DrawRectangle(HealthBarArea, Color.Red * 0.5f);
|
||||
DrawRectangle(AfflictionAreaLeft, Color.Red * 0.5f);
|
||||
DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f);
|
||||
DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f);
|
||||
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f);
|
||||
DrawRectangle(ItemHUDArea, Color.Magenta * 0.3f);
|
||||
|
||||
void DrawRectangle(Rectangle r, Color c) => GUI.DrawRectangle(spriteBatch, r, c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace Barotrauma
|
||||
public SubmarineInfo displayedSubmarine;
|
||||
public GUITextBlock submarineName;
|
||||
public GUITextBlock submarineClass;
|
||||
public GUITextBlock submarineTier;
|
||||
public GUITextBlock submarineFee;
|
||||
public GUIButton selectSubmarineButton;
|
||||
public GUITextBlock middleTextBlock;
|
||||
@@ -162,7 +163,12 @@ namespace Barotrauma
|
||||
IgnoreLayoutGroups = true
|
||||
};
|
||||
new GUIListBox(new RectTransform(Vector2.One, infoFrame.RectTransform)) { IgnoreLayoutGroups = true, CanBeFocused = false };
|
||||
specsFrame = new GUIListBox(new RectTransform(new Vector2(0.39f, 1f), infoFrame.RectTransform), style: null) { Spacing = GUI.IntScale(5), Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding, 0, 0) };
|
||||
specsFrame = new GUIListBox(new RectTransform(new Vector2(0.39f, 1f), infoFrame.RectTransform), style: null)
|
||||
{
|
||||
CurrentSelectMode = GUIListBox.SelectMode.None,
|
||||
Spacing = GUI.IntScale(5),
|
||||
Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding, 0, 0)
|
||||
};
|
||||
new GUIFrame(new RectTransform(new Vector2(0.02f, 0.8f), infoFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, style: "VerticalLine");
|
||||
GUIListBox descriptionFrame = new GUIListBox(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform), style: null) { Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding / 2f) };
|
||||
descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUIStyle.Font, wrap: true) { CanBeFocused = false };
|
||||
@@ -221,7 +227,8 @@ namespace Barotrauma
|
||||
submarineDisplayElement.submarineImage = new GUIImage(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), null, true);
|
||||
submarineDisplayElement.middleTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), string.Empty, textAlignment: Alignment.Center);
|
||||
submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Center);
|
||||
submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Left);
|
||||
submarineDisplayElement.submarineTier = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Right);
|
||||
submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null);
|
||||
submarineDisplayElement.previewButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, submarineDisplayElement.background.RectTransform, anchor: Anchor.BottomRight, pivot: Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * background.Rect.Height)) }, style: "ExpandButton")
|
||||
@@ -355,6 +362,7 @@ namespace Barotrauma
|
||||
submarineDisplays[i].submarineName.Text = string.Empty;
|
||||
submarineDisplays[i].submarineFee.Text = string.Empty;
|
||||
submarineDisplays[i].submarineClass.Text = string.Empty;
|
||||
submarineDisplays[i].submarineTier.Text = string.Empty;
|
||||
submarineDisplays[i].selectSubmarineButton.Enabled = false;
|
||||
submarineDisplays[i].selectSubmarineButton.OnClicked = null;
|
||||
submarineDisplays[i].displayedSubmarine = null;
|
||||
@@ -389,6 +397,7 @@ namespace Barotrauma
|
||||
|
||||
submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName;
|
||||
submarineDisplays[i].submarineClass.Text = TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}"));
|
||||
submarineDisplays[i].submarineTier.Text = TextManager.Get($"submarinetier.{subToDisplay.Tier}");
|
||||
|
||||
if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay))
|
||||
{
|
||||
@@ -847,7 +856,7 @@ namespace Barotrauma
|
||||
msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext",
|
||||
("[submarinename]", selectedSubmarine.DisplayName),
|
||||
("[amount]", selectedSubmarine.Price.ToString()),
|
||||
("[currencyname]", currencyName)), messageBoxOptions);
|
||||
("[currencyname]", currencyName)) + '\n' + TextManager.Get("submarineswitchinstruction"), messageBoxOptions);
|
||||
|
||||
msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
|
||||
{
|
||||
|
||||
@@ -1722,7 +1722,7 @@ namespace Barotrauma
|
||||
|
||||
var subInfoTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, paddedFrame.RectTransform));
|
||||
|
||||
LocalizedString className = !sub.Info.HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}") : TextManager.Get("shuttle");
|
||||
LocalizedString className = !sub.Info.HasTag(SubmarineTag.Shuttle) ? $"{TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}")} ({TextManager.Get($"submarinetier.{sub.Info.Tier}")})" : TextManager.Get("shuttle");
|
||||
|
||||
int nameHeight = (int)GUIStyle.LargeFont.MeasureString(sub.Info.DisplayName, true).Y;
|
||||
int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y;
|
||||
@@ -1763,7 +1763,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
var specsListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.57f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft));
|
||||
var specsListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.57f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft))
|
||||
{
|
||||
CurrentSelectMode = GUIListBox.SelectMode.None
|
||||
};
|
||||
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true);
|
||||
}
|
||||
}
|
||||
@@ -1954,14 +1957,15 @@ namespace Barotrauma
|
||||
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
|
||||
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
|
||||
|
||||
if (subTree.TalentOptionStages.Count <= i) { continue; }
|
||||
if (subTree.TalentOptionStages.Length <= i) { continue; }
|
||||
|
||||
TalentOption talentOption = subTree.TalentOptionStages[i];
|
||||
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
|
||||
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
|
||||
|
||||
foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier))
|
||||
foreach (Identifier talentId in talentOption.TalentIdentifiers.OrderBy(t => t))
|
||||
{
|
||||
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab talent)) { continue; }
|
||||
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
|
||||
@@ -17,7 +17,7 @@ using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class UpgradeStore
|
||||
internal sealed class UpgradeStore
|
||||
{
|
||||
public readonly struct CategoryData
|
||||
{
|
||||
@@ -847,7 +847,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (UpgradePrefab prefab in prefabs)
|
||||
{
|
||||
CreateUpgradeEntry(prefab, category, parent.Content, entitiesOnSub);
|
||||
if (prefab.MaxLevel is 0) { continue; }
|
||||
CreateUpgradeEntry(prefab, category, parent.Content, submarine, entitiesOnSub);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1124,7 +1125,11 @@ namespace Barotrauma
|
||||
//negative price = refund
|
||||
if (price < 0) { formattedPrice = "+" + formattedPrice; }
|
||||
buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" };
|
||||
var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center);
|
||||
var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center)
|
||||
{
|
||||
//prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades
|
||||
Visible = userData is ItemPrefab
|
||||
};
|
||||
if (price < 0)
|
||||
{
|
||||
priceText.TextColor = GUIStyle.Green;
|
||||
@@ -1183,9 +1188,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item>? itemsOnSubmarine)
|
||||
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, Submarine submarine, List<Item>? itemsOnSubmarine)
|
||||
{
|
||||
if (Campaign is null) { return; }
|
||||
Submarine? sub = GameMain.GameSession?.Submarine ?? Submarine.MainSub;
|
||||
if (Campaign is null || sub is null) { return; }
|
||||
|
||||
GUIFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.25f, parent));
|
||||
var prefabLayout = prefabFrame.GetChild<GUILayoutGroup>();
|
||||
@@ -1201,7 +1207,7 @@ namespace Barotrauma
|
||||
var buyButtonLayout = childLayouts[2];
|
||||
var buyButton = buyButtonLayout.GetChild<GUIButton>();
|
||||
|
||||
if (!HasPermission || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))))
|
||||
if (!HasPermission || !prefab.IsApplicable(submarine.Info) || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))))
|
||||
{
|
||||
prefabFrame.Enabled = false;
|
||||
description.Enabled = false;
|
||||
@@ -1446,7 +1452,7 @@ namespace Barotrauma
|
||||
* |--------------------------------------------------|
|
||||
* | name |
|
||||
* |--------------------------------------------------|
|
||||
* | class |
|
||||
* | class + tier |
|
||||
* |--------------------------------------------------|
|
||||
* | description |
|
||||
* | |
|
||||
@@ -1456,13 +1462,31 @@ namespace Barotrauma
|
||||
submarineInfoFrame = new GUILayoutGroup(rectT(0.25f, 0.2f, mainStoreLayout, Anchor.TopRight)) { IgnoreLayoutGroups = true };
|
||||
// submarine name
|
||||
new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.DisplayName, textAlignment: Alignment.Right, font: GUIStyle.LargeFont);
|
||||
// submarine class
|
||||
new GUITextBlock(rectT(1, 0, submarineInfoFrame), $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}"))}", textAlignment: Alignment.Right, font: GUIStyle.Font);
|
||||
|
||||
GUILayoutGroup classLayout = new GUILayoutGroup(rectT(1, 0.15f, submarineInfoFrame), isHorizontal: true) { Stretch = true };
|
||||
LocalizedString classText = $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}"))}";
|
||||
// submarine class + tier
|
||||
new GUITextBlock(rectT(0.8f, 1, classLayout), classText, textAlignment: Alignment.Right, font: GUIStyle.Font)
|
||||
{
|
||||
ToolTip = TextManager.Get("submarineclass.description") + '\n' + TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}.description")
|
||||
};
|
||||
int tier = submarine.Info.Tier;
|
||||
string tierStyle = $"SubmarineTier.{tier}";
|
||||
if (GUIStyle.GetComponentStyle(tierStyle) != null)
|
||||
{
|
||||
LocalizedString tooltip = TextManager.Get("submarinetier.description").Fallback(string.Empty);
|
||||
new GUIImage(rectT(0.15f, 1, classLayout), style: tierStyle, scaleToFit: false)
|
||||
{
|
||||
ToolTip = tooltip
|
||||
};
|
||||
}
|
||||
|
||||
var description = new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.Description, textAlignment: Alignment.Right, wrap: true);
|
||||
submarineInfoFrame.RectTransform.ScreenSpaceOffset = new Point(0, (int)(16 * GUI.Scale));
|
||||
|
||||
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>().ToList();
|
||||
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();
|
||||
|
||||
List<ushort> ids = GameMain.GameSession.SubmarineInfo?.LeftBehindDockingPortIDs ?? new List<ushort>();
|
||||
pointsOfInterest.AddRange(submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs).Where(item => ids.Contains(item.ID)));
|
||||
@@ -1610,6 +1634,7 @@ namespace Barotrauma
|
||||
List<GUITextBlock> textBlocks = buttonParent.GetAllChildren<GUITextBlock>().ToList();
|
||||
|
||||
GUITextBlock priceLabel = textBlocks[0];
|
||||
priceLabel.Visible = true;
|
||||
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
|
||||
|
||||
if (priceLabel != null && !WaitForServerUpdate)
|
||||
@@ -1648,9 +1673,15 @@ namespace Barotrauma
|
||||
IEnumerable<UpgradeCategory> applicableCategories)
|
||||
{
|
||||
// Disables the parent and only re-enables if the submarine contains valid items
|
||||
if (!category.IsWallUpgrade && drawnSubmarine != null)
|
||||
if (!category.IsWallUpgrade && drawnSubmarine?.Info != null)
|
||||
{
|
||||
if (applicableCategories.Contains(category))
|
||||
if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0))
|
||||
{
|
||||
parent.ToolTip = TextManager.Get("upgradecategorynotapplicable");
|
||||
parent.Enabled = false;
|
||||
parent.SelectedColor = GUIStyle.Red * 0.5f;
|
||||
}
|
||||
else if (applicableCategories.Contains(category))
|
||||
{
|
||||
parent.Enabled = true;
|
||||
parent.SelectedColor = parent.Style.SelectedColor;
|
||||
@@ -1670,14 +1701,27 @@ namespace Barotrauma
|
||||
{
|
||||
if (component.UserData != prefab) { continue; }
|
||||
|
||||
if (prefab.MaxLevel is 0)
|
||||
{
|
||||
component.Visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
Dictionary<Identifier, GUIComponentStyle> styles = GUIStyle.GetComponentStyle("upgradeindicator").ChildStyles;
|
||||
if (!styles.ContainsKey("upgradeindicatoron") || !styles.ContainsKey("upgradeindicatordim") || !styles.ContainsKey("upgradeindicatoroff")) { continue; }
|
||||
|
||||
GUIComponentStyle onStyle = styles["upgradeindicatoron".ToIdentifier()];
|
||||
GUIComponentStyle dimStyle = styles["upgradeindicatordim".ToIdentifier()];
|
||||
GUIComponentStyle offStyle = styles["upgradeindicatoroff".ToIdentifier()];
|
||||
int maxLevel = prefab.MaxLevel;
|
||||
|
||||
if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= prefab.MaxLevel)
|
||||
if (maxLevel == 0)
|
||||
{
|
||||
SetOff();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= maxLevel)
|
||||
{
|
||||
// we check this to avoid flickering from re-applying the same style
|
||||
if (image.Style == onStyle) { continue; }
|
||||
@@ -1690,7 +1734,12 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (image.Style == offStyle) { continue; }
|
||||
SetOff();
|
||||
}
|
||||
|
||||
void SetOff()
|
||||
{
|
||||
if (image.Style == offStyle) { return; }
|
||||
image.ApplyStyle(offStyle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,9 +131,9 @@ namespace Barotrauma
|
||||
LoadContent(contentPath, videoSettings, textSettings, contentId, startPlayback, new RawLString(""), null);
|
||||
}
|
||||
|
||||
public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, Identifier contentId, bool startPlayback, LocalizedString objective, Action callback = null)
|
||||
public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, Identifier contentId, bool startPlayback, LocalizedString objective, Action onStop = null)
|
||||
{
|
||||
callbackOnStop = callback;
|
||||
callbackOnStop = onStop;
|
||||
filePath = contentPath + videoSettings.File;
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
|
||||
@@ -557,6 +557,8 @@ namespace Barotrauma
|
||||
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError));
|
||||
}
|
||||
|
||||
GameSettings.OnGameMainHasLoaded?.Invoke();
|
||||
|
||||
TitleScreen.LoadState = 100.0f;
|
||||
HasLoaded = true;
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
|
||||
@@ -183,14 +183,13 @@ namespace Barotrauma
|
||||
{
|
||||
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"),
|
||||
ToolTip = TextManager.GetWithVariable("hudbutton.chatbox", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ChatBox)),
|
||||
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;
|
||||
chatBox.Toggle();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -540,37 +540,37 @@ namespace Barotrauma
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
|
||||
|
||||
msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
|
||||
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
|
||||
msg.WriteUInt16(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
|
||||
msg.WriteUInt16(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
|
||||
|
||||
var selectedMissionIndices = map.GetSelectedMissionIndices();
|
||||
msg.Write((byte)selectedMissionIndices.Count());
|
||||
msg.WriteByte((byte)selectedMissionIndices.Count());
|
||||
foreach (int selectedMissionIndex in selectedMissionIndices)
|
||||
{
|
||||
msg.Write((byte)selectedMissionIndex);
|
||||
msg.WriteByte((byte)selectedMissionIndex);
|
||||
}
|
||||
msg.Write(PurchasedHullRepairs);
|
||||
msg.Write(PurchasedItemRepairs);
|
||||
msg.Write(PurchasedLostShuttles);
|
||||
msg.WriteBoolean(PurchasedHullRepairs);
|
||||
msg.WriteBoolean(PurchasedItemRepairs);
|
||||
msg.WriteBoolean(PurchasedLostShuttles);
|
||||
|
||||
WriteItems(msg, CargoManager.ItemsInBuyCrate);
|
||||
WriteItems(msg, CargoManager.ItemsInSellFromSubCrate);
|
||||
WriteItems(msg, CargoManager.PurchasedItems);
|
||||
WriteItems(msg, CargoManager.SoldItems);
|
||||
|
||||
msg.Write((ushort)UpgradeManager.PurchasedUpgrades.Count);
|
||||
msg.WriteUInt16((ushort)UpgradeManager.PurchasedUpgrades.Count);
|
||||
foreach (var (prefab, category, level) in UpgradeManager.PurchasedUpgrades)
|
||||
{
|
||||
msg.Write(prefab.Identifier);
|
||||
msg.Write(category.Identifier);
|
||||
msg.Write((byte)level);
|
||||
msg.WriteIdentifier(prefab.Identifier);
|
||||
msg.WriteIdentifier(category.Identifier);
|
||||
msg.WriteByte((byte)level);
|
||||
}
|
||||
|
||||
msg.Write((ushort)UpgradeManager.PurchasedItemSwaps.Count);
|
||||
msg.WriteUInt16((ushort)UpgradeManager.PurchasedItemSwaps.Count);
|
||||
foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps)
|
||||
{
|
||||
msg.Write(itemSwap.ItemToRemove.ID);
|
||||
msg.Write(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty);
|
||||
msg.WriteUInt16(itemSwap.ItemToRemove.ID);
|
||||
msg.WriteIdentifier(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -49,6 +45,31 @@ namespace Barotrauma
|
||||
|
||||
private bool showCampaignResetText;
|
||||
|
||||
public override bool PurchasedHullRepairs
|
||||
{
|
||||
get { return PurchasedHullRepairsInLatestSave; }
|
||||
set
|
||||
{
|
||||
PurchasedHullRepairsInLatestSave = value;
|
||||
}
|
||||
}
|
||||
public override bool PurchasedLostShuttles
|
||||
{
|
||||
get { return PurchasedLostShuttlesInLatestSave; }
|
||||
set
|
||||
{
|
||||
PurchasedLostShuttlesInLatestSave = value;
|
||||
}
|
||||
}
|
||||
public override bool PurchasedItemRepairs
|
||||
{
|
||||
get { return PurchasedItemRepairsInLatestSave; }
|
||||
set
|
||||
{
|
||||
PurchasedItemRepairsInLatestSave = value;
|
||||
}
|
||||
}
|
||||
|
||||
#region Constructors/initialization
|
||||
|
||||
/// <summary>
|
||||
@@ -214,7 +235,7 @@ namespace Barotrauma
|
||||
base.Start();
|
||||
CargoManager.CreatePurchasedItems();
|
||||
UpgradeManager.ApplyUpgrades();
|
||||
UpgradeManager.SanityCheckUpgrades(Submarine.MainSub);
|
||||
UpgradeManager.SanityCheckUpgrades();
|
||||
|
||||
if (!savedOnStart)
|
||||
{
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class CaptainTutorial : ScenarioTutorial
|
||||
{
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
|
||||
// Room 2
|
||||
private Door captain_firstDoor;
|
||||
private LightComponent captain_firstDoorLight;
|
||||
|
||||
// Room 3
|
||||
private Character captain_medic;
|
||||
private MotionSensor captain_medicObjectiveSensor;
|
||||
private Vector2 captain_medicSpawnPos;
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
|
||||
// Submarine
|
||||
private MotionSensor captain_enteredSubmarineSensor;
|
||||
private Steering captain_navConsole;
|
||||
private Sonar captain_sonar;
|
||||
private Item captain_statusMonitor;
|
||||
private Character captain_security;
|
||||
private Character captain_mechanic;
|
||||
private Character captain_engineer;
|
||||
private Reactor tutorial_submarineReactor;
|
||||
private Door tutorial_lockedDoor_1;
|
||||
private Door tutorial_lockedDoor_2;
|
||||
|
||||
// Variables
|
||||
private Character captain;
|
||||
private LocalizedString radioSpeakerName;
|
||||
private Sprite captain_steerIcon;
|
||||
private Color captain_steerIconColor;
|
||||
|
||||
public CaptainTutorial() : base("tutorial.captaintraining".ToIdentifier(),
|
||||
new Segment(
|
||||
"Captain.CommandMedic".ToIdentifier(),
|
||||
"Captain.CommandMedicObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Captain.CommandMedicText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_command.webm", TextTag = "Captain.CommandMedicText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Captain.CommandMechanic".ToIdentifier(),
|
||||
"Captain.CommandMechanicObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Captain.CommandMechanicText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Captain.CommandSecurity".ToIdentifier(),
|
||||
"Captain.CommandSecurityObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Captain.CommandSecurityText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Captain.CommandEngineer".ToIdentifier(),
|
||||
"Captain.CommandEngineerObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Captain.CommandEngineerText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Captain.Undock".ToIdentifier(),
|
||||
"Captain.UndockObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Captain.UndockText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_undock.webm", TextTag = "Captain.UndockText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Captain.Navigate".ToIdentifier(),
|
||||
"Captain.NavigateObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Captain.NavigateText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_navigation.webm", TextTag = "Captain.NavigateText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Captain.Dock".ToIdentifier(),
|
||||
"Captain.DockObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Captain.DockText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_docking.webm", TextTag = "Captain.DockText".ToIdentifier(), Width = 450, Height = 80 }))
|
||||
{ }
|
||||
|
||||
protected override CharacterInfo GetCharacterInfo()
|
||||
{
|
||||
return new CharacterInfo(
|
||||
CharacterPrefab.HumanSpeciesName,
|
||||
jobOrJobPrefab: new Job(
|
||||
JobPrefab.Prefabs["captain"], Rand.RandSync.Unsynced, 0,
|
||||
new Skill("medical".ToIdentifier(), 20),
|
||||
new Skill("weapons".ToIdentifier(), 20),
|
||||
new Skill("mechanical".ToIdentifier(), 20),
|
||||
new Skill("electrical".ToIdentifier(), 20),
|
||||
new Skill("helm".ToIdentifier(), 70)));
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
captain = Character.Controlled;
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman");
|
||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
|
||||
|
||||
foreach (Item item in captain.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
|
||||
item.Unequip(captain);
|
||||
captain.Inventory.RemoveItem(item);
|
||||
}
|
||||
|
||||
var steerOrder = OrderPrefab.Prefabs["steer"];
|
||||
captain_steerIcon = steerOrder.SymbolSprite;
|
||||
captain_steerIconColor = steerOrder.Color;
|
||||
|
||||
// Room 2
|
||||
captain_firstDoor = Item.ItemList.Find(i => i.HasTag("captain_firstdoor")).GetComponent<Door>();
|
||||
captain_firstDoorLight = Item.ItemList.Find(i => i.HasTag("captain_firstdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(captain_firstDoor, captain_firstDoorLight, true);
|
||||
|
||||
// Room 3
|
||||
captain_medicObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_medicobjectivesensor")).GetComponent<MotionSensor>();
|
||||
captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition;
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("medicaldoctor".ToIdentifier()))
|
||||
{
|
||||
TeamID = CharacterTeamType.Team1
|
||||
};
|
||||
captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
|
||||
captain_medic.GiveJobItems(null);
|
||||
captain_medic.CanSpeak = captain_medic.AIController.Enabled = false;
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
|
||||
|
||||
// Submarine
|
||||
captain_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("captain_enteredsubmarinesensor")).GetComponent<MotionSensor>();
|
||||
tutorial_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent<Reactor>();
|
||||
captain_navConsole = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
||||
captain_sonar = captain_navConsole.Item.GetComponent<Sonar>();
|
||||
captain_statusMonitor = Item.ItemList.Find(i => i.HasTag("captain_statusmonitor"));
|
||||
|
||||
tutorial_submarineReactor.CanBeSelected = false;
|
||||
tutorial_submarineReactor.IsActive = tutorial_submarineReactor.AutoTemp = false;
|
||||
|
||||
tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent<Door>();
|
||||
tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent<Door>();
|
||||
SetDoorAccess(tutorial_lockedDoor_1, null, false);
|
||||
SetDoorAccess(tutorial_lockedDoor_2, null, false);
|
||||
|
||||
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("mechanic".ToIdentifier()))
|
||||
{
|
||||
TeamID = CharacterTeamType.Team1
|
||||
};
|
||||
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "mechanic");
|
||||
captain_mechanic.GiveJobItems();
|
||||
|
||||
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer".ToIdentifier()))
|
||||
{
|
||||
TeamID = CharacterTeamType.Team1
|
||||
};
|
||||
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "securityofficer");
|
||||
captain_security.GiveJobItems();
|
||||
|
||||
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier()))
|
||||
{
|
||||
TeamID = CharacterTeamType.Team1
|
||||
};
|
||||
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "engineer");
|
||||
captain_engineer.GiveJobItems();
|
||||
|
||||
captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false;
|
||||
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = false;
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Started");
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
|
||||
}
|
||||
|
||||
public override IEnumerable<CoroutineStatus> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
// Room 1
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
|
||||
// Room 2
|
||||
do { yield return null; } while (!captain_firstDoor.IsOpen);
|
||||
captain_medic.AIController.Enabled = true;
|
||||
|
||||
// Room 3
|
||||
do { yield return null; } while (!captain_medicObjectiveSensor.MotionDetected);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(captain_medic.Info.DisplayName, TextManager.Get("Captain.Radio.Medic"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
GameMain.GameSession.CrewManager.AutoShowCrewList();
|
||||
GameMain.GameSession.CrewManager.AddCharacter(captain_medic);
|
||||
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command));
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_medic, "follow", highlightColor, new Vector2(5, 5));
|
||||
}
|
||||
while (!HasOrder(captain_medic, "follow"));
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
RemoveCompletedObjective(0);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective0");
|
||||
|
||||
// Submarine
|
||||
do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected);
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = true;
|
||||
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command));
|
||||
GameMain.GameSession.CrewManager.AddCharacter(captain_mechanic);
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5));
|
||||
//HighlightOrderOption("jobspecific");
|
||||
} while (!HasOrder(captain_mechanic, "repairsystems") && !HasOrder(captain_mechanic, "repairmechanical") && !HasOrder(captain_mechanic, "repairelectrical"));
|
||||
RemoveCompletedObjective(1);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective1");
|
||||
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command));
|
||||
GameMain.GameSession.CrewManager.AddCharacter(captain_security);
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5));
|
||||
HighlightOrderOption("fireatwill");
|
||||
}
|
||||
while (!HasOrder(captain_security, "operateweapons"));
|
||||
RemoveCompletedObjective(2);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective2");
|
||||
|
||||
yield return new WaitForSeconds(4f, false);
|
||||
TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command));
|
||||
GameMain.GameSession.CrewManager.AddCharacter(captain_engineer);
|
||||
tutorial_submarineReactor.CanBeSelected = true;
|
||||
//recreate autonomous objectives to make sure the engineer didn't abandon the operate reactor objective because it was not selectable
|
||||
(captain_engineer.AIController as HumanAIController).ObjectiveManager.CreateAutonomousObjectives();
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_engineer, "operatereactor", highlightColor, new Vector2(5, 5));
|
||||
HighlightOrderOption("powerup");
|
||||
}
|
||||
while (!HasOrder(captain_engineer, "operatereactor", "powerup"));
|
||||
RemoveCompletedObjective(3);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective3");
|
||||
|
||||
do { yield return null; } while (!tutorial_submarineReactor.IsActive); // Wait until reactor on
|
||||
TriggerTutorialSegment(4);
|
||||
while (ContentRunning) yield return null;
|
||||
captain.AddActiveObjectiveEntity(captain_navConsole.Item, captain_steerIcon, captain_steerIconColor);
|
||||
SetHighlight(captain_navConsole.Item, true);
|
||||
SetHighlight(captain_sonar.Item, true);
|
||||
SetHighlight(captain_statusMonitor, true);
|
||||
do
|
||||
{
|
||||
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
} while (Submarine.MainSub.DockedTo.Any());
|
||||
captain_navConsole.UseAutoDocking = false;
|
||||
RemoveCompletedObjective(4);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective4");
|
||||
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(5); // Navigate to destination
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(captain_navConsole.Item))
|
||||
{
|
||||
if (captain_sonar.SonarModeSwitch.Frame.FlashTimer <= 0)
|
||||
{
|
||||
captain_sonar.SonarModeSwitch.Frame.Flash(highlightColor, 1.5f, false, false, new Vector2(2.5f, 2.5f));
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (captain_sonar.CurrentMode != Sonar.Mode.Active);
|
||||
do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f);
|
||||
RemoveCompletedObjective(5);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective5");
|
||||
|
||||
captain_navConsole.UseAutoDocking = true;
|
||||
yield return new WaitForSeconds(4f, false);
|
||||
TriggerTutorialSegment(6); // Docking
|
||||
do
|
||||
{
|
||||
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
} while (!Submarine.MainSub.AtEndExit || !Submarine.MainSub.DockedTo.Any());
|
||||
RemoveCompletedObjective(6);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective6");
|
||||
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
|
||||
SetHighlight(captain_navConsole.Item, false);
|
||||
SetHighlight(captain_sonar.Item, false);
|
||||
SetHighlight(captain_statusMonitor, false);
|
||||
captain.RemoveActiveObjectiveEntity(captain_navConsole.Item);
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Completed");
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
private void HighlightOrderOption(string option)
|
||||
{
|
||||
if (GameMain.GameSession.CrewManager.OrderOptionButtons.Count == 0) return;
|
||||
var order = GameMain.GameSession.CrewManager.OrderOptionButtons[0].UserData as Order;
|
||||
|
||||
int orderIndex = 0;
|
||||
for (int i = 0; i < GameMain.GameSession.CrewManager.OrderOptionButtons.Count; i++)
|
||||
{
|
||||
if (orderIndex >= order.Options.Length)
|
||||
{
|
||||
orderIndex = 0;
|
||||
}
|
||||
if (order.Options[orderIndex] == option)
|
||||
{
|
||||
if (GameMain.GameSession.CrewManager.OrderOptionButtons[i].FlashTimer <= 0)
|
||||
{
|
||||
GameMain.GameSession.CrewManager.OrderOptionButtons[i].Flash(highlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
orderIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSelectedItem(Item item)
|
||||
{
|
||||
return
|
||||
captain?.SelectedItem == item ||
|
||||
(captain?.SelectedItem?.linkedTo?.Contains(item) ?? false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,521 +0,0 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class DoctorTutorial : ScenarioTutorial
|
||||
{
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
|
||||
private LocalizedString radioSpeakerName;
|
||||
private Character doctor;
|
||||
|
||||
private ItemContainer doctor_suppliesCabinet;
|
||||
private ItemContainer doctor_medBayCabinet;
|
||||
private Character patient1, patient2;
|
||||
private List<Character> subPatients;
|
||||
private Hull medBay;
|
||||
|
||||
private Door doctor_firstDoor;
|
||||
private Door doctor_secondDoor;
|
||||
private Door doctor_thirdDoor;
|
||||
private Door tutorial_upperFinalDoor;
|
||||
private Door tutorial_lockedDoor_2;
|
||||
|
||||
private LightComponent doctor_firstDoorLight;
|
||||
private LightComponent doctor_secondDoorLight;
|
||||
private LightComponent doctor_thirdDoorLight;
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
|
||||
// Variables
|
||||
private Sprite doctor_firstAidIcon;
|
||||
private Color doctor_firstAidIconColor;
|
||||
|
||||
|
||||
public DoctorTutorial() : base("tutorial.medicaldoctortraining".ToIdentifier(),
|
||||
new Segment(
|
||||
"Doctor.Supplies".ToIdentifier(),
|
||||
"Doctor.SuppliesObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Doctor.SuppliesText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Doctor.OpenMedicalInterface".ToIdentifier(),
|
||||
"Doctor.OpenMedicalInterfaceObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Doctor.OpenMedicalInterfaceText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_medinterface1.webm", TextTag = "Doctor.OpenMedicalInterfaceText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Doctor.FirstAidSelf".ToIdentifier(),
|
||||
"Doctor.FirstAidSelfObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Doctor.FirstAidSelfText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_medinterface1.webm", TextTag = "Doctor.FirstAidSelfText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Doctor.Medbay".ToIdentifier(),
|
||||
"Doctor.MedbayObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Doctor.MedbayText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_command.webm", TextTag = "Doctor.MedbayText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Doctor.TreatBurns".ToIdentifier(),
|
||||
"Doctor.TreatBurnsObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Doctor.TreatBurnsText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_medinterface2.webm", TextTag = "Doctor.TreatBurnsText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Doctor.CPR".ToIdentifier(),
|
||||
"Doctor.CPRObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Doctor.CPRText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_cpr.webm", TextTag = "Doctor.CPRText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Doctor.Submarine".ToIdentifier(),
|
||||
"Doctor.SubmarineObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Doctor.SubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }))
|
||||
{ }
|
||||
|
||||
protected override CharacterInfo GetCharacterInfo()
|
||||
{
|
||||
return new CharacterInfo(
|
||||
CharacterPrefab.HumanSpeciesName,
|
||||
jobOrJobPrefab: new Job(
|
||||
JobPrefab.Prefabs["medicaldoctor"], Rand.RandSync.Unsynced, 0,
|
||||
new Skill("medical".ToIdentifier(), 70),
|
||||
new Skill("weapons".ToIdentifier(), 20),
|
||||
new Skill("mechanical".ToIdentifier(), 20),
|
||||
new Skill("electrical".ToIdentifier(), 20),
|
||||
new Skill("helm".ToIdentifier(), 20)));
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
var firstAidOrder = OrderPrefab.Prefabs["requestfirstaid"];
|
||||
doctor_firstAidIcon = firstAidOrder.SymbolSprite;
|
||||
doctor_firstAidIconColor = firstAidOrder.Color;
|
||||
|
||||
subPatients = new List<Character>();
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||
doctor = Character.Controlled;
|
||||
|
||||
foreach (Item item in doctor.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
|
||||
item.Unequip(doctor);
|
||||
doctor.Inventory.RemoveItem(item);
|
||||
}
|
||||
|
||||
doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent<ItemContainer>();
|
||||
doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent<ItemContainer>();
|
||||
|
||||
var patientHull1 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "waitingroom").CurrentHull;
|
||||
var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull;
|
||||
medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull;
|
||||
|
||||
var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant".ToIdentifier()))
|
||||
{
|
||||
TeamID = CharacterTeamType.Team1
|
||||
};
|
||||
patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
|
||||
patient1.GiveJobItems(null);
|
||||
patient1.CanSpeak = false;
|
||||
patient1.Params.Health.BurnReduction = 0;
|
||||
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 15.0f) }, stun: 0, playSound: false);
|
||||
patient1.AIController.Enabled = false;
|
||||
|
||||
assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant".ToIdentifier()))
|
||||
{
|
||||
TeamID = CharacterTeamType.Team1
|
||||
};
|
||||
patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
|
||||
patient2.GiveJobItems(null);
|
||||
patient2.CanSpeak = false;
|
||||
patient2.AIController.Enabled = false;
|
||||
|
||||
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier()))
|
||||
{
|
||||
TeamID = CharacterTeamType.Team1
|
||||
};
|
||||
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
|
||||
subPatient1.Params.Health.BurnReduction = 0;
|
||||
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
|
||||
subPatients.Add(subPatient1);
|
||||
|
||||
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer".ToIdentifier()));
|
||||
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
|
||||
subPatient2.TeamID = CharacterTeamType.Team1;
|
||||
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
|
||||
subPatients.Add(subPatient2);
|
||||
|
||||
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier()))
|
||||
{
|
||||
TeamID = CharacterTeamType.Team1
|
||||
};
|
||||
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
|
||||
subPatient3.Params.Health.BurnReduction = 0;
|
||||
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
|
||||
subPatients.Add(subPatient3);
|
||||
|
||||
doctor_firstDoor = Item.ItemList.Find(i => i.HasTag("doctor_firstdoor")).GetComponent<Door>();
|
||||
doctor_secondDoor = Item.ItemList.Find(i => i.HasTag("doctor_seconddoor")).GetComponent<Door>();
|
||||
doctor_thirdDoor = Item.ItemList.Find(i => i.HasTag("doctor_thirddoor")).GetComponent<Door>();
|
||||
tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent<Door>();
|
||||
doctor_firstDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_firstdoorlight")).GetComponent<LightComponent>();
|
||||
doctor_secondDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_seconddoorlight")).GetComponent<LightComponent>();
|
||||
doctor_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_thirddoorlight")).GetComponent<LightComponent>();
|
||||
SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, false);
|
||||
SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, false);
|
||||
SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, false);
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
|
||||
tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent<Door>();
|
||||
SetDoorAccess(tutorial_lockedDoor_2, null, true);
|
||||
|
||||
|
||||
foreach (var patient in subPatients)
|
||||
{
|
||||
patient.CanSpeak = false;
|
||||
patient.AIController.Enabled = false;
|
||||
patient.GiveJobItems();
|
||||
}
|
||||
|
||||
Item reactorItem = Item.ItemList.Find(i => i.Submarine == Submarine.MainSub && i.GetComponent<Reactor>() != null);
|
||||
reactorItem.GetComponent<Reactor>().AutoTemp = true;
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Started");
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
|
||||
}
|
||||
|
||||
public override IEnumerable<CoroutineStatus> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
// explosions and radio messages ------------------------------------------------------
|
||||
|
||||
yield return new WaitForSeconds(3.0f, false);
|
||||
|
||||
//SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||
//// Room 1
|
||||
//while (shakeTimer > 0.0f) // Wake up, shake
|
||||
//{
|
||||
// shakeTimer -= 0.1f;
|
||||
// GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
// yield return new WaitForSeconds(0.1f);
|
||||
//}
|
||||
//yield return new WaitForSeconds(2.5f);
|
||||
//GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||
|
||||
//yield return new WaitForSeconds(2.5f);
|
||||
|
||||
doctor.SetStun(1.5f);
|
||||
var explosion = new Explosion(range: 100, force: 10, damage: 0, structureDamage: 0, itemDamage: 0);
|
||||
explosion.DisableParticles();
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
explosion.Explode(Character.Controlled.WorldPosition - Vector2.UnitX * 25, null);
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition - Vector2.UnitX * 25);
|
||||
|
||||
yield return new WaitForSeconds(0.5f, false);
|
||||
|
||||
doctor.DamageLimb(
|
||||
Character.Controlled.WorldPosition,
|
||||
doctor.AnimController.GetLimb(LimbType.Torso),
|
||||
new List<Affliction> { new Affliction(AfflictionPrefab.InternalDamage, 10.0f) },
|
||||
stun: 3.0f, playSound: true, attackImpulse: 0.0f);
|
||||
|
||||
shakeTimer = 0.5f;
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(3.0f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.KnockedDown"), ChatMessageType.Radio, null);
|
||||
|
||||
// first tutorial segment, get medical supplies ------------------------------------------------------
|
||||
|
||||
yield return new WaitForSeconds(1.5f, false);
|
||||
SetHighlight(doctor_suppliesCabinet.Item, true);
|
||||
|
||||
/*while (doctor.CurrentHull != doctor_suppliesCabinet.Item.CurrentHull)
|
||||
{
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
}*/
|
||||
|
||||
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), "None"); // Medical supplies objective
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < doctor_suppliesCabinet.Inventory.Capacity; i++)
|
||||
{
|
||||
if (doctor_suppliesCabinet.Inventory.GetItemAt(i) != null)
|
||||
{
|
||||
HighlightInventorySlot(doctor_suppliesCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
if (doctor.SelectedItem == doctor_suppliesCabinet.Item)
|
||||
{
|
||||
for (int i = 0; i < doctor.Inventory.Capacity; i++)
|
||||
{
|
||||
if (doctor.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); }
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (doctor.Inventory.FindItemByIdentifier("antidama1".ToIdentifier()) == null); // Wait until looted
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
|
||||
SetHighlight(doctor_suppliesCabinet.Item, false);
|
||||
RemoveCompletedObjective(0);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective0");
|
||||
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
|
||||
// 2nd tutorial segment, treat self -------------------------------------------------------------------------
|
||||
|
||||
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // Open health interface
|
||||
while (CharacterHealth.OpenHealthWindow == null)
|
||||
{
|
||||
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
|
||||
yield return null;
|
||||
}
|
||||
yield return null;
|
||||
RemoveCompletedObjective(1);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective1");
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
TriggerTutorialSegment(2); //Treat self
|
||||
while (doctor.CharacterHealth.GetAfflictionStrength("damage") > 0.01f)
|
||||
{
|
||||
if (CharacterHealth.OpenHealthWindow == null)
|
||||
{
|
||||
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
HighlightInventorySlot(doctor.Inventory, "antidama1".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
RemoveCompletedObjective(2);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective2");
|
||||
SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, true);
|
||||
|
||||
while (CharacterHealth.OpenHealthWindow != null)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
|
||||
// treat patient --------------------------------------------------------------------------------------------
|
||||
|
||||
//patient 1 requests first aid
|
||||
var newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], patient1.CurrentHull, null, orderGiver: patient1);
|
||||
doctor.AddActiveObjectiveEntity(patient1, doctor_firstAidIcon, doctor_firstAidIconColor);
|
||||
//GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(patient1.Name, newOrder.GetChatMessage("", patient1.CurrentHull?.DisplayName?.Value, givingOrderToSelf: false), ChatMessageType.Order, null);
|
||||
|
||||
while (doctor.CurrentHull != patient1.CurrentHull)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
yield return new WaitForSeconds(0.0f, false);
|
||||
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurns"), ChatMessageType.Radio, null);
|
||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
|
||||
GameMain.GameSession.CrewManager.AddCharacter(doctor);
|
||||
GameMain.GameSession.CrewManager.AddCharacter(patient1);
|
||||
GameMain.GameSession.CrewManager.AutoShowCrewList();
|
||||
patient1.CharacterHealth.UseHealthWindow = false;
|
||||
|
||||
yield return new WaitForSeconds(3.0f, false);
|
||||
patient1.AIController.Enabled = true;
|
||||
doctor.RemoveActiveObjectiveEntity(patient1);
|
||||
TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); // Get the patient to medbay
|
||||
|
||||
while (patient1.GetCurrentOrderWithTopPriority()?.Identifier != "follow")
|
||||
{
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5));
|
||||
yield return null;
|
||||
}
|
||||
|
||||
SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, true);
|
||||
|
||||
while (patient1.CurrentHull != medBay)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
RemoveCompletedObjective(3);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective3");
|
||||
SetHighlight(doctor_medBayCabinet.Item, true);
|
||||
SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, true);
|
||||
patient1.CharacterHealth.UseHealthWindow = true;
|
||||
|
||||
yield return new WaitForSeconds(2.0f, false);
|
||||
|
||||
TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // treat burns
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (doctor_medBayCabinet.Inventory.GetItemAt(i) != null)
|
||||
{
|
||||
HighlightInventorySlot(doctor_medBayCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
if (doctor.SelectedItem == doctor_medBayCabinet.Item)
|
||||
{
|
||||
for (int i = 0; i < doctor.Inventory.Capacity; i++)
|
||||
{
|
||||
if (doctor.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); }
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (doctor.Inventory.FindItemByIdentifier("antibleeding1".ToIdentifier()) == null); // Wait until looted
|
||||
SetHighlight(doctor_medBayCabinet.Item, false);
|
||||
SetHighlight(patient1, true);
|
||||
|
||||
while (patient1.CharacterHealth.GetAfflictionStrength("burn") > 0.01f)
|
||||
{
|
||||
if (CharacterHealth.OpenHealthWindow == null)
|
||||
{
|
||||
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
HighlightInventorySlot(doctor.Inventory, "antibleeding1".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
yield return null;
|
||||
|
||||
}
|
||||
RemoveCompletedObjective(4);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective4");
|
||||
SetHighlight(patient1, false);
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurnsHealed"), ChatMessageType.Radio, null);
|
||||
|
||||
// treat unconscious patient ------------------------------------------------------
|
||||
|
||||
//patient calls for help
|
||||
//patient2.CanSpeak = true;
|
||||
yield return new WaitForSeconds(2.0f, false);
|
||||
newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], patient2.CurrentHull, null, orderGiver: patient2);
|
||||
doctor.AddActiveObjectiveEntity(patient2, doctor_firstAidIcon, doctor_firstAidIconColor);
|
||||
//GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(patient2.Name, newOrder.GetChatMessage("", patient1.CurrentHull?.DisplayName?.Value, givingOrderToSelf: false), ChatMessageType.Order, null);
|
||||
patient2.AIController.Enabled = true;
|
||||
patient2.Oxygen = -50;
|
||||
CoroutineManager.StartCoroutine(KeepPatientAlive(patient2), "KeepPatient2Alive");
|
||||
|
||||
/*while (doctor.CurrentHull != patient2.CurrentHull)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
}*/
|
||||
do { yield return null; } while (!tutorial_upperFinalDoor.IsOpen);
|
||||
yield return new WaitForSeconds(2.0f, false);
|
||||
|
||||
TriggerTutorialSegment(5, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // perform CPR
|
||||
SetHighlight(patient2, true);
|
||||
while (patient2.IsUnconscious)
|
||||
{
|
||||
if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR)
|
||||
{
|
||||
//Disabled pulse until it's replaced by a better effect
|
||||
//CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f);
|
||||
if (CharacterHealth.OpenHealthWindow.CPRButton.FlashTimer <= 0.0f)
|
||||
{
|
||||
CharacterHealth.OpenHealthWindow.CPRButton.Flash(highlightColor);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
RemoveCompletedObjective(5);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective5");
|
||||
SetHighlight(patient2, false);
|
||||
doctor.RemoveActiveObjectiveEntity(patient2);
|
||||
CoroutineManager.StopCoroutines("KeepPatient2Alive");
|
||||
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
|
||||
while (doctor.Submarine != Submarine.MainSub)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
|
||||
subPatients[2].Oxygen = -50;
|
||||
CoroutineManager.StartCoroutine(KeepPatientAlive(subPatients[2]), "KeepPatient3Alive");
|
||||
|
||||
yield return new WaitForSeconds(5.0f, false);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null);
|
||||
|
||||
yield return new WaitForSeconds(3.0f, false);
|
||||
TriggerTutorialSegment(6, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // give treatment to anyone in need
|
||||
|
||||
foreach (var patient in subPatients)
|
||||
{
|
||||
//patient.CanSpeak = true;
|
||||
patient.AIController.Enabled = true;
|
||||
SetHighlight(patient, true);
|
||||
}
|
||||
|
||||
double subEnterTime = Timing.TotalTime;
|
||||
|
||||
bool[] patientCalledHelp = new bool[] { false, false, false };
|
||||
|
||||
while (subPatients.Any(p => p.Vitality < p.MaxVitality * 0.9f && !p.IsDead))
|
||||
{
|
||||
for (int i = 0; i < subPatients.Count; i++)
|
||||
{
|
||||
//make patients call for help to make sure the player finds them
|
||||
//(within 1 minute intervals of entering the sub)
|
||||
if (!patientCalledHelp[i] && Timing.TotalTime > subEnterTime + 60 * (i + 1))
|
||||
{
|
||||
doctor.AddActiveObjectiveEntity(subPatients[i], doctor_firstAidIcon, doctor_firstAidIconColor);
|
||||
newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], subPatients[i].CurrentHull, null, orderGiver: subPatients[i]);
|
||||
string message = newOrder.GetChatMessage("", subPatients[i].CurrentHull?.DisplayName?.Value, givingOrderToSelf: false);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(subPatients[i].Name, message, ChatMessageType.Order, null);
|
||||
patientCalledHelp[i] = true;
|
||||
}
|
||||
|
||||
if (subPatients[i].ExternalHighlight && subPatients[i].Vitality >= subPatients[i].MaxVitality * 0.9f)
|
||||
{
|
||||
doctor.RemoveActiveObjectiveEntity(subPatients[i]);
|
||||
SetHighlight(subPatients[i], false);
|
||||
}
|
||||
}
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
RemoveCompletedObjective(6);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective6");
|
||||
foreach (var patient in subPatients)
|
||||
{
|
||||
SetHighlight(patient, false);
|
||||
doctor.RemoveActiveObjectiveEntity(patient);
|
||||
}
|
||||
|
||||
// END TUTORIAL
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Completed");
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
public IEnumerable<CoroutineStatus> KeepPatientAlive(Character patient)
|
||||
{
|
||||
while (patient != null && !patient.Removed)
|
||||
{
|
||||
patient.Oxygen = Math.Max(patient.Oxygen, -50);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,664 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class EngineerTutorial : ScenarioTutorial
|
||||
{
|
||||
// Other tutorial items
|
||||
private LightComponent tutorial_securityFinalDoorLight;
|
||||
private LightComponent tutorial_mechanicFinalDoorLight;
|
||||
private Steering tutorial_submarineSteering;
|
||||
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
|
||||
// Room 2
|
||||
private MotionSensor engineer_equipmentObjectiveSensor;
|
||||
private ItemContainer engineer_equipmentCabinet;
|
||||
private Door engineer_firstDoor;
|
||||
private LightComponent engineer_firstDoorLight;
|
||||
|
||||
// Room 3
|
||||
private Powered tutorial_oxygenGenerator;
|
||||
private Reactor engineer_reactor;
|
||||
private Door engineer_secondDoor;
|
||||
private LightComponent engineer_secondDoorLight;
|
||||
|
||||
// Room 4
|
||||
private Item engineer_brokenJunctionBox;
|
||||
private Door engineer_thirdDoor;
|
||||
private LightComponent engineer_thirdDoorLight;
|
||||
|
||||
// Room 5
|
||||
private PowerTransfer[] engineer_disconnectedJunctionBoxes;
|
||||
private ConnectionPanel[] engineer_disconnectedConnectionPanels;
|
||||
private Item engineer_wire_1;
|
||||
private Powered engineer_lamp_1;
|
||||
private Item engineer_wire_2;
|
||||
private Powered engineer_lamp_2;
|
||||
private Door engineer_fourthDoor;
|
||||
private LightComponent engineer_fourthDoorLight;
|
||||
|
||||
// Room 6
|
||||
private Pump engineer_workingPump;
|
||||
private Door tutorial_lockedDoor_1;
|
||||
|
||||
// Submarine
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
private MotionSensor tutorial_enteredSubmarineSensor;
|
||||
private Item engineer_submarineJunctionBox_1;
|
||||
private Item engineer_submarineJunctionBox_2;
|
||||
private Item engineer_submarineJunctionBox_3;
|
||||
private Reactor engineer_submarineReactor;
|
||||
|
||||
// Variables
|
||||
private LocalizedString radioSpeakerName;
|
||||
private Character engineer;
|
||||
private int[] reactorLoads = new int[5] { 1500, 3000, 2000, 5000, 3500 };
|
||||
private float reactorLoadChangeTime = 2f;
|
||||
private float reactorLoadError = 200f;
|
||||
private bool reactorOperatedProperly;
|
||||
private const float waterVolumeBeforeOpening = 15f;
|
||||
private Sprite engineer_repairIcon;
|
||||
private Color engineer_repairIconColor;
|
||||
private Sprite engineer_reactorIcon;
|
||||
private Color engineer_reactorIconColor;
|
||||
private bool wiringActive = false;
|
||||
|
||||
public EngineerTutorial() : base("tutorial.engineertraining".ToIdentifier(),
|
||||
new Segment(
|
||||
"Mechanic.Equipment".ToIdentifier(),
|
||||
"Mechanic.EquipmentObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Engineer.Reactor".ToIdentifier(),
|
||||
"Engineer.ReactorObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_reactor.webm", TextTag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80 }),
|
||||
new Segment(
|
||||
"Engineer.OperateReactor".ToIdentifier(),
|
||||
"Engineer.OperateReactorObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Engineer.OperateReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_reactor.webm", TextTag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80 }),
|
||||
new Segment(
|
||||
"Engineer.RepairJunctionBox".ToIdentifier(),
|
||||
"Engineer.RepairJunctionBoxObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Engineer.RepairJunctionBoxText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Engineer.WireJunctionBoxes".ToIdentifier(),
|
||||
"Engineer.WireJunctionBoxesObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Engineer.WireJunctionBoxesText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_wiring.webm", TextTag = "Engineer.WireJunctionBoxesText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Engineer.RepairElectricalRoom".ToIdentifier(),
|
||||
"Engineer.RepairElectricalRoomObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Engineer.RepairElectricalRoomText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Engineer.PowerUpReactor".ToIdentifier(),
|
||||
"Engineer.PowerUpReactorObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Engineer.PowerUpReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center }))
|
||||
{ }
|
||||
|
||||
protected override CharacterInfo GetCharacterInfo()
|
||||
{
|
||||
return new CharacterInfo(
|
||||
CharacterPrefab.HumanSpeciesName,
|
||||
jobOrJobPrefab: new Job(
|
||||
JobPrefab.Prefabs["engineer"], Rand.RandSync.Unsynced, 0,
|
||||
new Skill("medical".ToIdentifier(), 0),
|
||||
new Skill("weapons".ToIdentifier(), 0),
|
||||
new Skill("mechanical".ToIdentifier(), 20),
|
||||
new Skill("electrical".ToIdentifier(), 60),
|
||||
new Skill("helm".ToIdentifier(), 0)));
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||
engineer = Character.Controlled;
|
||||
|
||||
foreach (Item item in engineer.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
|
||||
item.Unequip(engineer);
|
||||
engineer.Inventory.RemoveItem(item);
|
||||
}
|
||||
|
||||
var repairOrder = OrderPrefab.Prefabs["repairsystems"];
|
||||
engineer_repairIcon = repairOrder.SymbolSprite;
|
||||
engineer_repairIconColor = repairOrder.Color;
|
||||
|
||||
var reactorOrder = OrderPrefab.Prefabs["operatereactor"];
|
||||
engineer_reactorIcon = reactorOrder.SymbolSprite;
|
||||
engineer_reactorIconColor = reactorOrder.Color;
|
||||
|
||||
// Other tutorial items
|
||||
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
||||
|
||||
tutorial_submarineSteering.CanBeSelected = false;
|
||||
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
|
||||
{
|
||||
ic.CanBeSelected = false;
|
||||
}
|
||||
|
||||
SetDoorAccess(null, tutorial_securityFinalDoorLight, false);
|
||||
SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false);
|
||||
|
||||
// Room 2
|
||||
engineer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_equipmentobjectivesensor")).GetComponent<MotionSensor>();
|
||||
engineer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("engineer_equipmentcabinet")).GetComponent<ItemContainer>();
|
||||
engineer_firstDoor = Item.ItemList.Find(i => i.HasTag("engineer_firstdoor")).GetComponent<Door>();
|
||||
engineer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_firstdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, false);
|
||||
|
||||
// Room 3
|
||||
tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent<OxygenGenerator>();
|
||||
engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent<Reactor>();
|
||||
engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity;
|
||||
engineer_reactor.FuelConsumptionRate = 0.0f;
|
||||
engineer_reactor.PowerOn = true;
|
||||
reactorOperatedProperly = false;
|
||||
|
||||
engineer_secondDoor = Item.ItemList.Find(i => i.HasTag("engineer_seconddoor")).GetComponent<Door>(); ;
|
||||
engineer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_seconddoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, false);
|
||||
|
||||
// Room 4
|
||||
engineer_brokenJunctionBox = Item.ItemList.Find(i => i.HasTag("engineer_brokenjunctionbox"));
|
||||
engineer_thirdDoor = Item.ItemList.Find(i => i.HasTag("engineer_thirddoor")).GetComponent<Door>();
|
||||
engineer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_thirddoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
engineer_brokenJunctionBox.Indestructible = false;
|
||||
engineer_brokenJunctionBox.Condition = 0f;
|
||||
|
||||
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, false);
|
||||
|
||||
// Room 5
|
||||
engineer_disconnectedJunctionBoxes = new PowerTransfer[4];
|
||||
engineer_disconnectedConnectionPanels = new ConnectionPanel[4];
|
||||
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
engineer_disconnectedJunctionBoxes[i] = Item.ItemList.Find(item => item.HasTag($"engineer_disconnectedjunctionbox_{i + 1}")).GetComponent<PowerTransfer>();
|
||||
engineer_disconnectedConnectionPanels[i] = engineer_disconnectedJunctionBoxes[i].Item.GetComponent<ConnectionPanel>();
|
||||
engineer_disconnectedConnectionPanels[i].Locked = false;
|
||||
|
||||
for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++)
|
||||
{
|
||||
foreach (Wire wire in engineer_disconnectedJunctionBoxes[i].PowerConnections[j].Wires)
|
||||
{
|
||||
if (wire == null) continue;
|
||||
wire.Locked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
engineer_wire_1 = Item.ItemList.Find(i => i.HasTag("engineer_wire_1"));
|
||||
engineer_wire_1.SpriteColor = Color.Transparent;
|
||||
engineer_wire_2 = Item.ItemList.Find(i => i.HasTag("engineer_wire_2"));
|
||||
engineer_wire_2.SpriteColor = Color.Transparent;
|
||||
engineer_lamp_1 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_1")).GetComponent<Powered>();
|
||||
engineer_lamp_2 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_2")).GetComponent<Powered>();
|
||||
engineer_fourthDoor = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoor")).GetComponent<Door>();
|
||||
engineer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoorlight")).GetComponent<LightComponent>();
|
||||
SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, false);
|
||||
|
||||
// Room 6
|
||||
engineer_workingPump = Item.ItemList.Find(i => i.HasTag("engineer_workingpump")).GetComponent<Pump>();
|
||||
engineer_workingPump.Item.CurrentHull.WaterVolume += engineer_workingPump.Item.CurrentHull.Volume;
|
||||
engineer_workingPump.IsActive = true;
|
||||
tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent<Door>();
|
||||
SetDoorAccess(tutorial_lockedDoor_1, null, true);
|
||||
|
||||
// Submarine
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
|
||||
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
|
||||
engineer_submarineJunctionBox_1 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_1"));
|
||||
engineer_submarineJunctionBox_2 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_2"));
|
||||
engineer_submarineJunctionBox_3 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_3"));
|
||||
engineer_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent<Reactor>();
|
||||
engineer_submarineReactor.PowerOn = true;
|
||||
engineer_submarineReactor.IsActive = engineer_submarineReactor.AutoTemp = false;
|
||||
|
||||
engineer_submarineJunctionBox_1.Indestructible = false;
|
||||
engineer_submarineJunctionBox_1.Condition = 0f;
|
||||
engineer_submarineJunctionBox_2.Indestructible = false;
|
||||
engineer_submarineJunctionBox_2.Condition = 0f;
|
||||
engineer_submarineJunctionBox_3.Indestructible = false;
|
||||
engineer_submarineJunctionBox_3.Condition = 0f;
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Started");
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
|
||||
}
|
||||
|
||||
public override IEnumerable<CoroutineStatus> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) { yield return null; }
|
||||
|
||||
// Room 1
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
|
||||
//// Remove
|
||||
//for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
//{
|
||||
// SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true);
|
||||
//}
|
||||
//do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump
|
||||
//CheckGhostWires();
|
||||
//// Remove
|
||||
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||
SetHighlight(engineer_equipmentCabinet.Item, true);
|
||||
|
||||
// Room 2
|
||||
do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(0.5f, false);
|
||||
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Retrieve equipment
|
||||
bool firstSlotRemoved = false;
|
||||
bool secondSlotRemoved = false;
|
||||
bool thirdSlotRemoved = false;
|
||||
bool fourthSlotRemoved = false;
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_equipmentCabinet.Item))
|
||||
{
|
||||
if (!firstSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
if (engineer_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; }
|
||||
}
|
||||
|
||||
if (!secondSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
|
||||
if (engineer_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; }
|
||||
}
|
||||
|
||||
if (!thirdSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
|
||||
if (engineer_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; }
|
||||
}
|
||||
|
||||
if (!fourthSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 3, highlightColor, .5f, .5f, 0f);
|
||||
if (engineer_equipmentCabinet.Inventory.GetItemAt(2) == null) { fourthSlotRemoved = true; }
|
||||
}
|
||||
|
||||
for (int i = 0; i < engineer.Inventory.visualSlots.Length; i++)
|
||||
{
|
||||
if (engineer.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(engineer.Inventory, i, highlightColor, .5f, .5f, 0f); }
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
} while (!engineer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted
|
||||
RemoveCompletedObjective(0);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective0");
|
||||
SetHighlight(engineer_equipmentCabinet.Item, false);
|
||||
SetHighlight(engineer_reactor.Item, true);
|
||||
SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, true);
|
||||
|
||||
// Room 3
|
||||
do { yield return null; } while (!IsSelectedItem(engineer_reactor.Item));
|
||||
yield return new WaitForSeconds(0.5f, false);
|
||||
TriggerTutorialSegment(1);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_reactor.Item))
|
||||
{
|
||||
engineer_reactor.AutoTemp = false;
|
||||
if (engineer_reactor.PowerButton.FlashTimer <= 0)
|
||||
{
|
||||
engineer_reactor.PowerButton.Flash(highlightColor, 1.5f, false);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!engineer_reactor.PowerOn);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_reactor.Item) && engineer_reactor.Item.OwnInventory.visualSlots != null)
|
||||
{
|
||||
engineer_reactor.AutoTemp = false;
|
||||
HighlightInventorySlot(engineer.Inventory, "fuelrod".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
|
||||
|
||||
for (int i = 0; i < engineer_reactor.Item.OwnInventory.visualSlots.Length; i++)
|
||||
{
|
||||
HighlightInventorySlot(engineer_reactor.Item.OwnInventory, i, highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (engineer_reactor.AvailableFuel == 0);
|
||||
RemoveCompletedObjective(1);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective1");
|
||||
TriggerTutorialSegment(2);
|
||||
CoroutineManager.StartCoroutine(ReactorOperatedProperly());
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_reactor.Item))
|
||||
{
|
||||
engineer_reactor.AutoTemp = false;
|
||||
if (engineer_reactor.FissionRateScrollBar.FlashTimer <= 0)
|
||||
{
|
||||
engineer_reactor.FissionRateScrollBar.Flash(highlightColor, 1.5f);
|
||||
}
|
||||
|
||||
if (engineer_reactor.TurbineOutputScrollBar.FlashTimer <= 0)
|
||||
{
|
||||
engineer_reactor.TurbineOutputScrollBar.Flash(highlightColor, 1.5f);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!reactorOperatedProperly);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ReactorStable"), ChatMessageType.Radio, null);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_reactor.Item))
|
||||
{
|
||||
if (engineer_reactor.AutoTempSwitch.FlashTimer <= 0)
|
||||
{
|
||||
engineer_reactor.AutoTempSwitch.Flash(highlightColor, 1.5f, false, false, new Vector2(10, 10));
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!engineer_reactor.AutoTemp);
|
||||
|
||||
float wait = 1.5f;
|
||||
do
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
wait -= 0.1f;
|
||||
engineer_reactor.AutoTemp = true;
|
||||
} while (wait > 0.0f);
|
||||
engineer.SelectedItem = null;
|
||||
engineer_reactor.CanBeSelected = false;
|
||||
RemoveCompletedObjective(2);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective2");
|
||||
SetHighlight(engineer_reactor.Item, false);
|
||||
SetHighlight(engineer_brokenJunctionBox, true);
|
||||
SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, true);
|
||||
|
||||
// Room 4
|
||||
do { yield return null; } while (!engineer_secondDoor.IsOpen);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
Repairable repairableJunctionBoxComponent = engineer_brokenJunctionBox.GetComponent<Repairable>();
|
||||
TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Repair the junction box
|
||||
do
|
||||
{
|
||||
if (!engineer.HasEquippedItem("screwdriver".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlot(engineer.Inventory, "screwdriver".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
else if (IsSelectedItem(engineer_brokenJunctionBox) && repairableJunctionBoxComponent.CurrentFixer == null)
|
||||
{
|
||||
if (repairableJunctionBoxComponent.RepairButton.FlashTimer <= 0)
|
||||
{
|
||||
repairableJunctionBoxComponent.RepairButton.Flash();
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (repairableJunctionBoxComponent.IsBelowRepairThreshold); // Wait until repaired
|
||||
SetHighlight(engineer_brokenJunctionBox, false);
|
||||
RemoveCompletedObjective(3);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective3");
|
||||
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true);
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true);
|
||||
}
|
||||
|
||||
// Room 5
|
||||
do { yield return null; } while (!engineer_thirdDoor.IsOpen);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Connect the junction boxes
|
||||
do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump
|
||||
CheckGhostWires();
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false);
|
||||
}
|
||||
RemoveCompletedObjective(4);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective4");
|
||||
do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained
|
||||
wiringActive = false;
|
||||
SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ChangeOfPlans"), ChatMessageType.Radio, null);
|
||||
|
||||
// Submarine
|
||||
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Submarine"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(5); // Repair junction box
|
||||
while (ContentRunning) yield return null;
|
||||
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor);
|
||||
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor);
|
||||
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_3, engineer_repairIcon, engineer_repairIconColor);
|
||||
SetHighlight(engineer_submarineJunctionBox_1, true);
|
||||
SetHighlight(engineer_submarineJunctionBox_2, true);
|
||||
SetHighlight(engineer_submarineJunctionBox_3, true);
|
||||
|
||||
Repairable repairableJunctionBoxComponent1 = engineer_submarineJunctionBox_1.GetComponent<Repairable>();
|
||||
Repairable repairableJunctionBoxComponent2 = engineer_submarineJunctionBox_2.GetComponent<Repairable>();
|
||||
Repairable repairableJunctionBoxComponent3 = engineer_submarineJunctionBox_3.GetComponent<Repairable>();
|
||||
|
||||
// Remove highlights when each individual machine is repaired
|
||||
do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (repairableJunctionBoxComponent1.IsBelowRepairThreshold || repairableJunctionBoxComponent2.IsBelowRepairThreshold || repairableJunctionBoxComponent3.IsBelowRepairThreshold);
|
||||
CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3);
|
||||
RemoveCompletedObjective(5);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective5");
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
|
||||
TriggerTutorialSegment(6); // Powerup reactor
|
||||
SetHighlight(engineer_submarineReactor.Item, true);
|
||||
engineer.AddActiveObjectiveEntity(engineer_submarineReactor.Item, engineer_reactorIcon, engineer_reactorIconColor);
|
||||
do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load
|
||||
engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item);
|
||||
SetHighlight(engineer_submarineReactor.Item, false);
|
||||
RemoveCompletedObjective(6);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective6");
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null);
|
||||
|
||||
yield return new WaitForSeconds(4f, false);
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Completed");
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (wiringActive)
|
||||
{
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++)
|
||||
{
|
||||
engineer_disconnectedJunctionBoxes[i].PowerConnections[j].UpdateFlashTimer(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSelectedItem(Item item)
|
||||
{
|
||||
return engineer?.SelectedItem == item;
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> ReactorOperatedProperly()
|
||||
{
|
||||
float timer;
|
||||
|
||||
for (int i = 0; i < reactorLoads.Length; i++)
|
||||
{
|
||||
timer = reactorLoadChangeTime;
|
||||
tutorial_oxygenGenerator.PowerConsumption = reactorLoads[i];
|
||||
while (timer > 0)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
if (CoroutineManager.DeltaTime > 0.0f && IsReactorPoweredUp(engineer_reactor))
|
||||
{
|
||||
timer -= CoroutineManager.DeltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reactorOperatedProperly = true;
|
||||
}
|
||||
|
||||
private void CheckGhostWires()
|
||||
{
|
||||
Color wireColor =
|
||||
Color.Orange *
|
||||
MathHelper.Lerp(0.25f, 0.75f, (float)(Math.Sin((Timing.TotalTime * 4.0f)) + 1.0f) / 2.0f);
|
||||
|
||||
if (engineer_wire_1 != null)
|
||||
{
|
||||
engineer_wire_1.SpriteColor = wireColor;
|
||||
if (engineer_lamp_1.Voltage > engineer_lamp_1.MinVoltage)
|
||||
{
|
||||
engineer_wire_1.Remove();
|
||||
engineer_wire_1 = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (engineer_wire_2 != null)
|
||||
{
|
||||
engineer_wire_2.SpriteColor = wireColor;
|
||||
if (engineer_lamp_2.Voltage > engineer_lamp_2.MinVoltage)
|
||||
{
|
||||
engineer_wire_2.Remove();
|
||||
engineer_wire_2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void HandleJunctionBoxWiringHighlights()
|
||||
{
|
||||
Item selected = engineer.SelectedItem;
|
||||
|
||||
if (!engineer.HasEquippedItem("screwdriver".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlot(engineer.Inventory, "screwdriver".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
|
||||
int selectedIndex = -1;
|
||||
|
||||
if (selected != null)
|
||||
{
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
if (selected == engineer_disconnectedJunctionBoxes[i].Item)
|
||||
{
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wiringActive = selectedIndex != -1;
|
||||
|
||||
if (!engineer.HasEquippedItem("wire".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlotWithTag(engineer.Inventory, "wire".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!wiringActive) return;
|
||||
for (int i = 0; i < engineer_disconnectedConnectionPanels[selectedIndex].Connections.Count; i++)
|
||||
{
|
||||
var connection = engineer_disconnectedConnectionPanels[selectedIndex].Connections[i];
|
||||
if (connection.IsPower && connection.FlashTimer <= 0)
|
||||
{
|
||||
foreach (Wire wire in engineer_disconnectedConnectionPanels[selectedIndex].Connections[i].Wires)
|
||||
{
|
||||
if (wire == null) continue;
|
||||
if (!wire.Locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connection.Flash(highlightColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckJunctionBoxHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
|
||||
{
|
||||
if (!comp1.IsBelowRepairThreshold && engineer_submarineJunctionBox_1.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(engineer_submarineJunctionBox_1, false);
|
||||
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1);
|
||||
}
|
||||
if (!comp2.IsBelowRepairThreshold && engineer_submarineJunctionBox_2.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(engineer_submarineJunctionBox_2, false);
|
||||
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2);
|
||||
}
|
||||
if (!comp3.IsBelowRepairThreshold && engineer_submarineJunctionBox_3.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(engineer_submarineJunctionBox_3, false);
|
||||
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsReactorPoweredUp(Reactor reactor)
|
||||
{
|
||||
float load = 0.0f;
|
||||
List<Connection> connections = reactor.Item.Connections;
|
||||
if (connections != null && connections.Count > 0)
|
||||
{
|
||||
foreach (Connection connection in connections)
|
||||
{
|
||||
if (!connection.IsPower) continue;
|
||||
foreach (Connection recipient in connection.Recipients)
|
||||
{
|
||||
if (!(recipient.Item is Item it)) continue;
|
||||
|
||||
PowerTransfer pt = it.GetComponent<PowerTransfer>();
|
||||
if (pt == null) continue;
|
||||
|
||||
load = Math.Max(load, pt.PowerLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Math.Abs(load + reactor.CurrPowerConsumption) < reactorLoadError;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,737 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class MechanicTutorial : ScenarioTutorial
|
||||
{
|
||||
// Other tutorial items
|
||||
private LightComponent tutorial_securityFinalDoorLight;
|
||||
private Door tutorial_upperFinalDoor;
|
||||
private Steering tutorial_submarineSteering;
|
||||
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
private Door mechanic_firstDoor;
|
||||
private LightComponent mechanic_firstDoorLight;
|
||||
|
||||
// Room 2
|
||||
private MotionSensor mechanic_equipmentObjectiveSensor;
|
||||
private ItemContainer mechanic_equipmentCabinet;
|
||||
private Door mechanic_secondDoor;
|
||||
private LightComponent mechanic_secondDoorLight;
|
||||
|
||||
// Room 3
|
||||
private MotionSensor mechanic_weldingObjectiveSensor;
|
||||
private Pump mechanic_workingPump;
|
||||
private Door mechanic_thirdDoor;
|
||||
private LightComponent mechanic_thirdDoorLight;
|
||||
private Structure mechanic_brokenWall_1;
|
||||
private Hull mechanic_brokenhull_1;
|
||||
|
||||
// Room 4
|
||||
private MotionSensor mechanic_craftingObjectiveSensor;
|
||||
private Deconstructor mechanic_deconstructor;
|
||||
private Fabricator mechanic_fabricator;
|
||||
private ItemContainer mechanic_craftingCabinet;
|
||||
private Door mechanic_fourthDoor;
|
||||
private LightComponent mechanic_fourthDoorLight;
|
||||
|
||||
// Room 5
|
||||
private MotionSensor mechanic_fireSensor;
|
||||
private DummyFireSource mechanic_fire;
|
||||
private Door mechanic_fifthDoor;
|
||||
private LightComponent mechanic_fifthDoorLight;
|
||||
|
||||
// Room 6
|
||||
private MotionSensor mechanic_divingSuitObjectiveSensor;
|
||||
private ItemContainer mechanic_divingSuitContainer;
|
||||
private ItemContainer mechanic_oxygenContainer;
|
||||
private Door tutorial_mechanicFinalDoor;
|
||||
private LightComponent tutorial_mechanicFinalDoorLight;
|
||||
|
||||
// Room 7
|
||||
private Pump mechanic_brokenPump;
|
||||
private Structure mechanic_brokenWall_2;
|
||||
private Hull mechanic_brokenhull_2;
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
|
||||
// Submarine
|
||||
private MotionSensor tutorial_enteredSubmarineSensor;
|
||||
private Engine mechanic_submarineEngine;
|
||||
private Pump mechanic_ballastPump_1;
|
||||
private Pump mechanic_ballastPump_2;
|
||||
|
||||
// Variables
|
||||
private const float waterVolumeBeforeOpening = 15f;
|
||||
private LocalizedString radioSpeakerName;
|
||||
private Character mechanic;
|
||||
private Sprite mechanic_repairIcon;
|
||||
private Color mechanic_repairIconColor;
|
||||
private Sprite mechanic_weldIcon;
|
||||
|
||||
public MechanicTutorial() : base("tutorial.mechanictraining".ToIdentifier(),
|
||||
new Segment(
|
||||
"Mechanic.OpenDoor".ToIdentifier(),
|
||||
"Mechanic.OpenDoorObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.OpenDoorText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Mechanic.Equipment".ToIdentifier(),
|
||||
"Mechanic.EquipmentObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_inventory.webm", TextTag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Mechanic.Welding".ToIdentifier(),
|
||||
"Mechanic.WeldingObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.WeldingText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_equip.webm", TextTag = "Mechanic.WeldingText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Mechanic.Drain".ToIdentifier(),
|
||||
"Mechanic.DrainObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.DrainText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Mechanic.Deconstruct".ToIdentifier(),
|
||||
"Mechanic.DeconstructObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.DeconstructText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_deconstruct.webm", TextTag = "Mechanic.DeconstructText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Mechanic.Fabricate".ToIdentifier(),
|
||||
"Mechanic.FabricateObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.FabricateText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_fabricate.webm", TextTag = "Mechanic.FabricateText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Mechanic.Extinguisher".ToIdentifier(),
|
||||
"Mechanic.ExtinguisherObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.ExtinguisherText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Mechanic.DropExtinguisher".ToIdentifier(),
|
||||
"Mechanic.DropExtinguisherObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.DropExtinguisherText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Mechanic.Diving".ToIdentifier(),
|
||||
"Mechanic.DivingObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.DivingText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Mechanic.RepairPump".ToIdentifier(),
|
||||
"Mechanic.RepairPumpObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.RepairPumpText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Mechanic.RepairSubmarine".ToIdentifier(),
|
||||
"Mechanic.RepairSubmarineObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.RepairSubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"tutorial.laddertitle".ToIdentifier(),
|
||||
"tutorial.laddertitle".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "tutorial.ladderdescription".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }))
|
||||
{ }
|
||||
|
||||
protected override CharacterInfo GetCharacterInfo()
|
||||
{
|
||||
return new CharacterInfo(
|
||||
CharacterPrefab.HumanSpeciesName,
|
||||
jobOrJobPrefab: new Job(
|
||||
JobPrefab.Prefabs["mechanic"], Rand.RandSync.Unsynced, 0,
|
||||
new Skill("medical".ToIdentifier(), 0),
|
||||
new Skill("weapons".ToIdentifier(), 0),
|
||||
new Skill("mechanical".ToIdentifier(), 50),
|
||||
new Skill("electrical".ToIdentifier(), 20),
|
||||
new Skill("helm".ToIdentifier(), 0)));
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||
mechanic = Character.Controlled;
|
||||
|
||||
foreach (Item item in mechanic.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
|
||||
item.Unequip(mechanic);
|
||||
mechanic.Inventory.RemoveItem(item);
|
||||
}
|
||||
|
||||
var repairOrder = OrderPrefab.Prefabs["repairsystems"];
|
||||
mechanic_repairIcon = repairOrder.SymbolSprite;
|
||||
mechanic_repairIconColor = repairOrder.Color;
|
||||
mechanic_weldIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(1, 256, 127, 127), new Vector2(0.5f, 0.5f));
|
||||
|
||||
// Other tutorial items
|
||||
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent<Door>();
|
||||
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
||||
|
||||
tutorial_submarineSteering.CanBeSelected = false;
|
||||
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
|
||||
{
|
||||
ic.CanBeSelected = false;
|
||||
}
|
||||
|
||||
SetDoorAccess(null, tutorial_securityFinalDoorLight, false);
|
||||
SetDoorAccess(tutorial_upperFinalDoor, null, false);
|
||||
|
||||
// Room 1
|
||||
mechanic_firstDoor = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoor")).GetComponent<Door>();
|
||||
mechanic_firstDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, false);
|
||||
|
||||
// Room 2
|
||||
mechanic_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentobjectivesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentcabinet")).GetComponent<ItemContainer>();
|
||||
mechanic_secondDoor = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoor")).GetComponent<Door>();
|
||||
mechanic_secondDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, false);
|
||||
|
||||
// Room 3
|
||||
mechanic_weldingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_weldingobjectivesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_workingPump = Item.ItemList.Find(i => i.HasTag("mechanic_workingpump")).GetComponent<Pump>();
|
||||
mechanic_thirdDoor = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoor")).GetComponent<Door>();
|
||||
mechanic_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoorlight")).GetComponent<LightComponent>();
|
||||
mechanic_brokenWall_1 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_1");
|
||||
//mechanic_ladderSensor = Item.ItemList.Find(i => i.HasTag("mechanic_laddersensor")).GetComponent<MotionSensor>();
|
||||
|
||||
SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, false);
|
||||
mechanic_brokenWall_1.Indestructible = false;
|
||||
mechanic_brokenWall_1.SpriteColor = Color.White;
|
||||
for (int i = 0; i < mechanic_brokenWall_1.SectionCount; i++)
|
||||
{
|
||||
mechanic_brokenWall_1.AddDamage(i, 85);
|
||||
}
|
||||
mechanic_brokenhull_1 = mechanic_brokenWall_1.Sections[0].gap.FlowTargetHull;
|
||||
|
||||
// Room 4
|
||||
mechanic_craftingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_craftingobjectivesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_deconstructor = Item.ItemList.Find(i => i.HasTag("mechanic_deconstructor")).GetComponent<Deconstructor>();
|
||||
mechanic_fabricator = Item.ItemList.Find(i => i.HasTag("mechanic_fabricator")).GetComponent<Fabricator>();
|
||||
mechanic_craftingCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_craftingcabinet")).GetComponent<ItemContainer>();
|
||||
mechanic_fourthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoor")).GetComponent<Door>();
|
||||
mechanic_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, false);
|
||||
|
||||
// Room 5
|
||||
mechanic_fifthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoor")).GetComponent<Door>();
|
||||
mechanic_fifthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoorlight")).GetComponent<LightComponent>();
|
||||
mechanic_fireSensor = Item.ItemList.Find(i => i.HasTag("mechanic_firesensor")).GetComponent<MotionSensor>();
|
||||
|
||||
SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, false);
|
||||
|
||||
// Room 6
|
||||
mechanic_divingSuitObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitobjectivesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_divingSuitContainer = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitcontainer")).GetComponent<ItemContainer>();
|
||||
foreach (Item item in mechanic_divingSuitContainer.Inventory.AllItems)
|
||||
{
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
ic.CanBePicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
mechanic_oxygenContainer = Item.ItemList.Find(i => i.HasTag("mechanic_oxygencontainer")).GetComponent<ItemContainer>();
|
||||
foreach (Item item in mechanic_oxygenContainer.Inventory.AllItems)
|
||||
{
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
ic.CanBePicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
tutorial_mechanicFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoor")).GetComponent<Door>();
|
||||
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, false);
|
||||
|
||||
// Room 7
|
||||
mechanic_brokenPump = Item.ItemList.Find(i => i.HasTag("mechanic_brokenpump")).GetComponent<Pump>();
|
||||
mechanic_brokenPump.Item.Indestructible = false;
|
||||
mechanic_brokenPump.Item.Condition = 0;
|
||||
mechanic_brokenPump.CanBeSelected = false;
|
||||
mechanic_brokenPump.Item.GetComponent<Repairable>().CanBeSelected = false;
|
||||
mechanic_brokenWall_2 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_2");
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
mechanic_brokenWall_2.Indestructible = false;
|
||||
mechanic_brokenWall_2.SpriteColor = Color.White;
|
||||
for (int i = 0; i < mechanic_brokenWall_2.SectionCount; i++)
|
||||
{
|
||||
mechanic_brokenWall_2.AddDamage(i, 85);
|
||||
}
|
||||
mechanic_brokenhull_2 = mechanic_brokenWall_2.Sections[0].gap.FlowTargetHull;
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
|
||||
|
||||
// Submarine
|
||||
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_submarineEngine = Item.ItemList.Find(i => i.HasTag("mechanic_submarineengine")).GetComponent<Engine>();
|
||||
mechanic_submarineEngine.Item.Indestructible = false;
|
||||
mechanic_submarineEngine.Item.Condition = 0f;
|
||||
mechanic_ballastPump_1 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_1")).GetComponent<Pump>();
|
||||
mechanic_ballastPump_1.Item.Indestructible = false;
|
||||
mechanic_ballastPump_1.Item.Condition = 0f;
|
||||
mechanic_ballastPump_2 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_2")).GetComponent<Pump>();
|
||||
mechanic_ballastPump_2.Item.Indestructible = false;
|
||||
mechanic_ballastPump_2.Item.Condition = 0f;
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Started");
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (mechanic_brokenhull_1 != null)
|
||||
{
|
||||
mechanic_brokenhull_1.WaterVolume = MathHelper.Clamp(mechanic_brokenhull_1.WaterVolume, 0, mechanic_brokenhull_1.Volume * 0.85f);
|
||||
}
|
||||
base.Update(deltaTime);
|
||||
}
|
||||
|
||||
public override IEnumerable<CoroutineStatus> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
// Room 1
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
yield return new WaitForSeconds(2.5f, false);
|
||||
|
||||
mechanic_fabricator.RemoveFabricationRecipes(allowedIdentifiers:
|
||||
new[] { "extinguisher", "wrench", "weldingtool", "weldingfuel", "divingmask", "railgunshell", "nuclearshell", "uex", "harpoongun" }.ToIdentifiers());
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||
|
||||
yield return new WaitForSeconds(2.5f, false);
|
||||
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Up), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Left), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Down), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Right), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Open door objective
|
||||
yield return new WaitForSeconds(0.0f, false);
|
||||
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, true);
|
||||
SetHighlight(mechanic_firstDoor.Item, true);
|
||||
do { yield return null; } while (!mechanic_firstDoor.IsOpen);
|
||||
SetHighlight(mechanic_firstDoor.Item, false);
|
||||
yield return new WaitForSeconds(1.5f, false);
|
||||
RemoveCompletedObjective(0);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective0");
|
||||
|
||||
// Room 2
|
||||
yield return new WaitForSeconds(0.0f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null);
|
||||
do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Equipment & inventory objective
|
||||
SetHighlight(mechanic_equipmentCabinet.Item, true);
|
||||
bool firstSlotRemoved = false;
|
||||
bool secondSlotRemoved = false;
|
||||
bool thirdSlotRemoved = false;
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(mechanic_equipmentCabinet.Item))
|
||||
{
|
||||
if (!firstSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
if (mechanic_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; }
|
||||
}
|
||||
|
||||
if (!secondSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
|
||||
if (mechanic_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; }
|
||||
}
|
||||
|
||||
if (!thirdSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
|
||||
if (mechanic_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; }
|
||||
}
|
||||
|
||||
for (int i = 0; i < mechanic.Inventory.Capacity; i++)
|
||||
{
|
||||
if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); }
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
} while (mechanic.Inventory.FindItemByIdentifier("divingmask".ToIdentifier()) == null || mechanic.Inventory.FindItemByIdentifier("weldingtool".ToIdentifier()) == null || mechanic.Inventory.FindItemByIdentifier("wrench".ToIdentifier()) == null); // Wait until looted
|
||||
SetHighlight(mechanic_equipmentCabinet.Item, false);
|
||||
yield return new WaitForSeconds(1.5f, false);
|
||||
RemoveCompletedObjective(1);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective1");
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Breach"), ChatMessageType.Radio, null);
|
||||
|
||||
// Room 3
|
||||
do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Welding objective
|
||||
do
|
||||
{
|
||||
if (!mechanic.HasEquippedItem("divingmask".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "divingmask".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
|
||||
if (!mechanic.HasEquippedItem("weldingtool".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "weldingtool".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
yield return null;
|
||||
} while (!mechanic.HasEquippedItem("divingmask".ToIdentifier()) || !mechanic.HasEquippedItem("weldingtool".ToIdentifier())); // Wait until equipped
|
||||
SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, true);
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_1, mechanic_weldIcon, mechanic_repairIconColor);
|
||||
do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1);
|
||||
RemoveCompletedObjective(2);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective2");
|
||||
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Pump objective
|
||||
SetHighlight(mechanic_workingPump.Item, true);
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
if (IsSelectedItem(mechanic_workingPump.Item))
|
||||
{
|
||||
if (mechanic_workingPump.PowerButton.FlashTimer <= 0)
|
||||
{
|
||||
mechanic_workingPump.PowerButton.Flash(uiHighlightColor, 1.5f, true);
|
||||
}
|
||||
}
|
||||
} while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining
|
||||
SetHighlight(mechanic_workingPump.Item, false);
|
||||
do { yield return null; } while (mechanic_brokenhull_1 != null && mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained
|
||||
RemoveCompletedObjective(3);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective3");
|
||||
|
||||
SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, true);
|
||||
//TriggerTutorialSegment(11, GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Up], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Down], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select]); // Ladder objective
|
||||
//do { yield return null; } while (!mechanic_ladderSensor.MotionDetected);
|
||||
//RemoveCompletedObjective(segments[11]);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.News"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Fire"), ChatMessageType.Radio, null);
|
||||
|
||||
// Room 4
|
||||
do { yield return null; } while (!mechanic_thirdDoor.IsOpen);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
mechanic_fire = new DummyFireSource(new Vector2(20f, 2f), Item.ItemList.Find(i => i.HasTag("mechanic_fire")).WorldPosition);
|
||||
//do { yield return null; } while (!mechanic_craftingObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(4); // Deconstruct
|
||||
|
||||
SetHighlight(mechanic_craftingCabinet.Item, true);
|
||||
|
||||
bool gotOxygenTank = false;
|
||||
bool gotSodium = false;
|
||||
do
|
||||
{
|
||||
if (mechanic.SelectedItem == mechanic_craftingCabinet.Item)
|
||||
{
|
||||
for (int i = 0; i < mechanic.Inventory.Capacity; i++)
|
||||
{
|
||||
if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); }
|
||||
}
|
||||
|
||||
if (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) == null && mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null)
|
||||
{
|
||||
for (int i = 0; i < mechanic_craftingCabinet.Capacity; i++)
|
||||
{
|
||||
Item item = mechanic_craftingCabinet.Inventory.GetItemAt(i);
|
||||
if (item != null && item.Prefab.Identifier == "oxygentank")
|
||||
{
|
||||
HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) == null)
|
||||
{
|
||||
for (int i = 0; i < mechanic_craftingCabinet.Inventory.Capacity; i++)
|
||||
{
|
||||
Item item = mechanic_craftingCabinet.Inventory.GetItemAt(i);
|
||||
if (item != null && item.Prefab.Identifier == "sodium")
|
||||
{
|
||||
HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotOxygenTank && (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null ||
|
||||
mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null))
|
||||
{
|
||||
gotOxygenTank = true;
|
||||
}
|
||||
if (!gotSodium && mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null)
|
||||
{
|
||||
gotSodium = true;
|
||||
}
|
||||
yield return null;
|
||||
} while (!gotOxygenTank || !gotSodium); // Wait until looted
|
||||
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
SetHighlight(mechanic_craftingCabinet.Item, false);
|
||||
SetHighlight(mechanic_deconstructor.Item, true);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(mechanic_deconstructor.Item))
|
||||
{
|
||||
if (mechanic_deconstructor.OutputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_deconstructor.OutputContainer.Inventory, "aluminium".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
|
||||
for (int i = 0; i < mechanic.Inventory.Capacity; i++)
|
||||
{
|
||||
if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null && mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) == null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "oxygentank".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
for (int i = 0; i < mechanic_deconstructor.InputContainer.Inventory.Capacity; i++)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_deconstructor.InputContainer.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null && !mechanic_deconstructor.IsActive)
|
||||
{
|
||||
if (mechanic_deconstructor.ActivateButton.FlashTimer <= 0)
|
||||
{
|
||||
mechanic_deconstructor.ActivateButton.Flash(highlightColor, 1.5f, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (
|
||||
mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null &&
|
||||
mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null); // Wait until aluminium obtained
|
||||
|
||||
SetHighlight(mechanic_deconstructor.Item, false);
|
||||
RemoveCompletedObjective(4);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective4");
|
||||
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
TriggerTutorialSegment(5); // Fabricate
|
||||
SetHighlight(mechanic_fabricator.Item, true);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(mechanic_fabricator.Item))
|
||||
{
|
||||
if (mechanic_fabricator.SelectedItem?.TargetItem.Identifier != "extinguisher")
|
||||
{
|
||||
mechanic_fabricator.HighlightRecipe("extinguisher", highlightColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mechanic_fabricator.OutputContainer.Inventory.FindItemByIdentifier("extinguisher".ToIdentifier()) != null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_fabricator.OutputContainer.Inventory, "extinguisher".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
|
||||
/*for (int i = 0; i < mechanic.Inventory.Capacity; i++)
|
||||
{
|
||||
if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}*/
|
||||
}
|
||||
else if (mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null && mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null && !mechanic_fabricator.IsActive)
|
||||
{
|
||||
if (mechanic_fabricator.ActivateButton.FlashTimer <= 0)
|
||||
{
|
||||
mechanic_fabricator.ActivateButton.Flash(highlightColor, 1.5f, false);
|
||||
}
|
||||
}
|
||||
else if (mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null || mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "aluminium".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
HighlightInventorySlot(mechanic.Inventory, "sodium".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
|
||||
if (mechanic_fabricator.InputContainer.Inventory.GetItemAt(0) == null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
|
||||
if (mechanic_fabricator.InputContainer.Inventory.GetItemAt(1) == null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 1, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (mechanic.Inventory.FindItemByIdentifier("extinguisher".ToIdentifier()) == null); // Wait until extinguisher is created
|
||||
RemoveCompletedObjective(5);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective5");
|
||||
SetHighlight(mechanic_fabricator.Item, false);
|
||||
SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, true);
|
||||
|
||||
// Room 5
|
||||
do { yield return null; } while (!mechanic_fireSensor.MotionDetected);
|
||||
TriggerTutorialSegment(6, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Using the extinguisher
|
||||
do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
RemoveCompletedObjective(6);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective6");
|
||||
|
||||
if (mechanic.HasEquippedItem("extinguisher".ToIdentifier())) // do not trigger if dropped already
|
||||
{
|
||||
TriggerTutorialSegment(7);
|
||||
do
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "extinguisher".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
|
||||
yield return null;
|
||||
} while (mechanic.HasEquippedItem("extinguisher".ToIdentifier()));
|
||||
RemoveCompletedObjective(7);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective7");
|
||||
}
|
||||
SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, true);
|
||||
|
||||
// Room 6
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Diving"), ChatMessageType.Radio, null);
|
||||
do { yield return null; } while (!mechanic_divingSuitObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(8); // Dangers of pressure, equip diving suit objective
|
||||
SetHighlight(mechanic_divingSuitContainer.Item, true);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(mechanic_divingSuitContainer.Item))
|
||||
{
|
||||
if (mechanic_divingSuitContainer.Inventory.visualSlots != null)
|
||||
{
|
||||
for (int i = 0; i < mechanic_divingSuitContainer.Inventory.Capacity; i++)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_divingSuitContainer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!mechanic.HasEquippedItem("divingsuit".ToIdentifier(), slotType: InvSlotType.OuterClothes));
|
||||
SetHighlight(mechanic_divingSuitContainer.Item, false);
|
||||
RemoveCompletedObjective(8);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective8");
|
||||
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true);
|
||||
|
||||
// Room 7
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_2, mechanic_weldIcon, mechanic_repairIconColor);
|
||||
do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_2));
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
|
||||
TriggerTutorialSegment(9, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)); // Repairing machinery (pump)
|
||||
SetHighlight(mechanic_brokenPump.Item, true);
|
||||
mechanic_brokenPump.CanBeSelected = true;
|
||||
Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent<Repairable>();
|
||||
repairablePumpComponent.CanBeSelected = true;
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
if (repairablePumpComponent.IsBelowRepairThreshold)
|
||||
{
|
||||
if (!mechanic.HasEquippedItem("wrench".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "wrench".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
else if (IsSelectedItem(mechanic_brokenPump.Item) && repairablePumpComponent.CurrentFixer == null)
|
||||
{
|
||||
if (repairablePumpComponent.RepairButton.FlashTimer <= 0)
|
||||
{
|
||||
repairablePumpComponent.RepairButton.Flash();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsSelectedItem(mechanic_brokenPump.Item))
|
||||
{
|
||||
if (mechanic_brokenPump.PowerButton.FlashTimer <= 0)
|
||||
{
|
||||
mechanic_brokenPump.PowerButton.Flash(uiHighlightColor, 1.5f, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (repairablePumpComponent.IsBelowRepairThreshold || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive);
|
||||
RemoveCompletedObjective(9);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective9");
|
||||
SetHighlight(mechanic_brokenPump.Item, false);
|
||||
do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening);
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
|
||||
// Submarine
|
||||
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Submarine"), ChatMessageType.Radio, null);
|
||||
TriggerTutorialSegment(10); // Repairing ballast pumps, engine
|
||||
while (ContentRunning) yield return null;
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_1.Item, mechanic_repairIcon, mechanic_repairIconColor);
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_2.Item, mechanic_repairIcon, mechanic_repairIconColor);
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_submarineEngine.Item, mechanic_repairIcon, mechanic_repairIconColor);
|
||||
SetHighlight(mechanic_ballastPump_1.Item, true);
|
||||
SetHighlight(mechanic_ballastPump_2.Item, true);
|
||||
SetHighlight(mechanic_submarineEngine.Item, true);
|
||||
|
||||
Repairable repairablePumpComponent1 = mechanic_ballastPump_1.Item.GetComponent<Repairable>();
|
||||
Repairable repairablePumpComponent2 = mechanic_ballastPump_2.Item.GetComponent<Repairable>();
|
||||
Repairable repairableEngineComponent = mechanic_submarineEngine.Item.GetComponent<Repairable>();
|
||||
|
||||
// Remove highlights when each individual machine is repaired
|
||||
do { CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); yield return null; } while (repairablePumpComponent1.IsBelowRepairThreshold || repairablePumpComponent2.IsBelowRepairThreshold || repairableEngineComponent.IsBelowRepairThreshold);
|
||||
CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent);
|
||||
RemoveCompletedObjective(10);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective10");
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null);
|
||||
|
||||
// END TUTORIAL
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Completed");
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
private bool IsSelectedItem(Item item)
|
||||
{
|
||||
return mechanic?.SelectedItem == item;
|
||||
}
|
||||
|
||||
private bool WallHasDamagedSections(Structure wall)
|
||||
{
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
if (wall.Sections[i].damage > 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CheckHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
|
||||
{
|
||||
if (!comp1.IsBelowRepairThreshold && mechanic_ballastPump_1.Item.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(mechanic_ballastPump_1.Item, false);
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item);
|
||||
}
|
||||
if (!comp2.IsBelowRepairThreshold && mechanic_ballastPump_2.Item.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(mechanic_ballastPump_2.Item, false);
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item);
|
||||
}
|
||||
if (!comp3.IsBelowRepairThreshold && mechanic_submarineEngine.Item.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(mechanic_submarineEngine.Item, false);
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class OfficerTutorial : ScenarioTutorial
|
||||
{
|
||||
// Other tutorial items
|
||||
private LightComponent tutorial_mechanicFinalDoorLight;
|
||||
private Steering tutorial_submarineSteering;
|
||||
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
|
||||
// Room 2
|
||||
private MotionSensor officer_equipmentObjectiveSensor;
|
||||
private ItemContainer officer_equipmentCabinet;
|
||||
private Door officer_firstDoor;
|
||||
private LightComponent officer_firstDoorLight;
|
||||
|
||||
// Room 3
|
||||
private MotionSensor officer_crawlerSensor;
|
||||
private Character officer_crawler;
|
||||
private Vector2 officer_crawlerSpawnPos;
|
||||
private Door officer_secondDoor;
|
||||
private LightComponent officer_secondDoorLight;
|
||||
|
||||
// Room 4
|
||||
private MotionSensor officer_somethingBigSensor;
|
||||
private ItemContainer officer_coilgunLoader;
|
||||
private ItemContainer officer_ammoShelf_1;
|
||||
private ItemContainer officer_ammoShelf_2;
|
||||
private PowerContainer officer_superCapacitor;
|
||||
private Item officer_coilgunPeriscope;
|
||||
private Character officer_hammerhead;
|
||||
private Vector2 officer_hammerheadSpawnPos;
|
||||
private Door officer_thirdDoor;
|
||||
private LightComponent officer_thirdDoorLight;
|
||||
|
||||
// Room 5
|
||||
private MotionSensor officer_rangedWeaponSensor;
|
||||
private ItemContainer officer_rangedWeaponCabinet;
|
||||
private ItemContainer officer_rangedWeaponHolder;
|
||||
private Door officer_fourthDoor;
|
||||
private LightComponent officer_fourthDoorLight;
|
||||
|
||||
// Room 6
|
||||
private MotionSensor officer_mudraptorObjectiveSensor;
|
||||
private Vector2 officer_mudraptorSpawnPos;
|
||||
private Character officer_mudraptor;
|
||||
private Door tutorial_securityFinalDoor;
|
||||
private LightComponent tutorial_securityFinalDoorLight;
|
||||
|
||||
// Submarine
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
private MotionSensor tutorial_enteredSubmarineSensor;
|
||||
private Item officer_subAmmoBox_1;
|
||||
private Item officer_subAmmoBox_2;
|
||||
private ItemContainer officer_subAmmoShelf;
|
||||
private ItemContainer officer_subLoader_1;
|
||||
private ItemContainer officer_subLoader_2;
|
||||
private PowerContainer officer_subSuperCapacitor_1;
|
||||
private PowerContainer officer_subSuperCapacitor_2;
|
||||
|
||||
// Variables
|
||||
private LocalizedString radioSpeakerName;
|
||||
private Character officer;
|
||||
private float superCapacitorRechargeRate = 10;
|
||||
private Sprite officer_gunIcon;
|
||||
private Color officer_gunIconColor;
|
||||
|
||||
public OfficerTutorial() : base("tutorial.securityofficertraining".ToIdentifier(),
|
||||
new Segment(
|
||||
"Mechanic.Equipment".ToIdentifier(),
|
||||
"Mechanic.EquipmentObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Officer.MeleeWeapon".ToIdentifier(),
|
||||
"Officer.MeleeWeaponObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Officer.MeleeWeaponText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Officer.Crawler".ToIdentifier(),
|
||||
"Officer.CrawlerObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Officer.CrawlerText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Officer.SomethingBig".ToIdentifier(),
|
||||
"Officer.SomethingBigObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Officer.SomethingBigText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_loaders.webm", TextTag = "Officer.SomethingBigText".ToIdentifier(), Width = 700, Height = 80 }),
|
||||
new Segment(
|
||||
"Officer.Hammerhead".ToIdentifier(),
|
||||
"Officer.HammerheadObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Officer.HammerheadText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Officer.RangedWeapon".ToIdentifier(),
|
||||
"Officer.RangedWeaponObjective".ToIdentifier(),
|
||||
TutorialContentType.ManualVideo,
|
||||
textContent: new Segment.Text { Tag = "Officer.RangedWeaponText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
|
||||
videoContent: new Segment.Video { File = "tutorial_ranged.webm", TextTag = "Officer.RangedWeaponText".ToIdentifier(), Width = 450, Height = 80 }),
|
||||
new Segment(
|
||||
"Officer.Mudraptor".ToIdentifier(),
|
||||
"Officer.MudraptorObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Officer.MudraptorText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
|
||||
new Segment(
|
||||
"Officer.ArmSubmarine".ToIdentifier(),
|
||||
"Officer.ArmSubmarineObjective".ToIdentifier(),
|
||||
TutorialContentType.TextOnly,
|
||||
textContent: new Segment.Text { Tag = "Officer.ArmSubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }))
|
||||
{ }
|
||||
|
||||
protected override CharacterInfo GetCharacterInfo()
|
||||
{
|
||||
return new CharacterInfo(
|
||||
CharacterPrefab.HumanSpeciesName,
|
||||
jobOrJobPrefab: new Job(
|
||||
JobPrefab.Prefabs["securityofficer"], Rand.RandSync.Unsynced, 0,
|
||||
new Skill("medical".ToIdentifier(), 20),
|
||||
new Skill("weapons".ToIdentifier(), 70),
|
||||
new Skill("mechanical".ToIdentifier(), 20),
|
||||
new Skill("electrical".ToIdentifier(), 20),
|
||||
new Skill("helm".ToIdentifier(), 20)));
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||
officer = Character.Controlled;
|
||||
|
||||
foreach (Item item in officer.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
|
||||
item.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(item);
|
||||
}
|
||||
|
||||
var gunOrder = OrderPrefab.Prefabs["operateweapons"];
|
||||
officer_gunIcon = gunOrder.SymbolSprite;
|
||||
officer_gunIconColor = gunOrder.Color;
|
||||
|
||||
var bandage = FindOrGiveItem(officer, "antibleeding1".ToIdentifier());
|
||||
bandage.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(bandage);
|
||||
FindOrGiveItem(officer, "antibleeding1".ToIdentifier());
|
||||
|
||||
// Other tutorial items
|
||||
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
||||
|
||||
tutorial_submarineSteering.CanBeSelected = false;
|
||||
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
|
||||
{
|
||||
ic.CanBeSelected = false;
|
||||
}
|
||||
|
||||
SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false);
|
||||
|
||||
// Room 2
|
||||
officer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_equipmentobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("officer_equipmentcabinet")).GetComponent<ItemContainer>();
|
||||
officer_firstDoor = Item.ItemList.Find(i => i.HasTag("officer_firstdoor")).GetComponent<Door>();
|
||||
officer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("officer_firstdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, false);
|
||||
|
||||
// Room 3
|
||||
officer_crawlerSensor = Item.ItemList.Find(i => i.HasTag("officer_crawlerobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_crawlerSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_crawlerspawn")).WorldPosition;
|
||||
officer_secondDoor = Item.ItemList.Find(i => i.HasTag("officer_seconddoor")).GetComponent<Door>();
|
||||
officer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("officer_seconddoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(officer_secondDoor, officer_secondDoorLight, false);
|
||||
|
||||
// Room 4
|
||||
officer_somethingBigSensor = Item.ItemList.Find(i => i.HasTag("officer_somethingbigobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_coilgunLoader = Item.ItemList.Find(i => i.HasTag("officer_coilgunloader")).GetComponent<ItemContainer>();
|
||||
officer_superCapacitor = Item.ItemList.Find(i => i.HasTag("officer_supercapacitor")).GetComponent<PowerContainer>();
|
||||
officer_coilgunPeriscope = Item.ItemList.Find(i => i.HasTag("officer_coilgunperiscope"));
|
||||
officer_hammerheadSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_hammerheadspawn")).WorldPosition;
|
||||
officer_thirdDoor = Item.ItemList.Find(i => i.HasTag("officer_thirddoor")).GetComponent<Door>();
|
||||
officer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("officer_thirddoorlight")).GetComponent<LightComponent>();
|
||||
officer_ammoShelf_1 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_1")).GetComponent<ItemContainer>();
|
||||
officer_ammoShelf_2 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_2")).GetComponent<ItemContainer>();
|
||||
|
||||
SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, false);
|
||||
|
||||
// Room 5
|
||||
officer_rangedWeaponSensor = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_rangedWeaponCabinet = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponcabinet")).GetComponent<ItemContainer>();
|
||||
officer_rangedWeaponHolder = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponholder")).GetComponent<ItemContainer>();
|
||||
officer_fourthDoor = Item.ItemList.Find(i => i.HasTag("officer_fourthdoor")).GetComponent<Door>();
|
||||
officer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("officer_fourthdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, false);
|
||||
|
||||
// Room 6
|
||||
officer_mudraptorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_mudraptorobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_mudraptorSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_mudraptorspawn")).WorldPosition;
|
||||
tutorial_securityFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoor")).GetComponent<Door>();
|
||||
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, false);
|
||||
|
||||
// Submarine
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
|
||||
officer_subAmmoBox_1 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_1"));
|
||||
officer_subAmmoBox_2 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_2"));
|
||||
officer_subLoader_1 = Item.ItemList.Find(i => i.HasTag("officer_subloader_1")).GetComponent<ItemContainer>();
|
||||
officer_subLoader_2 = Item.ItemList.Find(i => i.HasTag("officer_subloader_2")).GetComponent<ItemContainer>();
|
||||
officer_subSuperCapacitor_1 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_1")).GetComponent<PowerContainer>();
|
||||
officer_subSuperCapacitor_2 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_2")).GetComponent<PowerContainer>();
|
||||
officer_subAmmoShelf = Item.ItemList.Find(i => i.HasTag("officer_subammoshelf")).GetComponent<ItemContainer>();
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Started");
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
|
||||
}
|
||||
|
||||
public override IEnumerable<CoroutineStatus> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
yield return new WaitForSeconds(0.01f);
|
||||
|
||||
// Room 1
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||
|
||||
// Room 2
|
||||
do { yield return null; } while (!officer_equipmentObjectiveSensor.MotionDetected);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Equipment"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
//TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Deselect]); // Retrieve equipment
|
||||
SetHighlight(officer_equipmentCabinet.Item, true);
|
||||
bool firstSlotRemoved = false;
|
||||
bool secondSlotRemoved = false;
|
||||
bool thirdSlotRemoved = false;
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(officer_equipmentCabinet.Item))
|
||||
{
|
||||
if (!firstSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
if (officer_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; }
|
||||
}
|
||||
|
||||
if (!secondSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
|
||||
if (officer_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; }
|
||||
}
|
||||
|
||||
if (!thirdSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
|
||||
if (officer_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; }
|
||||
}
|
||||
|
||||
for (int i = 0; i < officer.Inventory.visualSlots.Length; i++)
|
||||
{
|
||||
if (officer.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(officer.Inventory, i, highlightColor, .5f, .5f, 0f); }
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
} while (!officer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted
|
||||
//RemoveCompletedObjective(segments[0]);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective0");
|
||||
SetHighlight(officer_equipmentCabinet.Item, false);
|
||||
do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item));
|
||||
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Equip melee weapon & armor
|
||||
do
|
||||
{
|
||||
if (!officer.HasEquippedItem("stunbaton".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "stunbaton".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
if (!officer.HasEquippedItem("bodyarmor".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "bodyarmor".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
if (!officer.HasEquippedItem("ballistichelmet1".ToIdentifier()))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "ballistichelmet1".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
} while (!officer.HasEquippedItem("stunbaton".ToIdentifier()) || !officer.HasEquippedItem("bodyarmor".ToIdentifier()) || !officer.HasEquippedItem("ballistichelmet1".ToIdentifier()));
|
||||
RemoveCompletedObjective(1);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective1");
|
||||
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true);
|
||||
|
||||
// Room 3
|
||||
do { yield return null; } while (!officer_crawlerSensor.MotionDetected);
|
||||
TriggerTutorialSegment(2);
|
||||
officer_crawler = SpawnMonster("crawler", officer_crawlerSpawnPos);
|
||||
do { yield return null; } while (!officer_crawler.IsDead);
|
||||
RemoveCompletedObjective(2);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective2");
|
||||
Heal(officer);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.CrawlerDead"), ChatMessageType.Radio, null);
|
||||
SetDoorAccess(officer_secondDoor, officer_secondDoorLight, true);
|
||||
|
||||
// Room 4
|
||||
do { yield return null; } while (!officer_somethingBigSensor.MotionDetected);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.SomethingBig"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(3); // Arm coilgun
|
||||
do
|
||||
{
|
||||
SetHighlight(officer_coilgunLoader.Item, officer_coilgunLoader.Inventory.GetItemAt(0) == null || officer_coilgunLoader.Inventory.GetItemAt(0).Condition == 0);
|
||||
HighlightInventorySlot(officer_coilgunLoader.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
SetHighlight(officer_superCapacitor.Item, officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate);
|
||||
SetHighlight(officer_ammoShelf_1.Item, officer_coilgunLoader.Item.ExternalHighlight );
|
||||
SetHighlight(officer_ammoShelf_2.Item, officer_coilgunLoader.Item.ExternalHighlight );
|
||||
if (IsSelectedItem(officer_coilgunLoader.Item))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "coilgunammobox".ToIdentifier(), highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
yield return null;
|
||||
} while (officer_coilgunLoader.Inventory.GetItemAt(0) == null || officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate || officer_coilgunLoader.Inventory.GetItemAt(0).Condition == 0);
|
||||
SetHighlight(officer_coilgunLoader.Item, false);
|
||||
SetHighlight(officer_superCapacitor.Item, false);
|
||||
SetHighlight(officer_ammoShelf_1.Item, false);
|
||||
SetHighlight(officer_ammoShelf_2.Item, false);
|
||||
RemoveCompletedObjective(3);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective3");
|
||||
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Kill hammerhead
|
||||
officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos);
|
||||
officer_hammerhead.Params.AI.AvoidAbyss = false;
|
||||
officer_hammerhead.Params.AI.StayInAbyss = false;
|
||||
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
|
||||
SetHighlight(officer_coilgunPeriscope, true);
|
||||
float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos);
|
||||
do
|
||||
{
|
||||
float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition);
|
||||
if (distance > originalDistance * 1.5f || officer_hammerhead.WorldPosition.Y > officer_coilgunPeriscope.WorldPosition.Y)
|
||||
{
|
||||
// Don't let the Hammerhead go too far.
|
||||
officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000));
|
||||
}
|
||||
if (distance > originalDistance)
|
||||
{
|
||||
// Ensure that the Hammerhead targets the player
|
||||
officer.AiTarget.SoundRange = float.MaxValue;
|
||||
officer.AiTarget.SightRange = float.MaxValue;
|
||||
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
|
||||
if ((officer_hammerhead.AIController as EnemyAIController)?.SelectedTargetingParams != null)
|
||||
{
|
||||
((EnemyAIController)officer_hammerhead.AIController).SelectedTargetingParams.ReactDistance = 5000.0f;
|
||||
}
|
||||
/*var ai = officer_hammerhead.AIController as EnemyAIController;
|
||||
ai.sight = 2.0f;*/
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
while(!officer_hammerhead.IsDead);
|
||||
Heal(officer);
|
||||
SetHighlight(officer_coilgunPeriscope, false);
|
||||
RemoveCompletedObjective(4);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective4");
|
||||
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.HammerheadDead"), ChatMessageType.Radio, null);
|
||||
SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, true);
|
||||
|
||||
// Room 5
|
||||
//do { yield return null; } while (!officer_rangedWeaponSensor.MotionDetected);
|
||||
do { yield return null; } while (!officer_thirdDoor.IsOpen);
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
TriggerTutorialSegment(5, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Ranged weapons
|
||||
SetHighlight(officer_rangedWeaponHolder.Item, true);
|
||||
do { yield return null; } while (!officer_rangedWeaponHolder.Inventory.IsEmpty()); // Wait until looted
|
||||
SetHighlight(officer_rangedWeaponHolder.Item, false);
|
||||
do
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "shotgun".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
|
||||
yield return null;
|
||||
} while (!officer.HasEquippedItem("shotgun".ToIdentifier())); // Wait until equipped
|
||||
ItemContainer shotGunChamber = officer.Inventory.FindItemByIdentifier("shotgun".ToIdentifier()).GetComponent<ItemContainer>();
|
||||
SetHighlight(officer_rangedWeaponCabinet.Item, true);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(officer_rangedWeaponCabinet.Item))
|
||||
{
|
||||
if (officer_rangedWeaponCabinet.Inventory.visualSlots != null)
|
||||
{
|
||||
for (int i = 0; i < officer_rangedWeaponCabinet.Inventory.Capacity; i++)
|
||||
{
|
||||
if (officer_rangedWeaponCabinet.Inventory.GetItemAt(i)?.Prefab.Identifier == "shotgunshell")
|
||||
{
|
||||
HighlightInventorySlot(officer_rangedWeaponCabinet.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < officer.Inventory.Capacity; i++)
|
||||
{
|
||||
if (officer.Inventory.GetItemAt(i)?.Prefab.Identifier == "shotgunshell")
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (officer.Inventory.FindItemByIdentifier("shotgunshell".ToIdentifier()) != null || (IsSelectedItem(officer_rangedWeaponCabinet.Item) && officer_rangedWeaponCabinet.Inventory.FindItemByIdentifier("shotgunshell".ToIdentifier()) != null))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "shotgun".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
yield return null;
|
||||
} while (!shotGunChamber.Inventory.IsFull(takeStacksIntoAccount: true)); // Wait until all six harpoons loaded
|
||||
RemoveCompletedObjective(5);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective5");
|
||||
SetHighlight(officer_rangedWeaponCabinet.Item, false);
|
||||
SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, true);
|
||||
|
||||
// Room 6
|
||||
do { yield return null; } while (!officer_mudraptorObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(6);
|
||||
officer_mudraptor = SpawnMonster("mudraptor", officer_mudraptorSpawnPos);
|
||||
do { yield return null; } while (!officer_mudraptor.IsDead);
|
||||
Heal(officer);
|
||||
RemoveCompletedObjective(6);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective6");
|
||||
SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, true);
|
||||
|
||||
// Submarine
|
||||
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
|
||||
TriggerTutorialSegment(7);
|
||||
while (ContentRunning) yield return null;
|
||||
officer.AddActiveObjectiveEntity(officer_subAmmoBox_1, officer_gunIcon, officer_gunIconColor);
|
||||
officer.AddActiveObjectiveEntity(officer_subAmmoBox_2, officer_gunIcon, officer_gunIconColor);
|
||||
officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_1.Item, officer_gunIcon, officer_gunIconColor);
|
||||
officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_2.Item, officer_gunIcon, officer_gunIconColor);
|
||||
SetHighlight(officer_subSuperCapacitor_1.Item, true);
|
||||
SetHighlight(officer_subSuperCapacitor_2.Item, true);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Submarine"), ChatMessageType.Radio, null);
|
||||
do
|
||||
{
|
||||
SetHighlight(officer_subLoader_1.Item, officer_subLoader_1.Inventory.GetItemAt(0) == null || officer_subLoader_1.Inventory.GetItemAt(0).Condition == 0);
|
||||
SetHighlight(officer_subLoader_2.Item, officer_subLoader_2.Inventory.GetItemAt(0) == null || officer_subLoader_2.Inventory.GetItemAt(0).Condition == 0);
|
||||
HighlightInventorySlot(officer_subLoader_1.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
HighlightInventorySlot(officer_subLoader_2.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
|
||||
if (officer_subSuperCapacitor_1.Item.ExternalHighlight && officer_subSuperCapacitor_1.RechargeSpeed >= superCapacitorRechargeRate)
|
||||
{
|
||||
SetHighlight(officer_subSuperCapacitor_1.Item, false);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item);
|
||||
}
|
||||
|
||||
if (officer_subSuperCapacitor_2.Item.ExternalHighlight && officer_subSuperCapacitor_2.RechargeSpeed >= superCapacitorRechargeRate)
|
||||
{
|
||||
SetHighlight(officer_subSuperCapacitor_2.Item, false);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item);
|
||||
}
|
||||
|
||||
SetHighlight(officer_subAmmoBox_1, officer_subAmmoBox_1.ParentInventory != officer_subLoader_1.Inventory && officer_subAmmoBox_1.ParentInventory != officer_subLoader_2.Inventory);
|
||||
SetHighlight(officer_subAmmoBox_2, officer_subAmmoBox_2.ParentInventory != officer_subLoader_1.Inventory && officer_subAmmoBox_2.ParentInventory != officer_subLoader_2.Inventory);
|
||||
SetHighlight(officer_subAmmoShelf.Item, officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight);
|
||||
if (officer_subAmmoBox_1.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_1.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1);
|
||||
if (officer_subAmmoBox_2.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_2.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2);
|
||||
yield return null;
|
||||
} while (officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight || officer_subSuperCapacitor_1.Item.ExternalHighlight || officer_subSuperCapacitor_2.Item.ExternalHighlight);
|
||||
SetHighlight(officer_subLoader_1.Item, false);
|
||||
SetHighlight(officer_subLoader_2.Item, false);
|
||||
SetHighlight(officer_subSuperCapacitor_1.Item, false);
|
||||
SetHighlight(officer_subSuperCapacitor_2.Item, false);
|
||||
SetHighlight(officer_subAmmoBox_1, false);
|
||||
SetHighlight(officer_subAmmoBox_2, false);
|
||||
SetHighlight(officer_subAmmoShelf.Item, false);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2);
|
||||
RemoveCompletedObjective(7);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective7");
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Complete"), ChatMessageType.Radio, null);
|
||||
|
||||
yield return new WaitForSeconds(4f, false);
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Completed");
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
private bool IsSelectedItem(Item item)
|
||||
{
|
||||
return officer?.SelectedItem == item;
|
||||
}
|
||||
|
||||
private Character SpawnMonster(string speciesName, Vector2 pos)
|
||||
{
|
||||
var character = Character.Create(speciesName, pos, ToolBox.RandomSeed(8));
|
||||
var ai = character.AIController as EnemyAIController;
|
||||
ai.TargetOutposts = true;
|
||||
character.CharacterHealth.SetVitality(character.Health / 2);
|
||||
character.AnimController.Limbs.Where(l => l.attack != null).Select(l => l.attack).ForEach(a => a.AfterAttack = AIBehaviorAfterAttack.FallBack);
|
||||
return character;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
abstract class ScenarioTutorial : Tutorial
|
||||
{
|
||||
private CoroutineHandle tutorialCoroutine;
|
||||
|
||||
private Character character;
|
||||
|
||||
private const string submarinePath = "Content/Tutorials/Dugong_Tutorial.sub";
|
||||
private const string startOutpostPath = "Content/Tutorials/TutorialOutpost.sub";
|
||||
//private const string endOutpostPath = "";
|
||||
|
||||
private const string levelSeed = "nLoZLLtza";
|
||||
private const string levelParams = "ColdCavernsTutorial";
|
||||
|
||||
//private const string spawnSub = "startoutpost";
|
||||
private const SpawnType spawnPointType = SpawnType.Human;
|
||||
|
||||
private SubmarineInfo startOutpost = null;
|
||||
private SubmarineInfo endOutpost = null;
|
||||
private bool currentTutorialCompleted = false;
|
||||
private float fadeOutTime = 3f;
|
||||
protected float waitBeforeFade = 4f;
|
||||
|
||||
// Colors
|
||||
protected Color highlightColor = Color.OrangeRed;
|
||||
protected Color uiHighlightColor = new Color(150, 50, 0);
|
||||
protected Color buttonHighlightColor = new Color(255, 100, 0);
|
||||
protected Color inaccessibleColor = GUIStyle.Red;
|
||||
protected Color accessibleColor = GUIStyle.Green;
|
||||
|
||||
protected ScenarioTutorial(Identifier identifier, params Segment[] segments) : base(identifier, segments) { }
|
||||
|
||||
protected abstract void Initialize();
|
||||
|
||||
protected override IEnumerable<CoroutineStatus> Loading()
|
||||
{
|
||||
SubmarineInfo subInfo = new SubmarineInfo(submarinePath);
|
||||
|
||||
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier == levelParams);
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null);
|
||||
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
|
||||
|
||||
if (generationParams != null)
|
||||
{
|
||||
Biome biome =
|
||||
Biome.Prefabs.FirstOrDefault(b => generationParams.AllowedBiomeIdentifiers.Contains(b.Identifier)) ??
|
||||
Biome.Prefabs.First();
|
||||
|
||||
if (!string.IsNullOrEmpty(startOutpostPath))
|
||||
{
|
||||
startOutpost = new SubmarineInfo(startOutpostPath);
|
||||
}
|
||||
|
||||
/*if (!string.IsNullOrEmpty(endOutpostPath))
|
||||
{
|
||||
endOutpost = new SubmarineInfo(endOutpostPath);
|
||||
}*/
|
||||
|
||||
LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome);
|
||||
GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost, endOutpost: endOutpost);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.GameSession.StartRound(levelSeed);
|
||||
}
|
||||
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Clear();
|
||||
GameMain.GameSession.EventManager.Enabled = false;
|
||||
GameMain.GameScreen.Select();
|
||||
|
||||
|
||||
Submarine.MainSub.GodMode = true;
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != null && wall.Submarine.Info.IsOutpost)
|
||||
{
|
||||
wall.Indestructible = true;
|
||||
}
|
||||
}
|
||||
|
||||
CharacterInfo charInfo = GetCharacterInfo();
|
||||
|
||||
WayPoint wayPoint = GetSpawnPoint(charInfo);
|
||||
|
||||
if (wayPoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint with the spawntype \"" + spawnPointType + "\" is required for the tutorial event");
|
||||
yield return CoroutineStatus.Failure;
|
||||
yield break;
|
||||
}
|
||||
|
||||
character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false);
|
||||
character.TeamID = CharacterTeamType.Team1;
|
||||
Character.Controlled = character;
|
||||
character.GiveJobItems(null);
|
||||
|
||||
var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
|
||||
if (idCard == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Item prefab \"ID Card\" not found!");
|
||||
yield return CoroutineStatus.Failure;
|
||||
yield break;
|
||||
}
|
||||
idCard.AddTag("com");
|
||||
idCard.AddTag("eng");
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
Door door = item.GetComponent<Door>();
|
||||
if (door != null)
|
||||
{
|
||||
door.CanBeWelded = false;
|
||||
}
|
||||
}
|
||||
|
||||
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
|
||||
|
||||
Initialize();
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
protected abstract CharacterInfo GetCharacterInfo();
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
if (!currentTutorialCompleted)
|
||||
{
|
||||
base.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
|
||||
private WayPoint GetSpawnPoint(CharacterInfo charInfo)
|
||||
{
|
||||
/*Submarine spawnSub = null;
|
||||
|
||||
if (this.spawnSub != string.Empty)
|
||||
{
|
||||
switch (this.spawnSub)
|
||||
{
|
||||
case "startoutpost":
|
||||
spawnSub = Level.Loaded.StartOutpost;
|
||||
break;
|
||||
|
||||
case "endoutpost":
|
||||
spawnSub = Level.Loaded.EndOutpost;
|
||||
break;
|
||||
|
||||
default:
|
||||
spawnSub = Submarine.MainSub;
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
Submarine spawnSub = Level.Loaded.StartOutpost;
|
||||
return WayPoint.GetRandom(spawnPointType, charInfo.Job?.Prefab, spawnSub);
|
||||
}
|
||||
|
||||
protected bool HasOrder(Character character, string identifier, string option = null)
|
||||
{
|
||||
var currentOrderInfo = character.GetCurrentOrderWithTopPriority();
|
||||
if (currentOrderInfo?.Identifier == identifier)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return currentOrderInfo?.Option == option;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void SetHighlight(Item item, bool state)
|
||||
{
|
||||
if (item.ExternalHighlight == state) return;
|
||||
item.SpriteColor = (state) ? highlightColor : Color.White;
|
||||
item.ExternalHighlight = state;
|
||||
}
|
||||
|
||||
protected void SetHighlight(Structure structure, bool state)
|
||||
{
|
||||
structure.SpriteColor = (state) ? highlightColor : Color.White;
|
||||
structure.ExternalHighlight = state;
|
||||
}
|
||||
|
||||
protected void SetHighlight(Character character, bool state)
|
||||
{
|
||||
character.ExternalHighlight = state;
|
||||
}
|
||||
|
||||
protected void SetDoorAccess(Door door, LightComponent light, bool state)
|
||||
{
|
||||
if (state && door != null) door.requiredItems.Clear();
|
||||
if (light != null) light.LightColor = (state) ? accessibleColor : inaccessibleColor;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (character != null)
|
||||
{
|
||||
if (character.Oxygen < 1)
|
||||
{
|
||||
character.Oxygen = 1;
|
||||
}
|
||||
if (character.IsDead)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(Dead());
|
||||
}
|
||||
else if (Character.Controlled == null)
|
||||
{
|
||||
if (tutorialCoroutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(tutorialCoroutine);
|
||||
}
|
||||
GUI.PreventPauseMenuToggle = false;
|
||||
ContentRunning = false;
|
||||
infoBox = null;
|
||||
}
|
||||
else if (Character.Controlled.IsDead)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(Dead());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
if (tutorialCoroutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(tutorialCoroutine);
|
||||
}
|
||||
base.Stop();
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> Dead()
|
||||
{
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
Character.Controlled = character = null;
|
||||
Stop();
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Died");
|
||||
|
||||
yield return new WaitForSeconds(3.0f);
|
||||
|
||||
var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
|
||||
|
||||
messageBox.Buttons[0].OnClicked += Restart;
|
||||
messageBox.Buttons[0].OnClicked += messageBox.Close;
|
||||
|
||||
|
||||
messageBox.Buttons[1].OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu;
|
||||
messageBox.Buttons[1].OnClicked += messageBox.Close;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
protected IEnumerable<CoroutineStatus> TutorialCompleted()
|
||||
{
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
|
||||
Character.Controlled.ClearInputs();
|
||||
Character.Controlled = null;
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Completed");
|
||||
|
||||
yield return new WaitForSeconds(waitBeforeFade);
|
||||
|
||||
var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: fadeOutTime);
|
||||
currentTutorialCompleted = Completed = true;
|
||||
while (endCinematic.Running) yield return null;
|
||||
Stop();
|
||||
GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
|
||||
}
|
||||
|
||||
protected void Heal(Character character)
|
||||
{
|
||||
character.SetAllDamage(0.0f, 0.0f, 0.0f);
|
||||
character.Oxygen = 100.0f;
|
||||
character.Bloodloss = 0.0f;
|
||||
character.SetStun(0.0f, true);
|
||||
}
|
||||
|
||||
protected Item FindOrGiveItem(Character character, Identifier identifier)
|
||||
{
|
||||
var item = character.Inventory.FindItemByIdentifier(identifier);
|
||||
if (item != null && !item.Removed) { return item; }
|
||||
|
||||
ItemPrefab itemPrefab = MapEntityPrefab.Find(name: null, identifier: identifier) as ItemPrefab;
|
||||
item = new Item(itemPrefab, Vector2.Zero, submarine: null);
|
||||
character.Inventory.TryPutItem(item, character, item.AllowedSlots);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,32 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
enum TutorialContentType { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 };
|
||||
enum AutoPlayVideo { Yes, No };
|
||||
|
||||
/// <summary>
|
||||
/// If you're seeing this and are currently working on improving the tutorials, consider
|
||||
/// deleting this class and all that derive from it, and starting from scratch.
|
||||
/// </summary>
|
||||
abstract class Tutorial
|
||||
enum TutorialSegmentType { MessageBox, InfoBox };
|
||||
|
||||
class Tutorial
|
||||
{
|
||||
#region Constants
|
||||
public const string PlayableContentPath = "Content/Tutorials/TutorialVideos/";
|
||||
|
||||
private const string PlayableContentPath = "Content/Tutorials/TutorialVideos/";
|
||||
private const SpawnType SpawnPointType = SpawnType.Human;
|
||||
private const float FadeOutTime = 3f;
|
||||
private const float WaitBeforeFade = 4f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tutorial variables
|
||||
|
||||
public static ImmutableHashSet<Type> Types;
|
||||
|
||||
static Tutorial()
|
||||
{
|
||||
Types = ReflectionUtils.GetDerivedNonAbstract<Tutorial>()
|
||||
@@ -37,30 +39,41 @@ namespace Barotrauma.Tutorials
|
||||
|
||||
public bool ContentRunning { get; protected set; }
|
||||
|
||||
protected GUIComponent infoBox;
|
||||
private GUIComponent infoBox;
|
||||
private Action infoBoxClosedCallback;
|
||||
|
||||
protected VideoPlayer videoPlayer;
|
||||
protected Point screenResolution;
|
||||
protected WindowMode windowMode;
|
||||
protected float prevUIScale;
|
||||
private VideoPlayer videoPlayer;
|
||||
private Point screenResolution;
|
||||
private WindowMode windowMode;
|
||||
private float prevUIScale;
|
||||
|
||||
private GUIFrame holderFrame, objectiveFrame;
|
||||
private readonly List<Index> activeObjectives;
|
||||
private readonly LocalizedString objectiveTranslated;
|
||||
private GUILayoutGroup objectiveGroup;
|
||||
private readonly LocalizedString objectiveTextTranslated;
|
||||
|
||||
protected readonly ImmutableArray<Segment> segments;
|
||||
protected Index activeContentSegmentIndex;
|
||||
protected Segment activeContentSegment => segments[activeContentSegmentIndex];
|
||||
private readonly List<Segment> ActiveObjectives = new List<Segment>();
|
||||
private const float ObjectiveComponentRemovalTime = 1.5f;
|
||||
private Segment ActiveContentSegment { get; set; }
|
||||
|
||||
protected class Segment
|
||||
public class Segment
|
||||
{
|
||||
public struct Text
|
||||
{
|
||||
private const Anchor DefaultAnchor = Anchor.Center;
|
||||
|
||||
public Identifier Tag;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public Anchor Anchor;
|
||||
|
||||
public Text(Identifier tag, int? width = null, int? height = null, Anchor? anchor = null)
|
||||
{
|
||||
Tag = tag;
|
||||
Width = width ?? DefaultWidth;
|
||||
Height = height ?? DefaultHeight;
|
||||
Anchor = anchor ?? DefaultAnchor;
|
||||
}
|
||||
|
||||
public Text(string tag, int? width = null, int? height = null, Anchor? anchor = null) : this(tag.ToIdentifier(), width, height, anchor) { }
|
||||
}
|
||||
|
||||
public struct Video
|
||||
@@ -69,36 +82,63 @@ namespace Barotrauma.Tutorials
|
||||
public Identifier TextTag;
|
||||
public int Width;
|
||||
public int Height;
|
||||
|
||||
public Video(string file, Identifier textTag, int? width = null, int? height = null)
|
||||
{
|
||||
File = file;
|
||||
TextTag = textTag;
|
||||
Width = width ?? DefaultWidth;
|
||||
Height = height ?? DefaultHeight;
|
||||
}
|
||||
|
||||
public Video(string file, string textTag, int? width = null, int? height = null) : this(file, textTag.ToIdentifier(), width, height) { }
|
||||
}
|
||||
|
||||
public bool IsTriggered;
|
||||
public GUIButton ReplayButton;
|
||||
public GUITextBlock LinkedTitle, LinkedText;
|
||||
public object[] Args;
|
||||
public LocalizedString Objective;
|
||||
private const int DefaultWidth = 450;
|
||||
private const int DefaultHeight = 80;
|
||||
|
||||
public GUIImage ObjectiveStateIndicator;
|
||||
public GUIButton ObjectiveButton;
|
||||
public GUITextBlock LinkedTextBlock;
|
||||
public LocalizedString ObjectiveText;
|
||||
|
||||
public readonly Identifier Id;
|
||||
public readonly Text? TextContent;
|
||||
public readonly Video? VideoContent;
|
||||
public readonly TutorialContentType ContentType;
|
||||
public readonly AutoPlayVideo AutoPlayVideo;
|
||||
|
||||
public Segment(Identifier id, Identifier objectiveTextTag, TutorialContentType contentType, Text? textContent = null, Video? videoContent = null)
|
||||
public Action OnClickToDisplayMessage;
|
||||
|
||||
public readonly TutorialSegmentType SegmentType;
|
||||
|
||||
public Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text? textContent = null, Video? videoContent = null)
|
||||
{
|
||||
Id = id;
|
||||
Objective = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
||||
ContentType = contentType;
|
||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
||||
AutoPlayVideo = autoPlayVideo;
|
||||
TextContent = textContent;
|
||||
VideoContent = videoContent;
|
||||
SegmentType = TutorialSegmentType.InfoBox;
|
||||
}
|
||||
|
||||
IsTriggered = false;
|
||||
public Segment(Identifier id, Action onClickToDisplayMessage)
|
||||
{
|
||||
Id = id;
|
||||
var objetiveTextTag = $"{id}.objective".ToIdentifier();
|
||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objetiveTextTag));
|
||||
OnClickToDisplayMessage = onClickToDisplayMessage;
|
||||
SegmentType = TutorialSegmentType.MessageBox;
|
||||
}
|
||||
}
|
||||
|
||||
private bool completed;
|
||||
public bool Completed
|
||||
{
|
||||
get { return completed; }
|
||||
protected set
|
||||
get
|
||||
{
|
||||
return completed;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (completed == value) { return; }
|
||||
completed = value;
|
||||
@@ -109,26 +149,150 @@ namespace Barotrauma.Tutorials
|
||||
GameSettings.SaveCurrentConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public readonly TutorialPrefab TutorialPrefab;
|
||||
private readonly EventPrefab eventPrefab;
|
||||
|
||||
private CoroutineHandle tutorialCoroutine;
|
||||
|
||||
private Character character;
|
||||
|
||||
private readonly string submarinePath = "Content/Tutorials/Dugong_Tutorial.sub";
|
||||
private readonly string startOutpostPath = "Content/Tutorials/TutorialOutpost.sub";
|
||||
|
||||
private readonly string levelSeed = "nLoZLLtza";
|
||||
private readonly string levelParams = "ColdCavernsTutorial";
|
||||
|
||||
private SubmarineInfo startOutpost = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tutorial Controls
|
||||
protected Tutorial(Identifier identifier, params Segment[] segments)
|
||||
|
||||
public Tutorial(TutorialPrefab prefab)
|
||||
{
|
||||
Identifier = identifier;
|
||||
this.segments = segments.ToImmutableArray();
|
||||
Identifier = $"tutorial.{prefab?.Identifier ?? Identifier.Empty}".ToIdentifier();
|
||||
DisplayName = TextManager.Get(Identifier);
|
||||
activeObjectives = new List<Index>();
|
||||
objectiveTranslated = TextManager.Get("Tutorial.Objective");
|
||||
objectiveTextTranslated = TextManager.Get("Tutorial.Objective");
|
||||
|
||||
TutorialPrefab = prefab;
|
||||
submarinePath = prefab.SubmarinePath.Value;
|
||||
startOutpostPath = prefab.OutpostPath.Value;
|
||||
levelSeed = prefab.LevelSeed;
|
||||
levelParams = prefab.LevelParams;
|
||||
eventPrefab = EventSet.GetEventPrefab(prefab.EventIdentifier);
|
||||
}
|
||||
|
||||
protected abstract IEnumerable<CoroutineStatus> Loading();
|
||||
private IEnumerable<CoroutineStatus> Loading()
|
||||
{
|
||||
SubmarineInfo subInfo = new SubmarineInfo(submarinePath);
|
||||
|
||||
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier == levelParams);
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null);
|
||||
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
|
||||
|
||||
if (generationParams != null)
|
||||
{
|
||||
Biome biome =
|
||||
Biome.Prefabs.FirstOrDefault(b => generationParams.AllowedBiomeIdentifiers.Contains(b.Identifier)) ??
|
||||
Biome.Prefabs.First();
|
||||
|
||||
if (!string.IsNullOrEmpty(startOutpostPath))
|
||||
{
|
||||
startOutpost = new SubmarineInfo(startOutpostPath);
|
||||
}
|
||||
|
||||
LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome);
|
||||
GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.GameSession.StartRound(levelSeed);
|
||||
}
|
||||
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Clear();
|
||||
GameMain.GameSession.EventManager.Enabled = true;
|
||||
GameMain.GameScreen.Select();
|
||||
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
Submarine.MainSub.GodMode = true;
|
||||
}
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != null && wall.Submarine.Info.IsOutpost)
|
||||
{
|
||||
wall.Indestructible = true;
|
||||
}
|
||||
}
|
||||
|
||||
var charInfo = TutorialPrefab.GetTutorialCharacterInfo();
|
||||
|
||||
var wayPoint = WayPoint.GetRandom(SpawnPointType, charInfo.Job?.Prefab, Level.Loaded.StartOutpost);
|
||||
|
||||
if (wayPoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint with the spawntype \"" + SpawnPointType + "\" is required for the tutorial event");
|
||||
yield return CoroutineStatus.Failure;
|
||||
yield break;
|
||||
}
|
||||
|
||||
character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false);
|
||||
character.TeamID = CharacterTeamType.Team1;
|
||||
Character.Controlled = character;
|
||||
character.GiveJobItems(null);
|
||||
|
||||
var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
|
||||
if (idCard == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Item prefab \"ID Card\" not found!");
|
||||
yield return CoroutineStatus.Failure;
|
||||
yield break;
|
||||
}
|
||||
idCard.AddTag("com");
|
||||
idCard.AddTag("eng");
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
Door door = item.GetComponent<Door>();
|
||||
if (door != null)
|
||||
{
|
||||
door.CanBeWelded = false;
|
||||
}
|
||||
}
|
||||
|
||||
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
|
||||
|
||||
Initialize();
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = TutorialPrefab.AllowCharacterSwitch;
|
||||
|
||||
if (Character.Controlled is Character character)
|
||||
{
|
||||
foreach (Item item in character.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.HasTag(TutorialPrefab.StartingItemTags)) { continue; }
|
||||
item.Unequip(character);
|
||||
character.Inventory.RemoveItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
videoPlayer = new VideoPlayer();
|
||||
GameMain.Instance.ShowLoading(Loading());
|
||||
ActiveObjectives.Clear();
|
||||
ActiveContentSegment = null;
|
||||
|
||||
activeObjectives.Clear();
|
||||
CreateObjectiveFrame();
|
||||
|
||||
// Setup doors: Clear all requirements, unless the door is setup as locked.
|
||||
@@ -145,35 +309,73 @@ namespace Barotrauma.Tutorials
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AddToGUIUpdateList()
|
||||
public void AddToGUIUpdateList()
|
||||
{
|
||||
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale || GameSettings.CurrentConfig.Graphics.DisplayMode != windowMode)
|
||||
{
|
||||
CreateObjectiveFrame();
|
||||
}
|
||||
|
||||
if (objectiveFrame != null && activeObjectives.Count > 0)
|
||||
if (ActiveObjectives.Count > 0)
|
||||
{
|
||||
objectiveFrame.AddToGUIUpdateList(order: -1);
|
||||
objectiveGroup?.AddToGUIUpdateList(order: -1);
|
||||
}
|
||||
|
||||
if (infoBox != null) infoBox.AddToGUIUpdateList(order: 100);
|
||||
if (videoPlayer != null) videoPlayer.AddToGUIUpdateList(order: 100);
|
||||
infoBox?.AddToGUIUpdateList(order: 100);
|
||||
videoPlayer?.AddToGUIUpdateList(order: 100);
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
public void Update()
|
||||
{
|
||||
videoPlayer?.Update();
|
||||
|
||||
if (activeObjectives != null)
|
||||
if (character != null)
|
||||
{
|
||||
for (int i = 0; i < activeObjectives.Count; i++)
|
||||
if (character.Oxygen < 1)
|
||||
{
|
||||
CheckActiveObjectives(activeObjectives[i], deltaTime);
|
||||
character.Oxygen = 1;
|
||||
}
|
||||
if (character.IsDead)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(Dead());
|
||||
}
|
||||
else if (Character.Controlled == null)
|
||||
{
|
||||
if (tutorialCoroutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(tutorialCoroutine);
|
||||
}
|
||||
GUI.PreventPauseMenuToggle = false;
|
||||
ContentRunning = false;
|
||||
infoBox = null;
|
||||
}
|
||||
else if (Character.Controlled.IsDead)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(Dead());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> Dead()
|
||||
{
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
Character.Controlled = character = null;
|
||||
Stop();
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Died");
|
||||
|
||||
yield return new WaitForSeconds(3.0f);
|
||||
|
||||
var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
|
||||
|
||||
messageBox.Buttons[0].OnClicked += Restart;
|
||||
messageBox.Buttons[0].OnClicked += messageBox.Close;
|
||||
|
||||
|
||||
messageBox.Buttons[1].OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu;
|
||||
messageBox.Buttons[1].OnClicked += messageBox.Close;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
public void CloseActiveContentGUI()
|
||||
{
|
||||
if (videoPlayer.IsPlaying)
|
||||
@@ -182,257 +384,307 @@ namespace Barotrauma.Tutorials
|
||||
}
|
||||
else if (infoBox != null)
|
||||
{
|
||||
CloseInfoFrame(null, null);
|
||||
CloseInfoFrame();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<CoroutineStatus> UpdateState()
|
||||
public IEnumerable<CoroutineStatus> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen || Level.Loaded == null || Level.Loaded.Generating)
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
DebugConsole.ShowError($"No tutorial event defined for the tutorial (identifier: \"{TutorialPrefab?.Identifier.ToString() ?? "null"})\"");
|
||||
yield return CoroutineStatus.Failure;
|
||||
}
|
||||
|
||||
if (eventPrefab.CreateInstance() is Event eventInstance)
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventInstance);
|
||||
while (!eventInstance.IsFinished)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ShowError($"Failed to create an instance for a tutorial event (identifier: \"{eventPrefab.Identifier}\"");
|
||||
yield return CoroutineStatus.Failure;
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
protected bool Restart(GUIButton button, object obj)
|
||||
public void Complete()
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent($"Tutorial:{Identifier}:Completed");
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
|
||||
IEnumerable<CoroutineStatus> TutorialCompleted()
|
||||
{
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
Character.Controlled.ClearInputs();
|
||||
Character.Controlled = null;
|
||||
GameAnalyticsManager.AddDesignEvent("Tutorial:Completed");
|
||||
|
||||
yield return new WaitForSeconds(WaitBeforeFade);
|
||||
|
||||
var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: FadeOutTime);
|
||||
Completed = true;
|
||||
|
||||
while (endCinematic.Running) { yield return null; }
|
||||
|
||||
Stop();
|
||||
GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private bool Restart(GUIButton button, object obj)
|
||||
{
|
||||
GUI.PreventPauseMenuToggle = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void TriggerTutorialSegment(Index index, params object[] args)
|
||||
public void TriggerTutorialSegment(Segment segment)
|
||||
{
|
||||
if (segment.SegmentType == TutorialSegmentType.MessageBox)
|
||||
{
|
||||
ActiveObjectives.Add(segment);
|
||||
AddToObjectiveList(segment);
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory.DraggingItems.Clear();
|
||||
ContentRunning = true;
|
||||
activeContentSegmentIndex = index;
|
||||
segments[index].Args = args;
|
||||
ActiveContentSegment = segment;
|
||||
|
||||
LocalizedString tutorialText = TextManager.GetFormatted(segments[index].TextContent.Value.Tag, args);
|
||||
var title = TextManager.Get(segment.Id);
|
||||
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Value.Tag);
|
||||
tutorialText = TextManager.ParseInputTypes(tutorialText);
|
||||
LocalizedString objectiveText = string.Empty;
|
||||
|
||||
if (!segments[index].Objective.IsNullOrEmpty())
|
||||
switch (segment.AutoPlayVideo)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
objectiveText = segments[index].Objective;
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveText = TextManager.GetFormatted(segments[index].Objective, args);
|
||||
}
|
||||
objectiveText = TextManager.ParseInputTypes(objectiveText);
|
||||
segments[index].Objective = objectiveText;
|
||||
}
|
||||
else
|
||||
{
|
||||
segments[index].IsTriggered = true; // Complete at this stage only if no related objective
|
||||
}
|
||||
|
||||
|
||||
switch (segments[index].ContentType)
|
||||
{
|
||||
case TutorialContentType.None:
|
||||
case AutoPlayVideo.Yes:
|
||||
infoBox = CreateInfoFrame(
|
||||
title,
|
||||
tutorialText,
|
||||
segment.TextContent.Value.Width,
|
||||
segment.TextContent.Value.Height,
|
||||
segment.TextContent.Value.Anchor,
|
||||
hasButton: true,
|
||||
onInfoBoxClosed: LoadActiveContentVideo);
|
||||
break;
|
||||
case TutorialContentType.Video:
|
||||
infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText,
|
||||
activeContentSegment.TextContent.Value.Width,
|
||||
activeContentSegment.TextContent.Value.Height,
|
||||
activeContentSegment.TextContent.Value.Anchor, true, () => LoadVideo(activeContentSegment));
|
||||
break;
|
||||
case TutorialContentType.ManualVideo:
|
||||
infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText,
|
||||
activeContentSegment.TextContent.Value.Width,
|
||||
activeContentSegment.TextContent.Value.Height,
|
||||
activeContentSegment.TextContent.Value.Anchor, true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment));
|
||||
break;
|
||||
case TutorialContentType.TextOnly:
|
||||
infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText,
|
||||
activeContentSegment.TextContent.Value.Width,
|
||||
activeContentSegment.TextContent.Value.Height,
|
||||
activeContentSegment.TextContent.Value.Anchor, true, StopCurrentContentSegment);
|
||||
case AutoPlayVideo.No:
|
||||
infoBox = CreateInfoFrame(
|
||||
title,
|
||||
tutorialText,
|
||||
segment.TextContent.Value.Width,
|
||||
segment.TextContent.Value.Height,
|
||||
segment.TextContent.Value.Anchor,
|
||||
hasButton: true,
|
||||
onInfoBoxClosed: StopCurrentContentSegment,
|
||||
onVideoButtonClicked: LoadActiveContentVideo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Stop()
|
||||
public void CompleteTutorialSegment(Identifier segmentId)
|
||||
{
|
||||
if (!(GetActiveObjective(segmentId) is Segment segment))
|
||||
{
|
||||
DebugConsole.AddWarning($"Warning: tried to complete the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!");
|
||||
return;
|
||||
}
|
||||
if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style)
|
||||
{
|
||||
segment.ObjectiveStateIndicator.ApplyStyle(style);
|
||||
segment.ObjectiveStateIndicator.Flash(color: GUIStyle.Green);
|
||||
}
|
||||
segment.ObjectiveButton.OnClicked = null;
|
||||
segment.ObjectiveButton.CanBeFocused = false;
|
||||
}
|
||||
|
||||
public void RemoveTutorialSegment(Identifier segmentId)
|
||||
{
|
||||
if (!(GetActiveObjective(segmentId) is Segment segment))
|
||||
{
|
||||
DebugConsole.AddWarning($"Warning: tried to remove the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!");
|
||||
return;
|
||||
}
|
||||
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentRemovalTime, false);
|
||||
segment.LinkedTextBlock.FadeOut(ObjectiveComponentRemovalTime, false);
|
||||
var parent = segment.LinkedTextBlock.Parent;
|
||||
parent.FadeOut(ObjectiveComponentRemovalTime, true, onRemove: () =>
|
||||
{
|
||||
ActiveObjectives.Remove(segment);
|
||||
objectiveGroup?.Recalculate();
|
||||
});
|
||||
var targetPos = new Point(GameMain.GraphicsWidth - parent.Rect.X, 0);
|
||||
parent.RectTransform.MoveOverTime(targetPos, ObjectiveComponentRemovalTime);
|
||||
segment.ObjectiveButton.OnClicked = null;
|
||||
segment.ObjectiveButton.CanBeFocused = false;
|
||||
}
|
||||
|
||||
private Segment GetActiveObjective(Identifier id) => ActiveObjectives.FirstOrDefault(s => s.Id == id);
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (tutorialCoroutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(tutorialCoroutine);
|
||||
}
|
||||
ContentRunning = false;
|
||||
infoBox = null;
|
||||
videoPlayer.Remove();
|
||||
videoPlayer?.Remove();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Objectives
|
||||
|
||||
/// <summary>
|
||||
/// Create the objective list that holds the objectives (called on start and on resolution change)
|
||||
/// </summary>
|
||||
private void CreateObjectiveFrame()
|
||||
{
|
||||
holderFrame = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center));
|
||||
objectiveFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ObjectiveAnchor, holderFrame.RectTransform), style: null);
|
||||
|
||||
for (int i = 0; i < activeObjectives.Count; i++)
|
||||
var objectiveListFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), style: null);
|
||||
objectiveGroup = new GUILayoutGroup(new RectTransform(Vector2.One, objectiveListFrame.RectTransform))
|
||||
{
|
||||
CreateObjectiveGUI(activeObjectives[i], i, segments[activeObjectives[i]].ContentType);
|
||||
AbsoluteSpacing = (int)GUIStyle.Font.LineHeight
|
||||
};
|
||||
for (int i = 0; i < ActiveObjectives.Count; i++)
|
||||
{
|
||||
AddToObjectiveList(ActiveObjectives[i]);
|
||||
}
|
||||
|
||||
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
windowMode = GameSettings.CurrentConfig.Graphics.DisplayMode;
|
||||
prevUIScale = GUI.Scale;
|
||||
}
|
||||
|
||||
protected void StopCurrentContentSegment()
|
||||
/// <summary>
|
||||
/// Stops content running and adds the active segment to the objective list
|
||||
/// </summary>
|
||||
private void StopCurrentContentSegment()
|
||||
{
|
||||
if (!activeContentSegment.Objective.IsNullOrEmpty())
|
||||
if (!ActiveContentSegment.ObjectiveText.IsNullOrEmpty())
|
||||
{
|
||||
AddNewObjective(activeContentSegmentIndex, activeContentSegment.ContentType);
|
||||
ActiveObjectives.Add(ActiveContentSegment);
|
||||
AddToObjectiveList(ActiveContentSegment);
|
||||
}
|
||||
|
||||
ContentRunning = false;
|
||||
activeContentSegmentIndex = Index.End;
|
||||
ActiveContentSegment = null;
|
||||
}
|
||||
|
||||
protected virtual void CheckActiveObjectives(Index objective, float deltaTime)
|
||||
/// <summary>
|
||||
/// Adds the segment to the objective list
|
||||
/// </summary>
|
||||
private void AddToObjectiveList(Segment segment)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected bool HasObjective(Index segment)
|
||||
{
|
||||
return activeObjectives.Contains(segment);
|
||||
}
|
||||
|
||||
protected void AddNewObjective(Index segment, TutorialContentType type)
|
||||
{
|
||||
activeObjectives.Add(segment);
|
||||
CreateObjectiveGUI(segment, activeObjectives.Count - 1, type);
|
||||
}
|
||||
|
||||
private void CreateObjectiveGUI(Index segmentIndex, int index, TutorialContentType type)
|
||||
{
|
||||
var segment = segments[segmentIndex];
|
||||
LocalizedString objectiveText = TextManager.ParseInputTypes(segment.Objective);
|
||||
Point replayButtonSize = new Point((int)(GUIStyle.LargeFont.MeasureString(objectiveText).X), (int)(GUIStyle.LargeFont.MeasureString(objectiveText).Y * 1.45f));
|
||||
|
||||
segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null);
|
||||
segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) =>
|
||||
var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform)
|
||||
{
|
||||
if (type == TutorialContentType.Video)
|
||||
{
|
||||
ReplaySegmentVideo(segment);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSegmentText(segment);
|
||||
}
|
||||
return true;
|
||||
MinSize = new Point(0, objectiveGroup.AbsoluteSpacing)
|
||||
};
|
||||
var frame = new GUIFrame(frameRt, style: null)
|
||||
{
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
LocalizedString objectiveTitleText = TextManager.ParseInputTypes(objectiveTranslated);
|
||||
int yOffset = (int)((GUIStyle.SubHeadingFont.MeasureString(objectiveTitleText).Y + 5));
|
||||
segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point((int)GUIStyle.SubHeadingFont.MeasureString(objectiveTitleText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.BottomLeft) /*{ AbsoluteOffset = new Point((int)(-10 * GUI.Scale), 0) }*/,
|
||||
objectiveTitleText, textColor: Color.White, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
|
||||
segment.LinkedTextBlock = new GUITextBlock(
|
||||
new RectTransform(new Point(frameRt.Rect.Width - objectiveGroup.AbsoluteSpacing, 0), frame.RectTransform, anchor: Anchor.TopRight),
|
||||
TextManager.ParseInputTypes(segment.ObjectiveText),
|
||||
wrap: true);
|
||||
|
||||
var size = new Point(segment.LinkedTextBlock.Rect.Width, segment.LinkedTextBlock.Rect.Height);
|
||||
segment.LinkedTextBlock.RectTransform.NonScaledSize = size;
|
||||
segment.LinkedTextBlock.RectTransform.MinSize = size;
|
||||
segment.LinkedTextBlock.RectTransform.MaxSize = size;
|
||||
segment.LinkedTextBlock.RectTransform.IsFixedSize = true;
|
||||
frame.RectTransform.Resize(new Point(frame.Rect.Width, segment.LinkedTextBlock.RectTransform.Rect.Height), resizeChildren: false);
|
||||
frame.RectTransform.IsFixedSize = true;
|
||||
|
||||
var indicatorRt = new RectTransform(new Point(objectiveGroup.AbsoluteSpacing), frame.RectTransform, isFixedSize: true);
|
||||
segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete");;
|
||||
|
||||
SetTransparent(segment.LinkedTextBlock);
|
||||
|
||||
segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null)
|
||||
{
|
||||
ForceUpperCase = ForceUpperCase.Yes
|
||||
ToolTip = objectiveTextTranslated,
|
||||
OnClicked = (GUIButton btn, object userdata) =>
|
||||
{
|
||||
if (segment.SegmentType == TutorialSegmentType.InfoBox)
|
||||
{
|
||||
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
|
||||
{
|
||||
ReplaySegmentVideo(segment);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSegmentText(segment);
|
||||
}
|
||||
}
|
||||
else if (segment.SegmentType == TutorialSegmentType.MessageBox)
|
||||
{
|
||||
segment.OnClickToDisplayMessage?.Invoke();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
SetTransparent(segment.ObjectiveButton);
|
||||
|
||||
segment.LinkedText = new GUITextBlock(new RectTransform(new Point((int)GUIStyle.LargeFont.MeasureString(objectiveText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.TopLeft) /*{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }*/,
|
||||
objectiveText, textColor: new Color(4, 180, 108), font: GUIStyle.LargeFont, textAlignment: Alignment.CenterLeft);
|
||||
|
||||
segment.LinkedTitle.Color = segment.LinkedTitle.HoverColor = segment.LinkedTitle.PressedColor = segment.LinkedTitle.SelectedColor = Color.Transparent;
|
||||
segment.LinkedText.Color = segment.LinkedText.HoverColor = segment.LinkedText.PressedColor = segment.LinkedText.SelectedColor = Color.Transparent;
|
||||
segment.ReplayButton.Color = segment.ReplayButton.HoverColor = segment.ReplayButton.PressedColor = segment.ReplayButton.SelectedColor = Color.Transparent;
|
||||
static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent;
|
||||
}
|
||||
|
||||
private void ReplaySegmentVideo(Segment segment)
|
||||
{
|
||||
if (ContentRunning) return;
|
||||
if (ContentRunning) { return; }
|
||||
Inventory.DraggingItems.Clear();
|
||||
ContentRunning = true;
|
||||
LoadVideo(segment);
|
||||
//videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false);
|
||||
}
|
||||
|
||||
private void ShowSegmentText(Segment segment)
|
||||
{
|
||||
if (ContentRunning) return;
|
||||
if (ContentRunning) { return; }
|
||||
Inventory.DraggingItems.Clear();
|
||||
ContentRunning = true;
|
||||
|
||||
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Value.Tag, segment.Args);
|
||||
|
||||
Action videoAction = null;
|
||||
|
||||
if (segment.ContentType != TutorialContentType.TextOnly)
|
||||
{
|
||||
videoAction = () => LoadVideo(segment);
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame(TextManager.Get(segment.Id), tutorialText,
|
||||
segment.TextContent.Value.Width,
|
||||
segment.TextContent.Value.Height,
|
||||
segment.TextContent.Value.Anchor, true, () => ContentRunning = false, videoAction);
|
||||
}
|
||||
|
||||
protected void RemoveCompletedObjective(Index segmentIndex)
|
||||
{
|
||||
if (!HasObjective(segmentIndex)) return;
|
||||
var segment = segments[segmentIndex];
|
||||
segment.IsTriggered = true;
|
||||
segment.ReplayButton.OnClicked = null;
|
||||
|
||||
int checkMarkHeight = (int)(segment.ReplayButton.Rect.Height * 1.2f);
|
||||
int checkMarkWidth = (int)(checkMarkHeight * 0.93f);
|
||||
|
||||
Color color = new Color(4, 180, 108);
|
||||
|
||||
int objectiveTextWidth = segment.LinkedText.Rect.Width;
|
||||
int objectiveTitleWidth = segment.LinkedTitle.Rect.Width;
|
||||
|
||||
RectTransform rectTA;
|
||||
if (objectiveTextWidth > objectiveTitleWidth)
|
||||
{
|
||||
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight);
|
||||
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight);
|
||||
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale) - (objectiveTitleWidth - objectiveTextWidth), 0);
|
||||
}
|
||||
|
||||
GUIImage checkmark = new GUIImage(rectTA, "CheckMark");
|
||||
checkmark.Color = checkmark.SelectedColor = checkmark.HoverColor = checkmark.PressedColor = color;
|
||||
|
||||
RectTransform rectTB = new RectTransform(new Vector2(1.0f, .8f), segment.LinkedText.RectTransform, Anchor.Center, Pivot.Center);
|
||||
GUIImage stroke = new GUIImage(rectTB, "Stroke");
|
||||
stroke.Color = stroke.SelectedColor = stroke.HoverColor = stroke.PressedColor = color;
|
||||
|
||||
CoroutineManager.StartCoroutine(WaitForObjectiveEnd(segmentIndex));
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> WaitForObjectiveEnd(Index objectiveIndex)
|
||||
{
|
||||
var objective = segments[objectiveIndex];
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
objectiveFrame.RemoveChild(objective.ReplayButton);
|
||||
activeObjectives.Remove(objectiveIndex);
|
||||
|
||||
for (int i = 0; i < activeObjectives.Count; i++)
|
||||
{
|
||||
var activeObjective = segments[activeObjectives[i]];
|
||||
activeObjective.ReplayButton.RectTransform.AbsoluteOffset = new Point(0, (activeObjective.ReplayButton.Rect.Height + 20) * i);
|
||||
}
|
||||
ActiveContentSegment = segment;
|
||||
infoBox = CreateInfoFrame(
|
||||
TextManager.Get(segment.Id),
|
||||
TextManager.Get(segment.TextContent.Value.Tag),
|
||||
segment.TextContent.Value.Width,
|
||||
segment.TextContent.Value.Height,
|
||||
segment.TextContent.Value.Anchor,
|
||||
hasButton: true,
|
||||
onInfoBoxClosed: () => ContentRunning = false,
|
||||
onVideoButtonClicked: () => LoadVideo(segment));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region InfoFrame
|
||||
protected bool CloseInfoFrame(GUIButton button, object userData)
|
||||
|
||||
private void CloseInfoFrame() => CloseInfoFrame(null, null);
|
||||
|
||||
private bool CloseInfoFrame(GUIButton button, object userData)
|
||||
{
|
||||
infoBox = null;
|
||||
infoBoxClosedCallback?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action callback = null, Action showVideo = null)
|
||||
/// <summary>
|
||||
// Creates and displays a tutorial info box
|
||||
/// </summary>
|
||||
private GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action onInfoBoxClosed = null, Action onVideoButtonClicked = null)
|
||||
{
|
||||
if (hasButton) height += 60;
|
||||
if (hasButton)
|
||||
{
|
||||
height += 60;
|
||||
}
|
||||
|
||||
width = (int)(width * GUI.Scale);
|
||||
height = (int)(height * GUI.Scale);
|
||||
@@ -467,7 +719,7 @@ namespace Barotrauma.Tutorials
|
||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true);
|
||||
|
||||
textBlock.RectTransform.IsFixedSize = true;
|
||||
infoBoxClosedCallback = callback;
|
||||
infoBoxClosedCallback = onInfoBoxClosed;
|
||||
|
||||
if (hasButton)
|
||||
{
|
||||
@@ -477,7 +729,7 @@ namespace Barotrauma.Tutorials
|
||||
};
|
||||
buttonContainer.RectTransform.IsFixedSize = true;
|
||||
|
||||
if (showVideo != null)
|
||||
if (onVideoButtonClicked != null)
|
||||
{
|
||||
buttonContainer.Stretch = true;
|
||||
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
||||
@@ -485,7 +737,7 @@ namespace Barotrauma.Tutorials
|
||||
{
|
||||
OnClicked = (GUIButton button, object obj) =>
|
||||
{
|
||||
showVideo();
|
||||
onVideoButtonClicked();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -509,57 +761,39 @@ namespace Barotrauma.Tutorials
|
||||
|
||||
return background;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Video
|
||||
protected void LoadVideo(Segment segment)
|
||||
|
||||
private void LoadVideo(Segment segment)
|
||||
{
|
||||
if (videoPlayer == null) videoPlayer = new VideoPlayer();
|
||||
if (segment.ContentType != TutorialContentType.ManualVideo)
|
||||
videoPlayer ??= new VideoPlayer();
|
||||
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
|
||||
{
|
||||
videoPlayer.LoadContent(
|
||||
PlayableContentPath,
|
||||
new VideoPlayer.VideoSettings(segment.VideoContent.Value.File),
|
||||
new VideoPlayer.TextSettings(segment.VideoContent.Value.TextTag, segment.VideoContent.Value.Width),
|
||||
segment.Id, true, segment.Objective, StopCurrentContentSegment);
|
||||
contentPath: PlayableContentPath,
|
||||
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.Value.File),
|
||||
textSettings: new VideoPlayer.TextSettings(segment.VideoContent.Value.TextTag, segment.VideoContent.Value.Width),
|
||||
contentId: segment.Id,
|
||||
startPlayback: true,
|
||||
objective: segment.ObjectiveText,
|
||||
onStop: StopCurrentContentSegment);
|
||||
}
|
||||
else
|
||||
{
|
||||
videoPlayer.LoadContent(PlayableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent.Value.File), null, segment.Id, true, string.Empty, null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Highlights
|
||||
protected void HighlightInventorySlot(Inventory inventory, Identifier identifier, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount)
|
||||
{
|
||||
if (inventory.visualSlots == null) { return; }
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (inventory.GetItemAt(i)?.Prefab.Identifier == identifier)
|
||||
{
|
||||
HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount);
|
||||
}
|
||||
videoPlayer.LoadContent(
|
||||
contentPath: PlayableContentPath,
|
||||
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.Value.File),
|
||||
textSettings: null,
|
||||
contentId: segment.Id,
|
||||
startPlayback: true,
|
||||
objective: string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
protected void HighlightInventorySlotWithTag(Inventory inventory, Identifier tag, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount)
|
||||
{
|
||||
if (inventory.visualSlots == null) { return; }
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (inventory.GetItemAt(i)?.HasTag(tag) ?? false)
|
||||
{
|
||||
HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void LoadActiveContentVideo() => LoadVideo(ActiveContentSegment);
|
||||
|
||||
protected void HighlightInventorySlot(Inventory inventory, int index, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount)
|
||||
{
|
||||
if (inventory.visualSlots == null || index < 0 || inventory.visualSlots[index].HighlightTimer > 0) { return; }
|
||||
inventory.visualSlots[index].ShowBorderHighlight(color, fadeInDuration, fadeOutDuration, scaleUpAmount);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ namespace Barotrauma
|
||||
{
|
||||
public Tutorial Tutorial;
|
||||
|
||||
public TutorialMode(GameModePreset preset)
|
||||
: base(preset)
|
||||
{
|
||||
}
|
||||
public TutorialMode(GameModePreset preset) : base(preset) { }
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
@@ -31,7 +28,7 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
Tutorial.Update(deltaTime);
|
||||
Tutorial.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,14 +354,14 @@ namespace Barotrauma
|
||||
private static IWriteMessage StartSending()
|
||||
{
|
||||
IWriteMessage writeMessage = new WriteOnlyMessage();
|
||||
writeMessage.Write((byte)ClientPacketHeader.MEDICAL);
|
||||
writeMessage.WriteByte((byte)ClientPacketHeader.MEDICAL);
|
||||
return writeMessage;
|
||||
}
|
||||
|
||||
private static void ClientSend(INetSerializableStruct? netStruct, NetworkHeader header, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
IWriteMessage msg = StartSending();
|
||||
msg.Write((byte)header);
|
||||
msg.WriteByte((byte)header);
|
||||
netStruct?.Write(msg);
|
||||
GameMain.Client.ClientPeer?.Send(msg, deliveryMethod);
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Barotrauma
|
||||
|
||||
public static void ClientRead(IReadMessage inc)
|
||||
{
|
||||
ReadyCheckState state = (ReadyCheckState) inc.ReadByte();
|
||||
ReadyCheckState state = (ReadyCheckState)inc.ReadByte();
|
||||
CrewManager? crewManager = GameMain.GameSession?.CrewManager;
|
||||
var otherClients = GameMain.Client.ConnectedClients;
|
||||
if (crewManager == null || otherClients == null)
|
||||
@@ -196,7 +196,7 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case ReadyCheckState.Update:
|
||||
ReadyStatus newState = (ReadyStatus) inc.ReadByte();
|
||||
ReadyStatus newState = (ReadyStatus)inc.ReadByte();
|
||||
byte targetId = inc.ReadByte();
|
||||
if (crewManager.ActiveReadyCheck != null)
|
||||
{
|
||||
@@ -208,7 +208,7 @@ namespace Barotrauma
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
byte id = inc.ReadByte();
|
||||
ReadyStatus status = (ReadyStatus) inc.ReadByte();
|
||||
ReadyStatus status = (ReadyStatus)inc.ReadByte();
|
||||
crewManager.ActiveReadyCheck?.UpdateState(id, status);
|
||||
}
|
||||
|
||||
@@ -269,9 +269,9 @@ namespace Barotrauma
|
||||
private static void SendState(ReadyStatus status)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte) ClientPacketHeader.READY_CHECK);
|
||||
msg.Write((byte) ReadyCheckState.Update);
|
||||
msg.Write((byte) status);
|
||||
msg.WriteByte((byte)ClientPacketHeader.READY_CHECK);
|
||||
msg.WriteByte((byte)ReadyCheckState.Update);
|
||||
msg.WriteByte((byte)status);
|
||||
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
@@ -283,8 +283,8 @@ namespace Barotrauma
|
||||
ReadyCheckCooldown = DateTime.Now.AddMinutes(1);
|
||||
#endif
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte) ClientPacketHeader.READY_CHECK);
|
||||
msg.Write((byte) ReadyCheckState.Start);
|
||||
msg.WriteByte((byte)ClientPacketHeader.READY_CHECK);
|
||||
msg.WriteByte((byte)ReadyCheckState.Start);
|
||||
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,10 +166,10 @@ namespace Barotrauma.Items.Components
|
||||
List<VineTile> tiles = new List<VineTile>();
|
||||
for (int i = 0; i < vineCount; i++)
|
||||
{
|
||||
VineTileType vineType = (VineTileType) msg.ReadRangedInteger(0b0000, 0b1111);
|
||||
VineTileType vineType = (VineTileType)msg.ReadRangedInteger(0b0000, 0b1111);
|
||||
int flowerConfig = msg.ReadRangedInteger(0, 0xFFF);
|
||||
int leafConfig = msg.ReadRangedInteger(0, 0xFFF);
|
||||
sbyte posX = (sbyte) msg.ReadByte(), posY = (sbyte) msg.ReadByte();
|
||||
sbyte posX = (sbyte)msg.ReadByte(), posY = (sbyte)msg.ReadByte();
|
||||
Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size);
|
||||
|
||||
tiles.Add(new VineTile(this, pos, vineType, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig)));
|
||||
|
||||
@@ -69,8 +69,8 @@ namespace Barotrauma.Items.Components
|
||||
var eventData = ExtractEventData<EventData>(extraData);
|
||||
|
||||
Vector2 attachPos = eventData.AttachPos;
|
||||
msg.Write(attachPos.X);
|
||||
msg.Write(attachPos.Y);
|
||||
msg.WriteSingle(attachPos.X);
|
||||
msg.WriteSingle(attachPos.Y);
|
||||
}
|
||||
|
||||
public override void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float IsActiveTimer;
|
||||
|
||||
public virtual bool RecreateGUIOnResolutionChange => false;
|
||||
|
||||
public GUILayoutSettings DefaultLayout { get; protected set; }
|
||||
public GUILayoutSettings AlternativeLayout { get; protected set; }
|
||||
|
||||
@@ -574,7 +576,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged;
|
||||
}
|
||||
GameMain.Instance.ResolutionChanged += OnResolutionChanged;
|
||||
GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate;
|
||||
}
|
||||
|
||||
protected void TryCreateDragHandle()
|
||||
@@ -610,13 +612,17 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
}
|
||||
foreach (ItemComponent ic in activeHuds)
|
||||
{
|
||||
//refresh slots to ensure they're rendered at the correct position
|
||||
(ic as ItemContainer)?.Inventory.CreateSlots();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
|
||||
new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 10) },
|
||||
style: "GUIButtonRefresh")
|
||||
new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4) },
|
||||
style: "GUIButtonSettings")
|
||||
{
|
||||
OnClicked = (btn, userdata) =>
|
||||
{
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
/// <summary>
|
||||
/// Depth at which the contained sprites are drawn. If not set, the original depth of the item sprites is used.
|
||||
/// </summary>
|
||||
|
||||
@@ -90,8 +90,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ?
|
||||
SpriteEffects.FlipHorizontally : SpriteEffects.None;
|
||||
SetLightSourceTransformProjSpecific();
|
||||
}
|
||||
SetLightSourceTransformProjSpecific();
|
||||
}
|
||||
|
||||
partial void OnStateChanged()
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -33,14 +34,18 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes)]
|
||||
public float InfoAreaWidth { get; set; }
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool ShowOutput { get; set; }
|
||||
|
||||
partial void InitProjSpecific(XElement _)
|
||||
{
|
||||
CreateGUI();
|
||||
}
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
protected override void OnResolutionChanged()
|
||||
{
|
||||
base.OnResolutionChanged();
|
||||
OnItemLoadedProjSpecific();
|
||||
}
|
||||
|
||||
@@ -122,11 +127,14 @@ namespace Barotrauma.Items.Components
|
||||
// === OUTPUT SLOTS === //
|
||||
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null);
|
||||
|
||||
GUILayoutGroup outputDisplayLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedFrame.RectTransform), childAnchor: Anchor.TopCenter);
|
||||
GUILayoutGroup outDisplayTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.2f), outputDisplayLayout.RectTransform), isHorizontal: true);
|
||||
GUITextBlock outDisplayBlock = new GUITextBlock(new RectTransform(Vector2.One, outDisplayTopGroup.RectTransform), TextManager.Get("deconstructor.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
|
||||
GUILayoutGroup outDisplayBottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.975f, 0.8f), outputDisplayLayout.RectTransform), isHorizontal: true);
|
||||
outputDisplayListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), outDisplayBottomGroup.RectTransform), isHorizontal: true, style: null);
|
||||
if (ShowOutput)
|
||||
{
|
||||
GUILayoutGroup outputDisplayLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedFrame.RectTransform), childAnchor: Anchor.TopCenter);
|
||||
GUILayoutGroup outDisplayTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.2f), outputDisplayLayout.RectTransform), isHorizontal: true);
|
||||
GUITextBlock outDisplayBlock = new GUITextBlock(new RectTransform(Vector2.One, outDisplayTopGroup.RectTransform), TextManager.Get("deconstructor.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
|
||||
GUILayoutGroup outDisplayBottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.975f, 0.8f), outputDisplayLayout.RectTransform), isHorizontal: true);
|
||||
outputDisplayListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), outDisplayBottomGroup.RectTransform), isHorizontal: true, style: null);
|
||||
}
|
||||
|
||||
if (InfoAreaWidth >= 0.0f)
|
||||
{
|
||||
@@ -255,16 +263,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (DeconstructItem deconstructItem in it.Prefab.DeconstructItems)
|
||||
{
|
||||
if (!deconstructItem.IsValidDeconstructor(item)) { continue; }
|
||||
RegisterItem(deconstructItem.ItemIdentifier, deconstructItem.Amount);
|
||||
}
|
||||
|
||||
if (it.OwnInventory is { } inventory)
|
||||
/*if (it.OwnInventory is { } inventory)
|
||||
{
|
||||
foreach (Item inventoryItems in inventory.AllItems)
|
||||
{
|
||||
RegisterItem(inventoryItems.Prefab.Identifier);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
void RegisterItem(Identifier identifier, int amount = 1)
|
||||
{
|
||||
@@ -383,6 +392,8 @@ namespace Barotrauma.Items.Components
|
||||
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
|
||||
outputContainer.AllowUIOverlap = true;
|
||||
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
|
||||
|
||||
inputContainer.Inventory.Locked = IsActive;
|
||||
}
|
||||
|
||||
private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
|
||||
@@ -446,7 +457,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(pendingState);
|
||||
msg.WriteBoolean(pendingState);
|
||||
}
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
|
||||
@@ -51,11 +51,13 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)]
|
||||
public string FabricationLimitReachedText { get; set; }
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
protected override void OnResolutionChanged()
|
||||
{
|
||||
if (GuiFrame != null)
|
||||
{
|
||||
OnItemLoadedProjSpecific();
|
||||
InitInventoryUIs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +234,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private void InitInventoryUIs()
|
||||
{
|
||||
if (inputInventoryHolder != null)
|
||||
{
|
||||
inputContainer.AllowUIOverlap = true;
|
||||
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
|
||||
}
|
||||
outputContainer.AllowUIOverlap = true;
|
||||
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
|
||||
}
|
||||
|
||||
private LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
|
||||
{
|
||||
if (fabricationRecipe == null) { return ""; }
|
||||
@@ -249,13 +262,7 @@ namespace Barotrauma.Items.Components
|
||||
partial void OnItemLoadedProjSpecific()
|
||||
{
|
||||
CreateGUI();
|
||||
if (inputInventoryHolder != null)
|
||||
{
|
||||
inputContainer.AllowUIOverlap = true;
|
||||
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
|
||||
}
|
||||
outputContainer.AllowUIOverlap = true;
|
||||
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
|
||||
InitInventoryUIs();
|
||||
}
|
||||
|
||||
partial void SelectProjSpecific(Character character)
|
||||
@@ -328,76 +335,112 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<FabricationRecipe.RequiredItem, int> missingIngredientCounts = new Dictionary<FabricationRecipe.RequiredItem, int>();
|
||||
private float ingredientHighlightTimer;
|
||||
|
||||
private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
|
||||
{
|
||||
overlayComponent.RectTransform.SetAsLastChild();
|
||||
|
||||
missingIngredientCounts.Clear();
|
||||
|
||||
FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
|
||||
if (targetItem != null)
|
||||
{
|
||||
int slotIndex = 0;
|
||||
|
||||
var missingItems = new List<FabricationRecipe.RequiredItem>();
|
||||
|
||||
foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems)
|
||||
{
|
||||
for (int i = 0; i < requiredItem.Amount; i++)
|
||||
if (missingIngredientCounts.ContainsKey(requiredItem))
|
||||
{
|
||||
missingItems.Add(requiredItem);
|
||||
missingIngredientCounts[requiredItem] += requiredItem.Amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
missingIngredientCounts[requiredItem] = requiredItem.Amount;
|
||||
}
|
||||
}
|
||||
foreach (Item item in inputContainer.Inventory.AllItems)
|
||||
{
|
||||
missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefabs.Contains(item.Prefab)));
|
||||
}
|
||||
var missingCounts = missingItems.GroupBy(missingItem => missingItem).ToDictionary(x => x.Key, x => x.Count());
|
||||
missingItems = missingItems.Distinct().ToList();
|
||||
var missingIngredient = missingIngredientCounts.Keys.FirstOrDefault(mi => mi.MatchesItem(item));
|
||||
if (missingIngredient == null) { continue; }
|
||||
|
||||
foreach (FabricationRecipe.RequiredItem requiredItem in missingItems)
|
||||
if (missingIngredientCounts[missingIngredient] == 1)
|
||||
{
|
||||
missingIngredientCounts.Remove(missingIngredient);
|
||||
}
|
||||
else
|
||||
{
|
||||
missingIngredientCounts[missingIngredient]--;
|
||||
}
|
||||
}
|
||||
|
||||
if (ingredientHighlightTimer <= 0.0f)
|
||||
{
|
||||
//highlight inventory slots that contain suitable ingredients in linked inventories
|
||||
foreach (var inventory in linkedInventories)
|
||||
{
|
||||
if (inventory.visualSlots == null) { continue; }
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (inventory.visualSlots[i].HighlightTimer > 0.0f) { continue; }
|
||||
var availableItem = inventory.GetItemAt(i);
|
||||
if (availableItem == null) { continue; }
|
||||
|
||||
if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableItem)))
|
||||
{
|
||||
inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
|
||||
continue;
|
||||
}
|
||||
if (availableItem.OwnInventory != null)
|
||||
{
|
||||
for (int j = 0; j < availableItem.OwnInventory.Capacity; j++)
|
||||
{
|
||||
var availableContainedItem = availableItem.OwnInventory.GetItemAt(i);
|
||||
if (availableContainedItem == null) { continue; }
|
||||
if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableContainedItem)))
|
||||
{
|
||||
inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ingredientHighlightTimer = 1.0f;
|
||||
}
|
||||
|
||||
int slotIndex = 0;
|
||||
foreach (var kvp in missingIngredientCounts)
|
||||
{
|
||||
var requiredItem = kvp.Key;
|
||||
int missingCount = kvp.Value;
|
||||
|
||||
while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.GetItemAt(slotIndex) != null)
|
||||
{
|
||||
slotIndex++;
|
||||
}
|
||||
|
||||
requiredItem.ItemPrefabs
|
||||
.Where(requiredPrefab => availableIngredients.ContainsKey(requiredPrefab.Identifier))
|
||||
.ForEach(requiredPrefab => {
|
||||
var availableItems = availableIngredients[requiredPrefab.Identifier];
|
||||
foreach (Item it in availableItems)
|
||||
{
|
||||
if (it.ParentInventory == inputContainer.Inventory) { continue; }
|
||||
var rootInventoryOwner = it.GetRootInventoryOwner();
|
||||
Inventory rootInventory = (rootInventoryOwner as Item)?.OwnInventory as Inventory ?? (rootInventoryOwner as Character)?.Inventory;
|
||||
if (rootInventory?.visualSlots == null) { continue; }
|
||||
int availableSlotIndex = rootInventory.FindIndex((it.Container != rootInventoryOwner ? it.Container : it) ?? it);
|
||||
if (availableSlotIndex < 0) { continue; }
|
||||
if (rootInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f)
|
||||
{
|
||||
rootInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
|
||||
if (slotIndex < inputContainer.Capacity)
|
||||
{
|
||||
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (slotIndex >= inputContainer.Capacity) { break; }
|
||||
|
||||
var itemIcon = requiredItem.ItemPrefabs.First().InventoryIcon ?? requiredItem.ItemPrefabs.First().Sprite;
|
||||
if (slotIndex < inputContainer.Capacity &&
|
||||
inputContainer.Inventory.visualSlots[slotIndex].HighlightTimer <= 0.0f &&
|
||||
availableIngredients.Any(i => i.Value.Any() && requiredItem.MatchesItem(i.Value.First())))
|
||||
{
|
||||
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
|
||||
}
|
||||
|
||||
var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
|
||||
var itemIcon = requiredItemPrefab.InventoryIcon ?? requiredItemPrefab.Sprite;
|
||||
Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect;
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: requiredItem.ItemPrefabs.First().InventoryIconColor * 0.3f,
|
||||
color: requiredItemPrefab.InventoryIconColor * 0.3f,
|
||||
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y));
|
||||
|
||||
|
||||
if (missingCounts[requiredItem] > 1)
|
||||
if (missingCount > 1)
|
||||
{
|
||||
Vector2 stackCountPos = new Vector2(slotRect.Right, slotRect.Bottom);
|
||||
string stackCountText = "x" + missingCounts[requiredItem];
|
||||
string stackCountText = "x" + missingCount;
|
||||
stackCountPos -= GUIStyle.SmallFont.MeasureString(stackCountText) + new Vector2(4, 2);
|
||||
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black);
|
||||
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White);
|
||||
@@ -446,9 +489,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
|
||||
}
|
||||
if (!requiredItem.ItemPrefabs.First().Description.IsNullOrEmpty())
|
||||
if (!requiredItemPrefab.Description.IsNullOrEmpty())
|
||||
{
|
||||
toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description;
|
||||
toolTipText += '\n' + requiredItemPrefab.Description;
|
||||
}
|
||||
tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText };
|
||||
}
|
||||
@@ -715,6 +758,8 @@ namespace Barotrauma.Items.Components
|
||||
activateButton.Enabled = false;
|
||||
inSufficientPowerWarning.Visible = IsActive && !hasPower;
|
||||
|
||||
ingredientHighlightTimer -= deltaTime;
|
||||
|
||||
if (!IsActive)
|
||||
{
|
||||
//only check ingredients if the fabricator isn't active (if it is, this is done in Update)
|
||||
@@ -763,7 +808,7 @@ namespace Barotrauma.Items.Components
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0;
|
||||
msg.Write(recipeHash);
|
||||
msg.WriteUInt32(recipeHash);
|
||||
}
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
|
||||
@@ -510,7 +510,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 origin = weaponSprite.Origin;
|
||||
float scale = parentWidth / Math.Max(weaponSprite.size.X, weaponSprite.size.Y);
|
||||
Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? Color.DimGray : GUIStyle.Green;
|
||||
weaponSprite.Draw(batch, center, color, origin, rotation, scale, it.SpriteEffects);
|
||||
weaponSprite.Draw(batch, center, color, origin, rotation, scale, SpriteEffects.None);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
|
||||
msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10);
|
||||
msg.Write(IsActive);
|
||||
msg.WriteBoolean(IsActive);
|
||||
}
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
|
||||
@@ -34,9 +34,8 @@ namespace Barotrauma.Items.Components
|
||||
private Color optimalRangeColor = new Color(74,238,104,255);
|
||||
private Color offRangeColor = Color.Orange;
|
||||
private Color warningColor = Color.Red;
|
||||
private Color coldColor = Color.LightBlue;
|
||||
private Color warmColor = Color.Orange;
|
||||
private Color hotColor = Color.Red;
|
||||
|
||||
private readonly Color[] temperatureColors = new Color[] { Color.Blue, Color.LightBlue, Color.Orange, Color.Red };
|
||||
private Color outputColor = Color.Goldenrod;
|
||||
private Color loadColor = Color.LightSteelBlue;
|
||||
|
||||
@@ -66,10 +65,11 @@ namespace Barotrauma.Items.Components
|
||||
"ReactorWarningOverheating", "ReactorWarningHighOutput", "ReactorWarningFuelOut", "ReactorWarningSCRAM"
|
||||
};
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element)
|
||||
{
|
||||
// TODO: need to recreate the gui when the resolution changes
|
||||
|
||||
CreateGUI();
|
||||
fissionRateMeter = new Sprite(element.GetChildElement("fissionratemeter")?.GetChildElement("sprite"));
|
||||
turbineOutputMeter = new Sprite(element.GetChildElement("turbineoutputmeter")?.GetChildElement("sprite"));
|
||||
meterPointer = new Sprite(element.GetChildElement("meterpointer")?.GetChildElement("sprite"));
|
||||
@@ -81,7 +81,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
string textureDir = GetTextureDirectory(subElement);
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "temperatureboostsoundup":
|
||||
@@ -92,11 +91,16 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CreateGUI()
|
||||
{
|
||||
warningButtons.Clear();
|
||||
|
||||
paddedFrame = new GUILayoutGroup(new RectTransform(
|
||||
GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
||||
{ AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
isHorizontal: true)
|
||||
GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
||||
{ AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
isHorizontal: true)
|
||||
{
|
||||
RelativeSpacing = 0.012f,
|
||||
Stretch = true
|
||||
@@ -438,6 +442,19 @@ namespace Barotrauma.Items.Components
|
||||
};
|
||||
LocalizedString outputStr = TextManager.Get("ReactorOutput");
|
||||
outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}";
|
||||
|
||||
InitInventoryUI();
|
||||
}
|
||||
|
||||
private void InitInventoryUI()
|
||||
{
|
||||
var itemContainer = item.GetComponent<ItemContainer>();
|
||||
if (itemContainer != null)
|
||||
{
|
||||
itemContainer.UILabel = "";
|
||||
itemContainer.AllowUIOverlap = true;
|
||||
itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
@@ -445,23 +462,10 @@ namespace Barotrauma.Items.Components
|
||||
base.OnItemLoaded();
|
||||
TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f;
|
||||
FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f;
|
||||
var itemContainer = item.GetComponent<ItemContainer>();
|
||||
if (itemContainer != null)
|
||||
{
|
||||
itemContainer.UILabel = "";
|
||||
itemContainer.AllowUIOverlap = true;
|
||||
itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform;
|
||||
/*var inventoryLabel = inventoryContainer.Parent?.GetChild<GUITextBlock>();
|
||||
if (inventoryLabel != null)
|
||||
{
|
||||
inventoryLabel.RectTransform.MinSize = new Point(100, 0);
|
||||
inventoryLabel.Text = itemContainer.GetUILabel();
|
||||
inventoryLabel.CalculateHeightFromText();
|
||||
(inventoryLabel.Parent as GUILayoutGroup).Recalculate();
|
||||
}*/
|
||||
}
|
||||
InitInventoryUI();
|
||||
}
|
||||
|
||||
|
||||
private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||
{
|
||||
Vector2 meterPos = new Vector2(container.Rect.X, container.Rect.Y);
|
||||
@@ -474,7 +478,7 @@ namespace Barotrauma.Items.Components
|
||||
while (meterBarPos.Y > container.Rect.Bottom + (int)(5 * GUI.yScale) - container.Rect.Height * tempFill)
|
||||
{
|
||||
float tempRatio = 1.0f - ((meterBarPos.Y - container.Rect.Y) / container.Rect.Height);
|
||||
Color color = ToolBox.GradientLerp(tempRatio, coldColor, optimalRangeColor, warmColor, hotColor);
|
||||
Color color = ToolBox.GradientLerp(tempRatio, temperatureColors);
|
||||
tempMeterBar.Draw(spriteBatch, meterBarPos, color: color, scale: meterBarScale);
|
||||
int spacing = 2;
|
||||
meterBarPos.Y -= tempMeterBar.size.Y * meterBarScale + spacing;
|
||||
@@ -691,7 +695,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float normalizedValue = (value - range.X) / (range.Y - range.X);
|
||||
float valueRad = MathHelper.Lerp(sectorRad.X, sectorRad.Y, normalizedValue);
|
||||
Vector2 offset = new Vector2(0, 40) * scale;
|
||||
meterPointer.Draw(spriteBatch, pointerPos, valueRad, scale);
|
||||
}
|
||||
|
||||
@@ -769,8 +772,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(autoTemp);
|
||||
msg.Write(PowerOn);
|
||||
msg.WriteBoolean(autoTemp);
|
||||
msg.WriteBoolean(PowerOn);
|
||||
msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8);
|
||||
|
||||
|
||||
@@ -140,8 +140,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
private bool AllowUsingMineralScanner =>
|
||||
HasMineralScanner && !isConnectedToSteering;
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element)
|
||||
{
|
||||
@@ -195,7 +194,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override void OnResolutionChanged()
|
||||
{
|
||||
base.OnResolutionChanged();
|
||||
UpdateGUIElements();
|
||||
}
|
||||
|
||||
@@ -302,7 +300,7 @@ namespace Barotrauma.Items.Components
|
||||
TextManager.Get("SonarDirectionalPing"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft);
|
||||
textBlocksToScaleAndNormalize.Add(directionalModeSwitchText);
|
||||
|
||||
if (AllowUsingMineralScanner)
|
||||
if (HasMineralScanner)
|
||||
{
|
||||
AddMineralScannerSwitchToGUI();
|
||||
}
|
||||
@@ -373,7 +371,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
zoomSlider.BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom);
|
||||
if (AllowUsingMineralScanner && mineralScannerSwitch == null)
|
||||
if (HasMineralScanner && mineralScannerSwitch == null)
|
||||
{
|
||||
AddMineralScannerSwitchToGUI();
|
||||
GUITextBlock.AutoScaleAndNormalize(textBlocksToScaleAndNormalize);
|
||||
@@ -417,12 +415,32 @@ namespace Barotrauma.Items.Components
|
||||
unsentChanges = true;
|
||||
correctionTimer = CorrectionDelay;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
var mineralScannerSwitchText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1), mineralScannerFrame.RectTransform, Anchor.CenterRight),
|
||||
TextManager.Get("SonarMineralScanner"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft);
|
||||
textBlocksToScaleAndNormalize.Add(mineralScannerSwitchText);
|
||||
|
||||
PreventMineralScannerOverlap();
|
||||
}
|
||||
|
||||
private void PreventMineralScannerOverlap()
|
||||
{
|
||||
if (item.GetComponent<Steering>() is { } steering && controlContainer is { } container)
|
||||
{
|
||||
int containerBottom = container.Rect.Y + container.Rect.Height,
|
||||
steeringTop = steering.ControlContainer.Rect.Top;
|
||||
|
||||
int amountRaised = 0;
|
||||
|
||||
while (GetContainerBottom() > steeringTop) { amountRaised++; }
|
||||
|
||||
container.RectTransform.AbsoluteOffset = new Point(0, -amountRaised);
|
||||
|
||||
int GetContainerBottom() => containerBottom - amountRaised;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
@@ -502,7 +520,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2.DistanceSquared(sonarView.Rect.Center.ToVector2(), PlayerInput.MousePosition) <
|
||||
(sonarView.Rect.Width / 2 * sonarView.Rect.Width / 2);
|
||||
|
||||
if (AllowUsingMineralScanner && Level.Loaded != null && !Level.Loaded.Generating)
|
||||
if (HasMineralScanner && Level.Loaded != null && !Level.Loaded.Generating)
|
||||
{
|
||||
if (MineralClusters == null)
|
||||
{
|
||||
@@ -985,7 +1003,7 @@ namespace Barotrauma.Items.Components
|
||||
missionIndex++;
|
||||
}
|
||||
|
||||
if (AllowUsingMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null &&
|
||||
if (HasMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null &&
|
||||
(item.CurrentHull == null || !DetectSubmarineWalls))
|
||||
{
|
||||
foreach (var c in MineralClusters)
|
||||
@@ -1754,17 +1772,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(currentMode == Mode.Active);
|
||||
msg.WriteBoolean(currentMode == Mode.Active);
|
||||
if (currentMode == Mode.Active)
|
||||
{
|
||||
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
|
||||
msg.Write(useDirectionalPing);
|
||||
msg.WriteBoolean(useDirectionalPing);
|
||||
if (useDirectionalPing)
|
||||
{
|
||||
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
|
||||
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
|
||||
}
|
||||
msg.Write(useMineralScanner);
|
||||
msg.WriteBoolean(useMineralScanner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private GUITickBox maintainPosTickBox, levelEndTickBox, levelStartTickBox;
|
||||
|
||||
private GUIComponent statusContainer, dockingContainer, controlContainer;
|
||||
private GUIComponent statusContainer, dockingContainer;
|
||||
|
||||
public GUIComponent ControlContainer { get; private set; }
|
||||
|
||||
private bool dockingNetworkMessagePending;
|
||||
|
||||
@@ -90,6 +92,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool disableControls;
|
||||
/// <summary>
|
||||
/// Can be used by status effects to disable all the UI controls
|
||||
/// </summary>
|
||||
public bool DisableControls
|
||||
{
|
||||
get { return disableControls; }
|
||||
set
|
||||
{
|
||||
if (disableControls == value) { return; }
|
||||
disableControls = value;
|
||||
UpdateGUIElements();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -112,8 +132,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override void CreateGUI()
|
||||
{
|
||||
controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterRight), "ItemUI");
|
||||
var paddedControlContainer = new GUIFrame(new RectTransform(controlContainer.Rect.Size - GUIStyle.ItemFrameMargin, controlContainer.RectTransform, Anchor.Center)
|
||||
ControlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterRight), "ItemUI");
|
||||
var paddedControlContainer = new GUIFrame(new RectTransform(ControlContainer.Rect.Size - GUIStyle.ItemFrameMargin, ControlContainer.RectTransform, Anchor.Center)
|
||||
{
|
||||
AbsoluteOffset = GUIStyle.ItemFrameOffset
|
||||
}, style: null);
|
||||
@@ -472,7 +492,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override void OnResolutionChanged()
|
||||
{
|
||||
base.OnResolutionChanged();
|
||||
UpdateGUIElements();
|
||||
}
|
||||
|
||||
@@ -658,6 +677,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (DisableControls)
|
||||
{
|
||||
dockingModeEnabled = false;
|
||||
}
|
||||
|
||||
dockingContainer.Visible = DockingModeEnabled;
|
||||
statusContainer.Visible = !DockingModeEnabled;
|
||||
if (!DockingModeEnabled)
|
||||
@@ -745,7 +769,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
iceSpireWarningText.Visible = item.Submarine != null && !pressureWarningText.Visible && showIceSpireWarning && Timing.TotalTime % 1.0f < 0.8f;
|
||||
|
||||
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
|
||||
if (!disableControls && Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen &&
|
||||
(!GameMain.GameSession?.Campaign?.ShowCampaignUI ?? true) && !GUIMessageBox.MessageBoxes.Any(msgBox => msgBox is GUIMessageBox { MessageBoxType: GUIMessageBox.Type.Default }))
|
||||
@@ -815,7 +839,7 @@ namespace Barotrauma.Items.Components
|
||||
keyboardInput = Vector2.Zero;
|
||||
}
|
||||
|
||||
if (!UseAutoDocking) { return; }
|
||||
if (!UseAutoDocking || DisableControls) { return; }
|
||||
|
||||
if (checkConnectedPortsTimer <= 0.0f)
|
||||
{
|
||||
@@ -894,27 +918,27 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write(AutoPilot);
|
||||
msg.Write(dockingNetworkMessagePending);
|
||||
msg.WriteBoolean(AutoPilot);
|
||||
msg.WriteBoolean(dockingNetworkMessagePending);
|
||||
dockingNetworkMessagePending = false;
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
//no need to write steering info if autopilot is controlling
|
||||
msg.Write(steeringInput.X);
|
||||
msg.Write(steeringInput.Y);
|
||||
msg.WriteSingle(steeringInput.X);
|
||||
msg.WriteSingle(steeringInput.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(posToMaintain != null);
|
||||
msg.WriteBoolean(posToMaintain != null);
|
||||
if (posToMaintain != null)
|
||||
{
|
||||
msg.Write(((Vector2)posToMaintain).X);
|
||||
msg.Write(((Vector2)posToMaintain).Y);
|
||||
msg.WriteSingle(((Vector2)posToMaintain).X);
|
||||
msg.WriteSingle(((Vector2)posToMaintain).Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(LevelStartSelected);
|
||||
msg.WriteBoolean(LevelStartSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -998,9 +1022,20 @@ namespace Barotrauma.Items.Components
|
||||
steeringModeSwitch.Selected = AutoPilot;
|
||||
autopilotIndicator.Selected = AutoPilot;
|
||||
manualPilotIndicator.Selected = !AutoPilot;
|
||||
maintainPosTickBox.Enabled = AutoPilot;
|
||||
levelEndTickBox.Enabled = AutoPilot;
|
||||
levelStartTickBox.Enabled = AutoPilot;
|
||||
if (DisableControls)
|
||||
{
|
||||
steeringModeSwitch.Enabled = false;
|
||||
maintainPosTickBox.Enabled = false;
|
||||
levelEndTickBox.Enabled = false;
|
||||
levelStartTickBox.Enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringModeSwitch.Enabled = true;
|
||||
maintainPosTickBox.Enabled = AutoPilot;
|
||||
levelEndTickBox.Enabled = AutoPilot;
|
||||
levelStartTickBox.Enabled = AutoPilot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using Barotrauma.Sounds;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Powered : ItemComponent
|
||||
{
|
||||
|
||||
@@ -451,7 +451,7 @@ namespace Barotrauma.Items.Components
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger((int)requestStartFixAction, 0, 2);
|
||||
msg.Write(qteSuccess);
|
||||
msg.WriteBoolean(qteSuccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override void OnResolutionChanged()
|
||||
{
|
||||
base.OnResolutionChanged();
|
||||
OnItemLoadedProjSpecific();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,13 @@ namespace Barotrauma.Items.Components
|
||||
public float FlashTimer { get; private set; }
|
||||
public static Wire DraggingConnected { get; private set; }
|
||||
|
||||
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character)
|
||||
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Rectangle dragArea, Character character)
|
||||
{
|
||||
if (DraggingConnected?.Item?.Removed ?? true)
|
||||
{
|
||||
DraggingConnected = null;
|
||||
}
|
||||
|
||||
Rectangle panelRect = panel.GuiFrame.Rect;
|
||||
int x = panelRect.X, y = panelRect.Y;
|
||||
int width = panelRect.Width, height = panelRect.Height;
|
||||
@@ -131,7 +132,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (mouseInRect)
|
||||
{
|
||||
DrawWire(spriteBatch, DraggingConnected, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height - 10), null, panel, "");
|
||||
Vector2 wireDragPos = new Vector2(
|
||||
MathHelper.Clamp(PlayerInput.MousePosition.X, dragArea.X, dragArea.Right),
|
||||
MathHelper.Clamp(PlayerInput.MousePosition.Y, dragArea.Y, dragArea.Bottom));
|
||||
DrawWire(spriteBatch, DraggingConnected, wireDragPos, new Vector2(x + width / 2, y + height - 10), null, panel, "");
|
||||
}
|
||||
panel.TriggerRewiringSound();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -27,6 +26,10 @@ namespace Barotrauma.Items.Components
|
||||
private Point originalMaxSize;
|
||||
private Vector2 originalRelativeSize;
|
||||
|
||||
private GUIComponent dragArea;
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
if (GuiFrame == null) { return; }
|
||||
@@ -40,9 +43,8 @@ namespace Barotrauma.Items.Components
|
||||
content.RectTransform.SetAsFirstChild();
|
||||
|
||||
//prevents inputs from going through the GUICustomComponent to the drag handle
|
||||
var blocker = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
||||
{ AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
style: null);
|
||||
dragArea = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
||||
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null);
|
||||
}
|
||||
|
||||
public void TriggerRewiringSound()
|
||||
@@ -111,7 +113,7 @@ namespace Barotrauma.Items.Components
|
||||
if (user != Character.Controlled || user == null) { return; }
|
||||
|
||||
HighlightedWire = null;
|
||||
Connection.DrawConnections(spriteBatch, this, user);
|
||||
Connection.DrawConnections(spriteBatch, this, dragArea.Rect, user);
|
||||
|
||||
foreach (UISprite sprite in GUIStyle.GetComponentStyle("ConnectionPanelFront").Sprites[GUIComponent.ComponentState.None])
|
||||
{
|
||||
@@ -121,7 +123,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override void OnResolutionChanged()
|
||||
{
|
||||
base.OnResolutionChanged();
|
||||
if (GuiFrame == null) { return; }
|
||||
CheckForLabelOverlap();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
CreateGUI();
|
||||
@@ -354,29 +356,29 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!element.IsNumberInput)
|
||||
{
|
||||
msg.Write(((GUITextBox)uiElements[i]).Text);
|
||||
msg.WriteString(((GUITextBox)uiElements[i]).Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (element.NumberType)
|
||||
{
|
||||
case NumberType.Float:
|
||||
msg.Write(((GUINumberInput)uiElements[i]).FloatValue.ToString());
|
||||
msg.WriteString(((GUINumberInput)uiElements[i]).FloatValue.ToString());
|
||||
break;
|
||||
case NumberType.Int:
|
||||
default:
|
||||
msg.Write(((GUINumberInput)uiElements[i]).IntValue.ToString());
|
||||
msg.WriteString(((GUINumberInput)uiElements[i]).IntValue.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.ContinuousSignal)
|
||||
{
|
||||
msg.Write(((GUITickBox)uiElements[i]).Selected);
|
||||
msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
|
||||
msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (TryExtractEventData(extraData, out ClientEventData eventData))
|
||||
{
|
||||
msg.Write(eventData.Text);
|
||||
msg.WriteString(eventData.Text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -603,11 +603,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var eventData = ExtractEventData<ClientEventData>(extraData);
|
||||
int nodeCount = eventData.NodeCount;
|
||||
msg.Write((byte)nodeCount);
|
||||
msg.WriteByte((byte)nodeCount);
|
||||
if (nodeCount > 0)
|
||||
{
|
||||
msg.Write(nodes.Last().X);
|
||||
msg.Write(nodes.Last().Y);
|
||||
msg.WriteSingle(nodes.Last().X);
|
||||
msg.WriteSingle(nodes.Last().Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.Write((byte)allowOutpostAutoDocking);
|
||||
msg.WriteByte((byte)allowOutpostAutoDocking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +594,10 @@ namespace Barotrauma
|
||||
if (body.LinearVelocity.Y < 0.0f)
|
||||
{
|
||||
int n = (int)((Position.X - CurrentHull.Rect.X) / Hull.WaveWidth);
|
||||
CurrentHull.WaveVel[n] += MathHelper.Clamp(body.LinearVelocity.Y * massFactor, -5.0f, 5.0f);
|
||||
if (n >= 0 && n < currentHull.WaveVel.Length)
|
||||
{
|
||||
CurrentHull.WaveVel[n] += MathHelper.Clamp(body.LinearVelocity.Y * massFactor, -5.0f, 5.0f);
|
||||
}
|
||||
}
|
||||
SoundPlayer.PlaySplashSound(WorldPosition, Math.Abs(body.LinearVelocity.Y) + Rand.Range(-10.0f, -5.0f));
|
||||
}
|
||||
@@ -1469,8 +1472,8 @@ namespace Barotrauma
|
||||
case TreatmentEventData treatmentEventData:
|
||||
Character targetCharacter = treatmentEventData.TargetCharacter;
|
||||
|
||||
msg.Write(targetCharacter.ID);
|
||||
msg.Write(treatmentEventData.LimbIndex);
|
||||
msg.WriteUInt16(targetCharacter.ID);
|
||||
msg.WriteByte(treatmentEventData.LimbIndex);
|
||||
break;
|
||||
case ChangePropertyEventData changePropertyEventData:
|
||||
WritePropertyChange(msg, changePropertyEventData, inGameEditableOnly: true);
|
||||
@@ -1478,7 +1481,7 @@ namespace Barotrauma
|
||||
break;
|
||||
case CombineEventData combineEventData:
|
||||
Item combineTarget = combineEventData.CombineTarget;
|
||||
msg.Write(combineTarget.ID);
|
||||
msg.WriteUInt16(combineTarget.ID);
|
||||
break;
|
||||
default:
|
||||
throw error($"Unsupported event type {eventData.GetType().Name}");
|
||||
|
||||
@@ -222,7 +222,7 @@ namespace Barotrauma
|
||||
|
||||
if (!ResizeHorizontal && !ResizeVertical)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||
if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null)
|
||||
{
|
||||
var item = new Item(new Rectangle((int)position.X, (int)position.Y, (int)(Sprite.size.X * Scale), (int)(Sprite.size.Y * Scale)), this, Submarine.MainSub)
|
||||
{
|
||||
|
||||
@@ -611,7 +611,7 @@ namespace Barotrauma
|
||||
case DecalEventData decalEventData:
|
||||
var decal = decalEventData.Decal;
|
||||
int decalIndex = decals.IndexOf(decal);
|
||||
msg.Write((byte)(decalIndex < 0 ? 255 : decalIndex));
|
||||
msg.WriteByte((byte)(decalIndex < 0 ? 255 : decalIndex));
|
||||
msg.WriteRangedSingle(decal.BaseAlpha, 0.0f, 1.0f, 8);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -741,6 +741,15 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static void DrawSelecting(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (Screen.Selected is SubEditorScreen subEditor)
|
||||
{
|
||||
if (subEditor.IsMouseOnEditorGUI()) { return; }
|
||||
}
|
||||
else if (GUI.MouseOn != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 position = PlayerInput.MousePosition;
|
||||
position = cam.ScreenToWorld(position);
|
||||
|
||||
|
||||
@@ -37,17 +37,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes)]
|
||||
#else
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
#endif
|
||||
public string SpecialTag
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
Prefab.Sprite?.EnsureLazyLoaded();
|
||||
|
||||
@@ -27,9 +27,10 @@ namespace Barotrauma
|
||||
|
||||
if (placePosition == Vector2.Zero)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null)
|
||||
if (PlayerInput.PrimaryMouseButtonHeld() && GUI.MouseOn == null)
|
||||
{
|
||||
placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
||||
|
||||
}
|
||||
newRect.X = (int)position.X;
|
||||
newRect.Y = (int)position.Y;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ namespace Barotrauma
|
||||
{
|
||||
UserData = "descriptionbox",
|
||||
ScrollBarVisible = true,
|
||||
Spacing = 5
|
||||
Spacing = 5,
|
||||
CurrentSelectMode = GUIListBox.SelectMode.None
|
||||
};
|
||||
|
||||
GUIFont font = parent.Rect.Width < 350 ? GUIStyle.SmallFont : GUIStyle.Font;
|
||||
@@ -92,7 +93,10 @@ namespace Barotrauma
|
||||
{
|
||||
float leftPanelWidth = 0.6f;
|
||||
float rightPanelWidth = 0.4f / leftPanelWidth;
|
||||
LocalizedString className = !HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{SubmarineClass}") : TextManager.Get("shuttle");
|
||||
LocalizedString className = !HasTag(SubmarineTag.Shuttle)
|
||||
? $"{TextManager.Get($"submarineclass.{SubmarineClass}")} ({TextManager.Get($"submarinetier.{Tier}")})"
|
||||
: TextManager.Get("shuttle");
|
||||
|
||||
|
||||
int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y;
|
||||
int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth);
|
||||
@@ -102,12 +106,19 @@ namespace Barotrauma
|
||||
if (includeTitle)
|
||||
{
|
||||
int nameHeight = (int)GUIStyle.LargeFont.MeasureString(DisplayName, true).Y;
|
||||
submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUIStyle.LargeFont) { CanBeFocused = false };
|
||||
submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUIStyle.LargeFont)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y);
|
||||
}
|
||||
if (includeClass)
|
||||
{
|
||||
submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont) { CanBeFocused = false };
|
||||
submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont)
|
||||
{
|
||||
ToolTip = TextManager.Get("submarinetierandclass.description")+"\n\n"+ TextManager.Get($"submarineclass.{SubmarineClass}.description")
|
||||
};
|
||||
submarineClassText.HoverColor = Color.Transparent;
|
||||
submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y);
|
||||
}
|
||||
|
||||
|
||||
@@ -144,10 +144,11 @@ namespace Barotrauma
|
||||
|
||||
specsContainer = new GUIListBox(new RectTransform(new Vector2(0.4f, 1f), innerPadded.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(0.015f, 0.07f) })
|
||||
{
|
||||
CurrentSelectMode = GUIListBox.SelectMode.None,
|
||||
Color = Color.Black * 0.65f,
|
||||
ScrollBarEnabled = false,
|
||||
ScrollBarVisible = false,
|
||||
Spacing = 5
|
||||
Spacing = GUI.IntScale(5)
|
||||
};
|
||||
subInfo.CreateSpecsWindow(specsContainer, GUIStyle.Font, includeTitle: false, includeDescription: true);
|
||||
int width = specsContainer.Rect.Width;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
#nullable enable
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -24,36 +25,31 @@ namespace Barotrauma.Networking
|
||||
|
||||
partial class BanList
|
||||
{
|
||||
private GUIComponent banFrame;
|
||||
|
||||
public GUIComponent BanFrame
|
||||
{
|
||||
get { return banFrame; }
|
||||
}
|
||||
public GUIComponent? BanFrame { get; private set; }
|
||||
|
||||
public List<UInt32> localRemovedBans = new List<UInt32>();
|
||||
|
||||
private void RecreateBanFrame()
|
||||
{
|
||||
if (banFrame != null)
|
||||
if (BanFrame != null)
|
||||
{
|
||||
var parent = banFrame.Parent;
|
||||
parent.RemoveChild(banFrame);
|
||||
var parent = BanFrame.Parent;
|
||||
parent.RemoveChild(BanFrame);
|
||||
CreateBanFrame(parent);
|
||||
}
|
||||
}
|
||||
|
||||
public GUIComponent CreateBanFrame(GUIComponent parent)
|
||||
{
|
||||
banFrame = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center));
|
||||
BanFrame = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center));
|
||||
|
||||
foreach (BannedPlayer bannedPlayer in bannedPlayers)
|
||||
{
|
||||
if (localRemovedBans.Contains(bannedPlayer.UniqueIdentifier)) { continue; }
|
||||
|
||||
var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)banFrame).Content.RectTransform) { MinSize = new Point(0, 70) })
|
||||
var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)BanFrame).Content.RectTransform) { MinSize = new Point(0, 70) })
|
||||
{
|
||||
UserData = banFrame
|
||||
UserData = BanFrame
|
||||
};
|
||||
|
||||
var paddedPlayerFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), playerFrame.RectTransform, Anchor.Center))
|
||||
@@ -102,16 +98,15 @@ namespace Barotrauma.Networking
|
||||
|
||||
paddedPlayerFrame.Recalculate();
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)banFrame).Content.RectTransform), style: "HorizontalLine");
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)BanFrame).Content.RectTransform), style: "HorizontalLine");
|
||||
}
|
||||
|
||||
return banFrame;
|
||||
return BanFrame;
|
||||
}
|
||||
|
||||
private bool RemoveBan(GUIButton button, object obj)
|
||||
{
|
||||
BannedPlayer banned = obj as BannedPlayer;
|
||||
if (banned == null) { return false; }
|
||||
if (!(obj is BannedPlayer banned)) { return false; }
|
||||
|
||||
localRemovedBans.Add(banned.UniqueIdentifier);
|
||||
RecreateBanFrame();
|
||||
@@ -178,10 +173,10 @@ namespace Barotrauma.Networking
|
||||
bannedPlayers.Add(new BannedPlayer(uniqueIdentifier, name, addressOrAccountId, reason, expiration));
|
||||
}
|
||||
|
||||
if (banFrame != null)
|
||||
if (BanFrame != null)
|
||||
{
|
||||
var parent = banFrame.Parent;
|
||||
parent.RemoveChild(banFrame);
|
||||
var parent = BanFrame.Parent;
|
||||
parent.RemoveChild(BanFrame);
|
||||
CreateBanFrame(parent);
|
||||
}
|
||||
}
|
||||
@@ -191,7 +186,7 @@ namespace Barotrauma.Networking
|
||||
outMsg.WriteVariableUInt32((UInt32)localRemovedBans.Count);
|
||||
foreach (UInt32 uniqueId in localRemovedBans)
|
||||
{
|
||||
outMsg.Write(uniqueId);
|
||||
outMsg.WriteUInt32(uniqueId);
|
||||
}
|
||||
|
||||
localRemovedBans.Clear();
|
||||
|
||||
@@ -8,11 +8,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public virtual void ClientWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.Write(NetStateID);
|
||||
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.WriteUInt16(NetStateID);
|
||||
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
||||
msg.Write(Text);
|
||||
msg.WriteString(Text);
|
||||
}
|
||||
|
||||
public static void ClientRead(IReadMessage msg)
|
||||
|
||||
@@ -93,6 +93,12 @@ namespace Barotrauma.Networking
|
||||
|
||||
public int ID;
|
||||
|
||||
public const int DataBufferSize = 50;
|
||||
/// <summary>
|
||||
/// Data that we've ignored because we're waiting for some earlier data. Key = byte offset, value = the actual data
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, byte[]> DataBuffer = new Dictionary<int, byte[]>();
|
||||
|
||||
public FileTransferIn(NetworkConnection connection, string filePath, FileTransferType fileType)
|
||||
{
|
||||
FilePath = filePath;
|
||||
@@ -128,20 +134,25 @@ namespace Barotrauma.Networking
|
||||
bytesToRead -= Received + bytesToRead - FileSize;
|
||||
}
|
||||
|
||||
byte[] all = inc.ReadBytes(bytesToRead);
|
||||
Received += all.Length;
|
||||
WriteStream.Write(all, 0, all.Length);
|
||||
ReadBytes(inc.ReadBytes(bytesToRead));
|
||||
}
|
||||
|
||||
public void ReadBytes(byte[] data)
|
||||
{
|
||||
Received += data.Length;
|
||||
WriteStream.Write(data, 0, data.Length);
|
||||
|
||||
int passed = Environment.TickCount - TimeStarted;
|
||||
float psec = passed / 1000.0f;
|
||||
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log($"Received {all.Length} bytes of the file {FileName} ({Received / 1000}/{FileSize / 1000} kB received)");
|
||||
}
|
||||
|
||||
BytesPerSecond = Received / psec;
|
||||
|
||||
var outdatedKeys = DataBuffer.Keys.Where(k => k < Received).ToList();
|
||||
foreach (int key in outdatedKeys)
|
||||
{
|
||||
DataBuffer.Remove(key);
|
||||
}
|
||||
|
||||
Status = Received >= FileSize ? FileTransferStatus.Finished : FileTransferStatus.Receiving;
|
||||
}
|
||||
|
||||
@@ -349,6 +360,10 @@ namespace Barotrauma.Networking
|
||||
if (offset != activeTransfer.Received)
|
||||
{
|
||||
activeTransfer.LastSeen = Math.Max(offset, activeTransfer.LastSeen);
|
||||
if (!activeTransfer.DataBuffer.ContainsKey(offset) && activeTransfer.DataBuffer.Count < FileTransferIn.DataBufferSize)
|
||||
{
|
||||
activeTransfer.DataBuffer.Add(offset, inc.ReadBytes(bytesToRead));
|
||||
}
|
||||
DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} (ignoring: offset {offset}, waiting for {activeTransfer.Received})");
|
||||
GameMain.Client.UpdateFileTransfer(activeTransfer, activeTransfer.Received, activeTransfer.LastSeen);
|
||||
return;
|
||||
@@ -371,6 +386,15 @@ namespace Barotrauma.Networking
|
||||
try
|
||||
{
|
||||
activeTransfer.ReadBytes(inc, bytesToRead);
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)");
|
||||
}
|
||||
while (activeTransfer.DataBuffer.TryGetValue(activeTransfer.Received, out byte[] data))
|
||||
{
|
||||
activeTransfer.ReadBytes(data);
|
||||
DebugConsole.Log($"Read {data.Length} bytes of buffer data of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -434,7 +458,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fileName) ||
|
||||
fileName.IndexOfAny(Path.GetInvalidFileNameChars().ToArray()) > -1)
|
||||
fileName.IndexOfAny(Path.GetInvalidFileNameCharsCrossPlatform().ToArray()) > -1)
|
||||
{
|
||||
errorMessage = "Illegal characters in file name ''" + fileName + "''";
|
||||
return false;
|
||||
@@ -470,7 +494,7 @@ namespace Barotrauma.Networking
|
||||
System.IO.Stream stream;
|
||||
try
|
||||
{
|
||||
stream = SaveUtil.DecompressFiletoStream(fileTransfer.FilePath);
|
||||
stream = SaveUtil.DecompressFileToStream(fileTransfer.FilePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -674,13 +674,13 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
case ServerPacketHeader.PING_REQUEST:
|
||||
IWriteMessage response = new WriteOnlyMessage();
|
||||
response.Write((byte)ClientPacketHeader.PING_RESPONSE);
|
||||
response.WriteByte((byte)ClientPacketHeader.PING_RESPONSE);
|
||||
byte requestLen = inc.ReadByte();
|
||||
response.Write(requestLen);
|
||||
response.WriteByte(requestLen);
|
||||
for (int i = 0; i < requestLen; i++)
|
||||
{
|
||||
byte b = inc.ReadByte();
|
||||
response.Write(b);
|
||||
response.WriteByte(b);
|
||||
}
|
||||
clientPeer.Send(response, DeliveryMethod.Unreliable);
|
||||
break;
|
||||
@@ -752,7 +752,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
|
||||
readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME);
|
||||
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
|
||||
|
||||
if (campaign != null) { campaign.PendingSubmarineSwitch = null; }
|
||||
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
|
||||
@@ -770,7 +770,7 @@ namespace Barotrauma.Networking
|
||||
campaign.LastSaveID == campaignSaveID &&
|
||||
campaignUpdateIDs.All(kvp => campaign.GetLastUpdateIdForFlag(kvp.Key) == kvp.Value);
|
||||
}
|
||||
readyToStartMsg.Write(readyToStart);
|
||||
readyToStartMsg.WriteBoolean(readyToStart);
|
||||
|
||||
DebugConsole.Log(readyToStart ? "Ready to start." : "Not ready to start.");
|
||||
|
||||
@@ -1202,7 +1202,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
VoipClient = new VoipClient(this, clientPeer);
|
||||
|
||||
if (Screen.Selected != GameMain.GameScreen && !(Screen.Selected is RoundSummaryScreen))
|
||||
//if we're still in the game, roundsummary or lobby screen, we don't need to redownload the mods
|
||||
if (!(Screen.Selected is GameScreen) && !(Screen.Selected is RoundSummaryScreen) && !(Screen.Selected is NetLobbyScreen))
|
||||
{
|
||||
GameMain.ModDownloadScreen.Select();
|
||||
}
|
||||
@@ -1639,7 +1640,7 @@ namespace Barotrauma.Networking
|
||||
DateTime requestFinalizeTime = DateTime.Now;
|
||||
TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2);
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
|
||||
msg.WriteByte((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
|
||||
clientPeer.Send(msg, DeliveryMethod.Unreliable);
|
||||
|
||||
GUIMessageBox interruptPrompt = null;
|
||||
@@ -1653,7 +1654,7 @@ namespace Barotrauma.Networking
|
||||
if (DateTime.Now > requestFinalizeTime)
|
||||
{
|
||||
msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
|
||||
msg.WriteByte((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
|
||||
clientPeer.Send(msg, DeliveryMethod.Unreliable);
|
||||
requestFinalizeTime = DateTime.Now + requestFinalizeInterval;
|
||||
}
|
||||
@@ -1816,16 +1817,24 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (Screen.Selected == GameMain.GameScreen)
|
||||
{
|
||||
Submarine refSub = Submarine.MainSub;
|
||||
if (Submarine.MainSubs[1] != null &&
|
||||
GameMain.GameSession.GameMode is PvPMode &&
|
||||
GameMain.GameSession.WinningTeam.HasValue && GameMain.GameSession.WinningTeam == CharacterTeamType.Team1)
|
||||
{
|
||||
refSub = Submarine.MainSubs[1];
|
||||
}
|
||||
|
||||
// Enable characters near the main sub for the endCinematic
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
|
||||
if (Vector2.DistanceSquared(refSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
|
||||
{
|
||||
c.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
EndCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight);
|
||||
EndCinematic = new CameraTransition(refSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight);
|
||||
while (EndCinematic.Running && Screen.Selected == GameMain.GameScreen)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
@@ -1874,7 +1883,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, ServerSubmarines);
|
||||
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, ServerSubmarines);
|
||||
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, ServerSubmarines.Where(s => s.HasTag(SubmarineTag.Shuttle)));
|
||||
|
||||
gameStarted = inc.ReadBoolean();
|
||||
bool allowSpectating = inc.ReadBoolean();
|
||||
@@ -2073,7 +2082,7 @@ namespace Barotrauma.Networking
|
||||
(isInitialUpdate || initialUpdateReceived))
|
||||
{
|
||||
ReadWriteMessage settingsBuf = new ReadWriteMessage();
|
||||
settingsBuf.Write(settingsData, 0, settingsLen); settingsBuf.BitPosition = 0;
|
||||
settingsBuf.WriteBytes(settingsData, 0, settingsLen); settingsBuf.BitPosition = 0;
|
||||
serverSettings.ClientRead(settingsBuf);
|
||||
if (!IsServerOwner)
|
||||
{
|
||||
@@ -2325,38 +2334,38 @@ namespace Barotrauma.Networking
|
||||
private void SendLobbyUpdate()
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
|
||||
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
||||
|
||||
outmsg.Write((byte)ClientNetObject.SYNC_IDS);
|
||||
outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
||||
outmsg.Write(ChatMessage.LastID);
|
||||
outmsg.Write(LastClientListUpdateID);
|
||||
outmsg.Write(nameId);
|
||||
outmsg.Write(Name);
|
||||
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
|
||||
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
|
||||
outmsg.WriteUInt16(ChatMessage.LastID);
|
||||
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||
outmsg.WriteUInt16(nameId);
|
||||
outmsg.WriteString(Name);
|
||||
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
|
||||
if (jobPreferences.Count > 0)
|
||||
{
|
||||
outmsg.Write(jobPreferences[0].Prefab.Identifier);
|
||||
outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
outmsg.Write("");
|
||||
outmsg.WriteIdentifier(Identifier.Empty);
|
||||
}
|
||||
outmsg.Write((byte)MultiplayerPreferences.Instance.TeamPreference);
|
||||
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
|
||||
|
||||
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
||||
{
|
||||
outmsg.Write((UInt16)0);
|
||||
outmsg.WriteUInt16((UInt16)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
outmsg.Write(campaign.LastSaveID);
|
||||
outmsg.Write(campaign.CampaignID);
|
||||
outmsg.WriteUInt16(campaign.LastSaveID);
|
||||
outmsg.WriteByte(campaign.CampaignID);
|
||||
foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
|
||||
{
|
||||
outmsg.Write(campaign.GetLastUpdateIdForFlag(netFlag));
|
||||
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag));
|
||||
}
|
||||
outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
||||
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
||||
}
|
||||
|
||||
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
|
||||
@@ -2369,7 +2378,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
chatMsgQueue[i].ClientWrite(outmsg);
|
||||
}
|
||||
outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE);
|
||||
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
|
||||
|
||||
if (outmsg.LengthBytes > MsgConstants.MTU)
|
||||
{
|
||||
@@ -2382,29 +2391,29 @@ namespace Barotrauma.Networking
|
||||
private void SendIngameUpdate()
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte)ClientPacketHeader.UPDATE_INGAME);
|
||||
outmsg.Write(entityEventManager.MidRoundSyncingDone);
|
||||
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_INGAME);
|
||||
outmsg.WriteBoolean(entityEventManager.MidRoundSyncingDone);
|
||||
outmsg.WritePadBits();
|
||||
|
||||
outmsg.Write((byte)ClientNetObject.SYNC_IDS);
|
||||
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
|
||||
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
||||
outmsg.Write(ChatMessage.LastID);
|
||||
outmsg.Write(entityEventManager.LastReceivedID);
|
||||
outmsg.Write(LastClientListUpdateID);
|
||||
outmsg.WriteUInt16(ChatMessage.LastID);
|
||||
outmsg.WriteUInt16(entityEventManager.LastReceivedID);
|
||||
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||
|
||||
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
||||
{
|
||||
outmsg.Write((UInt16)0);
|
||||
outmsg.WriteUInt16((UInt16)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
outmsg.Write(campaign.LastSaveID);
|
||||
outmsg.Write(campaign.CampaignID);
|
||||
outmsg.WriteUInt16(campaign.LastSaveID);
|
||||
outmsg.WriteByte(campaign.CampaignID);
|
||||
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
|
||||
{
|
||||
outmsg.Write(campaign.GetLastUpdateIdForFlag(flag));
|
||||
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
|
||||
}
|
||||
outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
||||
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
||||
}
|
||||
|
||||
Character.Controlled?.ClientWriteInput(outmsg);
|
||||
@@ -2423,7 +2432,7 @@ namespace Barotrauma.Networking
|
||||
chatMsgQueue[i].ClientWrite(outmsg);
|
||||
}
|
||||
|
||||
outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE);
|
||||
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
|
||||
|
||||
if (outmsg.LengthBytes > MsgConstants.MTU)
|
||||
{
|
||||
@@ -2462,8 +2471,8 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
WaitForNextRoundRespawn = waitForNextRoundRespawn;
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.READY_TO_SPAWN);
|
||||
msg.Write((bool)waitForNextRoundRespawn);
|
||||
msg.WriteByte((byte)ClientPacketHeader.READY_TO_SPAWN);
|
||||
msg.WriteBoolean((bool)waitForNextRoundRespawn);
|
||||
clientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
@@ -2475,13 +2484,13 @@ namespace Barotrauma.Networking
|
||||
$"Sending a file request to the server (type: {fileType}, path: {file ?? "null"}");
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.FILE_REQUEST);
|
||||
msg.Write((byte)FileTransferMessageType.Initiate);
|
||||
msg.Write((byte)fileType);
|
||||
msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST);
|
||||
msg.WriteByte((byte)FileTransferMessageType.Initiate);
|
||||
msg.WriteByte((byte)fileType);
|
||||
if (fileType != FileTransferType.CampaignSave)
|
||||
{
|
||||
msg.Write(file ?? throw new ArgumentNullException(nameof(file)));
|
||||
msg.Write(fileHash ?? throw new ArgumentNullException(nameof(fileHash)));
|
||||
msg.WriteString(file ?? throw new ArgumentNullException(nameof(file)));
|
||||
msg.WriteString(fileHash ?? throw new ArgumentNullException(nameof(fileHash)));
|
||||
}
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -2500,20 +2509,20 @@ namespace Barotrauma.Networking
|
||||
transfer.RecordOffsetAckTime();
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.FILE_REQUEST);
|
||||
msg.Write((byte)FileTransferMessageType.Data);
|
||||
msg.Write((byte)transfer.ID);
|
||||
msg.Write(expecting);
|
||||
msg.Write(lastSeen);
|
||||
msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST);
|
||||
msg.WriteByte((byte)FileTransferMessageType.Data);
|
||||
msg.WriteByte((byte)transfer.ID);
|
||||
msg.WriteInt32(expecting);
|
||||
msg.WriteInt32(lastSeen);
|
||||
clientPeer.Send(msg, reliable ? DeliveryMethod.Reliable : DeliveryMethod.Unreliable);
|
||||
}
|
||||
|
||||
public void CancelFileTransfer(int id)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.FILE_REQUEST);
|
||||
msg.Write((byte)FileTransferMessageType.Cancel);
|
||||
msg.Write((byte)id);
|
||||
msg.WriteByte((byte)ClientPacketHeader.FILE_REQUEST);
|
||||
msg.WriteByte((byte)FileTransferMessageType.Cancel);
|
||||
msg.WriteByte((byte)id);
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
@@ -2547,8 +2556,7 @@ namespace Barotrauma.Networking
|
||||
Color newSubTextColor = new Color(subElement.GetChild<GUITextBlock>().TextColor, 1.0f);
|
||||
subElement.GetChild<GUITextBlock>().TextColor = newSubTextColor;
|
||||
|
||||
GUITextBlock classTextBlock = subElement.GetChildByUserData("classtext") as GUITextBlock;
|
||||
if (classTextBlock != null)
|
||||
if (subElement.GetChildByUserData("classtext") is GUITextBlock classTextBlock)
|
||||
{
|
||||
Color newSubClassTextColor = new Color(classTextBlock.TextColor, 0.8f);
|
||||
classTextBlock.Text = TextManager.Get($"submarineclass.{newSub.SubmarineClass}");
|
||||
@@ -2742,39 +2750,39 @@ namespace Barotrauma.Networking
|
||||
public void SendCharacterInfo(string newName = null)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
|
||||
msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
|
||||
WriteCharacterInfo(msg, newName);
|
||||
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
clientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
|
||||
{
|
||||
msg.Write(characterInfo == null);
|
||||
msg.WriteBoolean(characterInfo == null);
|
||||
if (characterInfo == null) { return; }
|
||||
|
||||
msg.Write(newName ?? string.Empty);
|
||||
msg.WriteString(newName ?? string.Empty);
|
||||
|
||||
msg.Write((byte)characterInfo.Head.Preset.TagSet.Count);
|
||||
msg.WriteByte((byte)characterInfo.Head.Preset.TagSet.Count);
|
||||
foreach (Identifier tag in characterInfo.Head.Preset.TagSet)
|
||||
{
|
||||
msg.Write(tag);
|
||||
msg.WriteIdentifier(tag);
|
||||
}
|
||||
msg.Write((byte)characterInfo.Head.HairIndex);
|
||||
msg.Write((byte)characterInfo.Head.BeardIndex);
|
||||
msg.Write((byte)characterInfo.Head.MoustacheIndex);
|
||||
msg.Write((byte)characterInfo.Head.FaceAttachmentIndex);
|
||||
msg.WriteByte((byte)characterInfo.Head.HairIndex);
|
||||
msg.WriteByte((byte)characterInfo.Head.BeardIndex);
|
||||
msg.WriteByte((byte)characterInfo.Head.MoustacheIndex);
|
||||
msg.WriteByte((byte)characterInfo.Head.FaceAttachmentIndex);
|
||||
msg.WriteColorR8G8B8(characterInfo.Head.SkinColor);
|
||||
msg.WriteColorR8G8B8(characterInfo.Head.HairColor);
|
||||
msg.WriteColorR8G8B8(characterInfo.Head.FacialHairColor);
|
||||
|
||||
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
|
||||
int count = Math.Min(jobPreferences.Count, 3);
|
||||
msg.Write((byte)count);
|
||||
msg.WriteByte((byte)count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
msg.Write(jobPreferences[i].Prefab.Identifier);
|
||||
msg.Write((byte)jobPreferences[i].Variant);
|
||||
msg.WriteIdentifier(jobPreferences[i].Prefab.Identifier);
|
||||
msg.WriteByte((byte)jobPreferences[i].Variant);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2783,10 +2791,10 @@ namespace Barotrauma.Networking
|
||||
if (clientPeer == null) { return; }
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
|
||||
msg.Write((byte)ClientNetObject.VOTE);
|
||||
msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
||||
msg.WriteByte((byte)ClientNetObject.VOTE);
|
||||
Voting.ClientWrite(msg, voteType, data);
|
||||
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -2794,7 +2802,6 @@ namespace Barotrauma.Networking
|
||||
public void VoteForKick(Client votedClient)
|
||||
{
|
||||
if (votedClient == null) { return; }
|
||||
votedClient.AddKickVote(ConnectedClients.FirstOrDefault(c => c.SessionId == SessionId));
|
||||
Vote(VoteType.Kick, votedClient);
|
||||
}
|
||||
|
||||
@@ -2840,10 +2847,10 @@ namespace Barotrauma.Networking
|
||||
public override void KickPlayer(string kickedName, string reason)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.Kick);
|
||||
msg.Write(kickedName);
|
||||
msg.Write(reason);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.Kick);
|
||||
msg.WriteString(kickedName);
|
||||
msg.WriteString(reason);
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -2851,11 +2858,11 @@ namespace Barotrauma.Networking
|
||||
public override void BanPlayer(string kickedName, string reason, TimeSpan? duration = null)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.Ban);
|
||||
msg.Write(kickedName);
|
||||
msg.Write(reason);
|
||||
msg.Write(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.Ban);
|
||||
msg.WriteString(kickedName);
|
||||
msg.WriteString(reason);
|
||||
msg.WriteDouble(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -2863,28 +2870,28 @@ namespace Barotrauma.Networking
|
||||
public override void UnbanPlayer(string playerName)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.Unban);
|
||||
msg.Write(true); msg.WritePadBits();
|
||||
msg.Write(playerName);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.Unban);
|
||||
msg.WriteBoolean(true); msg.WritePadBits();
|
||||
msg.WriteString(playerName);
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public override void UnbanPlayer(Endpoint endpoint)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.Unban);
|
||||
msg.Write(false); msg.WritePadBits();
|
||||
msg.Write(endpoint.StringRepresentation);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.Unban);
|
||||
msg.WriteBoolean(false); msg.WritePadBits();
|
||||
msg.WriteString(endpoint.StringRepresentation);
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public void UpdateClientPermissions(Client targetClient)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.ManagePermissions);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.ManagePermissions);
|
||||
targetClient.WritePermissions(msg);
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -2897,10 +2904,10 @@ namespace Barotrauma.Networking
|
||||
return;
|
||||
}
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.ManageCampaign);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign);
|
||||
campaign.ClientWrite(msg);
|
||||
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
@@ -2913,12 +2920,12 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.ConsoleCommands);
|
||||
msg.Write(command);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.ConsoleCommands);
|
||||
msg.WriteString(command);
|
||||
Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
msg.Write(cursorWorldPos.X);
|
||||
msg.Write(cursorWorldPos.Y);
|
||||
msg.WriteSingle(cursorWorldPos.X);
|
||||
msg.WriteSingle(cursorWorldPos.Y);
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -2929,10 +2936,10 @@ namespace Barotrauma.Networking
|
||||
public void RequestStartRound(bool continueCampaign = false)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.ManageRound);
|
||||
msg.Write(false); //indicates round start
|
||||
msg.Write(continueCampaign);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.ManageRound);
|
||||
msg.WriteBoolean(false); //indicates round start
|
||||
msg.WriteBoolean(continueCampaign);
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -2940,25 +2947,16 @@ namespace Barotrauma.Networking
|
||||
/// <summary>
|
||||
/// Tell the server to select a submarine (permission required)
|
||||
/// </summary>
|
||||
public void RequestSelectSub(int subIndex, bool isShuttle)
|
||||
public void RequestSelectSub(SubmarineInfo sub, bool isShuttle)
|
||||
{
|
||||
if (!HasPermission(ClientPermissions.SelectSub)) return;
|
||||
|
||||
var subList = isShuttle ? GameMain.NetLobbyScreen.ShuttleList.ListBox : GameMain.NetLobbyScreen.SubList;
|
||||
|
||||
if (subIndex < 0 || subIndex >= subList.Content.CountChildren)
|
||||
{
|
||||
DebugConsole.ThrowError("Submarine index out of bounds (" + subIndex + ")\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
if (!HasPermission(ClientPermissions.SelectSub) || sub == null) { return; }
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.SelectSub);
|
||||
msg.Write(isShuttle); msg.WritePadBits();
|
||||
msg.Write((UInt16)subIndex);
|
||||
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
|
||||
msg.WriteBoolean(isShuttle); msg.WritePadBits();
|
||||
msg.WriteString(sub.MD5Hash.StringRepresentation);
|
||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
@@ -2975,10 +2973,10 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.SelectMode);
|
||||
msg.Write((UInt16)modeIndex);
|
||||
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.SelectMode);
|
||||
msg.WriteUInt16((UInt16)modeIndex);
|
||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -2991,14 +2989,14 @@ namespace Barotrauma.Networking
|
||||
saveName = Path.GetFileNameWithoutExtension(saveName);
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
|
||||
msg.WriteByte((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
|
||||
|
||||
msg.Write(true); msg.WritePadBits();
|
||||
msg.Write(saveName);
|
||||
msg.Write(mapSeed);
|
||||
msg.Write(sub.Name);
|
||||
msg.Write(sub.MD5Hash.StringRepresentation);
|
||||
msg.Write(settings);
|
||||
msg.WriteBoolean(true); msg.WritePadBits();
|
||||
msg.WriteString(saveName);
|
||||
msg.WriteString(mapSeed);
|
||||
msg.WriteString(sub.Name);
|
||||
msg.WriteString(sub.MD5Hash.StringRepresentation);
|
||||
msg.WriteNetSerializableStruct(settings);
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -3011,10 +3009,10 @@ namespace Barotrauma.Networking
|
||||
GameMain.NetLobbyScreen.CampaignFrame.Visible = false;
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
|
||||
msg.WriteByte((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
|
||||
|
||||
msg.Write(false); msg.WritePadBits();
|
||||
msg.Write(saveName);
|
||||
msg.WriteBoolean(false); msg.WritePadBits();
|
||||
msg.WriteString(saveName);
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -3025,10 +3023,10 @@ namespace Barotrauma.Networking
|
||||
public void RequestRoundEnd(bool save)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.Write((UInt16)ClientPermissions.ManageRound);
|
||||
msg.Write(true); //indicates round end
|
||||
msg.Write(save);
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
msg.WriteUInt16((UInt16)ClientPermissions.ManageRound);
|
||||
msg.WriteBoolean(true); //indicates round end
|
||||
msg.WriteBoolean(save);
|
||||
|
||||
clientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
@@ -3049,11 +3047,11 @@ namespace Barotrauma.Networking
|
||||
if (clientPeer == null) { return false; }
|
||||
|
||||
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
|
||||
readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME);
|
||||
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
|
||||
|
||||
//assume we have the required sub files to start the round
|
||||
//(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
|
||||
readyToStartMsg.Write(true);
|
||||
readyToStartMsg.WriteBoolean(true);
|
||||
|
||||
WriteCharacterInfo(readyToStartMsg);
|
||||
|
||||
@@ -3480,10 +3478,6 @@ namespace Barotrauma.Networking
|
||||
UserData = client,
|
||||
OnClicked = (btn, userdata) => { VoteForKick(client); btn.Enabled = false; return true; }
|
||||
};
|
||||
if (GameMain.NetworkMember.ConnectedClients != null)
|
||||
{
|
||||
kickVoteButton.Enabled = !client.HasKickVoteFromSessionId(SessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3568,21 +3562,21 @@ namespace Barotrauma.Networking
|
||||
public void ReportError(ClientNetError error, UInt16 expectedId = 0, UInt16 eventId = 0, UInt16 entityId = 0)
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)ClientPacketHeader.ERROR);
|
||||
outMsg.Write((byte)error);
|
||||
outMsg.WriteByte((byte)ClientPacketHeader.ERROR);
|
||||
outMsg.WriteByte((byte)error);
|
||||
switch (error)
|
||||
{
|
||||
case ClientNetError.MISSING_EVENT:
|
||||
outMsg.Write(expectedId);
|
||||
outMsg.Write(eventId);
|
||||
outMsg.WriteUInt16(expectedId);
|
||||
outMsg.WriteUInt16(eventId);
|
||||
break;
|
||||
case ClientNetError.MISSING_ENTITY:
|
||||
outMsg.Write(eventId);
|
||||
outMsg.Write(entityId);
|
||||
outMsg.Write((byte)Submarine.Loaded.Count);
|
||||
outMsg.WriteUInt16(eventId);
|
||||
outMsg.WriteUInt16(entityId);
|
||||
outMsg.WriteByte((byte)Submarine.Loaded.Count);
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
outMsg.Write(sub.Info.Name);
|
||||
outMsg.WriteString(sub.Info.Name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Barotrauma.Networking
|
||||
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
|
||||
}
|
||||
|
||||
msg.Write((byte)ClientNetObject.ENTITY_STATE);
|
||||
msg.WriteByte((byte)ClientNetObject.ENTITY_STATE);
|
||||
Write(msg, eventsToSync, out _);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void Write(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(CharacterStateID);
|
||||
msg.WriteUInt16(CharacterStateID);
|
||||
serializable.ClientEventWrite(msg, Data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public override void ClientWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.Write(NetStateID);
|
||||
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.WriteUInt16(NetStateID);
|
||||
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
||||
WriteOrder(msg);
|
||||
|
||||
@@ -1,60 +1,23 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class ClientPeer
|
||||
internal abstract class ClientPeer
|
||||
{
|
||||
public class ServerContentPackage
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly Md5Hash Hash;
|
||||
public readonly UInt64 WorkshopId;
|
||||
public readonly DateTime InstallTime;
|
||||
|
||||
public RegularPackage? RegularPackage
|
||||
{
|
||||
get
|
||||
{
|
||||
return ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash));
|
||||
}
|
||||
}
|
||||
|
||||
public CorePackage? CorePackage
|
||||
{
|
||||
get
|
||||
{
|
||||
return ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash));
|
||||
}
|
||||
}
|
||||
|
||||
public ContentPackage? ContentPackage
|
||||
=> (ContentPackage?)RegularPackage ?? CorePackage;
|
||||
|
||||
|
||||
public string GetPackageStr()
|
||||
=> $"\"{Name}\" (hash {Hash.ShortRepresentation})";
|
||||
|
||||
public ServerContentPackage(string name, Md5Hash hash, UInt64 workshopId, DateTime installTime)
|
||||
{
|
||||
Name = name;
|
||||
Hash = hash;
|
||||
WorkshopId = workshopId;
|
||||
InstallTime = installTime;
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
|
||||
ImmutableArray<ServerContentPackage>.Empty;
|
||||
|
||||
public delegate void MessageCallback(IReadMessage message);
|
||||
|
||||
public delegate void DisconnectCallback(bool disableReconnect);
|
||||
|
||||
public delegate void DisconnectMessageCallback(string message);
|
||||
|
||||
public delegate void PasswordCallback(int salt, int retries);
|
||||
|
||||
public delegate void InitializationCompleteCallback();
|
||||
|
||||
[Obsolete("TODO: delete in nr3-layer-1-2-cleanup")]
|
||||
@@ -66,7 +29,11 @@ namespace Barotrauma.Networking
|
||||
public readonly PasswordCallback OnRequestPassword;
|
||||
public readonly InitializationCompleteCallback OnInitializationComplete;
|
||||
|
||||
public Callbacks(MessageCallback onMessageReceived, DisconnectCallback onDisconnect, DisconnectMessageCallback onDisconnectMessageReceived, PasswordCallback onRequestPassword, InitializationCompleteCallback onInitializationComplete)
|
||||
public Callbacks(MessageCallback onMessageReceived,
|
||||
DisconnectCallback onDisconnect,
|
||||
DisconnectMessageCallback onDisconnectMessageReceived,
|
||||
PasswordCallback onRequestPassword,
|
||||
InitializationCompleteCallback onInitializationComplete)
|
||||
{
|
||||
OnMessageReceived = onMessageReceived;
|
||||
OnDisconnect = onDisconnect;
|
||||
@@ -98,91 +65,99 @@ namespace Barotrauma.Networking
|
||||
public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true);
|
||||
public abstract void SendPassword(string password);
|
||||
|
||||
protected abstract void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg);
|
||||
protected abstract void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body);
|
||||
|
||||
protected ConnectionInitialization initializationStep;
|
||||
public bool ContentPackageOrderReceived { get; protected set; }
|
||||
protected int passwordSalt;
|
||||
protected Steamworks.AuthTicket? steamAuthTicket;
|
||||
protected void ReadConnectionInitializationStep(IReadMessage inc)
|
||||
|
||||
public struct IncomingInitializationMessage
|
||||
{
|
||||
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
|
||||
public ConnectionInitialization InitializationStep;
|
||||
public IReadMessage Message;
|
||||
}
|
||||
|
||||
IWriteMessage outMsg;
|
||||
|
||||
switch (step)
|
||||
protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc)
|
||||
{
|
||||
switch (inc.InitializationStep)
|
||||
{
|
||||
case ConnectionInitialization.SteamTicketAndVersion:
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
|
||||
outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
|
||||
outMsg.Write(GameMain.Client.Name);
|
||||
outMsg.Write(ownerKey.Fallback(0));
|
||||
outMsg.Write(SteamManager.GetSteamId().Select(steamId => steamId.Value).Fallback(0));
|
||||
if (steamAuthTicket == null)
|
||||
{
|
||||
outMsg.Write((UInt16)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
|
||||
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
|
||||
}
|
||||
outMsg.Write(GameMain.Version.ToString());
|
||||
outMsg.Write(GameSettings.CurrentConfig.Language.Value);
|
||||
|
||||
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
|
||||
PeerPacketHeaders headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.SteamTicketAndVersion
|
||||
};
|
||||
|
||||
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket
|
||||
{
|
||||
Name = GameMain.Client.Name,
|
||||
OwnerKey = ownerKey,
|
||||
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
|
||||
SteamAuthTicket = steamAuthTicket switch
|
||||
{
|
||||
null => Option<byte[]>.None(),
|
||||
var ticket => Option<byte[]>.Some(ticket.Data)
|
||||
},
|
||||
GameVersion = GameMain.Version.ToString(),
|
||||
Language = GameSettings.CurrentConfig.Language.Value
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
break;
|
||||
}
|
||||
case ConnectionInitialization.ContentPackageOrder:
|
||||
{
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
|
||||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
|
||||
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
|
||||
outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
|
||||
|
||||
UInt32 packageCount = inc.ReadVariableUInt32();
|
||||
List<ServerContentPackage> serverPackages = new List<ServerContentPackage>();
|
||||
for (int i = 0; i < packageCount; i++)
|
||||
initializationStep == ConnectionInitialization.Password)
|
||||
{
|
||||
string name = inc.ReadString();
|
||||
UInt32 hashByteCount = inc.ReadVariableUInt32();
|
||||
byte[] hashBytes = inc.ReadBytes((int)hashByteCount);
|
||||
UInt64 workshopId = inc.ReadUInt64();
|
||||
UInt32 installTimeDiffSeconds = inc.ReadUInt32();
|
||||
DateTime installTime = DateTime.UtcNow + TimeSpan.FromSeconds(installTimeDiffSeconds);
|
||||
|
||||
var pkg = new ServerContentPackage(name, Md5Hash.BytesAsHash(hashBytes), workshopId, installTime);
|
||||
serverPackages.Add(pkg);
|
||||
initializationStep = ConnectionInitialization.ContentPackageOrder;
|
||||
}
|
||||
|
||||
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
|
||||
|
||||
PeerPacketHeaders headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.ContentPackageOrder
|
||||
};
|
||||
|
||||
var orderPacket = INetSerializableStruct.Read<ServerPeerContentPackageOrderPacket>(inc.Message);
|
||||
|
||||
if (!ContentPackageOrderReceived)
|
||||
{
|
||||
ServerContentPackages = serverPackages.ToImmutableArray();
|
||||
if (serverPackages.Count == 0)
|
||||
ServerContentPackages = orderPacket.ContentPackages;
|
||||
if (ServerContentPackages.Length == 0)
|
||||
{
|
||||
string errorMsg = "Error in ContentPackageOrder message: list of content packages enabled on the server was empty.";
|
||||
GameAnalyticsManager.AddErrorEventOnce("ClientPeer.ReadConnectionInitializationStep:NoContentPackages", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
}
|
||||
ContentPackageOrderReceived = true;
|
||||
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
|
||||
|
||||
SendMsgInternal(headers, null);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ConnectionInitialization.Password:
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
|
||||
{
|
||||
initializationStep = ConnectionInitialization.Password;
|
||||
}
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password) { return; }
|
||||
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
|
||||
int retries = 0;
|
||||
if (incomingSalt)
|
||||
{
|
||||
passwordSalt = inc.ReadInt32();
|
||||
}
|
||||
else
|
||||
{
|
||||
retries = inc.ReadInt32();
|
||||
}
|
||||
|
||||
var passwordPacket = INetSerializableStruct.Read<ServerPeerPasswordPacket>(inc.Message);
|
||||
|
||||
passwordPacket.Salt.TryUnwrap(out passwordSalt);
|
||||
passwordPacket.RetriesLeft.TryUnwrap(out var retries);
|
||||
|
||||
callbacks.OnRequestPassword.Invoke(passwordSalt, retries);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
using Barotrauma.Steam;
|
||||
using Lidgren.Network;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Lidgren.Network;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class LidgrenClientPeer : ClientPeer
|
||||
internal sealed class LidgrenClientPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
private NetClient netClient;
|
||||
private NetPeerConfiguration netPeerConfiguration;
|
||||
private NetClient? netClient;
|
||||
private readonly NetPeerConfiguration netPeerConfiguration;
|
||||
|
||||
List<NetIncomingMessage> incomingLidgrenMessages;
|
||||
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
|
||||
|
||||
private LidgrenEndpoint lidgrenEndpoint
|
||||
=> ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
|
||||
private LidgrenEndpoint lidgrenEndpoint =>
|
||||
ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
|
||||
? result
|
||||
: throw new InvalidOperationException();
|
||||
|
||||
@@ -25,13 +26,6 @@ namespace Barotrauma.Networking
|
||||
|
||||
netClient = null;
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (isActive) { return; }
|
||||
|
||||
ContentPackageOrderReceived = false;
|
||||
|
||||
netPeerConfiguration = new NetPeerConfiguration("barotrauma")
|
||||
{
|
||||
@@ -45,6 +39,17 @@ namespace Barotrauma.Networking
|
||||
| NetIncomingMessageType.ErrorMessage
|
||||
| NetIncomingMessageType.Error);
|
||||
|
||||
incomingLidgrenMessages = new List<NetIncomingMessage>();
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (isActive) { return; }
|
||||
|
||||
incomingLidgrenMessages.Clear();
|
||||
|
||||
ContentPackageOrderReceived = false;
|
||||
|
||||
netClient = new NetClient(netPeerConfiguration);
|
||||
|
||||
if (SteamManager.IsInitialized)
|
||||
@@ -56,14 +61,13 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
incomingLidgrenMessages = new List<NetIncomingMessage>();
|
||||
|
||||
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
|
||||
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpoint))
|
||||
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpointValue))
|
||||
{
|
||||
throw new InvalidCastException("endPoint is not IPEndPoint");
|
||||
throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}");
|
||||
}
|
||||
|
||||
if (ServerConnection != null)
|
||||
{
|
||||
throw new InvalidOperationException("ServerConnection is not null");
|
||||
@@ -71,7 +75,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
netClient.Start();
|
||||
|
||||
var netConnection = netClient.Connect(lidgrenEndpoint.NetEndpoint);
|
||||
var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
|
||||
|
||||
ServerConnection = new LidgrenConnection(netConnection)
|
||||
{
|
||||
@@ -85,11 +89,18 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
ToolBox.ThrowIfNull(incomingLidgrenMessages);
|
||||
|
||||
if (isOwner && !(ChildServerRelay.Process is { HasExited: false }))
|
||||
{
|
||||
Close();
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
||||
{
|
||||
GameMain.MainMenuScreen.Select();
|
||||
return false;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,7 +112,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
|
||||
{
|
||||
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint)) { continue; }
|
||||
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint))
|
||||
{
|
||||
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (inc.MessageType)
|
||||
{
|
||||
@@ -115,15 +130,23 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDataMessage(NetIncomingMessage inc)
|
||||
private void HandleDataMessage(NetIncomingMessage lidgrenMsg)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
|
||||
ToolBox.ThrowIfNull(ServerConnection);
|
||||
|
||||
IReadMessage inc = lidgrenMsg.ToReadMessage();
|
||||
|
||||
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false));
|
||||
ReadConnectionInitializationStep(new IncomingInitializationMessage
|
||||
{
|
||||
InitializationStep = initialization ?? throw new Exception("Initialization step missing"),
|
||||
Message = inc
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -132,9 +155,9 @@ namespace Barotrauma.Networking
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
UInt16 length = inc.ReadUInt16();
|
||||
IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, ServerConnection);
|
||||
callbacks.OnMessageReceived.Invoke(msg);
|
||||
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +165,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte();
|
||||
NetConnectionStatus status = inc.ReadHeader<NetConnectionStatus>();
|
||||
switch (status)
|
||||
{
|
||||
case NetConnectionStatus.Disconnected:
|
||||
@@ -157,29 +180,38 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password) { return; }
|
||||
NetOutgoingMessage outMsg = netClient.CreateMessage();
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.Password);
|
||||
byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt);
|
||||
outMsg.Write((byte)saltedPw.Length);
|
||||
outMsg.Write(saltedPw, 0, saltedPw.Length);
|
||||
NetSendResult result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
|
||||
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
|
||||
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to send " + initializationStep.ToString() + " message to host: " + result);
|
||||
}
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.Password
|
||||
};
|
||||
var body = new ClientPeerPasswordPacket
|
||||
{
|
||||
Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt)
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
}
|
||||
|
||||
public override void Close(string msg = null, bool disableReconnect = false)
|
||||
public override void Close(string? msg = null, bool disableReconnect = false)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
|
||||
isActive = false;
|
||||
|
||||
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting").Value);
|
||||
netClient = null;
|
||||
steamAuthTicket?.Cancel(); steamAuthTicket = null;
|
||||
|
||||
steamAuthTicket?.Cancel();
|
||||
steamAuthTicket = null;
|
||||
|
||||
callbacks.OnDisconnect.Invoke(disableReconnect);
|
||||
}
|
||||
|
||||
@@ -187,19 +219,8 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
|
||||
switch (deliveryMethod)
|
||||
{
|
||||
case DeliveryMethod.Unreliable:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
|
||||
break;
|
||||
case DeliveryMethod.Reliable:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
|
||||
break;
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
|
||||
break;
|
||||
}
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
ToolBox.ThrowIfNull(netPeerConfiguration);
|
||||
|
||||
#if DEBUG
|
||||
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
|
||||
@@ -208,30 +229,42 @@ namespace Barotrauma.Networking
|
||||
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
|
||||
#endif
|
||||
|
||||
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
|
||||
byte[] msgData = new byte[msg.LengthBytes];
|
||||
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
|
||||
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
|
||||
lidgrenMsg.Write((UInt16)length);
|
||||
lidgrenMsg.Write(msgData, 0, length);
|
||||
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
|
||||
NetSendResult result = netClient.SendMessage(lidgrenMsg, lidgrenDeliveryMethod);
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
|
||||
Initialization = null
|
||||
};
|
||||
var body = new PeerPacketMessage
|
||||
{
|
||||
Buffer = bufAux
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.WriteNetSerializableStruct(headers);
|
||||
body?.Write(msg);
|
||||
|
||||
NetSendResult result = ForwardToLidgren(msg, DeliveryMethod.Reliable);
|
||||
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to send message to host: " + result);
|
||||
DebugConsole.NewMessage($"Failed to send message to host: {result}\n{Environment.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
|
||||
private NetSendResult ForwardToLidgren(IWriteMessage msg, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
|
||||
lidgrenMsg.Write(msg.Buffer, 0, msg.LengthBytes);
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
|
||||
NetSendResult result = netClient.SendMessage(lidgrenMsg, NetDeliveryMethod.ReliableUnordered);
|
||||
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to send message to host: " + result + "\n" + Environment.StackTrace);
|
||||
}
|
||||
return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren());
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using Barotrauma.Steam;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.Steam;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class SteamP2PClientPeer : ClientPeer
|
||||
internal sealed class SteamP2PClientPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
private readonly SteamId hostSteamId;
|
||||
@@ -17,8 +18,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
private long sentBytes, receivedBytes;
|
||||
|
||||
private List<IReadMessage> incomingInitializationMessages;
|
||||
private List<IReadMessage> incomingDataMessages;
|
||||
private readonly List<IncomingInitializationMessage> incomingInitializationMessages = new List<IncomingInitializationMessage>();
|
||||
private readonly List<IReadMessage> incomingDataMessages = new List<IReadMessage>();
|
||||
|
||||
public SteamP2PClientPeer(SteamP2PEndpoint endpoint, Callbacks callbacks) : base(endpoint, callbacks, Option<int>.None())
|
||||
{
|
||||
@@ -55,16 +56,13 @@ namespace Barotrauma.Networking
|
||||
ServerConnection = new SteamP2PConnection(hostSteamId);
|
||||
ServerConnection.SetAccountInfo(new AccountInfo(hostSteamId));
|
||||
|
||||
incomingInitializationMessages = new List<IReadMessage>();
|
||||
incomingDataMessages = new List<IReadMessage>();
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)DeliveryMethod.Reliable);
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.ConnectionStarted);
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.ConnectionStarted
|
||||
};
|
||||
SendMsgInternal(headers, null);
|
||||
|
||||
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
|
||||
@@ -78,6 +76,7 @@ namespace Barotrauma.Networking
|
||||
private void OnIncomingConnection(Steamworks.SteamId steamId)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (steamId == hostSteamId.Value)
|
||||
{
|
||||
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId);
|
||||
@@ -86,16 +85,18 @@ namespace Barotrauma.Networking
|
||||
initializationStep != ConnectionInitialization.ContentPackageOrder &&
|
||||
initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
DebugConsole.ThrowError($"Connection from incorrect SteamID was rejected: "+
|
||||
$"expected {hostSteamId}," +
|
||||
$"got {new SteamId(steamId)}");
|
||||
DebugConsole.ThrowError("Connection from incorrect SteamID was rejected: " +
|
||||
$"expected {hostSteamId}," +
|
||||
$"got {new SteamId(steamId)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnectionFailed(Steamworks.SteamId steamId, Steamworks.P2PSessionError error)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (steamId != hostSteamId.Value) { return; }
|
||||
|
||||
Close($"SteamP2P connection failed: {error}");
|
||||
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P connection failed: {error}");
|
||||
}
|
||||
@@ -103,27 +104,31 @@ namespace Barotrauma.Networking
|
||||
private void OnP2PData(ulong steamId, byte[] data, int dataLength)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (steamId != hostSteamId.Value) { return; }
|
||||
|
||||
timeout = Screen.Selected == GameMain.GameScreen ?
|
||||
NetworkConnection.TimeoutThresholdInGame :
|
||||
NetworkConnection.TimeoutThreshold;
|
||||
timeout = Screen.Selected == GameMain.GameScreen
|
||||
? NetworkConnection.TimeoutThresholdInGame
|
||||
: NetworkConnection.TimeoutThreshold;
|
||||
|
||||
PacketHeader packetHeader = (PacketHeader)data[0];
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
|
||||
|
||||
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (!packetHeader.IsServerMessage()) { return; }
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
if (packetHeader.IsConnectionInitializationStep() && initialization.HasValue)
|
||||
{
|
||||
ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8);
|
||||
ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8 + 32);
|
||||
ulong lobbyId = low + (high << 32);
|
||||
var relayPacket = INetSerializableStruct.Read<SteamP2PInitializationRelayPacket>(inc);
|
||||
|
||||
Steam.SteamManager.JoinLobby(lobbyId, false);
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - (1 + 8), ServerConnection);
|
||||
SteamManager.JoinLobby(relayPacket.LobbyID, false);
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
incomingInitializationMessages.Add(inc);
|
||||
incomingInitializationMessages.Add(new IncomingInitializationMessage
|
||||
{
|
||||
InitializationStep = initialization.Value,
|
||||
Message = relayPacket.Message.GetReadMessage()
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (packetHeader.IsHeartbeatMessage())
|
||||
@@ -132,17 +137,14 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection);
|
||||
string msg = inc.ReadString();
|
||||
Close(msg);
|
||||
callbacks.OnDisconnectMessageReceived.Invoke(msg);
|
||||
PeerDisconnectPacket packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||
Close(packet.Message);
|
||||
callbacks.OnDisconnectMessageReceived.Invoke(packet.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(data, 16, 8);
|
||||
|
||||
IReadMessage inc = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 3, length, ServerConnection);
|
||||
incomingDataMessages.Add(inc);
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
incomingDataMessages.Add(packet.GetReadMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +156,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
timeout -= deltaTime;
|
||||
}
|
||||
|
||||
heartbeatTimer -= deltaTime;
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password &&
|
||||
@@ -163,20 +166,20 @@ namespace Barotrauma.Networking
|
||||
connectionStatusTimer -= deltaTime;
|
||||
if (connectionStatusTimer <= 0.0)
|
||||
{
|
||||
var state = Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value);
|
||||
if (state == null)
|
||||
if (Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value) is { } state)
|
||||
{
|
||||
if (state.P2PSessionError != Steamworks.P2PSessionError.None)
|
||||
{
|
||||
Close($"SteamP2P error code: {state.P2PSessionError}");
|
||||
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state.P2PSessionError}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Close("SteamP2P connection could not be established");
|
||||
callbacks.OnDisconnectMessageReceived.Invoke(DisconnectReason.SteamP2PError.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state?.P2PSessionError != Steamworks.P2PSessionError.None)
|
||||
{
|
||||
Close($"SteamP2P error code: {state?.P2PSessionError}");
|
||||
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state?.P2PSessionError}");
|
||||
}
|
||||
}
|
||||
|
||||
connectionStatusTimer = 1.0f;
|
||||
}
|
||||
}
|
||||
@@ -184,11 +187,12 @@ namespace Barotrauma.Networking
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
|
||||
|
||||
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
|
||||
if (packet.HasValue)
|
||||
if (packet is { SteamId: var steamId, Data: var data })
|
||||
{
|
||||
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0);
|
||||
receivedBytes += packet?.Data.Length ?? 0;
|
||||
OnP2PData(steamId, data, data.Length);
|
||||
receivedBytes += data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,14 +201,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (heartbeatTimer < 0.0)
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)DeliveryMethod.Unreliable);
|
||||
outMsg.Write((byte)PacketHeader.IsHeartbeatMessage);
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Unreliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Unreliable,
|
||||
PacketHeader = PacketHeader.IsHeartbeatMessage,
|
||||
Initialization = null
|
||||
};
|
||||
SendMsgInternal(headers, null);
|
||||
}
|
||||
|
||||
if (timeout < 0.0)
|
||||
@@ -238,7 +241,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (IReadMessage inc in incomingInitializationMessages)
|
||||
foreach (var inc in incomingInitializationMessages)
|
||||
{
|
||||
ReadConnectionInitializationStep(inc);
|
||||
}
|
||||
@@ -261,76 +264,40 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
byte[] buf = new byte[msg.LengthBytes + 4];
|
||||
buf[0] = (byte)deliveryMethod;
|
||||
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
|
||||
byte[] bufAux = new byte[msg.LengthBytes];
|
||||
msg.PrepareForSending(ref bufAux, compressPastThreshold, out bool isCompressed, out int length);
|
||||
|
||||
buf[1] = (byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None);
|
||||
|
||||
buf[2] = (byte)(length & 0xff);
|
||||
buf[3] = (byte)((length >> 8) & 0xff);
|
||||
|
||||
Array.Copy(bufAux, 0, buf, 4, length);
|
||||
|
||||
Steamworks.P2PSend sendType;
|
||||
switch (deliveryMethod)
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
case DeliveryMethod.Reliable:
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
//the documentation seems to suggest that the Reliable send type
|
||||
//enforces packet order (TODO: verify)
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
break;
|
||||
default:
|
||||
sendType = Steamworks.P2PSend.Unreliable;
|
||||
break;
|
||||
}
|
||||
|
||||
if (length + 8 >= MsgConstants.MTU)
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
|
||||
Initialization = null
|
||||
};
|
||||
var body = new PeerPacketMessage
|
||||
{
|
||||
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + length.ToString() + " bytes)");
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
}
|
||||
Buffer = bufAux
|
||||
};
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
|
||||
// Using an extra local method here to reduce chance of error whenever we need to change this
|
||||
void performSend() => SendMsgInternal(headers, body);
|
||||
#if DEBUG
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (GameMain.Client == null) { return; }
|
||||
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; }
|
||||
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Send(buf, length + 4, sendType);
|
||||
}
|
||||
},
|
||||
GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency));
|
||||
#else
|
||||
Send(buf, length + 4, sendType);
|
||||
#endif
|
||||
}
|
||||
if (GameMain.Client == null) { return; }
|
||||
|
||||
private void Send(byte[] buf, int length, Steamworks.P2PSend sendType)
|
||||
{
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, buf, length + 4, 0, sendType);
|
||||
sentBytes += length + 4;
|
||||
if (!successSend)
|
||||
{
|
||||
if (sendType != Steamworks.P2PSend.Reliable)
|
||||
{
|
||||
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + length.ToString() + " bytes)");
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, buf, length + 4, 0, sendType);
|
||||
sentBytes += length + 4;
|
||||
}
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning("Failed to send message to remote peer! (" + length.ToString() + " bytes)");
|
||||
}
|
||||
}
|
||||
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && deliveryMethod is DeliveryMethod.Unreliable) { return; }
|
||||
|
||||
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
performSend();
|
||||
}
|
||||
},
|
||||
GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency));
|
||||
#else
|
||||
performSend();
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void SendPassword(string password)
|
||||
@@ -338,20 +305,22 @@ namespace Barotrauma.Networking
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password) { return; }
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)DeliveryMethod.Reliable);
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.Password);
|
||||
byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt);
|
||||
outMsg.Write((byte)saltedPw.Length);
|
||||
outMsg.Write(saltedPw, 0, saltedPw.Length);
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.Password
|
||||
};
|
||||
var body = new ClientPeerPasswordPacket
|
||||
{
|
||||
Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt)
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
}
|
||||
|
||||
public override void Close(string msg = null, bool disableReconnect = false)
|
||||
public override void Close(string? msg = null, bool disableReconnect = false)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
@@ -359,54 +328,59 @@ namespace Barotrauma.Networking
|
||||
|
||||
isActive = false;
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)DeliveryMethod.Reliable);
|
||||
outMsg.Write((byte)PacketHeader.IsDisconnectMessage);
|
||||
outMsg.Write(msg ?? "Disconnected");
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsDisconnectMessage,
|
||||
Initialization = null
|
||||
};
|
||||
var body = new PeerDisconnectPacket
|
||||
{
|
||||
Message = msg ?? "Disconnected"
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to send a disconnect message to the server using SteamP2P.", e);
|
||||
}
|
||||
SendMsgInternal(headers, body);
|
||||
|
||||
Thread.Sleep(100);
|
||||
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId.Value);
|
||||
|
||||
steamAuthTicket?.Cancel(); steamAuthTicket = null;
|
||||
steamAuthTicket?.Cancel();
|
||||
steamAuthTicket = null;
|
||||
|
||||
callbacks.OnDisconnect.Invoke(disableReconnect);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
Steamworks.P2PSend sendType;
|
||||
switch (deliveryMethod)
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
msgToSend.WriteNetSerializableStruct(headers);
|
||||
body?.Write(msgToSend);
|
||||
ForwardToSteamP2P(msgToSend, headers.DeliveryMethod);
|
||||
}
|
||||
|
||||
private void ForwardToSteamP2P(IWriteMessage msg, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
heartbeatTimer = 5.0;
|
||||
int length = msg.LengthBytes;
|
||||
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, deliveryMethod.ToSteam());
|
||||
sentBytes += length;
|
||||
|
||||
if (successSend) { return; }
|
||||
|
||||
if (deliveryMethod is DeliveryMethod.Unreliable)
|
||||
{
|
||||
case DeliveryMethod.Reliable:
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
//the documentation seems to suggest that the Reliable send type
|
||||
//enforces packet order (TODO: verify)
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
break;
|
||||
default:
|
||||
sendType = Steamworks.P2PSend.Unreliable;
|
||||
break;
|
||||
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, DeliveryMethod.Reliable.ToSteam());
|
||||
sentBytes += length;
|
||||
}
|
||||
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
msgToSend.Write((byte)deliveryMethod);
|
||||
msgToSend.Write(msg.Buffer, 0, msg.LengthBytes);
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msgToSend.Buffer, msgToSend.LengthBytes, 0, sendType);
|
||||
sentBytes += msg.LengthBytes;
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
@@ -1,38 +1,46 @@
|
||||
using Barotrauma.Extensions;
|
||||
#nullable enable
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class SteamP2POwnerPeer : ClientPeer
|
||||
sealed class SteamP2POwnerPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
|
||||
private readonly SteamId selfSteamID;
|
||||
private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
|
||||
|
||||
private SteamId ReadSteamId(IReadMessage inc)
|
||||
=> new SteamId(inc.ReadUInt64() ^ ownerKey64);
|
||||
private void WriteSteamId(IWriteMessage msg, SteamId val)
|
||||
=> msg.Write(val.Value ^ ownerKey64);
|
||||
private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64);
|
||||
private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64);
|
||||
|
||||
private long sentBytes, receivedBytes;
|
||||
|
||||
class RemotePeer
|
||||
private sealed class RemotePeer
|
||||
{
|
||||
public SteamId SteamId;
|
||||
public readonly SteamId SteamId;
|
||||
public Option<SteamId> OwnerSteamId;
|
||||
public double? DisconnectTime;
|
||||
public bool Authenticating;
|
||||
public bool Authenticated;
|
||||
|
||||
public class UnauthedMessage
|
||||
public readonly struct UnauthedMessage
|
||||
{
|
||||
public DeliveryMethod DeliveryMethod;
|
||||
public IWriteMessage Message;
|
||||
public readonly SteamId Sender;
|
||||
public readonly byte[] Bytes;
|
||||
public readonly int Length;
|
||||
|
||||
public UnauthedMessage(SteamId sender, byte[] bytes)
|
||||
{
|
||||
Sender = sender;
|
||||
Bytes = bytes;
|
||||
Length = bytes.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<UnauthedMessage> UnauthedMessages;
|
||||
|
||||
public RemotePeer(SteamId steamId)
|
||||
@@ -45,9 +53,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
UnauthedMessages = new List<UnauthedMessage>();
|
||||
}
|
||||
|
||||
}
|
||||
List<RemotePeer> remotePeers;
|
||||
|
||||
private List<RemotePeer> remotePeers = null!;
|
||||
|
||||
public SteamP2POwnerPeer(Callbacks callbacks, int ownerKey) : base(new PipeEndpoint(), callbacks, Option<int>.Some(ownerKey))
|
||||
{
|
||||
@@ -84,8 +92,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
|
||||
{
|
||||
RemotePeer remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
DebugConsole.Log(steamId + " validation: " + status + ", " + (remotePeer != null));
|
||||
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
DebugConsole.Log($"{steamId} validation: {status}, {remotePeer != null}");
|
||||
|
||||
if (remotePeer == null) { return; }
|
||||
|
||||
@@ -93,36 +101,32 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (status != Steamworks.AuthResponse.OK)
|
||||
{
|
||||
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
|
||||
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication status changed: {status}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == Steamworks.AuthResponse.OK)
|
||||
{
|
||||
remotePeer.OwnerSteamId = Option<SteamId>.Some(new SteamId(ownerId));
|
||||
SteamId ownerSteamId = new SteamId(ownerId);
|
||||
remotePeer.OwnerSteamId = Option<SteamId>.Some(ownerSteamId);
|
||||
remotePeer.Authenticated = true;
|
||||
remotePeer.Authenticating = false;
|
||||
foreach (var msg in remotePeer.UnauthedMessages)
|
||||
foreach (var unauthedMessage in remotePeer.UnauthedMessages)
|
||||
{
|
||||
//rewrite the owner id before
|
||||
//forwarding the messages to
|
||||
//the server, since it's only
|
||||
//known now
|
||||
int prevBitPosition = msg.Message.BitPosition;
|
||||
msg.Message.BitPosition = sizeof(ulong) * 8;
|
||||
WriteSteamId(msg.Message, new SteamId(ownerId));
|
||||
msg.Message.BitPosition = prevBitPosition;
|
||||
byte[] msgToSend = (byte[])msg.Message.Buffer.Clone();
|
||||
Array.Resize(ref msgToSend, msg.Message.LengthBytes);
|
||||
ChildServerRelay.Write(msgToSend);
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
WriteSteamId(msg, unauthedMessage.Sender);
|
||||
WriteSteamId(msg, ownerSteamId);
|
||||
msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.Length);
|
||||
ForwardToServerProcess(msg);
|
||||
}
|
||||
|
||||
remotePeer.UnauthedMessages.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString());
|
||||
return;
|
||||
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication failed: {status}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,58 +142,54 @@ namespace Barotrauma.Networking
|
||||
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); //accept all connections, the server will figure things out later
|
||||
}
|
||||
|
||||
private void OnP2PData(ulong steamId, byte[] data, int dataLength, int _)
|
||||
private void OnP2PData(ulong steamId, IReadMessage inc)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
RemotePeer remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
if (remotePeer == null) { return; }
|
||||
|
||||
if (remotePeer.DisconnectTime != null) { return; }
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
var steamUserId = new SteamId(steamId);
|
||||
WriteSteamId(outMsg, steamUserId);
|
||||
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
|
||||
outMsg.Write(data, 1, dataLength - 1);
|
||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
|
||||
|
||||
PacketHeader packetHeader = (PacketHeader)data[1];
|
||||
PacketHeader packetHeader = peerPacketHeaders.PacketHeader;
|
||||
|
||||
if (!remotePeer.Authenticated && !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
remotePeer.DisconnectTime = null;
|
||||
|
||||
IReadMessage authMsg = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 2, dataLength - 2, null);
|
||||
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
|
||||
ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing");
|
||||
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
|
||||
{
|
||||
remotePeer.Authenticating = true;
|
||||
|
||||
authMsg.ReadString(); //skip name
|
||||
authMsg.ReadInt32(); //skip owner key
|
||||
authMsg.ReadUInt64(); //skip steamid
|
||||
UInt16 ticketLength = authMsg.ReadUInt16();
|
||||
byte[] ticket = authMsg.ReadBytes(ticketLength);
|
||||
var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
|
||||
|
||||
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
|
||||
packet.SteamAuthTicket.TryUnwrap(out byte[] ticket);
|
||||
|
||||
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
|
||||
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
|
||||
{
|
||||
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
|
||||
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam auth session failed to start: {authSessionStartState}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var steamUserId = new SteamId(steamId);
|
||||
if (remotePeer.Authenticating)
|
||||
{
|
||||
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage() { DeliveryMethod = deliveryMethod, Message = outMsg });
|
||||
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(steamUserId, inc.Buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
|
||||
Array.Resize(ref msgToSend, outMsg.LengthBytes);
|
||||
ChildServerRelay.Write(msgToSend);
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, steamUserId);
|
||||
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
|
||||
outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
|
||||
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
Close();
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
||||
{
|
||||
GameMain.MainMenuScreen.Select();
|
||||
return false;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -216,11 +220,12 @@ namespace Barotrauma.Networking
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
|
||||
|
||||
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
|
||||
if (packet.HasValue)
|
||||
if (packet is { SteamId: var steamId, Data: var data })
|
||||
{
|
||||
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0, 0);
|
||||
receivedBytes += packet?.Data.Length ?? 0;
|
||||
OnP2PData(steamId, new ReadWriteMessage(data, 0, data.Length * 8, false));
|
||||
receivedBytes += data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,139 +245,124 @@ namespace Barotrauma.Networking
|
||||
if (!isActive) { return; }
|
||||
|
||||
SteamId recipientSteamId = ReadSteamId(inc);
|
||||
DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte();
|
||||
|
||||
int p2pDataStart = inc.BytePosition;
|
||||
|
||||
PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
|
||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (recipientSteamId != selfSteamID)
|
||||
{
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message meant for remote peer");
|
||||
return;
|
||||
}
|
||||
|
||||
RemotePeer peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
|
||||
|
||||
if (peer == null) { return; }
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
DisconnectPeer(peer, inc.ReadString());
|
||||
return;
|
||||
}
|
||||
|
||||
Steamworks.P2PSend sendType;
|
||||
switch (deliveryMethod)
|
||||
{
|
||||
case DeliveryMethod.Reliable:
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
//the documentation seems to suggest that the
|
||||
//Reliable send type enforces packet order
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
break;
|
||||
default:
|
||||
sendType = Steamworks.P2PSend.Unreliable;
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] p2pData;
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
p2pData = new byte[inc.LengthBytes - p2pDataStart + 8];
|
||||
p2pData[0] = inc.Buffer[p2pDataStart];
|
||||
Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 8 * 8, p2pData, 1 * 8);
|
||||
Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 1 + 8, inc.LengthBytes - p2pDataStart - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
p2pData = new byte[inc.LengthBytes - p2pDataStart];
|
||||
Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length);
|
||||
|
||||
if (!packetHeader.IsHeartbeatMessage() && !packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(p2pData, 16, 8);
|
||||
if (length > p2pData.Length - 2)
|
||||
{
|
||||
string errorMsg = $"Length written in message to send to client is larger than buffer size ({length} > {p2pData.Length - 2})";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"SteamP2POwnerPeerLengthValidationFail",
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p2pData.Length + 4 >= MsgConstants.MTU)
|
||||
{
|
||||
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
}
|
||||
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId.Value, p2pData, p2pData.Length, 0, sendType);
|
||||
sentBytes += p2pData.Length;
|
||||
|
||||
if (!successSend)
|
||||
{
|
||||
if (sendType != Steamworks.P2PSend.Reliable)
|
||||
{
|
||||
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId.Value, p2pData, p2pData.Length, 0, sendType);
|
||||
sentBytes += p2pData.Length;
|
||||
}
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)");
|
||||
}
|
||||
}
|
||||
HandleMessageForRemotePeer(peerPacketHeaders, recipientSteamId, inc);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received disconnect message from owned server");
|
||||
return;
|
||||
}
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message from owned server");
|
||||
return;
|
||||
}
|
||||
if (packetHeader.IsHeartbeatMessage())
|
||||
{
|
||||
return; //no timeout since we're using pipes, ignore this message
|
||||
}
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep));
|
||||
outMsg.Write(GameMain.Client.Name);
|
||||
HandleMessageForOwner(peerPacketHeaders, inc);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
|
||||
Array.Resize(ref msgToSend, outMsg.LengthBytes);
|
||||
ChildServerRelay.Write(msgToSend);
|
||||
return;
|
||||
}
|
||||
else
|
||||
private static byte[] GetRemainingBytes(IReadMessage msg)
|
||||
{
|
||||
return msg.Buffer[msg.BytePosition..msg.LengthBytes];
|
||||
}
|
||||
|
||||
private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, SteamId recipientSteamId, IReadMessage inc)
|
||||
{
|
||||
var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders;
|
||||
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message meant for remote peer");
|
||||
return;
|
||||
}
|
||||
|
||||
RemotePeer? peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
|
||||
if (peer is null) { return; }
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||
DisconnectPeer(peer, packet.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = packetHeader,
|
||||
Initialization = initialization
|
||||
});
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
var initRelayPacket = new SteamP2PInitializationRelayPacket
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
LobbyID = SteamManager.CurrentLobbyID,
|
||||
Message = new PeerPacketMessage
|
||||
{
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
Buffer = GetRemainingBytes(inc)
|
||||
}
|
||||
UInt16 length = inc.ReadUInt16();
|
||||
IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, ServerConnection);
|
||||
callbacks.OnMessageReceived.Invoke(msg);
|
||||
};
|
||||
|
||||
return;
|
||||
outMsg.WriteNetSerializableStruct(initRelayPacket);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] userMessage = GetRemainingBytes(inc);
|
||||
outMsg.WriteBytes(userMessage, 0, userMessage.Length);
|
||||
}
|
||||
|
||||
ForwardToRemotePeer(deliveryMethod, recipientSteamId, outMsg);
|
||||
}
|
||||
|
||||
private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc)
|
||||
{
|
||||
var (_, packetHeader, _) = peerPacketHeaders;
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received disconnect message from owned server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message from owned server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (packetHeader.IsHeartbeatMessage())
|
||||
{
|
||||
return; //no timeout since we're using pipes, ignore this message
|
||||
}
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.SteamTicketAndVersion
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(new SteamP2PInitializationOwnerPacket
|
||||
{
|
||||
OwnerName = GameMain.Client.Name
|
||||
});
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
|
||||
PeerPacketMessage packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection);
|
||||
callbacks.OnMessageReceived.Invoke(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,14 +370,18 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(msg))
|
||||
{
|
||||
if (peer.DisconnectTime == null)
|
||||
{
|
||||
peer.DisconnectTime = Timing.TotalTime + 1.0;
|
||||
}
|
||||
peer.DisconnectTime ??= Timing.TotalTime + 1.0;
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)(PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage));
|
||||
outMsg.Write(msg);
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(new PeerDisconnectPacket
|
||||
{
|
||||
Message = msg
|
||||
});
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
@@ -406,10 +400,10 @@ namespace Barotrauma.Networking
|
||||
|
||||
public override void SendPassword(string password)
|
||||
{
|
||||
return; //owner doesn't send passwords
|
||||
//owner doesn't send passwords
|
||||
}
|
||||
|
||||
public override void Close(string msg = null, bool disableReconnect = false)
|
||||
public override void Close(string? msg = null, bool disableReconnect = false)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
@@ -441,25 +435,62 @@ namespace Barotrauma.Networking
|
||||
if (!isActive) { return; }
|
||||
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
byte[] msgData = new byte[msg.LengthBytes];
|
||||
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
|
||||
byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
WriteSteamId(msgToSend, selfSteamID);
|
||||
WriteSteamId(msgToSend, selfSteamID);
|
||||
msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
|
||||
msgToSend.Write((UInt16)length);
|
||||
msgToSend.Write(msgData, 0, length);
|
||||
|
||||
byte[] bufToSend = (byte[])msgToSend.Buffer.Clone();
|
||||
Array.Resize(ref bufToSend, msgToSend.LengthBytes);
|
||||
ChildServerRelay.Write(bufToSend);
|
||||
msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None
|
||||
});
|
||||
msgToSend.WriteNetSerializableStruct(new PeerPacketMessage
|
||||
{
|
||||
Buffer = msgData
|
||||
});
|
||||
ForwardToServerProcess(msgToSend);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
//not currently used by SteamP2POwnerPeer
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static void ForwardToServerProcess(IWriteMessage msg)
|
||||
{
|
||||
byte[] bufToSend = new byte[msg.LengthBytes];
|
||||
msg.Buffer[..msg.LengthBytes].CopyTo(bufToSend.AsSpan());
|
||||
ChildServerRelay.Write(bufToSend);
|
||||
}
|
||||
|
||||
private void ForwardToRemotePeer(DeliveryMethod deliveryMethod, SteamId recipent, IWriteMessage outMsg)
|
||||
{
|
||||
byte[] buf = outMsg.PrepareForSending(compressPastThreshold: false, out _, out int length);
|
||||
|
||||
if (length + 4 >= MsgConstants.MTU)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
|
||||
deliveryMethod = DeliveryMethod.Reliable;
|
||||
}
|
||||
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, deliveryMethod.ToSteam());
|
||||
sentBytes += length;
|
||||
|
||||
if (successSend) { return; }
|
||||
|
||||
if (deliveryMethod is DeliveryMethod.Unreliable)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, DeliveryMethod.Reliable.ToSteam());
|
||||
sentBytes += length;
|
||||
}
|
||||
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override void ForceTimeOut()
|
||||
{
|
||||
|
||||
@@ -50,8 +50,13 @@ namespace Barotrauma.Networking
|
||||
respawnPromptCoroutine = CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (Character.Controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; }
|
||||
|
||||
LocalizedString text =
|
||||
TextManager.GetWithVariable("respawnskillpenalty", "[percentage]", ((int)(SkillReductionOnDeath * 100)).ToString())
|
||||
+ "\n\n" + TextManager.Get("respawnquestionprompt");
|
||||
|
||||
var respawnPrompt = new GUIMessageBox(
|
||||
TextManager.Get("tutorial.tryagainheader"), TextManager.Get("respawnquestionprompt"),
|
||||
TextManager.Get("tutorial.tryagainheader"), text,
|
||||
new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") })
|
||||
{
|
||||
UserData = "respawnquestionprompt"
|
||||
|
||||
@@ -348,7 +348,7 @@ namespace Barotrauma.Networking
|
||||
ServerName = element.GetAttributeString("ServerName", ""),
|
||||
ServerMessage = element.GetAttributeString("ServerMessage", ""),
|
||||
Endpoint = endpoint,
|
||||
QueryPort = element.GetAttributeInt("QueryPort", 0),
|
||||
QueryPort = !string.IsNullOrEmpty(element.GetAttributeString("QueryPort", string.Empty)) ? element.GetAttributeInt("QueryPort", 0) : 0,
|
||||
GameMode = element.GetAttributeIdentifier("GameMode", Identifier.Empty),
|
||||
GameVersion = element.GetAttributeString("GameVersion", ""),
|
||||
MaxPlayers = Math.Min(element.GetAttributeInt("MaxPlayers", 0), NetConfig.MaxPlayers),
|
||||
|
||||
@@ -182,9 +182,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
outMsg.Write((byte)ClientPacketHeader.SERVER_SETTINGS);
|
||||
outMsg.WriteByte((byte)ClientPacketHeader.SERVER_SETTINGS);
|
||||
|
||||
outMsg.Write((byte)dataToSend);
|
||||
outMsg.WriteByte((byte)dataToSend);
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.Name))
|
||||
{
|
||||
@@ -192,7 +192,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
ServerName = GameMain.NetLobbyScreen.ServerName.Text;
|
||||
}
|
||||
outMsg.Write(ServerName);
|
||||
outMsg.WriteString(ServerName);
|
||||
}
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.Message))
|
||||
@@ -201,7 +201,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
ServerMessageText = GameMain.NetLobbyScreen.ServerMessage.Text;
|
||||
}
|
||||
outMsg.Write(ServerMessageText);
|
||||
outMsg.WriteString(ServerMessageText);
|
||||
}
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.Properties))
|
||||
@@ -213,15 +213,15 @@ namespace Barotrauma.Networking
|
||||
UInt32 count = (UInt32)changedProperties.Count();
|
||||
bool changedMonsterSettings = tempMonsterEnabled != null && tempMonsterEnabled.Any(p => p.Value != MonsterEnabled[p.Key]);
|
||||
|
||||
outMsg.Write(count);
|
||||
outMsg.WriteUInt32(count);
|
||||
foreach (KeyValuePair<UInt32, NetPropertyData> prop in changedProperties)
|
||||
{
|
||||
DebugConsole.NewMessage(prop.Value.Name.Value, Color.Lime);
|
||||
outMsg.Write(prop.Key);
|
||||
outMsg.WriteUInt32(prop.Key);
|
||||
prop.Value.Write(outMsg, prop.Value.GUIComponentValue);
|
||||
}
|
||||
|
||||
outMsg.Write(changedMonsterSettings); outMsg.WritePadBits();
|
||||
outMsg.WriteBoolean(changedMonsterSettings); outMsg.WritePadBits();
|
||||
if (changedMonsterSettings) WriteMonsterEnabled(outMsg, tempMonsterEnabled);
|
||||
BanList.ClientAdminWrite(outMsg);
|
||||
}
|
||||
@@ -235,23 +235,23 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All);
|
||||
outMsg.WriteRangedInteger(missionTypeAnd ?? (int)Barotrauma.MissionType.All, 0, (int)Barotrauma.MissionType.All);
|
||||
outMsg.Write((byte)(traitorSetting + 1));
|
||||
outMsg.Write((byte)(botCount + 1));
|
||||
outMsg.Write((byte)(botSpawnMode + 1));
|
||||
outMsg.WriteByte((byte)(traitorSetting + 1));
|
||||
outMsg.WriteByte((byte)(botCount + 1));
|
||||
outMsg.WriteByte((byte)(botSpawnMode + 1));
|
||||
|
||||
outMsg.Write(levelDifficulty ?? -1000.0f);
|
||||
outMsg.WriteSingle(levelDifficulty ?? -1000.0f);
|
||||
|
||||
outMsg.Write(useRespawnShuttle ?? UseRespawnShuttle);
|
||||
outMsg.WriteBoolean(useRespawnShuttle ?? UseRespawnShuttle);
|
||||
|
||||
outMsg.Write(autoRestart != null);
|
||||
outMsg.Write(autoRestart ?? false);
|
||||
outMsg.WriteBoolean(autoRestart != null);
|
||||
outMsg.WriteBoolean(autoRestart ?? false);
|
||||
|
||||
outMsg.WritePadBits();
|
||||
}
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.LevelSeed))
|
||||
{
|
||||
outMsg.Write(GameMain.NetLobbyScreen.SeedBox.Text);
|
||||
outMsg.WriteString(GameMain.NetLobbyScreen.SeedBox.Text);
|
||||
}
|
||||
|
||||
GameMain.Client.ClientPeer.Send(outMsg, DeliveryMethod.Reliable);
|
||||
|
||||
@@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
|
||||
using OpenAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
@@ -22,13 +21,13 @@ namespace Barotrauma.Networking
|
||||
public static IReadOnlyList<string> CaptureDeviceNames =>
|
||||
Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
|
||||
|
||||
private IntPtr captureDevice;
|
||||
private readonly IntPtr captureDevice;
|
||||
|
||||
private Thread captureThread;
|
||||
|
||||
private bool capturing;
|
||||
|
||||
private OpusEncoder encoder;
|
||||
private readonly OpusEncoder encoder;
|
||||
|
||||
public double LastdB
|
||||
{
|
||||
@@ -171,8 +170,8 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
IntPtr nativeBuffer;
|
||||
short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||
short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||
readonly short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||
readonly short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||
bool prevCaptured = true;
|
||||
int captureTimer;
|
||||
|
||||
@@ -227,16 +226,20 @@ namespace Barotrauma.Networking
|
||||
bool allowEnqueue = overrideSound != null;
|
||||
if (GameMain.WindowActive && SettingsMenu.Instance is null)
|
||||
{
|
||||
bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice);
|
||||
bool usingLocalMode = PlayerInput.KeyDown(InputType.LocalVoice);
|
||||
bool usingRadioMode = PlayerInput.KeyDown(InputType.RadioVoice);
|
||||
bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
|
||||
if (pttDown || captureTimer <= 0)
|
||||
{
|
||||
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
|
||||
}
|
||||
if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.Activity)
|
||||
{
|
||||
bool pttDown = (usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
|
||||
if (pttDown)
|
||||
{
|
||||
ForceLocal = usingLocalMode;
|
||||
}
|
||||
//in Activity mode, we default to the active mode UNLESS a specific ptt key is held
|
||||
else
|
||||
{
|
||||
ForceLocal = GameMain.ActiveChatMode == ChatMode.Local;
|
||||
}
|
||||
if (dB > GameSettings.CurrentConfig.Audio.NoiseGateThreshold)
|
||||
{
|
||||
allowEnqueue = true;
|
||||
@@ -244,6 +247,13 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.PushToTalk)
|
||||
{
|
||||
//in push-to-talk mode, InputType.Voice uses the active chat mode
|
||||
bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice);
|
||||
bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
|
||||
if (pttDown || captureTimer <= 0)
|
||||
{
|
||||
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
|
||||
}
|
||||
if (pttDown)
|
||||
{
|
||||
allowEnqueue = true;
|
||||
|
||||
@@ -72,8 +72,8 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
|
||||
msg.Write((byte)ClientPacketHeader.VOICE);
|
||||
msg.Write((byte)VoipCapture.Instance.QueueID);
|
||||
msg.WriteByte((byte)ClientPacketHeader.VOICE);
|
||||
msg.WriteByte((byte)VoipCapture.Instance.QueueID);
|
||||
VoipCapture.Instance.Write(msg);
|
||||
|
||||
netClient.Send(msg, DeliveryMethod.Unreliable);
|
||||
|
||||
@@ -113,35 +113,35 @@ namespace Barotrauma
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, VoteType voteType, object data)
|
||||
{
|
||||
msg.Write((byte)voteType);
|
||||
msg.WriteByte((byte)voteType);
|
||||
|
||||
switch (voteType)
|
||||
{
|
||||
case VoteType.Sub:
|
||||
if (!(data is SubmarineInfo sub)) { return; }
|
||||
msg.Write(sub.EqualityCheckVal);
|
||||
msg.WriteInt32(sub.EqualityCheckVal);
|
||||
if (sub.EqualityCheckVal == 0)
|
||||
{
|
||||
//sub doesn't exist client-side, use hash to let the server know which one we voted for
|
||||
msg.Write(sub.MD5Hash.StringRepresentation);
|
||||
msg.WriteString(sub.MD5Hash.StringRepresentation);
|
||||
}
|
||||
break;
|
||||
case VoteType.Mode:
|
||||
if (!(data is GameModePreset gameMode)) { return; }
|
||||
msg.Write(gameMode.Identifier);
|
||||
msg.WriteIdentifier(gameMode.Identifier);
|
||||
break;
|
||||
case VoteType.EndRound:
|
||||
if (!(data is bool)) { return; }
|
||||
msg.Write((bool)data);
|
||||
msg.WriteBoolean((bool)data);
|
||||
break;
|
||||
case VoteType.Kick:
|
||||
if (!(data is Client votedClient)) { return; }
|
||||
|
||||
msg.Write(votedClient.SessionId);
|
||||
msg.WriteByte(votedClient.SessionId);
|
||||
break;
|
||||
case VoteType.StartRound:
|
||||
if (!(data is bool)) { return; }
|
||||
msg.Write((bool)data);
|
||||
msg.WriteBoolean((bool)data);
|
||||
break;
|
||||
case VoteType.PurchaseAndSwitchSub:
|
||||
case VoteType.PurchaseSub:
|
||||
@@ -149,22 +149,22 @@ namespace Barotrauma
|
||||
if (data is (SubmarineInfo voteSub, bool transferItems))
|
||||
{
|
||||
//initiate sub vote
|
||||
msg.Write(true);
|
||||
msg.Write(voteSub.Name);
|
||||
msg.Write(transferItems);
|
||||
msg.WriteBoolean(true);
|
||||
msg.WriteString(voteSub.Name);
|
||||
msg.WriteBoolean(transferItems);
|
||||
}
|
||||
else
|
||||
{
|
||||
// vote
|
||||
if (!(data is int)) { return; }
|
||||
msg.Write(false);
|
||||
msg.Write((int)data);
|
||||
msg.WriteBoolean(false);
|
||||
msg.WriteInt32((int)data);
|
||||
}
|
||||
break;
|
||||
case VoteType.TransferMoney:
|
||||
if (!(data is int)) { return; }
|
||||
msg.Write(false); //not initiating a vote
|
||||
msg.Write((int)data);
|
||||
msg.WriteBoolean(false); //not initiating a vote
|
||||
msg.WriteInt32((int)data);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class WhiteListedPlayer
|
||||
{
|
||||
public WhiteListedPlayer(string name, UInt16 identifier, string ip)
|
||||
{
|
||||
Name = name;
|
||||
IP = ip;
|
||||
|
||||
UniqueIdentifier = identifier;
|
||||
}
|
||||
}
|
||||
|
||||
partial class WhiteList
|
||||
{
|
||||
private GUIComponent whitelistFrame;
|
||||
|
||||
private GUITextBox nameBox;
|
||||
private GUITextBox ipBox;
|
||||
private GUIButton addNewButton;
|
||||
|
||||
public class LocalAdded
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
};
|
||||
|
||||
public bool localEnabled;
|
||||
public List<UInt16> localRemoved = new List<UInt16>();
|
||||
public List<LocalAdded> localAdded = new List<LocalAdded>();
|
||||
|
||||
public GUIComponent CreateWhiteListFrame(GUIComponent parent)
|
||||
{
|
||||
if (whitelistFrame != null)
|
||||
{
|
||||
whitelistFrame.Parent.ClearChildren();
|
||||
whitelistFrame = null;
|
||||
}
|
||||
|
||||
whitelistFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), parent.RectTransform, Anchor.Center))
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
|
||||
var enabledTick = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.1f), whitelistFrame.RectTransform), TextManager.Get("WhiteListEnabled"))
|
||||
{
|
||||
Selected = localEnabled,
|
||||
UpdateOrder = 1,
|
||||
OnSelected = (GUITickBox box) =>
|
||||
{
|
||||
nameBox.Enabled = box.Selected;
|
||||
ipBox.Enabled = box.Selected;
|
||||
addNewButton.Enabled = box.Selected && !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
|
||||
localEnabled = box.Selected;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), whitelistFrame.RectTransform));
|
||||
foreach (WhiteListedPlayer wlp in whitelistedPlayers)
|
||||
{
|
||||
if (localRemoved.Contains(wlp.UniqueIdentifier)) continue;
|
||||
string blockText = wlp.Name;
|
||||
if (!string.IsNullOrWhiteSpace(wlp.IP)) blockText += " (" + wlp.IP + ")";
|
||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform),
|
||||
blockText)
|
||||
{
|
||||
UserData = wlp
|
||||
};
|
||||
|
||||
var removeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), textBlock.RectTransform, Anchor.CenterRight),
|
||||
TextManager.Get("WhiteListRemove"), style: "GUIButtonSmall")
|
||||
{
|
||||
UserData = wlp,
|
||||
OnClicked = RemoveFromWhiteList
|
||||
};
|
||||
}
|
||||
|
||||
foreach (LocalAdded lad in localAdded)
|
||||
{
|
||||
string blockText = lad.Name;
|
||||
if (!string.IsNullOrWhiteSpace(lad.IP)) blockText += " (" + lad.IP + ")";
|
||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform),
|
||||
blockText)
|
||||
{
|
||||
UserData = lad
|
||||
};
|
||||
|
||||
var removeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), textBlock.RectTransform, Anchor.CenterRight),
|
||||
TextManager.Get("WhiteListRemove"), style: "GUIButtonSmall")
|
||||
{
|
||||
UserData = lad,
|
||||
OnClicked = RemoveFromWhiteList
|
||||
};
|
||||
}
|
||||
|
||||
foreach (GUIComponent c in listBox.Content.Children)
|
||||
{
|
||||
c.RectTransform.MinSize = new Point(0, Math.Max((int)(20 * GUI.Scale), c.RectTransform.Children.Max(c2 => c2.MinSize.Y)));
|
||||
}
|
||||
|
||||
var nameArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), whitelistFrame.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), nameArea.RectTransform), TextManager.Get("WhiteListName"));
|
||||
nameBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), nameArea.RectTransform), "");
|
||||
nameBox.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
addNewButton.Enabled = !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
|
||||
return true;
|
||||
};
|
||||
nameArea.RectTransform.MinSize = new Point(0, nameBox.RectTransform.MinSize.Y);
|
||||
|
||||
var ipArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), whitelistFrame.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), ipArea.RectTransform), TextManager.Get("WhiteListIP"));
|
||||
ipBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), ipArea.RectTransform), "");
|
||||
ipBox.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
addNewButton.Enabled = !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
|
||||
return true;
|
||||
};
|
||||
ipBox.RectTransform.MinSize = new Point(0, ipBox.RectTransform.MinSize.Y);
|
||||
|
||||
addNewButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), whitelistFrame.RectTransform), TextManager.Get("WhiteListAdd"), style: "GUIButtonSmall")
|
||||
{
|
||||
OnClicked = AddToWhiteList
|
||||
};
|
||||
GUITextBlock.AutoScaleAndNormalize(addNewButton.TextBlock);
|
||||
|
||||
nameBox.Enabled = localEnabled;
|
||||
ipBox.Enabled = localEnabled;
|
||||
addNewButton.Enabled = false;
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
private bool RemoveFromWhiteList(GUIButton button, object obj)
|
||||
{
|
||||
if (obj is WhiteListedPlayer)
|
||||
{
|
||||
if (!(obj is WhiteListedPlayer wlp)) return false;
|
||||
if (!localRemoved.Contains(wlp.UniqueIdentifier)) localRemoved.Add(wlp.UniqueIdentifier);
|
||||
}
|
||||
else if (obj is LocalAdded)
|
||||
{
|
||||
if (!(obj is LocalAdded lad)) return false;
|
||||
if (localAdded.Contains(lad)) localAdded.Remove(lad);
|
||||
}
|
||||
|
||||
if (whitelistFrame != null)
|
||||
{
|
||||
CreateWhiteListFrame(whitelistFrame.Parent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AddToWhiteList(GUIButton button, object obj)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nameBox.Text)) return false;
|
||||
if (whitelistedPlayers.Any(x => x.Name.ToLower() == nameBox.Text.ToLower() && x.IP == ipBox.Text)) return false;
|
||||
|
||||
if (!localAdded.Any(p => p.IP == ipBox.Text)) localAdded.Add(new LocalAdded() { Name = nameBox.Text, IP = ipBox.Text });
|
||||
|
||||
if (whitelistFrame != null)
|
||||
{
|
||||
CreateWhiteListFrame(whitelistFrame.Parent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClientAdminRead(IReadMessage incMsg)
|
||||
{
|
||||
bool hasPermission = incMsg.ReadBoolean();
|
||||
if (!hasPermission)
|
||||
{
|
||||
incMsg.ReadPadBits();
|
||||
return;
|
||||
}
|
||||
|
||||
bool isOwner = incMsg.ReadBoolean();
|
||||
localEnabled = incMsg.ReadBoolean();
|
||||
Enabled = localEnabled;
|
||||
incMsg.ReadPadBits();
|
||||
|
||||
whitelistedPlayers.Clear();
|
||||
UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32();
|
||||
for (int i = 0; i < (int)bannedPlayerCount; i++)
|
||||
{
|
||||
string name = incMsg.ReadString();
|
||||
UInt16 uniqueIdentifier = incMsg.ReadUInt16();
|
||||
|
||||
string ip = "";
|
||||
if (isOwner)
|
||||
{
|
||||
ip = incMsg.ReadString();
|
||||
}
|
||||
else
|
||||
{
|
||||
ip = "IP concealed by host";
|
||||
}
|
||||
whitelistedPlayers.Add(new WhiteListedPlayer(name, uniqueIdentifier, ip));
|
||||
}
|
||||
|
||||
if (whitelistFrame != null)
|
||||
{
|
||||
CreateWhiteListFrame(whitelistFrame.Parent);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientAdminWrite(IWriteMessage outMsg)
|
||||
{
|
||||
outMsg.Write(localEnabled);
|
||||
outMsg.WritePadBits();
|
||||
|
||||
outMsg.Write((UInt16)localRemoved.Count);
|
||||
foreach (UInt16 uniqueId in localRemoved)
|
||||
{
|
||||
outMsg.Write(uniqueId);
|
||||
}
|
||||
|
||||
outMsg.Write((UInt16)localAdded.Count);
|
||||
foreach (LocalAdded lad in localAdded)
|
||||
{
|
||||
outMsg.Write(lad.Name);
|
||||
outMsg.Write(lad.IP); //TODO: ENCRYPT
|
||||
}
|
||||
|
||||
localRemoved.Clear();
|
||||
localAdded.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,8 @@ namespace Barotrauma
|
||||
UserData = saveInfo.FilePath
|
||||
};
|
||||
|
||||
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath))
|
||||
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath),
|
||||
textColor: GUIStyle.TextColorBright)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
@@ -85,7 +86,6 @@ namespace Barotrauma
|
||||
UserData = saveInfo.FilePath
|
||||
};
|
||||
|
||||
|
||||
string saveTimeStr = string.Empty;
|
||||
if (saveInfo.SaveTime > 0)
|
||||
{
|
||||
@@ -187,9 +187,9 @@ namespace Barotrauma
|
||||
SettingValue<Identifier> startingSetInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("startitemset"), TextManager.Get("startitemsettooltip"), prevStartingSet, verticalSize, startingSetOptions);
|
||||
|
||||
ImmutableArray<SettingCarouselElement<StartingBalanceAmount>> fundOptions = ImmutableArray.Create(
|
||||
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.High, "startingfunds.high"),
|
||||
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.Low, "startingfunds.low"),
|
||||
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.Medium, "startingfunds.medium"),
|
||||
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.Low, "startingfunds.low")
|
||||
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.High, "startingfunds.high")
|
||||
);
|
||||
|
||||
SettingCarouselElement<StartingBalanceAmount> prevStartingFund = fundOptions.FirstOrNull(element => element.Value == prevSettings.StartingBalanceAmount) ?? fundOptions[1];
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Barotrauma
|
||||
{
|
||||
private GUIButton deleteMpSaveButton;
|
||||
|
||||
private int prevInitialMoney;
|
||||
|
||||
public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, List<CampaignMode.SaveInfo> saveFiles = null)
|
||||
: base(newGameContainer, loadGameContainer)
|
||||
{
|
||||
@@ -133,6 +135,7 @@ namespace Barotrauma
|
||||
StartButton.RectTransform.MaxSize = RectTransform.MaxPoint;
|
||||
StartButton.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint);
|
||||
|
||||
prevInitialMoney = 8000;
|
||||
InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUIStyle.SmallFont, textColor: GUIStyle.Green)
|
||||
{
|
||||
TextGetter = () =>
|
||||
@@ -142,11 +145,17 @@ namespace Barotrauma
|
||||
{
|
||||
initialMoney = definition.GetInt(elements.StartingFunds.GetValue().ToIdentifier());
|
||||
}
|
||||
if (prevInitialMoney != initialMoney)
|
||||
{
|
||||
GameMain.NetLobbyScreen.RefreshEnabledElements();
|
||||
prevInitialMoney = initialMoney;
|
||||
}
|
||||
if (GameMain.NetLobbyScreen.SelectedSub != null)
|
||||
{
|
||||
initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price;
|
||||
}
|
||||
initialMoney = Math.Max(initialMoney, MultiPlayerCampaign.MinimumInitialMoney);
|
||||
initialMoney = Math.Max(initialMoney, 0);
|
||||
|
||||
return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -476,8 +476,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (GUIComponent child in subList.Content.Children)
|
||||
{
|
||||
SubmarineInfo sub = child.UserData as SubmarineInfo;
|
||||
if (sub == null) { return; }
|
||||
if (!(child.UserData is SubmarineInfo sub)) { return; }
|
||||
child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.Contains(filter.ToLower(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -523,8 +522,10 @@ namespace Barotrauma
|
||||
|
||||
subsToShow.Sort((s1, s2) =>
|
||||
{
|
||||
int p1 = s1.Price > CurrentSettings.InitialMoney ? 10 : 0;
|
||||
int p2 = s2.Price > CurrentSettings.InitialMoney ? 10 : 0;
|
||||
int p1 = s1.Price;
|
||||
if (!s1.IsCampaignCompatible) { p1 += 100000; }
|
||||
int p2 = s2.Price;
|
||||
if (!s2.IsCampaignCompatible) { p2 += 100000; }
|
||||
return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name);
|
||||
});
|
||||
|
||||
@@ -533,7 +534,7 @@ namespace Barotrauma
|
||||
foreach (SubmarineInfo sub in subsToShow)
|
||||
{
|
||||
var textBlock = new GUITextBlock(
|
||||
new RectTransform(new Vector2(1, 0.1f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
|
||||
new RectTransform(new Vector2(1, 0.15f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
|
||||
ToolBox.LimitString(sub.DisplayName.Value, GUIStyle.Font, subList.Rect.Width - 65), style: "ListBoxElement")
|
||||
{
|
||||
ToolTip = sub.Description,
|
||||
@@ -546,12 +547,19 @@ namespace Barotrauma
|
||||
textBlock.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + textBlock.ToolTip.SanitizedString;
|
||||
}
|
||||
|
||||
var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight),
|
||||
TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont)
|
||||
var infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), isHorizontal: false);
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform),
|
||||
TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.BottomRight, font: GUIStyle.SmallFont)
|
||||
{
|
||||
TextColor = sub.Price > CurrentSettings.InitialMoney ? GUIStyle.Red : textBlock.TextColor * 0.8f,
|
||||
ToolTip = textBlock.ToolTip
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform),
|
||||
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.TopRight, font: GUIStyle.SmallFont)
|
||||
{
|
||||
TextColor = textBlock.TextColor * 0.8f,
|
||||
ToolTip = textBlock.ToolTip
|
||||
};
|
||||
#if !DEBUG
|
||||
if (!GameMain.DebugDraw)
|
||||
{
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace Barotrauma
|
||||
// Ok button
|
||||
msgBox.Buttons[1].OnClicked = delegate
|
||||
{
|
||||
foreach (var illegalChar in Path.GetInvalidFileNameChars())
|
||||
foreach (var illegalChar in Path.GetInvalidFileNameCharsCrossPlatform())
|
||||
{
|
||||
if (!nameInput.Text.Contains(illegalChar)) { continue; }
|
||||
|
||||
@@ -274,7 +274,7 @@ namespace Barotrauma
|
||||
// Ok button
|
||||
msgBox.Buttons[1].OnClicked = delegate
|
||||
{
|
||||
foreach (var illegalChar in Path.GetInvalidFileNameChars())
|
||||
foreach (var illegalChar in Path.GetInvalidFileNameCharsCrossPlatform())
|
||||
{
|
||||
if (!nameInput.Text.Contains(illegalChar)) { continue; }
|
||||
|
||||
|
||||
@@ -434,25 +434,18 @@ namespace Barotrauma
|
||||
{
|
||||
PlaySoundOnSelect = true,
|
||||
};
|
||||
var tutorialTypes = new List<Type>()
|
||||
foreach (var tutorialPrefab in TutorialPrefab.Prefabs.OrderBy(p => p.Order))
|
||||
{
|
||||
typeof(MechanicTutorial),
|
||||
typeof(EngineerTutorial),
|
||||
typeof(DoctorTutorial),
|
||||
typeof(OfficerTutorial),
|
||||
typeof(CaptainTutorial),
|
||||
};
|
||||
foreach (Type tutorialType in tutorialTypes)
|
||||
{
|
||||
Tutorial tutorial = (Tutorial)Activator.CreateInstance(tutorialType);
|
||||
var tutorial = new Tutorial(tutorialPrefab);
|
||||
var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), tutorialList.Content.RectTransform), tutorial.DisplayName, textAlignment: Alignment.Center, font: GUIStyle.LargeFont)
|
||||
{
|
||||
TextColor = GUIStyle.Green,
|
||||
UserData = tutorial
|
||||
};
|
||||
}
|
||||
tutorialList.OnSelected += (component, obj) =>
|
||||
{
|
||||
(obj as Tutorial).Start();
|
||||
(obj as Tutorial)?.Start();
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -737,34 +730,12 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateTutorialList()
|
||||
{
|
||||
var tutorialList = menuTabs[Tab.Tutorials].GetChild<GUIListBox>();
|
||||
|
||||
int completedTutorials = 0;
|
||||
|
||||
foreach (GUITextBlock tutorialText in tutorialList.Content.Children)
|
||||
foreach (GUITextBlock tutorialText in menuTabs[Tab.Tutorials].GetChild<GUIListBox>().Content.Children)
|
||||
{
|
||||
if (CompletedTutorials.Instance.Contains(((Tutorial)tutorialText.UserData).Identifier))
|
||||
{
|
||||
completedTutorials++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < tutorialList.Content.Children.Count(); i++)
|
||||
{
|
||||
if (i < completedTutorials + 1)
|
||||
{
|
||||
(tutorialList.Content.GetChild(i) as GUITextBlock).TextColor = GUIStyle.Green;
|
||||
#if !DEBUG
|
||||
(tutorialList.Content.GetChild(i) as GUITextBlock).CanBeFocused = true;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
(tutorialList.Content.GetChild(i) as GUITextBlock).TextColor = Color.Gray;
|
||||
#if !DEBUG
|
||||
(tutorialList.Content.GetChild(i) as GUITextBlock).CanBeFocused = false;
|
||||
#endif
|
||||
}
|
||||
var tutorial = (Tutorial)tutorialText.UserData;
|
||||
tutorialText.Text = CompletedTutorials.Instance.Contains(tutorial.Identifier) ?
|
||||
TextManager.GetWithVariable("tutorialcompleted", "[tutorialname]", tutorial.DisplayName) :
|
||||
tutorial.DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Color = Microsoft.Xna.Framework.Color;
|
||||
using ServerContentPackage = Barotrauma.Networking.ClientPeer.ServerContentPackage;
|
||||
using ServerContentPackage = Barotrauma.Networking.ServerContentPackage;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -866,7 +867,7 @@ namespace Barotrauma
|
||||
{
|
||||
OnSelected = (component, obj) =>
|
||||
{
|
||||
GameMain.Client?.RequestSelectSub(component.Parent.GetChildIndex(component), isShuttle: true);
|
||||
GameMain.Client?.RequestSelectSub(obj as SubmarineInfo, isShuttle: true);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -1794,7 +1795,7 @@ namespace Barotrauma
|
||||
MissionType = missionType;
|
||||
}
|
||||
|
||||
public void UpdateSubList(GUIComponent subList, List<SubmarineInfo> submarines)
|
||||
public void UpdateSubList(GUIComponent subList, IEnumerable<SubmarineInfo> submarines)
|
||||
{
|
||||
if (subList == null) { return; }
|
||||
|
||||
@@ -1817,7 +1818,7 @@ namespace Barotrauma
|
||||
subList = dropDown.ListBox.Content;
|
||||
}
|
||||
|
||||
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), subList.RectTransform) { MinSize = new Point(0, 20) },
|
||||
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), subList.RectTransform) { MinSize = new Point(0, 25) },
|
||||
style: "ListBoxElement")
|
||||
{
|
||||
ToolTip = sub.Description,
|
||||
@@ -1873,7 +1874,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (sub.HasTag(SubmarineTag.Shuttle))
|
||||
{
|
||||
var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) },
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) },
|
||||
TextManager.Get("Shuttle", "RespawnShuttle"), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont)
|
||||
{
|
||||
TextColor = subTextBlock.TextColor * 0.8f,
|
||||
@@ -1881,7 +1882,7 @@ namespace Barotrauma
|
||||
CanBeFocused = false
|
||||
};
|
||||
//make shuttles more dim in the sub list (selecting a shuttle as the main sub is allowed but not recommended)
|
||||
if (subList == this.SubList.Content)
|
||||
if (subList == SubList.Content)
|
||||
{
|
||||
subTextBlock.TextColor *= 0.8f;
|
||||
foreach (GUIComponent child in parent.Children)
|
||||
@@ -1892,8 +1893,16 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) },
|
||||
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont)
|
||||
var infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) }, isHorizontal: false);
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform),
|
||||
TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.BottomRight, font: GUIStyle.SmallFont)
|
||||
{
|
||||
UserData = "pricetext",
|
||||
TextColor = subTextBlock.TextColor * 0.8f,
|
||||
CanBeFocused = false
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform),
|
||||
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.TopRight, font: GUIStyle.SmallFont)
|
||||
{
|
||||
UserData = "classtext",
|
||||
TextColor = subTextBlock.TextColor * 0.8f,
|
||||
@@ -1913,6 +1922,17 @@ namespace Barotrauma
|
||||
if (!GameMain.Client.ServerSettings.AllowSubVoting)
|
||||
{
|
||||
var selectedSub = component.UserData as SubmarineInfo;
|
||||
if (SelectedMode == GameModePreset.MultiPlayerCampaign && CampaignSetupUI != null)
|
||||
{
|
||||
if (selectedSub.Price > CampaignSetupUI.CurrentSettings.InitialMoney)
|
||||
{
|
||||
new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("campaignsubtooexpensive"));
|
||||
}
|
||||
if (!selectedSub.IsCampaignCompatible)
|
||||
{
|
||||
new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("campaignsubincompatible"));
|
||||
}
|
||||
}
|
||||
if (!selectedSub.RequiredContentPackagesInstalled)
|
||||
{
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"),
|
||||
@@ -1924,7 +1944,7 @@ namespace Barotrauma
|
||||
msgBox.Buttons[0].OnClicked = msgBox.Close;
|
||||
msgBox.Buttons[0].OnClicked += (button, obj) =>
|
||||
{
|
||||
GameMain.Client.RequestSelectSub(component.Parent.GetChildIndex(component), isShuttle: false);
|
||||
GameMain.Client.RequestSelectSub(obj as SubmarineInfo, isShuttle: false);
|
||||
return true;
|
||||
};
|
||||
msgBox.Buttons[1].OnClicked = msgBox.Close;
|
||||
@@ -1932,7 +1952,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (GameMain.Client.HasPermission(ClientPermissions.SelectSub))
|
||||
{
|
||||
GameMain.Client.RequestSelectSub(component.Parent.GetChildIndex(component), isShuttle: false);
|
||||
GameMain.Client.RequestSelectSub(selectedSub, isShuttle: false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -2518,7 +2538,6 @@ namespace Barotrauma
|
||||
var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaLower.RectTransform),
|
||||
TextManager.Get("VoteToKick"))
|
||||
{
|
||||
Enabled = !selectedClient.HasKickVoteFromSessionId(GameMain.Client.SessionId),
|
||||
OnClicked = (btn, userdata) => { GameMain.Client.VoteForKick(selectedClient); btn.Enabled = false; return true; },
|
||||
UserData = selectedClient
|
||||
};
|
||||
@@ -3223,6 +3242,22 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Client == null) { return; }
|
||||
|
||||
foreach (var subElement in SubList.Content.Children)
|
||||
{
|
||||
subElement.CanBeFocused = true;
|
||||
foreach (var textBlock in subElement.GetAllChildren<GUITextBlock>())
|
||||
{
|
||||
textBlock.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
SubList.Content.RectTransform.SortChildren((rt1, rt2) =>
|
||||
{
|
||||
SubmarineInfo s1 = rt1.GUIComponent.UserData as SubmarineInfo;
|
||||
SubmarineInfo s2 = rt2.GUIComponent.UserData as SubmarineInfo;
|
||||
return s1.Name.CompareTo(s2.Name);
|
||||
});
|
||||
|
||||
autoRestartBox.Parent.Visible = true;
|
||||
settingsBlocker.Visible = false;
|
||||
if (SelectedMode == GameModePreset.Mission || SelectedMode == GameModePreset.PvP)
|
||||
@@ -3255,6 +3290,33 @@ namespace Barotrauma
|
||||
TextManager.Get("campaignstarting"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center, wrap: true);
|
||||
}
|
||||
}
|
||||
|
||||
if (CampaignSetupUI != null)
|
||||
{
|
||||
foreach (var subElement in SubList.Content.Children)
|
||||
{
|
||||
var sub = subElement.UserData as SubmarineInfo;
|
||||
bool tooExpensive = sub.Price > CampaignSetupUI.CurrentSettings.InitialMoney;
|
||||
if (tooExpensive || !sub.IsCampaignCompatible)
|
||||
{
|
||||
foreach (var textBlock in subElement.GetAllChildren<GUITextBlock>())
|
||||
{
|
||||
textBlock.DisabledTextColor = (textBlock.UserData as string == "pricetext" && tooExpensive ? GUIStyle.Red : GUIStyle.TextColorNormal) * 0.7f;
|
||||
textBlock.Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
SubList.Content.RectTransform.SortChildren((rt1, rt2) =>
|
||||
{
|
||||
SubmarineInfo s1 = rt1.GUIComponent.UserData as SubmarineInfo;
|
||||
SubmarineInfo s2 = rt2.GUIComponent.UserData as SubmarineInfo;
|
||||
int p1 = s1.Price;
|
||||
if (!s1.IsCampaignCompatible) { p1 += 100000; }
|
||||
int p2 = s2.Price;
|
||||
if (!s2.IsCampaignCompatible) { p2 += 100000; }
|
||||
return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3656,7 +3718,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private List<SubmarineInfo> visibilityMenuOrder = new List<SubmarineInfo>();
|
||||
private readonly List<SubmarineInfo> visibilityMenuOrder = new List<SubmarineInfo>();
|
||||
private void CreateSubmarineVisibilityMenu()
|
||||
{
|
||||
var messageBox = new GUIMessageBox(TextManager.Get("SubmarineVisibility"), "",
|
||||
|
||||
@@ -1839,7 +1839,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var illegalChar in Path.GetInvalidFileNameChars())
|
||||
foreach (var illegalChar in Path.GetInvalidFileNameCharsCrossPlatform())
|
||||
{
|
||||
if (!name.Contains(illegalChar)) { continue; }
|
||||
GUI.AddMessage(TextManager.GetWithVariable("SubNameIllegalCharsWarning", "[illegalchar]", illegalChar.ToString()), GUIStyle.Red);
|
||||
@@ -2410,12 +2410,15 @@ namespace Barotrauma
|
||||
Stretch = true
|
||||
};
|
||||
var classText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), classGroup.RectTransform),
|
||||
TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true);
|
||||
TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true)
|
||||
{
|
||||
ToolTip = TextManager.Get("submarineclass.description")
|
||||
};
|
||||
GUIDropDown classDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), classGroup.RectTransform));
|
||||
classDropDown.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
|
||||
foreach (SubmarineClass @class in Enum.GetValues(typeof(SubmarineClass)))
|
||||
foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass)))
|
||||
{
|
||||
classDropDown.AddItem(TextManager.Get($"{nameof(SubmarineClass)}.{@class}"), @class);
|
||||
classDropDown.AddItem(TextManager.Get($"{nameof(SubmarineClass)}.{subClass}"), subClass, toolTip: TextManager.Get($"submarineclass.{subClass}.description"));
|
||||
}
|
||||
classDropDown.AddItem(TextManager.Get(nameof(SubmarineTag.Shuttle)), SubmarineTag.Shuttle);
|
||||
classDropDown.OnSelected += (selected, userdata) =>
|
||||
@@ -2435,6 +2438,31 @@ namespace Barotrauma
|
||||
};
|
||||
classDropDown.SelectItem(!MainSub.Info.HasTag(SubmarineTag.Shuttle) ? MainSub.Info.SubmarineClass : (object)SubmarineTag.Shuttle);
|
||||
|
||||
var tierGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), tierGroup.RectTransform),
|
||||
TextManager.Get("subeditor.tier"), textAlignment: Alignment.CenterLeft, wrap: true)
|
||||
{
|
||||
ToolTip = TextManager.Get("submarinetier.description")
|
||||
};
|
||||
|
||||
new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), tierGroup.RectTransform), NumberType.Int)
|
||||
{
|
||||
IntValue = SubmarineInfo.GetDefaultTier(MainSub.Info.Price),
|
||||
MinValueInt = 1,
|
||||
MaxValueInt = 3,
|
||||
OnValueChanged = (numberInput) =>
|
||||
{
|
||||
MainSub.Info.Tier = numberInput.IntValue;
|
||||
}
|
||||
};
|
||||
if (MainSub?.Info != null)
|
||||
{
|
||||
MainSub.Info.Tier = Math.Clamp(MainSub.Info.Tier, 1, 3);
|
||||
}
|
||||
|
||||
var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
@@ -2970,7 +2998,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (char illegalChar in Path.GetInvalidFileNameChars())
|
||||
foreach (char illegalChar in Path.GetInvalidFileNameCharsCrossPlatform())
|
||||
{
|
||||
if (nameBox.Text.Contains(illegalChar))
|
||||
{
|
||||
@@ -5014,6 +5042,10 @@ namespace Barotrauma
|
||||
SkipInventorySlotUpdate = false;
|
||||
ImageManager.Update((float)deltaTime);
|
||||
|
||||
#if DEBUG
|
||||
Hull.UpdateCheats((float)deltaTime, cam);
|
||||
#endif
|
||||
|
||||
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y)
|
||||
{
|
||||
saveFrame = null;
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
|
||||
private string Percentage(float v) => TextManager.GetWithVariable("percentageformat", "[value]", Round(v * 100).ToString()).Value;
|
||||
private string Percentage(float v) => ToolBox.GetFormattedPercentage(v);
|
||||
|
||||
private int Round(float v) => (int)MathF.Round(v);
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FlowSounds[i] == null) { continue; }
|
||||
if (FlowSounds[i]?.Sound == null) { continue; }
|
||||
Vector2 soundPos = new Vector2(GameMain.SoundManager.ListenerPosition.X + (flowVolumeRight[i] - flowVolumeLeft[i]) * 100, GameMain.SoundManager.ListenerPosition.Y);
|
||||
if (flowSoundChannels[i] == null || !flowSoundChannels[i].IsPlaying)
|
||||
{
|
||||
|
||||
@@ -2,40 +2,30 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.19.1.0</Version>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
DebugLinux is configured to use .NET 6.0 so I can use Hot Reload while working on the game.
|
||||
If you are running Linux and and have issues compiling in debug configurations remove the
|
||||
<TargetFramework>net6.0</TargetFramework> and <LangVersion>8</LangVersion> elements under <PropertyGroup>.
|
||||
- Markus
|
||||
-->
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>DEBUG;TRACE;CLIENT;LINUX;USE_STEAM</DefineConstants>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DefineConstants>TRACE;DEBUG;CLIENT;LINUX;X64;USE_STEAM</DefineConstants>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.19.1.0</Version>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.19.1.0</Version>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,40 +2,30 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.19.1.0</Version>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
DebugLinux is configured to use .NET 6.0 so I can use Hot Reload while working on the game.
|
||||
If you are running Linux and and have issues compiling in debug configurations remove the
|
||||
<TargetFramework>net6.0</TargetFramework> elements under <PropertyGroup>:s.
|
||||
- Markus
|
||||
-->
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>DEBUG;TRACE;SERVER;LINUX;USE_STEAM</DefineConstants>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DefineConstants>TRACE;DEBUG;SERVER;LINUX;X64;USE_STEAM</DefineConstants>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputPath>..\bin\$(Configuration)Linux\</OutputPath>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.19.1.0</Version>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -46,39 +46,39 @@ namespace Barotrauma
|
||||
|
||||
public void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(ID);
|
||||
msg.Write(Name);
|
||||
msg.Write(OriginalName);
|
||||
msg.Write((byte)Head.Preset.TagSet.Count);
|
||||
msg.WriteUInt16(ID);
|
||||
msg.WriteString(Name);
|
||||
msg.WriteString(OriginalName);
|
||||
msg.WriteByte((byte)Head.Preset.TagSet.Count);
|
||||
foreach (Identifier tag in Head.Preset.TagSet)
|
||||
{
|
||||
msg.Write(tag);
|
||||
msg.WriteIdentifier(tag);
|
||||
}
|
||||
msg.Write((byte)Head.HairIndex);
|
||||
msg.Write((byte)Head.BeardIndex);
|
||||
msg.Write((byte)Head.MoustacheIndex);
|
||||
msg.Write((byte)Head.FaceAttachmentIndex);
|
||||
msg.WriteByte((byte)Head.HairIndex);
|
||||
msg.WriteByte((byte)Head.BeardIndex);
|
||||
msg.WriteByte((byte)Head.MoustacheIndex);
|
||||
msg.WriteByte((byte)Head.FaceAttachmentIndex);
|
||||
msg.WriteColorR8G8B8(Head.SkinColor);
|
||||
msg.WriteColorR8G8B8(Head.HairColor);
|
||||
msg.WriteColorR8G8B8(Head.FacialHairColor);
|
||||
msg.Write(ragdollFileName);
|
||||
msg.WriteString(ragdollFileName);
|
||||
|
||||
if (Job != null)
|
||||
{
|
||||
msg.Write(Job.Prefab.UintIdentifier);
|
||||
msg.Write((byte)Job.Variant);
|
||||
msg.WriteUInt32(Job.Prefab.UintIdentifier);
|
||||
msg.WriteByte((byte)Job.Variant);
|
||||
foreach (SkillPrefab skillPrefab in Job.Prefab.Skills.OrderBy(s => s.Identifier))
|
||||
{
|
||||
msg.Write(Job.GetSkill(skillPrefab.Identifier).Level);
|
||||
msg.WriteSingle(Job.GetSkill(skillPrefab.Identifier).Level);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((uint)0);
|
||||
msg.Write((byte)0);
|
||||
msg.WriteUInt32((uint)0);
|
||||
msg.WriteByte((byte)0);
|
||||
}
|
||||
|
||||
msg.Write((ushort)ExperiencePoints);
|
||||
msg.WriteUInt16((ushort)ExperiencePoints);
|
||||
msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class Character
|
||||
{
|
||||
public Endpoint OwnerClientEndpoint;
|
||||
public Address OwnerClientAddress;
|
||||
public string OwnerClientName;
|
||||
public bool ClientDisconnected;
|
||||
public float KillDisconnectedTimer;
|
||||
@@ -301,25 +301,25 @@ namespace Barotrauma
|
||||
|
||||
public void ServerWritePosition(IWriteMessage msg, Client c)
|
||||
{
|
||||
msg.Write(ID);
|
||||
msg.WriteUInt16(ID);
|
||||
|
||||
IWriteMessage tempBuffer = new WriteOnlyMessage();
|
||||
|
||||
if (this == c.Character)
|
||||
{
|
||||
tempBuffer.Write(true);
|
||||
tempBuffer.WriteBoolean(true);
|
||||
if (LastNetworkUpdateID < memInput.Count + 1)
|
||||
{
|
||||
tempBuffer.Write((UInt16)0);
|
||||
tempBuffer.WriteUInt16((UInt16)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1));
|
||||
tempBuffer.WriteUInt16((UInt16)(LastNetworkUpdateID - memInput.Count - 1));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write(false);
|
||||
tempBuffer.WriteBoolean(false);
|
||||
|
||||
bool aiming = false;
|
||||
bool use = false;
|
||||
@@ -342,41 +342,41 @@ namespace Barotrauma
|
||||
networkUpdateSent = true;
|
||||
}
|
||||
|
||||
tempBuffer.Write(aiming);
|
||||
tempBuffer.Write(shoot);
|
||||
tempBuffer.Write(use);
|
||||
tempBuffer.WriteBoolean(aiming);
|
||||
tempBuffer.WriteBoolean(shoot);
|
||||
tempBuffer.WriteBoolean(use);
|
||||
if (AnimController is HumanoidAnimController)
|
||||
{
|
||||
tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching);
|
||||
tempBuffer.WriteBoolean(((HumanoidAnimController)AnimController).Crouching);
|
||||
}
|
||||
tempBuffer.Write(attack);
|
||||
tempBuffer.WriteBoolean(attack);
|
||||
|
||||
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
|
||||
tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI)));
|
||||
tempBuffer.WriteUInt16((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI)));
|
||||
|
||||
tempBuffer.Write(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated);
|
||||
tempBuffer.WriteBoolean(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated);
|
||||
|
||||
tempBuffer.Write(AnimController.Dir > 0.0f);
|
||||
tempBuffer.WriteBoolean(AnimController.Dir > 0.0f);
|
||||
}
|
||||
|
||||
if (SelectedCharacter != null || HasSelectedAnyItem)
|
||||
{
|
||||
tempBuffer.Write(true);
|
||||
tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID);
|
||||
tempBuffer.Write(SelectedItem != null ? SelectedItem.ID : NullEntityID);
|
||||
tempBuffer.Write(SelectedSecondaryItem != null ? SelectedSecondaryItem.ID : NullEntityID);
|
||||
tempBuffer.WriteBoolean(true);
|
||||
tempBuffer.WriteUInt16(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID);
|
||||
tempBuffer.WriteUInt16(SelectedItem != null ? SelectedItem.ID : NullEntityID);
|
||||
tempBuffer.WriteUInt16(SelectedSecondaryItem != null ? SelectedSecondaryItem.ID : NullEntityID);
|
||||
if (SelectedCharacter != null)
|
||||
{
|
||||
tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR);
|
||||
tempBuffer.WriteBoolean(AnimController.Anim == AnimController.Animation.CPR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write(false);
|
||||
tempBuffer.WriteBoolean(false);
|
||||
}
|
||||
|
||||
tempBuffer.Write(SimPosition.X);
|
||||
tempBuffer.Write(SimPosition.Y);
|
||||
tempBuffer.WriteSingle(SimPosition.X);
|
||||
tempBuffer.WriteSingle(SimPosition.Y);
|
||||
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
|
||||
AnimController.Collider.LinearVelocity = new Vector2(
|
||||
MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel),
|
||||
@@ -385,17 +385,17 @@ namespace Barotrauma
|
||||
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12);
|
||||
|
||||
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled;
|
||||
tempBuffer.Write(fixedRotation);
|
||||
tempBuffer.WriteBoolean(fixedRotation);
|
||||
if (!fixedRotation)
|
||||
{
|
||||
tempBuffer.Write(AnimController.Collider.Rotation);
|
||||
tempBuffer.WriteSingle(AnimController.Collider.Rotation);
|
||||
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
|
||||
AnimController.Collider.AngularVelocity = NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8);
|
||||
tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8);
|
||||
}
|
||||
|
||||
bool writeStatus = healthUpdateTimer <= 0.0f;
|
||||
tempBuffer.Write(writeStatus);
|
||||
tempBuffer.WriteBoolean(writeStatus);
|
||||
if (writeStatus)
|
||||
{
|
||||
WriteStatus(tempBuffer);
|
||||
@@ -406,7 +406,7 @@ namespace Barotrauma
|
||||
tempBuffer.WritePadBits();
|
||||
|
||||
msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes);
|
||||
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
|
||||
msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
|
||||
}
|
||||
|
||||
public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
@@ -417,13 +417,13 @@ namespace Barotrauma
|
||||
switch (eventData)
|
||||
{
|
||||
case InventoryStateEventData _:
|
||||
msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
|
||||
msg.WriteUInt16(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
|
||||
Inventory.ServerEventWrite(msg, c);
|
||||
break;
|
||||
case ControlEventData controlEventData:
|
||||
Client owner = controlEventData.Owner;
|
||||
msg.Write(owner == c && owner.Character == this);
|
||||
msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.SessionId : (byte)0);
|
||||
msg.WriteBoolean(owner == c && owner.Character == this);
|
||||
msg.WriteByte(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.SessionId : (byte)0);
|
||||
break;
|
||||
case CharacterStatusEventData statusEventData:
|
||||
WriteStatus(msg, statusEventData.ForceAfflictionData);
|
||||
@@ -431,16 +431,16 @@ namespace Barotrauma
|
||||
case UpdateSkillsEventData _:
|
||||
if (Info?.Job == null)
|
||||
{
|
||||
msg.Write((byte)0);
|
||||
msg.WriteByte((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var skills = Info.Job.GetSkills();
|
||||
msg.Write((byte)skills.Count());
|
||||
msg.WriteByte((byte)skills.Count());
|
||||
foreach (Skill skill in skills)
|
||||
{
|
||||
msg.Write(skill.Identifier);
|
||||
msg.Write(skill.Level);
|
||||
msg.WriteIdentifier(skill.Identifier);
|
||||
msg.WriteSingle(skill.Level);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -457,33 +457,33 @@ namespace Barotrauma
|
||||
targetLimbIndex = targetLimbsArray.IndexOf(attackEventData.TargetLimb);
|
||||
}
|
||||
}
|
||||
msg.Write((byte)(attackLimbIndex < 0 ? 255 : attackLimbIndex));
|
||||
msg.Write((ushort)targetEntityId);
|
||||
msg.Write((byte)(targetLimbIndex < 0 ? 255 : targetLimbIndex));
|
||||
msg.Write(attackEventData.TargetSimPos.X);
|
||||
msg.Write(attackEventData.TargetSimPos.Y);
|
||||
msg.WriteByte((byte)(attackLimbIndex < 0 ? 255 : attackLimbIndex));
|
||||
msg.WriteUInt16((ushort)targetEntityId);
|
||||
msg.WriteByte((byte)(targetLimbIndex < 0 ? 255 : targetLimbIndex));
|
||||
msg.WriteSingle(attackEventData.TargetSimPos.X);
|
||||
msg.WriteSingle(attackEventData.TargetSimPos.Y);
|
||||
}
|
||||
break;
|
||||
case AssignCampaignInteractionEventData _:
|
||||
msg.Write((byte)CampaignInteractionType);
|
||||
msg.Write(RequireConsciousnessForCustomInteract);
|
||||
msg.WriteByte((byte)CampaignInteractionType);
|
||||
msg.WriteBoolean(RequireConsciousnessForCustomInteract);
|
||||
break;
|
||||
case ObjectiveManagerStateEventData objectiveManagerStateEventData:
|
||||
AIObjectiveManager.ObjectiveType type = objectiveManagerStateEventData.ObjectiveType;
|
||||
msg.WriteRangedInteger((int)type, (int)AIObjectiveManager.ObjectiveType.MinValue, (int)AIObjectiveManager.ObjectiveType.MaxValue);
|
||||
if (!(AIController is HumanAIController controller))
|
||||
{
|
||||
msg.Write(false);
|
||||
msg.WriteBoolean(false);
|
||||
break;
|
||||
}
|
||||
if (type == AIObjectiveManager.ObjectiveType.Order)
|
||||
{
|
||||
var currentOrderInfo = controller.ObjectiveManager.GetCurrentOrderInfo();
|
||||
bool validOrder = currentOrderInfo != null;
|
||||
msg.Write(validOrder);
|
||||
msg.WriteBoolean(validOrder);
|
||||
if (!validOrder) { break; }
|
||||
var orderPrefab = currentOrderInfo.Prefab;
|
||||
msg.Write(orderPrefab.UintIdentifier);
|
||||
msg.WriteUInt32(orderPrefab.UintIdentifier);
|
||||
if (!orderPrefab.HasOptions) { break; }
|
||||
int optionIndex = orderPrefab.AllOptions.IndexOf(currentOrderInfo.Option);
|
||||
if (optionIndex == -1)
|
||||
@@ -496,65 +496,65 @@ namespace Barotrauma
|
||||
{
|
||||
var objective = controller.ObjectiveManager.CurrentObjective;
|
||||
bool validObjective = objective?.Identifier is { IsEmpty: false };
|
||||
msg.Write(validObjective);
|
||||
msg.WriteBoolean(validObjective);
|
||||
if (!validObjective) { break; }
|
||||
msg.Write(objective.Identifier);
|
||||
msg.Write(objective.Option);
|
||||
msg.WriteIdentifier(objective.Identifier);
|
||||
msg.WriteIdentifier(objective.Option);
|
||||
UInt16 targetEntityId = 0;
|
||||
if (objective is AIObjectiveOperateItem operateObjective && operateObjective.OperateTarget != null)
|
||||
{
|
||||
targetEntityId = operateObjective.OperateTarget.ID;
|
||||
}
|
||||
msg.Write(targetEntityId);
|
||||
msg.WriteUInt16(targetEntityId);
|
||||
}
|
||||
break;
|
||||
case TeamChangeEventData _:
|
||||
msg.Write((byte)TeamID);
|
||||
msg.WriteByte((byte)TeamID);
|
||||
break;
|
||||
case AddToCrewEventData addToCrewEventData:
|
||||
msg.Write((byte)addToCrewEventData.TeamType); // team id
|
||||
msg.WriteByte((byte)addToCrewEventData.TeamType); // team id
|
||||
ushort[] inventoryItemIDs = addToCrewEventData.InventoryItems.Select(item => item.ID).ToArray();
|
||||
msg.Write((ushort)inventoryItemIDs.Length);
|
||||
msg.WriteUInt16((ushort)inventoryItemIDs.Length);
|
||||
for (int i = 0; i < inventoryItemIDs.Length; i++)
|
||||
{
|
||||
msg.Write(inventoryItemIDs[i]);
|
||||
msg.WriteUInt16(inventoryItemIDs[i]);
|
||||
}
|
||||
break;
|
||||
case UpdateExperienceEventData _:
|
||||
msg.Write(Info.ExperiencePoints);
|
||||
msg.WriteInt32(Info.ExperiencePoints);
|
||||
break;
|
||||
case UpdateTalentsEventData _:
|
||||
msg.Write((ushort)characterTalents.Count);
|
||||
msg.WriteUInt16((ushort)characterTalents.Count);
|
||||
foreach (var unlockedTalent in characterTalents)
|
||||
{
|
||||
msg.Write(unlockedTalent.AddedThisRound);
|
||||
msg.Write(unlockedTalent.Prefab.UintIdentifier);
|
||||
msg.WriteBoolean(unlockedTalent.AddedThisRound);
|
||||
msg.WriteUInt32(unlockedTalent.Prefab.UintIdentifier);
|
||||
}
|
||||
break;
|
||||
case UpdateMoneyEventData _:
|
||||
msg.Write(GameMain.GameSession.Campaign.GetWallet(c).Balance);
|
||||
msg.WriteInt32(GameMain.GameSession.Campaign.GetWallet(c).Balance);
|
||||
break;
|
||||
case UpdatePermanentStatsEventData updatePermanentStatsEventData:
|
||||
StatTypes statType = updatePermanentStatsEventData.StatType;
|
||||
if (Info == null)
|
||||
{
|
||||
msg.Write((byte)0);
|
||||
msg.Write((byte)0);
|
||||
msg.WriteByte((byte)0);
|
||||
msg.WriteByte((byte)0);
|
||||
}
|
||||
else if (!Info.SavedStatValues.ContainsKey(statType))
|
||||
{
|
||||
msg.Write((byte)0);
|
||||
msg.Write((byte)statType);
|
||||
msg.WriteByte((byte)0);
|
||||
msg.WriteByte((byte)statType);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((byte)Info.SavedStatValues[statType].Count);
|
||||
msg.Write((byte)statType);
|
||||
msg.WriteByte((byte)Info.SavedStatValues[statType].Count);
|
||||
msg.WriteByte((byte)statType);
|
||||
foreach (var savedStatValue in Info.SavedStatValues[statType])
|
||||
{
|
||||
msg.Write(savedStatValue.StatIdentifier);
|
||||
msg.Write(savedStatValue.StatValue);
|
||||
msg.Write(savedStatValue.RemoveOnDeath);
|
||||
msg.WriteString(savedStatValue.StatIdentifier);
|
||||
msg.WriteSingle(savedStatValue.StatValue);
|
||||
msg.WriteBoolean(savedStatValue.RemoveOnDeath);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -568,15 +568,15 @@ namespace Barotrauma
|
||||
/// <param name="forceAfflictionData">Normally full affliction data is not written for dead characters, this can be used to force them to be written</param>
|
||||
private void WriteStatus(IWriteMessage msg, bool forceAfflictionData = false)
|
||||
{
|
||||
msg.Write(IsDead);
|
||||
msg.WriteBoolean(IsDead);
|
||||
if (IsDead)
|
||||
{
|
||||
msg.WriteRangedInteger((int)CauseOfDeath.Type, 0, Enum.GetValues(typeof(CauseOfDeathType)).Length - 1);
|
||||
if (CauseOfDeath.Type == CauseOfDeathType.Affliction)
|
||||
{
|
||||
msg.Write(CauseOfDeath.Affliction.UintIdentifier);
|
||||
msg.WriteUInt32(CauseOfDeath.Affliction.UintIdentifier);
|
||||
}
|
||||
msg.Write(forceAfflictionData);
|
||||
msg.WriteBoolean(forceAfflictionData);
|
||||
if (forceAfflictionData)
|
||||
{
|
||||
CharacterHealth.ServerWrite(msg);
|
||||
@@ -589,7 +589,7 @@ namespace Barotrauma
|
||||
if (AnimController?.LimbJoints == null)
|
||||
{
|
||||
//0 limbs severed
|
||||
msg.Write((byte)0);
|
||||
msg.WriteByte((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -601,10 +601,10 @@ namespace Barotrauma
|
||||
severedJointIndices.Add(i);
|
||||
}
|
||||
}
|
||||
msg.Write((byte)severedJointIndices.Count);
|
||||
msg.WriteByte((byte)severedJointIndices.Count);
|
||||
foreach (int jointIndex in severedJointIndices)
|
||||
{
|
||||
msg.Write((byte)jointIndex);
|
||||
msg.WriteByte((byte)jointIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -615,24 +615,24 @@ namespace Barotrauma
|
||||
|
||||
int initialMsgLength = msg.LengthBytes;
|
||||
|
||||
msg.Write(Info == null);
|
||||
msg.Write(entityId);
|
||||
msg.Write(SpeciesName);
|
||||
msg.Write(Seed);
|
||||
msg.WriteBoolean(Info == null);
|
||||
msg.WriteUInt16(entityId);
|
||||
msg.WriteIdentifier(SpeciesName);
|
||||
msg.WriteString(Seed);
|
||||
|
||||
if (Removed)
|
||||
{
|
||||
msg.Write(0.0f);
|
||||
msg.Write(0.0f);
|
||||
msg.WriteSingle(0.0f);
|
||||
msg.WriteSingle(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(WorldPosition.X);
|
||||
msg.Write(WorldPosition.Y);
|
||||
msg.WriteSingle(WorldPosition.X);
|
||||
msg.WriteSingle(WorldPosition.Y);
|
||||
}
|
||||
|
||||
msg.Write(Enabled);
|
||||
msg.Write(DisabledByEvent);
|
||||
msg.WriteBoolean(Enabled);
|
||||
msg.WriteBoolean(DisabledByEvent);
|
||||
|
||||
//character with no characterinfo (e.g. some monster)
|
||||
if (Info == null)
|
||||
@@ -644,54 +644,54 @@ namespace Barotrauma
|
||||
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
|
||||
if (ownerClient != null)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.Write(ownerClient.SessionId);
|
||||
msg.WriteBoolean(true);
|
||||
msg.WriteByte(ownerClient.SessionId);
|
||||
}
|
||||
else if (GameMain.Server.Character == this)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.Write((byte)0);
|
||||
msg.WriteBoolean(true);
|
||||
msg.WriteByte((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(false);
|
||||
msg.WriteBoolean(false);
|
||||
}
|
||||
msg.Write(HumanPrefabHealthMultiplier);
|
||||
msg.Write(Wallet.Balance);
|
||||
msg.WriteSingle(HumanPrefabHealthMultiplier);
|
||||
msg.WriteInt32(Wallet.Balance);
|
||||
msg.WriteRangedInteger(Wallet.RewardDistribution, 0, 100);
|
||||
msg.Write((byte)TeamID);
|
||||
msg.Write(this is AICharacter);
|
||||
msg.Write(info.SpeciesName);
|
||||
msg.WriteByte((byte)TeamID);
|
||||
msg.WriteBoolean(this is AICharacter);
|
||||
msg.WriteIdentifier(info.SpeciesName);
|
||||
int msgLengthBeforeInfo = msg.LengthBytes;
|
||||
info.ServerWrite(msg);
|
||||
int infoLength = msg.LengthBytes - msgLengthBeforeInfo;
|
||||
|
||||
msg.Write((byte)CampaignInteractionType);
|
||||
msg.WriteByte((byte)CampaignInteractionType);
|
||||
if (CampaignInteractionType == CampaignMode.InteractionType.Store)
|
||||
{
|
||||
msg.Write(MerchantIdentifier);
|
||||
msg.WriteIdentifier(MerchantIdentifier);
|
||||
}
|
||||
|
||||
int msgLengthBeforeOrders = msg.LengthBytes;
|
||||
// Current orders
|
||||
msg.Write((byte)info.CurrentOrders.Count(o => o != null));
|
||||
msg.WriteByte((byte)info.CurrentOrders.Count(o => o != null));
|
||||
foreach (var orderInfo in info.CurrentOrders)
|
||||
{
|
||||
if (orderInfo == null) { continue; }
|
||||
msg.Write(orderInfo.Prefab.UintIdentifier);
|
||||
msg.Write(orderInfo.TargetEntity == null ? (UInt16)0 : orderInfo.TargetEntity.ID);
|
||||
msg.WriteUInt32(orderInfo.Prefab.UintIdentifier);
|
||||
msg.WriteUInt16(orderInfo.TargetEntity == null ? (UInt16)0 : orderInfo.TargetEntity.ID);
|
||||
var hasOrderGiver = orderInfo.OrderGiver != null;
|
||||
msg.Write(hasOrderGiver);
|
||||
if (hasOrderGiver) { msg.Write(orderInfo.OrderGiver.ID); }
|
||||
msg.Write((byte)(orderInfo.Option == Identifier.Empty ? 0 : orderInfo.Prefab.Options.IndexOf(orderInfo.Option)));
|
||||
msg.Write((byte)orderInfo.ManualPriority);
|
||||
msg.WriteBoolean(hasOrderGiver);
|
||||
if (hasOrderGiver) { msg.WriteUInt16(orderInfo.OrderGiver.ID); }
|
||||
msg.WriteByte((byte)(orderInfo.Option == Identifier.Empty ? 0 : orderInfo.Prefab.Options.IndexOf(orderInfo.Option)));
|
||||
msg.WriteByte((byte)orderInfo.ManualPriority);
|
||||
var hasTargetPosition = orderInfo.TargetPosition != null;
|
||||
msg.Write(hasTargetPosition);
|
||||
msg.WriteBoolean(hasTargetPosition);
|
||||
if (hasTargetPosition)
|
||||
{
|
||||
msg.Write(orderInfo.TargetPosition.Position.X);
|
||||
msg.Write(orderInfo.TargetPosition.Position.Y);
|
||||
msg.Write(orderInfo.TargetPosition.Hull == null ? (UInt16)0 : orderInfo.TargetPosition.Hull.ID);
|
||||
msg.WriteSingle(orderInfo.TargetPosition.Position.X);
|
||||
msg.WriteSingle(orderInfo.TargetPosition.Position.Y);
|
||||
msg.WriteUInt16(orderInfo.TargetPosition.Hull == null ? (UInt16)0 : orderInfo.TargetPosition.Hull.ID);
|
||||
}
|
||||
}
|
||||
int ordersLength = msg.LengthBytes - msgLengthBeforeOrders;
|
||||
@@ -713,7 +713,7 @@ namespace Barotrauma
|
||||
WriteStatus(tempBuffer, forceAfflictionData: true);
|
||||
if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize)
|
||||
{
|
||||
msg.Write(false);
|
||||
msg.WriteBoolean(false);
|
||||
if (msgLengthBeforeStatus < 255)
|
||||
{
|
||||
string errorMsg = $"Error when writing character spawn data for \"{Name}\": status data caused the length of the message to exceed 255 bytes ({msgLengthBeforeStatus} + {tempBuffer.LengthBytes})";
|
||||
@@ -723,7 +723,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.WriteBoolean(true);
|
||||
WriteStatus(msg, forceAfflictionData: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,9 +302,9 @@ namespace Barotrauma
|
||||
{
|
||||
client ??= GameMain.Server.ConnectedClients.Find(c => c.SessionId == id);
|
||||
}
|
||||
if (Endpoint.Parse(arg).TryUnwrap(out var endpoint))
|
||||
if (Address.Parse(arg).TryUnwrap(out var address))
|
||||
{
|
||||
client ??= GameMain.Server.ConnectedClients.Find(c => c.EndpointMatches(endpoint));
|
||||
client ??= GameMain.Server.ConnectedClients.Find(c => c.AddressMatches(address));
|
||||
}
|
||||
if (AccountId.Parse(arg).TryUnwrap(out var argAccountId))
|
||||
{
|
||||
@@ -915,7 +915,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Server == null || args.Length == 0) return;
|
||||
|
||||
if (!(Endpoint.Parse(args[0]).TryUnwrap(out var endpoint))) { return; }
|
||||
if (!(Address.Parse(args[0]).TryUnwrap(out var address))) { return; }
|
||||
|
||||
ShowQuestionPrompt("Reason for banning the endpoint \"" + args[0] + "\"? (c to cancel)", (reason) =>
|
||||
{
|
||||
@@ -934,10 +934,10 @@ namespace Barotrauma
|
||||
banDuration = parsedBanDuration;
|
||||
}
|
||||
|
||||
var clients = GameMain.Server.ConnectedClients.Where(c => c.EndpointMatches(endpoint)).ToList();
|
||||
var clients = GameMain.Server.ConnectedClients.Where(c => c.AddressMatches(address)).ToList();
|
||||
if (clients.Count == 0)
|
||||
{
|
||||
GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", endpoint, reason, banDuration);
|
||||
GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", address, reason, banDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1510,8 +1510,8 @@ namespace Barotrauma
|
||||
(Client client, Vector2 cursorPos, string[] args) =>
|
||||
{
|
||||
if (args.Length < 1) { return; }
|
||||
if (!(Endpoint.Parse(args[0]).TryUnwrap(out var endpoint))) { return; }
|
||||
var clients = GameMain.Server.ConnectedClients.Where(c => c.EndpointMatches(endpoint)).ToList();
|
||||
if (!(Address.Parse(args[0]).TryUnwrap(out var address))) { return; }
|
||||
var clients = GameMain.Server.ConnectedClients.Where(c => c.AddressMatches(address)).ToList();
|
||||
TimeSpan? duration = null;
|
||||
if (args.Length > 1)
|
||||
{
|
||||
@@ -1530,7 +1530,7 @@ namespace Barotrauma
|
||||
|
||||
if (clients.Count == 0)
|
||||
{
|
||||
GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", endpoint, reason, duration);
|
||||
GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", address, reason, duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1867,17 +1867,14 @@ namespace Barotrauma
|
||||
|
||||
foreach (var talentTree in talentTrees)
|
||||
{
|
||||
foreach (var subTree in talentTree.TalentSubTrees)
|
||||
foreach (var talentId in talentTree.AllTalentIdentifiers)
|
||||
{
|
||||
foreach (var option in subTree.TalentOptionStages)
|
||||
if (TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab talentPrefab))
|
||||
{
|
||||
foreach (var talent in option.Talents)
|
||||
{
|
||||
targetCharacter.GiveTalent(talent);
|
||||
NewMessage($"Talent \"{talent.DisplayName}\" given to \"{targetCharacter.Name}\" by \"{client.Name}\".");
|
||||
GameMain.Server.SendConsoleMessage($"Gave talent \"{talent.DisplayName}\" to \"{targetCharacter.Name}\".", client);
|
||||
NewMessage($"Unlocked talent \"{talent.DisplayName}\".");
|
||||
}
|
||||
targetCharacter.GiveTalent(talentPrefab);
|
||||
NewMessage($"Talent \"{talentPrefab.DisplayName}\" given to \"{targetCharacter.Name}\" by \"{client.Name}\".");
|
||||
GameMain.Server.SendConsoleMessage($"Gave talent \"{talentPrefab.DisplayName}\" to \"{targetCharacter.Name}\".", client);
|
||||
NewMessage($"Unlocked talent \"{talentPrefab.DisplayName}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user