Unstable v0.19.5.0
This commit is contained in:
@@ -527,7 +527,7 @@ namespace Barotrauma
|
||||
{
|
||||
// We'll want this to run each time, because the delegate is used to find a valid button component.
|
||||
bool canAccessButtons = false;
|
||||
foreach (var button in door.Item.GetConnectedComponents<Controller>(true))
|
||||
foreach (var button in door.Item.GetConnectedComponents<Controller>(true, connectionFilter: c => c.Name == "toggle" || c.Name == "set_state"))
|
||||
{
|
||||
if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button)))
|
||||
{
|
||||
@@ -675,6 +675,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition);
|
||||
//heavily prefer buttons linked to the door, so sub builders can help the bots figure out which button to use by linking them
|
||||
if (door.Item.linkedTo.Contains(button.Item)) { distance *= 0.1f; }
|
||||
if (closestButton == null || distance < closestDist && character.CanSeeTarget(button.Item))
|
||||
{
|
||||
closestButton = button;
|
||||
|
||||
@@ -1634,9 +1634,9 @@ namespace Barotrauma
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void ApplyHealthData(Character character, XElement healthData)
|
||||
public static void ApplyHealthData(Character character, XElement healthData, Func<AfflictionPrefab, bool> afflictionPredicate = null)
|
||||
{
|
||||
if (healthData != null) { character?.CharacterHealth.Load(healthData); }
|
||||
if (healthData != null) { character?.CharacterHealth.Load(healthData, afflictionPredicate); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,12 +12,8 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly static PrefabCollection<CharacterPrefab> Prefabs = new PrefabCollection<CharacterPrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
Character.RemoveByPrefab(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,13 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<CorpsePrefab> Prefabs = new PrefabCollection<CorpsePrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
|
||||
public static CorpsePrefab Get(Identifier identifier)
|
||||
{
|
||||
|
||||
@@ -1227,7 +1227,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(XElement element)
|
||||
public void Load(XElement element, Func<AfflictionPrefab, bool> afflictionPredicate = null)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
@@ -1260,6 +1260,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error while loading character health: affliction \"{id}\" not found.");
|
||||
return;
|
||||
}
|
||||
if (afflictionPredicate != null && !afflictionPredicate.Invoke(afflictionPrefab)) { return; }
|
||||
float strength = afflictionElement.GetAttributeFloat("strength", 0.0f);
|
||||
var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
||||
if (irremovableAffliction != null)
|
||||
|
||||
@@ -65,13 +65,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<JobPrefab> Prefabs = new PrefabCollection<JobPrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
|
||||
private static readonly Dictionary<Identifier, float> _itemRepairPriorities = new Dictionary<Identifier, float>();
|
||||
/// <summary>
|
||||
|
||||
@@ -56,11 +56,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -40,7 +40,9 @@ namespace Barotrauma
|
||||
}
|
||||
catch
|
||||
{
|
||||
prefab.Dispose(); //clean up before rethrowing, since some prefab types might lock resources
|
||||
//clean up before rethrowing, since some prefab types might lock resources
|
||||
prefab.Dispose();
|
||||
Prefabs.Remove(prefab);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-18
@@ -29,25 +29,25 @@ namespace Barotrauma
|
||||
public readonly ImmutableArray<string> AltNames;
|
||||
public readonly string Path;
|
||||
public string Dir => Barotrauma.IO.Path.GetDirectoryName(Path) ?? "";
|
||||
public readonly UInt64 SteamWorkshopId;
|
||||
public readonly Option<ContentPackageId> UgcId;
|
||||
|
||||
public readonly Version GameVersion;
|
||||
public readonly string ModVersion;
|
||||
public Md5Hash Hash { get; private set; }
|
||||
public readonly DateTime? InstallTime;
|
||||
public readonly Option<DateTime> InstallTime;
|
||||
|
||||
public ImmutableArray<ContentFile> Files { get; private set; }
|
||||
public ImmutableArray<ContentFile.LoadError> Errors { get; private set; }
|
||||
|
||||
public async Task<bool> IsUpToDate()
|
||||
{
|
||||
if (SteamWorkshopId != 0 && InstallTime.HasValue)
|
||||
{
|
||||
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(SteamWorkshopId);
|
||||
if (item is null) { return true; }
|
||||
return item.Value.LatestUpdateTime <= InstallTime;
|
||||
}
|
||||
return true;
|
||||
if (!UgcId.TryUnwrap(out var ugcId)) { return true; }
|
||||
if (!(ugcId is SteamWorkshopId steamWorkshopId)) { return true; }
|
||||
if (!InstallTime.TryUnwrap(out var installTime)) { return true; }
|
||||
|
||||
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(steamWorkshopId.Value);
|
||||
if (item is null) { return true; }
|
||||
return item.Value.LatestUpdateTime <= installTime;
|
||||
}
|
||||
|
||||
public int Index => ContentPackageManager.EnabledPackages.IndexOf(this);
|
||||
@@ -66,18 +66,19 @@ namespace Barotrauma
|
||||
AltNames = rootElement.GetAttributeStringArray("altnames", Array.Empty<string>())
|
||||
.Select(n => n.Trim()).ToImmutableArray();
|
||||
AssertCondition(!string.IsNullOrEmpty(Name), "Name is null or empty");
|
||||
SteamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
||||
|
||||
UInt64 steamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
||||
|
||||
UgcId = steamWorkshopId != 0
|
||||
? Option<ContentPackageId>.Some(new SteamWorkshopId(steamWorkshopId))
|
||||
: Option<ContentPackageId>.None();
|
||||
|
||||
GameVersion = rootElement.GetAttributeVersion("gameversion", GameMain.Version);
|
||||
ModVersion = rootElement.GetAttributeString("modversion", DefaultModVersion);
|
||||
if (rootElement.Attribute("installtime") != null)
|
||||
{
|
||||
InstallTime = ToolBox.Epoch.ToDateTime(rootElement.GetAttributeUInt("installtime", 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
InstallTime = null;
|
||||
}
|
||||
UInt64 installTimeUnix = rootElement.GetAttributeUInt64("installtime", 0);
|
||||
InstallTime = installTimeUnix != 0
|
||||
? Option<DateTime>.Some(ToolBox.Epoch.ToDateTime(installTimeUnix))
|
||||
: Option<DateTime>.None();
|
||||
|
||||
var fileResults = rootElement.Elements()
|
||||
.Select(e => ContentFile.CreateFromXElement(this, e))
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class ContentPackageId
|
||||
{
|
||||
public abstract string StringRepresentation { get; }
|
||||
|
||||
public override string ToString()
|
||||
=> StringRepresentation;
|
||||
|
||||
public abstract override bool Equals(object? obj);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
|
||||
public static Option<ContentPackageId> Parse(string s)
|
||||
=> ReflectionUtils.ParseDerived<ContentPackageId, string>(s);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SteamWorkshopId : ContentPackageId
|
||||
{
|
||||
public readonly UInt64 Value;
|
||||
|
||||
public SteamWorkshopId(UInt64 value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
private const string Prefix = "STEAM_WORKSHOP_";
|
||||
|
||||
public override string StringRepresentation => Value.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is SteamWorkshopId otherWorkshopId && otherWorkshopId.Value == Value;
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
|
||||
public new static Option<SteamWorkshopId> Parse(string s)
|
||||
{
|
||||
if (s.StartsWith(Prefix)) { s = s[Prefix.Length..]; }
|
||||
if (!UInt64.TryParse(s, out var id) || id == 0) { return Option<SteamWorkshopId>.None(); }
|
||||
return Option<SteamWorkshopId>.Some(new SteamWorkshopId(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Core != null && !ContentPackageManager.CorePackages.Contains(Core))
|
||||
{
|
||||
SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.SteamWorkshopId == Core.SteamWorkshopId) ??
|
||||
SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.UgcId == Core.UgcId) ??
|
||||
ContentPackageManager.CorePackages.First());
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Barotrauma
|
||||
newRegular.Add(p);
|
||||
}
|
||||
else if (ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2
|
||||
=> p2.SteamWorkshopId == p.SteamWorkshopId) is { } newP)
|
||||
=> p2.UgcId == p.UgcId) is { } newP)
|
||||
{
|
||||
newRegular.Add(newP);
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@ namespace Barotrauma
|
||||
cachedValue = cachedValue
|
||||
.Replace(ModDirStr, modPath, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace(string.Format(OtherModDirFmt, ContentPackage.Name), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
if (ContentPackage.SteamWorkshopId != 0)
|
||||
if (ContentPackage.UgcId.TryUnwrap(out var ugcId))
|
||||
{
|
||||
cachedValue = cachedValue
|
||||
.Replace(string.Format(OtherModDirFmt, ContentPackage.SteamWorkshopId.ToString(CultureInfo.InvariantCulture)), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
.Replace(string.Format(OtherModDirFmt, ugcId.StringRepresentation), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
var allPackages = ContentPackageManager.AllPackages;
|
||||
@@ -55,9 +55,9 @@ namespace Barotrauma
|
||||
#endif
|
||||
foreach (Identifier otherModName in otherMods)
|
||||
{
|
||||
if (!UInt64.TryParse(otherModName.Value, out UInt64 workshopId)) { workshopId = 0; }
|
||||
Option<ContentPackageId> ugcId = ContentPackageId.Parse(otherModName.Value);
|
||||
ContentPackage? otherMod =
|
||||
allPackages.FirstOrDefault(p => workshopId != 0 && p.SteamWorkshopId != 0 && workshopId == p.SteamWorkshopId)
|
||||
allPackages.FirstOrDefault(p => ugcId == p.UgcId)
|
||||
?? allPackages.FirstOrDefault(p => p.Name == otherModName)
|
||||
?? allPackages.FirstOrDefault(p => p.NameMatches(otherModName))
|
||||
?? throw new MissingContentPackageException(ContentPackage, otherModName.Value);
|
||||
|
||||
@@ -278,6 +278,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ListCoroutines()
|
||||
{
|
||||
lock (Coroutines)
|
||||
{
|
||||
DebugConsole.NewMessage("***********");
|
||||
DebugConsole.NewMessage($"{Coroutines.Count} coroutine(s)");
|
||||
foreach (var c in Coroutines)
|
||||
{
|
||||
DebugConsole.NewMessage($"- {c.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WaitForSeconds : CoroutineStatus
|
||||
|
||||
@@ -1651,6 +1651,8 @@ namespace Barotrauma
|
||||
}, isCheat: false));
|
||||
|
||||
commands.Add(new Command("listtasks", "listtasks: Lists all asynchronous tasks currently in the task pool.", (string[] args) => { TaskPool.ListTasks(); }));
|
||||
|
||||
commands.Add(new Command("listcoroutines", "listcoroutines: Lists all coroutines currently running.", (string[] args) => { CoroutineManager.ListCoroutines(); }));
|
||||
|
||||
commands.Add(new Command("calculatehashes", "calculatehashes [content package name]: Show the MD5 hashes of the files in the selected content package. If the name parameter is omitted, the first content package is selected.", (string[] args) =>
|
||||
{
|
||||
|
||||
+1
-4
@@ -28,10 +28,7 @@ namespace Barotrauma
|
||||
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed.");
|
||||
}
|
||||
|
||||
static bool IsTargetTagAttribute(XAttribute attribute)
|
||||
{
|
||||
return attribute.Name.ToString().Equals("targettag", System.StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
static bool IsTargetTagAttribute(XAttribute attribute) => attribute.NameAsIdentifier() == "targettag";
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class CheckConnectionAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ConnectionName { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ConnectedItemTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OtherConnectionName { get; set; }
|
||||
|
||||
public CheckConnectionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var connectTargets = !ConnectedItemTag.IsEmpty ? ParentEvent.GetTargets(ConnectedItemTag) : Enumerable.Empty<Entity>();
|
||||
foreach (var target in ParentEvent.GetTargets(ItemTag))
|
||||
{
|
||||
if (target is not Item targetItem) { continue; }
|
||||
if (targetItem.GetComponent<ConnectionPanel>() is not ConnectionPanel panel) { continue; }
|
||||
if (panel.Connections == null || panel.Connections.None()) { continue; }
|
||||
foreach (var connection in panel.Connections)
|
||||
{
|
||||
if (!IsCorrectConnection(connection, ConnectionName)) { continue; }
|
||||
if (ConnectedItemTag.IsEmpty && OtherConnectionName.IsEmpty)
|
||||
{
|
||||
if (connection.Wires.Any()) { return true; }
|
||||
continue;
|
||||
}
|
||||
foreach (var wire in connection.Wires)
|
||||
{
|
||||
if (wire.OtherConnection(connection) is not Connection otherConnection) { continue; }
|
||||
if (ConnectedItemTag.IsEmpty)
|
||||
{
|
||||
if (IsCorrectConnection(otherConnection, OtherConnectionName)) { return true; }
|
||||
}
|
||||
else if (OtherConnectionName.IsEmpty)
|
||||
{
|
||||
if (IsCorrectItem()) { return true; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsCorrectConnection(otherConnection, OtherConnectionName)) { continue; }
|
||||
if (!IsCorrectItem()) { continue; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsCorrectItem() => connectTargets.Contains(otherConnection.Item);
|
||||
}
|
||||
|
||||
bool IsCorrectConnection(Connection connection, Identifier id) => connection.Name.ToIdentifier() == id;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ namespace Barotrauma
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string ItemTags { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool RequireEquipped { get; set; }
|
||||
|
||||
private readonly Identifier[] itemIdentifierSplit;
|
||||
private readonly Identifier[] itemTags;
|
||||
@@ -30,20 +33,24 @@ namespace Barotrauma
|
||||
if (!targets.Any()) { return null; }
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (!(target is Character chr)) { continue; }
|
||||
if (chr.Inventory == null) { continue; }
|
||||
|
||||
if (itemTags.Any(tag => chr.Inventory.FindItemByTag(tag, recursive: true) != null)) { return true; }
|
||||
|
||||
foreach (var identifier in itemIdentifierSplit)
|
||||
if (target is Character character)
|
||||
{
|
||||
if (chr.Inventory.FindItemByIdentifier(identifier, recursive: true) != null)
|
||||
if (RequireEquipped)
|
||||
{
|
||||
return true;
|
||||
if (itemTags.Any(tag => character.HasEquippedItem(tag))) { return true; }
|
||||
if (itemIdentifierSplit.Any(identifier => character.HasEquippedItem(identifier))) { return true; }
|
||||
return false;
|
||||
}
|
||||
if (character.Inventory is not CharacterInventory inventory) { continue; }
|
||||
if (itemTags.Any(tag => inventory.FindItemByTag(tag, recursive: true) is not null)) { return true; }
|
||||
if (itemIdentifierSplit.Any(identifier => inventory.FindItemByIdentifier(identifier, recursive: true) is not null)) { return true; }
|
||||
}
|
||||
else if (target is Item item && item.OwnInventory is ItemInventory inventory)
|
||||
{
|
||||
if (itemTags.Any(tag => inventory.FindItemByTag(tag, recursive: true) is not null)) { return true; }
|
||||
if (itemIdentifierSplit.Any(identifier => inventory.FindItemByIdentifier(identifier, recursive: true) is not null)) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,40 +15,36 @@ namespace Barotrauma
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
ISerializableEntity target = null;
|
||||
Character targetCharacter = null;
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
foreach (var t in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (t is ISerializableEntity e)
|
||||
if (t is Character c)
|
||||
{
|
||||
target = e;
|
||||
targetCharacter = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target == null)
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.");
|
||||
return true;
|
||||
}
|
||||
if (target is Character character)
|
||||
{
|
||||
var currentOrderInfo = character.GetCurrentOrderWithTopPriority();
|
||||
if (currentOrderInfo?.Identifier == OrderIdentifier)
|
||||
{
|
||||
if (OrderOption.IsEmpty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return currentOrderInfo?.Option == OrderOption;
|
||||
}
|
||||
}
|
||||
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target character was found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
var currentOrderInfo = targetCharacter.GetCurrentOrderWithTopPriority();
|
||||
if (currentOrderInfo?.Identifier == OrderIdentifier)
|
||||
{
|
||||
if (OrderOption.IsEmpty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return currentOrderInfo?.Option == OrderOption;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckSelectedItemAction : BinaryOptionAction
|
||||
{
|
||||
public enum SelectedItemType { Primary, Secondary, Any };
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CharacterTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize(SelectedItemType.Any, IsPropertySaveable.Yes)]
|
||||
public SelectedItemType ItemType { get; set; }
|
||||
|
||||
public CheckSelectedItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
Character character = null;
|
||||
if (!CharacterTag.IsEmpty)
|
||||
{
|
||||
foreach (var t in ParentEvent.GetTargets(CharacterTag))
|
||||
{
|
||||
if (t is Character c)
|
||||
{
|
||||
character = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (character == null)
|
||||
{
|
||||
DebugConsole.ShowError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
IEnumerable<Entity> targets = ParentEvent.GetTargets(TargetTag);
|
||||
if (targets.None())
|
||||
{
|
||||
DebugConsole.ShowError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target is not Item targetItem)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (IsSelected(targetItem))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
bool IsSelected(Item item)
|
||||
{
|
||||
return ItemType switch
|
||||
{
|
||||
SelectedItemType.Any => character.IsAnySelectedItem(item),
|
||||
SelectedItemType.Primary => character.SelectedItem == item,
|
||||
SelectedItemType.Secondary => character.SelectedSecondaryItem == item,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ItemType switch
|
||||
{
|
||||
SelectedItemType.Any => !character.HasSelectedAnyItem,
|
||||
SelectedItemType.Primary => character.SelectedItem == null,
|
||||
SelectedItemType.Secondary => character.SelectedSecondaryItem == null,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
{
|
||||
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal sealed class CheckTalentAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TalentIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
public CheckTalentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
if (TargetTag.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Character? matchingCharacter = null;
|
||||
|
||||
foreach (Entity entity in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (entity is Character character)
|
||||
{
|
||||
matchingCharacter = character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matchingCharacter is not null && matchingCharacter.HasTalent(TalentIdentifier);
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
string subActionStr = "";
|
||||
if (succeeded.HasValue)
|
||||
{
|
||||
subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}";
|
||||
}
|
||||
|
||||
return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(CheckTalentAction)} -> (Talent: {TalentIdentifier.ColorizeObject()}" +
|
||||
$" Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" +
|
||||
subActionStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MessageBoxAction : EventAction
|
||||
partial class MessageBoxAction : EventAction
|
||||
{
|
||||
public enum ActionType { Create, Close }
|
||||
|
||||
[Serialize(ActionType.Create, IsPropertySaveable.Yes)]
|
||||
public ActionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Identifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string Tag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Header { get; set; }
|
||||
|
||||
@@ -24,7 +32,7 @@ namespace Barotrauma
|
||||
public string CloseOnInput { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CloseOnInteractTag { get; set; }
|
||||
public Identifier CloseOnSelectTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CloseOnPickUpTag { get; set; }
|
||||
@@ -35,8 +43,11 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CloseOnExitRoomName { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool IsTutorialObjective { get; set; }
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CloseOnInRoomName { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ObjectiveTag { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
@@ -45,72 +56,16 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
#if CLIENT
|
||||
CreateMessageBox();
|
||||
if (IsTutorialObjective && GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
||||
{
|
||||
tutorialMode.Tutorial?.TriggerTutorialSegment(new Tutorials.Tutorial.Segment(Text, CreateMessageBox));
|
||||
}
|
||||
#endif
|
||||
UpdateProjSpecific();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public void CreateMessageBox()
|
||||
{
|
||||
new GUIMessageBox(
|
||||
headerText: TextManager.Get(Header),
|
||||
text: RichString.Rich(TextManager.ParseInputTypes(TextManager.Get(Text).Fallback(Text.ToString()), useColorHighlight: true)),
|
||||
buttons: Array.Empty<LocalizedString>(),
|
||||
type: GUIMessageBox.Type.Tutorial,
|
||||
iconStyle: IconStyle,
|
||||
autoCloseCondition: GetAutoCloseCondition(),
|
||||
hideCloseButton: HideCloseButton);
|
||||
}
|
||||
#endif
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
private Func<bool> GetAutoCloseCondition()
|
||||
{
|
||||
var character = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Character;
|
||||
Func<bool> autoCloseCondition = null;
|
||||
if (!string.IsNullOrEmpty(CloseOnInput) && Enum.TryParse(CloseOnInput, true, out InputType closeOnInput))
|
||||
{
|
||||
#if CLIENT
|
||||
autoCloseCondition = () => PlayerInput.KeyDown(closeOnInput);
|
||||
#endif
|
||||
}
|
||||
else if (!CloseOnInteractTag.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character?.SelectedItem != null && character.SelectedItem.HasTag(CloseOnInteractTag);
|
||||
}
|
||||
else if (!CloseOnPickUpTag.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character?.Inventory != null && character.Inventory.FindItemByTag(CloseOnPickUpTag, recursive: true) != null;
|
||||
}
|
||||
else if (!CloseOnEquipTag.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character != null && character.HasEquippedItem(CloseOnEquipTag);
|
||||
}
|
||||
else if (!CloseOnExitRoomName.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character?.CurrentHull != null && character.CurrentHull.RoomName.ToIdentifier() != CloseOnExitRoomName;
|
||||
}
|
||||
return autoCloseCondition;
|
||||
}
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override bool IsFinished(ref string goToLabel)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset() => isFinished = false;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MessageBoxAction)}";
|
||||
}
|
||||
public override string ToDebugString() => $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MessageBoxAction)}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
class TeleportAction : EventAction
|
||||
{
|
||||
public enum TeleportPosition { MainSub, Outpost }
|
||||
|
||||
[Serialize(TeleportPosition.MainSub, IsPropertySaveable.Yes)]
|
||||
public TeleportPosition Position { get; set; }
|
||||
|
||||
[Serialize(SpawnType.Human, IsPropertySaveable.Yes)]
|
||||
public SpawnType SpawnType { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string SpawnPointTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TeleportAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
Submarine sub = Position switch
|
||||
{
|
||||
TeleportPosition.MainSub => Submarine.MainSub,
|
||||
TeleportPosition.Outpost => GameMain.GameSession?.Level?.StartOutpost,
|
||||
_ => null
|
||||
};
|
||||
if (WayPoint.GetRandom(spawnType: SpawnType, sub: sub, spawnPointTag: SpawnPointTag) is WayPoint wp)
|
||||
{
|
||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (target is Character c)
|
||||
{
|
||||
c.TeleportTo(wp.WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override void Reset() => isFinished = false;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class TutorialHighlightAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool State { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TutorialHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
UpdateProjSpecific();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override void Reset() => isFinished = false;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class TutorialIconAction : EventAction
|
||||
{
|
||||
public enum ActionType { Add, Remove, RemoveTarget, RemoveIcon, Clear };
|
||||
|
||||
[Serialize(ActionType.Add, IsPropertySaveable.Yes)]
|
||||
public ActionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string IconStyle { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TutorialIconAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
||||
{
|
||||
if (ParentEvent.GetTargets(TargetTag).FirstOrDefault() is Entity target)
|
||||
{
|
||||
if (Type == ActionType.Add)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.Add((target, IconStyle));
|
||||
}
|
||||
else if(Type == ActionType.Remove)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.RemoveAll(i => i.entity == target && i.iconStyle.Equals(IconStyle, System.StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else if (Type == ActionType.RemoveTarget)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.RemoveAll(i => i.entity == target);
|
||||
}
|
||||
else if (Type == ActionType.RemoveIcon)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.RemoveAll(i => i.iconStyle.Equals(IconStyle, System.StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else if (Type == ActionType.Clear)
|
||||
{
|
||||
tutorialMode.Tutorial?.Icons.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goToLabel)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
}
|
||||
+9
-56
@@ -1,12 +1,8 @@
|
||||
#if CLIENT
|
||||
using Barotrauma.Tutorials;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TutorialSegmentAction : EventAction
|
||||
partial class TutorialSegmentAction : EventAction
|
||||
{
|
||||
public enum SegmentActionType { Trigger, Complete, Remove };
|
||||
public enum SegmentActionType { Trigger, Add, Complete, CompleteAndRemove, Remove };
|
||||
|
||||
[Serialize(SegmentActionType.Trigger, IsPropertySaveable.Yes)]
|
||||
public SegmentActionType Type { get; set; }
|
||||
@@ -15,7 +11,7 @@ namespace Barotrauma
|
||||
public Identifier Id { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ObjectiveTextTag { get; set; }
|
||||
public Identifier ObjectiveTag { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AutoPlayVideo { get; set; }
|
||||
@@ -32,64 +28,21 @@ namespace Barotrauma
|
||||
[Serialize(80, IsPropertySaveable.Yes)]
|
||||
public int Height { get; set; }
|
||||
|
||||
#if CLIENT
|
||||
private readonly Tutorial.Segment segment;
|
||||
#endif
|
||||
private bool isFinished;
|
||||
|
||||
public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
#if CLIENT
|
||||
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
|
||||
if (Type == SegmentActionType.Trigger)
|
||||
{
|
||||
segment = new Tutorial.Segment(Id, ObjectiveTextTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
|
||||
new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center),
|
||||
new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
||||
{
|
||||
if (tutorialMode.Tutorial is Tutorial tutorial)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case SegmentActionType.Trigger:
|
||||
tutorial.TriggerTutorialSegment(segment);
|
||||
break;
|
||||
case SegmentActionType.Complete:
|
||||
tutorial.CompleteTutorialSegment(Id);
|
||||
break;
|
||||
case SegmentActionType.Remove:
|
||||
tutorial.RemoveTutorialSegment(Id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ShowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!");
|
||||
}
|
||||
#endif
|
||||
|
||||
UpdateProjSpecific();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goToLabel)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
public override bool IsFinished(ref string goToLabel) => isFinished;
|
||||
|
||||
public override void Reset() => isFinished = false;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
class EventSet : Prefab
|
||||
sealed class EventSet : Prefab
|
||||
{
|
||||
internal class EventDebugStats
|
||||
{
|
||||
@@ -489,12 +489,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
foreach (var childSet in ChildSets)
|
||||
{
|
||||
childSet.Dispose();
|
||||
}
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,5 +298,15 @@ namespace Barotrauma.Extensions
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> NotNull<T>(this IEnumerable<T?> source) where T : struct
|
||||
=> source
|
||||
.Where(nullable => nullable.HasValue)
|
||||
.Select(nullable => nullable.Value);
|
||||
|
||||
public static IEnumerable<T> NotNone<T>(this IEnumerable<Option<T>> source)
|
||||
=> source
|
||||
.OfType<Some<T>>()
|
||||
.Select(some => some.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -7,14 +8,14 @@ namespace Barotrauma
|
||||
{
|
||||
public static string FallbackNullOrEmpty(this string s, string fallback) => string.IsNullOrEmpty(s) ? fallback : s;
|
||||
|
||||
public static bool IsNullOrEmpty(this string? s) => string.IsNullOrEmpty(s);
|
||||
public static bool IsNullOrWhiteSpace(this string? s) => string.IsNullOrWhiteSpace(s);
|
||||
public static bool IsNullOrEmpty(this ContentPath? p) => p?.IsNullOrEmpty() ?? true;
|
||||
public static bool IsNullOrWhiteSpace(this ContentPath? p) => p?.IsNullOrWhiteSpace() ?? true;
|
||||
public static bool IsNullOrEmpty(this LocalizedString? s) => s is null || string.IsNullOrEmpty(s.Value);
|
||||
public static bool IsNullOrWhiteSpace(this LocalizedString? s) => s is null || string.IsNullOrWhiteSpace(s.Value);
|
||||
public static bool IsNullOrEmpty(this RichString? s) => s is null || s.NestedStr.IsNullOrEmpty();
|
||||
public static bool IsNullOrWhiteSpace(this RichString? s) => s is null || s.NestedStr.IsNullOrWhiteSpace();
|
||||
public static bool IsNullOrEmpty([NotNullWhen(returnValue: false)]this string? s) => string.IsNullOrEmpty(s);
|
||||
public static bool IsNullOrWhiteSpace([NotNullWhen(returnValue: false)]this string? s) => string.IsNullOrWhiteSpace(s);
|
||||
public static bool IsNullOrEmpty([NotNullWhen(returnValue: false)]this ContentPath? p) => p?.IsNullOrEmpty() ?? true;
|
||||
public static bool IsNullOrWhiteSpace([NotNullWhen(returnValue: false)]this ContentPath? p) => p?.IsNullOrWhiteSpace() ?? true;
|
||||
public static bool IsNullOrEmpty([NotNullWhen(returnValue: false)]this LocalizedString? s) => s is null || string.IsNullOrEmpty(s.Value);
|
||||
public static bool IsNullOrWhiteSpace([NotNullWhen(returnValue: false)]this LocalizedString? s) => s is null || string.IsNullOrWhiteSpace(s.Value);
|
||||
public static bool IsNullOrEmpty([NotNullWhen(returnValue: false)]this RichString? s) => s is null || s.NestedStr.IsNullOrEmpty();
|
||||
public static bool IsNullOrWhiteSpace([NotNullWhen(returnValue: false)]this RichString? s) => s is null || s.NestedStr.IsNullOrWhiteSpace();
|
||||
|
||||
public static string RemoveFromEnd(this string s, string substr, StringComparison stringComparison = StringComparison.Ordinal)
|
||||
=> s.EndsWith(substr, stringComparison) ? s.Substring(0, s.Length - substr.Length) : s;
|
||||
|
||||
+12
-3
@@ -5,7 +5,13 @@ namespace Barotrauma
|
||||
{
|
||||
class TutorialPrefab : Prefab
|
||||
{
|
||||
public static readonly PrefabCollection<TutorialPrefab> Prefabs = new PrefabCollection<TutorialPrefab>();
|
||||
|
||||
public static readonly PrefabCollection<TutorialPrefab> Prefabs =
|
||||
#if CLIENT
|
||||
new PrefabCollection<TutorialPrefab>(onSort: MainMenuScreen.UpdateInstanceTutorialButtons);
|
||||
#else
|
||||
new PrefabCollection<TutorialPrefab>();
|
||||
#endif
|
||||
|
||||
public readonly int Order;
|
||||
public readonly bool DisableBotConversations;
|
||||
@@ -54,8 +60,11 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
Identifier speciesName = tutorialCharacterElement.GetAttributeIdentifier("speciesname", CharacterPrefab.HumanSpeciesName);
|
||||
string jobPrefabIdentifier = tutorialCharacterElement.GetAttributeString("jobidentifier", "assistant");
|
||||
var jobPrefab = JobPrefab.Prefabs.FirstOrDefault(p => p.Identifier == jobPrefabIdentifier) ?? JobPrefab.Prefabs.First();
|
||||
Identifier jobPrefabIdentifier = tutorialCharacterElement.GetAttributeIdentifier("jobidentifier", "assistant");
|
||||
if (!JobPrefab.Prefabs.TryGet(jobPrefabIdentifier, out var jobPrefab))
|
||||
{
|
||||
jobPrefab = JobPrefab.Prefabs.First();
|
||||
}
|
||||
int jobVariant = tutorialCharacterElement.GetAttributeInt("variant", 0);
|
||||
var characterInfo = new CharacterInfo(speciesName, jobOrJobPrefab: jobPrefab, variant: jobVariant);
|
||||
foreach (var skillElement in tutorialCharacterElement.GetChildElements("skill"))
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace Barotrauma.Items.Components
|
||||
private const float MinZoom = 1.0f, MaxZoom = 4.0f;
|
||||
private float zoom = 1.0f;
|
||||
|
||||
/// <remarks>Accessed through event actions. Do not remove even if there are no references in code.</remarks>
|
||||
public bool UseDirectionalPing => useDirectionalPing;
|
||||
private bool useDirectionalPing = false;
|
||||
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
|
||||
private bool useMineralScanner;
|
||||
|
||||
+7
-2
@@ -82,9 +82,14 @@ namespace Barotrauma.Items.Components
|
||||
public sealed override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
int receivedInputs = 0;
|
||||
bool allInputsTimedOut = true;
|
||||
for (int i = 0; i < timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] <= timeFrame) { receivedInputs += 1; }
|
||||
if (timeSinceReceived[i] <= timeFrame)
|
||||
{
|
||||
allInputsTimedOut = false;
|
||||
receivedInputs += 1;
|
||||
}
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
|
||||
@@ -93,7 +98,7 @@ namespace Barotrauma.Items.Components
|
||||
if (string.IsNullOrEmpty(signalOut))
|
||||
{
|
||||
//deactivate the component if state is false and there's no false output (will be woken up by non-zero signals in ReceiveSignal)
|
||||
if (!state) { IsActive = false; }
|
||||
if (!state && allInputsTimedOut) { IsActive = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool WaterDetected => isInWater;
|
||||
public int WaterPercentage => GetWaterPercentage(item.CurrentHull);
|
||||
|
||||
public WaterDetector(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
@@ -2179,7 +2179,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Note: This function generates garbage and might be a bit too heavy to be used once per frame.
|
||||
/// </summary>
|
||||
public List<T> GetConnectedComponents<T>(bool recursive = false, bool allowTraversingBackwards = true) where T : ItemComponent
|
||||
public List<T> GetConnectedComponents<T>(bool recursive = false, bool allowTraversingBackwards = true, Func<Connection, bool> connectionFilter = null) where T : ItemComponent
|
||||
{
|
||||
List<T> connectedComponents = new List<T>();
|
||||
|
||||
@@ -2195,6 +2195,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Connection c in connectionPanel.Connections)
|
||||
{
|
||||
if (connectionFilter != null && !connectionFilter.Invoke(c)) { continue; }
|
||||
var recipients = c.Recipients;
|
||||
foreach (Connection recipient in recipients)
|
||||
{
|
||||
|
||||
@@ -807,9 +807,6 @@ namespace Barotrauma
|
||||
//only used if the item doesn't have a name/description defined in the currently selected language
|
||||
string fallbackNameIdentifier = ConfigElement.GetAttributeString("fallbacknameidentifier", "");
|
||||
|
||||
//works the same as nameIdentifier, but just replaces the description
|
||||
Identifier descriptionIdentifier = ConfigElement.GetAttributeIdentifier("descriptionidentifier", "");
|
||||
|
||||
name = TextManager.Get(nameIdentifier.IsEmpty
|
||||
? $"EntityName.{Identifier}"
|
||||
: $"EntityName.{nameIdentifier}",
|
||||
@@ -858,18 +855,7 @@ namespace Barotrauma
|
||||
|
||||
SerializableProperty.DeserializeProperties(this, ConfigElement);
|
||||
|
||||
if (descriptionIdentifier != Identifier.Empty)
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}").Fallback(Description);
|
||||
}
|
||||
else if (nameIdentifier == Identifier.Empty)
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{Identifier}").Fallback(Description);
|
||||
}
|
||||
else
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{nameIdentifier}").Fallback(Description);
|
||||
}
|
||||
LoadDescription(ConfigElement);
|
||||
|
||||
var allowDroppingOnSwapWith = ConfigElement.GetAttributeIdentifierArray("allowdroppingonswapwith", Array.Empty<Identifier>());
|
||||
AllowDroppingOnSwapWith = allowDroppingOnSwapWith.ToImmutableHashSet();
|
||||
@@ -1320,12 +1306,8 @@ namespace Barotrauma
|
||||
throw new InvalidOperationException("Can't call ItemPrefab.CreateInstance");
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
Item.RemoveByPrefab(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
public override Sprite Sprite => null;
|
||||
|
||||
@@ -102,9 +101,8 @@ namespace Barotrauma
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(CoreEntityPrefab)}.{nameof(Dispose)} should never be called");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace Barotrauma
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
Dispose();
|
||||
Prefabs.Remove(this);
|
||||
try
|
||||
{
|
||||
if (ContentPackage is { Files: { Length: 1 } }
|
||||
|
||||
@@ -238,8 +238,8 @@ namespace Barotrauma
|
||||
//find the edge at the opposite side of the adjacent cell
|
||||
foreach (GraphEdge otherEdge in adjacentEmptyCell.Edges)
|
||||
{
|
||||
if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) < 0 &&
|
||||
otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType == CellType.Solid)
|
||||
if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) > 0 &&
|
||||
otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType != CellType.Solid)
|
||||
{
|
||||
adjacentEdge = otherEdge;
|
||||
break;
|
||||
|
||||
@@ -11,15 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class LinkedSubmarinePrefab : MapEntityPrefab
|
||||
{
|
||||
//public static readonly PrefabCollection<LinkedSubmarinePrefab> Prefabs = new PrefabCollection<LinkedSubmarinePrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
//Prefabs.Remove(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
|
||||
public readonly SubmarineInfo subInfo;
|
||||
|
||||
|
||||
@@ -1253,7 +1253,7 @@ namespace Barotrauma
|
||||
{
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (!characters.Any()) { return 0; }
|
||||
return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
}
|
||||
|
||||
public void Discover(bool checkTalents = true)
|
||||
|
||||
@@ -280,5 +280,29 @@ namespace Barotrauma
|
||||
return AllowedLinks.Contains(target.Identifier) || target.AllowedLinks.Contains(Identifier)
|
||||
|| target.Tags.Any(t => AllowedLinks.Contains(t)) || Tags.Any(t => target.AllowedLinks.Contains(t));
|
||||
}
|
||||
|
||||
protected void LoadDescription(ContentXElement element)
|
||||
{
|
||||
Identifier descriptionIdentifier = element.GetAttributeIdentifier("descriptionidentifier", "");
|
||||
Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", "");
|
||||
|
||||
string originalDescription = Description.Value;
|
||||
if (descriptionIdentifier != Identifier.Empty)
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}");
|
||||
}
|
||||
else if (nameIdentifier == Identifier.Empty)
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{Identifier}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{nameIdentifier}");
|
||||
}
|
||||
if (!originalDescription.IsNullOrEmpty())
|
||||
{
|
||||
Description = Description.Fallback(originalDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,8 +142,6 @@ namespace Barotrauma
|
||||
//only used if the item doesn't have a name/description defined in the currently selected language
|
||||
Identifier fallbackNameIdentifier = element.GetAttributeIdentifier("fallbacknameidentifier", "");
|
||||
|
||||
Identifier descriptionIdentifier = element.GetAttributeIdentifier("descriptionidentifier", "");
|
||||
|
||||
Name = TextManager.Get(nameIdentifier.IsEmpty
|
||||
? $"EntityName.{Identifier}"
|
||||
: $"EntityName.{nameIdentifier}",
|
||||
@@ -271,18 +269,7 @@ namespace Barotrauma
|
||||
tags.Add("wall".ToIdentifier());
|
||||
}
|
||||
|
||||
if (!descriptionIdentifier.IsEmpty)
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}").Fallback(Description);
|
||||
}
|
||||
else if (nameIdentifier.IsEmpty)
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{Identifier}").Fallback(Description);
|
||||
}
|
||||
else
|
||||
{
|
||||
Description = TextManager.Get($"EntityDescription.{nameIdentifier}").Fallback(Description);
|
||||
}
|
||||
LoadDescription(element);
|
||||
|
||||
//backwards compatibility
|
||||
if (element.GetAttribute("size") == null)
|
||||
@@ -331,12 +318,6 @@ namespace Barotrauma
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1548,6 +1548,7 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("description", Info.Description ?? ""));
|
||||
element.Add(new XAttribute("checkval", Rand.Int(int.MaxValue)));
|
||||
element.Add(new XAttribute("price", Info.Price));
|
||||
element.Add(new XAttribute("tier", Info.Tier));
|
||||
element.Add(new XAttribute("initialsuppliesspawned", Info.InitialSuppliesSpawned));
|
||||
element.Add(new XAttribute("noitems", Info.NoItems));
|
||||
element.Add(new XAttribute("lowfuel", !CheckFuel()));
|
||||
|
||||
@@ -205,15 +205,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
votes[(int)voteType] = value;
|
||||
}
|
||||
|
||||
public void ResetVotes()
|
||||
{
|
||||
for (int i = 0; i < votes.Length; i++)
|
||||
{
|
||||
votes[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool SessionOrAccountIdMatches(string userId)
|
||||
=> (AccountId.IsSome() && Networking.AccountId.Parse(userId) == AccountId)
|
||||
|| (byte.TryParse(userId, out byte sessionId) && SessionId == sessionId);
|
||||
|
||||
@@ -131,28 +131,29 @@ namespace Barotrauma.Networking
|
||||
|
||||
enum DisconnectReason
|
||||
{
|
||||
//do not attempt reconnecting with these reasons
|
||||
Unknown,
|
||||
Disconnected,
|
||||
Banned,
|
||||
Kicked,
|
||||
ServerShutdown,
|
||||
ServerCrashed,
|
||||
ServerFull,
|
||||
AuthenticationRequired,
|
||||
SteamAuthenticationRequired,
|
||||
SteamAuthenticationFailed,
|
||||
SessionTaken,
|
||||
TooManyFailedLogins,
|
||||
NoName,
|
||||
InvalidName,
|
||||
NameTaken,
|
||||
InvalidVersion,
|
||||
MissingContentPackage,
|
||||
IncompatibleContentPackage,
|
||||
SteamP2PError,
|
||||
|
||||
//attempt reconnecting with these reasons
|
||||
Timeout,
|
||||
ExcessiveDesyncOldEvent,
|
||||
ExcessiveDesyncRemovedEvent,
|
||||
SyncTimeout,
|
||||
SteamP2PError,
|
||||
SteamP2PTimeOut,
|
||||
SteamP2PTimeOut
|
||||
}
|
||||
|
||||
abstract partial class NetworkMember
|
||||
@@ -163,71 +164,38 @@ namespace Barotrauma.Networking
|
||||
set;
|
||||
}
|
||||
|
||||
public virtual bool IsServer
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
public abstract bool IsServer { get; }
|
||||
|
||||
public virtual bool IsClient
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
public abstract bool IsClient { get; }
|
||||
|
||||
public abstract void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null);
|
||||
|
||||
#if DEBUG
|
||||
public Dictionary<string, long> messageCount = new Dictionary<string, long>();
|
||||
#endif
|
||||
|
||||
protected ServerSettings serverSettings;
|
||||
public abstract Voting Voting { get; }
|
||||
|
||||
public Voting Voting { get; protected set; }
|
||||
|
||||
protected TimeSpan updateInterval;
|
||||
protected DateTime updateTimer;
|
||||
|
||||
protected bool gameStarted;
|
||||
|
||||
protected RespawnManager respawnManager;
|
||||
|
||||
public bool ShowNetStats;
|
||||
|
||||
public float SimulatedRandomLatency, SimulatedMinimumLatency;
|
||||
public float SimulatedLoss;
|
||||
public float SimulatedDuplicatesChance;
|
||||
|
||||
public int TickRate
|
||||
{
|
||||
get { return serverSettings.TickRate; }
|
||||
set
|
||||
{
|
||||
serverSettings.TickRate = MathHelper.Clamp(value, 1, 60);
|
||||
updateInterval = new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / serverSettings.TickRate, 1, 500));
|
||||
}
|
||||
}
|
||||
|
||||
public KarmaManager KarmaManager
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new KarmaManager();
|
||||
|
||||
public bool GameStarted
|
||||
{
|
||||
get { return gameStarted; }
|
||||
}
|
||||
public bool GameStarted { get; protected set; }
|
||||
|
||||
public abstract IReadOnlyList<Client> ConnectedClients { get; }
|
||||
|
||||
public RespawnManager RespawnManager
|
||||
{
|
||||
get { return respawnManager; }
|
||||
}
|
||||
public RespawnManager RespawnManager { get; protected set; }
|
||||
|
||||
public ServerSettings ServerSettings { get; protected set; }
|
||||
|
||||
public TimeSpan UpdateInterval => new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / ServerSettings.TickRate, 1, 500));
|
||||
|
||||
public ServerSettings ServerSettings
|
||||
{
|
||||
get { return serverSettings; }
|
||||
}
|
||||
|
||||
public bool CanUseRadio(Character sender)
|
||||
{
|
||||
@@ -277,24 +245,6 @@ namespace Barotrauma.Networking
|
||||
|
||||
public abstract void UnbanPlayer(Endpoint endpoint);
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual void Quit() { }
|
||||
|
||||
/// <summary>
|
||||
/// Check if the two version are compatible (= if they can play together in multiplayer).
|
||||
/// Returns null if compatibility could not be determined (invalid/unknown version number).
|
||||
/// </summary>
|
||||
public static bool? IsCompatible(string myVersion, string remoteVersion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(myVersion) || string.IsNullOrEmpty(remoteVersion)) { return null; }
|
||||
|
||||
if (!Version.TryParse(myVersion, out Version myVersionNumber)) { return null; }
|
||||
if (!Version.TryParse(remoteVersion, out Version remoteVersionNumber)) { return null; }
|
||||
|
||||
return IsCompatible(myVersionNumber, remoteVersionNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the two version are compatible (= if they can play together in multiplayer).
|
||||
/// </summary>
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ namespace Barotrauma.Networking
|
||||
public abstract string StringRepresentation { get; }
|
||||
|
||||
public static Option<AccountId> Parse(string str)
|
||||
=> ReflectionUtils.ParseDerived<AccountId>(str);
|
||||
=> ReflectionUtils.ParseDerived<AccountId, string>(str);
|
||||
|
||||
public abstract override bool Equals(object? obj);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Barotrauma.Networking
|
||||
public abstract string StringRepresentation { get; }
|
||||
|
||||
public static Option<Address> Parse(string str)
|
||||
=> ReflectionUtils.ParseDerived<Address>(str);
|
||||
=> ReflectionUtils.ParseDerived<Address, string>(str);
|
||||
|
||||
public abstract bool IsLocalHost { get; }
|
||||
|
||||
|
||||
+14
-18
@@ -17,31 +17,27 @@ namespace Barotrauma.Networking
|
||||
|
||||
public LidgrenAddress(IPAddress netAddress)
|
||||
{
|
||||
NetAddress = netAddress;
|
||||
if (IPAddress.IsLoopback(netAddress))
|
||||
{
|
||||
NetAddress = IPAddress.Loopback;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetAddress = netAddress;
|
||||
}
|
||||
}
|
||||
|
||||
public new static Option<LidgrenAddress> Parse(string endpointStr)
|
||||
{
|
||||
if (IPAddress.TryParse(endpointStr, out IPAddress? netEndpoint))
|
||||
if (endpointStr.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Option<LidgrenAddress>.Some(new LidgrenAddress(IPAddress.Loopback));
|
||||
}
|
||||
else if (IPAddress.TryParse(endpointStr, out IPAddress? netEndpoint))
|
||||
{
|
||||
return Option<LidgrenAddress>.Some(new LidgrenAddress(netEndpoint!));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var resolvedAddresses = Dns.GetHostAddresses(endpointStr);
|
||||
return resolvedAddresses.Any()
|
||||
? Option<LidgrenAddress>.Some(new LidgrenAddress(resolvedAddresses.First()))
|
||||
: Option<LidgrenAddress>.None();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return Option<LidgrenAddress>.None();
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return Option<LidgrenAddress>.None();
|
||||
}
|
||||
return Option<LidgrenAddress>.None();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
|
||||
+13
-4
@@ -22,12 +22,21 @@ namespace Barotrauma.Networking
|
||||
public override string ToString() => StringRepresentation;
|
||||
|
||||
public static Option<Endpoint> Parse(string str)
|
||||
=> ReflectionUtils.ParseDerived<Endpoint>(str);
|
||||
=> ReflectionUtils.ParseDerived<Endpoint, string>(str);
|
||||
|
||||
public static bool operator ==(Endpoint a, Endpoint b)
|
||||
=> a.Equals(b);
|
||||
public static bool operator ==(Endpoint? a, Endpoint? b)
|
||||
{
|
||||
if (a is null)
|
||||
{
|
||||
return b is null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator !=(Endpoint a, Endpoint b)
|
||||
public static bool operator !=(Endpoint? a, Endpoint? b)
|
||||
=> !(a == b);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-14
@@ -24,24 +24,23 @@ namespace Barotrauma.Networking
|
||||
|
||||
public new static Option<LidgrenEndpoint> Parse(string endpointStr)
|
||||
{
|
||||
if (IPEndPoint.TryParse(endpointStr, out IPEndPoint? netEndpoint))
|
||||
{
|
||||
return Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(netEndpoint!));
|
||||
}
|
||||
|
||||
string hostName = endpointStr;
|
||||
int port = NetConfig.DefaultPort;
|
||||
if (endpointStr.Count(c => c == ':') == 1)
|
||||
{
|
||||
string[] split = endpointStr.Split(':');
|
||||
string hostName = split[0];
|
||||
if (LidgrenAddress.Parse(hostName).TryUnwrap(out var adr)
|
||||
&& int.TryParse(split[1], out var port))
|
||||
{
|
||||
return Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(adr.NetAddress, port));
|
||||
}
|
||||
hostName = split[0];
|
||||
port = int.TryParse(split[1], out var tmpPort) ? tmpPort : port;
|
||||
}
|
||||
|
||||
return LidgrenAddress.Parse(endpointStr)
|
||||
.Select(adr => new LidgrenEndpoint(adr.NetAddress, NetConfig.DefaultPort));
|
||||
|
||||
if (LidgrenAddress.Parse(hostName).TryUnwrap(out var adr))
|
||||
{
|
||||
return Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(adr.NetAddress, port));
|
||||
}
|
||||
|
||||
return IPEndPoint.TryParse(endpointStr, out IPEndPoint? netEndpoint)
|
||||
? Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(netEndpoint))
|
||||
: Option<LidgrenEndpoint>.None();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
|
||||
+168
-7
@@ -1,8 +1,8 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
@@ -68,7 +68,7 @@ namespace Barotrauma.Networking
|
||||
public byte[] Buffer;
|
||||
public readonly int Length => Buffer.Length;
|
||||
|
||||
public readonly IReadMessage GetReadMessage() => new ReadWriteMessage(Buffer, 0, Length, copyBuf: false);
|
||||
public readonly IReadMessage GetReadMessageUncompressed() => new ReadWriteMessage(Buffer, 0, Length, copyBuf: false);
|
||||
public readonly IReadMessage GetReadMessage(bool isCompressed, NetworkConnection conn) => new ReadOnlyMessage(Buffer, isCompressed, 0, Length, conn);
|
||||
}
|
||||
|
||||
@@ -86,9 +86,164 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
internal struct PeerDisconnectPacket : INetSerializableStruct
|
||||
internal readonly struct PeerDisconnectPacket : INetSerializableStruct
|
||||
{
|
||||
public string Message;
|
||||
public readonly DisconnectReason DisconnectReason;
|
||||
|
||||
public readonly string AdditionalInformation;
|
||||
|
||||
private PeerDisconnectPacket(
|
||||
DisconnectReason disconnectReason,
|
||||
string additionalInformation = "")
|
||||
{
|
||||
DisconnectReason = disconnectReason;
|
||||
AdditionalInformation = additionalInformation;
|
||||
}
|
||||
|
||||
public LocalizedString ChatMessage(Client c)
|
||||
=> DisconnectReason switch
|
||||
{
|
||||
DisconnectReason.Disconnected => TextManager.GetWithVariable("ServerMessage.ClientLeftServer",
|
||||
"[client]", c.Name),
|
||||
_ => TextManager.GetWithVariables("ChatMsg.DisconnectedWithReason",
|
||||
("[client]", c.Name),
|
||||
("[reason]", TextManager.Get($"ChatMsg.DisconnectReason.{DisconnectReason}")))
|
||||
};
|
||||
|
||||
private LocalizedString msgWithReason
|
||||
=> TextManager.Get($"DisconnectReason.{DisconnectReason}")
|
||||
+ "\n\n"
|
||||
+ TextManager.Get("banreason") + " " + AdditionalInformation;
|
||||
|
||||
private LocalizedString serverMessage
|
||||
=> TextManager.Get($"ServerMessage.{DisconnectReason}");
|
||||
|
||||
public LocalizedString PopupMessage
|
||||
=> DisconnectReason switch
|
||||
{
|
||||
DisconnectReason.Banned => msgWithReason,
|
||||
DisconnectReason.Kicked => msgWithReason,
|
||||
DisconnectReason.InvalidVersion => TextManager.GetWithVariables("DisconnectMessage.InvalidVersion",
|
||||
("[version]", AdditionalInformation),
|
||||
("[clientversion]", GameMain.Version.ToString())),
|
||||
DisconnectReason.ExcessiveDesyncOldEvent => serverMessage,
|
||||
DisconnectReason.ExcessiveDesyncRemovedEvent => serverMessage,
|
||||
DisconnectReason.SyncTimeout => serverMessage,
|
||||
_ => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback(TextManager.Get("ConnectionLost"))
|
||||
};
|
||||
|
||||
public LocalizedString ReconnectMessage
|
||||
=> PopupMessage + "\n\n" + TextManager.Get("ConnectionLostReconnecting");
|
||||
|
||||
public PlayerConnectionChangeType ConnectionChangeType
|
||||
=> DisconnectReason switch
|
||||
{
|
||||
DisconnectReason.Banned => PlayerConnectionChangeType.Banned,
|
||||
DisconnectReason.Kicked => PlayerConnectionChangeType.Kicked,
|
||||
_ => PlayerConnectionChangeType.Disconnected
|
||||
};
|
||||
|
||||
public bool ShouldAttemptReconnect
|
||||
=> DisconnectReason
|
||||
is DisconnectReason.ExcessiveDesyncOldEvent
|
||||
or DisconnectReason.ExcessiveDesyncRemovedEvent
|
||||
or DisconnectReason.Timeout
|
||||
or DisconnectReason.SyncTimeout
|
||||
or DisconnectReason.SteamP2PTimeOut;
|
||||
|
||||
public bool IsEventSyncError
|
||||
=> DisconnectReason
|
||||
is DisconnectReason.ExcessiveDesyncOldEvent
|
||||
or DisconnectReason.ExcessiveDesyncRemovedEvent
|
||||
or DisconnectReason.SyncTimeout;
|
||||
|
||||
public bool ShouldCreateAnalyticsEvent
|
||||
=> DisconnectReason is not (
|
||||
DisconnectReason.Disconnected
|
||||
or DisconnectReason.Banned
|
||||
or DisconnectReason.Kicked
|
||||
or DisconnectReason.TooManyFailedLogins
|
||||
or DisconnectReason.InvalidVersion);
|
||||
|
||||
public bool ShouldShowMessage
|
||||
=> DisconnectReason is not DisconnectReason.Disconnected;
|
||||
|
||||
private const string lidgrenSeparator = ":hankey:";
|
||||
|
||||
/// <summary>
|
||||
/// This exists because Lidgren is a piece of shit and
|
||||
/// doesn't readily support sending anything other than
|
||||
/// a string through a disconnect packet, so this thing
|
||||
/// needs a sufficiently nasty string representation that
|
||||
/// can be decoded with some certainty that it won't get
|
||||
/// mangled by user input.
|
||||
/// </summary>
|
||||
public string ToLidgrenStringRepresentation()
|
||||
{
|
||||
static string strToBase64(string str)
|
||||
=> Convert.ToBase64String(Encoding.UTF8.GetBytes(str));
|
||||
|
||||
return DisconnectReason
|
||||
+ lidgrenSeparator
|
||||
+ strToBase64(AdditionalInformation);
|
||||
}
|
||||
|
||||
public static Option<PeerDisconnectPacket> FromLidgrenStringRepresentation(string str)
|
||||
{
|
||||
// Lidgren has some hardcoded disconnect strings that it uses
|
||||
// when it detects that a connection has failed. We can handle
|
||||
// timeouts, so let's look for strings related to that and return
|
||||
// an appropriate PeerDisconnectPacket.
|
||||
switch (str)
|
||||
{
|
||||
case Lidgren.Network.NetConnection.NoResponseMessage:
|
||||
case "Connection timed out":
|
||||
case "Reconnecting":
|
||||
return Option<PeerDisconnectPacket>.Some(WithReason(DisconnectReason.Timeout));
|
||||
}
|
||||
|
||||
static string base64ToStr(string base64)
|
||||
=> Encoding.UTF8.GetString(Convert.FromBase64String(base64));
|
||||
|
||||
string[] split = str.Split(lidgrenSeparator);
|
||||
if (split.Length != 2) { return Option<PeerDisconnectPacket>.None(); }
|
||||
if (!Enum.TryParse(split[0], out DisconnectReason disconnectReason)) { return Option<PeerDisconnectPacket>.None(); }
|
||||
return Option<PeerDisconnectPacket>.Some(new PeerDisconnectPacket(disconnectReason, base64ToStr(split[1])));
|
||||
}
|
||||
|
||||
public static PeerDisconnectPacket Custom(string customMessage)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.Unknown,
|
||||
customMessage);
|
||||
|
||||
public static PeerDisconnectPacket WithReason(DisconnectReason disconnectReason)
|
||||
=> new PeerDisconnectPacket(disconnectReason);
|
||||
|
||||
public static PeerDisconnectPacket Kicked(string? msg)
|
||||
=> new PeerDisconnectPacket(DisconnectReason.Kicked, msg ?? "");
|
||||
|
||||
public static PeerDisconnectPacket Banned(string? msg)
|
||||
=> new PeerDisconnectPacket(DisconnectReason.Banned, msg ?? "");
|
||||
|
||||
public static PeerDisconnectPacket InvalidVersion()
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.InvalidVersion,
|
||||
GameMain.Version.ToString());
|
||||
|
||||
public static PeerDisconnectPacket SteamP2PError(Steamworks.P2PSessionError error)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.SteamP2PError,
|
||||
error.ToString());
|
||||
|
||||
public static PeerDisconnectPacket SteamAuthError(Steamworks.BeginAuthResult error)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.SteamAuthenticationFailed,
|
||||
$"{nameof(Steamworks.BeginAuthResult)}.{error}");
|
||||
|
||||
public static PeerDisconnectPacket SteamAuthError(Steamworks.AuthResponse error)
|
||||
=> new PeerDisconnectPacket(
|
||||
DisconnectReason.SteamAuthenticationFailed,
|
||||
$"{nameof(Steamworks.AuthResponse)}.{error}");
|
||||
}
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global, FieldCanBeMadeReadOnly.Global, UnassignedField.Global
|
||||
@@ -101,11 +256,14 @@ namespace Barotrauma.Networking
|
||||
public byte[] HashBytes = Array.Empty<byte>();
|
||||
|
||||
[NetworkSerialize]
|
||||
public ulong WorkshopId;
|
||||
public string UgcId = "";
|
||||
|
||||
[NetworkSerialize]
|
||||
public uint InstallTimeDiffInSeconds;
|
||||
|
||||
[NetworkSerialize]
|
||||
public bool IsMandatory;
|
||||
|
||||
private Md5Hash? cachedHash;
|
||||
private DateTime? cachedDateTime;
|
||||
|
||||
@@ -130,9 +288,12 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
Name = contentPackage.Name;
|
||||
Hash = contentPackage.Hash;
|
||||
WorkshopId = contentPackage.SteamWorkshopId;
|
||||
UgcId = contentPackage.UgcId.TryUnwrap(out var ugcId)
|
||||
? ugcId.StringRepresentation
|
||||
: "";
|
||||
IsMandatory = !contentPackage.Files.All(f => f is SubmarineFile);
|
||||
InstallTimeDiffInSeconds =
|
||||
contentPackage.InstallTime is { } installTime
|
||||
contentPackage.InstallTime.TryUnwrap(out var installTime)
|
||||
? (uint)(installTime - referenceTime).TotalSeconds
|
||||
: 0;
|
||||
}
|
||||
|
||||
@@ -333,10 +333,14 @@ namespace Barotrauma.Networking
|
||||
RespawnCharactersProjSpecific(shuttlePos);
|
||||
}
|
||||
|
||||
public static AfflictionPrefab GetRespawnPenaltyAfflictionPrefab()
|
||||
{
|
||||
return AfflictionPrefab.Prefabs.First(a => a.AfflictionType == "respawnpenalty");
|
||||
}
|
||||
|
||||
public static Affliction GetRespawnPenaltyAffliction()
|
||||
{
|
||||
var respawnPenaltyAffliction = AfflictionPrefab.Prefabs.First(a => a.AfflictionType == "respawnpenalty");
|
||||
return respawnPenaltyAffliction?.Instantiate(10.0f);
|
||||
return GetRespawnPenaltyAfflictionPrefab()?.Instantiate(10.0f);
|
||||
}
|
||||
|
||||
public static void GiveRespawnPenaltyAffliction(Character character)
|
||||
|
||||
@@ -388,11 +388,12 @@ namespace Barotrauma.Networking
|
||||
|
||||
public List<SavedClientPermission> ClientPermissions { get; private set; } = new List<SavedClientPermission>();
|
||||
|
||||
private int tickRate = 20;
|
||||
[Serialize(20, IsPropertySaveable.Yes)]
|
||||
public int TickRate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get { return tickRate; }
|
||||
set { tickRate = MathHelper.Clamp(value, 1, 60); }
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
|
||||
@@ -56,23 +56,5 @@ namespace Barotrauma
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void ResetVotes(IEnumerable<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.ResetVotes();
|
||||
}
|
||||
#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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class Prefab : IDisposable
|
||||
public abstract class Prefab
|
||||
{
|
||||
public readonly static ImmutableHashSet<Type> Types;
|
||||
static Prefab()
|
||||
|
||||
@@ -37,6 +37,14 @@ namespace Barotrauma
|
||||
OnRemoveOverrideFile = onRemoveOverrideFile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with only the OnSort callback provided.
|
||||
/// </summary>
|
||||
public PrefabCollection(Action? onSort) : this()
|
||||
{
|
||||
OnSort = onSort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to be called when calling Add(T prefab, bool override).
|
||||
/// If provided, the method is called only if Add succeeds.
|
||||
|
||||
@@ -4,16 +4,20 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Barotrauma.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class PrefabSelector<T> : IEnumerable<T> where T : notnull, Prefab
|
||||
{
|
||||
private readonly ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
|
||||
|
||||
public T? BasePrefab
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (overrides) { return basePrefabInternal; }
|
||||
using (new ReadLock(rwl)) { return basePrefabInternal; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,51 +25,70 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (overrides) { return activePrefabInternal; }
|
||||
using (new ReadLock(rwl)) { return activePrefabInternal; }
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(T prefab, bool isOverride)
|
||||
{
|
||||
lock (overrides) { AddInternal(prefab, isOverride); }
|
||||
using (new WriteLock(rwl)) { AddInternal(prefab, isOverride); }
|
||||
}
|
||||
|
||||
public void RemoveIfContains(T prefab)
|
||||
{
|
||||
lock (overrides) { RemoveIfContainsInternal(prefab); }
|
||||
using (new WriteLock(rwl)) { RemoveIfContainsInternal(prefab); }
|
||||
}
|
||||
|
||||
public void Remove(T prefab)
|
||||
{
|
||||
lock (overrides) { RemoveInternal(prefab); }
|
||||
using (new WriteLock(rwl)) { RemoveInternal(prefab); }
|
||||
}
|
||||
|
||||
public void RemoveByFile(ContentFile file, Action<T>? callback = null)
|
||||
{
|
||||
lock (overrides) { RemoveByFileInternal(file, callback); }
|
||||
var removed = new List<T>();
|
||||
using (new WriteLock(rwl))
|
||||
{
|
||||
for (int i = overrides.Count-1; i >= 0; i--)
|
||||
{
|
||||
var prefab = overrides[i];
|
||||
if (prefab.ContentFile == file)
|
||||
{
|
||||
RemoveInternal(prefab);
|
||||
removed.Add(prefab);
|
||||
}
|
||||
}
|
||||
|
||||
if (basePrefabInternal is { ContentFile: var baseFile } p && baseFile == file)
|
||||
{
|
||||
RemoveInternal(basePrefabInternal);
|
||||
removed.Add(p);
|
||||
}
|
||||
}
|
||||
if (callback != null) { removed.ForEach(callback); }
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
{
|
||||
lock (overrides) { SortInternal(); }
|
||||
using (new WriteLock(rwl)) { SortInternal(); }
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (overrides) { return isEmptyInternal; }
|
||||
using (new ReadLock(rwl)) { return isEmptyInternal; }
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T prefab)
|
||||
{
|
||||
lock (overrides) { return ContainsInternal(prefab); }
|
||||
using (new ReadLock(rwl)) { return ContainsInternal(prefab); }
|
||||
}
|
||||
|
||||
public bool IsOverride(T prefab)
|
||||
{
|
||||
lock (overrides) { return IsOverrideInternal(prefab); }
|
||||
using (new ReadLock(rwl)) { return IsOverrideInternal(prefab); }
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +96,7 @@ namespace Barotrauma
|
||||
private T? basePrefabInternal;
|
||||
private readonly List<T> overrides = new List<T>();
|
||||
|
||||
private T? activePrefabInternal => overrides.Any() ? overrides.First() : basePrefabInternal;
|
||||
private T? activePrefabInternal => overrides.Count > 0 ? overrides.First() : basePrefabInternal;
|
||||
|
||||
private void AddInternal(T prefab, bool isOverride)
|
||||
{
|
||||
@@ -84,7 +107,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BasePrefab != null)
|
||||
if (basePrefabInternal != null)
|
||||
{
|
||||
string prefabName
|
||||
= prefab is MapEntityPrefab mapEntityPrefab
|
||||
@@ -92,7 +115,7 @@ namespace Barotrauma
|
||||
: $"\"{prefab.Identifier}\"";
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to add the prefab {prefabName} ({prefab.GetType()}) from \"{prefab.ContentPackage?.Name ?? "[NULL]"}\" ({prefab.ContentPackage?.Dir ?? ""}): "
|
||||
+ $"a prefab with the same identifier from \"{ActivePrefab!.ContentPackage?.Name ?? "[NULL]"}\" ({ActivePrefab!.ContentPackage?.Dir ?? ""}) already exists; try overriding");
|
||||
+ $"a prefab with the same identifier from \"{activePrefabInternal!.ContentPackage?.Name ?? "[NULL]"}\" ({activePrefabInternal!.ContentPackage?.Dir ?? ""}) already exists; try overriding");
|
||||
}
|
||||
basePrefabInternal = prefab;
|
||||
}
|
||||
@@ -114,31 +137,12 @@ namespace Barotrauma
|
||||
SortInternal();
|
||||
}
|
||||
|
||||
private void RemoveByFileInternal(ContentFile file, Action<T>? callback)
|
||||
{
|
||||
for (int i = overrides.Count-1; i >= 0; i--)
|
||||
{
|
||||
var prefab = overrides[i];
|
||||
if (prefab.ContentFile == file)
|
||||
{
|
||||
RemoveInternal(prefab);
|
||||
callback?.Invoke(prefab);
|
||||
}
|
||||
}
|
||||
|
||||
if (basePrefabInternal is { ContentFile: var baseFile } p && baseFile == file)
|
||||
{
|
||||
RemoveInternal(basePrefabInternal);
|
||||
callback?.Invoke(p);
|
||||
}
|
||||
}
|
||||
|
||||
private void SortInternal()
|
||||
{
|
||||
overrides.Sort((p1, p2) => (p1.ContentPackage?.Index ?? int.MaxValue) - (p2.ContentPackage?.Index ?? int.MaxValue));
|
||||
}
|
||||
|
||||
private bool isEmptyInternal => basePrefabInternal is null && !overrides.Any();
|
||||
private bool isEmptyInternal => basePrefabInternal is null && overrides.Count == 0;
|
||||
|
||||
private bool ContainsInternal(T prefab) => basePrefabInternal == prefab || overrides.Contains(prefab);
|
||||
|
||||
@@ -153,7 +157,7 @@ namespace Barotrauma
|
||||
{
|
||||
T? basePrefab;
|
||||
ImmutableArray<T> overrideClone;
|
||||
lock (overrides)
|
||||
using (new ReadLock(rwl))
|
||||
{
|
||||
basePrefab = basePrefabInternal;
|
||||
overrideClone = overrides.ToImmutableArray();
|
||||
|
||||
@@ -315,6 +315,25 @@ namespace Barotrauma
|
||||
return floatValue;
|
||||
}
|
||||
|
||||
public static bool TryGetAttributeInt(this XElement element, string name, out int result)
|
||||
{
|
||||
var attribute = element?.GetAttribute(name);
|
||||
result = default;
|
||||
if (attribute == null) { return false; }
|
||||
|
||||
if (int.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var intVal))
|
||||
{
|
||||
result = intVal;
|
||||
return true;
|
||||
}
|
||||
if (float.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var floatVal))
|
||||
{
|
||||
result = (int)floatVal;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int GetAttributeInt(this XElement element, string name, int defaultValue)
|
||||
{
|
||||
var attribute = element?.GetAttribute(name);
|
||||
|
||||
@@ -353,7 +353,7 @@ namespace Barotrauma
|
||||
// Check for duplicate binds when introducing new binds
|
||||
foreach (var defaultBinding in defaultBindings)
|
||||
{
|
||||
if (!savedBindings.ContainsKey(defaultBinding.Key))
|
||||
if (!IsSetToNone(defaultBinding.Value) && !savedBindings.ContainsKey(defaultBinding.Key))
|
||||
{
|
||||
foreach (var savedBinding in savedBindings)
|
||||
{
|
||||
@@ -373,6 +373,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsSetToNone(KeyOrMouse keyOrMouse) => keyOrMouse == Keys.None && keyOrMouse == MouseButton.None;
|
||||
}
|
||||
|
||||
// Clear the old chat binds for configs saved before the introduction of the new chat binds
|
||||
|
||||
@@ -119,8 +119,6 @@ namespace Barotrauma
|
||||
|
||||
if (!HasRequiredConditions(currentTargets)) { return; }
|
||||
|
||||
if (Entity.Spawner != null && Entity.Spawner.IsInRemoveQueue(entity)) { return; }
|
||||
|
||||
switch (delayType)
|
||||
{
|
||||
case DelayTypes.Timer:
|
||||
|
||||
@@ -3,6 +3,7 @@ using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -20,6 +21,16 @@ namespace Barotrauma.Steam
|
||||
public const string PreviewImageName = "PreviewImage.png";
|
||||
public const string DefaultPreviewImagePath = "Content/DefaultWorkshopPreviewImage.png";
|
||||
|
||||
public static bool TryExtractSteamWorkshopId(this ContentPackage contentPackage, [NotNullWhen(true)]out SteamWorkshopId? workshopId)
|
||||
{
|
||||
workshopId = null;
|
||||
if (!contentPackage.UgcId.TryUnwrap(out var ugcId)) { return false; }
|
||||
if (!(ugcId is SteamWorkshopId steamWorkshopId)) { return false; }
|
||||
|
||||
workshopId = steamWorkshopId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static partial class Workshop
|
||||
{
|
||||
private struct ItemEqualityComparer : IEqualityComparer<Steamworks.Ugc.Item>
|
||||
@@ -110,7 +121,10 @@ namespace Barotrauma.Steam
|
||||
{
|
||||
NukeDownload(workshopItem);
|
||||
var toUninstall
|
||||
= ContentPackageManager.WorkshopPackages.Where(p => p.SteamWorkshopId == workshopItem.Id)
|
||||
= ContentPackageManager.WorkshopPackages.Where(p =>
|
||||
p.UgcId.TryUnwrap(out var ugcId)
|
||||
&& ugcId is SteamWorkshopId { Value: var itemId }
|
||||
&& itemId == workshopItem.Id)
|
||||
.ToHashSet();
|
||||
ContentPackageManager.EnabledPackages.DisableMods(toUninstall);
|
||||
toUninstall.Select(p => p.Dir).ForEach(d => Directory.Delete(d));
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using System.Globalization;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -30,18 +31,22 @@ namespace Barotrauma
|
||||
|
||||
public static int LanguageVersion { get; private set; } = 0;
|
||||
|
||||
private readonly static Regex isCJK = new Regex(
|
||||
@"\p{IsHangulJamo}|" +
|
||||
@"\p{IsHiragana}|" +
|
||||
@"\p{IsKatakana}|" +
|
||||
@"\p{IsCJKRadicalsSupplement}|" +
|
||||
@"\p{IsCJKSymbolsandPunctuation}|" +
|
||||
@"\p{IsEnclosedCJKLettersandMonths}|" +
|
||||
@"\p{IsCJKCompatibility}|" +
|
||||
@"\p{IsCJKUnifiedIdeographsExtensionA}|" +
|
||||
@"\p{IsCJKUnifiedIdeographs}|" +
|
||||
@"\p{IsHangulSyllables}|" +
|
||||
@"\p{IsCJKCompatibilityForms}");
|
||||
private static readonly ImmutableArray<Range<int>> CjkRanges = new[]
|
||||
{
|
||||
UnicodeRanges.HangulJamo,
|
||||
UnicodeRanges.Hiragana,
|
||||
UnicodeRanges.Katakana,
|
||||
UnicodeRanges.CjkRadicalsSupplement,
|
||||
UnicodeRanges.CjkSymbolsandPunctuation,
|
||||
UnicodeRanges.EnclosedCjkLettersandMonths,
|
||||
UnicodeRanges.CjkCompatibility,
|
||||
UnicodeRanges.CjkUnifiedIdeographsExtensionA,
|
||||
UnicodeRanges.CjkUnifiedIdeographs,
|
||||
UnicodeRanges.HangulSyllables,
|
||||
UnicodeRanges.CjkCompatibilityForms
|
||||
}.Select(r => new Range<int>(r.FirstCodePoint, r.FirstCodePoint+r.Length-1))
|
||||
.OrderBy(r => r.Start)
|
||||
.ToImmutableArray();
|
||||
|
||||
/// <summary>
|
||||
/// Does the string contain symbols from Chinese, Japanese or Korean languages
|
||||
@@ -54,7 +59,24 @@ namespace Barotrauma
|
||||
public static bool IsCJK(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) { return false; }
|
||||
return isCJK.IsMatch(text);
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
char chr = text[i];
|
||||
for (int j = 0; j < CjkRanges.Length; j++)
|
||||
{
|
||||
var range = CjkRanges[j];
|
||||
|
||||
// If chr < range.Start, we know that it can't
|
||||
// be in any of the following ranges, so let's
|
||||
// not even bother checking them
|
||||
if (chr < range.Start) { break; }
|
||||
|
||||
// This character is in a range, return true
|
||||
if (range.Contains(chr)) { return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -50,10 +50,7 @@ namespace Barotrauma
|
||||
if (level > maxLevel) { maxLevel = level; }
|
||||
|
||||
int price = BasePrice;
|
||||
for (int i = 1; i <= level; i++)
|
||||
{
|
||||
price += (int)(price * MathHelper.Lerp(IncreaseLow, IncreaseHigh, i / (float)maxLevel) / 100);
|
||||
}
|
||||
price += (int)(price * MathHelper.Lerp(IncreaseLow, IncreaseHigh, level / (float)maxLevel) / 100);
|
||||
return location?.GetAdjustedMechanicalCost(price) ?? price;
|
||||
}
|
||||
}
|
||||
@@ -331,8 +328,6 @@ namespace Barotrauma
|
||||
|
||||
public ContentXElement SourceElement { get; }
|
||||
|
||||
private bool disposed;
|
||||
|
||||
public bool SuppressWarnings { get; }
|
||||
|
||||
public bool HideInMenus { get; }
|
||||
@@ -525,18 +520,12 @@ namespace Barotrauma
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
Prefabs.Remove(this);
|
||||
#if CLIENT
|
||||
Sprite?.Remove();
|
||||
Sprite = null;
|
||||
DecorativeSprites.ForEach(sprite => sprite.Remove());
|
||||
targetProperties.Clear();
|
||||
Sprite?.Remove();
|
||||
Sprite = null;
|
||||
DecorativeSprites.ForEach(sprite => sprite.Remove());
|
||||
targetProperties.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,18 @@ namespace Barotrauma
|
||||
public bool IsNone() => this is None<T>;
|
||||
public bool IsSome() => this is Some<T>;
|
||||
|
||||
public bool TryUnwrap(out T outValue)
|
||||
public bool TryUnwrap(out T outValue) => TryUnwrap<T>(out outValue);
|
||||
|
||||
public bool TryUnwrap<T1>(out T1 outValue) where T1 : T
|
||||
{
|
||||
switch (this)
|
||||
{
|
||||
case Some<T> { Value: var value }:
|
||||
case Some<T> { Value: T1 value }:
|
||||
outValue = value;
|
||||
return true;
|
||||
case None<T> _:
|
||||
default:
|
||||
outValue = default!;
|
||||
return false;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,16 +23,16 @@ namespace Barotrauma
|
||||
return cachedNonAbstractTypes[assembly].Where(t => t.IsSubclassOf(typeof(T)));
|
||||
}
|
||||
|
||||
public static Option<T1> ParseDerived<T1>(string str)
|
||||
public static Option<TBase> ParseDerived<TBase, TInput>(TInput input) where TInput : notnull
|
||||
{
|
||||
static Option<T1> none() => Option<T1>.None();
|
||||
static Option<TBase> none() => Option<TBase>.None();
|
||||
|
||||
var derivedTypes = GetDerivedNonAbstract<T1>();
|
||||
var derivedTypes = GetDerivedNonAbstract<TBase>();
|
||||
|
||||
Option<T1> parseOfType(Type t)
|
||||
Option<TBase> parseOfType(Type t)
|
||||
{
|
||||
//every T1 type is expected to have a method with the following signature:
|
||||
// public static Option<T> Parse(string str)
|
||||
//every TBase type is expected to have a method with the following signature:
|
||||
// public static Option<T> Parse(TInput str)
|
||||
var parseFunc = t.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static);
|
||||
if (parseFunc is null) { return none(); }
|
||||
|
||||
@@ -44,14 +44,17 @@ namespace Barotrauma
|
||||
if (returnType.GetGenericTypeDefinition() != typeof(Option<>)) { return none(); }
|
||||
if (returnType.GenericTypeArguments[0] != t) { return none(); }
|
||||
|
||||
//some hacky business to convert from Option<T2> to Option<T1> when we only know T2 at runtime
|
||||
static Option<T1> convert<T2>(Option<T2> option) where T2 : T1
|
||||
=> option.Select(v => (T1)v);
|
||||
Func<Option<T1>, Option<T1>> f = convert;
|
||||
var constructedConverter = f.Method.GetGenericMethodDefinition().MakeGenericMethod(typeof(T1), t);
|
||||
//some hacky business to convert from Option<T2> to Option<TBase> when we only know T2 at runtime
|
||||
static Option<TBase> convert<T2>(Option<T2> option) where T2 : TBase
|
||||
=> option.Select(v => (TBase)v);
|
||||
Func<Option<TBase>, Option<TBase>> f = convert;
|
||||
var genericArgs = f.Method.GetGenericArguments();
|
||||
genericArgs[^1] = t;
|
||||
var constructedConverter =
|
||||
f.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs);
|
||||
|
||||
return constructedConverter.Invoke(null, new object?[] { parseFunc.Invoke(null, new object[] { str }) })
|
||||
as Option<T1> ?? none();
|
||||
return constructedConverter.Invoke(null, new[] { parseFunc.Invoke(null, new object[] { input }) })
|
||||
as Option<TBase> ?? none();
|
||||
}
|
||||
|
||||
return derivedTypes.Select(parseOfType).FirstOrDefault(t => t.IsSome()) ?? none();
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma.Threading
|
||||
{
|
||||
internal readonly ref struct ReadLock
|
||||
{
|
||||
private readonly ReaderWriterLockSlim rwl;
|
||||
public ReadLock(ReaderWriterLockSlim rwl)
|
||||
{
|
||||
this.rwl = rwl;
|
||||
rwl.EnterReadLock();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
rwl.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly ref struct WriteLock
|
||||
{
|
||||
private readonly ReaderWriterLockSlim rwl;
|
||||
public WriteLock(ReaderWriterLockSlim rwl)
|
||||
{
|
||||
this.rwl = rwl;
|
||||
rwl.EnterWriteLock();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
rwl.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user