diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs index b7ef43d9f..2f9e0a46f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs @@ -189,6 +189,9 @@ namespace Barotrauma.Tutorials captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false; captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = false; + + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Started"); + GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); } public override IEnumerable UpdateState() @@ -223,6 +226,7 @@ namespace Barotrauma.Tutorials while (!HasOrder(captain_medic, "follow")); SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); RemoveCompletedObjective(0); + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective0"); // Submarine do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected); @@ -238,6 +242,8 @@ namespace Barotrauma.Tutorials //HighlightOrderOption("jobspecific"); } while (!HasOrder(captain_mechanic, "repairsystems") && !HasOrder(captain_mechanic, "repairmechanical") && !HasOrder(captain_mechanic, "repairelectrical")); RemoveCompletedObjective(1); + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective1"); + yield return new WaitForSeconds(2f, false); TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); GameMain.GameSession.CrewManager.AddCharacter(captain_security); @@ -250,6 +256,8 @@ namespace Barotrauma.Tutorials } while (!HasOrder(captain_security, "operateweapons")); RemoveCompletedObjective(2); + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective2"); + yield return new WaitForSeconds(4f, false); TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); GameMain.GameSession.CrewManager.AddCharacter(captain_engineer); @@ -265,6 +273,8 @@ namespace Barotrauma.Tutorials } while (!HasOrder(captain_engineer, "operatereactor", "powerup")); RemoveCompletedObjective(3); + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective3"); + do { yield return null; } while (!tutorial_submarineReactor.IsActive); // Wait until reactor on TriggerTutorialSegment(4); while (ContentRunning) yield return null; @@ -279,6 +289,8 @@ namespace Barotrauma.Tutorials } while (Submarine.MainSub.DockedTo.Any()); captain_navConsole.UseAutoDocking = false; RemoveCompletedObjective(4); + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective4"); + yield return new WaitForSeconds(2f, false); TriggerTutorialSegment(5); // Navigate to destination do @@ -294,6 +306,8 @@ namespace Barotrauma.Tutorials } while (captain_sonar.CurrentMode != Sonar.Mode.Active); do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f); RemoveCompletedObjective(5); + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective5"); + captain_navConsole.UseAutoDocking = true; yield return new WaitForSeconds(4f, false); TriggerTutorialSegment(6); // Docking @@ -303,13 +317,16 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f, false); } while (!Submarine.MainSub.AtEndExit || !Submarine.MainSub.DockedTo.Any()); RemoveCompletedObjective(6); + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective6"); + yield return new WaitForSeconds(3f, false); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null); SetHighlight(captain_navConsole.Item, false); SetHighlight(captain_sonar.Item, false); SetHighlight(captain_statusMonitor, false); captain.RemoveActiveObjectiveEntity(captain_navConsole.Item); - + + GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Completed"); CoroutineManager.StartCoroutine(TutorialCompleted()); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs index 4f6f7d940..2a0c3ff52 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs @@ -198,6 +198,9 @@ namespace Barotrauma.Tutorials Item reactorItem = Item.ItemList.Find(i => i.Submarine == Submarine.MainSub && i.GetComponent() != null); reactorItem.GetComponent().AutoTemp = true; + + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Started"); + GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); } public override IEnumerable UpdateState() @@ -281,6 +284,7 @@ namespace Barotrauma.Tutorials SetHighlight(doctor_suppliesCabinet.Item, false); RemoveCompletedObjective(0); + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective0"); yield return new WaitForSeconds(1.0f, false); @@ -294,6 +298,7 @@ namespace Barotrauma.Tutorials } yield return null; RemoveCompletedObjective(1); + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective1"); yield return new WaitForSeconds(1.0f, false); TriggerTutorialSegment(2); //Treat self while (doctor.CharacterHealth.GetAfflictionStrength("damage") > 0.01f) @@ -311,6 +316,7 @@ namespace Barotrauma.Tutorials } RemoveCompletedObjective(2); + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective2"); SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, true); while (CharacterHealth.OpenHealthWindow != null) @@ -358,6 +364,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f, false); } RemoveCompletedObjective(3); + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective3"); SetHighlight(doctor_medBayCabinet.Item, true); SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, true); patient1.CharacterHealth.UseHealthWindow = true; @@ -401,6 +408,7 @@ namespace Barotrauma.Tutorials } RemoveCompletedObjective(4); + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective4"); SetHighlight(patient1, false); yield return new WaitForSeconds(1.0f, false); @@ -442,6 +450,7 @@ namespace Barotrauma.Tutorials yield return null; } RemoveCompletedObjective(5); + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective5"); SetHighlight(patient2, false); doctor.RemoveActiveObjectiveEntity(patient2); CoroutineManager.StopCoroutines("KeepPatient2Alive"); @@ -497,6 +506,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f, false); } RemoveCompletedObjective(6); + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective6"); foreach (var patient in subPatients) { SetHighlight(patient, false); @@ -504,6 +514,7 @@ namespace Barotrauma.Tutorials } // END TUTORIAL + GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Completed"); CoroutineManager.StartCoroutine(TutorialCompleted()); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs index 80f7ed649..e55e315fb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs @@ -244,6 +244,9 @@ namespace Barotrauma.Tutorials engineer_submarineJunctionBox_2.Condition = 0f; engineer_submarineJunctionBox_3.Indestructible = false; engineer_submarineJunctionBox_3.Condition = 0f; + + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Started"); + GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); } public override IEnumerable UpdateState() @@ -317,6 +320,7 @@ namespace Barotrauma.Tutorials yield return null; } while (!engineer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted RemoveCompletedObjective(0); + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective0"); SetHighlight(engineer_equipmentCabinet.Item, false); SetHighlight(engineer_reactor.Item, true); SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, true); @@ -352,6 +356,7 @@ namespace Barotrauma.Tutorials yield return null; } while (engineer_reactor.AvailableFuel == 0); RemoveCompletedObjective(1); + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective1"); TriggerTutorialSegment(2); CoroutineManager.StartCoroutine(ReactorOperatedProperly()); do @@ -395,6 +400,7 @@ namespace Barotrauma.Tutorials engineer.SelectedConstruction = null; engineer_reactor.CanBeSelected = false; RemoveCompletedObjective(2); + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective2"); SetHighlight(engineer_reactor.Item, false); SetHighlight(engineer_brokenJunctionBox, true); SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, true); @@ -421,6 +427,7 @@ namespace Barotrauma.Tutorials } while (repairableJunctionBoxComponent.IsBelowRepairThreshold); // Wait until repaired SetHighlight(engineer_brokenJunctionBox, false); RemoveCompletedObjective(3); + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective3"); SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true); for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) { @@ -439,6 +446,7 @@ namespace Barotrauma.Tutorials SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false); } RemoveCompletedObjective(4); + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective4"); do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained wiringActive = false; SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true); @@ -465,6 +473,7 @@ namespace Barotrauma.Tutorials do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (repairableJunctionBoxComponent1.IsBelowRepairThreshold || repairableJunctionBoxComponent2.IsBelowRepairThreshold || repairableJunctionBoxComponent3.IsBelowRepairThreshold); CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); RemoveCompletedObjective(5); + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective5"); yield return new WaitForSeconds(2f, false); TriggerTutorialSegment(6); // Powerup reactor @@ -474,10 +483,12 @@ namespace Barotrauma.Tutorials engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item); SetHighlight(engineer_submarineReactor.Item, false); RemoveCompletedObjective(6); + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective6"); GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null); yield return new WaitForSeconds(4f, false); + GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Completed"); CoroutineManager.StartCoroutine(TutorialCompleted()); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs index a9ea9047a..dfe1fca82 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs @@ -290,6 +290,9 @@ namespace Barotrauma.Tutorials 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; + + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Started"); + GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); } public override void Update(float deltaTime) @@ -325,6 +328,7 @@ namespace Barotrauma.Tutorials SetHighlight(mechanic_firstDoor.Item, false); yield return new WaitForSeconds(1.5f, false); RemoveCompletedObjective(0); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective0"); // Room 2 yield return new WaitForSeconds(0.0f, false); @@ -368,6 +372,7 @@ namespace Barotrauma.Tutorials SetHighlight(mechanic_equipmentCabinet.Item, false); yield return new WaitForSeconds(1.5f, false); RemoveCompletedObjective(1); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective1"); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Breach"), ChatMessageType.Radio, null); // Room 3 @@ -391,6 +396,8 @@ namespace Barotrauma.Tutorials do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1); RemoveCompletedObjective(2); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective2"); + yield return new WaitForSeconds(1f, false); TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Pump objective SetHighlight(mechanic_workingPump.Item, true); @@ -408,6 +415,8 @@ namespace Barotrauma.Tutorials SetHighlight(mechanic_workingPump.Item, false); do { yield return null; } while (mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained RemoveCompletedObjective(3); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective3"); + SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, true); //TriggerTutorialSegment(11, GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Up], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Down], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select]); // Ladder objective //do { yield return null; } while (!mechanic_ladderSensor.MotionDetected); @@ -516,6 +525,8 @@ namespace Barotrauma.Tutorials SetHighlight(mechanic_deconstructor.Item, false); RemoveCompletedObjective(4); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective4"); + yield return new WaitForSeconds(1f, false); TriggerTutorialSegment(5); // Fabricate SetHighlight(mechanic_fabricator.Item, true); @@ -565,6 +576,7 @@ namespace Barotrauma.Tutorials yield return null; } while (mechanic.Inventory.FindItemByIdentifier("extinguisher".ToIdentifier()) == null); // Wait until extinguisher is created RemoveCompletedObjective(5); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective5"); SetHighlight(mechanic_fabricator.Item, false); SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, true); @@ -574,6 +586,7 @@ namespace Barotrauma.Tutorials do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished yield return new WaitForSeconds(3f, false); RemoveCompletedObjective(6); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective6"); if (mechanic.HasEquippedItem("extinguisher".ToIdentifier())) // do not trigger if dropped already { @@ -584,6 +597,7 @@ namespace Barotrauma.Tutorials yield return null; } while (mechanic.HasEquippedItem("extinguisher".ToIdentifier())); RemoveCompletedObjective(7); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective7"); } SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, true); @@ -608,6 +622,7 @@ namespace Barotrauma.Tutorials } while (!mechanic.HasEquippedItem("divingsuit".ToIdentifier(), slotType: InvSlotType.OuterClothes)); SetHighlight(mechanic_divingSuitContainer.Item, false); RemoveCompletedObjective(8); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective8"); SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true); // Room 7 @@ -650,6 +665,7 @@ namespace Barotrauma.Tutorials } } while (repairablePumpComponent.IsBelowRepairThreshold || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive); RemoveCompletedObjective(9); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective9"); SetHighlight(mechanic_brokenPump.Item, false); do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening); SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); @@ -674,9 +690,11 @@ namespace Barotrauma.Tutorials do { CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); yield return null; } while (repairablePumpComponent1.IsBelowRepairThreshold || repairablePumpComponent2.IsBelowRepairThreshold || repairableEngineComponent.IsBelowRepairThreshold); CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); RemoveCompletedObjective(10); + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective10"); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null); // END TUTORIAL + GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Completed"); CoroutineManager.StartCoroutine(TutorialCompleted()); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs index 751cc4cd8..b2514450f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs @@ -251,6 +251,9 @@ namespace Barotrauma.Tutorials 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); + + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Started"); + GameAnalyticsManager.AddDesignEvent("Tutorial:Started"); } public override IEnumerable UpdateState() @@ -310,6 +313,7 @@ namespace Barotrauma.Tutorials yield return null; } while (!officer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted //RemoveCompletedObjective(segments[0]); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective0"); SetHighlight(officer_equipmentCabinet.Item, false); do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item)); TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Equip melee weapon & armor @@ -330,6 +334,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1f, false); } while (!officer.HasEquippedItem("stunbaton".ToIdentifier()) || !officer.HasEquippedItem("bodyarmor".ToIdentifier()) || !officer.HasEquippedItem("ballistichelmet1".ToIdentifier())); RemoveCompletedObjective(1); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective1"); SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true); // Room 3 @@ -338,6 +343,7 @@ namespace Barotrauma.Tutorials officer_crawler = SpawnMonster("crawler", officer_crawlerSpawnPos); do { yield return null; } while (!officer_crawler.IsDead); RemoveCompletedObjective(2); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective2"); Heal(officer); yield return new WaitForSeconds(1f, false); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.CrawlerDead"), ChatMessageType.Radio, null); @@ -366,6 +372,8 @@ namespace Barotrauma.Tutorials SetHighlight(officer_ammoShelf_1.Item, false); SetHighlight(officer_ammoShelf_2.Item, false); RemoveCompletedObjective(3); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective3"); + yield return new WaitForSeconds(2f, false); TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Kill hammerhead officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos); @@ -401,6 +409,8 @@ namespace Barotrauma.Tutorials Heal(officer); SetHighlight(officer_coilgunPeriscope, false); RemoveCompletedObjective(4); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective4"); + yield return new WaitForSeconds(1f, false); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.HammerheadDead"), ChatMessageType.Radio, null); SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, true); @@ -451,6 +461,7 @@ namespace Barotrauma.Tutorials yield return null; } while (!shotGunChamber.Inventory.IsFull(takeStacksIntoAccount: true)); // Wait until all six harpoons loaded RemoveCompletedObjective(5); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective5"); SetHighlight(officer_rangedWeaponCabinet.Item, false); SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, true); @@ -461,6 +472,7 @@ namespace Barotrauma.Tutorials do { yield return null; } while (!officer_mudraptor.IsDead); Heal(officer); RemoveCompletedObjective(6); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective6"); SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, true); // Submarine @@ -512,9 +524,11 @@ namespace Barotrauma.Tutorials officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1); officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2); RemoveCompletedObjective(7); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective7"); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Complete"), ChatMessageType.Radio, null); yield return new WaitForSeconds(4f, false); + GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Completed"); CoroutineManager.StartCoroutine(TutorialCompleted()); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs index 60369b132..fd0a96f1d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs @@ -252,6 +252,8 @@ namespace Barotrauma.Tutorials Character.Controlled = character = null; Stop(); + GameAnalyticsManager.AddDesignEvent("Tutorial:Died"); + yield return new WaitForSeconds(3.0f); var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }); @@ -273,6 +275,8 @@ namespace Barotrauma.Tutorials Character.Controlled.ClearInputs(); Character.Controlled = null; + GameAnalyticsManager.AddDesignEvent("Tutorial:Completed"); + yield return new WaitForSeconds(waitBeforeFade); var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: fadeOutTime); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs index 580f16b8a..5bd83236c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs @@ -11,7 +11,13 @@ namespace Barotrauma.Networking private bool isActive; private readonly UInt64 selfSteamID; + private UInt64 ownerKey64 => unchecked((UInt64)ownerKey); + private UInt64 ReadSteamId(IReadMessage inc) + => inc.ReadUInt64() ^ ownerKey64; + private void WriteSteamId(IWriteMessage msg, UInt64 val) + => msg.Write(val ^ ownerKey64); + private long sentBytes, receivedBytes; class RemotePeer @@ -58,6 +64,8 @@ namespace Barotrauma.Networking { if (isActive) { return; } + this.ownerKey = ownerKey; + initializationStep = ConnectionInitialization.SteamTicketAndVersion; ServerConnection = new PipeConnection(selfSteamID); @@ -103,7 +111,7 @@ namespace Barotrauma.Networking //known now int prevBitPosition = msg.Message.BitPosition; msg.Message.BitPosition = sizeof(ulong) * 8; - msg.Message.Write(ownerID); + WriteSteamId(msg.Message, ownerID); msg.Message.BitPosition = prevBitPosition; byte[] msgToSend = (byte[])msg.Message.Buffer.Clone(); Array.Resize(ref msgToSend, msg.Message.LengthBytes); @@ -141,8 +149,8 @@ namespace Barotrauma.Networking } IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write(steamId); - outMsg.Write(remotePeer.OwnerSteamID); + WriteSteamId(outMsg, steamId); + WriteSteamId(outMsg, remotePeer.OwnerSteamID); outMsg.Write(data, 1, dataLength - 1); DeliveryMethod deliveryMethod = (DeliveryMethod)data[0]; @@ -232,7 +240,7 @@ namespace Barotrauma.Networking { if (!isActive) { return; } - UInt64 recipientSteamId = inc.ReadUInt64(); + UInt64 recipientSteamId = ReadSteamId(inc); DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte(); int p2pDataStart = inc.BytePosition; @@ -343,8 +351,8 @@ namespace Barotrauma.Networking if (packetHeader.IsConnectionInitializationStep()) { IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write(selfSteamID); - outMsg.Write(selfSteamID); + WriteSteamId(outMsg, selfSteamID); + WriteSteamId(outMsg, selfSteamID); outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep)); outMsg.Write(Name); @@ -436,8 +444,8 @@ namespace Barotrauma.Networking IWriteMessage msgToSend = new WriteOnlyMessage(); byte[] msgData = new byte[msg.LengthBytes]; msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); - msgToSend.Write(selfSteamID); - msgToSend.Write(selfSteamID); + WriteSteamId(msgToSend, selfSteamID); + WriteSteamId(msgToSend, selfSteamID); msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None)); msgToSend.Write((UInt16)length); msgToSend.Write(msgData, 0, length); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index 9df67c87e..9334f7060 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -839,16 +839,12 @@ namespace Barotrauma arguments += " -nopassword"; } - int ownerKey = 0; if (Steam.SteamManager.GetSteamID() != 0) { arguments += " -steamid " + Steam.SteamManager.GetSteamID(); } - else - { - ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1); - arguments += " -ownerkey " + ownerKey; - } + int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1); + arguments += " -ownerkey " + ownerKey; string filename = Path.Combine( Path.GetDirectoryName(exeName), diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index feb824001..d6ed8ba85 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.15.0 + 0.17.16.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index c0b1d5d63..2185d1a08 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.15.0 + 0.17.16.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 870cafa2d..95e6f1d27 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.15.0 + 0.17.16.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index f494058e0..a9f7c03f7 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.15.0 + 0.17.16.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index e3385b582..629e28b76 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.15.0 + 0.17.16.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 28d5adb13..96651f8ec 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -155,7 +155,7 @@ namespace Barotrauma.Networking else { Log("Using SteamP2P networking.", ServerLog.MessageType.ServerMessage); - serverPeer = new SteamP2PServerPeer(ownerSteamId.Value, serverSettings); + serverPeer = new SteamP2PServerPeer(ownerSteamId.Value, ownerKey.Value, serverSettings); } serverPeer.OnInitializationComplete = OnInitializationComplete; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs index 2d1cb634c..8718bc4d5 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs @@ -16,14 +16,21 @@ namespace Barotrauma.Networking private set; } - public SteamP2PServerPeer(UInt64 steamId, ServerSettings settings) + private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Value); + + private UInt64 ReadSteamId(IReadMessage inc) + => inc.ReadUInt64() ^ ownerKey64; + private void WriteSteamId(IWriteMessage msg, UInt64 val) + => msg.Write(val ^ ownerKey64); + + public SteamP2PServerPeer(UInt64 steamId, int ownerKey, ServerSettings settings) { serverSettings = settings; connectedClients = new List(); pendingClients = new List(); - ownerKey = null; + this.ownerKey = ownerKey; OwnerSteamID = steamId; @@ -33,7 +40,7 @@ namespace Barotrauma.Networking public override void Start() { IWriteMessage outMsg = new WriteOnlyMessage(); - outMsg.Write(OwnerSteamID); + WriteSteamId(outMsg, OwnerSteamID); outMsg.Write((byte)DeliveryMethod.Reliable); outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage)); @@ -122,8 +129,8 @@ namespace Barotrauma.Networking { if (!started) { return; } - UInt64 senderSteamId = inc.ReadUInt64(); - UInt64 ownerSteamId = inc.ReadUInt64(); + UInt64 senderSteamId = ReadSteamId(inc); + UInt64 ownerSteamId = ReadSteamId(inc); PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); @@ -264,7 +271,7 @@ namespace Barotrauma.Networking IWriteMessage msgToSend = new WriteOnlyMessage(); byte[] msgData = new byte[16]; msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); - msgToSend.Write(conn.SteamID); + WriteSteamId(msgToSend, conn.SteamID); msgToSend.Write((byte)deliveryMethod); msgToSend.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage)); msgToSend.Write((UInt16)length); @@ -281,7 +288,7 @@ namespace Barotrauma.Networking if (string.IsNullOrWhiteSpace(msg)) { return; } IWriteMessage msgToSend = new WriteOnlyMessage(); - msgToSend.Write(steamId); + WriteSteamId(msgToSend, steamId); msgToSend.Write((byte)DeliveryMethod.Reliable); msgToSend.Write((byte)(PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage)); msgToSend.Write(msg); @@ -318,7 +325,7 @@ namespace Barotrauma.Networking protected override void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg) { IWriteMessage msgToSend = new WriteOnlyMessage(); - msgToSend.Write(conn.SteamID); + WriteSteamId(msgToSend, conn.SteamID); msgToSend.Write((byte)deliveryMethod); msgToSend.Write(msg.Buffer, 0, msg.LengthBytes); byte[] bufToSend = (byte[])msgToSend.Buffer.Clone(); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 54c93dda6..c54e862a1 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.15.0 + 0.17.16.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs index 5711e7769..f5dba0b3d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs @@ -51,7 +51,7 @@ namespace Barotrauma => Element.Descendants().Select(e => new ContentXElement(ContentPackage, e)); public IEnumerable GetChildElements(string name) - => Elements().Where(e => string.Equals(name, e.Name.LocalName, StringComparison.CurrentCultureIgnoreCase)); + => Elements().Where(e => string.Equals(name, e.Name.LocalName, StringComparison.InvariantCultureIgnoreCase)); public XAttribute? GetAttribute(string name) => Element.GetAttribute(name); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs index f48d81a46..4defc4944 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs @@ -143,7 +143,7 @@ namespace Barotrauma.Networking private static void UpdateRead() { - Span msgLengthSpan = stackalloc byte[3]; + Span msgLengthSpan = stackalloc byte[4 + 1]; while (!shutDown) { CheckPipeConnected(nameof(readStream), readStream); @@ -167,8 +167,11 @@ namespace Barotrauma.Networking if (!readBytes(msgLengthSpan)) { shutDown = true; break; } - int msgLength = msgLengthSpan[0] | (msgLengthSpan[1] << 8); - WriteStatus writeStatus = (WriteStatus)msgLengthSpan[2]; + int msgLength = msgLengthSpan[0] + | (msgLengthSpan[1] << 8) + | (msgLengthSpan[2] << 16) + | (msgLengthSpan[3] << 24); + WriteStatus writeStatus = (WriteStatus)msgLengthSpan[4]; if (msgLength > 0) { @@ -210,12 +213,15 @@ namespace Barotrauma.Networking // when the function returns; placing it in the loop // this method is based around would lead to a stack // overflow real quick! - Span bytesToWrite = stackalloc byte[3 + msg.Length]; + Span bytesToWrite = stackalloc byte[4 + 1 + msg.Length]; bytesToWrite[0] = (byte)(msg.Length & 0xFF); bytesToWrite[1] = (byte)((msg.Length >> 8) & 0xFF); - bytesToWrite[2] = (byte)writeStatus; - Span msgSlice = bytesToWrite.Slice(3, msg.Length); + bytesToWrite[2] = (byte)((msg.Length >> 16) & 0xFF); + bytesToWrite[3] = (byte)((msg.Length >> 24) & 0xFF); + + bytesToWrite[4] = (byte)writeStatus; + Span msgSlice = bytesToWrite.Slice(4 + 1, msg.Length); msg.AsSpan().CopyTo(msgSlice); @@ -269,6 +275,12 @@ namespace Barotrauma.Networking { if (shutDown) { return; } + if (msg.Length > 0x1fff_ffff) + { + //This message is extremely long and is close to breaking + //ChildServerRelay, so let's not allow this to go through! + return; + } msgsToWrite.Enqueue(msg); writeManualResetEvent.Set(); } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 37b6f8943..292474ecf 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,14 @@ +--------------------------------------------------------------------------------------------------------- +v0.17.16.0 +--------------------------------------------------------------------------------------------------------- + +Changes: +- Added some tutorial information to the data sent to GameAnalytics. + +Fixes: +- Fixed an exploit that allowed modified clients to execute console commands server-side without the appropriate permissions. +- Fixed NPCs spawning without any items when the system language is set to Turkish. + --------------------------------------------------------------------------------------------------------- v0.17.15.0 ---------------------------------------------------------------------------------------------------------