From e83dba2959848a8a80cfebb21cd4b37572c21846 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 25 Apr 2019 18:07:37 +0300 Subject: [PATCH] (42d13c09f) Merge remote-tracking branch 'origin/tutorial-rework' into dev --- .../Source/Characters/Animation/Ragdoll.cs | 11 +- .../Source/Characters/Character.cs | 43 ++ .../Source/Characters/CharacterHUD.cs | 15 +- .../Characters/Health/CharacterHealth.cs | 11 + Barotrauma/BarotraumaClient/Source/GUI/GUI.cs | 14 +- .../BarotraumaClient/Source/GUI/GUIButton.cs | 5 + .../Source/GUI/GUIComponent.cs | 54 +- .../BarotraumaClient/Source/GUI/GUITextBox.cs | 4 +- .../Source/GUI/VideoPlayer.cs | 89 ++- .../BarotraumaClient/Source/GameMain.cs | 12 +- .../Source/GameSession/CrewManager.cs | 29 +- .../GameModes/Tutorials/BasicTutorial.cs | 106 ++-- .../GameModes/Tutorials/CaptainTutorial.cs | 244 ++++++++ .../GameModes/Tutorials/ContextualTutorial.cs | 277 +------- .../GameModes/Tutorials/DoctorTutorial.cs | 422 +++++++++++++ .../GameModes/Tutorials/EngineerTutorial.cs | 554 ++++++++++++++++ .../GameModes/Tutorials/MechanicTutorial.cs | 589 ++++++++++++++++++ .../GameModes/Tutorials/OfficerTutorial.cs | 452 ++++++++++++++ .../GameModes/Tutorials/ScenarioTutorial.cs | 228 ++++++- .../GameModes/Tutorials/Tutorial.cs | 484 ++++++++++++-- .../GameModes/Tutorials/TutorialMode.cs | 11 +- .../Components/Machines/Deconstructor.cs | 7 +- .../Items/Components/Machines/Fabricator.cs | 75 ++- .../Source/Items/Components/Machines/Pump.cs | 5 +- .../Items/Components/Machines/Reactor.cs | 18 + .../Source/Items/Components/Machines/Sonar.cs | 4 + .../Source/Items/Components/Repairable.cs | 4 + .../Items/Components/Signal/Connection.cs | 36 +- .../Components/Signal/CustomInterface.cs | 19 + .../Source/Items/Inventory.cs | 7 +- .../BarotraumaClient/Source/Items/Item.cs | 2 +- Barotrauma/BarotraumaClient/Source/Map/Gap.cs | 2 +- .../BarotraumaClient/Source/Map/Hull.cs | 4 +- .../Source/Map/LinkedSubmarine.cs | 2 +- .../BarotraumaClient/Source/Map/Structure.cs | 21 +- .../BarotraumaClient/Source/Map/WayPoint.cs | 2 +- .../Source/Screens/CampaignSetupUI.cs | 24 +- .../Source/Screens/MainMenuScreen.cs | 28 +- .../Source/Characters/Character.cs | 6 +- .../Source/Characters/CharacterInfo.cs | 25 +- .../Source/Items/Components/ItemComponent.cs | 5 +- .../Components/Machines/Deconstructor.cs | 5 + .../Items/Components/Machines/Fabricator.cs | 28 +- .../Source/Items/Inventory.cs | 10 + .../Source/Map/DummyFireSource.cs | 46 ++ .../BarotraumaShared/Source/Map/Explosion.cs | 12 +- .../BarotraumaShared/Source/Map/FireSource.cs | 28 +- .../Source/Map/Levels/Level.cs | 37 +- .../BarotraumaShared/Source/Map/MapEntity.cs | 8 +- .../BarotraumaShared/Source/Map/Submarine.cs | 8 +- .../BarotraumaShared/Source/Map/WayPoint.cs | 21 +- .../Source/Serialization/XMLExtensions.cs | 10 +- .../BarotraumaShared/Source/TextManager.cs | 10 + 53 files changed, 3623 insertions(+), 550 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs create mode 100644 Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/DoctorTutorial.cs create mode 100644 Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/EngineerTutorial.cs create mode 100644 Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs create mode 100644 Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/OfficerTutorial.cs create mode 100644 Barotrauma/BarotraumaShared/Source/Map/DummyFireSource.cs diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index 864fd5996..667d29d62 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -440,14 +440,15 @@ namespace Barotrauma return; } - //foreach (Limb limb in Limbs) - //{ - // limb.Draw(spriteBatch, cam); - //} + Color? color = null; + if (character.ExternalHighlight) + { + color = Color.Lerp(Color.White, Color.OrangeRed, (float)Math.Sin(Timing.TotalTime * 3.5f)); + } for (int i = 0; i < limbs.Length; i++) { - inversedLimbDrawOrder[i].Draw(spriteBatch, cam); + inversedLimbDrawOrder[i].Draw(spriteBatch, cam, color); } LimbJoints.ForEach(j => j.Draw(spriteBatch)); } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index 66ebbfb5b..2eff39aa6 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -33,6 +33,8 @@ namespace Barotrauma private List sounds; + public bool ExternalHighlight; + //the Character that the player is currently controlling private static Character controlled; @@ -109,6 +111,33 @@ namespace Barotrauma get { return gibEmitters; } } + public class ObjectiveEntity + { + public Entity Entity; + public Sprite Sprite; + public Color Color; + + public ObjectiveEntity(Entity entity, Sprite sprite, Color? color = null) + { + Entity = entity; + Sprite = sprite; + if (color.HasValue) + { + Color = color.Value; + } + else + { + Color = Color.White; + } + } + } + + private List activeObjectiveEntities = new List(); + public IEnumerable ActiveObjectiveEntities + { + get { return activeObjectiveEntities; } + } + partial void InitProjSpecific(XDocument doc) { soundInterval = doc.Root.GetAttributeFloat("soundinterval", 10.0f); @@ -732,6 +761,20 @@ namespace Barotrauma soundTimer = soundInterval; } + public void AddActiveObjectiveEntity(Entity entity, Sprite sprite, Color? color = null) + { + if (activeObjectiveEntities.Any(aoe => aoe.Entity == entity)) return; + ObjectiveEntity objectiveEntity = new ObjectiveEntity(entity, sprite, color); + activeObjectiveEntities.Add(objectiveEntity); + } + + public void RemoveActiveObjectiveEntity(Entity entity) + { + ObjectiveEntity found = activeObjectiveEntities.Find(aoe => aoe.Entity == entity); + if (found == null) return; + activeObjectiveEntities.Remove(found); + } + partial void ImplodeFX() { Vector2 centerOfMass = AnimController.GetCenterOfMass(); diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index a2137e660..441ae6a6e 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -168,7 +168,12 @@ namespace Barotrauma DrawOrderIndicator(spriteBatch, cam, character, character.CurrentOrder, 1.0f); } } - + + foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities) + { + DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f); + } + foreach (Item brokenItem in brokenItems) { float dist = Vector2.Distance(character.WorldPosition, brokenItem.WorldPosition); @@ -371,5 +376,13 @@ namespace Barotrauma orderIndicatorCount[target] = orderIndicatorCount[target] + 1; } + + private static void DrawObjectiveIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Character.ObjectiveEntity objectiveEntity, float iconAlpha = 1.0f) + { + if (objectiveEntity == null) return; + + Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f; + GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs index 3799e1756..34ebcb336 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs @@ -136,6 +136,17 @@ namespace Barotrauma } } + public GUIButton CPRButton + { + get { return cprButton; } + } + + public float HealthBarPulsateTimer + { + get { return healthBarPulsateTimer; } + set { healthBarPulsateTimer = MathHelper.Clamp(value, 0.0f, 10.0f); } + } + static CharacterHealth() { damageOverlay = new Sprite("Content/UI/damageOverlay.png", Vector2.Zero); diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs index dddd37a0f..90d96a1dd 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs @@ -129,6 +129,8 @@ namespace Barotrauma get { return pauseMenuOpen; } } + public static bool PreventPauseMenuToggle = false; + public static Color ScreenOverlayColor { get; @@ -1413,6 +1415,7 @@ namespace Barotrauma public static void TogglePauseMenu() { if (Screen.Selected == GameMain.MainMenuScreen) return; + if (PreventPauseMenuToggle) return; settingsMenuOpen = false; @@ -1546,9 +1549,16 @@ namespace Barotrauma if (GameMain.GameSession != null) { - if (ContextualTutorial.Initialized && GameMain.GameSession.GameMode is SinglePlayerCampaign) + if (Tutorial.Initialized) { - ((SinglePlayerCampaign)GameMain.GameSession.GameMode).ContextualTutorial.Stop(); + if (GameMain.GameSession.GameMode is SinglePlayerCampaign) + { + ((SinglePlayerCampaign)GameMain.GameSession.GameMode).ContextualTutorial.Stop(); + } + else + { + ((TutorialMode)GameMain.GameSession.GameMode).Tutorial.Stop(); + } } if (GameSettings.SendUserStatistics) diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIButton.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIButton.cs index 7917d904f..c4746fa69 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIButton.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIButton.cs @@ -168,6 +168,11 @@ namespace Barotrauma if (frame != null) frame.ApplyStyle(style); } + public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, Vector2? flashRectInflate = null) + { + Frame.Flash(color, flashDuration, useRectangleFlash, flashRectInflate); + } + protected override void Draw(SpriteBatch spriteBatch) { //do nothing diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs index c836b1f41..6f56b0654 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs @@ -121,11 +121,19 @@ namespace Barotrauma protected Color selectedColor; protected Color pressedColor; + private CoroutineHandle pulsateCoroutine; + protected ComponentState state; protected Color flashColor; protected float flashDuration = 1.5f; + private bool useRectangleFlash; + public float FlashTimer + { + get { return flashTimer; } + } protected float flashTimer; + private Vector2 flashRectInflate; public bool IgnoreLayoutGroups; @@ -261,6 +269,8 @@ namespace Barotrauma set { pressedColor = value; } } + public bool ExternalHighlight = false; + private RectTransform rectTransform; public RectTransform RectTransform { @@ -435,11 +445,21 @@ namespace Barotrauma int flashCycleCount = (int)Math.Max(flashDuration, 1); float flashCycleDuration = flashDuration / flashCycleCount; + Rectangle flashRect = Rect; + flashRect.Inflate(flashRectInflate.X, flashRectInflate.Y); + //MathHelper.Pi * 0.8f -> the curve goes from 144 deg to 0, //i.e. quickly bumps up from almost full brightness to full and then fades out - GUI.UIGlow.Draw(spriteBatch, - rect, - flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f)); + if (!useRectangleFlash) + { + GUI.UIGlow.Draw(spriteBatch, + flashRect, + flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f)); + } + else + { + GUI.DrawRectangle(spriteBatch, flashRect, flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), true); + } } } @@ -487,9 +507,11 @@ namespace Barotrauma color = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, a); } - public virtual void Flash(Color? color = null, float flashDuration = 1.5f) + public virtual void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, Vector2? flashRectInflate = null) { flashTimer = flashDuration; + this.flashRectInflate = flashRectInflate ?? Vector2.Zero; + this.useRectangleFlash = useRectangleFlash; this.flashDuration = flashDuration; flashColor = (color == null) ? Color.Red : (Color)color; } @@ -507,9 +529,7 @@ namespace Barotrauma while (t < duration) { t += CoroutineManager.DeltaTime; - SetAlpha(MathHelper.Lerp(startA, to, t / duration)); - yield return CoroutineStatus.Running; } @@ -523,6 +543,28 @@ namespace Barotrauma yield return CoroutineStatus.Success; } + public void Pulsate(Vector2 startScale, Vector2 endScale, float duration) + { + if (CoroutineManager.IsCoroutineRunning(pulsateCoroutine)) + { + return; + } + pulsateCoroutine = CoroutineManager.StartCoroutine(DoPulsate(startScale, endScale, duration), "Pulsate" + ToString()); + } + + private IEnumerable DoPulsate(Vector2 startScale, Vector2 endScale, float duration) + { + float t = 0.0f; + while (t < duration) + { + t += CoroutineManager.DeltaTime; + RectTransform.LocalScale = Vector2.Lerp(startScale, endScale, (float)Math.Sin(t / duration * MathHelper.Pi)); + yield return CoroutineStatus.Running; + } + RectTransform.LocalScale = startScale; + yield return CoroutineStatus.Success; + } + public virtual void ApplyStyle(GUIComponentStyle style) { if (style == null) return; diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs index 73fcd1b3a..886e5aa2c 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs @@ -365,9 +365,9 @@ namespace Barotrauma OnDeselected?.Invoke(this, Keys.None); } - public override void Flash(Color? color = null, float flashDuration = 1.5f) + public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, Vector2? flashRectOffset = null) { - textBlock.Flash(color, flashDuration); + textBlock.Flash(color, flashDuration, useRectangleFlash, flashRectOffset); } protected override void Update(float deltaTime) diff --git a/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs b/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs index bb327ac69..83a9e98f6 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs @@ -10,9 +10,10 @@ namespace Barotrauma { class VideoPlayer { + public bool IsPlaying; + private Video currentVideo; private string filePath; - private bool isPlaying; private GUIFrame background, videoFrame, textFrame; private GUITextBlock title, textContent, objectiveTitle, objectiveText; @@ -29,7 +30,9 @@ namespace Barotrauma private readonly int objectiveFrameHeight = 60; private readonly int textHeight = 25; - public struct TextSettings + private bool useTextOnRightSide = false; + + public class TextSettings { public string Text; public int Width; @@ -41,7 +44,7 @@ namespace Barotrauma } } - public struct VideoSettings + public class VideoSettings { public string File; @@ -62,7 +65,14 @@ namespace Barotrauma background = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas, Anchor.Center), "InnerFrame", backgroundColor); videoFrame = new GUIFrame(new RectTransform(Point.Zero, background.RectTransform, Anchor.Center, Pivot.Center), "SonarFrame"); - textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), "TextFrame"); + if (useTextOnRightSide) + { + textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), "TextFrame"); + } + else + { + textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), "TextFrame"); + } videoView = new GUICustomComponent(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.Center), (spriteBatch, guiCustomComponent) => { DrawVideo(spriteBatch, guiCustomComponent.Rect); }); title = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.VideoTitleFont, textColor: new Color(253, 174, 0), textAlignment: Alignment.Left); @@ -70,7 +80,7 @@ namespace Barotrauma textContent = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.Font, textAlignment: Alignment.TopLeft); objectiveTitle = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight, textColor: Color.White); - objectiveTitle.Text = TextManager.Get("NewObjective"); + objectiveTitle.Text = TextManager.Get("Tutorial.NewObjective"); objectiveText = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.ObjectiveNameFont, textColor: new Color(4, 180, 108), textAlignment: Alignment.CenterRight); objectiveTitle.Visible = objectiveText.Visible = false; @@ -78,12 +88,12 @@ namespace Barotrauma public void Play() { - isPlaying = true; + IsPlaying = true; } public void Stop() { - isPlaying = false; + IsPlaying = false; if (currentVideo == null) return; currentVideo.Dispose(); currentVideo = null; @@ -99,13 +109,6 @@ namespace Barotrauma public void Update() { if (currentVideo == null) return; - - if (PlayerInput.KeyHit(Keys.Enter) || PlayerInput.KeyHit(Keys.Escape)) - { - DisposeVideo(null, null); - return; - } - if (currentVideo.IsPlaying) return; currentVideo.Dispose(); @@ -115,7 +118,7 @@ namespace Barotrauma public void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0) { - if (!isPlaying) return; + if (!IsPlaying) return; background.AddToGUIUpdateList(ignoreChildren, order); } @@ -139,7 +142,7 @@ namespace Barotrauma currentVideo = CreateVideo(scaledVideoResolution); title.Text = TextManager.Get(contentId); - textContent.Text = textSettings.Text; + textContent.Text = textSettings != null ? textSettings.Text : string.Empty; objectiveText.Text = objective; AdjustFrames(videoSettings, textSettings); @@ -165,7 +168,8 @@ namespace Barotrauma title.TextScale = textContent.TextScale = objectiveText.TextScale = objectiveTitle.TextScale = GUI.Scale; int scaledBorderSize = (int)(borderSize * GUI.Scale); - int scaledTextWidth = (int)(textSettings.Width * GUI.Scale); + int scaledTextWidth = 0; + if (textSettings != null) scaledTextWidth = useTextOnRightSide ? (int)(textSettings.Width * GUI.Scale) : scaledVideoResolution.X / 2; int scaledTitleHeight = (int)(titleHeight * GUI.Scale); int scaledTextHeight = (int)(textHeight * GUI.Scale); int scaledObjectiveFrameHeight = (int)(objectiveFrameHeight * GUI.Scale); @@ -180,13 +184,21 @@ namespace Barotrauma title.RectTransform.NonScaledSize += new Point(scaledTextWidth, scaledTitleHeight); title.RectTransform.AbsoluteOffset = new Point((int)(5 * GUI.Scale), (int)(10 * GUI.Scale)); - if (!string.IsNullOrEmpty(textSettings.Text)) + if (textSettings != null && !string.IsNullOrEmpty(textSettings.Text)) { textSettings.Text = ToolBox.WrapText(textSettings.Text, scaledTextWidth, GUI.Font); int wrappedHeight = textSettings.Text.Split('\n').Length * scaledTextHeight; textFrame.RectTransform.NonScaledSize += new Point(scaledTextWidth + scaledBorderSize, wrappedHeight + scaledBorderSize + scaledButtonSize.Y + scaledTitleHeight); - textFrame.RectTransform.AbsoluteOffset = new Point(scaledVideoResolution.X + scaledBorderSize * 2, 0); + + if (useTextOnRightSide) + { + textFrame.RectTransform.AbsoluteOffset = new Point(scaledVideoResolution.X + scaledBorderSize * 2, 0); + } + else + { + textFrame.RectTransform.AbsoluteOffset = new Point(0, scaledVideoResolution.Y + scaledBorderSize * 2); + } textContent.RectTransform.NonScaledSize += new Point(scaledTextWidth, wrappedHeight); textContent.RectTransform.AbsoluteOffset = new Point(0, scaledBorderSize + scaledTitleHeight); @@ -209,22 +221,41 @@ namespace Barotrauma objectiveTitle.Visible = objectiveText.Visible = false; } - int totalFrameWidth = videoFrame.Rect.Width + textFrame.Rect.Width + scaledBorderSize * 2; - int xOffset = videoFrame.Rect.Width / 2 + scaledBorderSize - (videoFrame.Rect.Width / 2 - textFrame.Rect.Width / 2); - - - videoFrame.RectTransform.AbsoluteOffset = new Point(-xOffset, (int)(50 * GUI.Scale)); - if (okButton != null) { textFrame.RemoveChild(okButton); okButton = null; } - okButton = new GUIButton(new RectTransform(scaledButtonSize, textFrame.RectTransform, Anchor.BottomRight, Pivot.BottomRight) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("OK")) + if (textSettings != null) { - OnClicked = DisposeVideo - }; + if (useTextOnRightSide) + { + int totalFrameWidth = videoFrame.Rect.Width + textFrame.Rect.Width + scaledBorderSize * 2; + int xOffset = videoFrame.Rect.Width / 2 + scaledBorderSize - (videoFrame.Rect.Width / 2 - textFrame.Rect.Width / 2); + videoFrame.RectTransform.AbsoluteOffset = new Point(-xOffset, (int)(50 * GUI.Scale)); + } + else + { + int totalFrameHeight = videoFrame.Rect.Height + textFrame.Rect.Height + scaledBorderSize * 2; + int yOffset = videoFrame.Rect.Height / 2 + scaledBorderSize - (videoFrame.Rect.Height / 2 - textFrame.Rect.Height / 2); + videoFrame.RectTransform.AbsoluteOffset = new Point(0, -yOffset); + } + + okButton = new GUIButton(new RectTransform(scaledButtonSize, textFrame.RectTransform, Anchor.BottomRight, Pivot.BottomRight) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("OK")) + { + OnClicked = DisposeVideo + }; + } + else + { + videoFrame.RectTransform.AbsoluteOffset = new Point(0, (int)(100 * GUI.Scale)); + + okButton = new GUIButton(new RectTransform(scaledButtonSize, videoFrame.RectTransform, Anchor.TopLeft, Pivot.Center), TextManager.Get("Back")) + { + OnClicked = DisposeVideo + }; + } } private Video CreateVideo(Point resolution) @@ -245,7 +276,7 @@ namespace Barotrauma private void DrawVideo(SpriteBatch spriteBatch, Rectangle rect) { - if (!isPlaying) return; + if (!IsPlaying) return; spriteBatch.Draw(currentVideo.GetTexture(), rect, Color.White); } diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index 8756a1c17..4e0b61956 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -615,8 +615,12 @@ namespace Barotrauma { ((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); } + else if (Tutorial.Initialized && Tutorial.ContentRunning) + { + (GameMain.GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI(); + } else if ((Character.Controlled?.SelectedConstruction == null || !Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null)) - && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null) + && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null) { // Otherwise toggle pausing, unless another window/interface is open. GUI.TogglePauseMenu(); @@ -624,7 +628,7 @@ namespace Barotrauma } GUI.ClearUpdateList(); - paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen || ContextualTutorial.ContentRunning) && + paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen || Tutorial.ContentRunning) && (NetworkMember == null || !NetworkMember.GameStarted); Screen.Selected.AddToGUIUpdateList(); @@ -643,9 +647,9 @@ namespace Barotrauma { Screen.Selected.Update(Timing.Step); } - else if (ContextualTutorial.Initialized && ContextualTutorial.ContentRunning && GameSession.GameMode is SinglePlayerCampaign) + else if (Tutorial.Initialized && Tutorial.ContentRunning) { - (GameSession.GameMode as SinglePlayerCampaign).ContextualTutorial.Update((float)Timing.Step); + (GameSession.GameMode as TutorialMode).Update((float)Timing.Step); } if (NetworkMember != null) diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index 62060b55d..932a2caf8 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -49,6 +49,8 @@ namespace Barotrauma private GUIComponent orderTargetFrame, orderTargetFrameShadow; + public bool AllowCharacterSwitch = true; + public bool ToggleCrewAreaOpen { get { return toggleCrewAreaOpen; } @@ -473,7 +475,10 @@ namespace Barotrauma orderButtonFrame.RectTransform; var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft), - style: null); + style: null) + { + UserData = order + }; new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow") { @@ -565,6 +570,7 @@ namespace Barotrauma /// public bool CharacterClicked(GUIComponent component, object selection) { + if (!AllowCharacterSwitch) { return false; } Character character = selection as Character; if (character == null || character.IsDead || character.IsUnconscious) return false; SelectCharacter(character); @@ -961,6 +967,24 @@ namespace Barotrauma color: matchingItems.Count > 1 ? Color.Black * 0.9f : Color.Black * 0.7f); } + public void HighlightOrderButton(Character character, string orderAiTag, Color color, Vector2? flashRectInflate = null) + { + var order = Order.PrefabList.Find(o => o.AITag == orderAiTag); + if (order == null) + { + DebugConsole.ThrowError("Could not find an order with the AI tag \"" + orderAiTag + "\".\n" + Environment.StackTrace); + return; + } + var characterElement = characterListBox.Content.FindChild(character); + GUIButton orderBtn = characterElement.FindChild(order, recursive: true) as GUIButton; + if (orderBtn.Frame.FlashTimer <= 0) + { + orderBtn.Flash(color, 1.5f, false, flashRectInflate); + } + + //orderBtn.Pulsate(Vector2.One, Vector2.One * 2.0f, 1.5f); + } + #region Updating and drawing the UI private void DrawMiniMapOverlay(SpriteBatch spriteBatch, GUICustomComponent container) @@ -1018,6 +1042,7 @@ namespace Barotrauma public void SelectNextCharacter() { + if (!AllowCharacterSwitch) { return; } if (GameMain.IsMultiplayer) { return; } if (characters.None()) { return; } SelectCharacter(characters[TryAdjustIndex(1)]); @@ -1025,6 +1050,7 @@ namespace Barotrauma public void SelectPreviousCharacter() { + if (!AllowCharacterSwitch) { return; } if (GameMain.IsMultiplayer) { return; } if (characters.None()) { return; } SelectCharacter(characters[TryAdjustIndex(-1)]); @@ -1032,6 +1058,7 @@ namespace Barotrauma private void SelectCharacter(Character character) { + if (!AllowCharacterSwitch) { return; } //make the previously selected character wait in place for some time //(so they don't immediately start idling and walking away from their station) if (Character.Controlled?.AIController?.ObjectiveManager != null) diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs index c06fc83f9..0a0677484 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs @@ -38,13 +38,13 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(4.0f); - infoBox = CreateInfoFrame("Use WASD to move and the mouse to look around"); + infoBox = CreateInfoFrame("", "Use WASD to move and the mouse to look around"); yield return new WaitForSeconds(5.0f); //----------------------------------- - infoBox = CreateInfoFrame("Open the door at your right side by highlighting the button next to it with your cursor and pressing E"); + infoBox = CreateInfoFrame("", "Open the door at your right side by highlighting the button next to it with your cursor and pressing E"); Door tutorialDoor = Item.ItemList.Find(i => i.HasTag("tutorialdoor")).GetComponent(); @@ -57,7 +57,7 @@ namespace Barotrauma.Tutorials //----------------------------------- - infoBox = CreateInfoFrame("Hold W or S to walk up or down stairs. Use shift to run.", true); + infoBox = CreateInfoFrame("", "Hold W or S to walk up or down stairs. Use shift to run.", hasButton: true); while (infoBox != null) { @@ -66,7 +66,7 @@ namespace Barotrauma.Tutorials //----------------------------------- - infoBox = CreateInfoFrame("At the moment the submarine has no power, which means that crucial systems such as the oxygen generator or the engine aren't running. Let's fix this: go to the upper left corner of the submarine, where you'll find a nuclear reactor."); + infoBox = CreateInfoFrame("", "At the moment the submarine has no power, which means that crucial systems such as the oxygen generator or the engine aren't running. Let's fix this: go to the upper left corner of the submarine, where you'll find a nuclear reactor."); Reactor reactor = Item.ItemList.Find(i => i.HasTag("tutorialreactor")).GetComponent(); //reactor.MeltDownTemp = 20000.0f; @@ -76,21 +76,21 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The reactor requires fuel rods to generate power. You can grab one from the steel cabinet by walking next to it and pressing E."); + infoBox = CreateInfoFrame("", "The reactor requires fuel rods to generate power. You can grab one from the steel cabinet by walking next to it and pressing E."); while (Controlled.SelectedConstruction == null || Controlled.SelectedConstruction.Prefab.Identifier != "steelcabinet") { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Pick up one of the fuel rods either by double-clicking or dragging and dropping it into your inventory."); + infoBox = CreateInfoFrame("", "Pick up one of the fuel rods either by double-clicking or dragging and dropping it into your inventory."); while (!HasItem("fuelrod")) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Select the reactor by walking next to it and pressing E."); + infoBox = CreateInfoFrame("", "Select the reactor by walking next to it and pressing E."); while (Controlled.SelectedConstruction != reactor.Item) { @@ -98,14 +98,14 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("Load the fuel rod into the reactor by dropping it into any of the 5 slots."); + infoBox = CreateInfoFrame("", "Load the fuel rod into the reactor by dropping it into any of the 5 slots."); while (reactor.AvailableFuel <= 0.0f) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The reactor is now fueled up. Try turning it on by increasing the fission rate."); + infoBox = CreateInfoFrame("", "The reactor is now fueled up. Try turning it on by increasing the fission rate."); while (reactor.FissionRate <= 0.0f) { @@ -113,8 +113,8 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("The reactor core has started generating heat, which in turn generates power for the submarine. The power generation is very low at the moment," - + " because the reactor is set to shut itself down when the temperature rises above 500 degrees Celsius. You can adjust the temperature limit by changing the \"Shutdown Temperature\" in the control panel.", true); + infoBox = CreateInfoFrame("", "The reactor core has started generating heat, which in turn generates power for the submarine. The power generation is very low at the moment," + + " because the reactor is set to shut itself down when the temperature rises above 500 degrees Celsius. You can adjust the temperature limit by changing the \"Shutdown Temperature\" in the control panel.", hasButton: true); //TODO: reimplement /*while (infoBox != null) @@ -146,7 +146,7 @@ namespace Barotrauma.Tutorials }*/ yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("That's the basics of operating the reactor! Now that there's power available for the engines, it's time to get the submarine moving. " + infoBox = CreateInfoFrame("", "That's the basics of operating the reactor! Now that there's power available for the engines, it's time to get the submarine moving. " + "Deselect the reactor by pressing E and head to the command room at the right edge of the vessel."); Steering steering = Item.ItemList.Find(i => i.HasTag("tutorialsteering")).GetComponent(); @@ -159,7 +159,7 @@ namespace Barotrauma.Tutorials CoroutineManager.StartCoroutine(KeepReactorRunning(reactor)); - infoBox = CreateInfoFrame("Select the navigation terminal by walking next to it and pressing E."); + infoBox = CreateInfoFrame("", "Select the navigation terminal by walking next to it and pressing E."); while (Controlled.SelectedConstruction != steering.Item) { @@ -167,7 +167,7 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("There seems to be something wrong with the navigation terminal." + + infoBox = CreateInfoFrame("", "There seems to be something wrong with the navigation terminal." + " There's nothing on the monitor, so it's probably out of power. The reactor must still be" + " running or the lights would've gone out, so it's most likely a problem with the wiring." + " Deselect the terminal by pressing E to start checking the wiring."); @@ -178,7 +178,7 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(1.0f); - infoBox = CreateInfoFrame("You need a screwdriver to check the wiring of the terminal." + infoBox = CreateInfoFrame("", "You need a screwdriver to check the wiring of the terminal." + " Equip a screwdriver by pulling it to either of the slots with a hand symbol, and then use it on the terminal by left clicking."); while (Controlled.SelectedConstruction != steering.Item || @@ -188,7 +188,7 @@ namespace Barotrauma.Tutorials } - infoBox = CreateInfoFrame("Here you can see all the wires connected to the terminal. Apparently there's no wire" + infoBox = CreateInfoFrame("", "Here you can see all the wires connected to the terminal. Apparently there's no wire" + " going into the to the power connection - that's why the monitor isn't working." + " You should find a piece of wire to connect it. Try searching some of the cabinets scattered around the sub."); @@ -197,7 +197,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Head back to the navigation terminal to fix the wiring."); + infoBox = CreateInfoFrame("", "Head back to the navigation terminal to fix the wiring."); PowerTransfer junctionBox = Item.ItemList.Find(i => i != null && i.HasTag("tutorialjunctionbox")).GetComponent(); @@ -210,7 +210,7 @@ namespace Barotrauma.Tutorials if (Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent() != null) == null) { - infoBox = CreateInfoFrame("Equip the wire by dragging it to one of the slots with a hand symbol."); + infoBox = CreateInfoFrame("", "Equip the wire by dragging it to one of the slots with a hand symbol."); while (Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent() != null) == null) { @@ -218,7 +218,7 @@ namespace Barotrauma.Tutorials } } - infoBox = CreateInfoFrame("You can see the equipped wire at the middle of the connection panel. Drag it to the power connector."); + infoBox = CreateInfoFrame("", "You can see the equipped wire at the middle of the connection panel. Drag it to the power connector."); var steeringConnection = steering.Item.Connections.Find(c => c.Name.Contains("power")); @@ -228,7 +228,7 @@ namespace Barotrauma.Tutorials } - infoBox = CreateInfoFrame("Now you have to connect the other end of the wire to a power source. " + infoBox = CreateInfoFrame("", "Now you have to connect the other end of the wire to a power source. " + "The junction box in the room just below the command room should do."); while (Controlled.SelectedConstruction != null) @@ -238,7 +238,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(2.0f); - infoBox = CreateInfoFrame("You can now move the other end of the wire around, and attach it on the wall by left clicking or " + infoBox = CreateInfoFrame("", "You can now move the other end of the wire around, and attach it on the wall by left clicking or " + "remove the previous attachment by right clicking. Or if you don't care for neatly laid out wiring, you can just " + "run it straight to the junction box."); @@ -247,14 +247,14 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Connect the wire to the junction box by pulling it to the power connection, the same way you did with the navigation terminal."); + infoBox = CreateInfoFrame("", "Connect the wire to the junction box by pulling it to the power connection, the same way you did with the navigation terminal."); while (sonar.Voltage < 0.1f) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Great! Now we should be able to get moving."); + infoBox = CreateInfoFrame("", "Great! Now we should be able to get moving."); while (Controlled.SelectedConstruction != steering.Item) @@ -262,7 +262,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("You can take a look at the area around the sub by selecting the \"Active Sonar\" checkbox."); + infoBox = CreateInfoFrame("", "You can take a look at the area around the sub by selecting the \"Active Sonar\" checkbox."); while (!sonar.IsActive) { @@ -270,7 +270,7 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("The blue rectangle in the middle is the submarine, and the flickering shapes outside it are the walls of an underwater cavern. " + infoBox = CreateInfoFrame("", "The blue rectangle in the middle is the submarine, and the flickering shapes outside it are the walls of an underwater cavern. " + "Try moving the submarine by clicking somewhere on the monitor and dragging the pointer to the direction you want to go to."); while (steering.TargetVelocity == Vector2.Zero && steering.TargetVelocity.Length() < 50.0f) @@ -279,15 +279,15 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(4.0f); - infoBox = CreateInfoFrame("The submarine moves up and down by pumping water in and out of the two ballast tanks at the bottom of the submarine. " - + "The engine at the back of the sub moves it forwards and backwards.", true); + infoBox = CreateInfoFrame("", "The submarine moves up and down by pumping water in and out of the two ballast tanks at the bottom of the submarine. " + + "The engine at the back of the sub moves it forwards and backwards.", hasButton: true); while (infoBox != null) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Steer the submarine downwards, heading further into the cavern."); + infoBox = CreateInfoFrame("", "Steer the submarine downwards, heading further into the cavern."); while (Submarine.MainSub.WorldPosition.Y > 32000.0f) { @@ -303,7 +303,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f); - infoBox = CreateInfoFrame("Uh-oh... Something enormous just appeared on the sonar."); + infoBox = CreateInfoFrame("", "Uh-oh... Something enormous just appeared on the sonar."); List windows = new List(); foreach (Structure s in Structure.WallList) @@ -370,7 +370,7 @@ namespace Barotrauma.Tutorials var capacitor2 = Item.ItemList.Find(i => i.HasTag("capacitor1")).GetComponent(); CoroutineManager.StartCoroutine(KeepEnemyAway(moloch, new PowerContainer[] { capacitor1, capacitor2 })); - infoBox = CreateInfoFrame("The hull has been breached! Close all the doors to the command room to stop the water from flooding the entire sub!"); + infoBox = CreateInfoFrame("", "The hull has been breached! Close all the doors to the command room to stop the water from flooding the entire sub!"); Door commandDoor1 = Item.ItemList.Find(i => i.HasTag("commanddoor1")).GetComponent(); Door commandDoor2 = Item.ItemList.Find(i => i.HasTag("commanddoor2")).GetComponent(); @@ -385,7 +385,7 @@ namespace Barotrauma.Tutorials } - infoBox = CreateInfoFrame("You should quickly find yourself a diving mask or a diving suit. " + + infoBox = CreateInfoFrame("", "You should quickly find yourself a diving mask or a diving suit. " + "There are some in the room next to the airlock."); bool divingMaskSelected = false; @@ -395,7 +395,7 @@ namespace Barotrauma.Tutorials if (!divingMaskSelected && Controlled.FocusedItem != null && Controlled.FocusedItem.Prefab.Identifier == "divingsuit") { - infoBox = CreateInfoFrame("There can only be one item in each inventory slot, so you need to take off " + infoBox = CreateInfoFrame("", "There can only be one item in each inventory slot, so you need to take off " + "the jumpsuit if you wish to wear a diving suit."); divingMaskSelected = true; @@ -406,13 +406,13 @@ namespace Barotrauma.Tutorials if (HasItem("divingmask")) { - infoBox = CreateInfoFrame("The diving mask will let you breathe underwater, but it won't protect from the water pressure outside the sub. " + + infoBox = CreateInfoFrame("", "The diving mask will let you breathe underwater, but it won't protect from the water pressure outside the sub. " + "It should be fine for the situation at hand, but you still need to find an oxygen tank and drag it into the same slot as the mask." + "You should grab one or two from one of the cabinets."); } else if (HasItem("divingsuit")) { - infoBox = CreateInfoFrame("In addition to letting you breathe underwater, the suit will protect you from the water pressure outside the sub " + + infoBox = CreateInfoFrame("", "In addition to letting you breathe underwater, the suit will protect you from the water pressure outside the sub " + "(unlike the diving mask). However, you still need to drag an oxygen tank into the same slot as the suit to supply oxygen. " + "You should grab one or two from one of the cabinets."); } @@ -424,7 +424,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(5.0f); - infoBox = CreateInfoFrame("Now you should stop the creature attacking the submarine before it does any more damage. Head to the railgun room at the upper right corner of the sub."); + infoBox = CreateInfoFrame("", "Now you should stop the creature attacking the submarine before it does any more damage. Head to the railgun room at the upper right corner of the sub."); var railGun = Item.ItemList.Find(i => i.GetComponent() != null); @@ -433,7 +433,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f); } - infoBox = CreateInfoFrame("The railgun requires a large power surge to fire. The reactor can't provide a surge large enough, so we need to use the " + infoBox = CreateInfoFrame("", "The railgun requires a large power surge to fire. The reactor can't provide a surge large enough, so we need to use the " + " supercapacitors in the railgun room. The capacitors need to be charged first; select them and crank up the recharge rate."); while (capacitor1.RechargeSpeed < 0.5f && capacitor2.RechargeSpeed < 0.5f) @@ -441,7 +441,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f); } - infoBox = CreateInfoFrame("The capacitors take some time to recharge, so now is a good " + + infoBox = CreateInfoFrame("", "The capacitors take some time to recharge, so now is a good " + "time to head to the room below and load some shells for the railgun."); @@ -452,7 +452,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Grab one of the shells. You can load it by selecting the railgun loader and dragging the shell to. " + infoBox = CreateInfoFrame("", "Grab one of the shells. You can load it by selecting the railgun loader and dragging the shell to. " + "one of the free slots. You need two hands to carry a shell, so make sure you don't have anything else in either hand."); while (loader.Item.ContainedItems.FirstOrDefault(i => i != null && i.Prefab.Identifier == "railgunshell") == null) @@ -465,7 +465,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Now we're ready to shoot! Select the railgun controller."); + infoBox = CreateInfoFrame("", "Now we're ready to shoot! Select the railgun controller."); while (Controlled.SelectedConstruction == null || Controlled.SelectedConstruction.Prefab.Identifier != "railguncontroller") { @@ -474,7 +474,7 @@ namespace Barotrauma.Tutorials moloch.AnimController.SetPosition(ConvertUnits.ToSimUnits(Controlled.WorldPosition + Vector2.UnitY * 600.0f)); - infoBox = CreateInfoFrame("Use the right mouse button to aim and wait for the creature to come closer. When you're ready to shoot, " + infoBox = CreateInfoFrame("", "Use the right mouse button to aim and wait for the creature to come closer. When you're ready to shoot, " + "press the left mouse button."); while (!moloch.IsDead) @@ -490,7 +490,7 @@ namespace Barotrauma.Tutorials Submarine.MainSub.GodMode = false; - infoBox = CreateInfoFrame("The creature has died. Now you should fix the damages in the control room: " + + infoBox = CreateInfoFrame("", "The creature has died. Now you should fix the damages in the control room: " + "Grab a welding tool from the closet in the railgun room."); while (!HasItem("weldingtool")) @@ -498,7 +498,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The welding tool requires fuel to work. Grab a welding fuel tank and attach it to the tool " + + infoBox = CreateInfoFrame("", "The welding tool requires fuel to work. Grab a welding fuel tank and attach it to the tool " + "by dragging it into the same slot."); do @@ -511,7 +511,7 @@ namespace Barotrauma.Tutorials } while (true); - infoBox = CreateInfoFrame("You can aim with the tool using the right mouse button and weld using the left button. " + + infoBox = CreateInfoFrame("", "You can aim with the tool using the right mouse button and weld using the left button. " + "Head to the command room to fix the leaks there."); do @@ -531,7 +531,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f); } while (broken); - infoBox = CreateInfoFrame("The hull is fixed now, but there's still quite a bit of water inside the sub. It should be pumped out " + infoBox = CreateInfoFrame("", "The hull is fixed now, but there's still quite a bit of water inside the sub. It should be pumped out " + "using the bilge pump in the room at the bottom of the submarine."); Pump pump = Item.ItemList.Find(i => i.HasTag("tutorialpump")).GetComponent(); @@ -541,10 +541,10 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The two pumps inside the ballast tanks " + infoBox = CreateInfoFrame("", "The two pumps inside the ballast tanks " + "are connected straight to the navigation terminal and can't be manually controlled unless you mess with their wiring, " + "so you should only use the pump in the middle room to pump out the water. Select it, turn it on and adjust the pumping speed " + - "to start pumping water out.", true); + "to start pumping water out.", hasButton: true); while (infoBox != null) { @@ -562,8 +562,8 @@ namespace Barotrauma.Tutorials { brokenMsgShown = true; - infoBox = CreateInfoFrame("Looks like the pump isn't getting any power. The water must have short-circuited some of the junction " - +"boxes. You can check which boxes are broken by selecting them."); + infoBox = CreateInfoFrame("", "Looks like the pump isn't getting any power. The water must have short-circuited some of the junction " + + "boxes. You can check which boxes are broken by selecting them."); while (true) { @@ -573,8 +573,8 @@ namespace Barotrauma.Tutorials { brokenBox = Controlled.SelectedConstruction; - infoBox = CreateInfoFrame("Here's our problem: this junction box is broken. Luckily engineers are adept at fixing electrical devices - " - +"you just need to find a spare wire and click the \"Fix\"-button to repair the box."); + infoBox = CreateInfoFrame("", "Here's our problem: this junction box is broken. Luckily engineers are adept at fixing electrical devices - " + + "you just need to find a spare wire and click the \"Fix\"-button to repair the box."); break; } @@ -590,7 +590,7 @@ namespace Barotrauma.Tutorials if (pump.Voltage < pump.MinVoltage) { - infoBox = CreateInfoFrame("The pump is still not running. Check if there are more broken junction boxes between the pump and the reactor."); + infoBox = CreateInfoFrame("", "The pump is still not running. Check if there are more broken junction boxes between the pump and the reactor."); } brokenBox = null; } @@ -598,14 +598,14 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The pump is up and running. Wait for the water to be drained out."); + infoBox = CreateInfoFrame("", "The pump is up and running. Wait for the water to be drained out."); while (pump.Item.CurrentHull.WaterVolume > 1000.0f) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("That was all there is to this tutorial! Now you should be able to handle " + + infoBox = CreateInfoFrame("", "That was all there is to this tutorial! Now you should be able to handle " + "most of the basic tasks on board the submarine."); Completed = true; diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs new file mode 100644 index 000000000..35520cad9 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs @@ -0,0 +1,244 @@ +using System.Collections.Generic; +using System.Xml.Linq; +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma.Tutorials +{ + class CaptainTutorial : ScenarioTutorial + { + // Room 1 + private float shakeTimer = 1f; + private float shakeAmount = 20f; + + // Room 2 + private MotionSensor captain_equipmentObjectiveSensor; + private ItemContainer captain_equipmentCabinet; + 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 CustomInterface captain_navConsoleCustomInterface; + 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 string radioSpeakerName; + private Sprite captain_steerIcon; + private Color captain_steerIconColor; + + public CaptainTutorial(XElement element) : base(element) + { + } + + public override void Start() + { + base.Start(); + + captain = Character.Controlled; + radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman"); + GameMain.GameSession.CrewManager.AllowCharacterSwitch = false; + + var revolver = captain.Inventory.FindItemByIdentifier("revolver"); + revolver.Unequip(captain); + captain.Inventory.RemoveItem(revolver); + + var captainscap = captain.Inventory.FindItemByIdentifier("captainscap"); + captainscap.Unequip(captain); + captain.Inventory.RemoveItem(captainscap); + + var captainsuniform = captain.Inventory.FindItemByIdentifier("captainsuniform"); + captainsuniform.Unequip(captain); + captain.Inventory.RemoveItem(captainsuniform); + + var steerOrder = Order.PrefabList.Find(order => order.AITag == "steer"); + captain_steerIcon = steerOrder.SymbolSprite; + captain_steerIconColor = steerOrder.Color; + + // Room 2 + captain_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_equipmentobjectivesensor")).GetComponent(); + captain_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("captain_equipmentcabinet")).GetComponent(); + captain_firstDoor = Item.ItemList.Find(i => i.HasTag("captain_firstdoor")).GetComponent(); + captain_firstDoorLight = Item.ItemList.Find(i => i.HasTag("captain_firstdoorlight")).GetComponent(); + + SetDoorAccess(captain_firstDoor, captain_firstDoorLight, true); + + // Room 3 + captain_medicObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_medicobjectivesensor")).GetComponent(); + captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition; + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + var medicInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "medicaldoctor")); + 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(); + tutorial_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent(); + captain_navConsole = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + captain_navConsoleCustomInterface = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + captain_sonar = captain_navConsole.Item.GetComponent(); + 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(); + tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent(); + SetDoorAccess(tutorial_lockedDoor_1, null, false); + SetDoorAccess(tutorial_lockedDoor_2, null, false); + + var mechanicInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "mechanic")); + captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic"); + captain_mechanic.GiveJobItems(); + + var securityInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "securityofficer")); + captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer"); + captain_security.GiveJobItems(); + + var engineerInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")); + captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, 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; + } + + public override IEnumerable 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); + } + + // 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); + GameMain.GameSession.CrewManager.ToggleCrewAreaOpen = true; + GameMain.GameSession.CrewManager.AddCharacter(captain_medic); + TriggerTutorialSegment(0); + do + { + yield return new WaitForSeconds(1.5f); + GameMain.GameSession.CrewManager.HighlightOrderButton(captain_medic, "follow", highlightColor, new Vector2(5, 5)); + } + while (!HasOrder(captain_medic, "follow")); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + RemoveCompletedObjective(segments[0]); + + // Submarine + do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected); + yield return new WaitForSeconds(3f); + captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = true; + TriggerTutorialSegment(1); + GameMain.GameSession.CrewManager.AddCharacter(captain_mechanic); + do + { + yield return new WaitForSeconds(1.5f); + GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5)); + } + while (!HasOrder(captain_mechanic, "repairsystems")); + RemoveCompletedObjective(segments[1]); + yield return new WaitForSeconds(2f); + TriggerTutorialSegment(2); + GameMain.GameSession.CrewManager.AddCharacter(captain_security); + do + { + yield return new WaitForSeconds(1.5f); + GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5)); + } + while (!HasOrder(captain_security, "operateweapons")); + RemoveCompletedObjective(segments[2]); + yield return new WaitForSeconds(4f); + TriggerTutorialSegment(3); + GameMain.GameSession.CrewManager.AddCharacter(captain_engineer); + do + { + yield return new WaitForSeconds(1.5f); + GameMain.GameSession.CrewManager.HighlightOrderButton(captain_engineer, "operatereactor", highlightColor, new Vector2(5, 5)); + } + while (!HasOrder(captain_engineer, "operatereactor", "powerup")); + RemoveCompletedObjective(segments[3]); + tutorial_submarineReactor.CanBeSelected = true; + 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); + } while (Submarine.MainSub.DockedTo.Count > 0); + RemoveCompletedObjective(segments[4]); + yield return new WaitForSeconds(2f); + TriggerTutorialSegment(5); // Navigate to destination + do + { + if (IsSelectedItem(captain_navConsole.Item)) + { + if (captain_sonar.ActiveTickBox.Box.FlashTimer <= 0) + { + captain_sonar.ActiveTickBox.Box.Flash(highlightColor, 1.5f, false, new Vector2(2.5f, 2.5f)); + //captain_sonar.ActiveTickBox.Box.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.5f); + } + } + yield return null; + } while (!captain_sonar.IsActive); + do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f); + RemoveCompletedObjective(segments[5]); + yield return new WaitForSeconds(4f); + TriggerTutorialSegment(6); // Docking + do + { + captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); + yield return new WaitForSeconds(1.0f); + } while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0); + RemoveCompletedObjective(segments[6]); + yield return new WaitForSeconds(3f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Captain.Radio.Complete").Replace("[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); + + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + private bool IsSelectedItem(Item item) + { + return captain?.SelectedConstruction == item; + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs index d8ac5bc80..b883c43ed 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs @@ -4,22 +4,12 @@ using System; using Microsoft.Xna.Framework; using Barotrauma.Items.Components; using System.Linq; -using Microsoft.Xna.Framework.Input; namespace Barotrauma.Tutorials { class ContextualTutorial : Tutorial { public static bool Selected = false; - public static bool ContentRunning = false; - public static bool Initialized = false; - - private enum ContentTypes { None = 0, Video = 1, TextOnly = 2 }; - - private TutorialSegment activeSegment; - private List segments; - - private VideoPlayer videoPlayer; private Steering navConsole; private Reactor reactor; @@ -33,83 +23,29 @@ namespace Barotrauma.Tutorials private List> characterTimeOnSonar; private float requiredTimeOnSonar = 5f; - private bool started = false; - private string playableContentPath; - private float tutorialTimer; private bool disableTutorialOnDeficiencyFound = true; - private GUIFrame holderFrame, objectiveFrame; - private List activeObjectives = new List(); - private string objectiveTranslated; - private float floodTutorialTimer = 0.0f; private const float floodTutorialDelay = 2.0f; private float medicalTutorialTimer = 0.0f; private const float medicalTutorialDelay = 2.0f; - private Point screenResolution; - private float prevUIScale; - - private class TutorialSegment - { - public string Id; - public string Objective; - public ContentTypes ContentType; - public XElement TextContent; - public XElement VideoContent; - public bool IsTriggered; - public GUIButton ReplayButton; - public GUITextBlock LinkedTitle, LinkedText; - - public TutorialSegment(XElement config) - { - Id = config.GetAttributeString("id", "Missing ID"); - Objective = TextManager.Get(config.GetAttributeString("objective", string.Empty), true); - Enum.TryParse(config.GetAttributeString("contenttype", "None"), true, out ContentType); - IsTriggered = config.GetAttributeBool("istriggered", false); - - switch (ContentType) - { - case ContentTypes.None: - break; - case ContentTypes.Video: - VideoContent = config.Element("Video"); - TextContent = config.Element("Text"); - break; - case ContentTypes.TextOnly: - TextContent = config.Element("Text"); - break; - } - } - } - public ContextualTutorial(XElement element) : base(element) { - playableContentPath = element.GetAttributeString("playablecontentpath", ""); - segments = new List(); - - foreach (var segment in element.Elements("Segment")) - { - segments.Add(new TutorialSegment(segment)); - } - Name = "ContextualTutorial"; } public override void Initialize() { + base.Initialize(); + for (int i = 0; i < segments.Count; i++) { segments[i].IsTriggered = false; } - if (Initialized) return; - Initialized = true; - - base.Initialize(); - videoPlayer = new VideoPlayer(); characterTimeOnSonar = new List>(); } @@ -167,10 +103,7 @@ namespace Barotrauma.Tutorials base.Start(); injuredMember = null; - activeObjectives.Clear(); - objectiveTranslated = TextManager.Get("Objective"); - CreateObjectiveFrame(); - activeSegment = null; + activeContentSegment = null; tutorialTimer = floodTutorialTimer = medicalTutorialTimer = 0.0f; subStartingPosition = Vector2.Zero; characterTimeOnSonar.Clear(); @@ -183,10 +116,10 @@ namespace Barotrauma.Tutorials #if DEBUG if (reactor == null || navConsole == null || sonar == null) { - infoBox = CreateInfoFrame("Submarine not compatible with the tutorial:" + infoBox = CreateInfoFrame("Error", "Submarine not compatible with the tutorial:" + "\nReactor - " + (reactor != null ? "OK" : "Tag 'reactor' not found") + "\nNavigation Console - " + (navConsole != null ? "OK" : "Tag 'command' not found") - + "\nSonar - " + (sonar != null ? "OK" : "Not found under Navigation Console"), true); + + "\nSonar - " + (sonar != null ? "OK" : "Not found under Navigation Console"), hasButton: true); CoroutineManager.StartCoroutine(WaitForErrorClosed()); return; } @@ -222,62 +155,15 @@ namespace Barotrauma.Tutorials } #endif - public void Stop() + public override void Stop() { - started = ContentRunning = Initialized = false; - videoPlayer.Remove(); - videoPlayer = null; + base.Stop(); characterTimeOnSonar = null; } - 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++) - { - CreateObjectiveGUI(activeObjectives[i], i); - } - - screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); - prevUIScale = GUI.Scale; - } - - public override void AddToGUIUpdateList() - { - if (videoPlayer != null) - { - videoPlayer.AddToGUIUpdateList(order: 100); - } - - if (GUI.DisableHUD) return; - if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale) - { - CreateObjectiveFrame(); - } - - if (objectiveFrame != null && activeObjectives.Count > 0) - { - objectiveFrame.AddToGUIUpdateList(order: -1); - } - base.AddToGUIUpdateList(); - } - public override void Update(float deltaTime) { - if (videoPlayer != null) - { - videoPlayer.Update(); - } - - if (infoBox != null) - { - if (PlayerInput.KeyHit(Keys.Enter) || PlayerInput.KeyHit(Keys.Escape)) - { - CloseInfoFrame(null, null); - } - } + base.Update(deltaTime); if (!started || ContentRunning) return; @@ -285,93 +171,12 @@ namespace Barotrauma.Tutorials for (int i = 0; i < segments.Count; i++) { - if (segments[i].IsTriggered || activeObjectives.Contains(segments[i])) continue; + if (segments[i].IsTriggered || HasObjective(segments[i])) continue; if (CheckContextualTutorials(i, deltaTime)) // Found a relevant tutorial, halt finding new ones { break; } } - - for (int i = 0; i < activeObjectives.Count; i++) - { - CheckActiveObjectives(activeObjectives[i], deltaTime); - } - } - - private void ClosePreTextAndTriggerVideoCallback() - { - videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(activeSegment.VideoContent), new VideoPlayer.TextSettings(activeSegment.VideoContent), activeSegment.Id, true, activeSegment.Objective, CurrentSegmentStopCallback); - } - - private void CurrentSegmentStopCallback() - { - if (!string.IsNullOrEmpty(activeSegment.Objective)) - { - AddNewObjective(activeSegment); - } - - activeSegment = null; - ContentRunning = false; - } - - private void AddNewObjective(TutorialSegment segment) - { - activeObjectives.Add(segment); - CreateObjectiveGUI(segment, activeObjectives.Count - 1); - } - - private void CreateObjectiveGUI(TutorialSegment segment, int index) - { - Point replayButtonSize = new Point((int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).X * GUI.Scale), (int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).Y * 1.45f * GUI.Scale)); - - segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopRight, Pivot.TopRight) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null); - segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) => - { - ReplaySegmentVideo(segment); - return true; - }; - - int yOffset = (int)((GUI.ObjectiveNameFont.MeasureString(objectiveTranslated).Y / 2f + 5) * GUI.Scale); - segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point(replayButtonSize.X, yOffset), segment.ReplayButton.RectTransform, Anchor.Center, Pivot.BottomCenter) { AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }, objectiveTranslated, textColor: Color.White, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight); - segment.LinkedText = new GUITextBlock(new RectTransform(new Point(replayButtonSize.X, yOffset), segment.ReplayButton.RectTransform, Anchor.Center, Pivot.TopCenter) { AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }, segment.Objective, textColor: new Color(4, 180, 108), font: GUI.ObjectiveNameFont, textAlignment: Alignment.CenterRight); - - segment.LinkedTitle.TextScale = segment.LinkedText.TextScale = GUI.Scale; - - 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; - } - - private void RemoveCompletedObjective(TutorialSegment objective) - { - objective.IsTriggered = true; - - int checkMarkHeight = (int)(objective.ReplayButton.Rect.Height * 1.2f); - int checkMarkWidth = (int)(checkMarkHeight * 0.93f); - - Color color = new Color(4, 180, 108); - RectTransform rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), objective.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft); - rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - 5, 0); - GUIImage checkmark = new GUIImage(rectTA, "CheckMark"); - checkmark.Color = color; - - RectTransform rectTB = new RectTransform(new Vector2(1.1f, .8f), objective.LinkedText.RectTransform, Anchor.Center, Pivot.Center); - GUIImage stroke = new GUIImage(rectTB, "Stroke"); - stroke.Color = color; - - CoroutineManager.StartCoroutine(WaitForObjectiveEnd(objective)); - } - - private IEnumerable WaitForObjectiveEnd(TutorialSegment objective) - { - yield return new WaitForSeconds(2.0f); - objectiveFrame.RemoveChild(objective.ReplayButton); - activeObjectives.Remove(objective); - - for (int i = 0; i < activeObjectives.Count; i++) - { - activeObjectives[i].ReplayButton.RectTransform.AbsoluteOffset = new Point(0, (activeObjectives[i].ReplayButton.Rect.Height + 20) * i); - } } private bool CheckContextualTutorials(int index, float deltaTime) @@ -526,17 +331,7 @@ namespace Barotrauma.Tutorials return true; } - private bool HasObjective(string objectiveName) - { - for (int i = 0; i < activeObjectives.Count; i++) - { - if (activeObjectives[i].Id == objectiveName) return true; - } - - return false; - } - - private void CheckActiveObjectives(TutorialSegment objective, float deltaTime) + protected override void CheckActiveObjectives(TutorialSegment objective, float deltaTime) { switch(objective.Id) { @@ -704,50 +499,9 @@ namespace Barotrauma.Tutorials return characterTimeOnSonar.Find(ct => ct.Second >= requiredTimeOnSonar && !ct.First.IsDead) != null; } - private void TriggerTutorialSegment(int index, params object[] args) + protected override void TriggerTutorialSegment(int index, params object[] args) { - Inventory.draggingItem = null; - ContentRunning = true; - activeSegment = segments[index]; - - string tutorialText = TextManager.GetFormatted(activeSegment.TextContent.GetAttributeString("tag", ""), true, args); - string objectiveText = string.Empty; - - if (!string.IsNullOrEmpty(activeSegment.Objective)) - { - if (args.Length == 0) - { - objectiveText = activeSegment.Objective; - } - else - { - objectiveText = string.Format(activeSegment.Objective, args); - } - - activeSegment.Objective = objectiveText; - } - else - { - activeSegment.IsTriggered = true; // Complete at this stage only if no related objective - } - - switch (activeSegment.ContentType) - { - case ContentTypes.None: - break; - case ContentTypes.Video: - infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Id), tutorialText, - activeSegment.TextContent.GetAttributeInt("width", 300), - activeSegment.TextContent.GetAttributeInt("height", 80), - activeSegment.TextContent.GetAttributeString("anchor", "Center"), true, ClosePreTextAndTriggerVideoCallback); - break; - case ContentTypes.TextOnly: - infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Id), tutorialText, - activeSegment.TextContent.GetAttributeInt("width", 300), - activeSegment.TextContent.GetAttributeInt("height", 80), - activeSegment.TextContent.GetAttributeString("anchor", "Center"), true, CurrentSegmentStopCallback); - break; - } + base.TriggerTutorialSegment(index, args); for (int i = 0; i < segments.Count; i++) { @@ -757,13 +511,6 @@ namespace Barotrauma.Tutorials CoroutineManager.StartCoroutine(WaitToStop()); // Completed } - private void ReplaySegmentVideo(TutorialSegment segment) - { - if (ContentRunning) return; - ContentRunning = true; - videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false); - } - private IEnumerable WaitToStop() { while (ContentRunning) yield return null; diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/DoctorTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/DoctorTutorial.cs new file mode 100644 index 000000000..8ad220618 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/DoctorTutorial.cs @@ -0,0 +1,422 @@ +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace Barotrauma.Tutorials +{ + class DoctorTutorial : ScenarioTutorial + { + // Room 1 + private float shakeTimer = 1f; + private float shakeAmount = 20f; + + private string radioSpeakerName; + private Character doctor; + + private ItemContainer doctor_suppliesCabinet; + private ItemContainer doctor_medBayCabinet; + private Character patient1, patient2; + private List subPatients = new List(); + private Hull startRoom; + 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 Color doctor_iconColor = new Color(178, 118, 139); + + public DoctorTutorial(XElement element) : base(element) + { + } + public override void Start() + { + base.Start(); + + radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); + doctor = Character.Controlled; + + doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent(); + doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent(); + + var patientHull1 = Hull.hullList.Find(h => h.RoomName == "Waiting room" && h.Submarine == doctor.Submarine); + var patientHull2 = Hull.hullList.Find(h => h.RoomName == "Airlock" && h.Submarine == doctor.Submarine); + medBay = Hull.hullList.Find(h => h.RoomName == "Med bay" && h.Submarine == doctor.Submarine); + + var assistantInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "assistant")); + patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1"); + patient1.GiveJobItems(null); + patient1.CanSpeak = false; + patient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false); + patient1.AIController.Enabled = false; + + assistantInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "assistant")); + patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2"); + patient2.GiveJobItems(null); + patient2.CanSpeak = false; + patient2.AIController.Enabled = false; + + var mechanicInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")); + var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); + subPatients.Add(subPatient1); + + var securityInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "securityofficer")); + var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient2.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); + subPatients.Add(subPatient2); + + var engineerInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")); + var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient3.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); + subPatients.Add(subPatient3); + + doctor_firstDoor = Item.ItemList.Find(i => i.HasTag("doctor_firstdoor")).GetComponent(); + doctor_secondDoor = Item.ItemList.Find(i => i.HasTag("doctor_seconddoor")).GetComponent(); + doctor_thirdDoor = Item.ItemList.Find(i => i.HasTag("doctor_thirddoor")).GetComponent(); + tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent(); + doctor_firstDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_firstdoorlight")).GetComponent(); + doctor_secondDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_seconddoorlight")).GetComponent(); + doctor_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_thirddoorlight")).GetComponent(); + 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(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); + tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent(); + 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() != null); + reactorItem.GetComponent().AutoTemp = true; + } + + public override IEnumerable UpdateState() + { + while (GameMain.Instance.LoadingScreenOpen) yield return null; + + // explosions and radio messages ------------------------------------------------------ + + yield return new WaitForSeconds(3.0f); + + //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); + 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); + + doctor.DamageLimb( + Character.Controlled.WorldPosition, + doctor.AnimController.GetLimb(LimbType.Torso), + new List { 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); + } + + yield return new WaitForSeconds(3.0f); + 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); + SetHighlight(doctor_suppliesCabinet.Item, true); + + /*while (doctor.CurrentHull != doctor_suppliesCabinet.Item.CurrentHull) + { + yield return new WaitForSeconds(2.0f); + }*/ + + TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Use), GameMain.Config.KeyBind(InputType.Deselect)); // Medical supplies objective + + do + { + for (int i = 0; i < doctor_suppliesCabinet.Inventory.Items.Length; i++) + { + if (doctor_suppliesCabinet.Inventory.Items[i] != null) + { + HighlightInventorySlot(doctor_suppliesCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + if (doctor.SelectedConstruction == doctor_suppliesCabinet.Item) + { + for (int i = 0; i < doctor.Inventory.slots.Length; i++) + { + if (doctor.Inventory.Items[i] == null) HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + yield return null; + } while (doctor.Inventory.FindItemByIdentifier("antidama1") == null); // Wait until looted + yield return new WaitForSeconds(1.0f); + + SetHighlight(doctor_suppliesCabinet.Item, false); + RemoveCompletedObjective(segments[0]); + + yield return new WaitForSeconds(1.0f); + + // 2nd tutorial segment, treat self ------------------------------------------------------------------------- + + TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Health)); // Open health interface + while (CharacterHealth.OpenHealthWindow == null) + { + yield return new WaitForSeconds(1.0f); + } + RemoveCompletedObjective(segments[1]); + + 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", highlightColor, .5f, .5f, 0f); + } + + yield return null; + } + + RemoveCompletedObjective(segments[2]); + SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, true); + + while (CharacterHealth.OpenHealthWindow != null) + { + yield return new WaitForSeconds(1.0f); + } + + // treat patient -------------------------------------------------------------------------------------------- + + //patient 1 requests first aid + patient1.CanSpeak = true; + var newOrder = new Order(Order.PrefabList.Find(o => o.AITag == "requestfirstaid"), patient1.CurrentHull, null); + GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime); + patient1.Speak(newOrder.GetChatMessage("", patient1.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order); + patient1.AIController.Enabled = true; + + while (doctor.CurrentHull != patient1.CurrentHull) + { + yield return new WaitForSeconds(1.0f); + } + yield return new WaitForSeconds(0.0f); + + 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.ToggleCrewAreaOpen = true; + + yield return new WaitForSeconds(3.0f); + TriggerTutorialSegment(3); // Get the patient to medbay + + while (patient1.CurrentOrder == null || patient1.CurrentOrder.AITag != "follow") + { + 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); + } + RemoveCompletedObjective(segments[3]); + SetHighlight(doctor_medBayCabinet.Item, true); + SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, true); + + yield return new WaitForSeconds(2.0f); + + TriggerTutorialSegment(4, GameMain.Config.KeyBind(InputType.Health)); // treat burns + + do + { + for (int i = 0; i < 3; i++) + { + if (doctor_medBayCabinet.Inventory.Items[i] != null) + { + HighlightInventorySlot(doctor_medBayCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + if (doctor.SelectedConstruction == doctor_medBayCabinet.Item) + { + for (int i = 0; i < doctor.Inventory.slots.Length; i++) + { + if (doctor.Inventory.Items[i] == null) HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + yield return null; + } while (doctor.Inventory.FindItemByIdentifier("antibleeding1") == 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", highlightColor, .5f, .5f, 0f); + } + yield return null; + + } + RemoveCompletedObjective(segments[4]); + SetHighlight(patient1, false); + yield return new WaitForSeconds(1.0f); + + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurnsHealed"), ChatMessageType.Radio, null); + + // treat unconscious patient ------------------------------------------------------ + + //patient calls for help + patient2.CanSpeak = true; + newOrder = new Order(Order.PrefabList.Find(o => o.AITag == "requestfirstaid"), patient2.CurrentHull, null); + GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime); + patient2.Speak(newOrder.GetChatMessage("", patient1.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order); + 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); + + TriggerTutorialSegment(5, GameMain.Config.KeyBind(InputType.Health)); // perform CPR + SetHighlight(patient2, true); + while (patient2.IsUnconscious) + { + if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR) + { + CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f); + CharacterHealth.OpenHealthWindow.CPRButton.Flash(); + } + yield return null; + } + RemoveCompletedObjective(segments[5]); + SetHighlight(patient2, false); + CoroutineManager.StopCoroutines("KeepPatient2Alive"); + + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + + while (doctor.Submarine != Submarine.MainSub) + { + yield return new WaitForSeconds(1.0f); + } + yield return new WaitForSeconds(5.0f); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null); + + yield return new WaitForSeconds(3.0f); + TriggerTutorialSegment(6, GameMain.Config.KeyBind(InputType.Health)); // give treatment to anyone in need + + foreach (var patient in subPatients) + { + patient.CanSpeak = true; + patient.AIController.Enabled = true; + SetHighlight(patient, true); + } + subPatients[2].Oxygen = -50; + CoroutineManager.StartCoroutine(KeepPatientAlive(subPatients[2]), "KeepPatient3Alive"); + + 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)) + { + newOrder = new Order(Order.PrefabList.Find(o => o.AITag == "requestfirstaid"), subPatients[i].CurrentHull, null); + GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime); + + string message = newOrder.GetChatMessage("", subPatients[i].CurrentHull?.RoomName, givingOrderToSelf: false); + if (subPatients[i].CanSpeak) + { + subPatients[i].Speak(message, ChatMessageType.Order); + } + else + { + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, message, ChatMessageType.Radio, null); + } + patientCalledHelp[i] = true; + } + + if (subPatients[i].ExternalHighlight && subPatients[i].Vitality >= subPatients[i].MaxVitality * 0.9f) + { + SetHighlight(subPatients[i], false); + } + } + yield return new WaitForSeconds(1.0f); + } + RemoveCompletedObjective(segments[6]); + foreach (var patient in subPatients) + { + SetHighlight(patient, false); + } + + // END TUTORIAL + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + public IEnumerable KeepPatientAlive(Character patient) + { + while (patient != null && !patient.Removed) + { + patient.Oxygen = Math.Max(patient.Oxygen, -50); + yield return null; + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/EngineerTutorial.cs new file mode 100644 index 000000000..7cedf8fad --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/EngineerTutorial.cs @@ -0,0 +1,554 @@ +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 MotionSensor engineer_reactorObjectiveSensor; + private Powered tutorial_oxygenGenerator; + private Reactor engineer_reactor; + private Door engineer_secondDoor; + private LightComponent engineer_secondDoorLight; + + // Room 4 + private MotionSensor engineer_repairJunctionBoxObjectiveSensor; + private Item engineer_brokenJunctionBox; + private Door engineer_thirdDoor; + private LightComponent engineer_thirdDoorLight; + + // Room 5 + private MotionSensor engineer_disconnectedJunctionBoxObjectiveSensor; + 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 string 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(XElement element) : base(element) + { + + } + + public override void Start() + { + base.Start(); + + radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); + engineer = Character.Controlled; + + var toolbox = engineer.Inventory.FindItemByIdentifier("toolbox"); + toolbox.Unequip(engineer); + engineer.Inventory.RemoveItem(toolbox); + + var repairOrder = Order.PrefabList.Find(order => order.AITag == "repairsystems"); + engineer_repairIcon = repairOrder.SymbolSprite; + engineer_repairIconColor = repairOrder.Color; + + var reactorOrder = Order.PrefabList.Find(order => order.AITag == "operatereactor"); + engineer_reactorIcon = reactorOrder.SymbolSprite; + engineer_reactorIconColor = reactorOrder.Color; + + // Other tutorial items + tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); + tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); + tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + + 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(); + engineer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("engineer_equipmentcabinet")).GetComponent(); + engineer_firstDoor = Item.ItemList.Find(i => i.HasTag("engineer_firstdoor")).GetComponent(); + engineer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_firstdoorlight")).GetComponent(); + + SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, false); + + // Room 3 + engineer_reactorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_reactorobjectivesensor")).GetComponent(); + tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent(); + engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent(); + engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity; + engineer_reactor.FuelConsumptionRate = 0.0f; + engineer_reactor.OnOffSwitch.BarScroll = 1f; + reactorOperatedProperly = false; + + engineer_secondDoor = Item.ItemList.Find(i => i.HasTag("engineer_seconddoor")).GetComponent(); ; + engineer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_seconddoorlight")).GetComponent(); + + SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, false); + + // Room 4 + engineer_repairJunctionBoxObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_repairjunctionboxobjectivesensor")).GetComponent(); + engineer_brokenJunctionBox = Item.ItemList.Find(i => i.HasTag("engineer_brokenjunctionbox")); + engineer_thirdDoor = Item.ItemList.Find(i => i.HasTag("engineer_thirddoor")).GetComponent(); + engineer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_thirddoorlight")).GetComponent(); + + engineer_brokenJunctionBox.Indestructible = false; + engineer_brokenJunctionBox.Condition = 0f; + + SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, false); + + // Room 5 + engineer_disconnectedJunctionBoxObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_disconnectedjunctionboxobjectivesensor")).GetComponent(); + + 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(); + engineer_disconnectedConnectionPanels[i] = engineer_disconnectedJunctionBoxes[i].Item.GetComponent(); + 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_2 = Item.ItemList.Find(i => i.HasTag("engineer_wire_2")); + engineer_lamp_1 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_1")).GetComponent(); + engineer_lamp_2 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_2")).GetComponent(); + engineer_fourthDoor = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoor")).GetComponent(); + engineer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoorlight")).GetComponent(); + SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, false); + + // Room 6 + engineer_workingPump = Item.ItemList.Find(i => i.HasTag("engineer_workingpump")).GetComponent(); + 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(); + SetDoorAccess(tutorial_lockedDoor_1, null, true); + + // Submarine + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + + tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent(); + 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(); + 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; + } + + public override IEnumerable 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); + } + + //// 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); + TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(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.Items[0] == null) firstSlotRemoved = true; + } + + if (!secondSlotRemoved) + { + HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); + if (engineer_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true; + } + + if (!thirdSlotRemoved) + { + HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); + if (engineer_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true; + } + + if (!fourthSlotRemoved) + { + HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 3, highlightColor, .5f, .5f, 0f); + if (engineer_equipmentCabinet.Inventory.Items[2] == null) fourthSlotRemoved = true; + } + + for (int i = 0; i < engineer.Inventory.slots.Length; i++) + { + if (engineer.Inventory.Items[i] == null) HighlightInventorySlot(engineer.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + + yield return null; + } while (engineer.Inventory.FindItemByIdentifier("screwdriver") == null || engineer.Inventory.FindItemByIdentifier("redwire") == null || engineer.Inventory.FindItemByIdentifier("bluewire") == null); // Wait until looted + RemoveCompletedObjective(segments[0]); + 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); + TriggerTutorialSegment(1); + do + { + if (IsSelectedItem(engineer_reactor.Item)) + { + if (engineer_reactor.OnOffSwitch.FlashTimer <= 0) + { + engineer_reactor.OnOffSwitch.Flash(highlightColor, 1.5f, false); + } + } + yield return null; + } while (engineer_reactor.OnOffSwitch.BarScroll > 0.45f); + do + { + if (IsSelectedItem(engineer_reactor.Item) && engineer_reactor.Item.OwnInventory.slots != null) + { + HighlightInventorySlot(engineer.Inventory, "fuelrod", highlightColor, 0.5f, 0.5f, 0f); + + for (int i = 0; i < engineer_reactor.Item.OwnInventory.slots.Length; i++) + { + HighlightInventorySlot(engineer_reactor.Item.OwnInventory, i, highlightColor, 0.5f, 0.5f, 0f); + } + } + yield return null; + } while (engineer_reactor.AvailableFuel == 0); + CoroutineManager.StartCoroutine(ReactorOperatedProperly()); + do + { + if (IsSelectedItem(engineer_reactor.Item)) + { + 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); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ReactorStable"), ChatMessageType.Radio, null); + do + { + if (IsSelectedItem(engineer_reactor.Item)) + { + if (engineer_reactor.AutoTempSlider.FlashTimer <= 0) + { + engineer_reactor.AutoTempSlider.Flash(highlightColor, 1.5f, false, new Vector2(10, 10)); + } + } + yield return null; + } while (!engineer_reactor.AutoTemp); + RemoveCompletedObjective(segments[1]); + 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); + TriggerTutorialSegment(2, GameMain.Config.KeyBind(InputType.Select)); // Repair the junction box + do { yield return null; } while (!engineer_brokenJunctionBox.IsFullCondition); // Wait until repaired + SetHighlight(engineer_brokenJunctionBox, false); + RemoveCompletedObjective(segments[2]); + 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); + TriggerTutorialSegment(3, GameMain.Config.KeyBind(InputType.Use), GameMain.Config.KeyBind(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(segments[3]); + 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); + TriggerTutorialSegment(4); // Repair junction box + while (ContentRunning) yield return null; + SetHighlight(engineer_submarineJunctionBox_1, true); + SetHighlight(engineer_submarineJunctionBox_2, true); + SetHighlight(engineer_submarineJunctionBox_3, true); + 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); + // Remove highlights when each individual machine is repaired + do { CheckJunctionBoxHighlights(); yield return null; } while (!engineer_submarineJunctionBox_1.IsFullCondition || !engineer_submarineJunctionBox_2.IsFullCondition || !engineer_submarineJunctionBox_3.IsFullCondition); + CheckJunctionBoxHighlights(); + RemoveCompletedObjective(segments[4]); + TriggerTutorialSegment(5); // 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(segments[5]); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null); + + 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?.SelectedConstruction == item; + } + + private IEnumerable ReactorOperatedProperly() + { + float timer; + + for (int i = 0; i < reactorLoads.Length; i++) + { + timer = reactorLoadChangeTime; + tutorial_oxygenGenerator.PowerConsumption = reactorLoads[i]; + while (timer > 0) + { + yield return new WaitForSeconds(0.1f); + if (IsReactorPoweredUp(engineer_reactor)) + { + timer -= 0.1f; + } + } + } + + reactorOperatedProperly = true; + } + + private void CheckGhostWires() + { + if (engineer_wire_1 != null && engineer_lamp_1.Voltage > engineer_lamp_1.MinVoltage) + { + engineer_wire_1.Remove(); + engineer_wire_1 = null; + } + + if (engineer_wire_2 != null && engineer_lamp_2.Voltage > engineer_lamp_2.MinVoltage) + { + engineer_wire_2.Remove(); + engineer_wire_2 = null; + } + } + + private void HandleJunctionBoxWiringHighlights() + { + Item selected = engineer.SelectedConstruction; + + if (!engineer.HasEquippedItem("screwdriver")) + { + HighlightInventorySlot(engineer.Inventory, "screwdriver", 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")) + { + HighlightInventorySlotWithTag(engineer.Inventory, "wire", 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() + { + if (engineer_submarineJunctionBox_1.IsFullCondition && engineer_submarineJunctionBox_1.ExternalHighlight) + { + SetHighlight(engineer_submarineJunctionBox_1, false); + engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1); + } + if (engineer_submarineJunctionBox_2.IsFullCondition && engineer_submarineJunctionBox_2.ExternalHighlight) + { + SetHighlight(engineer_submarineJunctionBox_2, false); + engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2); + } + if (engineer_submarineJunctionBox_3.IsFullCondition && engineer_submarineJunctionBox_3.ExternalHighlight) + { + SetHighlight(engineer_submarineJunctionBox_3, false); + engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3); + } + } + + private bool IsReactorPoweredUp(Reactor reactor) + { + float load = 0.0f; + List 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(); + if (pt == null) continue; + + load = Math.Max(load, pt.PowerLoad); + } + } + } + + return Math.Abs(load + reactor.CurrPowerConsumption) < reactorLoadError; + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs new file mode 100644 index 000000000..72dca0c66 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs @@ -0,0 +1,589 @@ +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; + private MotionSensor mechanic_ladderSensor; + + // 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 string radioSpeakerName; + private Character mechanic; + private Sprite mechanic_repairIcon; + private Color mechanic_repairIconColor; + + public MechanicTutorial(XElement element) : base(element) + { + + } + + public override void Start() + { + base.Start(); + + radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); + mechanic = Character.Controlled; + + var toolbox = mechanic.Inventory.FindItemByIdentifier("toolbox"); + toolbox.Unequip(mechanic); + mechanic.Inventory.RemoveItem(toolbox); + + var crowbar = mechanic.Inventory.FindItemByIdentifier("crowbar"); + crowbar.Unequip(mechanic); + mechanic.Inventory.RemoveItem(crowbar); + + var repairOrder = Order.PrefabList.Find(order => order.AITag == "repairsystems"); + mechanic_repairIcon = repairOrder.SymbolSprite; + mechanic_repairIconColor = repairOrder.Color; + + // Other tutorial items + tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); + tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent(); + tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + + 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(); + mechanic_firstDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoorlight")).GetComponent(); + + SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, false); + + // Room 2 + mechanic_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentobjectivesensor")).GetComponent(); + mechanic_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentcabinet")).GetComponent(); + mechanic_secondDoor = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoor")).GetComponent(); + mechanic_secondDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoorlight")).GetComponent(); + + SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, false); + + // Room 3 + mechanic_weldingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_weldingobjectivesensor")).GetComponent(); + mechanic_workingPump = Item.ItemList.Find(i => i.HasTag("mechanic_workingpump")).GetComponent(); + mechanic_thirdDoor = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoor")).GetComponent(); + mechanic_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoorlight")).GetComponent(); + mechanic_brokenWall_1 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_1"); + //mechanic_ladderSensor = Item.ItemList.Find(i => i.HasTag("mechanic_laddersensor")).GetComponent(); + + 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, 165); + } + mechanic_brokenhull_1 = mechanic_brokenWall_1.Sections[0].gap.FlowTargetHull; + + // Room 4 + mechanic_craftingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_craftingobjectivesensor")).GetComponent(); + mechanic_deconstructor = Item.ItemList.Find(i => i.HasTag("mechanic_deconstructor")).GetComponent(); + mechanic_fabricator = Item.ItemList.Find(i => i.HasTag("mechanic_fabricator")).GetComponent(); + mechanic_craftingCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_craftingcabinet")).GetComponent(); + mechanic_fourthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoor")).GetComponent(); + mechanic_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoorlight")).GetComponent(); + + SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, false); + + // Room 5 + mechanic_fifthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoor")).GetComponent(); + mechanic_fifthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoorlight")).GetComponent(); + mechanic_fireSensor = Item.ItemList.Find(i => i.HasTag("mechanic_firesensor")).GetComponent(); + + SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, false); + + // Room 6 + mechanic_divingSuitObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitobjectivesensor")).GetComponent(); + mechanic_divingSuitContainer = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitcontainer")).GetComponent(); + for (int i = 0; i < mechanic_divingSuitContainer.Inventory.Items.Length; i++) + { + foreach (ItemComponent ic in mechanic_divingSuitContainer.Inventory.Items[i].Components) + { + ic.CanBePicked = true; + } + } + mechanic_oxygenContainer = Item.ItemList.Find(i => i.HasTag("mechanic_oxygencontainer")).GetComponent(); + for (int i = 0; i < mechanic_oxygenContainer.Inventory.Items.Length; i++) + { + foreach (ItemComponent ic in mechanic_oxygenContainer.Inventory.Items[i].Components) + { + ic.CanBePicked = true; + } + } + tutorial_mechanicFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoor")).GetComponent(); + tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); + + SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, false); + + // Room 7 + mechanic_brokenPump = Item.ItemList.Find(i => i.HasTag("mechanic_brokenpump")).GetComponent(); + mechanic_brokenPump.Item.Indestructible = false; + mechanic_brokenPump.Item.Condition = 0; + mechanic_brokenWall_2 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_2"); + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + + 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, 165); + } + 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(); + mechanic_submarineEngine = Item.ItemList.Find(i => i.HasTag("mechanic_submarineengine")).GetComponent(); + mechanic_submarineEngine.Item.Indestructible = false; + mechanic_submarineEngine.Item.Condition = 0f; + mechanic_ballastPump_1 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_1")).GetComponent(); + 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(); + mechanic_ballastPump_2.Item.Indestructible = false; + mechanic_ballastPump_2.Item.Condition = 0f; + } + + public override void Update(float deltaTime) + { + mechanic_brokenhull_1.WaterVolume = MathHelper.Clamp(mechanic_brokenhull_1.WaterVolume, 0, mechanic_brokenhull_1.Volume * 0.85f); + base.Update(deltaTime); + } + + public override IEnumerable 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); + } + yield return new WaitForSeconds(2.5f); + + mechanic_fabricator.RemoveFabricationRecipes(new List() { "extinguisher", "wrench", "weldingtool", "weldingfuel", "divingmask", "railgunshell", "nuclearshell", "uex", "harpoongun" }); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null); + + yield return new WaitForSeconds(2.5f); + TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Up), GameMain.Config.KeyBind(InputType.Left), GameMain.Config.KeyBind(InputType.Down), GameMain.Config.KeyBind(InputType.Right), GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Select)); // Open door objective + yield return new WaitForSeconds(0.0f); + 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); + RemoveCompletedObjective(segments[0]); + + // Room 2 + yield return new WaitForSeconds(0.0f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null); + do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected); + TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(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.Items[0] == null) firstSlotRemoved = true; + } + + if (!secondSlotRemoved) + { + HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); + if (mechanic_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true; + } + + if (!thirdSlotRemoved) + { + HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); + if (mechanic_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true; + } + + for (int i = 0; i < mechanic.Inventory.slots.Length; i++) + { + if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + + yield return null; + } while (mechanic.Inventory.FindItemByIdentifier("divingmask") == null || mechanic.Inventory.FindItemByIdentifier("weldingtool") == null || mechanic.Inventory.FindItemByIdentifier("wrench") == null); // Wait until looted + SetHighlight(mechanic_equipmentCabinet.Item, false); + yield return new WaitForSeconds(1.5f); + RemoveCompletedObjective(segments[1]); + 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, GameMain.Config.KeyBind(InputType.Shoot), GameMain.Config.KeyBind(InputType.Aim)); // Welding objective + do + { + if (!mechanic.HasEquippedItem("divingmask")) + { + HighlightInventorySlot(mechanic.Inventory, "divingmask", highlightColor, .5f, .5f, 0f); + } + + if (!mechanic.HasEquippedItem("weldingtool")) + { + HighlightInventorySlot(mechanic.Inventory, "weldingtool", highlightColor, .5f, .5f, 0f); + } + yield return null; + } while (!mechanic.HasEquippedItem("divingmask") || !mechanic.HasEquippedItem("weldingtool")); // Wait until equipped + SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, true); + mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_1, mechanic_repairIcon, mechanic_repairIconColor); + do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired + mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1); + RemoveCompletedObjective(segments[2]); + yield return new WaitForSeconds(1f); + TriggerTutorialSegment(3, GameMain.Config.KeyBind(InputType.Select)); // Pump objective + SetHighlight(mechanic_workingPump.Item, true); + do + { + if (mechanic_workingPump.IsActiveSlider.FlashTimer <= 0) + { + mechanic_workingPump.IsActiveSlider.Flash(uiHighlightColor, 1.5f, true); + } + yield return null; + } while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining + SetHighlight(mechanic_workingPump.Item, false); + do { yield return null; } while (mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained + RemoveCompletedObjective(segments[3]); + SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, true); + yield return new WaitForSeconds(1.5f); + //TriggerTutorialSegment(11, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Up), GameMain.Config.KeyBind(InputType.Down), GameMain.Config.KeyBind(InputType.Select)); // Ladder objective + //do { yield return null; } while (!mechanic_ladderSensor.MotionDetected); + //RemoveCompletedObjective(segments[11]); + yield return new WaitForSeconds(2f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.News"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(1f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Fire"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(6f); + + // Room 4 + do { yield return null; } while (!mechanic_thirdDoor.IsOpen); + 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); + do + { + for (int i = 0; i < mechanic_craftingCabinet.Inventory.Items.Length; i++) + { + if (mechanic_craftingCabinet.Inventory.Items[i] != null) + { + HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + if (mechanic.SelectedConstruction == mechanic_craftingCabinet.Item) + { + for (int i = 0; i < mechanic.Inventory.slots.Length; i++) + { + if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + yield return null; + } while (mechanic.Inventory.FindItemByIdentifier("oxygentank") == null || mechanic.Inventory.FindItemByIdentifier("sodium") == null); // Wait until looted + yield return new WaitForSeconds(1.0f); + SetHighlight(mechanic_craftingCabinet.Item, false); + + SetHighlight(mechanic_deconstructor.Item, true); + + do + { + if (IsSelectedItem(mechanic_deconstructor.Item)) + { + if (mechanic.Inventory.FindItemByIdentifier("oxygentank") != null) + { + HighlightInventorySlot(mechanic.Inventory, "oxygentank", highlightColor, .5f, .5f, 0f); + + if (mechanic_deconstructor.InputContainer.Inventory.slots != null) + { + for (int i = 0; i < mechanic_deconstructor.InputContainer.Inventory.slots.Length; i++) + { + HighlightInventorySlot(mechanic_deconstructor.InputContainer.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + } + + if (mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank") != null && !mechanic_deconstructor.IsActive) + { + if (mechanic_deconstructor.ActivateButton.Frame.FlashTimer <= 0) + { + mechanic_deconstructor.ActivateButton.Frame.Flash(highlightColor, 1.5f, false); + } + } + + if (mechanic_deconstructor.OutputContainer.Inventory.FindItemByIdentifier("aluminium") != null) + { + HighlightInventorySlot(mechanic_deconstructor.OutputContainer.Inventory, "aluminium", highlightColor, .5f, .5f, 0f); + + for (int i = 0; i < mechanic.Inventory.slots.Length; i++) + { + if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + } + yield return null; + } while (mechanic.Inventory.FindItemByIdentifier("aluminium") == null); // Wait until deconstructed + + SetHighlight(mechanic_deconstructor.Item, false); + RemoveCompletedObjective(segments[4]); + yield return new WaitForSeconds(1f); + 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") != null) + { + HighlightInventorySlot(mechanic_fabricator.OutputContainer.Inventory, "extinguisher", highlightColor, .5f, .5f, 0f); + + for (int i = 0; i < mechanic.Inventory.slots.Length; i++) + { + if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + else if (mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium") != null && mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("sodium") != null && !mechanic_fabricator.IsActive) + { + if (mechanic_fabricator.ActivateButton.Frame.FlashTimer <= 0) + { + mechanic_fabricator.ActivateButton.Frame.Flash(highlightColor, 1.5f, false); + } + } + else if (mechanic.Inventory.FindItemByIdentifier("aluminium") != null || mechanic.Inventory.FindItemByIdentifier("sodium") != null) + { + HighlightInventorySlot(mechanic.Inventory, "aluminium", highlightColor, .5f, .5f, 0f); + HighlightInventorySlot(mechanic.Inventory, "sodium", highlightColor, .5f, .5f, 0f); + + if (mechanic_fabricator.InputContainer.Inventory.Items[0] == null) + { + HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 0, highlightColor, .5f, .5f, 0f); + } + + if (mechanic_fabricator.InputContainer.Inventory.Items[1] == null) + { + HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 1, highlightColor, .5f, .5f, 0f); + } + } + } + } + yield return null; + } while (mechanic.Inventory.FindItemByIdentifier("extinguisher") == null); // Wait until extinguisher is created + RemoveCompletedObjective(segments[5]); + SetHighlight(mechanic_fabricator.Item, false); + SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, true); + + // Room 5 + do { yield return null; } while (!mechanic_fireSensor.MotionDetected); + TriggerTutorialSegment(6, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Using the extinguisher + do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished + yield return new WaitForSeconds(3f); + RemoveCompletedObjective(segments[6]); + + if (mechanic.HasEquippedItem("extinguisher")) // do not trigger if dropped already + { + TriggerTutorialSegment(7); + do + { + HighlightInventorySlot(mechanic.Inventory, "extinguisher", highlightColor, 0.5f, 0.5f, 0f); + yield return null; + } while (mechanic.HasEquippedItem("extinguisher")); + RemoveCompletedObjective(segments[7]); + } + 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.slots != null) + { + for (int i = 0; i < mechanic_divingSuitContainer.Inventory.slots.Length; i++) + { + HighlightInventorySlot(mechanic_divingSuitContainer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); + } + } + } + yield return null; + } while (!mechanic.HasEquippedItem("divingsuit")); + SetHighlight(mechanic_divingSuitContainer.Item, false); + RemoveCompletedObjective(segments[8]); + SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true); + + // Room 7 + mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_2, mechanic_repairIcon, mechanic_repairIconColor); + do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_2)); + mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2); + TriggerTutorialSegment(9, GameMain.Config.KeyBind(InputType.Select)); // Repairing machinery (pump) + SetHighlight(mechanic_brokenPump.Item, true); + Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent(); + do + { + if (!mechanic_brokenPump.Item.IsFullCondition) + { + if (!mechanic.HasEquippedItem("wrench")) + { + HighlightInventorySlot(mechanic.Inventory, "wrench", highlightColor, 0.5f, 0.5f, 0f); + } + else if (IsSelectedItem(mechanic_brokenPump.Item) && repairablePumpComponent.CurrentFixer == null) + { + if (repairablePumpComponent.RepairButton.Frame.FlashTimer <= 0) + { + repairablePumpComponent.RepairButton.Frame.Flash(); + } + } + } + yield return null; + } while (!mechanic_brokenPump.Item.IsFullCondition || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive); + RemoveCompletedObjective(segments[9]); + 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); + // Remove highlights when each individual machine is repaired + do { CheckHighlights(); yield return null; } while (!mechanic_ballastPump_1.Item.IsFullCondition || !mechanic_ballastPump_2.Item.IsFullCondition || !mechanic_submarineEngine.Item.IsFullCondition); + CheckHighlights(); + RemoveCompletedObjective(segments[10]); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null); + + // END TUTORIAL + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + private bool IsSelectedItem(Item item) + { + return mechanic?.SelectedConstruction == 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() + { + if (mechanic_ballastPump_1.Item.IsFullCondition && mechanic_ballastPump_1.Item.ExternalHighlight) + { + SetHighlight(mechanic_ballastPump_1.Item, false); + mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item); + } + if (mechanic_ballastPump_2.Item.IsFullCondition && mechanic_ballastPump_2.Item.ExternalHighlight) + { + SetHighlight(mechanic_ballastPump_2.Item, false); + mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item); + } + if (mechanic_submarineEngine.Item.IsFullCondition && mechanic_submarineEngine.Item.ExternalHighlight) + { + SetHighlight(mechanic_submarineEngine.Item, false); + mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item); + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/OfficerTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/OfficerTutorial.cs new file mode 100644 index 000000000..3feff2ddd --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/OfficerTutorial.cs @@ -0,0 +1,452 @@ +using System; +using System.Collections.Generic; +using System.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 string radioSpeakerName; + private Character officer; + private string crawlerCharacterFile; + private string hammerheadCharacterFile; + private string mudraptorCharacterFile; + private float superCapacitorRechargeRate = 10; + private Sprite officer_gunIcon; + private Color officer_gunIconColor; + + public OfficerTutorial(XElement element) : base(element) + { + crawlerCharacterFile = Character.GetConfigFile("crawler"); + hammerheadCharacterFile = Character.GetConfigFile("hammerhead"); + mudraptorCharacterFile = Character.GetConfigFile("mudraptor"); + } + + public override void Start() + { + base.Start(); + + radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); + officer = Character.Controlled; + + var handcuffs = officer.Inventory.FindItemByIdentifier("handcuffs"); + handcuffs.Unequip(officer); + officer.Inventory.RemoveItem(handcuffs); + + var stunbaton = officer.Inventory.FindItemByIdentifier("stunbaton"); + stunbaton.Unequip(officer); + officer.Inventory.RemoveItem(stunbaton); + + var ballistichelmet = officer.Inventory.FindItemByIdentifier("ballistichelmet"); + ballistichelmet.Unequip(officer); + officer.Inventory.RemoveItem(ballistichelmet); + + var bodyarmor = officer.Inventory.FindItemByIdentifier("bodyarmor"); + bodyarmor.Unequip(officer); + officer.Inventory.RemoveItem(bodyarmor); + + var gunOrder = Order.PrefabList.Find(order => order.AITag == "operateweapons"); + officer_gunIcon = gunOrder.SymbolSprite; + officer_gunIconColor = gunOrder.Color; + + // Other tutorial items + tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); + tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + + 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(); + officer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("officer_equipmentcabinet")).GetComponent(); + officer_firstDoor = Item.ItemList.Find(i => i.HasTag("officer_firstdoor")).GetComponent(); + officer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("officer_firstdoorlight")).GetComponent(); + + SetDoorAccess(officer_firstDoor, officer_firstDoorLight, false); + + // Room 3 + officer_crawlerSensor = Item.ItemList.Find(i => i.HasTag("officer_crawlerobjectivesensor")).GetComponent(); + officer_crawlerSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_crawlerspawn")).WorldPosition; + officer_secondDoor = Item.ItemList.Find(i => i.HasTag("officer_seconddoor")).GetComponent(); + officer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("officer_seconddoorlight")).GetComponent(); + + SetDoorAccess(officer_secondDoor, officer_secondDoorLight, false); + + // Room 4 + officer_somethingBigSensor = Item.ItemList.Find(i => i.HasTag("officer_somethingbigobjectivesensor")).GetComponent(); + officer_coilgunLoader = Item.ItemList.Find(i => i.HasTag("officer_coilgunloader")).GetComponent(); + officer_superCapacitor = Item.ItemList.Find(i => i.HasTag("officer_supercapacitor")).GetComponent(); + 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(); + officer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("officer_thirddoorlight")).GetComponent(); + officer_ammoShelf_1 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_1")).GetComponent(); + officer_ammoShelf_2 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_2")).GetComponent(); + + SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, false); + + // Room 5 + officer_rangedWeaponSensor = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponobjectivesensor")).GetComponent(); + officer_rangedWeaponCabinet = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponcabinet")).GetComponent(); + officer_rangedWeaponHolder = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponholder")).GetComponent(); + officer_fourthDoor = Item.ItemList.Find(i => i.HasTag("officer_fourthdoor")).GetComponent(); + officer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("officer_fourthdoorlight")).GetComponent(); + + SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, false); + + // Room 6 + officer_mudraptorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_mudraptorobjectivesensor")).GetComponent(); + officer_mudraptorSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_mudraptorspawn")).WorldPosition; + tutorial_securityFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoor")).GetComponent(); + tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); + + SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, false); + + // Submarine + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent(); + 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(); + officer_subLoader_2 = Item.ItemList.Find(i => i.HasTag("officer_subloader_2")).GetComponent(); + officer_subSuperCapacitor_1 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_1")).GetComponent(); + officer_subSuperCapacitor_2 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_2")).GetComponent(); + officer_subAmmoShelf = Item.ItemList.Find(i => i.HasTag("officer_subammoshelf")).GetComponent(); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + } + + public override IEnumerable 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); + } + + 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); + //TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(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.Items[0] == null) firstSlotRemoved = true; + } + + if (!secondSlotRemoved) + { + HighlightInventorySlot(officer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); + if (officer_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true; + } + + if (!thirdSlotRemoved) + { + HighlightInventorySlot(officer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); + if (officer_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true; + } + + for (int i = 0; i < officer.Inventory.slots.Length; i++) + { + if (officer.Inventory.Items[i] == null) HighlightInventorySlot(officer.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + + yield return null; + } while (!officer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted + //RemoveCompletedObjective(segments[0]); + SetHighlight(officer_equipmentCabinet.Item, false); + do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item)); + TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Equip melee weapon & armor + do + { + if (!officer.HasEquippedItem("stunbaton")) + { + HighlightInventorySlot(officer.Inventory, "stunbaton", highlightColor, .5f, .5f, 0f); + } + if (!officer.HasEquippedItem("bodyarmor")) + { + HighlightInventorySlot(officer.Inventory, "bodyarmor", highlightColor, .5f, .5f, 0f); + } + if (!officer.HasEquippedItem("ballistichelmet")) + { + HighlightInventorySlot(officer.Inventory, "ballistichelmet", highlightColor, .5f, .5f, 0f); + } + yield return new WaitForSeconds(1f); + } while (!officer.HasEquippedItem("stunbaton") || !officer.HasEquippedItem("bodyarmor") || !officer.HasEquippedItem("ballistichelmet")); + RemoveCompletedObjective(segments[1]); + SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true); + + // Room 3 + do { yield return null; } while (!officer_crawlerSensor.MotionDetected); + TriggerTutorialSegment(2); + officer_crawler = SpawnMonster(crawlerCharacterFile, officer_crawlerSpawnPos); + do { yield return null; } while (!officer_crawler.IsDead); + RemoveCompletedObjective(segments[2]); + Heal(officer); + yield return new WaitForSeconds(1f); + 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); + TriggerTutorialSegment(3); // Arm railgun + do + { + SetHighlight(officer_coilgunLoader.Item, officer_coilgunLoader.Inventory.Items[0] == null || officer_coilgunLoader.Inventory.Items[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", highlightColor, .5f, .5f, 0f); + } + yield return null; + } while (officer_coilgunLoader.Inventory.Items[0] == null || officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate || officer_coilgunLoader.Inventory.Items[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(segments[3]); + yield return new WaitForSeconds(2f); + TriggerTutorialSegment(4, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Shoot), GameMain.Config.KeyBind(InputType.Deselect)); // Kill hammerhead + officer_hammerhead = SpawnMonster(hammerheadCharacterFile, officer_hammerheadSpawnPos); + 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) + { + // Don't let the Hammerhead go too far from the periscope. + officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos); + } + yield return null; + } + while(!officer_hammerhead.IsDead); + Heal(officer); + SetHighlight(officer_coilgunPeriscope, false); + RemoveCompletedObjective(segments[4]); + yield return new WaitForSeconds(1f); + 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); + TriggerTutorialSegment(5, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(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, "harpoongun", highlightColor, 0.5f, 0.5f, 0f); + yield return null; + } while (!officer.HasEquippedItem("harpoongun")); // Wait until equipped + ItemContainer harpoonGunChamber = officer.Inventory.FindItemByIdentifier("harpoongun").GetComponent(); + SetHighlight(officer_rangedWeaponCabinet.Item, true); + do + { + if (IsSelectedItem(officer_rangedWeaponCabinet.Item)) + { + if (officer_rangedWeaponCabinet.Inventory.slots != null) + { + for (int i = 0; i < officer_rangedWeaponCabinet.Inventory.Items.Length; i++) + { + if (officer_rangedWeaponCabinet.Inventory.Items[i] == null) continue; + if (officer_rangedWeaponCabinet.Inventory.Items[i].Prefab.Identifier == "spear") + { + HighlightInventorySlot(officer_rangedWeaponCabinet.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); + } + } + } + } + + for (int i = 0; i < officer.Inventory.Items.Length; i++) + { + if (officer.Inventory.Items[i] == null) continue; + if (officer.Inventory.Items[i].Prefab.Identifier == "spear") + { + HighlightInventorySlot(officer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); + } + } + + if (officer.Inventory.FindItemByIdentifier("spear") != null || (IsSelectedItem(officer_rangedWeaponCabinet.Item) && officer_rangedWeaponCabinet.Inventory.FindItemByIdentifier("spear") != null)) + { + HighlightInventorySlot(officer.Inventory, "harpoongun", highlightColor, 0.5f, 0.5f, 0f); + } + yield return null; + } while (!harpoonGunChamber.Inventory.IsFull()); // Wait until all five harpons loaded + RemoveCompletedObjective(segments[5]); + 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(mudraptorCharacterFile, officer_mudraptorSpawnPos); + do { yield return null; } while (!officer_mudraptor.IsDead); + Heal(officer); + RemoveCompletedObjective(segments[6]); + 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_1.Item, officer_gunIcon, officer_gunIconColor); + officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_2.Item, officer_gunIcon, officer_gunIconColor); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Submarine"), ChatMessageType.Radio, null); + do + { + SetHighlight(officer_subLoader_1.Item, officer_subLoader_1.Inventory.Items[0] == null || officer_subLoader_1.Inventory.Items[0].Condition == 0); + SetHighlight(officer_subLoader_2.Item, officer_subLoader_2.Inventory.Items[0] == null || officer_subLoader_2.Inventory.Items[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_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight); + SetHighlight(officer_subAmmoBox_2, officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight); + 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(segments[7]); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Complete"), ChatMessageType.Radio, null); + + yield return new WaitForSeconds(4f); + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + private bool IsSelectedItem(Item item) + { + return officer?.SelectedConstruction == item; + } + + private Character SpawnMonster(string characterFile, Vector2 pos) + { + var character = Character.Create(characterFile, 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; + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ScenarioTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ScenarioTutorial.cs index 025ae979e..cbff14dca 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ScenarioTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ScenarioTutorial.cs @@ -1,44 +1,118 @@ -using System; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Xml.Linq; namespace Barotrauma.Tutorials { class ScenarioTutorial : Tutorial { + private CoroutineHandle tutorialCoroutine; + private Character character; + private string spawnSub; private SpawnType spawnPointType; private string submarinePath; + private string startOutpostPath; + private string endOutpostPath; private string levelSeed; + private string levelParams; + + private Submarine startOutpost = null; + private Submarine 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 = Color.Red; + protected Color accessibleColor = Color.Green; public ScenarioTutorial(XElement element) : base(element) { submarinePath = element.GetAttributeString("submarinepath", ""); + startOutpostPath = element.GetAttributeString("startoutpostpath", ""); + endOutpostPath = element.GetAttributeString("endoutpostpath", ""); + levelSeed = element.GetAttributeString("levelseed", "tuto"); - Enum.TryParse(element.GetAttributeString("spawnpointtype", "Human"), true, out spawnPointType); + levelParams = element.GetAttributeString("levelparams", ""); + + spawnSub = element.GetAttributeString("spawnsub", ""); + Enum.TryParse(element.GetAttributeString("spawnpointtype", "Human"), true, out spawnPointType); } public override void Initialize() { base.Initialize(); + currentTutorialCompleted = false; GameMain.Instance.ShowLoading(Loading()); } + private IEnumerable Loading() + { + Submarine.MainSub = Submarine.Load(submarinePath, "", true); + + LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Name == levelParams); + + yield return CoroutineStatus.Running; + + GameMain.GameSession = new GameSession(Submarine.MainSub, "", + GameModePreset.List.Find(g => g.Identifier == "tutorial")); + (GameMain.GameSession.GameMode as TutorialMode).Tutorial = this; + + if (generationParams != null) + { + Biome biome = LevelGenerationParams.GetBiomes().Find(b => generationParams.AllowedBiomes.Contains(b)); + + if (startOutpostPath != string.Empty) + { + startOutpost = Submarine.Load(startOutpostPath, "", false); + } + + if (endOutpostPath != string.Empty) + { + endOutpost = Submarine.Load(endOutpostPath, "", false); + } + + Level tutorialLevel = new Level(levelSeed, 0, 0, generationParams, biome, startOutpost, endOutpost); + GameMain.GameSession.StartRound(tutorialLevel); + } + else + { + GameMain.GameSession.StartRound(levelSeed); + } + + GameMain.GameSession.EventManager.Events.Clear(); + GameMain.GameSession.EventManager.Enabled = false; + GameMain.GameScreen.Select(); + + yield return CoroutineStatus.Success; + } + public override void Start() { base.Start(); - WayPoint wayPoint = WayPoint.GetRandom(spawnPointType, null); + Submarine.MainSub.GodMode = true; + + CharacterInfo charInfo = configElement.Element("Character") == null ? + new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")) : + new CharacterInfo(configElement.Element("Character")); + + WayPoint wayPoint = GetSpawnPoint(charInfo); + if (wayPoint == null) { DebugConsole.ThrowError("A waypoint with the spawntype \"" + spawnPointType + "\" is required for the tutorial event"); return; } - CharacterInfo charInfo = configElement.Element("Character") == null ? - new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")) : - new CharacterInfo(configElement.Element("Character")); - character = Character.Create(charInfo, wayPoint.WorldPosition, "", false, false); Character.Controlled = character; character.GiveJobItems(null); @@ -52,22 +126,82 @@ namespace Barotrauma.Tutorials idCard.AddTag("com"); idCard.AddTag("eng"); - CoroutineManager.StartCoroutine(UpdateState()); + tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState()); } - private IEnumerable Loading() + public override void AddToGUIUpdateList() { - Submarine.MainSub = Submarine.Load(submarinePath, "", true); - yield return CoroutineStatus.Running; + if (!currentTutorialCompleted) + { + base.AddToGUIUpdateList(); + } + } - GameMain.GameSession = new GameSession(Submarine.MainSub, "", - GameModePreset.List.Find(g => g.Identifier == "tutorial")); - (GameMain.GameSession.GameMode as TutorialMode).tutorial = this; - GameMain.GameSession.StartRound(levelSeed); - GameMain.GameSession.EventManager.Events.Clear(); - GameMain.GameScreen.Select(); + private WayPoint GetSpawnPoint(CharacterInfo charInfo) + { + Submarine spawnSub = null; - yield return CoroutineStatus.Success; + if (this.spawnSub != string.Empty) + { + switch (this.spawnSub) + { + case "startoutpost": + spawnSub = startOutpost; + break; + + case "endoutpost": + spawnSub = endOutpost; + break; + + default: + spawnSub = Submarine.MainSub; + break; + } + } + + return WayPoint.GetRandom(spawnPointType, charInfo.Job, spawnSub); + } + + protected bool HasOrder(Character character, string aiTag, string option = null) + { + if (character.CurrentOrder?.AITag == aiTag) + { + if (option == null) + { + return true; + } + else + { + HumanAIController humanAI = character.AIController as HumanAIController; + return humanAI.CurrentOrderOption == 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) @@ -75,27 +209,47 @@ namespace Barotrauma.Tutorials base.Update(deltaTime); if (character != null) { - if (Character.Controlled == null) + if (character.Oxygen < 1) { - CoroutineManager.StopCoroutines("TutorialMode.UpdateState"); + character.Oxygen = 1; + } + if (character.IsDead) + { + CoroutineManager.StartCoroutine(Dead()); + } + else if (Character.Controlled == null) + { + if (tutorialCoroutine != null) + { + CoroutineManager.StopCoroutines(tutorialCoroutine); + } infoBox = null; } else if (Character.Controlled.IsDead) { - Character.Controlled = null; - - CoroutineManager.StopCoroutines("TutorialMode.UpdateState"); - infoBox = null; CoroutineManager.StartCoroutine(Dead()); } } } + public override void Stop() + { + if (tutorialCoroutine != null) + { + CoroutineManager.StopCoroutines(tutorialCoroutine); + } + base.Stop(); + } + private IEnumerable Dead() { + GUI.PreventPauseMenuToggle = true; + Character.Controlled = character = null; + Stop(); + yield return new WaitForSeconds(3.0f); - var messageBox = new GUIMessageBox("You have died", "Do you want to try again?", new string[] { "Yes", "No" }); + var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); messageBox.Buttons[0].OnClicked += Restart; messageBox.Buttons[0].OnClicked += messageBox.Close; @@ -106,5 +260,29 @@ namespace Barotrauma.Tutorials yield return CoroutineStatus.Success; } + + protected IEnumerable TutorialCompleted() + { + GUI.PreventPauseMenuToggle = true; + + Character.Controlled.ClearInputs(); + Character.Controlled = null; + + yield return new WaitForSeconds(waitBeforeFade); + + var endCinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, 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); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs index 69a5e98c9..22cc125a4 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs @@ -1,15 +1,23 @@ using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; using System.Reflection; +using System.Linq; using System.Xml.Linq; +using Barotrauma.Items.Components; +using Barotrauma.Extensions; namespace Barotrauma.Tutorials { abstract class Tutorial { + #region Tutorial variables + public static bool Initialized = false; + public static bool ContentRunning = false; public static List Tutorials; + protected bool started = false; protected GUIComponent infoBox; private Action infoBoxClosedCallback; protected XElement configElement; @@ -17,6 +25,54 @@ namespace Barotrauma.Tutorials private enum TutorialType { None, Scenario, Contextual }; private TutorialType tutorialType = TutorialType.None; + protected VideoPlayer videoPlayer; + protected enum TutorialContentTypes { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 }; + protected string playableContentPath; + protected Point screenResolution; + protected float prevUIScale; + + private GUIFrame holderFrame, objectiveFrame; + private List activeObjectives = new List(); + private string objectiveTranslated; + + protected TutorialSegment activeContentSegment; + protected List segments; + + protected class TutorialSegment + { + public string Id; + public string Objective; + public TutorialContentTypes ContentType; + public XElement TextContent; + public XElement VideoContent; + public bool IsTriggered; + public GUIButton ReplayButton; + public GUITextBlock LinkedTitle, LinkedText; + public object[] Args; + + public TutorialSegment(XElement config) + { + Id = config.GetAttributeString("id", "Missing ID"); + Objective = TextManager.Get(config.GetAttributeString("objective", string.Empty), true); + Enum.TryParse(config.GetAttributeString("contenttype", "None"), true, out ContentType); + IsTriggered = config.GetAttributeBool("istriggered", false); + + switch (ContentType) + { + case TutorialContentTypes.None: + break; + case TutorialContentTypes.Video: + case TutorialContentTypes.ManualVideo: + VideoContent = config.Element("Video"); + TextContent = config.Element("Text"); + break; + case TutorialContentTypes.TextOnly: + TextContent = config.Element("Text"); + break; + } + } + } + public string Name { get; @@ -34,7 +90,9 @@ namespace Barotrauma.Tutorials GameMain.Config.SaveNewPlayerConfig(); } } + #endregion + #region Tutorial Controls public static void Init() { Tutorials = new List(); @@ -107,26 +165,85 @@ namespace Barotrauma.Tutorials Name = element.GetAttributeString("name", "Unnamed"); completed = GameMain.Config.CompletedTutorialNames.Contains(Name); Enum.TryParse(element.GetAttributeString("tutorialtype", "Scenario"), true, out tutorialType); + playableContentPath = element.GetAttributeString("playablecontentpath", ""); + + segments = new List(); + + foreach (var segment in element.Elements("Segment")) + { + segments.Add(new TutorialSegment(segment)); + } } public virtual void Initialize() { - + if (Initialized) return; + Initialized = true; + videoPlayer = new VideoPlayer(); } public virtual void Start() { - + activeObjectives.Clear(); + objectiveTranslated = TextManager.Get("Tutorial.Objective"); + CreateObjectiveFrame(); + + // Setup doors: Clear all requirements, unless the door is setup as locked. + foreach (var item in Item.ItemList) + { + var door = item.GetComponent(); + if (door != null) + { + if (door.requiredItems.Values.None(ris => ris.None(ri => ri.Identifiers.None(i => i == "locked")))) + { + door.requiredItems.Clear(); + } + } + } } public virtual void AddToGUIUpdateList() { + if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale) + { + CreateObjectiveFrame(); + } + + if (objectiveFrame != null && activeObjectives.Count > 0) + { + objectiveFrame.AddToGUIUpdateList(order: -1); + } + if (infoBox != null) infoBox.AddToGUIUpdateList(order: 100); + if (videoPlayer != null) videoPlayer.AddToGUIUpdateList(order: 100); } public virtual void Update(float deltaTime) { - + if (videoPlayer != null) + { + videoPlayer.Update(); + } + + if (activeObjectives != null) + { + for (int i = 0; i < activeObjectives.Count; i++) + { + CheckActiveObjectives(activeObjectives[i], deltaTime); + } + } + } + + public void CloseActiveContentGUI() + { + if (videoPlayer.IsPlaying) + { + videoPlayer.Stop(); + } + else if (infoBox != null) + { + CloseInfoFrame(null, null); + } } public virtual IEnumerable UpdateState() @@ -134,6 +251,233 @@ namespace Barotrauma.Tutorials yield return CoroutineStatus.Success; } + protected bool Restart(GUIButton button, object obj) + { + GUI.PreventPauseMenuToggle = false; + TutorialMode.StartTutorial(this); + return true; + } + + protected virtual void TriggerTutorialSegment(int index, params object[] args) + { + Inventory.draggingItem = null; + ContentRunning = true; + activeContentSegment = segments[index]; + segments[index].Args = args; + + string tutorialText = TextManager.GetFormatted(activeContentSegment.TextContent.GetAttributeString("tag", ""), true, args); + tutorialText = TextManager.ParseInputTypes(tutorialText); + string objectiveText = string.Empty; + + if (!string.IsNullOrEmpty(activeContentSegment.Objective)) + { + if (args.Length == 0) + { + objectiveText = activeContentSegment.Objective; + } + else + { + objectiveText = string.Format(activeContentSegment.Objective, args); + } + objectiveText = TextManager.ParseInputTypes(objectiveText); + activeContentSegment.Objective = objectiveText; + } + else + { + activeContentSegment.IsTriggered = true; // Complete at this stage only if no related objective + } + + + switch (activeContentSegment.ContentType) + { + case TutorialContentTypes.None: + break; + case TutorialContentTypes.Video: + infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, + activeContentSegment.TextContent.GetAttributeInt("width", 300), + activeContentSegment.TextContent.GetAttributeInt("height", 80), + activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, () => LoadVideo(activeContentSegment)); + break; + case TutorialContentTypes.ManualVideo: + infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, + activeContentSegment.TextContent.GetAttributeInt("width", 300), + activeContentSegment.TextContent.GetAttributeInt("height", 80), + activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment, false)); + break; + case TutorialContentTypes.TextOnly: + infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, + activeContentSegment.TextContent.GetAttributeInt("width", 300), + activeContentSegment.TextContent.GetAttributeInt("height", 80), + activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment); + break; + } + } + + public virtual void Stop() + { + started = ContentRunning = Initialized = false; + infoBox = null; + if (videoPlayer != null) + { + videoPlayer.Remove(); + videoPlayer = null; + } + } + #endregion + + #region Objectives + 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++) + { + CreateObjectiveGUI(activeObjectives[i], i, activeObjectives[i].ContentType); + } + + screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + prevUIScale = GUI.Scale; + } + + protected void StopCurrentContentSegment() + { + if (!string.IsNullOrEmpty(activeContentSegment.Objective)) + { + AddNewObjective(activeContentSegment, activeContentSegment.ContentType); + } + + activeContentSegment = null; + ContentRunning = false; + } + + protected virtual void CheckActiveObjectives(TutorialSegment objective, float deltaTime) + { + + } + + protected bool HasObjective(TutorialSegment segment) + { + return activeObjectives.Contains(segment); + } + + protected void AddNewObjective(TutorialSegment segment, TutorialContentTypes type) + { + activeObjectives.Add(segment); + CreateObjectiveGUI(segment, activeObjectives.Count - 1, type); + } + + private void CreateObjectiveGUI(TutorialSegment segment, int index, TutorialContentTypes type) + { + Point replayButtonSize = new Point((int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).X * GUI.Scale), (int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).Y * 1.45f * GUI.Scale)); + + segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopRight, Pivot.TopRight) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null); + segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) => + { + if (type == TutorialContentTypes.Video) + { + ReplaySegmentVideo(segment); + } + else + { + ShowSegmentText(segment); + } + return true; + }; + + string objectiveText = TextManager.ParseInputTypes(objectiveTranslated); + int yOffset = (int)((GUI.ObjectiveNameFont.MeasureString(objectiveText).Y / 2f + 5) * GUI.Scale); + segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point((int)GUI.ObjectiveNameFont.MeasureString(objectiveText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterRight, Pivot.BottomRight) { AbsoluteOffset = new Point((int)(-10 * GUI.Scale), 0) }, + objectiveText, textColor: Color.White, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight); + segment.LinkedText = new GUITextBlock(new RectTransform(new Point(replayButtonSize.X, yOffset), segment.ReplayButton.RectTransform, Anchor.Center, Pivot.TopCenter) { AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }, + TextManager.ParseInputTypes(segment.Objective), textColor: new Color(4, 180, 108), font: GUI.ObjectiveNameFont, textAlignment: Alignment.CenterRight); + + segment.LinkedTitle.TextScale = segment.LinkedText.TextScale = GUI.Scale; + + 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; + } + + private void ReplaySegmentVideo(TutorialSegment segment) + { + if (ContentRunning) return; + ContentRunning = true; + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false); + } + + private void ShowSegmentText(TutorialSegment segment) + { + if (ContentRunning) return; + Inventory.draggingItem = null; + ContentRunning = true; + + string tutorialText = TextManager.GetFormatted(segment.TextContent.GetAttributeString("tag", ""), true, segment.Args); + + Action videoAction = null; + + if (segment.ContentType != TutorialContentTypes.TextOnly) + { + videoAction = () => LoadVideo(segment, false); + } + + infoBox = CreateInfoFrame(TextManager.Get(segment.Id), tutorialText, + segment.TextContent.GetAttributeInt("width", 300), + segment.TextContent.GetAttributeInt("height", 80), + segment.TextContent.GetAttributeString("anchor", "Center"), true, () => ContentRunning = false, videoAction); + } + + protected void RemoveCompletedObjective(TutorialSegment segment) + { + if (!HasObjective(segment)) return; + 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.BottomLeft, Pivot.BottomLeft); + rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - 5, 0); + } + else + { + rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft); + rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - 5 - (objectiveTitleWidth), 0); + } + + GUIImage checkmark = new GUIImage(rectTA, "CheckMark"); + checkmark.Color = checkmark.SelectedColor = checkmark.HoverColor = checkmark.PressedColor = color; + + RectTransform rectTB = new RectTransform(new Vector2(1.1f, .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(segment)); + } + + private IEnumerable WaitForObjectiveEnd(TutorialSegment objective) + { + yield return new WaitForSeconds(2.0f); + objectiveFrame.RemoveChild(objective.ReplayButton); + activeObjectives.Remove(objective); + + for (int i = 0; i < activeObjectives.Count; i++) + { + activeObjectives[i].ReplayButton.RectTransform.AbsoluteOffset = new Point(0, (activeObjectives[i].ReplayButton.Rect.Height + 20) * i); + } + } + + #endregion + + #region InfoFrame protected bool CloseInfoFrame(GUIButton button, object userData) { infoBox = null; @@ -141,73 +485,71 @@ namespace Barotrauma.Tutorials return true; } - protected GUIComponent CreateInfoFrame(string text, bool hasButton = false, Action callback = null) + protected GUIComponent CreateInfoFrame(string title, string text, int width = 300, int height = 80, string anchorStr = "", bool hasButton = false, Action callback = null, Action showVideo = null) { - int width = 300; - int height = hasButton ? 110 : 80; + if (hasButton) height += 60; - string wrappedText = ToolBox.WrapText(text, width, GUI.Font); - - height += wrappedText.Split('\n').Length * 25; - - var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), GUI.Canvas, Anchor.TopRight) { AbsoluteOffset = new Point(20) }); - infoBlock.Flash(Color.Green); - - var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.7f), infoBlock.RectTransform, Anchor.Center), - text, wrap: true); - - infoBoxClosedCallback = callback; - - if (hasButton) - { - var okButton = new GUIButton(new RectTransform(new Point(160, 50), infoBlock.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, -10) }, - TextManager.Get("OK")) - { - OnClicked = CloseInfoFrame - }; - } - - GUI.PlayUISound(GUISoundType.UIMessage); - - return infoBlock; - } - - protected GUIComponent CreateInfoFrame(string title, string text, int width, int height, string anchorStr, bool hasButton = false, Action callback = null) - { - if (hasButton) height += 30; - - string wrappedText = ToolBox.WrapText(text, width, GUI.Font); - - height += wrappedText.Split('\n').Length * 25; + float textScale = GUI.Scale; + string wrappedText = ToolBox.WrapText(text, width, GUI.Font, textScale); + height += (int)(GUI.Font.MeasureString(wrappedText).Y * textScale + 50); if (title.Length > 0) { height += 35; } Anchor anchor = Anchor.TopRight; - Enum.TryParse(anchorStr, out anchor); + + if (anchorStr != string.Empty) + { + Enum.TryParse(anchorStr, out anchor); + } var infoBlock = new GUIFrame(new RectTransform(new Point((int)(width * GUI.Scale), (int)(height * GUI.Scale)), GUI.Canvas, anchor) { AbsoluteOffset = new Point(20) }); infoBlock.Flash(Color.Green); + var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), infoBlock.RectTransform, Anchor.Center)) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + if (title.Length > 0) { - var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1f, .35f), infoBlock.RectTransform, Anchor.TopCenter, - Pivot.TopCenter), title, font: GUI.VideoTitleFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0)); - titleBlock.TextScale = GUI.Scale; + var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), + title, font: GUI.VideoTitleFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0)); + titleBlock.TextScale = textScale; } - var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.9f, 1f), infoBlock.RectTransform, Anchor.BottomCenter), + var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true); - textBlock.TextScale = GUI.Scale; + textBlock.TextScale = textScale; infoBoxClosedCallback = callback; if (hasButton) { - var okButton = new GUIButton(new RectTransform(new Point(160, 50), infoBlock.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, -10) }, - TextManager.Get("OK")) + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), infoContent.RectTransform) { MinSize = new Point(0, 30) }, isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.1f + }; + + if (showVideo != null) + { + var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform), + TextManager.Get("Video"), style: "GUIButtonLarge") + { + OnClicked = (GUIButton button, object obj) => + { + showVideo(); + return true; + } + }; + } + + var okButton = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), + TextManager.Get("OK"), style: "GUIButtonLarge") { OnClicked = CloseInfoFrame }; @@ -217,11 +559,53 @@ namespace Barotrauma.Tutorials return infoBlock; } + #endregion - protected bool Restart(GUIButton button, object obj) + #region Video + protected void LoadVideo(TutorialSegment segment, bool showText = true) { - TutorialMode.StartTutorial(this); - return true; + if (videoPlayer == null) videoPlayer = new VideoPlayer(); + if (showText) + { + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, segment.Objective, StopCurrentContentSegment); + } + else + { + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), null, segment.Id, true, segment.Objective, null); + } } + #endregion + + #region Highlights + protected void HighlightInventorySlot(Inventory inventory, string identifier, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) + { + if (inventory.slots == null) { return; } + for (int i = 0; i < inventory.Items.Length; i++) + { + if (inventory.Items[i] != null && inventory.Items[i].Prefab.Identifier == identifier) + { + HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount); + } + } + } + + protected void HighlightInventorySlotWithTag(Inventory inventory, string tag, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) + { + if (inventory.slots == null) { return; } + for (int i = 0; i < inventory.Items.Length; i++) + { + if (inventory.Items[i] != null && inventory.Items[i].HasTag(tag)) + { + HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount); + } + } + } + + protected void HighlightInventorySlot(Inventory inventory, int index, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) + { + if (inventory.slots == null || index < 0 || inventory.slots[index].HighlightTimer > 0) return; + inventory.slots[index].ShowBorderHighlight(color, fadeInDuration, fadeOutDuration, scaleUpAmount); + } + #endregion } } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/TutorialMode.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/TutorialMode.cs index f6b5dcd9c..407f742e9 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/TutorialMode.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/TutorialMode.cs @@ -1,11 +1,10 @@ using Barotrauma.Tutorials; -using Microsoft.Xna.Framework.Graphics; namespace Barotrauma { class TutorialMode : GameMode { - public Tutorial tutorial; + public Tutorial Tutorial; public static void StartTutorial(Tutorial tutorial) { @@ -20,18 +19,20 @@ namespace Barotrauma public override void Start() { base.Start(); - tutorial.Start(); + GameMain.GameSession.CrewManager = new CrewManager(true); + Tutorial.Start(); } public override void AddToGUIUpdateList() { - tutorial.AddToGUIUpdateList(); + base.AddToGUIUpdateList(); + Tutorial.AddToGUIUpdateList(); } public override void Update(float deltaTime) { base.Update(deltaTime); - tutorial.Update(deltaTime); + Tutorial.Update(deltaTime); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Deconstructor.cs index 85150941e..cb8889292 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Deconstructor.cs @@ -9,6 +9,10 @@ namespace Barotrauma.Items.Components { partial class Deconstructor : Powered, IServerSerializable, IClientSerializable { + public GUIButton ActivateButton + { + get { return activateButton; } + } private GUIButton activateButton; private GUIComponent inputInventoryHolder, outputInventoryHolder; private GUICustomComponent inputInventoryOverlay; @@ -44,7 +48,6 @@ namespace Barotrauma.Items.Components Visible = false, CanBeFocused = false }; - outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.3f), paddedFrame.RectTransform), style: null); } @@ -71,7 +74,7 @@ namespace Barotrauma.Items.Components public override void UpdateHUD(Character character, float deltaTime, Camera cam) { inSufficientPowerWarning.Visible = powerConsumption > 0 && voltage < minVoltage; - activateButton.Enabled = !inSufficientPowerWarning.Visible; + //activateButton.Enabled = !inSufficientPowerWarning.Visible; } private bool ToggleActive(GUIButton button, object obj) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs index 4dc0b89a1..f01906f3d 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs @@ -15,6 +15,10 @@ namespace Barotrauma.Items.Components private GUIFrame selectedItemFrame; + public GUIButton ActivateButton + { + get { return activateButton; } + } private GUIButton activateButton; private GUITextBox itemFilterBox; @@ -22,6 +26,10 @@ namespace Barotrauma.Items.Components private GUIComponent inputInventoryHolder, outputInventoryHolder; private GUICustomComponent inputInventoryOverlay, outputInventoryOverlay; + public FabricationRecipe SelectedItem + { + get { return selectedItem; } + } private FabricationRecipe selectedItem; private GUIComponent inSufficientPowerWarning; @@ -73,7 +81,31 @@ namespace Barotrauma.Items.Components { CanBeFocused = false }; - + + CreateRecipes(); + + activateButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.07f), paddedFrame.RectTransform), + TextManager.Get("FabricatorCreate"), style: "GUIButtonLarge") + { + OnClicked = StartButtonClicked, + UserData = selectedItem, + Enabled = false + }; + + inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), TextManager.Get("FabricatorNoPower"), + textColor: Color.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow") + { + HoverColor = Color.Black, + IgnoreLayoutGroups = true, + Visible = false, + CanBeFocused = false + }; + } + + partial void CreateRecipes() + { + itemList.Content.RectTransform.ClearChildren(); + foreach (FabricationRecipe fi in fabricationRecipes) { GUIFrame frame = new GUIFrame(new RectTransform(new Point(itemList.Rect.Width, 30), itemList.Content.RectTransform), style: null) @@ -101,23 +133,6 @@ namespace Barotrauma.Items.Components }; } } - - activateButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.07f), paddedFrame.RectTransform), - TextManager.Get("FabricatorCreate"), style: "GUIButtonLarge") - { - OnClicked = StartButtonClicked, - UserData = selectedItem, - Enabled = false - }; - - inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), TextManager.Get("FabricatorNoPower"), - textColor: Color.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow") - { - HoverColor = Color.Black, - IgnoreLayoutGroups = true, - Visible = false, - CanBeFocused = false - }; } partial void OnItemLoadedProjSpecific() @@ -241,6 +256,7 @@ namespace Barotrauma.Items.Components } } } + private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) { overlayComponent.RectTransform.SetAsLastChild(); @@ -363,6 +379,29 @@ namespace Barotrauma.Items.Components return true; } + public void HighlightRecipe(string identifier, Color color) + { + foreach (GUIComponent child in itemList.Content.Children) + { + FabricationRecipe recipe = child.UserData as FabricationRecipe; + if (recipe?.DisplayName == null) { continue; } + if (recipe.TargetItem.Identifier == identifier) + { + if (child.FlashTimer > 0.0f) return; + child.Flash(color, 1.5f, false); + + for (int i = 0; i < child.CountChildren; i++) + { + var grandChild = child.GetChild(i); + if (grandChild is GUITextBlock) continue; + grandChild.Flash(color, 1.5f, false); + } + + return; + } + } + } + private bool StartButtonClicked(GUIButton button, object obj) { if (selectedItem == null) { return false; } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs index b13727818..5ef0e010c 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs @@ -10,6 +10,10 @@ namespace Barotrauma.Items.Components { partial class Pump : Powered, IServerSerializable, IClientSerializable { + public GUIScrollBar IsActiveSlider + { + get { return isActiveSlider; } + } private GUIScrollBar isActiveSlider; private GUIScrollBar pumpSpeedSlider; private GUITickBox powerIndicator; @@ -49,7 +53,6 @@ namespace Barotrauma.Items.Components }; var sliderHandle = isActiveSlider.GetChild(); sliderHandle.RectTransform.NonScaledSize = new Point(84, sliderHandle.Rect.Height); - isActiveSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => { bool active = scrollBar.BarScroll < 0.5f; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs index 893943a62..b4d53d0e0 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs @@ -11,7 +11,16 @@ namespace Barotrauma.Items.Components { partial class Reactor : Powered, IServerSerializable, IClientSerializable { + public GUIScrollBar AutoTempSlider + { + get { return autoTempSlider; } + } private GUIScrollBar autoTempSlider; + + public GUIScrollBar OnOffSwitch + { + get { return onOffSwitch; } + } private GUIScrollBar onOffSwitch; private const int GraphSize = 25; @@ -27,7 +36,16 @@ namespace Barotrauma.Items.Components private Sprite graphLine; + public GUIScrollBar FissionRateScrollBar + { + get { return fissionRateScrollBar; } + } private GUIScrollBar fissionRateScrollBar; + + public GUIScrollBar TurbineOutputScrollBar + { + get { return turbineOutputScrollBar; } + } private GUIScrollBar turbineOutputScrollBar; private float[] outputGraph = new float[GraphSize]; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs index 3c20dbb30..0c7c9c058 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs @@ -20,6 +20,10 @@ namespace Barotrauma.Items.Components private bool unsentChanges; private float networkUpdateTimer; + public GUITickBox ActiveTickBox + { + get { return activeTickBox; } + } private GUITickBox activeTickBox, passiveTickBox; private GUITextBlock signalWarningText; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs index ddd2ffb29..5cde6d3ec 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs @@ -10,6 +10,10 @@ namespace Barotrauma.Items.Components { partial class Repairable : ItemComponent, IDrawableComponent { + public GUIButton RepairButton + { + get { return repairButton; } + } private GUIButton repairButton; private GUIProgressBar progressBar; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs index 53c247abe..f5a3c8999 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs @@ -18,6 +18,14 @@ namespace Barotrauma.Items.Components private static Wire draggingConnected; + private Color flashColor; + private float flashDuration = 1.5f; + public float FlashTimer + { + get { return flashTimer; } + } + private float flashTimer; + public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character) { Rectangle panelRect = panel.GuiFrame.Rect; @@ -174,14 +182,38 @@ namespace Barotrauma.Items.Components } } } - + + if (flashTimer > 0.0f) + { + //the number of flashes depends on the duration, 1 flash per 1 full second + int flashCycleCount = (int)Math.Max(flashDuration, 1); + float flashCycleDuration = flashDuration / flashCycleCount; + + //MathHelper.Pi * 0.8f -> the curve goes from 144 deg to 0, + //i.e. quickly bumps up from almost full brightness to full and then fades out + connectionSpriteHighlight.Draw(spriteBatch, position, flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f)); + } + if (Wires.Any(w => w != null && w != draggingConnected)) { int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count; screwSprites[screwIndex].Draw(spriteBatch, position); } } - + + public void Flash(Color? color = null, float flashDuration = 1.5f) + { + flashTimer = flashDuration; + this.flashDuration = flashDuration; + flashColor = (color == null) ? Color.Red : (Color)color; + } + + public void UpdateFlashTimer(float deltaTime) + { + if (flashTimer <= 0) return; + flashTimer -= deltaTime; + } + private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Item item, Vector2 end, Vector2 start, bool mouseIn, Wire equippedWire, ConnectionPanel panel, string label) { if (draggingConnected == wire) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs index fe1fbc065..124992e2c 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs @@ -92,6 +92,25 @@ namespace Barotrauma.Items.Components } } + public void HighlightElement(int index, Color color, float duration, float pulsateAmount = 0.0f) + { + if (index < 0 || index >= uiElements.Count) { return; } + uiElements[index].Flash(color, duration); + + if (pulsateAmount > 0.0f) + { + if (uiElements[index] is GUIButton button) + { + button.Frame.Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration); + button.Frame.RectTransform.SetPosition(Anchor.Center); + } + else + { + uiElements[index].Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration); + } + } + } + partial void UpdateLabelsProjSpecific() { for (int i = 0; i < labels.Length && i < uiElements.Count; i++) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 30eaf8155..8fb30030a 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -26,6 +26,7 @@ namespace Barotrauma public Color Color; public Color HighlightColor; + public float HighlightScaleUpAmount; private CoroutineHandle highlightCoroutine; public float HighlightTimer; @@ -80,7 +81,7 @@ namespace Barotrauma return rect.Contains(PlayerInput.MousePosition); } - public void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration) + public void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount = 0.5f) { if (highlightCoroutine != null) { @@ -88,6 +89,7 @@ namespace Barotrauma highlightCoroutine = null; } + HighlightScaleUpAmount = scaleUpAmount; highlightCoroutine = CoroutineManager.StartCoroutine(UpdateBorderHighlight(color, fadeInDuration, fadeOutDuration)); } @@ -800,8 +802,7 @@ namespace Barotrauma if (slot.HighlightColor.A > 0) { - float scaleUpAmount = 0.5f; - float inflateAmount = (slot.HighlightColor.A / 255.0f) * scaleUpAmount * 0.5f; + float inflateAmount = (slot.HighlightColor.A / 255.0f) * slot.HighlightScaleUpAmount * 0.5f; rect.Inflate(rect.Width * inflateAmount, rect.Height * inflateAmount); } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index f15e6eadd..07b2f78eb 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -184,7 +184,7 @@ namespace Barotrauma if (!Visible || (!editing && hiddenInGame)) return; if (editing && !ShowItems) return; - Color color = isHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? Color.Orange : GetSpriteColor(); + Color color = IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? Color.Orange : GetSpriteColor(); //if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f); BrokenItemSprite fadeInBrokenSprite = null; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Gap.cs b/Barotrauma/BarotraumaClient/Source/Map/Gap.cs index 10614b898..c158c9199 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Gap.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Gap.cs @@ -33,7 +33,7 @@ namespace Barotrauma if (!editing || !ShowGaps) return; Color clr = (open == 0.0f) ? Color.Red : Color.Cyan; - if (isHighlighted) clr = Color.Gold; + if (IsHighlighted) clr = Color.Gold; float depth = (ID % 255) * 0.000001f; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs index 8dc7700f0..252662461 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs @@ -292,12 +292,12 @@ namespace Barotrauma }*/ } - if ((IsSelected || isHighlighted) && editing) + if ((IsSelected || IsHighlighted) && editing) { GUI.DrawRectangle(spriteBatch, new Vector2(drawRect.X + 5, -drawRect.Y + 5), new Vector2(rect.Width - 10, rect.Height - 10), - isHighlighted ? Color.LightBlue * 0.5f : Color.Red * 0.5f, true, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); + IsHighlighted ? Color.LightBlue * 0.5f : Color.Red * 0.5f, true, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); } foreach (MapEntity e in linkedTo) diff --git a/Barotrauma/BarotraumaClient/Source/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaClient/Source/Map/LinkedSubmarine.cs index 0349f19dc..7b311c66b 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/LinkedSubmarine.cs @@ -13,7 +13,7 @@ namespace Barotrauma { if (!editing || wallVertices == null) return; - Color color = (isHighlighted) ? Color.Orange : Color.Green; + Color color = (IsHighlighted) ? Color.Orange : Color.Green; if (IsSelected) color = Color.Red; Vector2 pos = Position; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs index b3e69d226..11e85a147 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs @@ -1,4 +1,5 @@ -using Barotrauma.Lights; +using Barotrauma.Extensions; +using Barotrauma.Lights; using Barotrauma.Networking; using Lidgren.Network; using Microsoft.Xna.Framework; @@ -41,7 +42,21 @@ namespace Barotrauma MathHelper.Clamp(value.Y, 0.01f, 10)); } } - + + private string specialTag; + [Editable, Serialize("", true)] + public string SpecialTag + { + get { return specialTag; } + set { specialTag = value; } + } + + // Only for testing in the debug build. Not saved. +#if DEBUG + [Editable, Serialize(true, false)] +#endif + public bool DrawTiled { get; protected set; } = true; + protected Vector2 textureOffset = Vector2.Zero; [Editable(MinValueFloat = -1000f, MaxValueFloat = 1000f, ValueStep = 10f), Serialize("0.0, 0.0", true)] public Vector2 TextureOffset @@ -180,7 +195,7 @@ namespace Barotrauma if (HasBody && !ShowWalls) return; } - Color color = isHighlighted ? Color.Orange : spriteColor; + Color color = IsHighlighted ? Color.Orange : spriteColor; if (IsSelected && editing) { //color = Color.Lerp(color, Color.Gold, 0.5f); diff --git a/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs index 25fb723e0..fc1cb229c 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs @@ -30,7 +30,7 @@ namespace Barotrauma Color clr = currentHull == null ? Color.Blue : Color.White; if (IsSelected) clr = Color.Red; - if (isHighlighted) clr = Color.DarkRed; + if (IsHighlighted) clr = Color.DarkRed; int iconX = iconIndices[(int)spawnType] * IconSize % iconTexture.Width; int iconY = (int)(Math.Floor(iconIndices[(int)spawnType] * IconSize / (float)iconTexture.Width)) * IconSize; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index d4d830e8b..f989d7926 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -16,7 +16,6 @@ namespace Barotrauma private GUIListBox saveList; private GUITextBox saveNameBox, seedBox; - private GUITickBox contextualTutorialBox; private GUILayoutGroup subPreviewContainer; @@ -24,14 +23,6 @@ namespace Barotrauma public Action StartNewGame; public Action LoadGame; - public bool TutorialSelected - { - get - { - if (contextualTutorialBox == null) return false; - return contextualTutorialBox.Selected; - } - } private readonly bool isMultiplayer; @@ -68,12 +59,6 @@ namespace Barotrauma new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed") + ":"); seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); - if (!isMultiplayer) - { - contextualTutorialBox = new GUITickBox(new RectTransform(new Point(32, 32), leftColumn.RectTransform), TextManager.Get("TutorialActive")); - UpdateTutorialSelection(); - } - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub") + ":"); var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true) { @@ -400,14 +385,7 @@ namespace Barotrauma }, Enabled = false }; - } - - public void UpdateTutorialSelection() - { - if (isMultiplayer) return; - Tutorial contextualTutorial = Tutorial.Tutorials.Find(t => t is ContextualTutorial); - contextualTutorialBox.Selected = (contextualTutorial != null) ? !GameMain.Config.CompletedTutorialNames.Contains(contextualTutorial.Name) : true; - } + } private bool SelectSaveFile(GUIComponent component, object obj) { diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 727b00907..f00696f95 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -59,10 +59,10 @@ namespace Barotrauma Stretch = true, RelativeSpacing = 0.02f }; - + // === CAMPAIGN var campaignHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, isHorizontal: true); - + new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), campaignHolder.RectTransform), "MainMenuCampaignIcon") { CanBeFocused = false @@ -84,6 +84,17 @@ namespace Barotrauma RelativeSpacing = 0.035f }; + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), "Tutorial", textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.Tutorials, + OnClicked = (tb, userdata) => + { + SelectTab(tb, userdata); + return true; + } + }; + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("LoadGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") { ForceUpperCase = true, @@ -309,6 +320,7 @@ namespace Barotrauma false, null, ""); foreach (Tutorial tutorial in Tutorial.Tutorials) { + if (tutorial is ContextualTutorial) continue; var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), tutorialList.Content.RectTransform), tutorial.Name, textAlignment: Alignment.Center, font: GUI.LargeFont) { UserData = tutorial @@ -320,8 +332,6 @@ namespace Barotrauma return true; }; - UpdateTutorialList(); - this.game = game; } #endregion @@ -338,8 +348,6 @@ namespace Barotrauma } Submarine.Unload(); - - UpdateTutorialList(); ResetButtonStates(null); @@ -388,7 +396,6 @@ namespace Barotrauma case Tab.NewGame: campaignSetupUI.CreateDefaultSaveName(); campaignSetupUI.RandomizeSeed(); - campaignSetupUI.UpdateTutorialSelection(); campaignSetupUI.UpdateSubList(Submarine.SavedSubmarines); break; case Tab.LoadGame: @@ -405,6 +412,7 @@ namespace Barotrauma case Tab.HostServer: break; case Tab.Tutorials: + UpdateTutorialList(); break; case Tab.CharacterEditor: Submarine.MainSub = null; @@ -435,6 +443,8 @@ namespace Barotrauma public bool ReturnToMainMenu(GUIButton button, object obj) { + GUI.PreventPauseMenuToggle = false; + if (Selected != this) { Select(); @@ -770,13 +780,11 @@ namespace Barotrauma } selectedSub = new Submarine(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), ""); - - ContextualTutorial.Selected = campaignSetupUI.TutorialSelected; + GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.List.Find(g => g.Identifier == "singleplayercampaign")); (GameMain.GameSession.GameMode as CampaignMode).GenerateMap(mapSeed); - GameMain.LobbyScreen.Select(); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index b8f27ed24..acf97f741 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -426,7 +426,7 @@ namespace Barotrauma } } - private bool canSpeak; + public bool CanSpeak; private bool speechImpedimentSet; @@ -436,7 +436,7 @@ namespace Barotrauma { get { - if (!canSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f; + if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f; return speechImpediment; } set @@ -710,7 +710,7 @@ namespace Barotrauma displayName = TextManager.Get($"Character.{Path.GetFileName(Path.GetDirectoryName(file))}", true); IsHumanoid = doc.Root.GetAttributeBool("humanoid", false); - canSpeak = doc.Root.GetAttributeBool("canspeak", false); + CanSpeak = doc.Root.GetAttributeBool("canspeak", false); needsAir = doc.Root.GetAttributeBool("needsair", false); Noise = doc.Root.GetAttributeFloat("noise", 100f); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs index 8dcf98fce..cb7df70ec 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs @@ -399,7 +399,7 @@ namespace Barotrauma { ID = idCounter; idCounter++; - Name = element.GetAttributeString("name", "unnamed"); + Name = element.GetAttributeString("name", ""); string genderStr = element.GetAttributeString("gender", "male").ToLowerInvariant(); File = element.GetAttributeString("file", ""); SourceElement = GetConfig(File).Root; @@ -423,6 +423,29 @@ namespace Barotrauma element.GetAttributeInt("beardindex", -1), element.GetAttributeInt("moustacheindex", -1), element.GetAttributeInt("faceattachmentindex", -1)); + + if (string.IsNullOrEmpty(Name)) + { + if (SourceElement.Element("name") != null) + { + string firstNamePath = SourceElement.Element("name").GetAttributeString("firstname", ""); + if (firstNamePath != "") + { + firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); + Name = ToolBox.GetRandomLine(firstNamePath); + } + + string lastNamePath = SourceElement.Element("name").GetAttributeString("lastname", ""); + if (lastNamePath != "") + { + lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); + if (Name != "") Name += " "; + Name += ToolBox.GetRandomLine(lastNamePath); + } + } + } + + StartItemsGiven = element.GetAttributeBool("startitemsgiven", false); string personalityName = element.GetAttributeString("personality", ""); ragdollFileName = element.GetAttributeString("ragdoll", string.Empty); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index 831c07c39..fa9ff9e17 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -801,10 +801,7 @@ namespace Barotrauma.Items.Components string msg = TextManager.Get(Msg, true); if (msg != null) { - foreach (InputType inputType in Enum.GetValues(typeof(InputType))) - { - msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString()); - } + msg = TextManager.ParseInputTypes(msg); DisplayMsg = msg; } else diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index 95476dc1f..fb4392e3c 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -13,6 +13,11 @@ namespace Barotrauma.Items.Components private ItemContainer inputContainer, outputContainer; + public ItemContainer InputContainer + { + get { return inputContainer; } + } + public ItemContainer OutputContainer { get { return outputContainer; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs index 7ade916d4..cd1b0e081 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs @@ -23,6 +23,16 @@ namespace Barotrauma.Items.Components private ItemContainer inputContainer, outputContainer; + public ItemContainer InputContainer + { + get { return inputContainer; } + } + + public ItemContainer OutputContainer + { + get { return outputContainer; } + } + private float progressState; public Fabricator(Item item, XElement element) @@ -98,7 +108,23 @@ namespace Barotrauma.Items.Components { return (picker != null); } - + + public void RemoveFabricationRecipes(List allowedIdentifiers) + { + for (int i = 0; i < fabricationRecipes.Count; i++) + { + if (!allowedIdentifiers.Contains(fabricationRecipes[i].TargetItem.Identifier)) + { + fabricationRecipes.RemoveAt(i); + i--; + } + } + + CreateRecipes(); + } + + partial void CreateRecipes(); + private void StartFabricating(FabricationRecipe selectedItem, Character user) { if (selectedItem == null) return; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index e6e6a9541..85d352153 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -206,6 +206,16 @@ namespace Barotrauma return true; } + public bool IsFull() + { + for (int i = 0; i < capacity; i++) + { + if (Items[i] == null) return false; + } + + return true; + } + protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent) { if (item?.ParentInventory == null || Items[index] == null) return false; diff --git a/Barotrauma/BarotraumaShared/Source/Map/DummyFireSource.cs b/Barotrauma/BarotraumaShared/Source/Map/DummyFireSource.cs new file mode 100644 index 000000000..fe8d445ec --- /dev/null +++ b/Barotrauma/BarotraumaShared/Source/Map/DummyFireSource.cs @@ -0,0 +1,46 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Barotrauma +{ + partial class DummyFireSource : FireSource + { + private Vector2 maxSize; + + public bool Removed + { + get { return removed; } + } + + public DummyFireSource(Vector2 maxSize, Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) : base(worldPosition, spawningHull, isNetworkMessage) + { + this.maxSize = maxSize; + } + + public override float DamageRange + { + get { return 5f; } + } + + protected override void LimitSize() + { + if (hull == null) return; + + position.X = Math.Max(hull.Rect.X, position.X); + position.Y = Math.Min(hull.Rect.Y, position.Y); + + size.X = Math.Min(maxSize.X, size.X); + size.Y = Math.Min(maxSize.Y, size.Y); + } + + protected override void AdjustXPos(float growModifier, float deltaTime) + { + + } + + protected override void ReduceOxygen(float deltaTime) + { + + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs index a73ac8907..40f2acd92 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs @@ -40,7 +40,7 @@ namespace Barotrauma flames = true; underwaterBubble = true; } - + public Explosion(XElement element, string parentDebugName) { attack = new Attack(element, parentDebugName + ", Explosion"); @@ -62,6 +62,16 @@ namespace Barotrauma CameraShake = element.GetAttributeFloat("camerashake", attack.Range * 0.1f); } + public void DisableParticles() + { + sparks = false; + shockwave = false; + smoke = false; + flash = false; + flames = false; + underwaterBubble = false; + } + public List> GetRecentExplosions(float maxSecondsAgo) { return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo); diff --git a/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs b/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs index c252c399a..f3b97de1c 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs @@ -14,15 +14,15 @@ namespace Barotrauma { const float OxygenConsumption = 50.0f; const float GrowSpeed = 5.0f; - - private Hull hull; - private Vector2 position; - private Vector2 size; + protected Hull hull; + + protected Vector2 position; + protected Vector2 size; private Entity Submarine; - private bool removed; + protected bool removed; #if CLIENT private List burnDecals = new List(); @@ -59,7 +59,7 @@ namespace Barotrauma } } - public float DamageRange + public virtual float DamageRange { get { return (float)Math.Sqrt(size.X) * 20.0f; } } @@ -94,7 +94,7 @@ namespace Barotrauma size = new Vector2(10.0f, 10.0f); } - private void LimitSize() + protected virtual void LimitSize() { if (hull == null) return; @@ -160,9 +160,9 @@ namespace Barotrauma if (removed) { return; } } - hull.Oxygen -= size.X * deltaTime * OxygenConsumption; + ReduceOxygen(deltaTime); - position.X -= GrowSpeed * growModifier * 0.5f * deltaTime; + AdjustXPos(growModifier, deltaTime); size.X += GrowSpeed * growModifier * deltaTime; size.Y = MathHelper.Clamp(size.Y + GrowSpeed * growModifier * deltaTime, 10.0f, 50.0f); @@ -182,6 +182,16 @@ namespace Barotrauma } } + protected virtual void ReduceOxygen(float deltaTime) + { + hull.Oxygen -= size.X * deltaTime * OxygenConsumption; + } + + protected virtual void AdjustXPos(float growModifier, float deltaTime) + { + position.X -= GrowSpeed * growModifier * 0.5f * deltaTime; + } + partial void UpdateProjSpecific(float growModifier); private void OnChangeHull(Vector2 pos, Hull particleHull) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 5bace8352..695b56df0 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -138,6 +138,9 @@ namespace Barotrauma public Submarine StartOutpost { get; private set; } public Submarine EndOutpost { get; private set; } + private Submarine preSelectedStartOutpost; + private Submarine preSelectedEndOutpost; + public string Seed { get { return seed; } @@ -209,7 +212,7 @@ namespace Barotrauma /// /// A scalar between 0-100 /// A scalar between 0-1 (0 = the minimum width defined in the generation params is used, 1 = the max width is used) - public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome) + public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome, Submarine startOutpost = null, Submarine endOutPost = null) : base(null) { @@ -225,6 +228,9 @@ namespace Barotrauma (width / GridCellSize) * GridCellSize, (generationParams.Height / GridCellSize) * GridCellSize); + preSelectedStartOutpost = startOutpost; + preSelectedEndOutpost = endOutPost; + //remove from entity dictionary base.Remove(); } @@ -1510,14 +1516,24 @@ namespace Barotrauma continue; } - //only create a starting outpost in campaign mode - if (GameMain.GameSession?.GameMode as CampaignMode == null && ((i == 0) == !Mirrored)) + //only create a starting outpost in campaign and tutorial modes + if (!IsModeStartOutpostCompatible() && ((i == 0) == !Mirrored)) { continue; } - - string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server); - var outpost = new Submarine(outpostFile, tryLoad: false); + + Submarine outpost = null; + + if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null) + { + string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server); + outpost = new Submarine(outpostFile, tryLoad: false); + } + else + { + outpost = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost; + } + outpost.Load(unloadPrevious: false); outpost.MakeOutpost(); @@ -1569,6 +1585,15 @@ namespace Barotrauma } } + private bool IsModeStartOutpostCompatible() + { +#if CLIENT + return GameMain.GameSession?.GameMode as CampaignMode != null || GameMain.GameSession?.GameMode as TutorialMode != null; +#else + return GameMain.GameSession?.GameMode as CampaignMode != null; +#endif + } + public override void Remove() { base.Remove(); diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index 858b7bcca..a239508b9 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -30,13 +30,15 @@ namespace Barotrauma //the position and dimensions of the entity protected Rectangle rect; - + + public bool ExternalHighlight = false; + //is the mouse inside the rect - protected bool isHighlighted; + private bool isHighlighted; public bool IsHighlighted { - get { return isHighlighted; } + get { return isHighlighted || ExternalHighlight; } set { isHighlighted = value; } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index ab486666c..b686f5dac 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -431,6 +431,12 @@ namespace Barotrauma else if (ic is Pickable pickable) { //prevent picking up (or deattaching) items +#if CLIENT + if (GameMain.GameSession.GameMode is TutorialMode) + { + continue; + } +#endif pickable.CanBePicked = false; pickable.CanBeSelected = false; } @@ -1426,7 +1432,7 @@ namespace Barotrauma doc.Root.Add(new XAttribute("md5hash", hash.Hash)); if (previewImage != null) { - doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray()))); + //doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray()))); } try diff --git a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs index f932497b4..8b69e2434 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs @@ -299,7 +299,7 @@ namespace Barotrauma { for (int dir = -1; dir <= 1; dir += 2) { - WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f,30f)); + WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f, 30f)); if (closest == null) continue; stairPoints[i].ConnectTo(closest); } @@ -412,7 +412,7 @@ namespace Barotrauma WayPoint closest = wayPoint.FindClosest( dir, true, new Vector2(-tolerance, tolerance), - gap.ConnectedDoor == null ? null : gap.ConnectedDoor.Body.FarseerBody); + gap.ConnectedDoor?.Body.FarseerBody); if (closest != null) { @@ -423,13 +423,26 @@ namespace Barotrauma foreach (Gap gap in Gap.GapList) { - if (gap.IsHorizontal || gap.IsRoomToRoom) continue; + if (gap.IsHorizontal || gap.IsRoomToRoom || !gap.linkedTo.Any(l => l is Hull)) { continue; } //too small to walk through - if (gap.Rect.Width < 100.0f) continue; + if (gap.Rect.Width < 100.0f) { continue; } var wayPoint = new WayPoint( new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2), SpawnType.Path, submarine, gap); + + float tolerance = outSideWaypointInterval / 2.0f; + Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull); + int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y); + + WayPoint closest = wayPoint.FindClosest( + dir, false, new Vector2(-tolerance, tolerance), + gap.ConnectedDoor?.Body.FarseerBody); + + if (closest != null) + { + wayPoint.ConnectTo(closest); + } } var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && !w.linkedTo.Any()); diff --git a/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs index 8f9090ae9..10b3691c6 100644 --- a/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs @@ -46,7 +46,15 @@ namespace Barotrauma if (File.Exists(filePath)) { - doc = XDocument.Load(filePath, LoadOptions.SetBaseUri); + try + { + doc = XDocument.Load(filePath, LoadOptions.SetBaseUri); + } + catch + { + return null; + } + if (doc.Root == null) return null; } diff --git a/Barotrauma/BarotraumaShared/Source/TextManager.cs b/Barotrauma/BarotraumaShared/Source/TextManager.cs index 52df9ac7b..ff727c72b 100644 --- a/Barotrauma/BarotraumaShared/Source/TextManager.cs +++ b/Barotrauma/BarotraumaShared/Source/TextManager.cs @@ -118,6 +118,16 @@ namespace Barotrauma } } + public static string ParseInputTypes(string text) + { + foreach (InputType inputType in Enum.GetValues(typeof(InputType))) + { + text = text.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString()); + text = text.Replace("[InputType." + inputType.ToString() + "]", GameMain.Config.KeyBind(inputType).ToString()); + } + return text; + } + public static string GetFormatted(string textTag, bool returnNull = false, params object[] args) { string text = Get(textTag, returnNull);