Unstable 0.17.5.0

This commit is contained in:
Markus Isberg
2022-03-30 01:20:59 +09:00
parent c1b8e5a341
commit 44ded0225a
88 changed files with 2033 additions and 1430 deletions
@@ -130,7 +130,7 @@ namespace Barotrauma
}
}
private Wallet wallet = new Wallet();
private Wallet wallet;
public Wallet Wallet
{
@@ -1055,8 +1055,13 @@ namespace Barotrauma
return newCharacter;
}
private Character(Submarine submarine, ushort id): base(submarine, id)
{
wallet = new Wallet(Option<Character>.Some(this));
}
protected Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, RagdollParams ragdollParams = null)
: base(null, id)
: this(null, id)
{
this.Seed = seed;
this.Prefab = prefab;
@@ -172,11 +172,21 @@ namespace Barotrauma
SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.SteamWorkshopId == Core.SteamWorkshopId) ??
ContentPackageManager.CorePackages.First());
}
SetRegular(Regular
.Select(p => ContentPackageManager.RegularPackages.Contains(p)
? p
: ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2 => p2.SteamWorkshopId == p.SteamWorkshopId))
.ToArray());
List<RegularPackage> newRegular = new List<RegularPackage>();
foreach (var p in Regular)
{
if (ContentPackageManager.RegularPackages.Contains(p))
{
newRegular.Add(p);
}
else if (ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2
=> p2.SteamWorkshopId == p.SteamWorkshopId) is { } newP)
{
newRegular.Add(newP);
}
}
SetRegular(newRegular);
}
public static void BackUp()
@@ -115,6 +115,24 @@ namespace Barotrauma
}
public void Remove() => Element.Remove();
public override bool Equals(object? obj)
{
return obj is ContentXElement element && this == element;
}
public override int GetHashCode()
{
return HashCode.Combine(ContentPackage, Element);
}
public static bool operator ==(in ContentXElement? a, in ContentXElement? b)
{
return a?.ContentPackage == b?.ContentPackage && a?.Element == b?.Element;
}
public static bool operator !=(in ContentXElement? a, in ContentXElement? b) =>
!(a == b);
}
public static class ContentXElementExtensions
@@ -1,4 +1,6 @@
namespace Barotrauma
using System;
namespace Barotrauma
{
public enum TransitionMode
{
@@ -146,4 +148,11 @@
AlwaysStayConscious,
}
[Flags]
public enum CharacterType
{
Bot = 0b01,
Player = 0b10,
Both = Bot | Player
}
}
@@ -350,7 +350,7 @@ namespace Barotrauma
float difficultyMultiplier = 1 + level.Difficulty / 100f;
baseExperienceGain *= difficultyMultiplier;
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters();
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
// use multipliers here so that we can easily add them together without introducing multiplicative XP stacking
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
@@ -380,7 +380,7 @@ namespace Barotrauma
GameAnalyticsManager.AddMoneyGainedEvent(totalReward, GameAnalyticsManager.MoneySource.MissionReward, Prefab.Identifier.Value);
#if SERVER
totalReward = DistributeRewardsToCrew(GetSalaryEligibleCrew(), totalReward);
totalReward = DistributeRewardsToCrew(GameSession.GetSessionCrewCharacters(CharacterType.Player), totalReward);
#endif
if (totalReward > 0)
{
@@ -436,18 +436,6 @@ namespace Barotrauma
}
#endif
public static IEnumerable<Character> GetSalaryEligibleCrew()
{
if (!(GameMain.GameSession.CrewManager is { } crewManager)) { return Array.Empty<Character>(); }
IEnumerable<Character> characters = crewManager.GetCharacters();
#if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead).Concat(characters);
#elif CLIENT
return characters;
#endif
}
public static int GetRewardDistibutionSum(IEnumerable<Character> crew, int rewardDistribution = 0) => crew.Sum(c => c.Wallet.RewardDistribution) + rewardDistribution;
@@ -95,7 +95,7 @@ namespace Barotrauma
public readonly bool IsSideObjective;
public readonly bool RequireWreck;
public readonly bool RequireWreck, RequireRuin;
/// <summary>
/// The mission can only be received when travelling from a location of the first type to a location of the second type
@@ -152,6 +152,7 @@ namespace Barotrauma
AllowRetry = element.GetAttributeBool("allowretry", false);
IsSideObjective = element.GetAttributeBool("sideobjective", false);
RequireWreck = element.GetAttributeBool("requirewreck", false);
RequireRuin = element.GetAttributeBool("requireruin", false);
Commonness = element.GetAttributeInt("commonness", 1);
if (element.GetAttribute("difficulty") != null)
{
@@ -226,6 +226,7 @@ namespace Barotrauma
public void SetPurchasedItems(Dictionary<Identifier, List<PurchasedItem>> purchasedItems)
{
if (purchasedItems.Count == 0 && PurchasedItems.Count == 0) { return; }
PurchasedItems.Clear();
foreach (var entry in purchasedItems)
{
@@ -64,7 +64,7 @@ namespace Barotrauma
if (reputationChange > 0f)
{
float reputationGainMultiplier = 1f;
foreach (Character character in GameSession.GetSessionCrewCharacters())
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier);
}
@@ -6,6 +6,7 @@ namespace Barotrauma
{
internal readonly struct WalletChangedEvent
{
public readonly Option<Character> Owner;
public readonly Wallet Wallet;
public readonly WalletInfo Info;
public readonly WalletChangedData ChangedData;
@@ -15,6 +16,7 @@ namespace Barotrauma
Wallet = wallet;
Info = info;
ChangedData = changedData;
Owner = wallet.Owner;
}
}
@@ -109,6 +111,8 @@ namespace Barotrauma
// ReSharper disable ValueParameterNotUsed
internal sealed class InvalidWallet : Wallet
{
public InvalidWallet(): base(Option<Character>.None()) { }
public override int Balance
{
get => 0;
@@ -132,6 +136,8 @@ namespace Barotrauma
AttrubuteNameRewardDistribution = "rewarddistribution",
SaveElementName = "Wallet";
public readonly Option<Character> Owner;
private int balance;
public virtual int Balance
@@ -148,9 +154,12 @@ namespace Barotrauma
set => rewardDistribution = ClampRewardDistribution(value);
}
public Wallet() { }
public Wallet(Option<Character> owner)
{
Owner = owner;
}
public Wallet(XElement element)
public Wallet(Option<Character> owner, XElement element): this(owner)
{
balance = ClampBalance(element.GetAttributeInt(AttributeNameBalance, 0));
rewardDistribution = ClampBalance(element.GetAttributeInt(AttrubuteNameRewardDistribution, 0));
@@ -185,7 +194,7 @@ namespace Barotrauma
SettingsChanged(balanceChanged: Option<int>.Some(-price), rewardChanged: Option<int>.None());
}
public void SetRewardDistrubiton(int value)
public void SetRewardDistribution(int value)
{
int oldValue = RewardDistribution;
RewardDistribution = value;
@@ -53,7 +53,7 @@ namespace Barotrauma
public int GetAddedMissionCount()
{
int count = 0;
foreach (Character character in GameSession.GetSessionCrewCharacters())
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
count += (int)character.GetStatValue(StatTypes.ExtraMissionCount);
}
@@ -68,6 +68,15 @@ namespace Barotrauma
abstract partial class CampaignMode : GameMode
{
[NetworkSerialize]
public struct SaveInfo : INetSerializableStruct
{
public string FilePath;
public int SaveTime;
public string SubmarineName;
public string[] EnabledContentPackageNames;
}
public const int MaxMoney = int.MaxValue / 2; //about 1 billion
public const int InitialMoney = 8500;
@@ -180,13 +189,37 @@ namespace Barotrauma
protected CampaignMode(GameModePreset preset)
: base(preset)
{
Bank = new Wallet
Bank = new Wallet(Option<Character>.None())
{
Balance = InitialMoney
};
CargoManager = new CargoManager(this);
MedicalClinic = new MedicalClinic(this);
Identifier messageIdentifier = new Identifier("money");
#if CLIENT
OnMoneyChanged.RegisterOverwriteExisting(new Identifier("CampaignMoneyChangeNotification"), e =>
{
if (!(e.ChangedData.BalanceChanged is Some<int> { Value: var changed })) { return; }
bool isGain = changed > 0;
Color clr = isGain ? GUIStyle.Yellow : GUIStyle.Red;
switch (e.Owner)
{
case Some<Character> { Value: var owner}:
owner.AddMessage(FormatMessage(), clr, playSound: Character.Controlled == owner, messageIdentifier, changed);
break;
case None<Character> _ when IsSinglePlayer:
Character.Controlled?.AddMessage(FormatMessage(), clr, playSound: true, messageIdentifier, changed);
break;
}
string FormatMessage() => TextManager.GetWithVariable(isGain ? "moneygainformat" : "moneyloseformat", "[money]", TextManager.FormatCurrency(Math.Abs(changed))).ToString();
});
#endif
}
public virtual Wallet GetWallet(Client client = null)
@@ -167,7 +167,7 @@ namespace Barotrauma
LoadStats(subElement);
break;
case Wallet.LowerCaseSaveElementName:
Bank = new Wallet(subElement);
Bank = new Wallet(Option<Character>.None(), subElement);
break;
#if SERVER
case "savedexperiencepoints":
@@ -183,7 +183,7 @@ namespace Barotrauma
int oldMoney = element.GetAttributeInt("money", 0);
if (oldMoney > 0)
{
Bank = new Wallet
Bank = new Wallet(Option<Character>.None())
{
Balance = oldMoney
};
@@ -735,14 +735,40 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime);
public static IEnumerable<Character> GetSessionCrewCharacters()
/// <summary>
/// Returns a list of crew characters currently in the game with a given filter.
/// </summary>
/// <param name="type">Character type filter</param>
/// <returns></returns>
/// <remarks>
/// In singleplayer mode the CharacterType.Player returns the currently controlled player.
/// </remarks>
public static ImmutableHashSet<Character> GetSessionCrewCharacters(CharacterType type)
{
if (!(GameMain.GameSession.CrewManager is { } crewManager)) { return ImmutableHashSet<Character>.Empty; }
IEnumerable<Character> players;
IEnumerable<Character> bots;
HashSet<Character> characters = new HashSet<Character>();
#if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead);
#else
if (GameMain.GameSession?.CrewManager is null) { return Enumerable.Empty<Character>(); }
return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null && !c.IsDead);
players = GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead);
bots = crewManager.GetCharacters().Where(c => !c.IsRemotePlayer);
#elif CLIENT
players = crewManager.GetCharacters().Where(c => c.IsPlayer);
bots = crewManager.GetCharacters().Where(c => c.IsBot);
#endif
if (type.HasFlag(CharacterType.Bot))
{
foreach (Character bot in bots) { characters.Add(bot); }
}
if (type.HasFlag(CharacterType.Player))
{
foreach (Character player in players) { characters.Add(player); }
}
return characters.ToImmutableHashSet();
}
public void EndRound(string endMessage, List<TraitorMissionResult>? traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
@@ -754,7 +780,7 @@ namespace Barotrauma
try
{
ImmutableArray<Character> crewCharacters = GetSessionCrewCharacters().ToImmutableArray();
ImmutableHashSet<Character> crewCharacters = GetSessionCrewCharacters(CharacterType.Both);
int prevMoney = GetAmountOfMoney(crewCharacters);
@@ -898,7 +924,7 @@ namespace Barotrauma
}
}
foreach (Character c in GetSessionCrewCharacters())
foreach (Character c in GetSessionCrewCharacters(CharacterType.Both))
{
foreach (var itemSelectedDuration in c.ItemSelectedDurations)
{
@@ -948,25 +974,25 @@ namespace Barotrauma
#endif
}
public static bool IsCompatibleWithEnabledContentPackages(IList<string> contentPackagePaths, out LocalizedString errorMsg)
public static bool IsCompatibleWithEnabledContentPackages(IList<string> contentPackageNames, out LocalizedString errorMsg)
{
errorMsg = "";
//no known content packages, must be an older save file
if (!contentPackagePaths.Any()) { return true; }
if (!contentPackageNames.Any()) { return true; }
List<string> missingPackages = new List<string>();
foreach (string packagePath in contentPackagePaths)
foreach (string packageName in contentPackageNames)
{
if (!ContentPackageManager.EnabledPackages.All.Any(cp => cp.Path == packagePath))
if (!ContentPackageManager.EnabledPackages.All.Any(cp => cp.NameMatches(packageName)))
{
missingPackages.Add(packagePath);
missingPackages.Add(packageName);
}
}
List<string> excessPackages = new List<string>();
foreach (ContentPackage cp in ContentPackageManager.EnabledPackages.All)
{
if (!cp.HasMultiplayerSyncedContent) { continue; }
if (!contentPackagePaths.Any(p => p == cp.Path))
if (!contentPackageNames.Any(p => cp.NameMatches(p)))
{
excessPackages.Add(cp.Name);
}
@@ -976,9 +1002,9 @@ namespace Barotrauma
if (missingPackages.Count == 0 && missingPackages.Count == 0)
{
var enabledPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).ToImmutableArray();
for (int i = 0; i < contentPackagePaths.Count && i < enabledPackages.Length; i++)
for (int i = 0; i < contentPackageNames.Count && i < enabledPackages.Length; i++)
{
if (contentPackagePaths[i] != enabledPackages[i].Path)
if (!enabledPackages[i].NameMatches(contentPackageNames[i]))
{
orderMismatch = true;
break;
@@ -1009,7 +1035,7 @@ namespace Barotrauma
if (orderMismatch)
{
if (!errorMsg.IsNullOrEmpty()) { errorMsg += "\n"; }
errorMsg += TextManager.GetWithVariable("campaignmode.contentpackageordermismatch", "[loadorder]", string.Join(", ", contentPackagePaths));
errorMsg += TextManager.GetWithVariable("campaignmode.contentpackageordermismatch", "[loadorder]", string.Join(", ", contentPackageNames));
}
return false;
@@ -1040,8 +1066,8 @@ namespace Barotrauma
}
}
if (Map != null) { rootElement.Add(new XAttribute("mapseed", Map.Seed)); }
rootElement.Add(new XAttribute("selectedcontentpackages",
string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).Select(cp => cp.Path))));
rootElement.Add(new XAttribute("selectedcontentpackagenames",
string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).Select(cp => cp.Name.Replace("|", @"\|")))));
((CampaignMode)GameMode).Save(doc.Root);
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Items.Components
{
@@ -13,6 +12,7 @@ namespace Barotrauma.Items.Components
{
[Editable]
public LimbType LimbType { get; set; }
[Editable]
public Vector2 Position { get; set; }
@@ -33,7 +33,7 @@ namespace Barotrauma.Items.Components
partial class Controller : ItemComponent, IServerSerializable
{
//where the limbs of the user should be positioned when using the controller
private readonly List<LimbPos> limbPositions;
private readonly List<LimbPos> limbPositions = new List<LimbPos>();
private Direction dir;
@@ -117,38 +117,9 @@ namespace Barotrauma.Items.Components
public Controller(Item item, ContentXElement element)
: base(item, element)
{
limbPositions = new List<LimbPos>();
userPos = element.GetAttributeVector2("UserPos", Vector2.Zero);
Enum.TryParse(element.GetAttributeString("direction", "None"), out dir);
foreach (var subElement in element.Elements())
{
if (subElement.Name != "limbposition") { continue; }
string limbStr = subElement.GetAttributeString("limb", "");
if (!Enum.TryParse(subElement.GetAttribute("limb").Value, out LimbType limbType))
{
DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type.");
}
else
{
LimbPos limbPos = new LimbPos(limbType,
subElement.GetAttributeVector2("position", Vector2.Zero),
subElement.GetAttributeBool("allowusinglimb", false));
limbPositions.Add(limbPos);
if (!limbPos.AllowUsingLimb)
{
if (limbType == LimbType.RightHand || limbType == LimbType.RightForearm || limbType == LimbType.RightArm ||
limbType == LimbType.LeftHand || limbType == LimbType.LeftForearm || limbType == LimbType.LeftArm)
{
AllowAiming = false;
}
}
}
}
LoadLimbPositions(element);
IsActive = true;
}
@@ -529,5 +500,63 @@ namespace Barotrauma.Items.Components
}
partial void HideHUDs(bool value);
public override XElement Save(XElement parentElement)
{
return SaveLimbPositions(base.Save(parentElement));
}
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues, idRemap);
if (GameMain.GameSession?.GameMode?.Preset == GameModePreset.TestMode)
{
LoadLimbPositions(componentElement);
}
}
private XElement SaveLimbPositions(XElement element)
{
if (Screen.Selected == GameMain.SubEditorScreen)
{
foreach (var limbPos in limbPositions)
{
element.Add(new XElement("limbposition",
new XAttribute("limb", limbPos.LimbType),
new XAttribute("position", XMLExtensions.Vector2ToString(limbPos.Position)),
new XAttribute("allowusinglimb", limbPos.AllowUsingLimb)));
}
}
return element;
}
private void LoadLimbPositions(XElement element)
{
limbPositions.Clear();
foreach (var subElement in element.Elements())
{
if (subElement.Name != "limbposition") { continue; }
string limbStr = subElement.GetAttributeString("limb", "");
if (!Enum.TryParse(subElement.GetAttribute("limb").Value, out LimbType limbType))
{
DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type.");
}
else
{
LimbPos limbPos = new LimbPos(limbType,
subElement.GetAttributeVector2("position", Vector2.Zero),
subElement.GetAttributeBool("allowusinglimb", false));
limbPositions.Add(limbPos);
if (!limbPos.AllowUsingLimb)
{
if (limbType == LimbType.RightHand || limbType == LimbType.RightForearm || limbType == LimbType.RightArm ||
limbType == LimbType.LeftHand || limbType == LimbType.LeftForearm || limbType == LimbType.LeftArm)
{
AllowAiming = false;
}
}
}
}
}
}
}
@@ -80,6 +80,8 @@ namespace Barotrauma.Items.Components
private float progressState;
private readonly Dictionary<uint, int> fabricationLimits = new Dictionary<uint, int>();
public Fabricator(Item item, ContentXElement element)
: base(item, element)
{
@@ -105,6 +107,10 @@ namespace Barotrauma.Items.Components
}
}
fabricationRecipes.Add(recipe.RecipeHash, recipe);
if (recipe.FabricationLimitMax >= 0)
{
fabricationLimits.Add(recipe.RecipeHash, Rand.Range(recipe.FabricationLimitMin, recipe.FabricationLimitMax + 1));
}
}
}
this.fabricationRecipes = fabricationRecipes.ToImmutableDictionary();
@@ -244,7 +250,7 @@ namespace Barotrauma.Items.Components
itemList.Enabled = true;
if (activateButton != null)
{
activateButton.Text = TextManager.Get("FabricatorCreate");
activateButton.Text = TextManager.Get(CreateButtonText);
}
#endif
fabricatedItem = null;
@@ -400,8 +406,22 @@ namespace Barotrauma.Items.Components
quality = GetFabricatedItemQuality(fabricatedItem, user);
}
int amount = (int)fabricationitemAmount.Value;
if (fabricationLimits.ContainsKey(fabricatedItem.RecipeHash))
{
if (amount > fabricationLimits[fabricatedItem.RecipeHash])
{
amount = fabricationLimits[fabricatedItem.RecipeHash];
fabricationLimits[fabricatedItem.RecipeHash] = 0;
}
else
{
fabricationLimits[fabricatedItem.RecipeHash] -= amount;
}
}
var tempUser = user;
for (int i = 0; i < (int)fabricationitemAmount.Value; i++)
for (int i = 0; i < amount; i++)
{
float outCondition = fabricatedItem.OutCondition;
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + fabricatedItem.TargetItem.Identifier);
@@ -535,6 +555,11 @@ namespace Barotrauma.Items.Components
}
}
if (fabricationLimits.TryGetValue(fabricableItem.RecipeHash, out int amount) && amount <= 0)
{
return false;
}
return fabricableItem.RequiredItems.All(requiredItem =>
{
int availablePrefabsAmount = 0;
@@ -698,7 +723,6 @@ namespace Barotrauma.Items.Components
componentElement.Add(new XAttribute("fabricateditemidentifier", fabricatedItem.TargetItem.Identifier));
componentElement.Add(new XAttribute("savedtimeuntilready", timeUntilReady.ToString("G", CultureInfo.InvariantCulture)));
componentElement.Add(new XAttribute("savedrequiredtime", requiredTime.ToString("G", CultureInfo.InvariantCulture)));
}
return componentElement;
}
@@ -5,6 +5,7 @@ using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -114,7 +115,7 @@ namespace Barotrauma
private readonly Quality qualityComponent;
private readonly Queue<float> impactQueue = new Queue<float>();
private readonly ConcurrentQueue<float> impactQueue = new ConcurrentQueue<float>();
//a dictionary containing lists of the status effects in all the components of the item
private readonly bool[] hasStatusEffectsOfType;
@@ -1695,9 +1696,8 @@ namespace Barotrauma
public override void Update(float deltaTime, Camera cam)
{
while (impactQueue.Count > 0)
while (impactQueue.TryDequeue(out float impact))
{
float impact = impactQueue.Dequeue();
HandleCollision(impact);
}
@@ -1933,10 +1933,7 @@ namespace Barotrauma
if (contact.FixtureA.Body == f1.Body) { normal = -normal; }
float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal);
lock (impactQueue)
{
impactQueue.Enqueue(impact);
}
impactQueue.Enqueue(impact);
return true;
}
@@ -119,6 +119,11 @@ namespace Barotrauma
public readonly uint RecipeHash;
public readonly int Amount;
/// <summary>
/// How many of this item the fabricator can create (< 0 = unlimited)
/// </summary>
public readonly int FabricationLimitMin, FabricationLimitMax;
public FabricationRecipe(XElement element, Identifier itemPrefab)
{
TargetItemPrefabIdentifier = itemPrefab;
@@ -141,6 +146,10 @@ namespace Barotrauma
RequiresRecipe = element.GetAttributeBool("requiresrecipe", false);
Amount = element.GetAttributeInt("amount", 1);
int limitDefault = element.GetAttributeInt("fabricationlimit", -1);
FabricationLimitMin = element.GetAttributeInt(nameof(FabricationLimitMin), limitDefault);
FabricationLimitMax = element.GetAttributeInt(nameof(FabricationLimitMax), limitDefault);
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -973,7 +982,11 @@ namespace Barotrauma
public int? GetMinPrice()
{
int? minPrice = StorePrices.Values.Min(p => p.Price);
int? minPrice = null;
if (StorePrices != null && StorePrices.Any())
{
minPrice = StorePrices.Values.Min(p => p.Price);
}
if (minPrice.HasValue)
{
if (DefaultPrice != null)
@@ -831,7 +831,13 @@ namespace Barotrauma
}
List<Point> ruinPositions = new List<Point>();
for (int i = 0; i < GenerationParams.RuinCount; i++)
int ruinCount = GenerationParams.RuinCount;
if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireRuin) ?? false)
{
ruinCount = Math.Max(ruinCount, 1);
}
for (int i = 0; i < ruinCount; i++)
{
Point ruinSize = new Point(5000);
int limitLeft = Math.Max(startPosition.X, ruinSize.X / 2);
@@ -387,14 +387,14 @@ namespace Barotrauma
set;
}
[Serialize(3, IsPropertySaveable.Yes, description: "Minimum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
[Serialize(10, IsPropertySaveable.Yes, description: "Minimum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int AbyssResourceClustersMin
{
get;
set;
}
[Serialize(20, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
[Serialize(50, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int AbyssResourceClustersMax
{
get;
@@ -257,7 +257,7 @@ namespace Barotrauma
}
DailySpecials.Clear();
int extraSpecialSalesCount = Location.GetExtraSpecialSalesCount();
for (int i = 0; i < DailySpecialsCount + extraSpecialSalesCount; i++)
for (int i = 0; i < Location.DailySpecialsCount + extraSpecialSalesCount; i++)
{
if (availableStock.None()) { break; }
var item = ToolBox.SelectWeightedRandom(availableStock.Keys.ToList(), availableStock.Values.ToList(), Rand.RandSync.Unsynced);
@@ -266,7 +266,7 @@ namespace Barotrauma
availableStock.Remove(item);
}
RequestedGoods.Clear();
for (int i = 0; i < RequestedGoodsCount; i++)
for (int i = 0; i < Location.RequestedGoodsCount; i++)
{
var item = ItemPrefab.Prefabs.GetRandom(p =>
p.CanBeSold && !RequestedGoods.Contains(p) &&
@@ -359,8 +359,8 @@ namespace Barotrauma
/// How many map progress steps it takes before the discounts should be updated.
/// </summary>
private const int SpecialsUpdateInterval = 3;
private const int DailySpecialsCount = 3;
private const int RequestedGoodsCount = 3;
private int DailySpecialsCount => Type.DailySpecialsCount;
private int RequestedGoodsCount => Type.RequestedGoodsCount;
private int StepsSinceSpecialsUpdated { get; set; }
public HashSet<Identifier> StoreIdentifiers { get; } = new HashSet<Identifier>();
@@ -1226,7 +1226,7 @@ namespace Barotrauma
public int GetExtraSpecialSalesCount()
{
var characters = GameSession.GetSessionCrewCharacters();
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
if (!characters.Any()) { return 0; }
return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
}
@@ -1252,7 +1252,7 @@ namespace Barotrauma
Discovered = true;
if (checkTalents)
{
GameSession.GetSessionCrewCharacters().ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
}
}
@@ -1,13 +1,11 @@
using Microsoft.Xna.Framework;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using System.Collections.Immutable;
namespace Barotrauma
{
@@ -21,8 +19,9 @@ namespace Barotrauma
//<name, commonness>
private readonly ImmutableArray<(Identifier Name, float Commonness)> hireableJobs;
private readonly float totalHireableWeight;
public Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
public readonly Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
public readonly Dictionary<int, int> MinCountPerZone = new Dictionary<int, int>();
public readonly LocalizedString Name;
@@ -65,7 +64,7 @@ namespace Barotrauma
get;
private set;
}
public string ReplaceInRadiation { get; }
public Sprite Sprite { get; private set; }
@@ -86,6 +85,8 @@ namespace Barotrauma
/// In percentages
/// </summary>
public int StorePriceModifierRange { get; } = 5;
public int DailySpecialsCount { get; } = 1;
public int RequestedGoodsCount { get; } = 1;
public List<StoreBalanceStatus> StoreBalanceStatuses { get; } = new List<StoreBalanceStatus>()
{
@@ -144,7 +145,7 @@ namespace Barotrauma
names = new List<string>() { "Name file not found" };
}
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", new string[] { "" });
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", Array.Empty<string>());
foreach (string commonnessPerZoneStr in commonnessPerZoneStrs)
{
string[] splitCommonnessPerZone = commonnessPerZoneStr.Split(':');
@@ -152,12 +153,26 @@ namespace Barotrauma
!int.TryParse(splitCommonnessPerZone[0].Trim(), out int zoneIndex) ||
!float.TryParse(splitCommonnessPerZone[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float zoneCommonness))
{
DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone0index: zone0commonness, zone1index: zone1commonness\"");
DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone1index: zone1commonness, zone2index: zone2commonness\"");
break;
}
CommonnessPerZone[zoneIndex] = zoneCommonness;
}
string[] minCountPerZoneStrs = element.GetAttributeStringArray("mincountperzone", Array.Empty<string>());
foreach (string minCountPerZoneStr in minCountPerZoneStrs)
{
string[] splitMinCountPerZone = minCountPerZoneStr.Split(':');
if (splitMinCountPerZone.Length != 2 ||
!int.TryParse(splitMinCountPerZone[0].Trim(), out int zoneIndex) ||
!int.TryParse(splitMinCountPerZone[1].Trim(), out int minCount))
{
DebugConsole.ThrowError("Failed to read minimum count values for location type \"" + Identifier + "\" - minimum counts should be given in the format \"zone1index: zone1mincount, zone2index: zone2mincount\"");
break;
}
MinCountPerZone[zoneIndex] = minCount;
}
var hireableJobs = new List<(Identifier, float)>();
foreach (var subElement in element.Elements())
{
@@ -205,6 +220,8 @@ namespace Barotrauma
StoreBalanceStatuses.Add(new StoreBalanceStatus(percentage, modifier, color));
}
}
DailySpecialsCount = subElement.GetAttributeInt("dailyspecialscount", DailySpecialsCount);
RequestedGoodsCount = subElement.GetAttributeInt("requestedgoodscount", RequestedGoodsCount);
break;
}
}
@@ -218,16 +218,24 @@ namespace Barotrauma
foreach (Location location in Locations)
{
if (location.Type.Identifier != "city" &&
location.Type.Identifier != "outpost")
{
continue;
}
if (location.Type.Identifier != "outpost") { continue; }
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
}
}
//if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost
if (CurrentLocation == null)
{
foreach (Location location in Locations)
{
if (!location.Type.HasOutpost) { continue; }
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
}
}
}
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
CurrentLocation.Discover(true);
@@ -273,6 +281,7 @@ namespace Barotrauma
}
voronoiSites.Clear();
Dictionary<int, List<Location>> locationsPerZone = new Dictionary<int, List<Location>>();
foreach (GraphEdge edge in edges)
{
if (edge.Point1 == edge.Point2) { continue; }
@@ -301,7 +310,24 @@ namespace Barotrauma
Vector2 position = points[positionIndex];
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; }
int zone = GetZoneIndex(position.X);
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.ServerAndClient), requireOutpost: false, existingLocations: Locations);
if (!locationsPerZone.ContainsKey(zone))
{
locationsPerZone[zone] = new List<Location>();
}
LocationType forceLocationType = null;
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
{
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
{
forceLocationType = locationType;
break;
}
}
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.ServerAndClient),
requireOutpost: false, forceLocationType: forceLocationType, existingLocations: Locations);
locationsPerZone[zone].Add(newLocations[i]);
Locations.Add(newLocations[i]);
}
@@ -448,8 +474,7 @@ namespace Barotrauma
Connections[i].Locations[1];
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier == "abandoned")
{
#warning TODO: determinism?
leftMostLocation.ChangeType(LocationType.Prefabs.First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
leftMostLocation.ChangeType(LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
}
leftMostLocation.IsGateBetweenBiomes = true;
Connections[i].Locked = true;
@@ -752,6 +752,7 @@ namespace Barotrauma
bool solutionFound = false;
foreach (PlacedModule module in movableModules)
{
if (module.ThisGap.ConnectedDoor == null && module.PreviousGap.ConnectedDoor == null) { continue; }
Vector2 moveDir = GetMoveDir(module.ThisGapPosition);
Vector2 moveStep = moveDir * 50.0f;
Vector2 currentMove = Vector2.Zero;
@@ -1093,6 +1094,10 @@ namespace Barotrauma
}
thisWayPoint.Remove();
}
else
{
DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.PreviousModule.Info.Name}\".");
}
gapToRemove.ConnectedDoor?.Item.Remove();
if (hallwayLength <= 1.0f) { gapToRemove?.Remove(); }
@@ -28,8 +28,6 @@ namespace Barotrauma
partial class SubmarineInfo : IDisposable
{
public const string SavePath = "Submarines";
private static List<SubmarineInfo> savedSubmarines = new List<SubmarineInfo>();
public static IEnumerable<SubmarineInfo> SavedSubmarines => savedSubmarines;
@@ -578,58 +576,14 @@ namespace Barotrauma
if (File.Exists(savedSubmarines[i].FilePath))
{
bool isDownloadedSub = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
bool isInSubmarinesFolder = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SavePath);
bool isInContentPackage = contentPackageSubs.Any(f => f.Path == savedSubmarines[i].FilePath);
if (isDownloadedSub) { continue; }
if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && (isInSubmarinesFolder || isInContentPackage)) { continue; }
if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && isInContentPackage) { continue; }
}
savedSubmarines[i].Dispose();
}
if (!Directory.Exists(SavePath))
{
try
{
Directory.CreateDirectory(SavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Directory \"" + SavePath + "\" not found and creating the directory failed.", e);
return;
}
}
List<string> filePaths;
string[] subDirectories;
try
{
filePaths = Directory.GetFiles(SavePath).ToList();
subDirectories = Directory.GetDirectories(SavePath).Where(s =>
{
DirectoryInfo dir = new DirectoryInfo(s);
return !dir.Attributes.HasFlag(System.IO.FileAttributes.Hidden) && !dir.Name.StartsWith(".");
}).ToArray();
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open directory \"" + SavePath + "\"!", e);
return;
}
foreach (string subDirectory in subDirectories)
{
try
{
filePaths.AddRange(Directory.GetFiles(subDirectory).ToList());
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open subdirectory \"" + subDirectory + "\"!", e);
return;
}
}
List<string> filePaths = new List<string>();
foreach (BaseSubFile subFile in contentPackageSubs)
{
if (!filePaths.Any(fp => fp == subFile.Path))
@@ -643,34 +597,7 @@ namespace Barotrauma
foreach (string path in filePaths)
{
var subInfo = new SubmarineInfo(path);
if (subInfo.IsFileCorrupted)
{
#if CLIENT
if (DebugConsole.IsOpen) { DebugConsole.Toggle(); }
var deleteSubPrompt = new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariable("SubLoadError", "[subname]", subInfo.Name) + "\n" +
TextManager.GetWithVariable("DeleteFileVerification", "[filename]", subInfo.Name),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
string filePath = path;
deleteSubPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
try
{
File.Delete(filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to delete file \"{filePath}\".", e);
}
deleteSubPrompt.Close();
return true;
};
deleteSubPrompt.Buttons[1].OnClicked += deleteSubPrompt.Close;
#endif
}
else
if (!subInfo.IsFileCorrupted)
{
savedSubmarines.Add(subInfo);
}
@@ -22,11 +22,12 @@ namespace Barotrauma.Networking
ManageSettings = 0x200,
ManagePermissions = 0x400,
KarmaImmunity = 0x800,
BuyItems = 0x1000,
ManageMoney = 0x1000,
SellInventoryItems = 0x2000,
SellSubItems = 0x4000,
CampaignStore = 0x8000,
All = 0xFFFF
ManageMap = 0x8000,
ManageHires = 0x10000,
All = 0x1FFFF
}
class PermissionPreset
@@ -116,7 +116,8 @@ namespace Barotrauma.Networking
StartRound,
PurchaseAndSwitchSub,
PurchaseSub,
SwitchSub
SwitchSub,
TransferMoney
}
public enum ReadyCheckState
@@ -179,11 +180,11 @@ namespace Barotrauma.Networking
protected ServerSettings serverSettings;
public Voting Voting { get; protected set; }
protected TimeSpan updateInterval;
protected DateTime updateTimer;
public int EndVoteCount, EndVoteMax, SubmarineVoteYesCount, SubmarineVoteNoCount, SubmarineVoteMax;
protected bool gameStarted;
protected RespawnManager respawnManager;
@@ -278,8 +278,6 @@ namespace Barotrauma.Networking
{
ServerLog = new ServerLog(serverName);
Voting = new Voting();
Whitelist = new WhiteList();
BanList = new BanList();
@@ -378,8 +376,6 @@ namespace Barotrauma.Networking
public ServerLog ServerLog;
public Voting Voting;
public Dictionary<Identifier, bool> MonsterEnabled { get; private set; }
public const int MaxExtraCargoItemsOfType = 10;
@@ -577,34 +573,20 @@ namespace Barotrauma.Networking
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowVoteKick
{
get
{
return Voting.AllowVoteKick;
}
set
{
Voting.AllowVoteKick = value;
}
get; set;
}
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowEndVoting
{
get
{
return Voting.AllowEndVoting;
}
set
{
Voting.AllowEndVoting = value;
}
get; set;
}
private bool allowRespawn;
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowRespawn
{
get { return allowRespawn; ; }
get { return allowRespawn; }
set
{
if (allowRespawn == value) { return; }
@@ -779,7 +761,7 @@ namespace Barotrauma.Networking
set
{
subSelectionMode = value;
Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote;
AllowSubVoting = subSelectionMode == SelectionMode.Vote;
ServerDetailsChanged = true;
}
}
@@ -792,7 +774,7 @@ namespace Barotrauma.Networking
set
{
modeSelectionMode = value;
Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
ServerDetailsChanged = true;
}
}
@@ -807,14 +789,14 @@ namespace Barotrauma.Networking
}
[Serialize(0.6f, IsPropertySaveable.Yes)]
public float SubmarineVoteRequiredRatio
public float VoteRequiredRatio
{
get;
private set;
}
[Serialize(30f, IsPropertySaveable.Yes)]
public float SubmarineVoteTimeout
public float VoteTimeout
{
get;
private set;
@@ -928,6 +910,59 @@ namespace Barotrauma.Networking
set { maxMissionCount = MathHelper.Clamp(value, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); }
}
private bool allowSubVoting;
//Don't serialize: the value is set based on SubSelectionMode
public bool AllowSubVoting
{
get { return allowSubVoting; }
set
{
if (value == allowSubVoting) { return; }
allowSubVoting = value;
#if CLIENT
GameMain.NetLobbyScreen.SubList.Enabled = value ||
(GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.SelectSub));
var subVotesLabel = GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true) as GUITextBlock;
subVotesLabel.Visible = value;
var subVisButton = GameMain.NetLobbyScreen.SubVisibilityButton;
subVisButton.RectTransform.AbsoluteOffset
= new Point(value ? (int)(subVotesLabel.TextSize.X + subVisButton.Rect.Width) : 0, 0);
GameMain.Client?.Voting.UpdateVoteTexts(null, VoteType.Sub);
GameMain.NetLobbyScreen.SubList.Deselect();
#endif
}
}
private bool allowModeVoting;
//Don't serialize: the value is set based on ModeSelectionMode
public bool AllowModeVoting
{
get { return allowModeVoting; }
set
{
if (value == allowModeVoting) { return; }
allowModeVoting = value;
#if CLIENT
GameMain.NetLobbyScreen.ModeList.Enabled =
value ||
(GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.SelectMode));
GameMain.NetLobbyScreen.Frame.FindChild("modevotes", true).Visible = value;
// Disable modes that cannot be voted on
foreach (var guiComponent in GameMain.NetLobbyScreen.ModeList.Content.Children)
{
if (guiComponent is GUIFrame frame)
{
frame.CanBeFocused = !allowModeVoting || ((GameModePreset)frame.UserData).Votable;
}
}
GameMain.Client?.Voting.UpdateVoteTexts(null, VoteType.Mode);
GameMain.NetLobbyScreen.ModeList.Deselect();
#endif
}
}
public void SetPassword(string password)
{
if (string.IsNullOrEmpty(password))
@@ -1,19 +1,11 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Voting
{
private bool allowSubVoting, allowModeVoting;
public bool AllowVoteKick = true;
public bool AllowEndVoting = true;
public bool VoteRunning = false;
public enum VoteState { None = 0, Started = 1, Running = 2, Passed = 3, Failed = 4 };
private IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, List<Client> voters)
@@ -39,12 +31,12 @@ namespace Barotrauma
public T HighestVoted<T>(VoteType voteType, List<Client> voters)
{
if (voteType == VoteType.Sub && !AllowSubVoting) return default(T);
if (voteType == VoteType.Mode && !AllowModeVoting) return default(T);
if (voteType == VoteType.Sub && !GameMain.NetworkMember.ServerSettings.AllowSubVoting) { return default; }
if (voteType == VoteType.Mode && !GameMain.NetworkMember.ServerSettings.AllowModeVoting) { return default; }
IReadOnlyDictionary<T, int> voteList = GetVoteCounts<T>(voteType, voters);
T selected = default(T);
T selected = default;
int highestVotes = 0;
foreach (KeyValuePair<T, int> votable in voteList)
{
@@ -71,11 +63,13 @@ namespace Barotrauma
{
client.ResetVotes();
}
GameMain.NetworkMember.EndVoteCount = 0;
GameMain.NetworkMember.EndVoteMax = 0;
#if CLIENT
foreach (VoteType voteType in Enum.GetValues(typeof(VoteType)))
{
SetVoteCountYes(voteType, 0);
SetVoteCountNo(voteType, 0);
SetVoteCountMax(voteType, 0);
}
UpdateVoteTexts(connectedClients, VoteType.Mode);
UpdateVoteTexts(connectedClients, VoteType.Sub);
#endif
@@ -348,6 +348,14 @@ namespace Barotrauma.Steam
string val = attribute.Value.CleanUpPathCrossPlatform(correctFilenameCase: false);
//Handle mods that have been mangled by pre-modding-refactor
//copying of post-modding-refactor mods (what a clusterfuck)
int modDirStrIndex = val.IndexOf(ContentPath.ModDirStr, StringComparison.OrdinalIgnoreCase);
if (modDirStrIndex >= 0)
{
val = val[modDirStrIndex..];
}
//Handle really old mods (0.9.0.4-era) that might be structured as
//%ModDir%/Mods/[NAME]/[RESOURCE]
string fullSrcPath = Path.Combine(fileListDir, val).CleanUpPath();
@@ -418,7 +426,7 @@ namespace Barotrauma.Steam
File.Copy(from, to, overwrite: true);
}
private static async Task CopyDirectory(string fileListDir, string modName, string from, string to)
public static async Task CopyDirectory(string fileListDir, string modName, string from, string to)
{
from = Path.GetFullPath(from); to = Path.GetFullPath(to);
Directory.CreateDirectory(to);
@@ -79,10 +79,10 @@ namespace Barotrauma
{
roundData.EnteredCrushDepth.Add(c);
}
else if (Level.Loaded.GetRealWorldDepth(c.WorldPosition.Y) < Level.Loaded.RealWorldCrushDepth * 0.5f)
else if (Level.Loaded.GetRealWorldDepth(c.WorldPosition.Y) < Level.Loaded.RealWorldCrushDepth - 500.0f)
{
//all characters that have entered crush depth and are still alive get an achievement
if (roundData.EnteredCrushDepth.Contains(c)) UnlockAchievement(c, "survivecrushdepth".ToIdentifier());
if (roundData.EnteredCrushDepth.Contains(c)) { UnlockAchievement(c, "survivecrushdepth".ToIdentifier()); }
}
}
}
@@ -426,7 +426,7 @@ namespace Barotrauma
!c.IsDead &&
c.TeamID != CharacterTeamType.FriendlyNPC &&
!(c.AIController is EnemyAIController) &&
(c.Submarine == gameSession.Submarine || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost)));
(c.Submarine == gameSession.Submarine || gameSession.Submarine.GetConnectedSubs().Contains(c.Submarine) || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost)));
if (charactersInSub.Count == 1)
{
@@ -454,6 +454,10 @@ namespace Barotrauma
}
foreach (Character character in charactersInSub)
{
if (roundData.EnteredCrushDepth.Contains(character))
{
UnlockAchievement(character, "survivecrushdepth".ToIdentifier());
}
if (character.Info.Job == null) { continue; }
UnlockAchievement(character, $"{character.Info.Job.Prefab.Identifier}round".ToIdentifier());
}
@@ -37,6 +37,12 @@ namespace Barotrauma
events.Remove(identifier);
}
public void TryDeregister(Identifier identifier)
{
if (!HasEvent(identifier)) { return; }
Deregister(identifier);
}
public bool HasEvent(Identifier identifier) => events.ContainsKey(identifier);
public void Invoke(T data)
@@ -9,6 +9,7 @@ using System.Threading;
using System.Xml.Linq;
using Steamworks.Data;
using Color = Microsoft.Xna.Framework.Color;
using System.Text.RegularExpressions;
namespace Barotrauma
{
@@ -227,7 +228,7 @@ namespace Barotrauma
return Path.Combine(folder, saveName);
}
public static IEnumerable<string> GetSaveFiles(SaveType saveType, bool includeInCompatible = true)
public static IReadOnlyList<CampaignMode.SaveInfo> GetSaveFiles(SaveType saveType, bool includeInCompatible = true)
{
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
if (!Directory.Exists(folder))
@@ -250,18 +251,61 @@ namespace Barotrauma
files.AddRange(Directory.GetFiles(legacyFolder, "*.save", System.IO.SearchOption.TopDirectoryOnly));
}
if (!includeInCompatible)
List<CampaignMode.SaveInfo> saveInfos = new List<CampaignMode.SaveInfo>();
foreach (string file in files)
{
for (int i = files.Count - 1; i >= 0; i--)
XDocument doc = LoadGameSessionDoc(file);
if (!includeInCompatible && !IsSaveFileCompatible(doc))
{
XDocument doc = LoadGameSessionDoc(files[i]);
if (!IsSaveFileCompatible(doc))
continue;
}
if (doc?.Root == null)
{
saveInfos.Add(new CampaignMode.SaveInfo()
{
files.RemoveAt(i);
FilePath = file
});
}
else
{
List<string> enabledContentPackageNames = new List<string>();
//backwards compatibility
string enabledContentPackagePathsStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackages", string.Empty);
foreach (string packagePath in enabledContentPackagePathsStr.Split('|'))
{
if (string.IsNullOrEmpty(packagePath)) { continue; }
//change paths to names
string fileName = Path.GetFileNameWithoutExtension(packagePath);
if (fileName == "filelist")
{
enabledContentPackageNames.Add(Path.GetFileName(Path.GetDirectoryName(packagePath)));
}
else
{
enabledContentPackageNames.Add(fileName);
}
}
string enabledContentPackageNamesStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackagenames", string.Empty);
//split on pipes, excluding pipes preceded by \
foreach (string packageName in Regex.Split(enabledContentPackageNamesStr, @"(?<!(?<!\\)*\\)\|"))
{
if (string.IsNullOrEmpty(packageName)) { continue; }
enabledContentPackageNames.Add(packageName.Replace(@"\|", "|"));
}
saveInfos.Add(new CampaignMode.SaveInfo()
{
FilePath = file,
SubmarineName = doc?.Root?.GetAttributeStringUnrestricted("submarine", ""),
SaveTime = doc.Root.GetAttributeInt("savetime", 0),
EnabledContentPackageNames = enabledContentPackageNames.ToArray(),
});
}
}
return files;
return saveInfos;
}
public static string CreateSavePath(SaveType saveType, string fileName = "Save_Default")