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
@@ -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;
@@ -1,220 +0,0 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace Barotrauma
{
public class Md5Hash
{
public static class Cache
{
private const string cachePath = "Data/hashcache.txt";
private readonly static List<(string Path, Md5Hash Hash, DateTime DateTime)> Entries
= new List<(string Path, Md5Hash Hash, DateTime DateTime)>();
public static void Load()
{
if (!File.Exists(cachePath)) { return; }
var lines = File.ReadAllLines(cachePath);
if (Version.TryParse(lines[0], out var cacheVersion) && cacheVersion == GameMain.Version)
{
for (int i = 1; i < lines.Length; i++)
{
string[] split = lines[i].Split('|');
string path = split[0].CleanUpPathCrossPlatform();
Md5Hash hash = Md5Hash.StringAsHash(split[1]);
DateTime? dateTime = null;
if (long.TryParse(split[2], out long dateTimeUlong))
{
dateTime = DateTime.FromBinary(dateTimeUlong);
}
if (File.Exists(path) && dateTime.HasValue && dateTime >= File.GetLastWriteTime(path))
{
Entries.Add((path, hash, dateTime.Value));
}
}
}
}
public static void Add(string path, Md5Hash hash, DateTime dateTime)
{
path = path.CleanUpPathCrossPlatform();
Remove(path);
Entries.Add((path, hash, dateTime));
}
public static void Remove(string path)
{
path = path.CleanUpPathCrossPlatform();
Entries.RemoveAll(e => e.Path == path);
}
}
public static readonly Md5Hash Blank = new Md5Hash(new string('0', 32));
private static string RemoveWhitespace(string s)
{
StringBuilder sb = new StringBuilder(s.Length / 2); // Reserve half the size of the original string because
// that's probably close enough to the size of the result
for (int i = 0; i < s.Length; i++)
{
if (char.IsWhiteSpace(s[i])) { continue; }
sb.Append(s[i]);
}
return sb.ToString();
}
//thanks to Jlobblet for this regex
private static readonly Regex stringHashRegex = new Regex(@"^[0-9a-fA-F]{7,32}$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
public readonly byte[] ByteRepresentation;
public readonly string StringRepresentation;
public readonly string ShortRepresentation;
private static void CalculateHash(byte[] bytes, out string stringRepresentation, out byte[] byteRepresentation)
{
using (MD5 md5 = MD5.Create())
{
byte[] byteHash = md5.ComputeHash(bytes);
byteRepresentation = byteHash;
stringRepresentation = ByteRepresentationToStringRepresentation(byteHash);
}
}
private static string ByteRepresentationToStringRepresentation(byte[] byteHash)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < byteHash.Length; i++)
{
sb.Append(byteHash[i].ToString("X2"));
}
return sb.ToString();
}
private static byte[] StringRepresentationToByteRepresentation(string strHash)
{
var byteRepresentation = new byte[strHash.Length / 2];
for (int i = 0; i < byteRepresentation.Length; i++)
{
byteRepresentation[i] = Convert.ToByte(strHash.Substring(i * 2, 2), 16);
}
return byteRepresentation;
}
public static string GetShortHash(string fullHash)
{
return fullHash.Length < 7 ? fullHash : fullHash.Substring(0, 7);
}
private Md5Hash(string md5Hash)
{
StringRepresentation = md5Hash;
ByteRepresentation = StringRepresentationToByteRepresentation(StringRepresentation);
ShortRepresentation = GetShortHash(md5Hash);
}
private Md5Hash(byte[] bytes, bool calculate)
{
if (calculate)
{
CalculateHash(bytes, out StringRepresentation, out ByteRepresentation);
}
else
{
StringRepresentation = ByteRepresentationToStringRepresentation(bytes);
ByteRepresentation = bytes;
}
ShortRepresentation = GetShortHash(StringRepresentation);
}
public static Md5Hash StringAsHash(string hash)
{
if (!stringHashRegex.IsMatch(hash)) { throw new ArgumentException($"{hash} is not a valid hash"); }
return new Md5Hash(hash);
}
public static Md5Hash CalculateForBytes(byte[] bytes)
{
return new Md5Hash(bytes, calculate: true);
}
public static Md5Hash BytesAsHash(byte[] bytes)
{
return new Md5Hash(bytes, calculate: false);
}
[Flags]
public enum StringHashOptions
{
BytePerfect = 0,
IgnoreCase = 0x1,
IgnoreWhitespace = 0x2
}
public static Md5Hash CalculateForFile(string path, StringHashOptions options)
{
if (options.HasFlag(StringHashOptions.IgnoreWhitespace) || options.HasFlag(StringHashOptions.IgnoreCase))
{
string str = File.ReadAllText(path, Encoding.UTF8);
return CalculateForString(str, options);
}
else
{
byte[] bytes = File.ReadAllBytes(path);
return CalculateForBytes(bytes);
}
}
public static Md5Hash CalculateForString(string str, StringHashOptions options)
{
if (options.HasFlag(StringHashOptions.IgnoreCase))
{
str = str.ToLowerInvariant();
}
if (options.HasFlag(StringHashOptions.IgnoreWhitespace))
{
str = RemoveWhitespace(str);
}
byte[] bytes = Encoding.UTF8.GetBytes(str);
return CalculateForBytes(bytes);
}
public override string ToString()
{
return StringRepresentation;
}
public override bool Equals(object? obj)
{
if (obj is Md5Hash { StringRepresentation: { } otherStr })
{
string selfStr = otherStr.Length < StringRepresentation.Length
? StringRepresentation[..otherStr.Length]
: StringRepresentation;
otherStr = StringRepresentation.Length < otherStr.Length
? otherStr[..StringRepresentation.Length]
: otherStr;
return selfStr.Equals(otherStr, StringComparison.OrdinalIgnoreCase);
}
return false;
}
public static bool operator ==(Md5Hash? a, Md5Hash? b)
=> (a is null == b is null) && (a?.Equals(b) ?? true);
public static bool operator !=(Md5Hash? a, Md5Hash? b) => !(a == b);
}
}
@@ -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);
}