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

View File

@@ -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<Item> brokenItems = new List<Item>();
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>();
@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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)

View File

@@ -14,6 +14,13 @@ namespace Barotrauma.Items.Components
private CoroutineHandle resetPredictionCoroutine;
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
{
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);
}
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.0.1.0</Version>
<Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.0.1.0</Version>
<Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.0.1.0</Version>
<Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.0.1.0</Version>
<Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.0.1.0</Version>
<Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -91,7 +91,7 @@ namespace Barotrauma
msg.WriteByte((byte)0);
}
msg.WriteUInt16((ushort)ExperiencePoints);
msg.WriteInt32(ExperiencePoints);
msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints);
}
}

View File

@@ -2367,10 +2367,7 @@ namespace Barotrauma.Networking
List<WayPoint> spawnWaypoints = null;
List<WayPoint> 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<int>(Voting.ActiveVote.VoteType) == 2);

View File

@@ -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<int>(ActiveVote.VoteType) == 2);
int no = eligibleClients.Count(c => c.GetVote<int>(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);
}
}

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.0.1.0</Version>
<Version>1.0.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -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<AIObjectiveExtinguishFire>();
bool ignoreOxygen = character.IsProtectedFromPressure || HasDivingGear(character);
bool ignoreOxygen = HasDivingGear(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
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;
}

View File

@@ -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
{

View File

@@ -52,7 +52,7 @@ namespace Barotrauma
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
&& character.IsProtectedFromPressure ? 0 : 100;
&& ((character.IsImmuneToPressure && !character.IsLowInOxygen)|| HumanAIController.HasDivingSuit(character)) ? 0 : 100;
}
else
{

View File

@@ -465,6 +465,10 @@ namespace Barotrauma
public readonly OrderTarget TargetPosition;
private ISpatialEntity targetSpatialEntity;
/// <summary>
/// Note this property doesn't return the follow target of the Follow objective, as expected!
/// </summary>
public ISpatialEntity TargetSpatialEntity
{
get

View File

@@ -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; }
}
}

View File

@@ -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;

View File

@@ -510,8 +510,11 @@ namespace Barotrauma
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 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()
{
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()
{
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)
{

View File

@@ -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<Identifier>(), trim: true);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -248,23 +248,7 @@ namespace Barotrauma
List<WayPoint> spawnWaypoints = null;
List<WayPoint> 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())
{

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -175,16 +175,6 @@ namespace Barotrauma.Items.Components
}
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 IsFullyClosed => IsClosed && OpenState <= 0f;

View File

@@ -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<Planter>() != null)
{

View File

@@ -234,6 +234,7 @@ namespace Barotrauma.Items.Components
public float RepairDegreeOfSuccess(Character character, List<Skill> 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);

View File

@@ -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;
/// <summary>
/// The current brightness of the light source, affected by powerconsumption/voltage
/// </summary>
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<Turret>();
#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()
{

View File

@@ -36,6 +36,10 @@ namespace Barotrauma
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 virtual Vector2 SimPosition => Vector2.Zero;

View File

@@ -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);

View File

@@ -401,6 +401,7 @@ namespace Barotrauma
private static readonly List<MapEntity> tempHighlightedEntities = new List<MapEntity>();
public static void ClearHighlightedEntities()
{
highlightedEntities.RemoveWhere(e => e.Removed);
tempHighlightedEntities.Clear();
tempHighlightedEntities.AddRange(highlightedEntities);
foreach (var entity in tempHighlightedEntities)

View File

@@ -1379,6 +1379,24 @@ namespace Barotrauma
Info = new SubmarineInfo(info);
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();
List<MapEntity> newEntities = new List<MapEntity>();
@@ -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);

View File

@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
namespace Barotrauma

View File

@@ -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)
{

View File

@@ -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; }

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
---------------------------------------------------------------------------------------------------------
@@ -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.

View File

@@ -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();
}
}

View File

@@ -14,8 +14,7 @@ public class EndpointParseTests
{
Endpoint.Parse("127.0.0.1:27015")
.Should()
.BeOfType<Some<Endpoint>>()
.And.BeEquivalentTo(
.BeEquivalentTo(
Option<Endpoint>.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)),
options => options.RespectingRuntimeTypes());
}
@@ -25,8 +24,7 @@ public class EndpointParseTests
{
Endpoint.Parse("localhost:27015")
.Should()
.BeOfType<Some<Endpoint>>()
.And.BeEquivalentTo(
.BeEquivalentTo(
Option<Endpoint>.Some(new LidgrenEndpoint(IPAddress.Loopback, 27015)),
options => options.RespectingRuntimeTypes());
}
@@ -36,8 +34,7 @@ public class EndpointParseTests
{
Address.Parse("127.0.0.1")
.Should()
.BeOfType<Some<Address>>()
.And.BeEquivalentTo(
.BeEquivalentTo(
Option<Address>.Some(new LidgrenAddress(IPAddress.Loopback)),
options => options.RespectingRuntimeTypes());
}
@@ -47,8 +44,7 @@ public class EndpointParseTests
{
Endpoint.Parse("STEAM_1:1:508792388")
.Should()
.BeOfType<Some<Endpoint>>()
.And.BeEquivalentTo(
.BeEquivalentTo(
Option<Endpoint>.Some(new SteamP2PEndpoint(new SteamId(76561198977850505))),
options => options.RespectingRuntimeTypes());
}
@@ -58,8 +54,7 @@ public class EndpointParseTests
{
Address.Parse("STEAM_1:1:508792388")
.Should()
.BeOfType<Some<Address>>()
.And.BeEquivalentTo(
.BeEquivalentTo(
Option<Address>.Some(new SteamP2PAddress(new SteamId(76561198977850505))),
options => options.RespectingRuntimeTypes());
new SteamId(76561198977850505).StringRepresentation.Should().BeEquivalentTo("STEAM_1:1:508792388");

View File

@@ -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)
});
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -10,8 +10,8 @@ namespace TestProject
{
public static Arbitrary<Vector2> Vector2Generator()
{
return Arb.From(from int x in Arb.Generate<int>()
from int y in Arb.Generate<int>()
return Arb.From(from float x in Arb.Generate<float>().Where(f => !float.IsNaN(f) && !float.IsInfinity(f))
from float y in Arb.Generate<float>().Where(f => !float.IsNaN(f) && !float.IsInfinity(f))
select new Vector2(x, y));
}