v1.0.7.0 (Full Release)

This commit is contained in:
Regalis11
2023-03-13 10:30:37 +02:00
parent 2c5a7923b0
commit bf73ddb6c3
50 changed files with 504 additions and 193 deletions
@@ -85,7 +85,7 @@ namespace Barotrauma
} }
} }
public abstract bool IsDuplicate(BossProgressBar progressBar); public abstract bool IsDuplicate(object targetObject);
} }
class BossHealthBar : BossProgressBar class BossHealthBar : BossProgressBar
@@ -109,9 +109,9 @@ namespace Barotrauma
Character = character; 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; 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<Item> brokenItems = new List<Item>(); private static readonly List<Item> brokenItems = new List<Item>();
private static float brokenItemsCheckTimer; private static float brokenItemsCheckTimer;
private static readonly List<BossProgressBar> bossHealthBars = new List<BossProgressBar>(); private static readonly List<BossProgressBar> bossProgressBars = new List<BossProgressBar>();
private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>(); private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>();
@@ -747,44 +747,44 @@ namespace Barotrauma
public static void ShowBossHealthBar(Character character, float damage) public static void ShowBossHealthBar(Character character, float damage)
{ {
if (character == null || character.IsDead || character.Removed) { return; } 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) public static void ShowMissionProgressBar(Mission mission)
{ {
if (mission == null || mission.Completed || mission.Failed) { return; } if (mission == null || mission.Completed || mission.Failed) { return; }
if (bossProgressBars.Any(b => b.IsDuplicate(mission))) { return; }
AddBossProgressBar(new MissionProgressBar(mission)); 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; var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode == EnemyHealthBarMode.HideAll) if (healthBarMode == EnemyHealthBarMode.HideAll)
{ {
return; return;
} }
if (bossProgressBars.Count > 5)
var existingBar = bossHealthBars.Find(b => b.IsDuplicate(progressBar));
if (existingBar != null)
{ {
existingBar.FadeTimer = BossHealthBarDuration; BossProgressBar oldestHealthBar = bossProgressBars.First();
if (damage > 0) foreach (var bar in bossProgressBars)
{
// 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)
{ {
if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize) if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
{ {
@@ -793,18 +793,18 @@ namespace Barotrauma
} }
oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f); oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f);
} }
bossHealthBars.Add(progressBar); bossProgressBars.Add(progressBar);
} }
public static void UpdateBossProgressBars(float deltaTime) public static void UpdateBossProgressBars(float deltaTime)
{ {
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars; 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) if (showTopBar && !bossHealthBar.TopContainer.Visible)
{ {
bossHealthBar.SideContainer.SetAsLastChild(); bossHealthBar.SideContainer.SetAsLastChild();
@@ -848,14 +848,14 @@ namespace Barotrauma
} }
bossHealthBar.FadeTimer -= deltaTime; 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) if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
{ {
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer); bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer); bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
bossHealthBars.RemoveAt(i); bossProgressBars.RemoveAt(i);
bossHealthContainer.Recalculate(); bossHealthContainer.Recalculate();
} }
} }
@@ -578,7 +578,7 @@ namespace Barotrauma
ch.SetPersonalityTrait(); ch.SetPersonalityTrait();
ch.Job?.OverrideSkills(skillLevels); ch.Job?.OverrideSkills(skillLevels);
ch.ExperiencePoints = inc.ReadUInt16(); ch.ExperiencePoints = inc.ReadInt32();
ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints); ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
return ch; return ch;
} }
@@ -76,6 +76,7 @@ namespace Barotrauma
partial void UpdateProjSpecific() partial void UpdateProjSpecific()
{ {
if (boss == null || boss.Removed) { return; }
if (Phase is MissionPhase.Initial or MissionPhase.NoItemsDestroyed or MissionPhase.SomeItemsDestroyed) if (Phase is MissionPhase.Initial or MissionPhase.NoItemsDestroyed or MissionPhase.SomeItemsDestroyed)
{ {
// Put asleep. // Put asleep.
@@ -611,10 +611,6 @@ namespace Barotrauma
GameMain.Client?.Draw(spriteBatch); 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?.Inventory != null)
{ {
if (Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead) if (Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead)
@@ -14,6 +14,13 @@ namespace Barotrauma.Items.Components
private CoroutineHandle resetPredictionCoroutine; private CoroutineHandle resetPredictionCoroutine;
private float resetPredictionTimer; private float resetPredictionTimer;
/// <summary>
/// The current multiplier for the light color (usually equal to <see cref="lightBrightness"/>, 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).
/// </summary>
private float lightColorMultiplier;
public Vector2 DrawSize public Vector2 DrawSize
{ {
get { return new Vector2(Light.Range * 2, Light.Range * 2); } 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; 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; } if (Light == null) { return; }
Light.Enabled = enabled; Light.Enabled = enabled;
if (brightness.HasValue) lightColorMultiplier = brightness;
{
lightBrightness = brightness.Value;
}
else
{
lightBrightness = enabled ? 1.0f : 0.0f;
}
if (enabled) if (enabled)
{ {
Light.Color = LightColor.Multiply(lightBrightness); Light.Color = LightColor.Multiply(lightColorMultiplier);
} }
} }
@@ -328,19 +328,21 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub: case VoteType.SwitchSub:
string subName2 = inc.ReadString(); 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(); bool transferItems = inc.ReadBoolean();
if (submarineInfo == null) if (GameMain.GameSession != null)
{ {
DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); var submarineInfo = GameMain.GameSession.OwnedSubmarines.FirstOrDefault(s => s.Name == subName2) ?? GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2);
return; 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; break;
} }
GameMain.Client.VotingInterface?.EndVote(passed, yesClientCount, noClientCount); GameMain.Client.VotingInterface?.EndVote(passed, yesClientCount, noClientCount);
if (passed && submarineVoteInfo.SubmarineInfo is { } subInfo) if (passed && submarineVoteInfo.SubmarineInfo is { } subInfo)
{ {
switch (voteType) switch (voteType)
@@ -1043,7 +1043,15 @@ namespace Barotrauma
if (backgroundSprite == null) if (backgroundSprite == null)
{ {
#if UNSTABLE
backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null); 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(); var vignette = GUIStyle.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite();
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>1.0.1.0</Version> <Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>1.0.1.0</Version> <Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>1.0.1.0</Version> <Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>1.0.1.0</Version> <Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>1.0.1.0</Version> <Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -91,7 +91,7 @@ namespace Barotrauma
msg.WriteByte((byte)0); msg.WriteByte((byte)0);
} }
msg.WriteUInt16((ushort)ExperiencePoints); msg.WriteInt32(ExperiencePoints);
msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints); msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints);
} }
} }
@@ -2367,10 +2367,7 @@ namespace Barotrauma.Networking
List<WayPoint> spawnWaypoints = null; List<WayPoint> spawnWaypoints = null;
List<WayPoint> mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]).ToList(); List<WayPoint> mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]).ToList();
if (Level.Loaded?.StartOutpost != null && if (Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost())
Level.Loaded.Type == LevelData.LevelType.Outpost &&
(Level.Loaded.StartOutpost.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false) &&
Level.Loaded.StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player))
{ {
spawnWaypoints = WayPoint.WayPointList.FindAll(wp => spawnWaypoints = WayPoint.WayPointList.FindAll(wp =>
wp.SpawnType == SpawnType.Human && wp.SpawnType == SpawnType.Human &&
@@ -3313,12 +3310,13 @@ namespace Barotrauma.Networking
if (checkActiveVote && Voting.ActiveVote != null) 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); 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); Voting.ActiveVote.Finish(Voting, passed: true);
} }
else else if (inGameClients.Any())
{ {
var eligibleClients = inGameClients.Where(c => c != Voting.ActiveVote.VoteStarter); var eligibleClients = inGameClients.Where(c => c != Voting.ActiveVote.VoteStarter);
int yes = eligibleClients.Count(c => c.GetVote<int>(Voting.ActiveVote.VoteType) == 2); int yes = eligibleClients.Count(c => c.GetVote<int>(Voting.ActiveVote.VoteType) == 2);
@@ -79,10 +79,10 @@ namespace Barotrauma
if (passed) if (passed)
{ {
Wallet fromWallet = From == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : From.Character?.Wallet; 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; Wallet toWallet = To == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : To.Character?.Wallet;
toWallet.Give(TransferAmount); toWallet?.Give(TransferAmount);
} }
} }
else else
@@ -203,12 +203,16 @@ namespace Barotrauma
// Do not take unanswered into account for total // Do not take unanswered into account for total
int yes = eligibleClients.Count(c => c.GetVote<int>(ActiveVote.VoteType) == 2); int yes = eligibleClients.Count(c => c.GetVote<int>(ActiveVote.VoteType) == 2);
int no = eligibleClients.Count(c => c.GetVote<int>(ActiveVote.VoteType) == 1); int no = eligibleClients.Count(c => c.GetVote<int>(ActiveVote.VoteType) == 1);
int total = Math.Max(yes + no, 1); int total = yes + no;
bool passed =
yes / (float)total >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio ||
inGameClients.Count() == 1;
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); ActiveVote.Finish(this, passed);
} }
} }
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>1.0.1.0</Version> <Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -546,12 +546,11 @@ namespace Barotrauma
bool NeedsDivingGearOnPath(AIObjectiveGoTo gotoObjective) bool NeedsDivingGearOnPath(AIObjectiveGoTo gotoObjective)
{ {
if (Character.IsImmuneToPressure) { return false; }
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty; bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
Hull targetHull = gotoObjective.GetTargetHull(); Hull targetHull = gotoObjective.GetTargetHull();
return gotoObjective.Target != null && targetHull == null || return (gotoObjective.Target != null && targetHull == null && !Character.IsImmuneToPressure) ||
NeedsDivingGear(targetHull, out _) || 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) if (isCarrying)
@@ -1550,23 +1549,19 @@ namespace Barotrauma
public bool NeedsDivingGear(Hull hull, out bool needsSuit) public bool NeedsDivingGear(Hull hull, out bool needsSuit)
{ {
if (Character.IsImmuneToPressure)
{
needsSuit = false;
return false;
}
needsSuit = false; needsSuit = false;
bool needsAir = Character.NeedsAir && Character.CharacterHealth.OxygenLowResistance < 1;
if (hull == null || if (hull == null ||
hull.WaterPercentage > 90 || hull.WaterPercentage > 90 ||
hull.LethalPressure > 0 || hull.LethalPressure > 0 ||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f)) hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f))
{ {
needsSuit = true; needsSuit = !Character.IsProtectedFromPressure;
return true; 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; return false;
} }
@@ -1943,7 +1938,7 @@ namespace Barotrauma
visibleHulls = VisibleHulls; visibleHulls = VisibleHulls;
} }
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>(); bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreOxygen = character.IsProtectedFromPressure || HasDivingGear(character); bool ignoreOxygen = HasDivingGear(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>(); bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater: false, ignoreOxygen, ignoreFire, ignoreEnemies); float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater: false, ignoreOxygen, ignoreFire, ignoreEnemies);
if (isCurrentHull) if (isCurrentHull)
@@ -1976,7 +1971,7 @@ namespace Barotrauma
waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume); waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume);
} }
} }
if (character.CharacterHealth.OxygenLowResistance >= 1) if (!character.NeedsOxygen || character.CharacterHealth.OxygenLowResistance >= 1)
{ {
oxygenFactor = 1; oxygenFactor = 1;
} }
@@ -26,7 +26,7 @@ namespace Barotrauma
private float findPathTimer; private float findPathTimer;
private const float buttonPressCooldown = 3; private const float ButtonPressCooldown = 1;
private float checkDoorsTimer; private float checkDoorsTimer;
private float buttonPressTimer; private float buttonPressTimer;
@@ -342,7 +342,7 @@ namespace Barotrauma
CheckDoorsInPath(); CheckDoorsInPath();
doorsChecked = true; 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. // We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again.
Reset(); Reset();
@@ -694,7 +694,7 @@ namespace Barotrauma
if (door.Item.TryInteract(character, forceSelectKey: true)) if (door.Item.TryInteract(character, forceSelectKey: true))
{ {
lastDoor = (door, shouldBeOpen); lastDoor = (door, shouldBeOpen);
buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0; buttonPressTimer = shouldBeOpen ? ButtonPressCooldown : 0;
} }
else else
{ {
@@ -712,7 +712,7 @@ namespace Barotrauma
if (closestButton.Item.TryInteract(character, forceSelectKey: true)) if (closestButton.Item.TryInteract(character, forceSelectKey: true))
{ {
lastDoor = (door, shouldBeOpen); lastDoor = (door, shouldBeOpen);
buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0; buttonPressTimer = shouldBeOpen ? ButtonPressCooldown : 0;
} }
else else
{ {
@@ -52,7 +52,7 @@ namespace Barotrauma
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) || objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() || objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0)) objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
&& character.IsProtectedFromPressure ? 0 : 100; && ((character.IsImmuneToPressure && !character.IsLowInOxygen)|| HumanAIController.HasDivingSuit(character)) ? 0 : 100;
} }
else else
{ {
@@ -465,6 +465,10 @@ namespace Barotrauma
public readonly OrderTarget TargetPosition; public readonly OrderTarget TargetPosition;
private ISpatialEntity targetSpatialEntity; private ISpatialEntity targetSpatialEntity;
/// <summary>
/// Note this property doesn't return the follow target of the Follow objective, as expected!
/// </summary>
public ISpatialEntity TargetSpatialEntity public ISpatialEntity TargetSpatialEntity
{ {
get get
@@ -348,6 +348,7 @@ namespace Barotrauma
{ {
if (body.UserData is Submarine) { return false; } if (body.UserData is Submarine) { return false; }
if (body.UserData is Structure s && !s.IsPlatform) { 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; } if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; }
} }
} }
@@ -476,10 +476,15 @@ namespace Barotrauma
} }
set set
{ {
if (info != null && info != value) info.Remove(); if (info != null && info != value)
{
info.Remove();
}
info = value; 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 InWater => AnimController is AnimController { InWater: true };
public bool IsLowInOxygen => NeedsOxygen && OxygenAvailable < CharacterHealth.LowOxygenThreshold; public bool IsLowInOxygen => CharacterHealth.OxygenAmount < 100;
public bool GodMode = false; public bool GodMode = false;
@@ -510,8 +510,11 @@ namespace Barotrauma
public List<Order> CurrentOrders { get; } = new List<Order>(); public List<Order> CurrentOrders { get; } = new List<Order>();
//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 /// <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.
/// </summary>
public ushort ID; public ushort ID;
public List<Identifier> SpriteTags public List<Identifier> SpriteTags
@@ -922,17 +925,25 @@ namespace Barotrauma
} }
} }
/// <summary>
/// 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.
/// </summary>
public int GetIdentifier() public int GetIdentifier()
{ {
return GetIdentifier(Name); return GetIdentifierHash(Name);
} }
/// <summary>
/// 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.
/// </summary>
public int GetIdentifierUsingOriginalName() 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))); int id = ToolBox.StringToInt(name + string.Join("", Head.Preset.TagSet.OrderBy(s => s)));
id ^= Head.HairIndex << 12; id ^= Head.HairIndex << 12;
@@ -1512,7 +1523,7 @@ namespace Barotrauma
} }
if (order.OrderGiver != null) 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) if (order.TargetSpatialEntity?.Submarine is Submarine targetSub)
{ {
@@ -1606,8 +1617,8 @@ namespace Barotrauma
continue; continue;
} }
var targetType = (Order.OrderTargetType)orderElement.GetAttributeInt("targettype", 0); var targetType = (Order.OrderTargetType)orderElement.GetAttributeInt("targettype", 0);
int orderGiverInfoId = orderElement.GetAttributeInt("ordergiverinfoid", -1); int orderGiverInfoId = orderElement.GetAttributeInt("ordergiver", -1);
var orderGiver = orderGiverInfoId >= 0 ? Character.CharacterList.FirstOrDefault(c => c.Info?.ID == orderGiverInfoId) : null; var orderGiver = orderGiverInfoId >= 0 ? Character.CharacterList.FirstOrDefault(c => c.Info?.GetIdentifier() == orderGiverInfoId) : null;
Entity targetEntity = null; Entity targetEntity = null;
switch (targetType) switch (targetType)
{ {
@@ -432,6 +432,9 @@ namespace Barotrauma
public readonly float BurnOverlayAlpha; public readonly float BurnOverlayAlpha;
public readonly float DamageOverlayAlpha; 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 //steam achievement given when the affliction is removed from the controlled character
public readonly Identifier AchievementOnRemoved; public readonly Identifier AchievementOnRemoved;
@@ -560,6 +563,7 @@ namespace Barotrauma
IconColors = element.GetAttributeColorArray(nameof(IconColors), null); IconColors = element.GetAttributeColorArray(nameof(IconColors), null);
AfflictionOverlayAlphaIsLinear = element.GetAttributeBool(nameof(AfflictionOverlayAlphaIsLinear), false); AfflictionOverlayAlphaIsLinear = element.GetAttributeBool(nameof(AfflictionOverlayAlphaIsLinear), false);
AchievementOnReceived = element.GetAttributeIdentifier(nameof(AchievementOnReceived), "");
AchievementOnRemoved = element.GetAttributeIdentifier(nameof(AchievementOnRemoved), ""); AchievementOnRemoved = element.GetAttributeIdentifier(nameof(AchievementOnRemoved), "");
TargetSpecies = element.GetAttributeIdentifierArray("targets", Array.Empty<Identifier>(), trim: true); TargetSpecies = element.GetAttributeIdentifierArray("targets", Array.Empty<Identifier>(), trim: true);
@@ -754,6 +754,7 @@ namespace Barotrauma
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))), Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))),
newAffliction.Source); newAffliction.Source);
afflictions.Add(copyAffliction, limbHealth); afflictions.Add(copyAffliction, limbHealth);
SteamAchievementManager.OnAfflictionReceived(copyAffliction, Character);
MedicalClinic.OnAfflictionCountChanged(Character); MedicalClinic.OnAfflictionCountChanged(Character);
Character.HealthUpdateInterval = 0.0f; Character.HealthUpdateInterval = 0.0f;
@@ -214,8 +214,7 @@ namespace Barotrauma
CharacterInfo characterInfo; CharacterInfo characterInfo;
if (characterElement == null) 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 else
{ {
characterInfo = new CharacterInfo(characterElement, Identifier); characterInfo = new CharacterInfo(characterElement, Identifier);
@@ -21,6 +21,9 @@ namespace Barotrauma
[Serialize(true, IsPropertySaveable.Yes)] [Serialize(true, IsPropertySaveable.Yes)]
public bool IgnoreIncapacitatedCharacters { get; set; } public bool IgnoreIncapacitatedCharacters { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
public bool AllowHiddenItems { get; set; }
private bool isFinished = false; private bool isFinished = false;
public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -139,7 +142,7 @@ namespace Barotrauma
private bool IsValidItem(Item it) private bool IsValidItem(Item it)
{ {
return !it.HiddenInGame && SubmarineTypeMatches(it.Submarine); return (!it.HiddenInGame || AllowHiddenItems) && SubmarineTypeMatches(it.Submarine);
} }
private bool SubmarineTypeMatches(Submarine sub) private bool SubmarineTypeMatches(Submarine sub)
@@ -116,7 +116,7 @@ namespace Barotrauma
{ {
foreach (Entity entity in Entity.GetEntities()) 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); targetsToReturn.Add(entity);
} }
@@ -131,7 +131,7 @@ namespace Barotrauma
{ {
foreach (Character npc in outpostNPCs) foreach (Character npc in outpostNPCs)
{ {
if (npc.Removed) { continue; } if (npc.Removed || targetsToReturn.Contains(npc)) { continue; }
targetsToReturn.Add(npc); targetsToReturn.Add(npc);
} }
} }
@@ -248,23 +248,7 @@ namespace Barotrauma
List<WayPoint> spawnWaypoints = null; List<WayPoint> spawnWaypoints = null;
List<WayPoint> mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub).ToList(); List<WayPoint> mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub).ToList();
bool hostileOutpost = false; if (Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost())
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)
{ {
spawnWaypoints = WayPoint.WayPointList.FindAll(wp => spawnWaypoints = WayPoint.WayPointList.FindAll(wp =>
wp.SpawnType == SpawnType.Human && wp.SpawnType == SpawnType.Human &&
@@ -55,10 +55,11 @@ namespace Barotrauma
{ {
DebugConsole.Log($"Set the value \"{identifier}\" to {value}"); DebugConsole.Log($"Set the value \"{identifier}\" to {value}");
SteamAchievementManager.OnCampaignMetadataSet(identifier, value, unlockClients: true);
if (!data.ContainsKey(identifier)) if (!data.ContainsKey(identifier))
{ {
data.Add(identifier, value); data.Add(identifier, value);
SteamAchievementManager.OnCampaignMetadataSet(identifier, value);
return; return;
} }
@@ -97,7 +97,7 @@ namespace Barotrauma
float probability = element.GetAttributeFloat("probability", 0.0f); float probability = element.GetAttributeFloat("probability", 0.0f);
MinProbability = element.GetAttributeFloat("minprobability", probability); MinProbability = element.GetAttributeFloat("minprobability", probability);
MaxProbability = element.GetAttributeFloat("maxprobability", 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); DisallowBetweenOtherFactionOutposts = element.GetAttributeBool(nameof(DisallowBetweenOtherFactionOutposts), false);
} }
} }
@@ -540,6 +540,8 @@ namespace Barotrauma
existingRoundSummary.ContinueButton.Visible = true; existingRoundSummary.ContinueButton.Visible = true;
} }
CharacterHUD.ClearBossProgressBars();
RoundSummary = new RoundSummary(GameMode, Missions, StartLocation, EndLocation); RoundSummary = new RoundSummary(GameMode, Missions, StartLocation, EndLocation);
if (GameMode is not TutorialMode && GameMode is not TestGameMode) 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(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); 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)); string joinedMissionNames = string.Join(", ", missions.Select(m => m.Name));
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), joinedMissionNames), Color.CadetBlue, playSound: false); GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), joinedMissionNames), Color.CadetBlue, playSound: false);
} }
else 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); GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), mission?.Name ?? TextManager.Get("None")), Color.CadetBlue, playSound: false);
} }
} }
@@ -898,7 +901,7 @@ namespace Barotrauma
TabMenu.OnRoundEnded(); TabMenu.OnRoundEnded();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb)); GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
ObjectiveManager.ResetUI(); ObjectiveManager.ResetUI();
CharacterHUD.ClearBossHealthBars(); CharacterHUD.ClearBossProgressBars();
#endif #endif
SteamAchievementManager.OnRoundEnded(this); SteamAchievementManager.OnRoundEnded(this);
@@ -175,16 +175,6 @@ namespace Barotrauma.Items.Components
} }
public bool IsClosed => !IsOpen; public bool IsClosed => !IsOpen;
/// <summary>
/// Is the door opening, but not yet fully opened? Returns false both when it's closed and when it's fully open.
/// </summary>
public bool IsOpening => IsOpen && !IsFullyOpen;
/// <summary>
/// Is the door closing, but not yet fully closed? Returns false both when the door is open and when it's fully closed.
/// </summary>
public bool IsClosing => IsClosed && !IsFullyClosed;
public bool IsFullyOpen => IsOpen && OpenState >= 1.0f; public bool IsFullyOpen => IsOpen && OpenState >= 1.0f;
public bool IsFullyClosed => IsClosed && OpenState <= 0f; public bool IsFullyClosed => IsClosed && OpenState <= 0f;
@@ -360,10 +360,12 @@ namespace Barotrauma.Items.Components
} }
var relatedItem = FindContainableItem(containedItem); var relatedItem = FindContainableItem(containedItem);
drawableContainedItems.RemoveAll(d => d.Item == containedItem);
drawableContainedItems.Add(new DrawableContainedItem(containedItem, drawableContainedItems.Add(new DrawableContainedItem(containedItem,
Hide: relatedItem?.Hide ?? false, Hide: relatedItem?.Hide ?? false,
ItemPos: relatedItem?.ItemPos, ItemPos: relatedItem?.ItemPos,
Rotation: relatedItem?.Rotation ?? 0.0f)); Rotation: relatedItem?.Rotation ?? 0.0f));
drawableContainedItems.Sort((DrawableContainedItem it1, DrawableContainedItem it2) => Inventory.FindIndex(it1.Item).CompareTo(Inventory.FindIndex(it2.Item)));
if (item.GetComponent<Planter>() != null) if (item.GetComponent<Planter>() != null)
{ {
@@ -234,6 +234,7 @@ namespace Barotrauma.Items.Components
public float RepairDegreeOfSuccess(Character character, List<Skill> skills) public float RepairDegreeOfSuccess(Character character, List<Skill> skills)
{ {
if (skills.Count == 0) { return 1.0f; } 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 skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
float average = skillSum / skills.Count; float average = skillSum / skills.Count;
@@ -243,6 +244,7 @@ namespace Barotrauma.Items.Components
public void RepairBoost(bool qteSuccess) public void RepairBoost(bool qteSuccess)
{ {
if (CurrentFixer == null) { return; }
if (qteSuccess) if (qteSuccess)
{ {
item.Condition += RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f); item.Condition += RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f);
@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Xml.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Extensions; using Barotrauma.Extensions;
#if CLIENT #if CLIENT
@@ -13,6 +12,9 @@ namespace Barotrauma.Items.Components
partial class LightComponent : Powered, IServerSerializable, IDrawableComponent partial class LightComponent : Powered, IServerSerializable, IDrawableComponent
{ {
private Color lightColor; private Color lightColor;
/// <summary>
/// The current brightness of the light source, affected by powerconsumption/voltage
/// </summary>
private float lightBrightness; private float lightBrightness;
private float blinkFrequency; private float blinkFrequency;
private float pulseFrequency, pulseAmount; private float pulseFrequency, pulseAmount;
@@ -174,7 +176,7 @@ namespace Barotrauma.Items.Components
#if CLIENT #if CLIENT
if (Light != null) if (Light != null)
{ {
Light.Color = IsOn ? lightColor.Multiply(lightBrightness) : Color.Transparent; Light.Color = IsOn ? lightColor.Multiply(lightColorMultiplier) : Color.Transparent;
} }
#endif #endif
} }
@@ -245,7 +247,7 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded() public override void OnItemLoaded()
{ {
base.OnItemLoaded(); base.OnItemLoaded();
SetLightSourceState(IsActive); SetLightSourceState(IsActive, lightBrightness);
turret = item.GetComponent<Turret>(); turret = item.GetComponent<Turret>();
#if CLIENT #if CLIENT
Drawable = AlphaBlend && Light.LightSprite != null; Drawable = AlphaBlend && Light.LightSprite != null;
@@ -279,7 +281,8 @@ namespace Barotrauma.Items.Components
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) && (statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
(IsActiveConditionals == null || IsActiveConditionals.Count == 0)) (IsActiveConditionals == null || IsActiveConditionals.Count == 0))
{ {
SetLightSourceState(true); lightBrightness = 1.0f;
SetLightSourceState(true, lightBrightness);
SetLightSourceTransformProjSpecific(); SetLightSourceTransformProjSpecific();
base.IsActive = false; base.IsActive = false;
isOn = true; isOn = true;
@@ -308,7 +311,8 @@ namespace Barotrauma.Items.Components
#endif #endif
if (item.Container != null && item.GetRootInventoryOwner() is not Character) if (item.Container != null && item.GetRootInventoryOwner() is not Character)
{ {
SetLightSourceState(false); lightBrightness = 0.0f;
SetLightSourceState(false, 0.0f);
return; return;
} }
@@ -317,7 +321,8 @@ namespace Barotrauma.Items.Components
PhysicsBody body = ParentBody ?? item.body; PhysicsBody body = ParentBody ?? item.body;
if (body != null && !body.Enabled) if (body != null && !body.Enabled)
{ {
SetLightSourceState(false); lightBrightness = 0.0f;
SetLightSourceState(false, 0.0f);
return; return;
} }
@@ -344,7 +349,7 @@ namespace Barotrauma.Items.Components
public override void UpdateBroken(float deltaTime, Camera cam) public override void UpdateBroken(float deltaTime, Camera cam)
{ {
SetLightSourceState(false); SetLightSourceState(false, 0.0f);
} }
public override bool Use(float deltaTime, Character character = null) public override bool Use(float deltaTime, Character character = null)
@@ -376,7 +381,7 @@ namespace Barotrauma.Items.Components
{ {
LightColor = XMLExtensions.ParseColor(signal.value, false); LightColor = XMLExtensions.ParseColor(signal.value, false);
#if CLIENT #if CLIENT
SetLightSourceState(Light.Enabled, lightBrightness); SetLightSourceState(Light.Enabled, lightColorMultiplier);
#endif #endif
prevColorSignal = signal.value; prevColorSignal = signal.value;
} }
@@ -394,7 +399,7 @@ namespace Barotrauma.Items.Components
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness); 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() public void SetLightSourceTransform()
{ {
@@ -36,6 +36,10 @@ namespace Barotrauma
public bool IdFreed { get; private set; } public bool IdFreed { get; private set; }
/// <summary>
/// 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.
/// </summary>
public readonly ushort ID; public readonly ushort ID;
public virtual Vector2 SimPosition => Vector2.Zero; public virtual Vector2 SimPosition => Vector2.Zero;
@@ -461,6 +461,19 @@ namespace Barotrauma
borders = new Rectangle(Point.Zero, levelData.Size); 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) public static Level Generate(LevelData levelData, bool mirror, Location startLocation, Location endLocation, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
{ {
Debug.Assert(levelData.Biome != null); Debug.Assert(levelData.Biome != null);
@@ -401,6 +401,7 @@ namespace Barotrauma
private static readonly List<MapEntity> tempHighlightedEntities = new List<MapEntity>(); private static readonly List<MapEntity> tempHighlightedEntities = new List<MapEntity>();
public static void ClearHighlightedEntities() public static void ClearHighlightedEntities()
{ {
highlightedEntities.RemoveWhere(e => e.Removed);
tempHighlightedEntities.Clear(); tempHighlightedEntities.Clear();
tempHighlightedEntities.AddRange(highlightedEntities); tempHighlightedEntities.AddRange(highlightedEntities);
foreach (var entity in tempHighlightedEntities) foreach (var entity in tempHighlightedEntities)
@@ -1379,6 +1379,24 @@ namespace Barotrauma
Info = new SubmarineInfo(info); Info = new SubmarineInfo(info);
ConnectedDockingPorts = new Dictionary<Submarine, DockingPort>(); ConnectedDockingPorts = new Dictionary<Submarine, DockingPort>();
//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(); IdOffset = IdRemap.DetermineNewOffset();
List<MapEntity> newEntities = new List<MapEntity>(); List<MapEntity> newEntities = new List<MapEntity>();
@@ -1406,6 +1424,7 @@ namespace Barotrauma
Vector2 center = Vector2.Zero; Vector2 center = Vector2.Zero;
var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this); var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this);
if (matchingHulls.Any()) if (matchingHulls.Any())
{ {
Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y); Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
@@ -1427,17 +1446,6 @@ namespace Barotrauma
} }
subBody = new SubmarineBody(this, showErrorMessages); 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); Vector2 pos = ConvertUnits.ToSimUnits(HiddenSubPosition);
subBody.Body.FarseerBody.SetTransformIgnoreContacts(ref pos, 0.0f); subBody.Body.FarseerBody.SetTransformIgnoreContacts(ref pos, 0.0f);
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
@@ -1444,7 +1444,6 @@ namespace Barotrauma
if (target == null) { continue; } if (target == null) { continue; }
foreach (Affliction affliction in Afflictions) foreach (Affliction affliction in Afflictions)
{ {
if (Rand.Value(Rand.RandSync.Unsynced) > affliction.Probability) { continue; }
Affliction newAffliction = affliction; Affliction newAffliction = affliction;
if (target is Character character) if (target is Character character)
{ {
@@ -219,10 +219,10 @@ namespace Barotrauma
UnlockAchievement($"discover{biome.Identifier.Value.Replace(" ", "")}".ToIdentifier()); 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; } 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) public static void OnItemRepaired(Item item, Character fixer)
@@ -236,6 +236,15 @@ namespace Barotrauma
UnlockAchievement(fixer, $"repair{item.Prefab.Identifier}".ToIdentifier()); 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) public static void OnAfflictionRemoved(Affliction affliction, Character character)
{ {
if (affliction.Prefab.AchievementOnRemoved.IsEmpty) { return; } if (affliction.Prefab.AchievementOnRemoved.IsEmpty) { return; }
+77 -10
View File
@@ -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 v1.0.1.0
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
@@ -56,7 +124,7 @@ Misc changes:
- Made high-quality stun guns more effective (stunning the target faster). - 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. - 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. - 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 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. - 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. - 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. - 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). - 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. - 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. - 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) - 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. - 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). - 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). - Boosted Plasma Cutter damage against doors and items (walls not touched).
- Made galena more common to make lead easier to get. - Made galena more common in order to make lead easier to get.
- Recreated all and added new slot indicator icons to provide information about the containable item restrictions.
- 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). - 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: 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 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 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. - 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. - 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 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. - 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. - 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 "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 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 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 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 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 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 inventory overlapping with the chatbox on low aspect ratios (small width, large height).
- Fixed some layering issues in abandoned outposts. - Fixed some layering issues in abandoned outposts.
- Fixed water-sensitive items sometimes spawning as loot in wrecks. - 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 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 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 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 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 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. - 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. - 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. - 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 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 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. - 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.
@@ -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<Vector2> 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<CustomGenerators>();
}
[Fact]
public void TestLocalToCanonical()
{
void testCase(Tuple<Vector2, Vector2, Vector2, Vector2> 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<Tuple<Vector2, Vector2, Vector2, Vector2>>().ToArbitrary(),
testCase).QuickCheckThrowOnFailure();
}
}
@@ -14,8 +14,7 @@ public class EndpointParseTests
{ {
Endpoint.Parse("127.0.0.1:27015") Endpoint.Parse("127.0.0.1:27015")
.Should() .Should()
.BeOfType<Some<Endpoint>>() .BeEquivalentTo(
.And.BeEquivalentTo(
Option<Endpoint>.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)), Option<Endpoint>.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)),
options => options.RespectingRuntimeTypes()); options => options.RespectingRuntimeTypes());
} }
@@ -25,8 +24,7 @@ public class EndpointParseTests
{ {
Endpoint.Parse("localhost:27015") Endpoint.Parse("localhost:27015")
.Should() .Should()
.BeOfType<Some<Endpoint>>() .BeEquivalentTo(
.And.BeEquivalentTo(
Option<Endpoint>.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)), Option<Endpoint>.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)),
options => options.RespectingRuntimeTypes()); options => options.RespectingRuntimeTypes());
} }
@@ -36,8 +34,7 @@ public class EndpointParseTests
{ {
Address.Parse("127.0.0.1") Address.Parse("127.0.0.1")
.Should() .Should()
.BeOfType<Some<Address>>() .BeEquivalentTo(
.And.BeEquivalentTo(
Option<Address>.Some(new LidgrenAddress(IPAddress.Loopback)), Option<Address>.Some(new LidgrenAddress(IPAddress.Loopback)),
options => options.RespectingRuntimeTypes()); options => options.RespectingRuntimeTypes());
} }
@@ -47,8 +44,7 @@ public class EndpointParseTests
{ {
Endpoint.Parse("STEAM_1:1:508792388") Endpoint.Parse("STEAM_1:1:508792388")
.Should() .Should()
.BeOfType<Some<Endpoint>>() .BeEquivalentTo(
.And.BeEquivalentTo(
Option<Endpoint>.Some(new SteamP2PEndpoint(new SteamId(76561198977850505))), Option<Endpoint>.Some(new SteamP2PEndpoint(new SteamId(76561198977850505))),
options => options.RespectingRuntimeTypes()); options => options.RespectingRuntimeTypes());
} }
@@ -58,8 +54,7 @@ public class EndpointParseTests
{ {
Address.Parse("STEAM_1:1:508792388") Address.Parse("STEAM_1:1:508792388")
.Should() .Should()
.BeOfType<Some<Address>>() .BeEquivalentTo(
.And.BeEquivalentTo(
Option<Address>.Some(new SteamP2PAddress(new SteamId(76561198977850505))), Option<Address>.Some(new SteamP2PAddress(new SteamId(76561198977850505))),
options => options.RespectingRuntimeTypes()); options => options.RespectingRuntimeTypes());
new SteamId(76561198977850505).StringRepresentation.Should().BeEquivalentTo("STEAM_1:1:508792388"); new SteamId(76561198977850505).StringRepresentation.Should().BeEquivalentTo("STEAM_1:1:508792388");
@@ -10,7 +10,7 @@ using Xunit;
namespace TestProject; namespace TestProject;
public class INetSerializableStructImplementationChecks public sealed class INetSerializableStructImplementationChecks
{ {
private delegate bool TryFindBehaviorDelegate(Type type, out NetSerializableProperties.IReadWriteBehavior behavior); private delegate bool TryFindBehaviorDelegate(Type type, out NetSerializableProperties.IReadWriteBehavior behavior);
@@ -49,7 +49,7 @@ public class INetSerializableStructImplementationChecks
viableArguments.AddRange(new[] viableArguments.AddRange(new[]
{ {
typeof(Vector2), typeof(Vector2),
typeof(Point), typeof(float),
typeof(int) typeof(int)
}); });
} }
@@ -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));
}
}
}
@@ -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<SerializableDateTime> 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<TestProject.CustomGenerators>();
Arb.Register<CustomGenerators>();
}
[Fact]
public void EqualityTest()
{
Prop.ForAll<SerializableDateTime>(EqualityCheck).QuickCheckThrowOnFailure();
}
[Fact]
public void ParseTest()
{
Prop.ForAll<SerializableDateTime>(ParseCheck).QuickCheckThrowOnFailure();
}
[Fact]
public void ToLocalTest()
{
Prop.ForAll<SerializableDateTime>(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);
}
}
+2 -2
View File
@@ -10,8 +10,8 @@ namespace TestProject
{ {
public static Arbitrary<Vector2> Vector2Generator() public static Arbitrary<Vector2> Vector2Generator()
{ {
return Arb.From(from int x in Arb.Generate<int>() return Arb.From(from float x in Arb.Generate<float>().Where(f => !float.IsNaN(f) && !float.IsInfinity(f))
from int y in Arb.Generate<int>() from float y in Arb.Generate<float>().Where(f => !float.IsNaN(f) && !float.IsInfinity(f))
select new Vector2(x, y)); select new Vector2(x, y));
} }