Unstable 0.17.5.0
This commit is contained in:
@@ -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;
|
||||
|
||||
+15
-5
@@ -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)
|
||||
|
||||
+2
-2
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user