Unstable v0.19.3.0

This commit is contained in:
Juan Pablo Arce
2022-09-02 15:10:56 -03:00
parent 28789616bd
commit 3f2c843247
336 changed files with 7152 additions and 7739 deletions

View File

@@ -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()

View File

@@ -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:

View File

@@ -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)));
}
}

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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));
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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)));

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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) =>
{

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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))

View File

@@ -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)

View File

@@ -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;
};
}

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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)));

View File

@@ -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)

View File

@@ -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) =>
{

View File

@@ -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>

View File

@@ -90,8 +90,8 @@ namespace Barotrauma.Items.Components
{
Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ?
SpriteEffects.FlipHorizontally : SpriteEffects.None;
SetLightSourceTransformProjSpecific();
}
SetLightSourceTransformProjSpecific();
}
partial void OnStateChanged()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);
}
});

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
{

View File

@@ -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);
}
}
}

View File

@@ -94,7 +94,6 @@ namespace Barotrauma.Items.Components
protected override void OnResolutionChanged()
{
base.OnResolutionChanged();
OnItemLoadedProjSpecific();
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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]);
}
}
}

View File

@@ -149,7 +149,7 @@ namespace Barotrauma.Items.Components
{
if (TryExtractEventData(extraData, out ClientEventData eventData))
{
msg.Write(eventData.Text);
msg.WriteString(eventData.Text);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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}");

View File

@@ -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)
{

View File

@@ -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:

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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)

View File

@@ -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)
{

View File

@@ -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;
}

View File

@@ -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 _);
}

View File

@@ -17,7 +17,7 @@ namespace Barotrauma.Networking
public void Write(IWriteMessage msg)
{
msg.Write(CharacterStateID);
msg.WriteUInt16(CharacterStateID);
serializable.ClientEventWrite(msg, Data);
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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()
{

View File

@@ -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"

View File

@@ -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),

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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();
}
}
}

View File

@@ -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];

View File

@@ -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));
}
};

View File

@@ -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)
{

View File

@@ -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; }

View File

@@ -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;
}
}

View File

@@ -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
{

View File

@@ -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"), "",

View File

@@ -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;

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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'">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'">

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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