diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index 4ada1f0c6..090a8e40d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -85,7 +85,7 @@ namespace Barotrauma } } - public abstract bool IsDuplicate(BossProgressBar progressBar); + public abstract bool IsDuplicate(object targetObject); } class BossHealthBar : BossProgressBar @@ -109,9 +109,9 @@ namespace Barotrauma Character = character; } - public override bool IsDuplicate(BossProgressBar progressBar) + public override bool IsDuplicate(object targetObject) { - return progressBar is BossHealthBar bossHealthBar && bossHealthBar.Character == Character; + return targetObject is Character character && Character == character; } } @@ -136,9 +136,9 @@ namespace Barotrauma Mission = mission; } - public override bool IsDuplicate(BossProgressBar progressBar) + public override bool IsDuplicate(object targetObject) { - return progressBar is MissionProgressBar missionProgressBar && missionProgressBar.Mission == Mission; + return targetObject is Mission mission && Mission == mission; } } @@ -150,7 +150,7 @@ namespace Barotrauma private static readonly List brokenItems = new List(); private static float brokenItemsCheckTimer; - private static readonly List bossHealthBars = new List(); + private static readonly List bossProgressBars = new List(); private static readonly Dictionary cachedHudTexts = new Dictionary(); @@ -747,44 +747,44 @@ namespace Barotrauma public static void ShowBossHealthBar(Character character, float damage) { if (character == null || character.IsDead || character.Removed) { return; } - AddBossProgressBar(new BossHealthBar(character), damage); + if (bossProgressBars.Any(b => b.IsDuplicate(character))) { return; } + AddBossProgressBar(new BossHealthBar(character)); } public static void ShowMissionProgressBar(Mission mission) { if (mission == null || mission.Completed || mission.Failed) { return; } + if (bossProgressBars.Any(b => b.IsDuplicate(mission))) { return; } AddBossProgressBar(new MissionProgressBar(mission)); } - public static void ClearBossHealthBars() + public static void ClearBossProgressBars() { - bossHealthBars.Clear(); + for (int i = bossProgressBars.Count - 1; i>= 0; i--) + { + RemoveBossProgressBar(bossProgressBars[i]); + } + bossProgressBars.Clear(); } - private static void AddBossProgressBar(BossProgressBar progressBar, float damage = 0.0f) + private static void RemoveBossProgressBar(BossProgressBar progressBar) + { + progressBar.SideContainer.Parent?.RemoveChild(progressBar.SideContainer); + progressBar.TopContainer.Parent?.RemoveChild(progressBar.TopContainer); + bossProgressBars.Remove(progressBar); + } + + private static void AddBossProgressBar(BossProgressBar progressBar) { var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars; if (healthBarMode == EnemyHealthBarMode.HideAll) { return; } - - var existingBar = bossHealthBars.Find(b => b.IsDuplicate(progressBar)); - if (existingBar != null) + if (bossProgressBars.Count > 5) { - existingBar.FadeTimer = BossHealthBarDuration; - if (damage > 0) - { - // Show the most recent target at the top of the screen - bossHealthBars.Remove(existingBar); - bossHealthBars.Add(existingBar); - } - return; - } - if (bossHealthBars.Count > 5) - { - BossProgressBar oldestHealthBar = bossHealthBars.First(); - foreach (var bar in bossHealthBars) + BossProgressBar oldestHealthBar = bossProgressBars.First(); + foreach (var bar in bossProgressBars) { if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize) { @@ -793,18 +793,18 @@ namespace Barotrauma } oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f); } - bossHealthBars.Add(progressBar); + bossProgressBars.Add(progressBar); } public static void UpdateBossProgressBars(float deltaTime) { var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars; - for (int i = 0; i < bossHealthBars.Count; i++) + for (int i = 0; i < bossProgressBars.Count; i++) { - var bossHealthBar = bossHealthBars[i]; + var bossHealthBar = bossProgressBars[i]; - bool showTopBar = i == bossHealthBars.Count - 1; + bool showTopBar = i == bossProgressBars.Count - 1; if (showTopBar && !bossHealthBar.TopContainer.Visible) { bossHealthBar.SideContainer.SetAsLastChild(); @@ -848,14 +848,14 @@ namespace Barotrauma } bossHealthBar.FadeTimer -= deltaTime; } - for (int i = bossHealthBars.Count - 1; i >= 0 ; i--) + for (int i = bossProgressBars.Count - 1; i >= 0 ; i--) { - var bossHealthBar = bossHealthBars[i]; + var bossHealthBar = bossProgressBars[i]; if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll) { bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer); bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer); - bossHealthBars.RemoveAt(i); + bossProgressBars.RemoveAt(i); bossHealthContainer.Recalculate(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index eb8cc5fb6..47d87e1ca 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -578,7 +578,7 @@ namespace Barotrauma ch.SetPersonalityTrait(); ch.Job?.OverrideSkills(skillLevels); - ch.ExperiencePoints = inc.ReadUInt16(); + ch.ExperiencePoints = inc.ReadInt32(); ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints); return ch; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EndMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EndMission.cs index d4b9a8dec..5becd9ba2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EndMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EndMission.cs @@ -76,6 +76,7 @@ namespace Barotrauma partial void UpdateProjSpecific() { + if (boss == null || boss.Removed) { return; } if (Phase is MissionPhase.Initial or MissionPhase.NoItemsDestroyed or MissionPhase.SomeItemsDestroyed) { // Put asleep. diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index 9223c03b6..641c6e3aa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -611,10 +611,6 @@ namespace Barotrauma GameMain.Client?.Draw(spriteBatch); - string factionWaterMark = "FACTION/ENDGAME TEST VERSION - please do not publicly share any material you see here!".ToUpper(); - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, 100 * yScale) - GUIStyle.SubHeadingFont.MeasureString(factionWaterMark) / 2, factionWaterMark, - GUIStyle.Red * 0.5f, font: GUIStyle.SubHeadingFont, backgroundColor: Color.Black * 0.5f, backgroundPadding: 10); - if (Character.Controlled?.Inventory != null) { if (Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs index 2c764937c..b29eb3176 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs @@ -14,6 +14,13 @@ namespace Barotrauma.Items.Components private CoroutineHandle resetPredictionCoroutine; private float resetPredictionTimer; + /// + /// The current multiplier for the light color (usually equal to , but in the case of e.g. blinking lights the multiplier + /// doesn't go to 0 when the light turns off, because otherwise it'd take a while for it turn back on based on the lightBrightness which is interpolated + /// towards the current voltage). + /// + private float lightColorMultiplier; + public Vector2 DrawSize { get { return new Vector2(Light.Range * 2, Light.Range * 2); } @@ -27,21 +34,14 @@ namespace Barotrauma.Items.Components Light.Position = ParentBody != null ? ParentBody.Position : item.Position; } - partial void SetLightSourceState(bool enabled, float? brightness) + partial void SetLightSourceState(bool enabled, float brightness) { if (Light == null) { return; } Light.Enabled = enabled; - if (brightness.HasValue) - { - lightBrightness = brightness.Value; - } - else - { - lightBrightness = enabled ? 1.0f : 0.0f; - } + lightColorMultiplier = brightness; if (enabled) { - Light.Color = LightColor.Multiply(lightBrightness); + Light.Color = LightColor.Multiply(lightColorMultiplier); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs index 6e7a4df7d..466b9418b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs @@ -328,19 +328,21 @@ namespace Barotrauma case VoteType.PurchaseAndSwitchSub: case VoteType.SwitchSub: string subName2 = inc.ReadString(); - var submarineInfo = GameMain.GameSession.OwnedSubmarines.FirstOrDefault(s => s.Name == subName2) ?? GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2); bool transferItems = inc.ReadBoolean(); - if (submarineInfo == null) + if (GameMain.GameSession != null) { - DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); - return; + var submarineInfo = GameMain.GameSession.OwnedSubmarines.FirstOrDefault(s => s.Name == subName2) ?? GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2); + if (submarineInfo == null) + { + DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); + return; + } + submarineVoteInfo = new SubmarineVoteInfo(submarineInfo, transferItems); } - submarineVoteInfo = new SubmarineVoteInfo(submarineInfo, transferItems); break; } - GameMain.Client.VotingInterface?.EndVote(passed, yesClientCount, noClientCount); - + GameMain.Client.VotingInterface?.EndVote(passed, yesClientCount, noClientCount); if (passed && submarineVoteInfo.SubmarineInfo is { } subInfo) { switch (voteType) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index 156e58cec..e812023ea 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -1043,7 +1043,15 @@ namespace Barotrauma if (backgroundSprite == null) { +#if UNSTABLE backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null); +#endif + if (GUIStyle.GetComponentStyle("MainMenuBackground") is { } mainMenuStyle && + mainMenuStyle.Sprites.TryGetValue(GUIComponent.ComponentState.None, out var sprites)) + { + backgroundSprite = sprites.GetRandomUnsynced()?.Sprite; + } + backgroundSprite ??= LocationType.Prefabs.GetRandomUnsynced()?.GetPortrait(0); } var vignette = GUIStyle.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite(); diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 47cc56757..578a3a4f2 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.0.1.0 + 1.0.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 6634acbb0..92f2c619e 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.0.1.0 + 1.0.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 4e65c1f58..ec949fa63 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.0.1.0 + 1.0.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 17eeb1278..309ef1a8d 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.0.1.0 + 1.0.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index a458e043f..9ed219df2 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.0.1.0 + 1.0.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index ae0c01dfb..0f0014017 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -91,7 +91,7 @@ namespace Barotrauma msg.WriteByte((byte)0); } - msg.WriteUInt16((ushort)ExperiencePoints); + msg.WriteInt32(ExperiencePoints); msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 5ec411eff..a4ffcda9c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -2367,10 +2367,7 @@ namespace Barotrauma.Networking List spawnWaypoints = null; List mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]).ToList(); - if (Level.Loaded?.StartOutpost != null && - Level.Loaded.Type == LevelData.LevelType.Outpost && - (Level.Loaded.StartOutpost.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false) && - Level.Loaded.StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player)) + if (Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost()) { spawnWaypoints = WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Human && @@ -3313,12 +3310,13 @@ namespace Barotrauma.Networking if (checkActiveVote && Voting.ActiveVote != null) { +#warning TODO: this is mostly the same as Voting.Update, deduplicate (if/when refactoring the Voting class?) var inGameClients = GameMain.Server.ConnectedClients.Where(c => c.InGame); - if (inGameClients.Count() == 1) + if (inGameClients.Count() == 1 && inGameClients.First() == Voting.ActiveVote.VoteStarter) { Voting.ActiveVote.Finish(Voting, passed: true); } - else + else if (inGameClients.Any()) { var eligibleClients = inGameClients.Where(c => c != Voting.ActiveVote.VoteStarter); int yes = eligibleClients.Count(c => c.GetVote(Voting.ActiveVote.VoteType) == 2); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index 53c3b7eda..787c786d6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -79,10 +79,10 @@ namespace Barotrauma if (passed) { Wallet fromWallet = From == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : From.Character?.Wallet; - if (fromWallet.TryDeduct(TransferAmount)) + if (fromWallet != null && fromWallet.TryDeduct(TransferAmount)) { Wallet toWallet = To == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : To.Character?.Wallet; - toWallet.Give(TransferAmount); + toWallet?.Give(TransferAmount); } } else @@ -203,12 +203,16 @@ namespace Barotrauma // Do not take unanswered into account for total int yes = eligibleClients.Count(c => c.GetVote(ActiveVote.VoteType) == 2); int no = eligibleClients.Count(c => c.GetVote(ActiveVote.VoteType) == 1); - int total = Math.Max(yes + no, 1); - - bool passed = - yes / (float)total >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio || - inGameClients.Count() == 1; + int total = yes + no; + bool passed = false; + //total can be zero if the client who initiated the vote has left + if (total > 0) + { + passed = + yes / (float)total >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio || + inGameClients.Count() == 1; + } ActiveVote.Finish(this, passed); } } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index ef40122b9..1ac03d6a3 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.0.1.0 + 1.0.7.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index ab2800893..2785720c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -546,12 +546,11 @@ namespace Barotrauma bool NeedsDivingGearOnPath(AIObjectiveGoTo gotoObjective) { - if (Character.IsImmuneToPressure) { return false; } bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty; Hull targetHull = gotoObjective.GetTargetHull(); - return gotoObjective.Target != null && targetHull == null || + return (gotoObjective.Target != null && targetHull == null && !Character.IsImmuneToPressure) || NeedsDivingGear(targetHull, out _) || - insideSteering && (PathSteering.CurrentPath.HasOutdoorsNodes || PathSteering.CurrentPath.Nodes.Any(n => NeedsDivingGear(n.CurrentHull, out _))); + (insideSteering && ((PathSteering.CurrentPath.HasOutdoorsNodes && !Character.IsImmuneToPressure) || PathSteering.CurrentPath.Nodes.Any(n => NeedsDivingGear(n.CurrentHull, out _)))); } if (isCarrying) @@ -1550,23 +1549,19 @@ namespace Barotrauma public bool NeedsDivingGear(Hull hull, out bool needsSuit) { - if (Character.IsImmuneToPressure) - { - needsSuit = false; - return false; - } needsSuit = false; + bool needsAir = Character.NeedsAir && Character.CharacterHealth.OxygenLowResistance < 1; if (hull == null || hull.WaterPercentage > 90 || hull.LethalPressure > 0 || hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f)) { - needsSuit = true; - return true; + needsSuit = !Character.IsProtectedFromPressure; + return needsAir || needsSuit; } - if (Character.CharacterHealth.OxygenLowResistance < 1 && (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)) + if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1) { - return true; + return needsAir; } return false; } @@ -1943,7 +1938,7 @@ namespace Barotrauma visibleHulls = VisibleHulls; } bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective(); - bool ignoreOxygen = character.IsProtectedFromPressure || HasDivingGear(character); + bool ignoreOxygen = HasDivingGear(character); bool ignoreEnemies = ObjectiveManager.IsCurrentOrder() || ObjectiveManager.IsCurrentObjective(); float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater: false, ignoreOxygen, ignoreFire, ignoreEnemies); if (isCurrentHull) @@ -1976,7 +1971,7 @@ namespace Barotrauma waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume); } } - if (character.CharacterHealth.OxygenLowResistance >= 1) + if (!character.NeedsOxygen || character.CharacterHealth.OxygenLowResistance >= 1) { oxygenFactor = 1; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 73db418cc..a98233d5d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -26,7 +26,7 @@ namespace Barotrauma private float findPathTimer; - private const float buttonPressCooldown = 3; + private const float ButtonPressCooldown = 1; private float checkDoorsTimer; private float buttonPressTimer; @@ -342,7 +342,7 @@ namespace Barotrauma CheckDoorsInPath(); doorsChecked = true; } - if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && lastDoor.door.IsOpening) + if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && !lastDoor.door.IsFullyOpen) { // We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again. Reset(); @@ -694,7 +694,7 @@ namespace Barotrauma if (door.Item.TryInteract(character, forceSelectKey: true)) { lastDoor = (door, shouldBeOpen); - buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0; + buttonPressTimer = shouldBeOpen ? ButtonPressCooldown : 0; } else { @@ -712,7 +712,7 @@ namespace Barotrauma if (closestButton.Item.TryInteract(character, forceSelectKey: true)) { lastDoor = (door, shouldBeOpen); - buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0; + buttonPressTimer = shouldBeOpen ? ButtonPressCooldown : 0; } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index d53fe6a38..221067c28 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -52,7 +52,7 @@ namespace Barotrauma objectiveManager.HasOrder(o => o.Priority > 0) || objectiveManager.HasActiveObjective() || objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0)) - && character.IsProtectedFromPressure ? 0 : 100; + && ((character.IsImmuneToPressure && !character.IsLowInOxygen)|| HumanAIController.HasDivingSuit(character)) ? 0 : 100; } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index 042e913cd..0b48320e7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -465,6 +465,10 @@ namespace Barotrauma public readonly OrderTarget TargetPosition; private ISpatialEntity targetSpatialEntity; + + /// + /// Note this property doesn't return the follow target of the Follow objective, as expected! + /// public ISpatialEntity TargetSpatialEntity { get diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 9e9398673..88db60899 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -348,6 +348,7 @@ namespace Barotrauma { if (body.UserData is Submarine) { return false; } if (body.UserData is Structure s && !s.IsPlatform) { return false; } + if (body.UserData is Voronoi2.VoronoiCell) { return false; } if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 17b174c8e..4df2d8591 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -476,10 +476,15 @@ namespace Barotrauma } set { - if (info != null && info != value) info.Remove(); - + if (info != null && info != value) + { + info.Remove(); + } info = value; - if (info != null) info.Character = this; + if (info != null) + { + info.Character = this; + } } } @@ -1064,7 +1069,7 @@ namespace Barotrauma public bool InWater => AnimController is AnimController { InWater: true }; - public bool IsLowInOxygen => NeedsOxygen && OxygenAvailable < CharacterHealth.LowOxygenThreshold; + public bool IsLowInOxygen => CharacterHealth.OxygenAmount < 100; public bool GodMode = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index dde2b1b08..58daaa291 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -510,8 +510,11 @@ namespace Barotrauma public List CurrentOrders { get; } = new List(); - //unique ID given to character infos in MP - //used by clients to identify which infos are the same to prevent duplicate characters in round summary + + /// + /// Unique ID given to character infos in MP. Non-persistent. + /// Used by clients to identify which infos are the same to prevent duplicate characters in round summary. + /// public ushort ID; public List SpriteTags @@ -922,17 +925,25 @@ namespace Barotrauma } } + /// + /// Returns a presumably (not guaranteed) unique hash using the (current) Name, appearence, and job. + /// So unless there's another character with the exactly same name, job, and appearance, the hash should be unique. + /// public int GetIdentifier() { - return GetIdentifier(Name); + return GetIdentifierHash(Name); } + /// + /// Returns a presumably (not guaranteed) unique hash using the OriginalName, appearence, and job. + /// So unless there's another character with the exactly same name, job, and appearance, the hash should be unique. + /// public int GetIdentifierUsingOriginalName() { - return GetIdentifier(OriginalName); + return GetIdentifierHash(OriginalName); } - private int GetIdentifier(string name) + private int GetIdentifierHash(string name) { int id = ToolBox.StringToInt(name + string.Join("", Head.Preset.TagSet.OrderBy(s => s))); id ^= Head.HairIndex << 12; @@ -1512,7 +1523,7 @@ namespace Barotrauma } if (order.OrderGiver != null) { - orderElement.Add(new XAttribute("ordergiverinfoid", order.OrderGiver.Info.ID)); + orderElement.Add(new XAttribute("ordergiver", order.OrderGiver.Info?.GetIdentifier())); } if (order.TargetSpatialEntity?.Submarine is Submarine targetSub) { @@ -1606,8 +1617,8 @@ namespace Barotrauma continue; } var targetType = (Order.OrderTargetType)orderElement.GetAttributeInt("targettype", 0); - int orderGiverInfoId = orderElement.GetAttributeInt("ordergiverinfoid", -1); - var orderGiver = orderGiverInfoId >= 0 ? Character.CharacterList.FirstOrDefault(c => c.Info?.ID == orderGiverInfoId) : null; + int orderGiverInfoId = orderElement.GetAttributeInt("ordergiver", -1); + var orderGiver = orderGiverInfoId >= 0 ? Character.CharacterList.FirstOrDefault(c => c.Info?.GetIdentifier() == orderGiverInfoId) : null; Entity targetEntity = null; switch (targetType) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 652cc60f0..be5da631e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -432,6 +432,9 @@ namespace Barotrauma public readonly float BurnOverlayAlpha; public readonly float DamageOverlayAlpha; + //steam achievement given when the controlled character receives the affliction + public readonly Identifier AchievementOnReceived; + //steam achievement given when the affliction is removed from the controlled character public readonly Identifier AchievementOnRemoved; @@ -560,6 +563,7 @@ namespace Barotrauma IconColors = element.GetAttributeColorArray(nameof(IconColors), null); AfflictionOverlayAlphaIsLinear = element.GetAttributeBool(nameof(AfflictionOverlayAlphaIsLinear), false); + AchievementOnReceived = element.GetAttributeIdentifier(nameof(AchievementOnReceived), ""); AchievementOnRemoved = element.GetAttributeIdentifier(nameof(AchievementOnRemoved), ""); TargetSpecies = element.GetAttributeIdentifierArray("targets", Array.Empty(), trim: true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 46b096bb5..12f7dbc87 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -754,6 +754,7 @@ namespace Barotrauma Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))), newAffliction.Source); afflictions.Add(copyAffliction, limbHealth); + SteamAchievementManager.OnAfflictionReceived(copyAffliction, Character); MedicalClinic.OnAfflictionCountChanged(Character); Character.HealthUpdateInterval = 0.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index 26f40125b..18ff8ac7f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -214,8 +214,7 @@ namespace Barotrauma CharacterInfo characterInfo; if (characterElement == null) { - characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: GetJobPrefab(randSync), npcIdentifier: Identifier); - } + characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: GetJobPrefab(randSync), npcIdentifier: Identifier, randSync: randSync);} else { characterInfo = new CharacterInfo(characterElement, Identifier); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs index 08a5116fe..ef66959e5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs @@ -21,6 +21,9 @@ namespace Barotrauma [Serialize(true, IsPropertySaveable.Yes)] public bool IgnoreIncapacitatedCharacters { get; set; } + [Serialize(false, IsPropertySaveable.Yes)] + public bool AllowHiddenItems { get; set; } + private bool isFinished = false; public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) @@ -139,7 +142,7 @@ namespace Barotrauma private bool IsValidItem(Item it) { - return !it.HiddenInGame && SubmarineTypeMatches(it.Submarine); + return (!it.HiddenInGame || AllowHiddenItems) && SubmarineTypeMatches(it.Submarine); } private bool SubmarineTypeMatches(Submarine sub) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs index cbb841a31..703aa19ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs @@ -116,7 +116,7 @@ namespace Barotrauma { foreach (Entity entity in Entity.GetEntities()) { - if (targetPredicates[tag].Any(p => p(entity))) + if (targetPredicates[tag].Any(p => p(entity)) && !targetsToReturn.Contains(entity)) { targetsToReturn.Add(entity); } @@ -131,7 +131,7 @@ namespace Barotrauma { foreach (Character npc in outpostNPCs) { - if (npc.Removed) { continue; } + if (npc.Removed || targetsToReturn.Contains(npc)) { continue; } targetsToReturn.Add(npc); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index b207870af..03839f12f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -248,23 +248,7 @@ namespace Barotrauma List spawnWaypoints = null; List mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub).ToList(); - bool hostileOutpost = false; - if (Level.IsLoadedOutpost) - { - if (Submarine.Loaded.Any(s => s.Info.Type == SubmarineType.Outpost && (s.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false))) - { - hostileOutpost = true; - } - else if (GameMain.GameSession?.GameMode is CampaignMode campaign) - { - var reputation = campaign.Map?.CurrentLocation?.Reputation; - if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold) - { - hostileOutpost = true; - } - } - } - if (hostileOutpost) + if (Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost()) { spawnWaypoints = WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Human && @@ -278,7 +262,7 @@ namespace Barotrauma while (spawnWaypoints.Any() && spawnWaypoints.Count < characterInfos.Count) { spawnWaypoints.Add(spawnWaypoints[Rand.Int(spawnWaypoints.Count)]); - } + } } if (spawnWaypoints == null || !spawnWaypoints.Any()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs index 16934e1bb..004c007bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs @@ -55,10 +55,11 @@ namespace Barotrauma { DebugConsole.Log($"Set the value \"{identifier}\" to {value}"); + SteamAchievementManager.OnCampaignMetadataSet(identifier, value, unlockClients: true); + if (!data.ContainsKey(identifier)) { data.Add(identifier, value); - SteamAchievementManager.OnCampaignMetadataSet(identifier, value); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs index a517f3d45..b08da08a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs @@ -97,7 +97,7 @@ namespace Barotrauma float probability = element.GetAttributeFloat("probability", 0.0f); MinProbability = element.GetAttributeFloat("minprobability", probability); MaxProbability = element.GetAttributeFloat("maxprobability", probability); - MaxDistanceFromFactionOutpost = element.GetAttributeInt("maxdistance", int.MaxValue); + MaxDistanceFromFactionOutpost = element.GetAttributeInt(nameof(MaxDistanceFromFactionOutpost), int.MaxValue); DisallowBetweenOtherFactionOutposts = element.GetAttributeBool(nameof(DisallowBetweenOtherFactionOutposts), false); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index c57e72e3e..e376e4e22 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -540,6 +540,8 @@ namespace Barotrauma existingRoundSummary.ContinueButton.Visible = true; } + CharacterHUD.ClearBossProgressBars(); + RoundSummary = new RoundSummary(GameMode, Missions, StartLocation, EndLocation); if (GameMode is not TutorialMode && GameMode is not TestGameMode) @@ -549,14 +551,15 @@ namespace Barotrauma { GUI.AddMessage(levelData.Biome.DisplayName, Color.Lerp(Color.CadetBlue, Color.DarkRed, levelData.Difficulty / 100.0f), 5.0f, playSound: false); GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Destination"), EndLocation.Name), Color.CadetBlue, playSound: false); - if (missions.Count > 1) + var missionsToShow = missions.Where(m => m.Prefab.ShowStartMessage); + if (missionsToShow.Count() > 1) { string joinedMissionNames = string.Join(", ", missions.Select(m => m.Name)); GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), joinedMissionNames), Color.CadetBlue, playSound: false); } else { - var mission = missions.FirstOrDefault(); + var mission = missionsToShow.FirstOrDefault(); GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), mission?.Name ?? TextManager.Get("None")), Color.CadetBlue, playSound: false); } } @@ -898,7 +901,7 @@ namespace Barotrauma TabMenu.OnRoundEnded(); GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb)); ObjectiveManager.ResetUI(); - CharacterHUD.ClearBossHealthBars(); + CharacterHUD.ClearBossProgressBars(); #endif SteamAchievementManager.OnRoundEnded(this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index 759ea395a..3c4600b95 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -175,16 +175,6 @@ namespace Barotrauma.Items.Components } public bool IsClosed => !IsOpen; - /// - /// Is the door opening, but not yet fully opened? Returns false both when it's closed and when it's fully open. - /// - public bool IsOpening => IsOpen && !IsFullyOpen; - - /// - /// Is the door closing, but not yet fully closed? Returns false both when the door is open and when it's fully closed. - /// - public bool IsClosing => IsClosed && !IsFullyClosed; - public bool IsFullyOpen => IsOpen && OpenState >= 1.0f; public bool IsFullyClosed => IsClosed && OpenState <= 0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 3a2e7a9c1..5a90d80a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -360,10 +360,12 @@ namespace Barotrauma.Items.Components } var relatedItem = FindContainableItem(containedItem); + drawableContainedItems.RemoveAll(d => d.Item == containedItem); drawableContainedItems.Add(new DrawableContainedItem(containedItem, Hide: relatedItem?.Hide ?? false, ItemPos: relatedItem?.ItemPos, Rotation: relatedItem?.Rotation ?? 0.0f)); + drawableContainedItems.Sort((DrawableContainedItem it1, DrawableContainedItem it2) => Inventory.FindIndex(it1.Item).CompareTo(Inventory.FindIndex(it2.Item))); if (item.GetComponent() != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 52e928c80..66e6e93e0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -234,6 +234,7 @@ namespace Barotrauma.Items.Components public float RepairDegreeOfSuccess(Character character, List skills) { if (skills.Count == 0) { return 1.0f; } + if (character == null) { return 0.0f; } float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum(); float average = skillSum / skills.Count; @@ -243,6 +244,7 @@ namespace Barotrauma.Items.Components public void RepairBoost(bool qteSuccess) { + if (CurrentFixer == null) { return; } if (qteSuccess) { item.Condition += RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index eb9d3990f..a2dcaacfa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -1,6 +1,5 @@ using Microsoft.Xna.Framework; using System; -using System.Xml.Linq; using Barotrauma.Networking; using Barotrauma.Extensions; #if CLIENT @@ -13,6 +12,9 @@ namespace Barotrauma.Items.Components partial class LightComponent : Powered, IServerSerializable, IDrawableComponent { private Color lightColor; + /// + /// The current brightness of the light source, affected by powerconsumption/voltage + /// private float lightBrightness; private float blinkFrequency; private float pulseFrequency, pulseAmount; @@ -174,7 +176,7 @@ namespace Barotrauma.Items.Components #if CLIENT if (Light != null) { - Light.Color = IsOn ? lightColor.Multiply(lightBrightness) : Color.Transparent; + Light.Color = IsOn ? lightColor.Multiply(lightColorMultiplier) : Color.Transparent; } #endif } @@ -214,7 +216,7 @@ namespace Barotrauma.Items.Components { if (base.IsActive == value) { return; } base.IsActive = isOn = value; - SetLightSourceState(value, value ? lightBrightness : 0.0f); + SetLightSourceState(value, value ? lightBrightness : 0.0f); } } @@ -245,7 +247,7 @@ namespace Barotrauma.Items.Components public override void OnItemLoaded() { base.OnItemLoaded(); - SetLightSourceState(IsActive); + SetLightSourceState(IsActive, lightBrightness); turret = item.GetComponent(); #if CLIENT Drawable = AlphaBlend && Light.LightSprite != null; @@ -279,7 +281,8 @@ namespace Barotrauma.Items.Components (statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) && (IsActiveConditionals == null || IsActiveConditionals.Count == 0)) { - SetLightSourceState(true); + lightBrightness = 1.0f; + SetLightSourceState(true, lightBrightness); SetLightSourceTransformProjSpecific(); base.IsActive = false; isOn = true; @@ -308,7 +311,8 @@ namespace Barotrauma.Items.Components #endif if (item.Container != null && item.GetRootInventoryOwner() is not Character) { - SetLightSourceState(false); + lightBrightness = 0.0f; + SetLightSourceState(false, 0.0f); return; } @@ -317,7 +321,8 @@ namespace Barotrauma.Items.Components PhysicsBody body = ParentBody ?? item.body; if (body != null && !body.Enabled) { - SetLightSourceState(false); + lightBrightness = 0.0f; + SetLightSourceState(false, 0.0f); return; } @@ -344,7 +349,7 @@ namespace Barotrauma.Items.Components public override void UpdateBroken(float deltaTime, Camera cam) { - SetLightSourceState(false); + SetLightSourceState(false, 0.0f); } public override bool Use(float deltaTime, Character character = null) @@ -376,7 +381,7 @@ namespace Barotrauma.Items.Components { LightColor = XMLExtensions.ParseColor(signal.value, false); #if CLIENT - SetLightSourceState(Light.Enabled, lightBrightness); + SetLightSourceState(Light.Enabled, lightColorMultiplier); #endif prevColorSignal = signal.value; } @@ -394,7 +399,7 @@ namespace Barotrauma.Items.Components target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness); } - partial void SetLightSourceState(bool enabled, float? brightness = null); + partial void SetLightSourceState(bool enabled, float brightness); public void SetLightSourceTransform() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs index 92effd495..59721d257 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs @@ -36,6 +36,10 @@ namespace Barotrauma public bool IdFreed { get; private set; } + /// + /// Unique, but non-persistent identifier. + /// Stays the same if the entities are created in the exactly same order, but doesn't persist e.g. between the rounds. + /// public readonly ushort ID; public virtual Vector2 SimPosition => Vector2.Zero; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 838bddf4c..a6675f4a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -461,6 +461,19 @@ namespace Barotrauma borders = new Rectangle(Point.Zero, levelData.Size); } + public bool ShouldSpawnCrewInsideOutpost() + { + if (StartOutpost != null && + Type == LevelData.LevelType.Outpost && + (StartOutpost.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false) && + StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player)) + { + var reputation = GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation; + return reputation == null || reputation.NormalizedValue >= Reputation.HostileThreshold; + } + return false; + } + public static Level Generate(LevelData levelData, bool mirror, Location startLocation, Location endLocation, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null) { Debug.Assert(levelData.Biome != null); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 7b680d358..93268f505 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -401,6 +401,7 @@ namespace Barotrauma private static readonly List tempHighlightedEntities = new List(); public static void ClearHighlightedEntities() { + highlightedEntities.RemoveWhere(e => e.Removed); tempHighlightedEntities.Clear(); tempHighlightedEntities.AddRange(highlightedEntities); foreach (var entity in tempHighlightedEntities) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 4d17081eb..de5632bef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -1379,6 +1379,24 @@ namespace Barotrauma Info = new SubmarineInfo(info); ConnectedDockingPorts = new Dictionary(); + + //place the sub above the top of the level + HiddenSubPosition = HiddenSubStartPosition; + if (GameMain.GameSession?.LevelData != null) + { + HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y; + } + + for (int i = 0; i < loaded.Count; i++) + { + Submarine sub = loaded[i]; + HiddenSubPosition = + new Vector2( + //1st sub on the left side, 2nd on the right, etc + HiddenSubPosition.X * (i % 2 == 0 ? 1 : -1), + HiddenSubPosition.Y + sub.Borders.Height + 5000.0f); + } + IdOffset = IdRemap.DetermineNewOffset(); List newEntities = new List(); @@ -1406,6 +1424,7 @@ namespace Barotrauma Vector2 center = Vector2.Zero; var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this); + if (matchingHulls.Any()) { Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y); @@ -1427,17 +1446,6 @@ namespace Barotrauma } subBody = new SubmarineBody(this, showErrorMessages); - - //place the sub above the top of the level - HiddenSubPosition = HiddenSubStartPosition; - if (GameMain.GameSession != null && GameMain.GameSession.LevelData != null) - { - HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y; - } - foreach (Submarine sub in loaded) - { - HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f); - } Vector2 pos = ConvertUnits.ToSimUnits(HiddenSubPosition); subBody.Body.FarseerBody.SetTransformIgnoreContacts(ref pos, 0.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs index 14ae70d17..72e237b6b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs @@ -1,5 +1,4 @@ using Barotrauma.Networking; -using System; using System.Collections.Generic; namespace Barotrauma diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 015a95416..c2d8bfaa6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1444,7 +1444,6 @@ namespace Barotrauma if (target == null) { continue; } foreach (Affliction affliction in Afflictions) { - if (Rand.Value(Rand.RandSync.Unsynced) > affliction.Probability) { continue; } Affliction newAffliction = affliction; if (target is Character character) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs index 09ebf269f..ee733cbe1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs @@ -219,10 +219,10 @@ namespace Barotrauma UnlockAchievement($"discover{biome.Identifier.Value.Replace(" ", "")}".ToIdentifier()); } - public static void OnCampaignMetadataSet(Identifier identifier, object value) + public static void OnCampaignMetadataSet(Identifier identifier, object value, bool unlockClients = false) { if (identifier.IsEmpty || value is null) { return; } - UnlockAchievement($"campaignmetadata_{identifier}_{value}".ToIdentifier()); + UnlockAchievement($"campaignmetadata_{identifier}_{value}".ToIdentifier(), unlockClients); } public static void OnItemRepaired(Item item, Character fixer) @@ -236,6 +236,15 @@ namespace Barotrauma UnlockAchievement(fixer, $"repair{item.Prefab.Identifier}".ToIdentifier()); } + public static void OnAfflictionReceived(Affliction affliction, Character character) + { + if (affliction.Prefab.AchievementOnReceived.IsEmpty) { return; } +#if CLIENT + if (GameMain.Client != null) { return; } +#endif + UnlockAchievement(character, affliction.Prefab.AchievementOnReceived); + } + public static void OnAfflictionRemoved(Affliction affliction, Character character) { if (affliction.Prefab.AchievementOnRemoved.IsEmpty) { return; } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 15470c0cb..0c984657f 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,71 @@ +--------------------------------------------------------------------------------------------------------- +v1.0.7.0 +--------------------------------------------------------------------------------------------------------- + +- Fixed mechanic tutorial getting stuck at the point where you need to weld a leak. +- Fixed treatment suggestions not showing up in the naloxone part of the medic tutorial. +- Fixed patient spawning dead in the CPR part of the medic tutorial. +- Fixed bots being unable to move through Herja's airlock due to an unlinked waypoint. +- Fixed tutorial Dugong spawning with empty ammo boxes. + +--------------------------------------------------------------------------------------------------------- +v1.0.6.0 +--------------------------------------------------------------------------------------------------------- + +- Fixed some new Steam achievements not unlocking. + +--------------------------------------------------------------------------------------------------------- +v1.0.5.0 +--------------------------------------------------------------------------------------------------------- + +- Fixes to Japanese translations. +- Implemented support for some upcoming Steam achievements. +- Improved backwards compatibility: fixed outpost managers no longer spawning in mods that override outpost generation parameters due to the generic non-faction-specific outpost manager prefab being removed. + +--------------------------------------------------------------------------------------------------------- +v1.0.4.0 +--------------------------------------------------------------------------------------------------------- + +- Fixed outpost NPCs getting randomized every time you re-enter an outpost. +- Fixed inability to gain more than 15 talent points in the multiplayer campaign. + +--------------------------------------------------------------------------------------------------------- +v1.0.3.0 +--------------------------------------------------------------------------------------------------------- + +- Adjusted plant spawn rates in caves. +- Made lead more common in stores. +- Fixed mission listing shown on the top of the screen spoiling enemy faction ambushes. +- Fixed enemy faction ambushes being possible everywhere on the map (they should only happen within 3 steps of outposts belonging to an enemy faction). +- Fixes bots sometimes running towards doors that are closed by something else (another person or an automatic logic). +- Fixes bots ordered to wait outside of the submarine not being able to switch their oxygen tanks. +- Added a couple of endgame-foreshadowing lore bits. + +--------------------------------------------------------------------------------------------------------- +v1.0.2.0 +--------------------------------------------------------------------------------------------------------- + +- Exosuits are powered by fuel rods instead of batteries. +- Fixed affliction probabilities being evaluated twice, meaning that e.g. 50% probability of getting some affliction from an attack was actually a 25% probability. +- Fixed item highlights from the previous round remaining visible the next round. +- Fixed swapping items in a container sometimes causing too many items to be visible in it. +- Fix the vitality modifiers on husk not working properly, because health indices on the limbs were not defined. Effectively husks always took 2x damage. +- Fixed characters still spawning inside outposts that have turned hostile due to low reputation. +- Fixed all special faction hire events getting stuck if you say you need to "think about it", return, and say you still need to think about it. +- Fixed missing "place in ceiling" text in beacon station save dialog. +- Fixed basic depth charges being cheaper than intended (only 30 mk). +- Fixed inability to make lights blink at a high frequency by rapidly turning them on and off with e.g. oscillators. +- Fixed red glow around the light switch's green button. +- Fixed odd fabrication list sorting: the items that require a recipe to fabricate were split into ones you have the skills to fabricate and ones you don't, even though that isn't visible in the UI, making the list just seem out of order. +- Fixed "skedaddle" not giving a 10% movement boost like the description says. +- Fixed acid burns not having a cause of death text. +- Fixed ranged weapons emitting particles in the wrong direction. There haven't been any changes to this code in years, so it must've been an issue for a long time, I guess we just never noticed because no gun before the scrap cannon emitted particles with a noticeable velocity? +- Fixed banana being held weirldy. +- Fixed a pathfinding issue that often made bots swim against cave walls. +- Fixed inability to join servers with a submarine switch/purchase vote running. +- Fixed votes passing if the client who initiated them disconnects before anyone else votes. +- Fixed follow orders not being persistent between singleplayer rounds. + --------------------------------------------------------------------------------------------------------- v1.0.1.0 --------------------------------------------------------------------------------------------------------- @@ -56,7 +124,7 @@ Misc changes: - Made high-quality stun guns more effective (stunning the target faster). - The health scanner always shows poisons and paralysis on monsters to make it easier to determine whether the poisoning is progressing or wearing off. - A pass on sound ranges: the ranges should now be more consistent and sensible. -- Made moloch shell fragment and riot shield as medium items instead of small to fix them going inside e.g. toolbelts. +- Made moloch shell fragment and riot shield medium items instead of small to fix them going inside e.g. toolbelts. - Made husk eggs consumable. - Made it more difficult to repeatedly enter an abandoned outpost and re-loot the bandits: now the bandits immediately attack you if you re-enter the outpost. - Monsters you haven't encountered yet are now hidden by default in the character editor. Can be enabled using the command "showmonsters" and re-hidden using "hidemonsters". The value is saved in creaturemetrics.xml. Doesn't affect custom creatures. @@ -65,14 +133,13 @@ Misc changes: - Added a round light component variant. - Increased the hard-coded max mission count back from 3 to 10. It'd be preferable to not change the value above 3 in the vanilla game, but since campaign settings are not moddable, we shouldn't be too strict about it (because it can be useful for a mod that this value can be adjusted). - Miscellaneous optimizations. -- New slot indicator icons (= the icons that show what can go inside some item, like tanks/ammo). +- New slot indicator icons (= the icons that show what can go inside some items, like tanks/ammo). - Made outpost hull repair service cheaper. - Doors can now be damaged by melee weapons and ranged (handheld) weapons. (They were already destructible by submarine mounted weapons and explosives) - Adjusted and rebalanced item damage for most items, to take into account doors being destructible. - Reduced time needed for a crowbar to open doors, 7.5s for regular doors, 6s for wrecked doors (down from 10 s). - Boosted Plasma Cutter damage against doors and items (walls not touched). -- Made galena more common to make lead easier to get. -- Recreated all and added new slot indicator icons to provide information about the containable item restrictions. +- Made galena more common in order to make lead easier to get. - Added Auto Operate option for all turrets. Can be enabled in the submarine editor. Not currently used on vanilla submarines. Auto operated turrets don't require a person to operate them, but they still require power and ammunition (-> someone needs to reload them). Multiplayer: @@ -89,9 +156,9 @@ AI: - Fixed bots deciding prematurely that they can't fix an item when it's deteriorating (e.g. when it's submerged). - Fixed bots removing battery cells from exosuits when ordered to charge batteries. - Fixed bots sometimes getting stuck in automatic doors and/or double doors, because they didn't wait for the door to open entirely before pressing the button again. -- Improved bot extinguish fires behavior. Fixes bots sometimes not being able to extinguish larger fires, because they stopped too far and didn't keep advancing towards the target. +- Improved bot ‘extinguish fires’ behavior. Fixes bots sometimes not being able to extinguish larger fires, because they stopped too far and didn't keep advancing towards the target. - Fixed bots claiming that they can't return back to the sub and then following the order anyway. -- Improved the find safety calculations so that the bots give more preference to the distance of the room. +- Improved the ‘find safety’ calculations so that the bots give more preference to the distance of the room. - Fixed some remaining issues and edge cases in the logic over when the bot needs diving gear and when it can be taken off. - Fixed captains (and some NPCs) idling in the airlock if they equip a diving suit. - Bot can now target items (like projectiles) with turrets and have different targeting priorities on different monsters. @@ -104,11 +171,11 @@ Fixes: - Fixed "kill" command not killing characters under the influence of "Miracle Worker". - Fixed some lights becoming invisible when their range is set to 0 and they're against a dark background. - Fixed lights turning on without power when they receive a toggle or set_color input. -- Fixed changing the amount to fabricate starting fabrication in MP if you've previously started fabricating something. +- Fixed changing the amount of items to fabricate inadvertently starting(or activating) fabrication in MP if you've previously started fabricating something - Fixed campaign settings resetting in the campaign setup menu every time you relaunch the game (meaning you'd always need to e.g. remember to toggle the tutorial off if you want to play without it). - Fixed inverted mouse buttons not working properly since the last update: the left mouse button was considered the primary mouse button regardless of your OS settings. - Fixed status monitor not properly displaying condition on tinkered items. -- Fixed machines at above 100% condition with tinkering smoking. +- Fixed machines smoking when above 100% condition with tinkering. - Fixed inventory overlapping with the chatbox on low aspect ratios (small width, large height). - Fixed some layering issues in abandoned outposts. - Fixed water-sensitive items sometimes spawning as loot in wrecks. @@ -117,7 +184,7 @@ Fixes: - Fixed crashing on startup if the MD5 hash cache file is empty. - Fixed research stations and loaders not being visible on the status monitor's electrical view. - Fixed artifact missions sometimes choosing the same artifact as a target if you happen to have multiple missions active at a time, which would lead to console errors when the round ends. -- Fixed exosuit playing the warning beep if there's empty or almost empty tanks in any of it's slots. +- Fixed exosuit playing the warning beep if there's empty or almost empty tanks in any of its slots. - Fixed oxygen generators deteriorating in some of the outpost modules. - Fixed reputation loss when a character other than the player (e.g. crawlers in the 'crawleroutbreak' event) damages the outpost walls. - Fixed outpost modules sometimes being placed in a way that makes them overlap with the sub. @@ -144,7 +211,7 @@ Fixes: - Venture: Fixed the battery room not flooding properly (again), fixed the two hulls in the airlock not being linked, adjusted the waypoints a bit. - Selkie: Disconnect the outer nodes from the ladder/door nodes, because the docking ports can't be opened manually. - Fixed Thalamus AI not running properly when there's no player characters or submarines around (e.g. when all the players are in the freecam mode). -- Fixed the ammo indicator not showing correctly on advanced syringe gun. +- Fixed the ammo indicator not showing correctly on the advanced syringe gun. - Fixed bots sometimes getting confused by outside waypoints while being inside an outpost. - Fixed item relocation logic running also on NPCs that are not in the player team, which could cause diving suits dropped by NPCs to get spawned in the player sub. diff --git a/Barotrauma/BarotraumaTest/CoordinateSpace2DTests.cs b/Barotrauma/BarotraumaTest/CoordinateSpace2DTests.cs new file mode 100644 index 000000000..e54c33018 --- /dev/null +++ b/Barotrauma/BarotraumaTest/CoordinateSpace2DTests.cs @@ -0,0 +1,55 @@ +using Barotrauma.Utils; +using FluentAssertions; +using FsCheck; +using Microsoft.Xna.Framework; +using System; +using Xunit; +namespace TestProject; + +public class CoordinateSpace2DTests +{ + class CustomGenerators + { + public static Arbitrary Vector2Generator() + { + const int intRange = 1 << 22; + const float intToFloat = 1 << 19; + + return Arb.From( + from int x in Gen.Choose(-intRange, intRange) + from int y in Gen.Choose(-intRange, intRange) + select new Vector2(x / intToFloat, y / intToFloat)); + } + } + + public CoordinateSpace2DTests() + { + Arb.Register(); + } + + [Fact] + public void TestLocalToCanonical() + { + void testCase(Tuple args) + { + var (vector, origin, i, j) = args; + + if (Vector2.DistanceSquared(i, j) <= 0.01f) { return; } + + var space = new CoordinateSpace2D + { + Origin = origin, + I = i, + J = j + }; + + Assert.True(Vector2.DistanceSquared( + Vector2.Transform(vector, space.LocalToCanonical), + origin + vector.X * i + vector.Y * j) < 0.01f); + } + + Prop.ForAll( + Arb.Generate>().ToArbitrary(), + testCase).QuickCheckThrowOnFailure(); + } +} diff --git a/Barotrauma/BarotraumaTest/EndpointParseTests.cs b/Barotrauma/BarotraumaTest/EndpointParseTests.cs index c8ccb4e43..0038d952b 100644 --- a/Barotrauma/BarotraumaTest/EndpointParseTests.cs +++ b/Barotrauma/BarotraumaTest/EndpointParseTests.cs @@ -14,8 +14,7 @@ public class EndpointParseTests { Endpoint.Parse("127.0.0.1:27015") .Should() - .BeOfType>() - .And.BeEquivalentTo( + .BeEquivalentTo( Option.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)), options => options.RespectingRuntimeTypes()); } @@ -25,8 +24,7 @@ public class EndpointParseTests { Endpoint.Parse("localhost:27015") .Should() - .BeOfType>() - .And.BeEquivalentTo( + .BeEquivalentTo( Option.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)), options => options.RespectingRuntimeTypes()); } @@ -36,8 +34,7 @@ public class EndpointParseTests { Address.Parse("127.0.0.1") .Should() - .BeOfType>() - .And.BeEquivalentTo( + .BeEquivalentTo( Option
.Some(new LidgrenAddress(IPAddress.Loopback)), options => options.RespectingRuntimeTypes()); } @@ -47,8 +44,7 @@ public class EndpointParseTests { Endpoint.Parse("STEAM_1:1:508792388") .Should() - .BeOfType>() - .And.BeEquivalentTo( + .BeEquivalentTo( Option.Some(new SteamP2PEndpoint(new SteamId(76561198977850505))), options => options.RespectingRuntimeTypes()); } @@ -58,8 +54,7 @@ public class EndpointParseTests { Address.Parse("STEAM_1:1:508792388") .Should() - .BeOfType>() - .And.BeEquivalentTo( + .BeEquivalentTo( Option
.Some(new SteamP2PAddress(new SteamId(76561198977850505))), options => options.RespectingRuntimeTypes()); new SteamId(76561198977850505).StringRepresentation.Should().BeEquivalentTo("STEAM_1:1:508792388"); diff --git a/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs b/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs index 86213b54d..76d2a689c 100644 --- a/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs +++ b/Barotrauma/BarotraumaTest/INetSerializableStructImplementationChecks.cs @@ -10,7 +10,7 @@ using Xunit; namespace TestProject; -public class INetSerializableStructImplementationChecks +public sealed class INetSerializableStructImplementationChecks { private delegate bool TryFindBehaviorDelegate(Type type, out NetSerializableProperties.IReadWriteBehavior behavior); @@ -49,7 +49,7 @@ public class INetSerializableStructImplementationChecks viableArguments.AddRange(new[] { typeof(Vector2), - typeof(Point), + typeof(float), typeof(int) }); } diff --git a/Barotrauma/BarotraumaTest/MathUtilsTests.cs b/Barotrauma/BarotraumaTest/MathUtilsTests.cs new file mode 100644 index 000000000..e624b3f4e --- /dev/null +++ b/Barotrauma/BarotraumaTest/MathUtilsTests.cs @@ -0,0 +1,67 @@ +using Barotrauma; +using FluentAssertions; +using Microsoft.Xna.Framework; +using System; +using Xunit; + +namespace TestProject; + +public class MathUtilsTests +{ + [Fact] + public void TestNearlyEquals() + { + MathUtils.NearlyEqual(0.0f, 0.0f).Should().BeTrue(); + MathUtils.NearlyEqual(-float.Epsilon, float.Epsilon).Should().BeTrue(); + MathUtils.NearlyEqual(0.1f + 0.2f, 0.3f).Should().BeTrue(); + MathUtils.NearlyEqual(-1.0f, 1.0f).Should().BeFalse(); + } + + [Fact] + public void TestWrapAngle() + { + MathUtils.NearlyEqual(MathUtils.WrapAnglePi(0.0f), 0.0f).Should().BeTrue(); + + CheckWrapAnglePiNearlyEqual(0, 0).Should().BeTrue(); + CheckWrapAnglePiNearlyEqual(-90, -90).Should().BeTrue(); + CheckWrapAnglePiNearlyEqual(-90, 90).Should().BeFalse(); + CheckWrapAnglePiNearlyEqual(-180, 180).Should().BeTrue(); + CheckWrapAnglePiNearlyEqual(-190.0f, 170.0f).Should().BeTrue(); + CheckWrapAnglePiNearlyEqual(-360, 0).Should().BeTrue(); + CheckWrapAnglePiNearlyEqual(360, 0).Should().BeTrue(); + + bool CheckWrapAnglePiNearlyEqual(float wrappedDeg, float deg) + { + float wrappedRad = MathUtils.WrapAnglePi(MathHelper.ToRadians(wrappedDeg)); + float rad = MathHelper.ToRadians(deg); + return MathUtils.NearlyEqual(wrappedRad, rad) || MathUtils.NearlyEqual(Math.Abs(wrappedRad - rad), MathHelper.TwoPi); + } + + CheckWrapAngleTwoPiNearlyEqual(0, 0).Should().BeTrue(); + CheckWrapAngleTwoPiNearlyEqual(90, 90).Should().BeTrue(); + CheckWrapAngleTwoPiNearlyEqual(-90, 270).Should().BeTrue(); + CheckWrapAngleTwoPiNearlyEqual(180, 180).Should().BeTrue(); + CheckWrapAngleTwoPiNearlyEqual(360 * 5, 0).Should().BeTrue(); + CheckWrapAngleTwoPiNearlyEqual(-360, 0).Should().BeTrue(); + + bool CheckWrapAngleTwoPiNearlyEqual(float wrappedDeg, float deg) + { + float wrappedRad = MathUtils.WrapAngleTwoPi(MathHelper.ToRadians(wrappedDeg)); + float rad = MathHelper.ToRadians(deg); + return MathUtils.NearlyEqual(wrappedRad, rad) || MathUtils.NearlyEqual(Math.Abs(wrappedRad - rad), MathHelper.TwoPi); + } + + CheckShortestAngleNearlyEqual(0.0f, 0.0f, 0.0f).Should().BeTrue(); + CheckShortestAngleNearlyEqual(0.0f, 90.0f, 90.0f).Should().BeTrue(); + CheckShortestAngleNearlyEqual(0.0f, 360.0f, 0.0f).Should().BeTrue(); + CheckShortestAngleNearlyEqual(0.0f, -365.0f, -5.0f).Should().BeTrue(); + CheckShortestAngleNearlyEqual(180.0f, -180.0f, 0.0f).Should().BeTrue(); + CheckShortestAngleNearlyEqual(-355.0f, 5.0f, 10.0f); + + bool CheckShortestAngleNearlyEqual(float deg1, float deg2, float angle) + { + return MathUtils.NearlyEqual(MathUtils.GetShortestAngle(MathHelper.ToRadians(deg1), MathHelper.ToRadians(deg2)), MathHelper.ToRadians(angle)); + } + + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaTest/SerializableDateTimeTests.cs b/Barotrauma/BarotraumaTest/SerializableDateTimeTests.cs new file mode 100644 index 000000000..03cbcb402 --- /dev/null +++ b/Barotrauma/BarotraumaTest/SerializableDateTimeTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics; +using Barotrauma; +using FluentAssertions; +using FsCheck; +using Xunit; + +namespace TestProject; + +public sealed class SerializableDateTimeTests +{ + private class CustomGenerators + { + private const short MinutesPerDay = 24 * 60; + private const int SecondsPerDay = MinutesPerDay * 60; + + public static Arbitrary SerializableDateTimeGenerator() + { + return Arb.From( + from int dateTimeDay in Gen.Choose(0, (int)(DateTime.MaxValue.Ticks / TimeSpan.TicksPerDay)) + from int dateTimeSeconds in Gen.Choose(0, SecondsPerDay) + from int timeZoneMinutes in Gen.Choose(-MinutesPerDay / 2, MinutesPerDay / 2) + select new SerializableDateTime( + DateTime.MinValue + TimeSpan.FromDays(dateTimeDay) + TimeSpan.FromSeconds(dateTimeSeconds), + new SerializableTimeZone(TimeSpan.FromMinutes(timeZoneMinutes)))); + } + } + + public SerializableDateTimeTests() + { + Arb.Register(); + Arb.Register(); + } + + [Fact] + public void EqualityTest() + { + Prop.ForAll(EqualityCheck).QuickCheckThrowOnFailure(); + } + + [Fact] + public void ParseTest() + { + Prop.ForAll(ParseCheck).QuickCheckThrowOnFailure(); + } + + [Fact] + public void ToLocalTest() + { + Prop.ForAll(ToLocalCheck).QuickCheckThrowOnFailure(); + } + + private static void EqualityCheck(SerializableDateTime original) + { + var local = original.ToLocal(); + var utc = original.ToUtc(); + original.Should().BeEquivalentTo(local, because: "original must equal local"); + original.Should().BeEquivalentTo(utc, because: "original must equal utc"); + local.Should().BeEquivalentTo(utc, because: "local must equal utc"); + } + + private static void ParseCheck(SerializableDateTime original) + { + var str = original.ToString(); + SerializableDateTime.Parse(str).TryUnwrap(out var parsedTime).Should().BeTrue(); + parsedTime.Should().BeEquivalentTo(original); + } + + private static void ToLocalCheck(SerializableDateTime original) + { + var localNow = SerializableDateTime.LocalNow; + var convertedDateTime = original.ToLocal(); + localNow.TimeZone.Should().BeEquivalentTo(convertedDateTime.TimeZone); + } +} diff --git a/Barotrauma/BarotraumaTest/TestProject.cs b/Barotrauma/BarotraumaTest/TestProject.cs index 6b36c44e1..479273e9e 100644 --- a/Barotrauma/BarotraumaTest/TestProject.cs +++ b/Barotrauma/BarotraumaTest/TestProject.cs @@ -10,8 +10,8 @@ namespace TestProject { public static Arbitrary Vector2Generator() { - return Arb.From(from int x in Arb.Generate() - from int y in Arb.Generate() + return Arb.From(from float x in Arb.Generate().Where(f => !float.IsNaN(f) && !float.IsInfinity(f)) + from float y in Arb.Generate().Where(f => !float.IsNaN(f) && !float.IsInfinity(f)) select new Vector2(x, y)); }