using Barotrauma.Items.Components; using Barotrauma.Networking; using Barotrauma.Steam; using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Barotrauma { static class SteamAchievementManager { private const float UpdateInterval = 1.0f; private static HashSet unlockedAchievements = new HashSet(); public static bool CheatsEnabled = false; private static float updateTimer; /// /// Keeps track of things that have happened during the round /// class RoundData { public readonly List Reactors = new List(); public readonly HashSet EnteredCrushDepth = new HashSet(); public readonly HashSet ReactorMeltdown = new HashSet(); public readonly HashSet Casualties = new HashSet(); } private static RoundData roundData; public static void OnStartRound() { roundData = new RoundData(); foreach (Item item in Item.ItemList) { Reactor reactor = item.GetComponent(); if (reactor != null) roundData.Reactors.Add(reactor); } } public static void Update(float deltaTime) { if (GameMain.GameSession == null) return; #if CLIENT if (GameMain.Client != null) return; #endif updateTimer -= deltaTime; if (updateTimer > 0.0f) return; updateTimer = UpdateInterval; if (Level.Loaded != null) { if (GameMain.GameSession.EventManager.CurrentIntensity > 0.99f) { UnlockAchievement("maxintensity", true, c => c != null && !c.IsDead && !c.IsUnconscious); } foreach (Character c in Character.CharacterList) { if (c.IsDead) continue; //achievement for descending below crush depth and coming back if (c.WorldPosition.Y < SubmarineBody.DamageDepth || (c.Submarine != null && c.Submarine.WorldPosition.Y < SubmarineBody.DamageDepth)) { roundData.EnteredCrushDepth.Add(c); } else if (c.WorldPosition.Y > SubmarineBody.DamageDepth * 0.5f) { //all characters that have entered crush depth and are still alive get an achievement if (roundData.EnteredCrushDepth.Contains(c)) UnlockAchievement(c, "survivecrushdepth"); } } foreach (Submarine sub in Submarine.Loaded) { foreach (Reactor reactor in roundData.Reactors) { if (reactor.Item.Condition <= 0.0f && reactor.Item.Submarine == sub) { //characters that were inside the sub during a reactor meltdown //get an achievement if they're still alive at the end of the round foreach (Character c in Character.CharacterList) { if (!c.IsDead && c.Submarine == sub) roundData.ReactorMeltdown.Add(c); } } } //convert submarine velocity to km/h Vector2 submarineVel = Physics.DisplayToRealWorldRatio * ConvertUnits.ToDisplayUnits(sub.Velocity) * 3.6f; //achievement for going > 100 km/h if (Math.Abs(submarineVel.X) > 100.0f) { //all conscious characters inside the sub get an achievement UnlockAchievement("subhighvelocity", true, c => c != null && c.Submarine == sub && !c.IsDead && !c.IsUnconscious); } //achievement for descending ridiculously deep float realWorldDepth = Math.Abs(sub.Position.Y - Level.Loaded.Size.Y) * Physics.DisplayToRealWorldRatio; if (realWorldDepth > 5000.0f) { //all conscious characters inside the sub get an achievement UnlockAchievement("subdeep", true, c => c != null && c.Submarine == sub && !c.IsDead && !c.IsUnconscious); } } } } public static void OnBiomeDiscovered(Biome biome) { UnlockAchievement("discover" + biome.Name.ToLowerInvariant().Replace(" ", "")); } public static void OnItemRepaired(Item item, Character fixer) { #if CLIENT if (GameMain.Client != null) return; #endif if (fixer == null) return; UnlockAchievement(fixer, "repairdevice"); UnlockAchievement(fixer, "repair" + item.Prefab.Identifier); } public static void OnAfflictionRemoved(Affliction affliction, Character character) { if (string.IsNullOrEmpty(affliction.Prefab.AchievementOnRemoved)) return; #if CLIENT if (GameMain.Client != null) return; #endif UnlockAchievement(character, affliction.Prefab.AchievementOnRemoved); } public static void OnCharacterRevived(Character character, Character reviver) { #if CLIENT if (GameMain.Client != null) return; #endif if (reviver == null) return; UnlockAchievement(reviver, "healcrit"); } public static void OnCharacterKilled(Character character, CauseOfDeath causeOfDeath) { #if CLIENT if (GameMain.Client != null || GameMain.GameSession == null) return; #endif if (character != Character.Controlled && causeOfDeath.Killer != null && causeOfDeath.Killer == Character.Controlled) { SteamManager.IncrementStat( character.SpeciesName.ToLowerInvariant() == "human" ? "humanskilled" : "monsterskilled", 1); } roundData.Casualties.Add(character); UnlockAchievement(causeOfDeath.Killer, "kill" + character.SpeciesName); if (character.CurrentHull != null) { UnlockAchievement(causeOfDeath.Killer, "kill" + character.SpeciesName + "indoors"); } if (character.HasEquippedItem("clownmask") && character.HasEquippedItem("clowncostume")) { UnlockAchievement(causeOfDeath.Killer, "killclown"); } if (causeOfDeath.DamageSource is Item item) { switch (item.Prefab.Identifier) { case "weldingtool": case "plasmacutter": case "wrench": UnlockAchievement(causeOfDeath.Killer, "killtool"); break; case "morbusine": UnlockAchievement(causeOfDeath.Killer, "killpoison"); break; case "nuclearshell": case "nucleardepthcharge": UnlockAchievement(causeOfDeath.Killer, "killnuke"); break; } } #if SERVER if (GameMain.Server?.TraitorManager != null) { foreach (Traitor traitor in GameMain.Server.TraitorManager.TraitorList) { if (traitor.TargetCharacter == character) { //killed the target as a traitor UnlockAchievement(traitor.Character, "traitorwin"); } else if (traitor.Character == character) { //someone killed a traitor UnlockAchievement(causeOfDeath.Killer, "killtraitor"); } } } #endif } public static void OnRoundEnded(GameSession gameSession) { //made it to the destination if (gameSession.Submarine.AtEndPosition && Level.Loaded != null) { float levelLengthMeters = Physics.DisplayToRealWorldRatio * Level.Loaded.Size.X; float levelLengthKilometers = levelLengthMeters / 1000.0f; //in multiplayer the client's/host's character must be inside the sub (or end outpost) and alive if (GameMain.NetworkMember != null) { #if CLIENT Character myCharacter = Character.Controlled; if (myCharacter != null && !myCharacter.IsDead && (myCharacter.Submarine == gameSession.Submarine || (Level.Loaded?.EndOutpost != null && myCharacter.Submarine == Level.Loaded.EndOutpost))) { SteamManager.IncrementStat("kmstraveled", levelLengthKilometers); } #endif } else { //in sp making it to the end is enough SteamManager.IncrementStat("kmstraveled", levelLengthKilometers); } } #if CLIENT if (GameMain.Client != null) { return; } #endif if (gameSession.Mission != null) { if (gameSession.Mission is CombatMission combatMission) { #if CLIENT //all characters that are alive and in the winning team get an achievement UnlockAchievement(gameSession.Mission.Prefab.AchievementIdentifier + (int)GameMain.GameSession.WinningTeam, true, c => c != null && !c.IsDead && !c.IsUnconscious && combatMission.IsInWinningTeam(c)); #endif } else if (gameSession.Mission.Completed) { //all characters get an achievement if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { UnlockAchievement(gameSession.Mission.Prefab.AchievementIdentifier, true, c => c != null); } else { UnlockAchievement(gameSession.Mission.Prefab.AchievementIdentifier); } } } //made it to the destination if (gameSession.Submarine.AtEndPosition) { #if SERVER if (GameMain.Server != null) { //in MP all characters that were inside the sub during reactor meltdown and still alive at the end of the round get an achievement UnlockAchievement("survivereactormeltdown", true, c => c != null && !c.IsDead && roundData.ReactorMeltdown.Contains(c)); } #endif #if CLIENT if (roundData.ReactorMeltdown.Any()) //in SP getting to the destination after a meltdown is enough { UnlockAchievement("survivereactormeltdown"); } #endif var charactersInSub = Character.CharacterList.FindAll(c => !c.IsDead && (c.Submarine == gameSession.Submarine || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost))); if (charactersInSub.Count == 1) { //there must be some non-enemy casualties to get the last mant standing achievement if (roundData.Casualties.Any(c => !(c.AIController is EnemyAIController))) { UnlockAchievement(charactersInSub[0], "lastmanstanding"); } else if (!Character.CharacterList.Any(c => !(c.AIController is EnemyAIController))) { UnlockAchievement(charactersInSub[0], "lonesailor"); } } } } private static void UnlockAchievement(Character recipient, string identifier) { if (CheatsEnabled) return; if (recipient == null) return; #if CLIENT if (recipient == Character.Controlled) { UnlockAchievement(identifier); } #endif #if SERVER GameMain.Server?.GiveAchievement(recipient, identifier); #endif } public static void UnlockAchievement(string identifier, bool unlockClients = false, Func conditions = null) { if (CheatsEnabled) return; #if SERVER identifier = identifier.ToLowerInvariant(); if (unlockClients && GameMain.Server != null) { foreach (Client c in GameMain.Server.ConnectedClients) { if (conditions != null && !conditions(c.Character)) continue; GameMain.Server.GiveAchievement(c, identifier); } } #endif //already unlocked, no need to do anything if (unlockedAchievements.Contains(identifier)) return; unlockedAchievements.Add(identifier); #if CLIENT if (conditions != null && !conditions(Character.Controlled)) return; #endif SteamManager.UnlockAchievement(identifier); } } }