Unstable 1.8.4.0
This commit is contained in:
@@ -19,7 +19,8 @@ namespace Barotrauma.Networking
|
||||
Order = 8,
|
||||
ServerLog = 9,
|
||||
ServerMessageBox = 10,
|
||||
ServerMessageBoxInGame = 11
|
||||
ServerMessageBoxInGame = 11,
|
||||
Team = 12,
|
||||
}
|
||||
|
||||
public enum PlayerConnectionChangeType { None = 0, Joined = 1, Kicked = 2, Disconnected = 3, Banned = 4 }
|
||||
@@ -50,7 +51,12 @@ namespace Barotrauma.Networking
|
||||
new Color(64, 240, 89), //private
|
||||
new Color(255, 255, 255), //console
|
||||
new Color(255, 255, 255), //messagebox
|
||||
new Color(255, 128, 0) //order
|
||||
new Color(255, 128, 0), //order
|
||||
new Color(), // ServerLog
|
||||
new Color(), // ServerMessageBox
|
||||
new Color(), // ServerMessageBoxInGame
|
||||
//new Color(128, 0, 255), // team
|
||||
new Color(86, 91, 205), // team
|
||||
};
|
||||
|
||||
public readonly string Text;
|
||||
@@ -224,15 +230,17 @@ namespace Barotrauma.Networking
|
||||
|
||||
public static string ApplyDistanceEffect(string text, float garbleAmount)
|
||||
{
|
||||
if (garbleAmount < 0.3f) return text;
|
||||
if (garbleAmount >= 1.0f) return "";
|
||||
if (garbleAmount < 0.3f) { return text; }
|
||||
if (garbleAmount >= 1.0f) { return ""; }
|
||||
|
||||
int startIndex = Math.Max(text.IndexOf(':') + 1, 1);
|
||||
string textWithoutColorTags = RichString.Rich(text).SanitizedValue;
|
||||
|
||||
int startIndex = Math.Max(textWithoutColorTags.IndexOf(':') + 1, 1);
|
||||
|
||||
StringBuilder sb = new StringBuilder(text.Length);
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
for (int i = 0; i < textWithoutColorTags.Length; i++)
|
||||
{
|
||||
sb.Append((i > startIndex && Rand.Range(0.0f, 1.0f) < garbleAmount) ? '-' : text[i]);
|
||||
sb.Append((i > startIndex && Rand.Range(0.0f, 1.0f) < garbleAmount) ? '-' : textWithoutColorTags[i]);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
@@ -10,6 +9,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public string Name;
|
||||
public Identifier PreferredJob;
|
||||
public CharacterTeamType TeamID;
|
||||
public CharacterTeamType PreferredTeam;
|
||||
public UInt16 NameId;
|
||||
public AccountInfo AccountInfo;
|
||||
@@ -51,7 +51,23 @@ namespace Barotrauma.Networking
|
||||
|
||||
public Identifier PreferredJob;
|
||||
|
||||
public CharacterTeamType TeamID;
|
||||
private CharacterTeamType teamID;
|
||||
public CharacterTeamType TeamID
|
||||
{
|
||||
get { return teamID; }
|
||||
set
|
||||
{
|
||||
if (value != teamID)
|
||||
{
|
||||
DebugConsole.Log($"Changed client {Name}'s team to {teamID}.");
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||
}
|
||||
teamID = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterTeamType PreferredTeam;
|
||||
|
||||
|
||||
@@ -83,10 +83,16 @@ namespace Barotrauma.Networking
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null) { return; }
|
||||
|
||||
List.Clear();
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
List.Add(new PermissionPreset(element));
|
||||
var newPermissionPreset = new PermissionPreset(element);
|
||||
var existingPreset = List.FirstOrDefault(p => p.Identifier == newPermissionPreset.Identifier);
|
||||
if (existingPreset != null)
|
||||
{
|
||||
List.Remove(existingPreset);
|
||||
DebugConsole.AddWarning($"The permission preset file {file} contains a permission preset that conflicts with another preset. Overriding the previous preset...");
|
||||
}
|
||||
List.Add(newPermissionPreset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ namespace Barotrauma
|
||||
public readonly Vector2 Position;
|
||||
public readonly Inventory Inventory;
|
||||
public readonly Submarine Submarine;
|
||||
public readonly float Condition;
|
||||
public readonly int Quality;
|
||||
public readonly Option<float> Condition;
|
||||
public readonly Option<int> Quality;
|
||||
|
||||
public bool SpawnIfInventoryFull = true;
|
||||
public bool IgnoreLimbSlots = false;
|
||||
@@ -35,30 +35,29 @@ namespace Barotrauma
|
||||
private readonly Action<Item> onSpawned;
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, Action<Item> onSpawned, float? condition = null, int? quality = null)
|
||||
: this(prefab, onSpawned, condition, quality)
|
||||
{
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Position = worldPosition;
|
||||
Condition = condition ?? prefab.Health;
|
||||
Quality = quality ?? 0;
|
||||
this.onSpawned = onSpawned;
|
||||
}
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, Action<Item> onSpawned, float? condition = null, int? quality = null)
|
||||
: this(prefab, onSpawned, condition, quality)
|
||||
{
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Position = position;
|
||||
Submarine = sub;
|
||||
Condition = condition ?? prefab.Health;
|
||||
Quality = quality ?? 0;
|
||||
this.onSpawned = onSpawned;
|
||||
}
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, Action<Item> onSpawned, float? condition = null, int? quality = null)
|
||||
: this(prefab, onSpawned, condition, quality)
|
||||
{
|
||||
Inventory = inventory;
|
||||
}
|
||||
|
||||
private ItemSpawnInfo(ItemPrefab prefab, Action<Item> onSpawned, float? condition = null, int? quality = null)
|
||||
{
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Inventory = inventory;
|
||||
Condition = condition ?? prefab.Health;
|
||||
Quality = quality ?? 0;
|
||||
Condition = condition.HasValue ? Option<float>.Some(condition.Value) : Option<float>.None();
|
||||
Quality = quality.HasValue ? Option<int>.Some(quality.Value) : Option<int>.None();
|
||||
this.onSpawned = onSpawned;
|
||||
}
|
||||
|
||||
@@ -71,15 +70,15 @@ namespace Barotrauma
|
||||
Item spawnedItem;
|
||||
if (Inventory?.Owner != null)
|
||||
{
|
||||
if (!SpawnIfInventoryFull && !Inventory.CanBePut(Prefab))
|
||||
if (!SpawnIfInventoryFull && !Inventory.CanProbablyBePut(Prefab))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
spawnedItem = new Item(Prefab, Vector2.Zero, null)
|
||||
{
|
||||
Condition = Condition,
|
||||
Quality = Quality
|
||||
};
|
||||
spawnedItem = new Item(Prefab, Inventory.Owner.Position, Inventory.Owner.Submarine);
|
||||
//this needs to be done before attempting to put the item in the inventory,
|
||||
//because the quality and condition may affect whether it can go in the inventory (into an existing stack)
|
||||
SetItemProperties(spawnedItem);
|
||||
|
||||
var slot = Slot != InvSlotType.None ? Slot.ToEnumerable() : spawnedItem.AllowedSlots;
|
||||
if (!Inventory.Owner.Removed && !Inventory.TryPutItem(spawnedItem, null, slot))
|
||||
{
|
||||
@@ -94,18 +93,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
spawnedItem.SetTransform(FarseerPhysics.ConvertUnits.ToSimUnits(Inventory.Owner?.WorldPosition ?? Vector2.Zero), spawnedItem.body?.Rotation ?? 0.0f, findNewHull: false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnedItem = new Item(Prefab, Position, Submarine)
|
||||
{
|
||||
Condition = Condition,
|
||||
Quality = Quality
|
||||
};
|
||||
spawnedItem = new Item(Prefab, Position, Submarine);
|
||||
SetItemProperties(spawnedItem);
|
||||
}
|
||||
return spawnedItem;
|
||||
|
||||
void SetItemProperties(Item spawnedItem)
|
||||
{
|
||||
if (Condition.TryUnwrap(out float condition))
|
||||
{
|
||||
spawnedItem.Condition = condition;
|
||||
}
|
||||
if (Quality.TryUnwrap(out int quality))
|
||||
{
|
||||
spawnedItem.Quality = quality;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSpawned(Entity spawnedItem)
|
||||
@@ -370,9 +377,8 @@ namespace Barotrauma
|
||||
if (IsInRemoveQueue(item) || item.Removed) { return; }
|
||||
|
||||
spawnOrRemoveQueue.Enqueue(item);
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
if (containedItems == null) { return; }
|
||||
foreach (Item containedItem in containedItems)
|
||||
|
||||
foreach (var containedItem in item.ContainedItems)
|
||||
{
|
||||
if (containedItem != null)
|
||||
{
|
||||
|
||||
@@ -93,6 +93,10 @@ namespace Barotrauma.Networking
|
||||
return cursorPositionError *= 0.7f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quantizes the value so it's "as accurate as it can be" when the value is represented using the specified number of bits.
|
||||
/// Relevant e.g. when writing float values into network messages using some specific number of bits.
|
||||
/// </summary>
|
||||
public static Vector2 Quantize(Vector2 value, float min, float max, int numberOfBits)
|
||||
{
|
||||
return new Vector2(
|
||||
@@ -100,15 +104,21 @@ namespace Barotrauma.Networking
|
||||
Quantize(value.Y, min, max, numberOfBits));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quantizes the value so it's "as accurate as it can be" when the value is represented using the specified number of bits.
|
||||
/// Relevant e.g. when writing float values into network messages using some specific number of bits.
|
||||
/// </summary>
|
||||
public static float Quantize(float value, float min, float max, int numberOfBits)
|
||||
{
|
||||
float step = (max - min) / (1 << (numberOfBits + 1));
|
||||
value = MathHelper.Clamp(value, min, max);
|
||||
|
||||
float step = (max - min) / ((1 << numberOfBits) - 1);
|
||||
if (Math.Abs(value) < step + 0.00001f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return MathUtils.RoundTowardsClosest(MathHelper.Clamp(value, min, max), step);
|
||||
return MathUtils.RoundTowardsClosest(value - min, step) + min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-3
@@ -1,9 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class EntityEventException : Exception
|
||||
{
|
||||
public readonly Entity Entity;
|
||||
|
||||
public EntityEventException(string errorMessage, Entity causingEntity, Exception innerException = null) : base(errorMessage, innerException)
|
||||
{
|
||||
Entity = causingEntity;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NetEntityEventManager
|
||||
{
|
||||
public const int MaxEventBufferLength = 1024;
|
||||
@@ -29,7 +38,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception);
|
||||
DebugConsole.ThrowError($"Failed to write an event (ID: {e.ID}) for the entity \"{e.Entity}\"", exception, contentPackage: e.Entity?.ContentPackage);
|
||||
GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(),
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
"Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace.CleanupStackTrace());
|
||||
@@ -37,7 +46,8 @@ namespace Barotrauma.Networking
|
||||
//write an empty event to avoid messing up IDs
|
||||
//(otherwise the clients might read the next event in the message and think its ID
|
||||
//is consecutive to the previous one, even though we skipped over this broken event)
|
||||
tempBuffer.WriteUInt16(Entity.NullEntityID);
|
||||
tempBuffer.WriteUInt16(Entity.NullEntityID);
|
||||
tempBuffer.WriteVariableUInt32(0); //size of the event
|
||||
eventCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -12,18 +12,23 @@ namespace Barotrauma.Networking
|
||||
UPDATE_INGAME, //update state ingame
|
||||
|
||||
SERVER_SETTINGS, //change server settings
|
||||
|
||||
SERVER_SETTINGS_PERKS, //change disembark perks (has different permissions from the rest of server settings)
|
||||
|
||||
CAMPAIGN_SETUP_INFO,
|
||||
|
||||
FILE_REQUEST, //request a (submarine) file from the server
|
||||
|
||||
VOICE,
|
||||
|
||||
|
||||
PING_RESPONSE,
|
||||
|
||||
RESPONSE_CANCEL_STARTGAME, //tell the server you do not wish to start with the given warnings active
|
||||
|
||||
RESPONSE_STARTGAME, //tell the server whether you're ready to start
|
||||
SERVER_COMMAND, //tell the server to end a round or kick/ban someone (special permissions required)
|
||||
|
||||
ENDROUND_SELF, //the client wants to end the round for themselves only and return to the lobby
|
||||
|
||||
EVENTMANAGER_RESPONSE,
|
||||
|
||||
REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization
|
||||
@@ -39,7 +44,10 @@ namespace Barotrauma.Networking
|
||||
CIRCUITBOX,
|
||||
READY_CHECK,
|
||||
READY_TO_SPAWN,
|
||||
TAKEOVERBOT
|
||||
TAKEOVERBOT,
|
||||
TOGGLE_RESERVE_BENCH,
|
||||
|
||||
REQUEST_BACKUP_INDICES // client wants a list of available backups for a save file
|
||||
}
|
||||
|
||||
enum ClientNetSegment
|
||||
@@ -81,6 +89,8 @@ namespace Barotrauma.Networking
|
||||
CLIENT_PINGS, //tell the client the pings of all other clients
|
||||
|
||||
QUERY_STARTGAME, //ask the clients whether they're ready to start
|
||||
WARN_STARTGAME, //round is about to start with invalid (perk) settings, warn the clients before starting
|
||||
CANCEL_STARTGAME, //someone requested the round start to be cancelled due to invalid settings, tell the other clients
|
||||
STARTGAME, //start a new round
|
||||
STARTGAMEFINALIZE, //finalize round initialization
|
||||
ENDGAME,
|
||||
@@ -92,7 +102,9 @@ namespace Barotrauma.Networking
|
||||
MEDICAL, //medical clinic
|
||||
CIRCUITBOX,
|
||||
MONEY,
|
||||
READY_CHECK //start, end and update a ready check
|
||||
READY_CHECK, //start, end and update a ready check
|
||||
|
||||
SEND_BACKUP_INDICES // the server sends a list of available backups for a save file
|
||||
}
|
||||
enum ServerNetSegment
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma.Networking
|
||||
/// </summary>
|
||||
public OrderChatMessage(Order order, Character targetCharacter, Character sender, bool isNewOrder = true)
|
||||
: this(order,
|
||||
order?.GetChatMessage(targetCharacter?.Name,
|
||||
order?.GetChatMessage(targetCharacter?.DisplayName,
|
||||
(order.TargetEntity as Hull ?? sender?.CurrentHull)?.DisplayName?.Value,
|
||||
givingOrderToSelf: targetCharacter == sender, orderOption: order.Option, isNewOrder: isNewOrder),
|
||||
targetCharacter, sender, isNewOrder)
|
||||
@@ -51,7 +51,7 @@ namespace Barotrauma.Networking
|
||||
=> entity switch
|
||||
{
|
||||
null => null,
|
||||
Character character => character.Name,
|
||||
Character character => character.DisplayName,
|
||||
Item it => it.Name,
|
||||
_ => throw new ArgumentException("Entity is not a character or item", nameof(entity))
|
||||
};
|
||||
|
||||
+2
@@ -5,4 +5,6 @@ namespace Barotrauma.Networking;
|
||||
sealed class EosP2PConnection : P2PConnection<EosP2PEndpoint>
|
||||
{
|
||||
public EosP2PConnection(EosP2PEndpoint endpoint) : base(endpoint) { }
|
||||
public override bool AddressMatches(NetworkConnection other)
|
||||
=> Endpoint == other.Endpoint;
|
||||
}
|
||||
|
||||
+5
@@ -10,5 +10,10 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
NetConnection = netConnection;
|
||||
}
|
||||
|
||||
public override bool AddressMatches(NetworkConnection other)
|
||||
=> other is LidgrenConnection { Endpoint: LidgrenEndpoint otherEndpoint }
|
||||
&& Endpoint is LidgrenEndpoint endpoint
|
||||
&& endpoint.Address == otherEndpoint.Address;
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -38,6 +38,11 @@ namespace Barotrauma.Networking
|
||||
public bool EndpointMatches(Endpoint endPoint)
|
||||
=> Endpoint == endPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Similar to EndpointMatches but ignores port on LidgrenEndpoint
|
||||
/// </summary>
|
||||
public abstract bool AddressMatches(NetworkConnection other);
|
||||
|
||||
public NetworkConnectionStatus Status = NetworkConnectionStatus.Disconnected;
|
||||
|
||||
public void SetAccountInfo(AccountInfo newInfo)
|
||||
|
||||
+3
@@ -29,6 +29,9 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
SetAccountInfo(new AccountInfo(accountId));
|
||||
}
|
||||
|
||||
public override bool AddressMatches(NetworkConnection other)
|
||||
=> other is PipeConnection;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -3,7 +3,10 @@
|
||||
sealed class SteamP2PConnection : P2PConnection<SteamP2PEndpoint>
|
||||
{
|
||||
public SteamP2PConnection(SteamId steamId) : this(new SteamP2PEndpoint(steamId)) { }
|
||||
|
||||
|
||||
public SteamP2PConnection(SteamP2PEndpoint endpoint) : base(endpoint) { }
|
||||
|
||||
public override bool AddressMatches(NetworkConnection other)
|
||||
=> Endpoint == other.Endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
@@ -23,8 +24,6 @@ namespace Barotrauma.Networking
|
||||
/// </summary>
|
||||
public static float SkillLossPercentageOnImmediateRespawn => GameMain.NetworkMember?.ServerSettings?.SkillLossPercentageOnImmediateRespawn ?? 10.0f;
|
||||
|
||||
public static RespawnMode RespawnMode => GameMain.NetworkMember?.ServerSettings?.RespawnMode ?? RespawnMode.MidRound;
|
||||
|
||||
public static bool UseDeathPrompt
|
||||
{
|
||||
get
|
||||
@@ -41,115 +40,161 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
private readonly NetworkMember networkMember;
|
||||
private readonly Steering shuttleSteering;
|
||||
private readonly List<Door> shuttleDoors;
|
||||
private readonly ItemContainer respawnContainer;
|
||||
private readonly Dictionary<CharacterTeamType, List<Steering>> shuttleSteering = new Dictionary<CharacterTeamType, List<Steering>>();
|
||||
private readonly Dictionary<CharacterTeamType, List<Door>> shuttleDoors = new Dictionary<CharacterTeamType, List<Door>>();
|
||||
private readonly Dictionary<CharacterTeamType, List<ItemContainer>> respawnContainers = new Dictionary<CharacterTeamType, List<ItemContainer>>();
|
||||
|
||||
//items created during respawn
|
||||
//any respawn items left in the shuttle are removed when the shuttle despawns
|
||||
private readonly List<Item> respawnItems = new List<Item>();
|
||||
private class TeamSpecificState
|
||||
{
|
||||
public readonly CharacterTeamType TeamID;
|
||||
|
||||
//characters who spawned during the last respawn
|
||||
private readonly List<Character> respawnedCharacters = new List<Character>();
|
||||
public State State;
|
||||
public readonly List<Character> RespawnedCharacters = new List<Character>();
|
||||
/// <summary>
|
||||
/// When will the shuttle be dispatched with respawned characters
|
||||
/// </summary>
|
||||
public DateTime RespawnTime;
|
||||
/// <summary>
|
||||
/// When will the sub start heading back out of the level
|
||||
/// </summary>
|
||||
public DateTime ReturnTime;
|
||||
public DateTime DespawnTime;
|
||||
public bool RespawnCountdownStarted;
|
||||
public bool ReturnCountdownStarted;
|
||||
|
||||
public int PendingRespawnCount, RequiredRespawnCount;
|
||||
public int PrevPendingRespawnCount, PrevRequiredRespawnCount;
|
||||
|
||||
public State CurrentState;
|
||||
|
||||
//items created during respawn
|
||||
//any respawn items left in the shuttle are removed when the shuttle despawns
|
||||
public readonly List<Item> RespawnItems = new List<Item>();
|
||||
|
||||
public TeamSpecificState(CharacterTeamType teamID)
|
||||
{
|
||||
TeamID = teamID;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<CharacterTeamType, TeamSpecificState> teamSpecificStates = new Dictionary<CharacterTeamType, TeamSpecificState>();
|
||||
|
||||
public bool UsingShuttle
|
||||
{
|
||||
get { return RespawnShuttle != null; }
|
||||
get { return respawnShuttles.Any(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When will the shuttle be dispatched with respawned characters
|
||||
/// </summary>
|
||||
public DateTime RespawnTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// When will the sub start heading back out of the level
|
||||
/// </summary>
|
||||
public DateTime ReturnTime { get; private set; }
|
||||
|
||||
public bool RespawnCountdownStarted
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool ReturnCountdownStarted
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public State CurrentState { get; private set; }
|
||||
|
||||
private float maxTransportTime;
|
||||
|
||||
private float updateReturnTimer;
|
||||
|
||||
public bool CanRespawnAgain =>
|
||||
/*can never respawn again if we're currently transporting and transport time is set to be infinite*/
|
||||
!(CurrentState == State.Transporting && maxTransportTime <= 0.0f);
|
||||
public bool CanRespawnAgain(CharacterTeamType team)
|
||||
{
|
||||
if (teamSpecificStates.TryGetValue(team, out var state))
|
||||
{
|
||||
return state.CurrentState == State.Transporting && maxTransportTime <= 0.0f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Submarine RespawnShuttle { get; private set; }
|
||||
private Dictionary<CharacterTeamType, Submarine> respawnShuttles = new Dictionary<CharacterTeamType, Submarine>();
|
||||
|
||||
public IEnumerable<Submarine> RespawnShuttles => respawnShuttles.Values;
|
||||
|
||||
public RespawnManager(NetworkMember networkMember, SubmarineInfo shuttleInfo)
|
||||
: base(null, Entity.RespawnManagerID)
|
||||
{
|
||||
this.networkMember = networkMember;
|
||||
|
||||
if (shuttleInfo != null && networkMember.ServerSettings is not { RespawnMode: RespawnMode.Permadeath })
|
||||
teamSpecificStates = new Dictionary<CharacterTeamType, TeamSpecificState>();
|
||||
int teamCount = GameMain.GameSession?.GameMode is PvPMode ? 2 : 1;
|
||||
|
||||
if (Level.Loaded == null)
|
||||
{
|
||||
RespawnShuttle = new Submarine(shuttleInfo, true);
|
||||
RespawnShuttle.PhysicsBody.FarseerBody.OnCollision += OnShuttleCollision;
|
||||
//set crush depth slightly deeper than the main sub's
|
||||
RespawnShuttle.SetCrushDepth(Math.Max(RespawnShuttle.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth * 1.2f));
|
||||
throw new InvalidOperationException("Attempted to instantiate a respawn manager before a level was loaded.");
|
||||
}
|
||||
|
||||
//prevent wifi components from communicating between the respawn shuttle and other subs
|
||||
List<WifiComponent> wifiComponents = new List<WifiComponent>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == RespawnShuttle) { wifiComponents.AddRange(item.GetComponents<WifiComponent>()); }
|
||||
}
|
||||
foreach (WifiComponent wifiComponent in wifiComponents)
|
||||
{
|
||||
wifiComponent.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
}
|
||||
bool shouldLoadShuttle =
|
||||
shuttleInfo != null &&
|
||||
!Level.Loaded.ShouldSpawnCrewInsideOutpost();
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
shuttleDoors = new List<Door>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != RespawnShuttle) { continue; }
|
||||
respawnShuttles.Clear();
|
||||
List<WifiComponent> wifiComponents = new List<WifiComponent>();
|
||||
for (int i = 0; i < teamCount; i++)
|
||||
{
|
||||
var teamId = i == 0 ? CharacterTeamType.Team1 : CharacterTeamType.Team2;
|
||||
teamSpecificStates.Add(teamId, new TeamSpecificState(teamId));
|
||||
|
||||
if (item.HasTag(Tags.RespawnContainer))
|
||||
if (shouldLoadShuttle)
|
||||
{
|
||||
shuttleDoors.Add(teamId, new List<Door>());
|
||||
shuttleSteering.Add(teamId, new List<Steering>());
|
||||
respawnContainers.Add(teamId, new List<ItemContainer>());
|
||||
|
||||
var respawnShuttle = new Submarine(shuttleInfo, true);
|
||||
if (teamId == CharacterTeamType.Team2)
|
||||
{
|
||||
respawnContainer = item.GetComponent<ItemContainer>();
|
||||
respawnShuttle.FlipX();
|
||||
}
|
||||
|
||||
var steering = item.GetComponent<Steering>();
|
||||
if (steering != null) { shuttleSteering = steering; }
|
||||
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null) { shuttleDoors.Add(door); }
|
||||
|
||||
//lock all wires to prevent the players from messing up the electronics
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null)
|
||||
respawnShuttles.Add(teamId, respawnShuttle);
|
||||
respawnShuttle.PhysicsBody.FarseerBody.OnCollision += OnShuttleCollision;
|
||||
//set crush depth slightly deeper than the main sub's
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
foreach (Connection connection in connectionPanel.Connections)
|
||||
respawnShuttle.SetCrushDepth(Math.Max(respawnShuttle.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth * 1.2f));
|
||||
}
|
||||
|
||||
//prevent wifi components from communicating between the respawn shuttle and other subs
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == respawnShuttle) { wifiComponents.AddRange(item.GetComponents<WifiComponent>()); }
|
||||
}
|
||||
foreach (WifiComponent wifiComponent in wifiComponents)
|
||||
{
|
||||
wifiComponent.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
}
|
||||
|
||||
ResetShuttle(teamSpecificStates[teamId]);
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != respawnShuttle) { continue; }
|
||||
|
||||
if (item.HasTag(Tags.RespawnContainer))
|
||||
{
|
||||
foreach (Wire wire in connection.Wires)
|
||||
if (GameMain.GameSession?.Missions != null)
|
||||
{
|
||||
if (wire != null) wire.Locked = true;
|
||||
foreach (var mission in GameMain.GameSession.Missions)
|
||||
{
|
||||
//append the mission type so respawn gear can be configured per mission type (e.g. respawncontainer_kingofthehull)
|
||||
item.AddTag(Tags.RespawnContainer.AppendIfMissing("_" + mission.Prefab.Type));
|
||||
}
|
||||
}
|
||||
respawnContainers[teamId].Add(item.GetComponent<ItemContainer>());
|
||||
}
|
||||
|
||||
var steering = item.GetComponent<Steering>();
|
||||
if (steering != null) { shuttleSteering[teamId].Add(steering); }
|
||||
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null) { shuttleDoors[teamId].Add(door); }
|
||||
|
||||
//lock all wires to prevent the players from messing up the electronics
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null)
|
||||
{
|
||||
foreach (Connection connection in connectionPanel.Connections)
|
||||
{
|
||||
foreach (Wire wire in connection.Wires)
|
||||
{
|
||||
if (wire != null) { wire.Locked = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RespawnShuttle = null;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (networkMember is GameServer server)
|
||||
@@ -161,105 +206,108 @@ namespace Barotrauma.Networking
|
||||
|
||||
private bool OnShuttleCollision(Fixture sender, Fixture other, Contact contact)
|
||||
{
|
||||
if (sender.Body.UserData is not Submarine sub || !teamSpecificStates.ContainsKey(sub.TeamID)) { return true; }
|
||||
//ignore collisions with the top barrier when returning
|
||||
return CurrentState != State.Returning || other?.Body != Level.Loaded?.TopBarrier;
|
||||
return teamSpecificStates[sub.TeamID].CurrentState != State.Returning || other?.Body != Level.Loaded?.TopBarrier;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (RespawnShuttle == null)
|
||||
foreach (var teamSpecificState in teamSpecificStates.Values)
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
if (RespawnShuttles.None())
|
||||
{
|
||||
CurrentState = State.Waiting;
|
||||
if (teamSpecificState.CurrentState != State.Waiting)
|
||||
{
|
||||
teamSpecificState.CurrentState = State.Waiting;
|
||||
}
|
||||
}
|
||||
switch (teamSpecificState.CurrentState)
|
||||
{
|
||||
case State.Waiting:
|
||||
UpdateWaiting(teamSpecificState);
|
||||
break;
|
||||
case State.Transporting:
|
||||
UpdateTransporting(teamSpecificState, deltaTime);
|
||||
break;
|
||||
case State.Returning:
|
||||
UpdateReturning(teamSpecificState, deltaTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (CurrentState)
|
||||
{
|
||||
case State.Waiting:
|
||||
UpdateWaiting(deltaTime);
|
||||
break;
|
||||
case State.Transporting:
|
||||
UpdateTransporting(deltaTime);
|
||||
break;
|
||||
case State.Returning:
|
||||
UpdateReturning(deltaTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateWaiting(float deltaTime);
|
||||
partial void UpdateWaiting(TeamSpecificState teamSpecificState);
|
||||
|
||||
private void UpdateTransporting(float deltaTime)
|
||||
private void UpdateTransporting(TeamSpecificState teamSpecificState, float deltaTime)
|
||||
{
|
||||
//infinite transport time -> shuttle wont return
|
||||
if (maxTransportTime <= 0.0f) return;
|
||||
UpdateTransportingProjSpecific(deltaTime);
|
||||
UpdateTransportingProjSpecific(teamSpecificState, deltaTime);
|
||||
}
|
||||
|
||||
partial void UpdateTransportingProjSpecific(float deltaTime);
|
||||
partial void UpdateTransportingProjSpecific(TeamSpecificState teamSpecificState, float deltaTime);
|
||||
|
||||
public void ForceRespawn()
|
||||
{
|
||||
ResetShuttle();
|
||||
RespawnCountdownStarted = true;
|
||||
RespawnTime = DateTime.Now;
|
||||
CurrentState = State.Waiting;
|
||||
foreach (var teamSpecificState in teamSpecificStates.Values)
|
||||
{
|
||||
if (teamSpecificState.CurrentState == State.Transporting) { continue; }
|
||||
ResetShuttle(teamSpecificState);
|
||||
teamSpecificState.RespawnCountdownStarted = true;
|
||||
teamSpecificState.RespawnTime = DateTime.Now;
|
||||
teamSpecificState.CurrentState = State.Waiting;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateReturning(float deltaTime)
|
||||
private void UpdateReturning(TeamSpecificState teamSpecificState, float deltaTime)
|
||||
{
|
||||
updateReturnTimer += deltaTime;
|
||||
if (updateReturnTimer > 1.0f)
|
||||
{
|
||||
updateReturnTimer = 0.0f;
|
||||
shuttleSteering?.SetDestinationLevelStart();
|
||||
UpdateReturningProjSpecific(deltaTime);
|
||||
shuttleSteering[teamSpecificState.TeamID].ForEach(steering => steering.SetDestinationLevelStart());
|
||||
UpdateReturningProjSpecific(teamSpecificState, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateReturningProjSpecific(float deltaTime);
|
||||
|
||||
private IEnumerable<CoroutineStatus> ForceShuttleToPos(Vector2 position, float speed)
|
||||
partial void UpdateReturningProjSpecific(TeamSpecificState teamSpecificState, float deltaTime);
|
||||
|
||||
private Submarine GetShuttle(CharacterTeamType team)
|
||||
{
|
||||
if (RespawnShuttle == null)
|
||||
if (respawnShuttles.TryGetValue(team, out Submarine sub))
|
||||
{
|
||||
yield return CoroutineStatus.Success;
|
||||
return sub;
|
||||
}
|
||||
|
||||
while (Math.Abs(position.Y - RespawnShuttle.WorldPosition.Y) > 100.0f)
|
||||
{
|
||||
Vector2 diff = position - RespawnShuttle.WorldPosition;
|
||||
if (diff.LengthSquared() > 0.01f)
|
||||
{
|
||||
Vector2 displayVel = Vector2.Normalize(diff) * speed;
|
||||
RespawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel);
|
||||
}
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
if (RespawnShuttle.SubBody == null) yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ResetShuttle()
|
||||
private void SetShuttleBodyType(CharacterTeamType team, BodyType bodyType)
|
||||
{
|
||||
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
|
||||
var shuttle = GetShuttle(team);
|
||||
if (shuttle != null)
|
||||
{
|
||||
shuttle.PhysicsBody.BodyType = bodyType;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetShuttle(TeamSpecificState teamSpecificState)
|
||||
{
|
||||
teamSpecificState.ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
|
||||
|
||||
#if SERVER
|
||||
despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30);
|
||||
teamSpecificState.DespawnTime = teamSpecificState.ReturnTime + new TimeSpan(0, 0, seconds: 30);
|
||||
#endif
|
||||
|
||||
if (RespawnShuttle == null) { return; }
|
||||
var shuttle = GetShuttle(teamSpecificState.TeamID);
|
||||
if (shuttle == null) { return; }
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != RespawnShuttle) { continue; }
|
||||
if (item.Submarine != shuttle) { continue; }
|
||||
|
||||
//remove respawn items that have been left in the shuttle
|
||||
if (respawnItems.Contains(item) || respawnContainer?.Item != null && item.IsOwnedBy(respawnContainer.Item))
|
||||
if (teamSpecificState.RespawnItems.Contains(item) ||
|
||||
respawnContainers[teamSpecificState.TeamID].Any(container => item.IsOwnedBy(container.Item)))
|
||||
{
|
||||
Spawner.AddItemToRemoveQueue(item);
|
||||
continue;
|
||||
@@ -294,11 +342,11 @@ namespace Barotrauma.Networking
|
||||
#endif
|
||||
}
|
||||
}
|
||||
respawnItems.Clear();
|
||||
teamSpecificState.RespawnItems.Clear();
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != RespawnShuttle) { continue; }
|
||||
if (wall.Submarine != shuttle) { continue; }
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.AddDamage(i, -100000.0f);
|
||||
@@ -307,7 +355,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != RespawnShuttle) { continue; }
|
||||
if (hull.Submarine != shuttle) { continue; }
|
||||
hull.OxygenPercentage = 100.0f;
|
||||
hull.WaterVolume = 0.0f;
|
||||
hull.BallastFlora?.Remove();
|
||||
@@ -316,8 +364,8 @@ namespace Barotrauma.Networking
|
||||
Dictionary<Character, Vector2> characterPositions = new Dictionary<Character, Vector2>();
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != RespawnShuttle) { continue; }
|
||||
if (!respawnedCharacters.Contains(c))
|
||||
if (c.Submarine != shuttle) { continue; }
|
||||
if (!teamSpecificState.RespawnedCharacters.Contains(c))
|
||||
{
|
||||
characterPositions.Add(c, c.WorldPosition);
|
||||
continue;
|
||||
@@ -338,21 +386,26 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
RespawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height));
|
||||
RespawnShuttle.Velocity = Vector2.Zero;
|
||||
shuttle.SetPosition(new Vector2(
|
||||
teamSpecificState.TeamID == CharacterTeamType.Team1 ? Level.Loaded.StartPosition.X : Level.Loaded.EndPosition.X,
|
||||
Level.Loaded.Size.Y + shuttle.Borders.Height));
|
||||
shuttle.Velocity = Vector2.Zero;
|
||||
|
||||
foreach (var characterPosition in characterPositions)
|
||||
{
|
||||
characterPosition.Key.TeleportTo(characterPosition.Value);
|
||||
}
|
||||
SetShuttleBodyType(teamSpecificState.TeamID, BodyType.Static);
|
||||
}
|
||||
|
||||
public static float GetReducedSkill(CharacterInfo characterInfo, Skill skill, float skillLossPercentage, float? currentSkillLevel = null)
|
||||
{
|
||||
var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier == s.Identifier);
|
||||
float currentLevel = currentSkillLevel ?? skill.Level;
|
||||
if (skillPrefab == null || currentLevel < skillPrefab.LevelRange.End) { return currentLevel; }
|
||||
return MathHelper.Lerp(currentLevel, skillPrefab.LevelRange.End, skillLossPercentage / 100.0f);
|
||||
if (skillPrefab == null) { return currentLevel; }
|
||||
var levelRange = skillPrefab.GetLevelRange(isPvP: GameMain.GameSession?.GameMode is PvPMode);
|
||||
if (currentLevel < levelRange.End) { return currentLevel; }
|
||||
return MathHelper.Lerp(currentLevel, levelRange.End, skillLossPercentage / 100.0f);
|
||||
}
|
||||
|
||||
partial void RespawnCharactersProjSpecific(Vector2? shuttlePos);
|
||||
@@ -380,18 +433,55 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 FindSpawnPos()
|
||||
public Vector2 FindSpawnPos(Submarine respawnShuttle, Submarine mainSub)
|
||||
{
|
||||
if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; }
|
||||
|
||||
Rectangle dockedBorders = RespawnShuttle.GetDockedBorders();
|
||||
Rectangle dockedBorders = respawnShuttle.GetDockedBorders();
|
||||
Vector2 diffFromDockedBorders =
|
||||
new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
|
||||
- new Vector2(RespawnShuttle.Borders.Center.X, RespawnShuttle.Borders.Y - RespawnShuttle.Borders.Height / 2);
|
||||
- new Vector2(respawnShuttle.Borders.Center.X, respawnShuttle.Borders.Y - respawnShuttle.Borders.Height / 2);
|
||||
|
||||
int minWidth = Math.Max(dockedBorders.Width, 1000);
|
||||
int minHeight = Math.Max(dockedBorders.Height, 1000);
|
||||
|
||||
List<Level.InterestingPosition> potentialSpawnPositions = FindValidSpawnPoints(respawnShuttle, minWidth, minHeight, minDistFromSubs: 10000.0f, minDistFromCharacters: 5000.0f);
|
||||
if (potentialSpawnPositions.None())
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to find a shuttle spawn position far away from submarines and characters, attempting to find one closer to to subs and characters...");
|
||||
potentialSpawnPositions = FindValidSpawnPoints(respawnShuttle, minWidth, minHeight, minDistFromSubs: 1000.0f, minDistFromCharacters: 500.0f);
|
||||
if (potentialSpawnPositions.None())
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to find a shuttle spawn position, using the level's start position instead.");
|
||||
return Level.Loaded.StartPosition;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 bestSpawnPos = Level.Loaded.StartPosition;
|
||||
float bestSpawnPosValue = 0.0f;
|
||||
foreach (var potentialSpawnPos in potentialSpawnPositions)
|
||||
{
|
||||
//the closer the spawnpos is to the main sub, the better
|
||||
float spawnPosValue = 100000.0f / Math.Max(Vector2.Distance(potentialSpawnPos.Position.ToVector2(), mainSub.WorldPosition), 1.0f);
|
||||
|
||||
//prefer spawnpoints that are at the left side of the sub (so the shuttle doesn't have to go backwards)
|
||||
if (potentialSpawnPos.Position.X > mainSub.WorldPosition.X)
|
||||
{
|
||||
spawnPosValue *= 0.1f;
|
||||
}
|
||||
|
||||
if (spawnPosValue > bestSpawnPosValue)
|
||||
{
|
||||
bestSpawnPos = potentialSpawnPos.Position.ToVector2();
|
||||
bestSpawnPosValue = spawnPosValue;
|
||||
}
|
||||
}
|
||||
|
||||
return bestSpawnPos;
|
||||
}
|
||||
|
||||
private List<Level.InterestingPosition> FindValidSpawnPoints(Submarine respawnShuttle, float minWidth, float minHeight, float minDistFromSubs, float minDistFromCharacters)
|
||||
{
|
||||
List<Level.InterestingPosition> potentialSpawnPositions = new List<Level.InterestingPosition>();
|
||||
foreach (Level.InterestingPosition potentialSpawnPos in Level.Loaded.PositionsOfInterest.Where(p => p.PositionType == Level.PositionType.MainPath))
|
||||
{
|
||||
@@ -407,12 +497,12 @@ namespace Barotrauma.Networking
|
||||
//make sure there aren't any walls too close
|
||||
var tooCloseCells = Level.Loaded.GetTooCloseCells(potentialSpawnPos.Position.ToVector2(), Math.Max(minWidth, minHeight));
|
||||
if (tooCloseCells.Any()) { continue; }
|
||||
|
||||
|
||||
//make sure the spawnpoint is far enough from other subs
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub == RespawnShuttle || RespawnShuttle.DockedTo.Contains(sub)) { continue; }
|
||||
float minDist = Math.Max(Math.Max(minWidth, minHeight) + Math.Max(sub.Borders.Width, sub.Borders.Height), 10000.0f);
|
||||
if (sub == respawnShuttle || respawnShuttle.DockedTo.Contains(sub)) { continue; }
|
||||
float minDist = Math.Max(Math.Max(minWidth, minHeight) + Math.Max(sub.Borders.Width, sub.Borders.Height), minDistFromSubs);
|
||||
if (Vector2.DistanceSquared(sub.WorldPosition, potentialSpawnPos.Position.ToVector2()) < minDist * minDist)
|
||||
{
|
||||
invalid = true;
|
||||
@@ -433,7 +523,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
//cannot spawn near alive characters (to prevent other players from seeing the shuttle
|
||||
//appear out of nowhere, or monsters from immediatelly wrecking the shuttle)
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, potentialSpawnPos.Position.ToVector2()) < 5000.0f * 5000.0f)
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, potentialSpawnPos.Position.ToVector2()) < minDistFromCharacters * minDistFromCharacters)
|
||||
{
|
||||
invalid = true;
|
||||
break;
|
||||
@@ -444,27 +534,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
potentialSpawnPositions.Add(potentialSpawnPos);
|
||||
}
|
||||
Vector2 bestSpawnPos = new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height);
|
||||
float bestSpawnPosValue = 0.0f;
|
||||
foreach (var potentialSpawnPos in potentialSpawnPositions)
|
||||
{
|
||||
//the closer the spawnpos is to the main sub, the better
|
||||
float spawnPosValue = 100000.0f / Math.Max(Vector2.Distance(potentialSpawnPos.Position.ToVector2(), Submarine.MainSub.WorldPosition), 1.0f);
|
||||
|
||||
//prefer spawnpoints that are at the left side of the sub (so the shuttle doesn't have to go backwards)
|
||||
if (potentialSpawnPos.Position.X > Submarine.MainSub.WorldPosition.X)
|
||||
{
|
||||
spawnPosValue *= 0.1f;
|
||||
}
|
||||
|
||||
if (spawnPosValue > bestSpawnPosValue)
|
||||
{
|
||||
bestSpawnPos = potentialSpawnPos.Position.ToVector2();
|
||||
bestSpawnPosValue = spawnPosValue;
|
||||
}
|
||||
}
|
||||
|
||||
return bestSpawnPos;
|
||||
return potentialSpawnPositions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(SavePath);
|
||||
Directory.CreateDirectory(SavePath, catchUnauthorizedAccessExceptions: false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -179,12 +179,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllLines(filePath, unsavedLines.Select(l => l.Text.SanitizedValue));
|
||||
File.WriteAllLines(filePath, unsavedLines.Select(l => l.Text.SanitizedValue), catchUnauthorizedAccessExceptions: false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving the server log to " + filePath + " failed", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class DoNotSyncOverNetwork : Attribute { }
|
||||
|
||||
public enum SelectionMode
|
||||
{
|
||||
Manual = 0, Random = 1, Vote = 2
|
||||
@@ -32,6 +35,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public enum RespawnMode
|
||||
{
|
||||
None,
|
||||
MidRound,
|
||||
BetweenRounds,
|
||||
Permadeath,
|
||||
@@ -64,6 +68,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
public static readonly string PermissionPresetFile = "Data" + Path.DirectorySeparatorChar + "permissionpresets.xml";
|
||||
public static readonly string PermissionPresetFileCustom = "Data" + Path.DirectorySeparatorChar + "permissionpresets_player.xml";
|
||||
|
||||
public string Name
|
||||
{
|
||||
@@ -280,7 +285,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
HiddenSubs = new HashSet<string>();
|
||||
|
||||
PermissionPreset.List.Clear();
|
||||
PermissionPreset.LoadAll(PermissionPresetFile);
|
||||
PermissionPreset.LoadAll(PermissionPresetFileCustom);
|
||||
InitProjSpecific();
|
||||
|
||||
ServerName = serverName;
|
||||
@@ -300,6 +307,7 @@ namespace Barotrauma.Networking
|
||||
string typeName = SerializableProperty.GetSupportedTypeName(property.PropertyType);
|
||||
if (typeName != null || property.PropertyType.IsEnum)
|
||||
{
|
||||
if (property.GetAttribute<DoNotSyncOverNetwork>() is not null) { continue; }
|
||||
NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName);
|
||||
UInt32 key = ToolBoxCore.IdentifierToUint32Hash(netPropertyData.Name, md5);
|
||||
if (key == 0) { key++; } //0 is reserved to indicate the end of the netproperties section of a message
|
||||
@@ -412,6 +420,19 @@ namespace Barotrauma.Networking
|
||||
set { tickRate = MathHelper.Clamp(value, 1, 60); }
|
||||
}
|
||||
|
||||
private int maxLagCompensation = 150;
|
||||
[Serialize(150, IsPropertySaveable.Yes, description:
|
||||
"Maximum amount of lag compensation for firing weapons, in milliseconds. " +
|
||||
"E.g. when a client fires a gun, the server will be notified about it with some latency, and checks if it hit anything in the past (at the time the shot was taken), up to this limit. " +
|
||||
"The largest allowed lag compensation is 500 milliseconds.")]
|
||||
public int MaxLagCompensation
|
||||
{
|
||||
get { return maxLagCompensation; }
|
||||
set { maxLagCompensation = MathHelper.Clamp(value, 0, 500); }
|
||||
}
|
||||
|
||||
public float MaxLagCompensationSeconds => maxLagCompensation / 1000.0f;
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Do clients need to be authenticated (e.g. based on Steam ID or an EGS ownership token). Can be disabled if you for example want to play the game in a local network without a connection to external services.")]
|
||||
public bool RequireAuthentication
|
||||
{
|
||||
@@ -500,13 +521,16 @@ namespace Barotrauma.Networking
|
||||
/// <summary>
|
||||
/// This is an optional setting for permadeath mode. When it's enabled, a player client whose character dies cannot
|
||||
/// respawn or get a new character in any way in that game (unlike in normal permadeath mode), and can only spectate.
|
||||
/// </summary>
|
||||
/// NOTE: this setting will do nothing if respawn mode is not set to PermaDeath.
|
||||
/// </summary>
|
||||
public bool IronmanMode
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IronmanModeActive => IronmanMode && respawnMode == RespawnMode.Permadeath;
|
||||
|
||||
[Serialize(60.0f, IsPropertySaveable.Yes)]
|
||||
public float AutoRestartInterval
|
||||
{
|
||||
@@ -520,6 +544,20 @@ namespace Barotrauma.Networking
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(PvpTeamSelectionMode.PlayerPreference, IsPropertySaveable.Yes)]
|
||||
public PvpTeamSelectionMode PvpTeamSelectionMode
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int PvpAutoBalanceThreshold
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.8f, IsPropertySaveable.Yes)]
|
||||
public float StartWhenClientsReadyRatio
|
||||
@@ -527,6 +565,41 @@ namespace Barotrauma.Networking
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes)]
|
||||
public float PvPStunResist
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool PvPSpawnMonsters
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool PvPSpawnWrecks
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("Random", IsPropertySaveable.Yes)]
|
||||
public Identifier Biome
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("Random", IsPropertySaveable.Yes)]
|
||||
public Identifier SelectedOutpostName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private bool allowSpectating;
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
@@ -541,6 +614,19 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private bool allowAFK;
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool AllowAFK
|
||||
{
|
||||
get { return allowAFK; }
|
||||
private set
|
||||
{
|
||||
if (allowAFK == value) { return; }
|
||||
allowAFK = value;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool SaveServerLogs
|
||||
{
|
||||
@@ -922,7 +1008,7 @@ namespace Barotrauma.Networking
|
||||
public float KillDisconnectedTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -993,10 +1079,14 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
[Serialize("All", IsPropertySaveable.Yes)]
|
||||
public string MissionType
|
||||
public string MissionTypes
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get => string.Join(",", AllowedRandomMissionTypes.Select(t => t.ToIdentifier()));
|
||||
set
|
||||
{
|
||||
AllowedRandomMissionTypes = value.Split(",").Select(t => t.ToIdentifier()).Distinct().ToList();
|
||||
ValidateMissionTypes();
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(8, IsPropertySaveable.Yes)]
|
||||
@@ -1006,10 +1096,13 @@ namespace Barotrauma.Networking
|
||||
set { maxPlayers = MathHelper.Clamp(value, 1, NetConfig.MaxPlayers); }
|
||||
}
|
||||
|
||||
public List<MissionType> AllowedRandomMissionTypes
|
||||
/// <summary>
|
||||
/// Wrapper for <see cref="MissionTypes"/>.
|
||||
/// </summary>
|
||||
public List<Identifier> AllowedRandomMissionTypes
|
||||
{
|
||||
get;
|
||||
set;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(60f * 60.0f, IsPropertySaveable.Yes)]
|
||||
@@ -1035,6 +1128,24 @@ namespace Barotrauma.Networking
|
||||
[Serialize(0f, IsPropertySaveable.Yes)]
|
||||
public float NewCampaignDefaultSalary { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool TrackOpponentInPvP { get; set; }
|
||||
|
||||
[Serialize(7, IsPropertySaveable.Yes)]
|
||||
public int DisembarkPointAllowance { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes), DoNotSyncOverNetwork]
|
||||
public Identifier[] SelectedCoalitionPerks { get; set; } = Array.Empty<Identifier>();
|
||||
|
||||
/// <summary>
|
||||
/// The score required to win a PvP mission (if it is a mission with some scoring system, such as a deathmatch mission)
|
||||
/// </summary>
|
||||
[Serialize(200, IsPropertySaveable.Yes)]
|
||||
public int WinScorePvP { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes), DoNotSyncOverNetwork]
|
||||
public Identifier[] SelectedSeparatistsPerks { get; set; } = Array.Empty<Identifier>();
|
||||
|
||||
public CampaignSettings CampaignSettings { get; set; } = CampaignSettings.Empty;
|
||||
|
||||
private bool allowSubVoting;
|
||||
@@ -1093,6 +1204,9 @@ namespace Barotrauma.Networking
|
||||
public void SetPassword(string password)
|
||||
{
|
||||
this.password = string.IsNullOrEmpty(password) ? null : password;
|
||||
#if SERVER
|
||||
GameMain.Server?.ClearRecentlyDisconnectedClients();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static byte[] SaltPassword(byte[] password, int salt)
|
||||
@@ -1136,7 +1250,7 @@ namespace Barotrauma.Networking
|
||||
=> monsterEnabled.Keys
|
||||
.OrderBy(k => CharacterPrefab.Prefabs[k].UintIdentifier)
|
||||
.ToImmutableArray();
|
||||
|
||||
|
||||
public bool ReadMonsterEnabled(IReadMessage inc)
|
||||
{
|
||||
bool changed = false;
|
||||
@@ -1214,6 +1328,75 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public void WritePerks(IWriteMessage msg)
|
||||
{
|
||||
List<DisembarkPerkPrefab> coalitionPerks = GetPerks(SelectedCoalitionPerks);
|
||||
|
||||
msg.WriteVariableUInt32((uint)coalitionPerks.Count);
|
||||
foreach (DisembarkPerkPrefab perk in coalitionPerks)
|
||||
{
|
||||
msg.WriteUInt32(perk.UintIdentifier);
|
||||
}
|
||||
|
||||
List<DisembarkPerkPrefab> separatistsPerks = GetPerks(SelectedSeparatistsPerks);
|
||||
msg.WriteVariableUInt32((uint)separatistsPerks.Count);
|
||||
foreach (DisembarkPerkPrefab perk in separatistsPerks)
|
||||
{
|
||||
msg.WriteUInt32(perk.UintIdentifier);
|
||||
}
|
||||
|
||||
static List<DisembarkPerkPrefab> GetPerks(Identifier[] perkIdentifiers)
|
||||
{
|
||||
List<DisembarkPerkPrefab> perks = new();
|
||||
foreach (Identifier perk in perkIdentifiers)
|
||||
{
|
||||
if (!DisembarkPerkPrefab.Prefabs.TryGet(perk, out var prefab)) { continue; }
|
||||
perks.Add(prefab);
|
||||
}
|
||||
return perks;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReadPerks(IReadMessage msg)
|
||||
{
|
||||
uint coalitionCount = msg.ReadVariableUInt32();
|
||||
Identifier[] newCoalitionPerks = new Identifier[coalitionCount];
|
||||
for (int i = 0; i < coalitionCount; i++)
|
||||
{
|
||||
uint id = msg.ReadUInt32();
|
||||
DisembarkPerkPrefab prefab = DisembarkPerkPrefab.Prefabs.Find(p => p.UintIdentifier == id);
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Perk not found: {id}");
|
||||
continue;
|
||||
}
|
||||
|
||||
newCoalitionPerks[i] = prefab.Identifier;
|
||||
}
|
||||
|
||||
uint separatistsCount = msg.ReadVariableUInt32();
|
||||
Identifier[] newSeparatistsPerks = new Identifier[separatistsCount];
|
||||
for (int i = 0; i < separatistsCount; i++)
|
||||
{
|
||||
uint id = msg.ReadUInt32();
|
||||
DisembarkPerkPrefab prefab = DisembarkPerkPrefab.Prefabs.Find(p => p.UintIdentifier == id);
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Perk not found: {id}");
|
||||
continue;
|
||||
}
|
||||
|
||||
newSeparatistsPerks[i] = prefab.Identifier;
|
||||
}
|
||||
|
||||
bool changed = !SelectedCoalitionPerks.SequenceEqual(newCoalitionPerks) ||
|
||||
!SelectedSeparatistsPerks.SequenceEqual(newSeparatistsPerks);
|
||||
|
||||
SelectedCoalitionPerks = newCoalitionPerks;
|
||||
SelectedSeparatistsPerks = newSeparatistsPerks;
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void ReadHiddenSubs(IReadMessage msg)
|
||||
{
|
||||
var subList = GameMain.NetLobbyScreen.GetSubList();
|
||||
@@ -1285,5 +1468,34 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that there's at least one mission type selected for both co-op and PvP game modes.
|
||||
/// </summary>
|
||||
private void ValidateMissionTypes()
|
||||
{
|
||||
ValidateMissionTypes(MissionPrefab.CoOpMissionClasses.Values);
|
||||
ValidateMissionTypes(MissionPrefab.PvPMissionClasses.Values);
|
||||
}
|
||||
|
||||
private void ValidateMissionTypes(IEnumerable<Type> availableMissionClasses)
|
||||
{
|
||||
if (AllowedRandomMissionTypes.Contains(Tags.MissionTypeAll)) { return; }
|
||||
//no selectable mission types that match any of the available mission classes
|
||||
//(e.g. no mission types that use pvp-specific mission classes)
|
||||
if (MissionPrefab.GetAllMultiplayerSelectableMissionTypes().None(missionType =>
|
||||
MissionPrefab.Prefabs.Any(p => p.Type == missionType && AllowedRandomMissionTypes.Contains(p.Type) && availableMissionClasses.Contains(p.MissionClass))))
|
||||
{
|
||||
var matchingMission = MissionPrefab.Prefabs.First(p => availableMissionClasses.Contains(p.MissionClass));
|
||||
if (matchingMission == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"No missions found for any of the available mission classes ({string.Join(",", availableMissionClasses)})");
|
||||
}
|
||||
else
|
||||
{
|
||||
AllowedRandomMissionTypes.Add(matchingMission.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user