Skip to content
4 changes: 2 additions & 2 deletions Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1388,12 +1388,12 @@ async Task gameOwnershipTokenTest()
if (me.SimPosition.Length() > 2000.0f)
{
NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange);
MapEntity.MapEntityList.RemoveAt(i);
MapEntity.MapEntityList.Remove(me);
}
else if (!me.ShouldBeSaved)
{
NewMessage("Removed " + me.Name + " (!ShouldBeSaved)", Color.Orange);
MapEntity.MapEntityList.RemoveAt(i);
MapEntity.MapEntityList.Remove(me);
}
else if (me is Item)
{
Expand Down
12 changes: 6 additions & 6 deletions Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ partial class EventManager

public void DebugDraw(SpriteBatch spriteBatch)
{
foreach (Event ev in activeEvents)
foreach (Event ev in _activeEvents)
{
Vector2 drawPos = ev.DebugDrawPos;
drawPos.Y = -drawPos.Y;
Expand All @@ -41,7 +41,7 @@ public void DebugDraw(SpriteBatch spriteBatch)

public void DebugDrawHUD(SpriteBatch spriteBatch, float y)
{
foreach (ScriptedEvent scriptedEvent in activeEvents.Where(ev => !ev.IsFinished && ev is ScriptedEvent).Cast<ScriptedEvent>())
foreach (ScriptedEvent scriptedEvent in _activeEvents.Where(ev => !ev.IsFinished && ev is ScriptedEvent).Cast<ScriptedEvent>())
{
DrawEventTargetTags(spriteBatch, scriptedEvent);
}
Expand Down Expand Up @@ -156,7 +156,7 @@ void DrawTimeStamps(SpriteBatch sBatch, Color color, Vector2 pos, int order)
{
if (isGraphHovered || isGraphSelected)
{
foreach (var timeStamp in timeStamps)
foreach (var timeStamp in _timeStamps)
{
int t = (int)Math.Abs(Math.Round((timeStamp.Time - lastIntensityUpdate) / intensityGraphUpdateInterval));
if (t == order)
Expand Down Expand Up @@ -205,7 +205,7 @@ void DrawTimeStamps(SpriteBatch sBatch, Color color, Vector2 pos, int order)
}

adjustedYStep = GUI.AdjustForTextScale(12);
foreach (EventSet eventSet in pendingEventSets)
foreach (EventSet eventSet in _pendingEventSets)
{
if (Submarine.MainSub == null) { break; }

Expand Down Expand Up @@ -263,7 +263,7 @@ void DrawTimeStamps(SpriteBatch sBatch, Color color, Vector2 pos, int order)
y += yStep;

adjustedYStep = GUI.AdjustForTextScale(18);
foreach (Event ev in activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown()))
foreach (Event ev in _activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown()))
{
GUI.DrawString(spriteBatch, new Vector2(x + 5, y), ev.ToString(), (!ev.IsFinished ? Color.White : Color.Red) * 0.8f, null, 0, GUIStyle.SmallFont);

Expand Down Expand Up @@ -752,4 +752,4 @@ private static void ClientReadEventObjective(GameClient client, IReadMessage msg
entry.CanBeCompleted);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1933,7 +1933,7 @@ private void DrawMarker(SpriteBatch spriteBatch, string label, Identifier iconId

void CalculateDistance()
{
pathFinder ??= new PathFinder(WayPoint.WayPointList, false);
pathFinder ??= new PathFinder(WayPoint.WayPointList.ToList(), false);
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition));
if (!path.Unreachable)
{
Expand Down
10 changes: 5 additions & 5 deletions Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public static void ForceRemoveFromVisibleEntities(MapEntity entity)

public static void Draw(SpriteBatch spriteBatch, bool editing = false)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();

foreach (MapEntity e in entitiesToRender)
{
Expand All @@ -115,7 +115,7 @@ public static void Draw(SpriteBatch spriteBatch, bool editing = false)

public static void DrawFront(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();

foreach (MapEntity e in entitiesToRender)
{
Expand Down Expand Up @@ -164,7 +164,7 @@ public static void DrawFront(SpriteBatch spriteBatch, bool editing = false, Pred

public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();

depthSortedDamageable.Clear();

Expand Down Expand Up @@ -197,7 +197,7 @@ public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect,

public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();

foreach (MapEntity e in entitiesToRender)
{
Expand All @@ -217,7 +217,7 @@ public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = fal

public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList.ToList();

foreach (MapEntity e in entitiesToRender)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Barotrauma
{
partial class EntitySpawner : Entity, IServerSerializable
{
public readonly List<(Entity entity, bool isRemoval)> receivedEvents = new List<(Entity entity, bool isRemoval)>();
/// <summary>
/// Thread-safe queue for received entity spawn/remove events from the server.
/// </summary>
private readonly ConcurrentQueue<(Entity entity, bool isRemoval)> receivedEventsQueue = new ConcurrentQueue<(Entity entity, bool isRemoval)>();

/// <summary>
/// Gets a thread-safe snapshot of received events.
/// </summary>
public IEnumerable<(Entity entity, bool isRemoval)> GetReceivedEventsSnapshot()
{
return receivedEventsQueue.ToArray();
}

/// <summary>
/// Clears all received events from the queue.
/// </summary>
void ResetReceivedEvents()
{
while (receivedEventsQueue.TryDequeue(out _)) { }
}

public void ClientEventRead(IReadMessage message, float sendingTime)
{
Expand Down Expand Up @@ -34,7 +54,7 @@ public void ClientEventRead(IReadMessage message, float sendingTime)
{
DebugConsole.Log("Received entity removal message for ID " + entityId + ". Entity with a matching ID not found.");
}
receivedEvents.Add((entity, true));
receivedEventsQueue.Enqueue((entity, true));
}
else
{
Expand All @@ -57,7 +77,7 @@ public void ClientEventRead(IReadMessage message, float sendingTime)
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + newItem.Prefab.Identifier);
}
}
receivedEvents.Add((newItem, false));
receivedEventsQueue.Enqueue((newItem, false));
}
break;
case (byte)SpawnableType.Character:
Expand All @@ -68,7 +88,7 @@ public void ClientEventRead(IReadMessage message, float sendingTime)
}
else
{
receivedEvents.Add((character, false));
receivedEventsQueue.Enqueue((character, false));
}
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3916,7 +3916,7 @@ private void WriteEventErrorData(ClientNetError error, UInt16 expectedID, UInt16
{
errorLines.Add("");
errorLines.Add("EntitySpawner events:");
foreach ((Entity entity, bool isRemoval) in Entity.Spawner.receivedEvents)
foreach ((Entity entity, bool isRemoval) in Entity.Spawner.GetReceivedEventsSnapshot())
{
errorLines.Add(
(isRemoval ? "Remove " : "Create ") +
Expand Down
13 changes: 8 additions & 5 deletions Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfD
}
}

// Create snapshot to avoid concurrent access issues during parallel updates
var clients = GameMain.Server.ConnectedClients.ToArray();

if (GameMain.Server is { ServerSettings.RespawnMode: RespawnMode.Permadeath } &&
GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign &&
causeOfDeath != CauseOfDeathType.Disconnected)
{
Client ownerClient = GameMain.Server.ConnectedClients.FirstOrDefault(c => c.Character == this);
Client ownerClient = clients.FirstOrDefault(c => c.Character == this);
if (ownerClient != null)
{
ownerClient.SpectateOnly = true;
Expand All @@ -51,7 +54,7 @@ partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfD

if (HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter))
{
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
var ownerClient = clients.FirstOrDefault(c => c.Character == this);
if (ownerClient != null)
{
(GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(ownerClient);
Expand All @@ -62,7 +65,7 @@ partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfD

if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this)
{
var owner = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
var owner = clients.FirstOrDefault(c => c.Character == this);
if (owner != null)
{
if (!LuaCsSetup.Instance.Game.overrideTraitors)
Expand All @@ -71,11 +74,11 @@ partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfD
}
}
}
foreach (Client client in GameMain.Server.ConnectedClients)
foreach (Client client in clients)
{
if (client.InGame)
{
client.PendingPositionUpdates.Enqueue(this);
client.TryEnqueuePositionUpdate(this);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,9 @@ public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent
case ControlEventData controlEventData:
Client owner = controlEventData.Owner;
msg.WriteBoolean(owner == c && owner.Character == this);
msg.WriteByte(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.SessionId : (byte)0);
// Create snapshot to avoid concurrent access issues during parallel updates
var connectedClients = GameMain.Server.ConnectedClients.ToArray();
msg.WriteByte(owner != null && owner.Character == this && connectedClients.Contains(owner) ? owner.SessionId : (byte)0);
msg.WriteBoolean(info is { RenamingEnabled: true });
break;
case CharacterStatusEventData statusEventData:
Expand Down Expand Up @@ -742,7 +744,9 @@ public void WriteSpawnData(IWriteMessage msg, UInt16 entityId, bool restrictMess
return;
}

Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this && (!c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating));
// Create snapshot to avoid concurrent access issues during parallel updates
var clients = GameMain.Server.ConnectedClients.ToArray();
Client ownerClient = clients.FirstOrDefault(c => c.Character == this && (!c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating));
if (ownerClient != null)
{
msg.WriteBoolean(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,13 @@ public void IgnoreClient(Client c, float seconds)

private bool IsBlockedByAnotherConversation(IEnumerable<Entity> targets, float duration)
{
// Create snapshot to avoid concurrent access issues during parallel updates
var clients = GameMain.Server.ConnectedClients.ToArray();

if (targets == null || targets.None())
{
//if the action doesn't target anyone in specific, it's shown to every client
foreach (var client in GameMain.Server.ConnectedClients)
foreach (var client in clients)
{
if (IsBlockedByAnotherConversation(client, duration)) { return true; }
}
Expand All @@ -95,7 +98,7 @@ private bool IsBlockedByAnotherConversation(IEnumerable<Entity> targets, float d
foreach (Entity e in targets)
{
if (e is not Character character || !character.IsRemotePlayer) { continue; }
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
Client targetClient = clients.FirstOrDefault(c => c.Character == character);
if (targetClient != null && IsBlockedByAnotherConversation(targetClient, duration)) { return true; }
}
}
Expand All @@ -117,13 +120,16 @@ private bool IsBlockedByAnotherConversation(Client targetClient, float duration)
partial void ShowDialog(Character speaker, Character targetCharacter)
{
targetClients.Clear();
// Create snapshot to avoid concurrent access issues during parallel updates
var clients = GameMain.Server.ConnectedClients.ToArray();

if (!TargetTag.IsEmpty)
{
IEnumerable<Entity> entities = ParentEvent.GetTargets(TargetTag);
foreach (Entity e in entities)
{
if (e is not Character character || !character.IsRemotePlayer) { continue; }
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
Client targetClient = clients.FirstOrDefault(c => c.Character == character);
if (targetClient != null)
{
targetClients.Add(targetClient);
Expand All @@ -135,7 +141,7 @@ partial void ShowDialog(Character speaker, Character targetCharacter)
}
else
{
foreach (Client c in GameMain.Server.ConnectedClients)
foreach (Client c in clients)
{
if (CanClientReceive(c))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,52 @@ namespace Barotrauma;

partial class EventLogAction : EventAction
{
partial void AddEntryProjSpecific(EventLog? eventLog, string displayText)
partial void AddEntryProjSpecific(EventLog? eventLog, string displayText)
{
if (eventLog == null) { return; }

// Create snapshot to avoid concurrent access issues during parallel updates
var clients = GameMain.Server.ConnectedClients.ToArray();

if (!TargetTag.IsEmpty)
{
if (eventLog == null) { return; }
if (!TargetTag.IsEmpty)
List<Client> targetClients = new List<Client>();
foreach (var target in ParentEvent.GetTargets(TargetTag))
{
List<Client> targetClients = new List<Client>();
foreach (var target in ParentEvent.GetTargets(TargetTag))
if (target is Character character)
{
if (target is Character character)
{
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
if (ownerClient != null)
{
targetClients.Add(ownerClient);
}
}
else
var ownerClient = clients.FirstOrDefault(c => c.Character == character);
if (ownerClient != null)
{
DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.",
ParentEvent.Prefab.ContentPackage);
targetClients.Add(ownerClient);
}
}
if (eventLog!.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, targetClients) && ShowInServerLog)
else
{
Log(targetClients);
DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.",
ParentEvent.Prefab.ContentPackage);
}
}
else
if (eventLog!.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, targetClients) && ShowInServerLog)
{
if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, GameMain.Server.ConnectedClients) && ShowInServerLog)
{
Log(targetClients: null);
}
Log(targetClients);
}

void Log(List<Client>? targetClients)
}
else
{
if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, clients) && ShowInServerLog)
{
string clientStr = targetClients == null || targetClients.None() ?
string.Empty :
$" ({string.Join(", ", targetClients.Select(c => NetworkMember.ClientLogName(c)))})";
GameServer.Log($"Event \"{ParentEvent.Prefab.Name}\"{clientStr}: " + displayText,
ParentEvent is TraitorEvent ? ServerLog.MessageType.Traitors : ServerLog.MessageType.Chat);
Log(targetClients: null);
}
}

void Log(List<Client>? targetClients)
{
string clientStr = targetClients == null || targetClients.None() ?
string.Empty :
$" ({string.Join(", ", targetClients.Select(c => NetworkMember.ClientLogName(c)))})";
GameServer.Log($"Event \"{ParentEvent.Prefab.Name}\"{clientStr}: " + displayText,
ParentEvent is TraitorEvent ? ServerLog.MessageType.Traitors : ServerLog.MessageType.Chat);
}
}
}
Loading
Loading