using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Subsurface.Items.Components; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Subsurface { class TutorialMode : GameMode { public readonly CrewManager CrewManager; private GUIComponent infoBox; public static void Start() { Submarine.Load("Content/Map/TutorialSub.gz"); Game1.GameSession = new GameSession(Submarine.Loaded, "", GameModePreset.list.Find(gm => gm.Name.ToLower()=="tutorial")); Game1.GameSession.StartShift(TimeSpan.Zero, "tutorial"); Game1.GameSession.taskManager.Tasks.Clear(); Game1.GameScreen.Select(); } public TutorialMode(GameModePreset preset) : base(preset) { CrewManager = new CrewManager(); } public override void Start(TimeSpan duration) { base.Start(duration); WayPoint wayPoint = WayPoint.GetRandom(SpawnType.Cargo, null); if (wayPoint==null) { DebugConsole.ThrowError("A waypoint with the spawntype ''cargo'' is required for the tutorial event"); return; } CharacterInfo charInfo = new CharacterInfo(Character.HumanConfigFile, "", Gender.None, JobPrefab.List.Find(jp => jp.Name=="Engineer")); Character character = new Character(charInfo, wayPoint.SimPosition); Character.Controlled = character; character.GiveJobItems(null); foreach (Item item in character.Inventory.items) { if (item == null || item.Name != "ID Card") continue; item.AddTag("com"); item.AddTag("eng"); break; } CrewManager.AddCharacter(character); CoroutineManager.StartCoroutine(UpdateState()); } public override void Update(float deltaTime) { base.Update(deltaTime); CrewManager.Update(deltaTime); if (infoBox!=null) infoBox.Update(deltaTime); } private IEnumerable UpdateState() { yield return new WaitForSeconds(4.0f); infoBox = CreateInfoFrame("Use WASD to move and 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"); Door tutorialDoor = Item.itemList.Find(i => i.HasTag("tutorialdoor")).GetComponent(); while (!tutorialDoor.IsOpen) { yield return Status.Running; } yield return new WaitForSeconds(2.0f); //----------------------------------- infoBox = CreateInfoFrame("Now it's time to power up the submarine. 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(); while (Vector2.Distance(Character.Controlled.Position, reactor.Item.Position)>200.0f) { yield return Status.Running; } infoBox = CreateInfoFrame("Select the reactor by walking next to it and pressing E."); while (Character.Controlled.SelectedConstruction != reactor.Item) { yield return Status.Running; } yield return new WaitForSeconds(0.5f); infoBox = CreateInfoFrame("This is the control panel of the reactor. Try turning it on by increasing the fission rate."); while (reactor.FissionRate <= 0.0f) { yield return Status.Running; } yield return new WaitForSeconds(0.5f); infoBox = CreateInfoFrame("The reactor core has started generating heat, which in turn generates power for the submarine. It won't generate much power at the moment, " +" because the shutdown temperature is set to 500. When the temperature of the reactor raises higher than the shutdown temperature, the reactor will automatically start to cool itself down." + " You should increase it to somewhere around 5000."); while (Math.Abs(reactor.ShutDownTemp-5000.0f) > 400.0f) { yield return Status.Running; } yield return new WaitForSeconds(0.5f); infoBox = CreateInfoFrame("The amount of power generated by the reactor should be kept close to the amount of power consumed by the devices in the submarine. " +"If there's not enough power, devices won't function properly, and if there's too much power, some devices may be damaged. Turn on ''Automatic temperature control'' to " +"make the reactor automatically adjust the temperature to a suitable level."); while (!reactor.AutoTemp) { yield return Status.Running; } yield return new WaitForSeconds(0.5f); infoBox = CreateInfoFrame("That's the basics you need to know to power up the reactor! Now that there's power available for the engines, let's try steering the sub. " +"Deselect the reactor by pressing E and head to the command room at the left edge of the vessel."); Steering steering = Item.itemList.Find(i => i.HasTag("tutorialsteering")).GetComponent(); Radar radar = steering.Item.GetComponent(); while (Vector2.Distance(Character.Controlled.Position, steering.Item.Position) > 150.0f) { yield return Status.Running; } infoBox = CreateInfoFrame("Select the navigation terminal by walking next to it and pressing E."); while (Character.Controlled.SelectedConstruction != steering.Item) { yield return Status.Running; } yield return new WaitForSeconds(0.5f); 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."); while (Character.Controlled.SelectedConstruction == steering.Item) { yield return Status.Running; } yield return new WaitForSeconds(1.0f); 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 select the terminal again by pressing E."); while (Character.Controlled.SelectedConstruction != steering.Item || Character.Controlled.SelectedItems.FirstOrDefault(i => i != null && i.Name == "Screwdriver") == null) { yield return Status.Running; } 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."); while (!HasItem("Wire")) { yield return Status.Running; } infoBox = CreateInfoFrame("Head back to the navigation terminal to fix the wiring."); PowerTransfer junctionBox = Item.itemList.Find(i => i!=null && i.HasTag("tutorialjunctionbox")).GetComponent(); while ((Character.Controlled.SelectedConstruction != junctionBox.Item && Character.Controlled.SelectedConstruction != steering.Item) || Character.Controlled.SelectedItems.FirstOrDefault(i => i != null && i.Name == "Screwdriver") == null) { yield return Status.Running; } if (Character.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."); while (Character.Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent() != null) == null) { yield return Status.Running; } } 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")); var junctionConnection = junctionBox.Item.Connections.Find(c => c.Name.Contains("power")); while (steeringConnection.Wires.FirstOrDefault(w => w != null) == null) { yield return Status.Running; } 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 (Character.Controlled.SelectedConstruction!=null) { yield return Status.Running; } 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 " + "remove the previous attachment by right clicking. Or you can just run the wire straight to the junction box and attach it " + " the same way you did to the navigation terminal."); while (radar.Voltage<0.1f) { yield return Status.Running; } infoBox = CreateInfoFrame("Great! Now we should be able to get moving."); while (Character.Controlled.SelectedConstruction != steering.Item) { yield return Status.Running; } infoBox = CreateInfoFrame("You can take a look at the area around the sub by pressing ''Activate Radar''."); while (!radar.IsActive) { yield return Status.Running; } yield return new WaitForSeconds(0.5f); infoBox = CreateInfoFrame("The white box in the middle is the submarine, and the white lines outside it are the walls of an underwater cavern. " + "Try moving the submarine by clicking somewhere inside the rectangle and draggind the pointer to the direction you want to go to."); while (steering.CurrTargetVelocity == Vector2.Zero && steering.CurrTargetVelocity.Length() < 40.0f) { yield return Status.Running; } 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."); yield return new WaitForSeconds(8.0f); infoBox = CreateInfoFrame("Steer the submarine downwards, heading further into the cavern."); while (Submarine.Loaded.Position.Y > 31000.0f) { yield return Status.Running; } var moloch = new Character("Content/Characters/Moloch/moloch.xml", steering.Item.SimPosition + Vector2.UnitX * 15.0f); moloch.PlaySound(AIController.AiState.Attack); infoBox = CreateInfoFrame("Uh-oh... Something enormous just appeared on the radar."); List windows = new List(); foreach (Structure s in Structure.wallList) { if (s.CastShadow || !s.HasBody) continue; if (s.Rect.Right > steering.Item.Position.X) windows.Add(s); } bool broken = false; do { moloch.AIController.SelectTarget(steering.Item.CurrentHull.AiTarget); Vector2 steeringDir = windows[0].Position - moloch.Position; if (steeringDir != Vector2.Zero) steeringDir = Vector2.Normalize(steeringDir); foreach (Limb limb in moloch.AnimController.limbs) { limb.body.LinearVelocity = new Vector2(limb.LinearVelocity.X, limb.LinearVelocity.Y + steeringDir.Y*0.01f); } moloch.AIController.Steering = steeringDir; foreach (Structure window in windows) { for (int i = 0; i < window.SectionCount; i++) { if (!window.SectionHasHole(i)) continue; broken = true; break; } if (broken) break; } yield return new WaitForSeconds(1.0f); } while (!broken); yield return new WaitForSeconds(1.0f); var capacitor1 = Item.itemList.Find(i => i.HasTag("capacitor1")).GetComponent(); 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!"); Door commandDoor1 = Item.itemList.Find(i => i.HasTag("commanddoor1")).GetComponent(); Door commandDoor2 = Item.itemList.Find(i => i.HasTag("commanddoor2")).GetComponent(); Door commandDoor3 = Item.itemList.Find(i => i.HasTag("commanddoor3")).GetComponent(); while (commandDoor1.IsOpen && (commandDoor2.IsOpen || commandDoor3.IsOpen)) { yield return Status.Running; } infoBox = CreateInfoFrame("Great! You should find yourself an diving mask or a diving suit, in case the creature causes more damage. "+ "There are some in the room next to the airlock."); while (!HasItem("Diving Mask") && !HasItem("Diving Suit")) { yield return Status.Running; } if (HasItem("Diving Mask")) { 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."); } else if (HasItem("Diving Suit")) { 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."); } while (!HasItem("Oxygen Tank")) { yield return Status.Running; } yield return new WaitForSeconds(5.0f); infoBox = CreateInfoFrame("Now it's time to stop the creature attacking the submarine. Head to the railgun room at the upper right corner of the sub."); var railGun = Item.itemList.Find(i => i.GetComponent()!=null); while (Vector2.Distance(Character.Controlled.Position, railGun.Position)>500) { 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 " +" 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) { yield return new WaitForSeconds(1.0f); } infoBox = CreateInfoFrame("The capacitors consume large amounts of power when they're being charged at a high rate. "+ "Be cautious to overload the electrical grid or the reactor. They also take some time to recharge, so now is a good "+ "time to head to the room below to load some shells into the railgun."); var loader = Item.itemList.Find(i => i.Name == "Railgun Loader").GetComponent(); while (Math.Abs(Character.Controlled.Position.Y - loader.Item.Position.Y)>50) { yield return Status.Running; } 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."); while (loader.Item.ContainedItems.FirstOrDefault(i => i != null) != null) { capacitor1.Charge += 1.0f; capacitor2.Charge += 1.0f; yield return Status.Running; } yield return Status.Success; } private bool HasItem(string itemName) { if (Character.Controlled == null) return false; return Character.Controlled.Inventory.items.FirstOrDefault(i => i != null && i.Name == itemName)!=null; } /// /// keeps the enemy away from the sub until the capacitors are loaded /// private IEnumerable KeepEnemyAway(Character enemy, PowerContainer[] capacitors) { do { Vector2 targetPos = Character.Controlled.Position + new Vector2(0.0f, 3000.0f); Vector2 steering = targetPos - enemy.Position; if (steering != Vector2.Zero) steering = Vector2.Normalize(steering); enemy.AIController.Steering = steering*2.0f; yield return Status.Running; } while (capacitors.FirstOrDefault(c => c.Charge > 0.4f) == null); yield return Status.Success; } public override void Draw(SpriteBatch spriteBatch) { base.Draw(spriteBatch); CrewManager.Draw(spriteBatch); if (infoBox != null) infoBox.Draw(spriteBatch); } private GUIComponent CreateInfoFrame(string text) { int width = 300; int height = 80; string wrappedText = ToolBox.WrapText(text, width, GUI.Font); height += wrappedText.Split('\n').Length*25; var infoBlock = new GUIFrame(new Rectangle(-20, 20, width, height), null, Alignment.TopRight, GUI.style); //infoBlock.Color = infoBlock.Color * 0.8f; infoBlock.Padding = new Vector4(10.0f, 10.0f, 10.0f, 10.0f); infoBlock.Flash(Color.Green); new GUITextBlock(new Rectangle(10, 10, width - 40, height), text, GUI.style, infoBlock, true); GUI.PlayMessageSound(); return infoBlock; } } }